diff --git a/src/gui_qt/graphs/draw_inputs.py b/src/gui_qt/graphs/draw_inputs.py new file mode 100644 index 0000000..98e8ca5 --- /dev/null +++ b/src/gui_qt/graphs/draw_inputs.py @@ -0,0 +1,249 @@ +from __future__ import annotations + +from PyQt5 import QtCore, QtGui, QtWidgets + +from gui_qt.lib.delegates import ColorListEditor + + +class ObjectWidget(QtWidgets.QWidget): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.layout = QtWidgets.QGridLayout() + self.layout.setContentsMargins(3, 3, 3, 3) + + self.color_label = QtWidgets.QLabel('Color') + self.layout.addWidget(self.color_label, 0, 0, 1, 1) + self.color_box = ColorListEditor(self) + self.layout.addWidget(self.color_box, 0, 1, 1, 2) + self.setLayout(self.layout) + + def collect_args(self) -> dict: + return {'color': self.color_box.currentData(QtCore.Qt.UserRole)} + + @staticmethod + def parse_point(x_widget: QtWidgets.QLineEdit, y_widget: QtWidgets.QLineEdit) -> None | tuple[float, float]: + x = x_widget.text() + if not x: + return + y = y_widget.text() + if not y: + return + + try: + return float(x), float(y) + except ValueError: + return + + +class LineWidget(ObjectWidget): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.pos_label = QtWidgets.QLabel('Position') + self.layout.addWidget(self.pos_label, 1, 0, 1, 1) + self.pos_lineedit = QtWidgets.QLineEdit() + self.pos_lineedit.setValidator(QtGui.QDoubleValidator()) + self.layout.addWidget(self.pos_lineedit, 1, 1, 1, 2) + + self.orientation_label = QtWidgets.QLabel('Orientation') + self.layout.addWidget(self.orientation_label, 2, 0, 1, 1) + self.orient_combobox = QtWidgets.QComboBox() + self.orient_combobox.addItems(['Horizontal', 'Vertical']) + self.layout.addWidget(self.orient_combobox, 2, 1, 1, 2) + self.layout.setRowStretch(3, 1) + + def collect_args(self): + dic = super().collect_args() + pos = self.pos_lineedit.text() + if not pos: + return + + try: + dic['pos'] = float(pos) + except ValueError: + return + + dic['angle'] = self.orient_combobox.currentIndex() * 90 + + return dic + + +class MultiPointWidget(ObjectWidget): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.loop_checkbox = QtWidgets.QCheckBox('Close loop') + self.layout.addWidget(self.loop_checkbox, 1, 0, 1, 3) + + self.table_widget = QtWidgets.QTableWidget() + self.table_widget.setColumnCount(2) + self.table_widget.setHorizontalHeaderLabels(['x', 'y']) + header = self.table_widget.horizontalHeader() + header.setStretchLastSection(True) + self.layout.addWidget(self.table_widget, 2, 0, 1, 3) + + self.addButton = QtWidgets.QPushButton('Add point') + self.layout.addWidget(self.addButton, 3, 1, 1, 1) + self.layout.setRowStretch(3, 1) + self.addButton.clicked.connect(self.new_point) + + self.removeButton = QtWidgets.QPushButton('Remove point') + self.layout.addWidget(self.removeButton, 3, 2, 1, 1) + self.layout.setRowStretch(3, 1) + self.removeButton.clicked.connect(self.less_point) + + def new_point(self, _): + row = self.table_widget.rowCount() + self.table_widget.setRowCount(row+1) + placeholder = ['x', 'y'] + for column in range(2): + line_edit = QtWidgets.QLineEdit() + line_edit.setFrame(False) + line_edit.setPlaceholderText(placeholder[column]) + line_edit.setValidator(QtGui.QDoubleValidator()) + + self.table_widget.setCellWidget(row, column, line_edit) + + def less_point(self, _): + self.table_widget.removeRow(self.table_widget.rowCount()-1) + + def collect_args(self): + dic = super().collect_args() + + pts = [] + if self.table_widget.rowCount() <= 1: + return + + for row in range(self.table_widget.rowCount()): + next_pt = self.parse_point(self.table_widget.cellWidget(row, 0), self.table_widget.cellWidget(row, 1)) + if next_pt is None: + return + pts.append(next_pt) + + if pts: + dic['pts'] = pts + dic['closed'] = self.loop_checkbox.isChecked() + return dic + else: + return + + +class RectangleWidget(ObjectWidget): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.leftbottom_label = QtWidgets.QLabel('Lower left') + self.layout.addWidget(self.leftbottom_label, 1, 0, 1, 1) + self.left_x = QtWidgets.QLineEdit() + self.left_x.setPlaceholderText('x') + self.layout.addWidget(self.left_x, 1, 1, 1, 1) + self.left_y = QtWidgets.QLineEdit() + self.left_y.setPlaceholderText('y') + self.left_x.setValidator(QtGui.QDoubleValidator()) + self.left_y.setValidator(QtGui.QDoubleValidator()) + self.layout.addWidget(self.left_y, 1, 2, 1, 1) + + self.righttop_label = QtWidgets.QLabel('Upper right') + self.layout.addWidget(self.righttop_label, 2, 0, 1, 1) + self.right_x = QtWidgets.QLineEdit() + self.right_x.setPlaceholderText('x') + self.layout.addWidget(self.right_x, 2, 1, 1, 1) + self.right_y = QtWidgets.QLineEdit() + self.right_y.setPlaceholderText('y') + self.right_x.setValidator(QtGui.QDoubleValidator()) + self.right_y.setValidator(QtGui.QDoubleValidator()) + self.layout.addWidget(self.right_y, 2, 2, 1, 1) + + self.layout.setRowStretch(3, 1) + + def collect_args(self): + dic = super().collect_args() + left = self.parse_point(self.left_x, self.left_y) + if left is None: + return + dic['left'] = left + + right = self.parse_point(self.right_x, self.right_y) + if right is None: + return + dic['right'] = right + + return dic + + +class EllipseWidget(ObjectWidget): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.centre_label = QtWidgets.QLabel('Centre') + self.layout.addWidget(self.centre_label, 1, 0, 1, 1) + self.centre_x = QtWidgets.QLineEdit() + self.centre_x.setPlaceholderText('x') + self.layout.addWidget(self.centre_x, 1, 1, 1, 1) + self.centre_y = QtWidgets.QLineEdit() + self.centre_y.setPlaceholderText('y') + self.centre_x.setValidator(QtGui.QDoubleValidator()) + self.centre_y.setValidator(QtGui.QDoubleValidator()) + self.layout.addWidget(self.centre_y, 1, 2, 1, 1) + + self.axes_label = QtWidgets.QLabel('Axes') + self.layout.addWidget(self.axes_label, 2, 0, 1, 1) + self.width = QtWidgets.QLineEdit() + self.width.setPlaceholderText('width') + self.layout.addWidget(self.width, 2, 1, 1, 1) + self.height = QtWidgets.QLineEdit() + self.height.setPlaceholderText('Height') + self.width.setValidator(QtGui.QDoubleValidator()) + self.width.setValidator(QtGui.QDoubleValidator()) + self.layout.addWidget(self.height, 2, 2, 1, 1) + + self.layout.setRowStretch(3, 1) + + def collect_args(self): + dic = super().collect_args() + centre = self.parse_point(self.centre_x, self.centre_y) + if centre is None: + return + dic['centre'] = centre + + axes = self.parse_point(self.width, self.height) + if axes is None: + return + dic['axes'] = axes + + return dic + + +class TextWidget(ObjectWidget): + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.centre_label = QtWidgets.QLabel('Centre') + self.layout.addWidget(self.centre_label, 1, 0, 1, 1) + self.centre_x = QtWidgets.QLineEdit() + self.centre_x.setPlaceholderText('x') + self.layout.addWidget(self.centre_x, 1, 1, 1, 1) + self.centre_y = QtWidgets.QLineEdit() + self.centre_y.setPlaceholderText('y') + self.centre_x.setValidator(QtGui.QDoubleValidator()) + self.centre_y.setValidator(QtGui.QDoubleValidator()) + self.layout.addWidget(self.centre_y, 1, 2, 1, 1) + + self.text_label = QtWidgets.QLabel('Text') + self.layout.addWidget(self.text_label, 2, 0, 1, 1) + self.text_lineedit =QtWidgets.QLineEdit() + self.layout.addWidget(self.text_lineedit, 2, 1, 1, 2) + + self.layout.setRowStretch(3, 1) + + def collect_args(self): + dic = super().collect_args() + centre = self.parse_point(self.centre_x, self.centre_y) + if centre is None: + return + dic['centre'] = centre + + dic['text'] = self.text_lineedit.text() + + return dic diff --git a/src/gui_qt/graphs/draw_objects.py b/src/gui_qt/graphs/draw_objects.py new file mode 100644 index 0000000..528ed1e --- /dev/null +++ b/src/gui_qt/graphs/draw_objects.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import uuid + +from pyqtgraph import mkPen + +from gui_qt.lib.pg_objects import LogInfiniteLine, PlotItem + + +class LineObject: + def __init__(self, **kwargs): + self.id = str(uuid.uuid4()) + + self.pos = kwargs['pos'] + self.angle = kwargs['angle'] + self.color = kwargs['color'] + + self.drawing = LogInfiniteLine(pos=self.pos, angle=self.angle, pen=mkPen(color=self.color.rgb())) + + def __str__(self): + return f'{"x" if self.angle==90 else "y"}={self.pos}' + + +class MultipointObject: + def __init__(self, **kwargs): + self.id = str(uuid.uuid4()) + + self.color = kwargs['color'] + x, y = zip(*kwargs['pts']) + self.closed = kwargs['closed'] + + if self.closed: + x += (x[0],) + y += (y[0],) + self._x = x + self._y = y + + self.drawing = PlotItem(x=self._x, y=self._y, pen=mkPen(color=self.color.rgb())) + + def __str__(self): + return f'{len(self._y)-int(self.closed)}-pts' diff --git a/src/gui_qt/graphs/drawings.py b/src/gui_qt/graphs/drawings.py index cd62db4..000e777 100644 --- a/src/gui_qt/graphs/drawings.py +++ b/src/gui_qt/graphs/drawings.py @@ -1,5 +1,9 @@ -from ..Qt import QtWidgets, QtCore -from .._py.guidelinewidget import Ui_Form +from __future__ import annotations + +from gui_qt.Qt import QtWidgets, QtCore +from gui_qt._py.guidelinewidget import Ui_Form +from gui_qt.graphs.draw_inputs import EllipseWidget, LineWidget, MultiPointWidget, RectangleWidget, TextWidget +from gui_qt.graphs.draw_objects import LineObject, MultipointObject class DrawingsWidget(QtWidgets.QWidget, Ui_Form): @@ -7,13 +11,25 @@ class DrawingsWidget(QtWidgets.QWidget, Ui_Form): def __init__(self, parent=None): super().__init__(parent=parent) - self.connected_figure=None - self.setupUi(self) - def __call__(self, graphs): - for gid, name in graphs: - self.graph_comboBox.addItem(name, userData=gid) + self.widgets = [LineWidget(self), MultiPointWidget(self), TextWidget(self), RectangleWidget(self), EllipseWidget(self)] + for w in self.widgets: + self.stackedWidget.addWidget(w) + + self.graphs = None + + def update_tree(self): + for gid, windows in self.graphs.items(): + item = QtWidgets.QTreeWidgetItem([windows.title]) + item.setData(0, QtCore.Qt.UserRole, gid) + + for d in windows.drawings.values(): + child = QtWidgets.QTreeWidgetItem([d]) + item.addChild(child) + + self.treeWidget_2.addTopLevelItem(item) + # self.graph_comboBox.addItem(name, userData=gid) def clear(self): self.graph_comboBox.clear() @@ -22,140 +38,33 @@ class DrawingsWidget(QtWidgets.QWidget, Ui_Form): def change_draw_type(self, idx: int): self.stackedWidget.setCurrentIndex(idx) - - -""" - - self.lines = {} - self.comments = {} - - self.vh_pos_lineEdit.setValidator(QtGui.QDoubleValidator()) - - self.tableWidget.installEventFilter(self) - - @QtCore.pyqtSlot(name='on_pushButton_clicked') - def make_line(self): - invalid = True - - idx = self.mode_comboBox.currentIndex() - try: - pos = float(self.vh_pos_lineEdit.text()) - # Vertical: idx=0; horizontal: idx = 1 - angle = 90*abs(1-idx) - invalid = False - except ValueError: - pos = None - angle = None - pass - - if invalid: - QtWidgets.QMessageBox().information(self, 'Invalid input', 'Input is not a valid number') + @QtCore.pyqtSlot(name='on_pushButton_3_clicked') + def make_drawing(self): + dic = self.stackedWidget.currentWidget().collect_args() + if dic is None: + QtWidgets.QMessageBox.information(self, 'Not working', 'Something is missing to create this object') return + idx = self.treeWidget_2.selectedIndexes() + if idx: + item = self.treeWidget_2.itemFromIndex(idx[0]) + graph_id = item.data(0, QtCore.Qt.UserRole) + if self.mode_comboBox.currentIndex() == 0: + new_obj = LineObject(**dic) + elif self.mode_comboBox.currentIndex() == 1: + new_obj = MultipointObject(**dic) + child = QtWidgets.QTreeWidgetItem([str(new_obj)]) + child.setData(0, QtCore.Qt.UserRole, new_obj.id) + item.addChild(child) - qcolor = QtGui.QColor.fromRgb(*self.color_comboBox.value.rgb()) - comment = self.comment_lineEdit.text() - line = LogInfiniteLine(pos=pos, angle=angle, movable=self.drag_checkBox.isChecked(), pen=qcolor) - line.sigPositionChanged.connect(self.move_line) - - self.make_table_row(pos, angle, qcolor, comment) - - graph_id = self.graph_comboBox.currentData() - try: - self.lines[graph_id].append(line) - self.comments[graph_id].append(comment) - except KeyError: - self.lines[graph_id] = [line] - self.comments[graph_id] = [comment] - - self.line_created.emit(line, graph_id) - - def set_graphs(self, graphs: list): - for graph_id, name in graphs: - self.graph_comboBox.addItem(name, userData=graph_id) - - def remove_graph(self, graph_id: str): - idx = self.graph_comboBox.findData(graph_id) - if idx != -1: - self.graph_comboBox.removeItem(idx) - - if graph_id in self.lines: - del self.lines[graph_id] - - @QtCore.pyqtSlot(int, name='on_graph_comboBox_currentIndexChanged') - def change_graph(self, idx: int): - self.tableWidget.clear() - self.tableWidget.setRowCount(0) - - graph_id = self.graph_comboBox.itemData(idx) - if graph_id in self.lines: - lines = self.lines[graph_id] - comments = self.comments[graph_id] - for i, line in enumerate(lines): - self.make_table_row(line.pos(), line.angle, line.pen.color(), comments[i]) - - def make_table_row(self, position, angle, color, comment): - if angle == 0: - try: - pos_label = 'x = ' + str(position.y()) - except AttributeError: - pos_label = 'x = {position}' - - elif angle == 90: - try: - pos_label = f'y = {position.x()}' - except AttributeError: - pos_label = f'y = {position}' - + self.graphs[graph_id].addDrawing(new_obj) else: - raise ValueError('Only horizontal or vertical lines are supported') + QtWidgets.QMessageBox.information(self, 'Not working', 'No graph is selected to add this object.') - item = QtWidgets.QTableWidgetItem(pos_label) - item.setFlags(QtCore.Qt.ItemIsSelectable) - item.setForeground(QtGui.QBrush(QtGui.QColor('black'))) + self.treeWidget_2.expandAll() - row_count = self.tableWidget.rowCount() - self.tableWidget.setRowCount(row_count+1) - self.tableWidget.setItem(row_count, 0, item) - item2 = QtWidgets.QTableWidgetItem(comment) - self.tableWidget.setItem(row_count, 1, item2) - - colitem = QtWidgets.QTableWidgetItem(' ') - colitem.setBackground(QtGui.QBrush(color)) - colitem.setFlags(QtCore.Qt.ItemIsSelectable) - self.tableWidget.setVerticalHeaderItem(row_count, colitem) - - def eventFilter(self, src: QtCore.QObject, evt: QtCore.QEvent) -> bool: - if evt.type() == QtCore.QEvent.KeyPress: - if evt.key() == QtCore.Qt.Key_Delete: - self.delete_line() - return True - - return super().eventFilter(src, evt) - - def delete_line(self): - remove_rows = sorted([item.row() for item in self.tableWidget.selectedItems()]) - graph_id = self.graph_comboBox.currentData() - current_lines = self.lines[graph_id] - - print(remove_rows) - for i in reversed(remove_rows): - print(i) - self.tableWidget.removeRow(i) - self.line_deleted.emit(current_lines[i], graph_id) - - current_lines.pop(i) - self.comments[graph_id].pop(i) - - @QtCore.pyqtSlot(object) - def move_line(self, line: InfiniteLine): - current_idx = self.graph_comboBox.currentData() - graphs = self.lines[current_idx] - i = -1 - for i, line_i in enumerate(graphs): - if line == line_i: - break - pos = line.value() - text_item = self.tableWidget.item(i, 0) - text_item.setText(text_item.text()[:4]+f'{pos:.4g}') -""" \ No newline at end of file +if __name__ == '__main__': + app = QtWidgets.QApplication([]) + w = DrawingsWidget() + w.show() + app.exec() diff --git a/src/gui_qt/graphs/objects.py b/src/gui_qt/graphs/objects.py new file mode 100644 index 0000000..e69de29