geometry: Copy/Paste with system clipboard.

mesh
Pierre-Antoine Rouby 2023-04-26 16:33:21 +02:00
parent d610dfef7e
commit 012f7ce445
5 changed files with 115 additions and 22 deletions

View File

@ -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

View File

@ -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]

View File

@ -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 = []

View File

@ -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):

View File

@ -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
)
)
) )
) )