mirror of https://gitlab.com/pamhyr/pamhyr2
geometry: Copy/Paste with system clipboard.
parent
d610dfef7e
commit
012f7ce445
|
|
@ -116,3 +116,44 @@ class FileFormatError(ExeceptionWithMessageBox):
|
||||||
_translate("Exception", "format because of") +
|
_translate("Exception", "format because of") +
|
||||||
f" '{self.reason}'"
|
f" '{self.reason}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ClipboardFormatError(ExeceptionWithMessageBox):
|
||||||
|
def __init__(self, mime=None, header=None, data=None):
|
||||||
|
super(ClipboardFormatError, self).__init__(
|
||||||
|
title = _translate("Exception", "Clipboard format error")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mime = mime
|
||||||
|
self.header = header
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
if self.mime is not None:
|
||||||
|
self.msg = f"Impossible to decode data to mime code '{self.mime}'"
|
||||||
|
else:
|
||||||
|
if len(self.header) == 0:
|
||||||
|
msg = _translate("Exception", "without header")
|
||||||
|
else:
|
||||||
|
msg = (
|
||||||
|
_translate("Exception", "with header") +
|
||||||
|
f": {self.header}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.msg = (
|
||||||
|
_translate("Exception", "Invalid clipboard data format:") +
|
||||||
|
f" '{self.data}' {msg}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.alert()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.msg
|
||||||
|
|
||||||
|
def header(self):
|
||||||
|
return _translate("Exception", "Clipboard format error")
|
||||||
|
|
||||||
|
def short_message(self):
|
||||||
|
return _translate("Exception", "Clipboard format unknown")
|
||||||
|
|
||||||
|
def message(self):
|
||||||
|
return self.msg
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,12 @@ from Model.Geometry.PointXYZ import PointXYZ
|
||||||
from Model.Geometry.Vector_1d import Vector1d
|
from Model.Geometry.Vector_1d import Vector1d
|
||||||
|
|
||||||
class ProfileXYZ(Profile):
|
class ProfileXYZ(Profile):
|
||||||
def __init__(self, num: int = 0,
|
def __init__(self,
|
||||||
code1: int = 0, code2: int = 0,
|
name: str = "",
|
||||||
|
kp: float = 0.,
|
||||||
|
reach = None,
|
||||||
nb_point: int = 0,
|
nb_point: int = 0,
|
||||||
kp: float = 0., name: str = "",
|
code1: int = 0, code2: int = 0):
|
||||||
reach = None):
|
|
||||||
"""ProfileXYZ constructor
|
"""ProfileXYZ constructor
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -28,7 +29,7 @@ class ProfileXYZ(Profile):
|
||||||
Nothing.
|
Nothing.
|
||||||
"""
|
"""
|
||||||
super(ProfileXYZ, self).__init__(
|
super(ProfileXYZ, self).__init__(
|
||||||
num = num,
|
num = 0,
|
||||||
name = name,
|
name = name,
|
||||||
kp = kp,
|
kp = kp,
|
||||||
code1 = code1, code2 = code2,
|
code1 = code1, code2 = code2,
|
||||||
|
|
@ -36,6 +37,28 @@ class ProfileXYZ(Profile):
|
||||||
reach = reach,
|
reach = reach,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, header, data):
|
||||||
|
profile = None
|
||||||
|
try:
|
||||||
|
if len(header) == 0:
|
||||||
|
profile = cls(
|
||||||
|
*data
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
valid_header = {'name', 'reach', 'kp'}
|
||||||
|
d = {}
|
||||||
|
for i, v in enumerate(data):
|
||||||
|
h = header[i].strip().lower().split(' ')[0]
|
||||||
|
if h in valid_header:
|
||||||
|
d[h] = v
|
||||||
|
|
||||||
|
profile = cls(**d)
|
||||||
|
except Exception as e:
|
||||||
|
raise ClipboardFormatError(header, data)
|
||||||
|
|
||||||
|
return profile
|
||||||
|
|
||||||
def x(self):
|
def x(self):
|
||||||
return [point.x for point in self._points]
|
return [point.x for point in self._points]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,14 +24,19 @@ class WindowToolKit(object):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super(WindowToolKit, self).__init__()
|
super(WindowToolKit, self).__init__()
|
||||||
|
|
||||||
|
def copyTableIntoClipboard(self, table):
|
||||||
|
stream = StringIO()
|
||||||
|
csv.writer(stream, delimiter='\t').writerows(table)
|
||||||
|
QApplication.clipboard().setText(stream.getvalue())
|
||||||
|
|
||||||
def parseClipboardTable(self):
|
def parseClipboardTable(self):
|
||||||
clip = QApplication.clipboard()
|
clip = QApplication.clipboard()
|
||||||
mime = clip.mimeData()
|
mime = clip.mimeData()
|
||||||
# print(mime.formats())
|
if 'text/plain' not in mime.formats():
|
||||||
data = mime.data('text/plain').data().decode()
|
raise ClipboardFormatError(mime='text/plain')
|
||||||
|
|
||||||
|
data = mime.data('text/plain').data().decode()
|
||||||
has_header = csv.Sniffer().has_header(data)
|
has_header = csv.Sniffer().has_header(data)
|
||||||
print(f"header? {has_header}")
|
|
||||||
|
|
||||||
header = []
|
header = []
|
||||||
values = []
|
values = []
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from tools import timer
|
from tools import timer, trace
|
||||||
|
|
||||||
from PyQt5 import QtWidgets
|
from PyQt5 import QtWidgets
|
||||||
from PyQt5.QtGui import (
|
from PyQt5.QtGui import (
|
||||||
|
|
@ -372,18 +372,27 @@ class GeometryWindow(QMainWindow, WindowToolKit):
|
||||||
.selectionModel()\
|
.selectionModel()\
|
||||||
.selectedRows()
|
.selectedRows()
|
||||||
|
|
||||||
self._clipboard = []
|
table = []
|
||||||
|
table.append(["name", "kp"])
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
self._clipboard.append(
|
profile = self._reach.profile(row.row())
|
||||||
deepcopy(
|
table.append(
|
||||||
self._reach.profile(row.row())
|
[profile.name, profile.kp]
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.copyTableIntoClipboard(table)
|
||||||
|
|
||||||
def paste(self):
|
def paste(self):
|
||||||
|
header, data = self.parseClipboardTable()
|
||||||
|
|
||||||
|
if len(header) != 0:
|
||||||
|
header.append("reach")
|
||||||
|
for row in data:
|
||||||
|
row.append(self._reach)
|
||||||
|
|
||||||
row = self.index_selected_row()
|
row = self.index_selected_row()
|
||||||
self._tablemodel.paste(row, self._clipboard)
|
self._tablemodel.paste(row, header, data)
|
||||||
self.select_current_profile()
|
self.select_current_profile()
|
||||||
|
|
||||||
def undo(self):
|
def undo(self):
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from tools import timer, trace
|
||||||
|
|
||||||
from PyQt5.QtGui import (
|
from PyQt5.QtGui import (
|
||||||
QKeySequence, QColor
|
QKeySequence, QColor
|
||||||
)
|
)
|
||||||
|
|
@ -16,6 +18,7 @@ from PyQt5.QtWidgets import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from Model.Geometry import Reach
|
from Model.Geometry import Reach
|
||||||
|
from Model.Geometry.ProfileXYZ import ProfileXYZ
|
||||||
from View.Geometry.ReachUndoCommand import *
|
from View.Geometry.ReachUndoCommand import *
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -30,11 +33,16 @@ class TableEditableModel(QAbstractTableModel):
|
||||||
self._undo_stack = undo
|
self._undo_stack = undo
|
||||||
self._reach = reach
|
self._reach = reach
|
||||||
|
|
||||||
|
# Hack for qtlinguist
|
||||||
|
_ = _translate("Geometry", "Name")
|
||||||
|
_ = _translate("Geometry", "Kp (m)")
|
||||||
|
_ = _translate("Geometry", "Type")
|
||||||
|
|
||||||
if headers is None:
|
if headers is None:
|
||||||
self.headers = [
|
self.headers = [
|
||||||
_translate("Geometry", "Name"),
|
"Name",
|
||||||
_translate("Geometry", "Kp (m)"),
|
"Kp (m)",
|
||||||
_translate("Geometry", "Type")
|
"Type"
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
self.headers = headers
|
self.headers = headers
|
||||||
|
|
@ -79,7 +87,7 @@ class TableEditableModel(QAbstractTableModel):
|
||||||
if role == Qt.DisplayRole:
|
if role == Qt.DisplayRole:
|
||||||
if orientation == Qt.Horizontal:
|
if orientation == Qt.Horizontal:
|
||||||
if section < len(self.headers):
|
if section < len(self.headers):
|
||||||
return self.headers[section]
|
return _translate("Geometry", self.headers[section])
|
||||||
else:
|
else:
|
||||||
return str(section + 1)
|
return str(section + 1)
|
||||||
|
|
||||||
|
|
@ -204,18 +212,25 @@ class TableEditableModel(QAbstractTableModel):
|
||||||
self.endMoveRows()
|
self.endMoveRows()
|
||||||
self.layoutChanged.emit()
|
self.layoutChanged.emit()
|
||||||
|
|
||||||
def paste(self, row, profiles):
|
@trace
|
||||||
|
def paste(self, row, header, data):
|
||||||
if row > self._reach.number_profiles:
|
if row > self._reach.number_profiles:
|
||||||
return
|
return
|
||||||
|
|
||||||
if len(profiles) == 0:
|
if len(data) == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.layoutAboutToBeChanged.emit()
|
self.layoutAboutToBeChanged.emit()
|
||||||
|
|
||||||
self._undo_stack.push(
|
self._undo_stack.push(
|
||||||
PasteCommand(
|
PasteCommand(
|
||||||
self._reach, row, profiles
|
self._reach, row,
|
||||||
|
list(
|
||||||
|
map(
|
||||||
|
lambda d: ProfileXYZ.from_data(header, d),
|
||||||
|
data
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue