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") +
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
class ProfileXYZ(Profile):
def __init__(self, num: int = 0,
code1: int = 0, code2: int = 0,
def __init__(self,
name: str = "",
kp: float = 0.,
reach = None,
nb_point: int = 0,
kp: float = 0., name: str = "",
reach = None):
code1: int = 0, code2: int = 0):
"""ProfileXYZ constructor
Args:
@ -28,7 +29,7 @@ class ProfileXYZ(Profile):
Nothing.
"""
super(ProfileXYZ, self).__init__(
num = num,
num = 0,
name = name,
kp = kp,
code1 = code1, code2 = code2,
@ -36,6 +37,28 @@ class ProfileXYZ(Profile):
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):
return [point.x for point in self._points]

View File

@ -24,14 +24,19 @@ class WindowToolKit(object):
def __init__(self, parent=None):
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):
clip = QApplication.clipboard()
mime = clip.mimeData()
# print(mime.formats())
data = mime.data('text/plain').data().decode()
if 'text/plain' not in mime.formats():
raise ClipboardFormatError(mime='text/plain')
data = mime.data('text/plain').data().decode()
has_header = csv.Sniffer().has_header(data)
print(f"header? {has_header}")
header = []
values = []

View File

@ -6,7 +6,7 @@ import sys
import time
from copy import deepcopy
from tools import timer
from tools import timer, trace
from PyQt5 import QtWidgets
from PyQt5.QtGui import (
@ -372,18 +372,27 @@ class GeometryWindow(QMainWindow, WindowToolKit):
.selectionModel()\
.selectedRows()
self._clipboard = []
table = []
table.append(["name", "kp"])
for row in rows:
self._clipboard.append(
deepcopy(
self._reach.profile(row.row())
)
profile = self._reach.profile(row.row())
table.append(
[profile.name, profile.kp]
)
self.copyTableIntoClipboard(table)
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()
self._tablemodel.paste(row, self._clipboard)
self._tablemodel.paste(row, header, data)
self.select_current_profile()
def undo(self):

View File

@ -2,6 +2,8 @@
import time
from tools import timer, trace
from PyQt5.QtGui import (
QKeySequence, QColor
)
@ -16,6 +18,7 @@ from PyQt5.QtWidgets import (
)
from Model.Geometry import Reach
from Model.Geometry.ProfileXYZ import ProfileXYZ
from View.Geometry.ReachUndoCommand import *
@ -30,11 +33,16 @@ class TableEditableModel(QAbstractTableModel):
self._undo_stack = undo
self._reach = reach
# Hack for qtlinguist
_ = _translate("Geometry", "Name")
_ = _translate("Geometry", "Kp (m)")
_ = _translate("Geometry", "Type")
if headers is None:
self.headers = [
_translate("Geometry", "Name"),
_translate("Geometry", "Kp (m)"),
_translate("Geometry", "Type")
"Name",
"Kp (m)",
"Type"
]
else:
self.headers = headers
@ -79,7 +87,7 @@ class TableEditableModel(QAbstractTableModel):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
if section < len(self.headers):
return self.headers[section]
return _translate("Geometry", self.headers[section])
else:
return str(section + 1)
@ -204,18 +212,25 @@ class TableEditableModel(QAbstractTableModel):
self.endMoveRows()
self.layoutChanged.emit()
def paste(self, row, profiles):
@trace
def paste(self, row, header, data):
if row > self._reach.number_profiles:
return
if len(profiles) == 0:
if len(data) == 0:
return
self.layoutAboutToBeChanged.emit()
self._undo_stack.push(
PasteCommand(
self._reach, row, profiles
self._reach, row,
list(
map(
lambda d: ProfileXYZ.from_data(header, d),
data
)
)
)
)