From 4d1cb634fb37302e96cab95dafbd8f14f2bec852 Mon Sep 17 00:00:00 2001 From: Blaine Date: Thu, 18 Jun 2020 14:17:51 +0530 Subject: implement header inclusive bottom line --- src/main/python/utils/streamTable.py | 15 +++++++++++++-- src/main/resources/base/app.qss | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/python/utils/streamTable.py b/src/main/python/utils/streamTable.py index acccdee..7dfc27f 100644 --- a/src/main/python/utils/streamTable.py +++ b/src/main/python/utils/streamTable.py @@ -93,6 +93,7 @@ class streamTable(QTableView): self.model.updateEvent.connect(self.resizeHandler) self.setItemDelegateForRow(0, drawBorderDelegate(self)) + self.borderThickness[0] = True def mousePressEvent(self, event): if event.button() == Qt.RightButton: @@ -109,11 +110,12 @@ class streamTable(QTableView): def changeRowBorder(self, row): if self.borderThickness[row]: - self.borderThickness[row] = False + self.borderThickness.pop(row) self.setItemDelegateForRow(row, QStyledItemDelegate(self)) else: self.borderThickness[row] = True self.setItemDelegateForRow(row, drawBorderDelegate(self)) + self.verticalHeader().repaint() def labelChange(self, index): newName, bool = QInputDialog.getText(self, "Change Property Name", "Enter new name", @@ -180,4 +182,13 @@ class verticalHeader(QHeaderView): def mouseDoubleClickEvent(self, event): index = self.logicalIndexAt(event.pos()) self.labelChangeRequested.emit(index) - return super().mouseDoubleClickEvent(event) \ No newline at end of file + return super().mouseDoubleClickEvent(event) + + def paintSection(self, painter, option, index): + painter.save() + super(verticalHeader, self).paintSection(painter, option, index) + painter.restore() + if self.parentWidget().borderThickness[index]: + rect = option + painter.drawLine(rect.bottomLeft(), rect.bottomRight()) + painter.setPen(QPen(Qt.black, 1, Qt.SolidLine)) \ No newline at end of file diff --git a/src/main/resources/base/app.qss b/src/main/resources/base/app.qss index d7b25b4..1d21ab4 100644 --- a/src/main/resources/base/app.qss +++ b/src/main/resources/base/app.qss @@ -275,9 +275,9 @@ QHeaderView::section { background-color: white; } -QHeaderView::section:first{ +/* QHeaderView::section:first{ border-bottom: 1px solid black; -} +} */ QTableView { border: none; -- cgit From 384bdf5f033ba1453efdbb8baf43cdaa9049c9df Mon Sep 17 00:00:00 2001 From: Blaine Date: Thu, 18 Jun 2020 14:27:19 +0530 Subject: fix line updation --- src/main/python/shapes/line.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/python/shapes/line.py b/src/main/python/shapes/line.py index c893fe6..6883662 100644 --- a/src/main/python/shapes/line.py +++ b/src/main/python/shapes/line.py @@ -1008,8 +1008,9 @@ class Line(QGraphicsPathItem): action = contextMenu.exec_(event.screenPos()) # check for label action and add text label as child if action == addLableAction: - print(event.scenePos(), event.pos()) - self.label.append(LineLabel(event.scenePos(), self)) # text label as child + label = LineLabel(event.scenePos(), self) + self.label.append(label) # text label as child + self.scene().labelAdded.emit(label) def __getstate__(self): return { -- cgit From c0c5fe6186ca5776c9ff8199b88c13c2ad0e72a8 Mon Sep 17 00:00:00 2001 From: Blaine Date: Thu, 18 Jun 2020 15:43:57 +0530 Subject: save fileupdate --- src/main/python/shapes/line.py | 11 +++++++++-- src/main/python/utils/app.py | 3 +-- src/main/python/utils/canvas.py | 25 +++++++++++++++---------- src/main/python/utils/streamTable.py | 12 ++++++++++++ 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/main/python/shapes/line.py b/src/main/python/shapes/line.py index 6883662..57778fa 100644 --- a/src/main/python/shapes/line.py +++ b/src/main/python/shapes/line.py @@ -292,13 +292,16 @@ class LineLabel(QGraphicsTextItem): "text": self.toPlainText(), "index": self.index, "gap": self.gap, - "pos": (self.pos().x(), self.pos().y()) + "pos": (self.pos().x(), self.pos().y()), + "values": self.values } def __setstate__(self, dict): self.setPlainText(dict['text']) self.index = dict['index'] self.gap = dict['gap'] + for key, value in dict['values']: + self.values[key] = value def findIndex(line, pos): @@ -1023,8 +1026,12 @@ class Line(QGraphicsPathItem): "refLine": hex(id(self.refLine)) if self.refLine else 0, "refIndex": self.refIndex, "label": [i for i in self.label], - "id": hex(id(self)) + "id": hex(id(self)), + "startGap": self.startGap, + "endGap": self.endGap } def __setstate__(self, dict): self.points = [QPointF(x, y) for x, y in dict["points"]] + self.startGap = dict['startGap'] + self.endGap = dict['endGap'] diff --git a/src/main/python/utils/app.py b/src/main/python/utils/app.py index 96a45b0..60ae47f 100644 --- a/src/main/python/utils/app.py +++ b/src/main/python/utils/app.py @@ -61,5 +61,4 @@ class JSON_Typer(JSONEncoder): importer = pyqtProperty(str, fileImporter) -shapeGrips = {} -lines = {} \ No newline at end of file +memMap = {} \ No newline at end of file diff --git a/src/main/python/utils/canvas.py b/src/main/python/utils/canvas.py index 47e95a9..e492878 100644 --- a/src/main/python/utils/canvas.py +++ b/src/main/python/utils/canvas.py @@ -6,7 +6,7 @@ from PyQt5.QtWidgets import (QFileDialog, QApplication, QHBoxLayout, QMenu, from . import dialogs from .graphics import customView, customScene from .data import paperSizes, ppiList, sheetDimensionList -from .app import shapeGrips, lines +from .app import memMap from .streamTable import streamTable, moveRect import shapes @@ -142,7 +142,8 @@ class canvas(customView): "ObjectName": self.objectName(), "symbols": [i for i in self.painter.items() if isinstance(i, shapes.NodeItem)], "lines": sorted([i for i in self.painter.items() if isinstance(i, shapes.Line)], key = lambda x: 1 if x.refLine else 0), - "landscape": self.landscape + "landscape": self.landscape, + "streamTable": [self.streamTable, (self.streamTableRect.pos().x(), self.streamTable.pos().y())] if self.streamTable else False } def __setstate__(self, dict): @@ -159,7 +160,7 @@ class canvas(customView): graphic.updateLineGripItem() graphic.updateSizeGripItem() for gripitem in item['lineGripItems']: - shapeGrips[gripitem[0]] = (graphic, gripitem[1]) + memMap[gripitem[0]] = (graphic, gripitem[1]) if item['label']: graphicLabel = shapes.ItemLabel(pos = QPointF(*item['label']['pos']), parent = graphic) graphicLabel.__setstate__(item['label']) @@ -167,19 +168,19 @@ class canvas(customView): for item in dict['lines']: line = shapes.Line(QPointF(*item['startPoint']), QPointF(*item['endPoint'])) - lines[item['id']] = line + memMap[item['id']] = line line.__setstate__(dict = item) self.painter.addItem(line) - graphic, index = shapeGrips[item['startGripItem']] + graphic, index = memMap[item['startGripItem']] line.startGripItem = graphic.lineGripItems[index] graphic.lineGripItems[index].line = line if item['endGripItem']: - graphic, index = shapeGrips[item['endGripItem']] + graphic, index = memMap[item['endGripItem']] line.endGripItem = graphic.lineGripItems[index] graphic.lineGripItems[index].line = line else: - line.refLine = lines[item['refLine']] - lines[item['refLine']].midLines.append(line) + line.refLine = memMap[item['refLine']] + memMap[item['refLine']].midLines.append(line) line.refIndex = item['refIndex'] for label in item['label']: labelItem = shapes.LineLabel(QPointF(*label['pos']), line) @@ -189,8 +190,12 @@ class canvas(customView): line.updateLine() line.addGrabber() - shapeGrips.clear() - lines.clear() + if dict['streamTable']: + table = streamTable(self.labelItems, self) + self.addStreamTable(QPointF(*dict['streamTable'][1]), table) + table.__setstate__(dict['streamTable'][0]) + + memMap.clear() self.painter.advance() diff --git a/src/main/python/utils/streamTable.py b/src/main/python/utils/streamTable.py index 7dfc27f..52b62c1 100644 --- a/src/main/python/utils/streamTable.py +++ b/src/main/python/utils/streamTable.py @@ -147,6 +147,18 @@ class streamTable(QTableView): for i in range(self.model.rowCount()): h += self.rowHeight(i) return QRect(0, 0, w, h) + + def __getstate__(self): + return { + "borderThickness": self.borderThickness, + "header": self.model.header + } + + def __setstate__(self, dict): + for key, value in dict['borderThickness']: + self.borderThickness[key] = value + self.model.header = dict['header'] + self.repaint() class drawBorderDelegate(QStyledItemDelegate): -- cgit From 85a2d6f42168a9e693b7649b979b86f7a94d6ca3 Mon Sep 17 00:00:00 2001 From: Blaine Date: Thu, 18 Jun 2020 15:59:40 +0530 Subject: save update 2 --- src/main/python/shapes/line.py | 4 +++- src/main/python/shapes/shapes.py | 3 ++- src/main/python/utils/canvas.py | 4 +++- src/main/python/utils/streamTable.py | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/python/shapes/line.py b/src/main/python/shapes/line.py index 57778fa..856869d 100644 --- a/src/main/python/shapes/line.py +++ b/src/main/python/shapes/line.py @@ -300,7 +300,7 @@ class LineLabel(QGraphicsTextItem): self.setPlainText(dict['text']) self.index = dict['index'] self.gap = dict['gap'] - for key, value in dict['values']: + for key, value in dict['values'].items(): self.values[key] = value @@ -1033,5 +1033,7 @@ class Line(QGraphicsPathItem): def __setstate__(self, dict): self.points = [QPointF(x, y) for x, y in dict["points"]] + self.startPoint = QPointF(*dict['startPoint']) + self.endPoint = QPointF(*dict['endPoint']) self.startGap = dict['startGap'] self.endGap = dict['endGap'] diff --git a/src/main/python/shapes/shapes.py b/src/main/python/shapes/shapes.py index 7205ba7..d492568 100644 --- a/src/main/python/shapes/shapes.py +++ b/src/main/python/shapes/shapes.py @@ -616,7 +616,8 @@ class NodeItem(QGraphicsSvgItem): "height": self.height, "pos": (self.pos().x(), self.pos().y()), "lineGripItems": [(hex(id(i)), i.m_index) for i in self.lineGripItems], - "label": self.label + "label": self.label, + "rotation": self.rotation } def __setstate__(self, dict): diff --git a/src/main/python/utils/canvas.py b/src/main/python/utils/canvas.py index e492878..ad8981d 100644 --- a/src/main/python/utils/canvas.py +++ b/src/main/python/utils/canvas.py @@ -143,7 +143,7 @@ class canvas(customView): "symbols": [i for i in self.painter.items() if isinstance(i, shapes.NodeItem)], "lines": sorted([i for i in self.painter.items() if isinstance(i, shapes.Line)], key = lambda x: 1 if x.refLine else 0), "landscape": self.landscape, - "streamTable": [self.streamTable, (self.streamTableRect.pos().x(), self.streamTable.pos().y())] if self.streamTable else False + "streamTable": [self.streamTable, (self.streamTableRect.pos().x(), self.streamTableRect.pos().y())] if self.streamTable else False } def __setstate__(self, dict): @@ -165,6 +165,7 @@ class canvas(customView): graphicLabel = shapes.ItemLabel(pos = QPointF(*item['label']['pos']), parent = graphic) graphicLabel.__setstate__(item['label']) self.painter.addItem(graphicLabel) + graphic.rotation = item['rotation'] for item in dict['lines']: line = shapes.Line(QPointF(*item['startPoint']), QPointF(*item['endPoint'])) @@ -189,6 +190,7 @@ class canvas(customView): self.painter.addItem(labelItem) line.updateLine() line.addGrabber() + print(line.startGripItem) if dict['streamTable']: table = streamTable(self.labelItems, self) diff --git a/src/main/python/utils/streamTable.py b/src/main/python/utils/streamTable.py index 52b62c1..b397016 100644 --- a/src/main/python/utils/streamTable.py +++ b/src/main/python/utils/streamTable.py @@ -155,7 +155,7 @@ class streamTable(QTableView): } def __setstate__(self, dict): - for key, value in dict['borderThickness']: + for key, value in dict['borderThickness'].items(): self.borderThickness[key] = value self.model.header = dict['header'] self.repaint() -- cgit From 5d08ffb7b9f28b9bd641c1d5981ea8b52f531c94 Mon Sep 17 00:00:00 2001 From: Blaine Date: Thu, 18 Jun 2020 16:03:21 +0530 Subject: save update 3 --- src/main/python/utils/canvas.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/python/utils/canvas.py b/src/main/python/utils/canvas.py index ad8981d..d5888bf 100644 --- a/src/main/python/utils/canvas.py +++ b/src/main/python/utils/canvas.py @@ -174,11 +174,11 @@ class canvas(customView): self.painter.addItem(line) graphic, index = memMap[item['startGripItem']] line.startGripItem = graphic.lineGripItems[index] - graphic.lineGripItems[index].line = line + graphic.lineGripItems[index].lines.append(line) if item['endGripItem']: graphic, index = memMap[item['endGripItem']] line.endGripItem = graphic.lineGripItems[index] - graphic.lineGripItems[index].line = line + graphic.lineGripItems[index].lines.append(line) else: line.refLine = memMap[item['refLine']] memMap[item['refLine']].midLines.append(line) -- cgit From 7f0ed11a3763ed1463519905f0b7e522bbbd2ee2 Mon Sep 17 00:00:00 2001 From: Blaine Date: Thu, 18 Jun 2020 16:04:25 +0530 Subject: logging fix --- src/main/python/shapes/shapes.py | 2 +- src/main/python/utils/canvas.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/python/shapes/shapes.py b/src/main/python/shapes/shapes.py index d492568..0d28a00 100644 --- a/src/main/python/shapes/shapes.py +++ b/src/main/python/shapes/shapes.py @@ -267,7 +267,7 @@ class LineGripItem(QGraphicsPathItem): if self.size: painter.save() pen = self.pen() - pen.setWidth(-1) + pen.setWidth(1) painter.setPen(pen) painter.drawPath(self.path()) painter.restore() diff --git a/src/main/python/utils/canvas.py b/src/main/python/utils/canvas.py index d5888bf..a75570c 100644 --- a/src/main/python/utils/canvas.py +++ b/src/main/python/utils/canvas.py @@ -190,7 +190,6 @@ class canvas(customView): self.painter.addItem(labelItem) line.updateLine() line.addGrabber() - print(line.startGripItem) if dict['streamTable']: table = streamTable(self.labelItems, self) -- cgit From 11c4c315e4616259b2c6813df94a3990784df781 Mon Sep 17 00:00:00 2001 From: Blaine Date: Thu, 18 Jun 2020 16:21:11 +0530 Subject: minor fixes 2 --- src/main/python/utils/streamTable.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/python/utils/streamTable.py b/src/main/python/utils/streamTable.py index b397016..c8e682c 100644 --- a/src/main/python/utils/streamTable.py +++ b/src/main/python/utils/streamTable.py @@ -47,6 +47,7 @@ class streamTableModel(QAbstractTableModel): int = int if int else self.rowCount()+1 self.beginInsertColumns(QModelIndex(), int, int) self.list.insert(int, item) + item.nameChanged.connect(self.parent().repaint) self.endInsertColumns() self.updateEvent.emit() -- cgit From 420d0c3a68aa4a649991d36f0f72772fab0dd2d4 Mon Sep 17 00:00:00 2001 From: Blaine Date: Thu, 18 Jun 2020 16:55:12 +0530 Subject: flipping feature --- src/main/python/shapes/shapes.py | 59 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/main/python/shapes/shapes.py b/src/main/python/shapes/shapes.py index 0d28a00..f47ea28 100644 --- a/src/main/python/shapes/shapes.py +++ b/src/main/python/shapes/shapes.py @@ -244,7 +244,12 @@ class LineGripItem(QGraphicsPathItem): @property def m_location(self): - return directionsEnum[(self._m_location + self.parentItem().rotation)%4] + index = (self._m_location + self.parentItem().rotation) + if index%2: + index = (index + 2*self.parentItem().flipH)%4 + else: + index = (index + 2*self.parentItem().flipV)%4 + return directionsEnum[index] @m_location.setter def m_location(self, location): @@ -451,7 +456,55 @@ class NodeItem(QGraphicsSvgItem): self.sizeGripItems = [] self.label = None self._rotation = 0 + self.flipState = [False, False] + + @property + def flipH(self): + return self.flipState[0] + + @property + def flipV(self): + return self.flipState[1] + + @flipH.setter + def flipH(self, state): + transform = QTransform() + if self.flipV and state: + self.flipState = [False, False] + self.rotation = self.rotation % 4 + transform.scale(1, 1) + else: + self.flipState[0] = state + if state: + transform.scale(-1, 1) + else: + transform.scale(1, 1) + self.setTransform(transform) + for i in self.lineGripItems: + i.updatePosition() + for j in i.lines: + j.createPath() + @flipV.setter + def flipV(self, state): + transform = QTransform() + if self.flipH and state: + self.flipState = [False, False] + self.rotation = self.rotation % 4 + transform.scale(1, 1) + else: + self.flipState[1] = state + transform = QTransform() + if state: + transform.scale(1, -1) + else: + transform.scale(1, 1) + self.setTransform(transform) + for i in self.lineGripItems: + i.updatePosition() + for j in i.lines: + j.createPath() + @property def rotation(self): return self._rotation @@ -604,6 +657,10 @@ class NodeItem(QGraphicsSvgItem): # create a menu and add action contextMenu = QMenu() addLableAction = contextMenu.addAction("add Label") # add action for text label + contextMenu.addAction("Rotate right", lambda : setattr(self, "rotation", self.rotation + 1)) + contextMenu.addAction("Rotate left", lambda : setattr(self, "rotation", self.rotation - 1)) + contextMenu.addAction("Flip Horizontally", lambda: setattr(self, "flipH", not self.flipH)) + contextMenu.addAction("Flip Vertically", lambda: setattr(self, "flipV", not self.flipV)) action = contextMenu.exec_(event.screenPos()) # check for label action and add text label as child if action == addLableAction: -- cgit From 70e9fc3756d8e94c374f498a63105f7345d7e7e2 Mon Sep 17 00:00:00 2001 From: Blaine Date: Thu, 18 Jun 2020 16:57:38 +0530 Subject: flipstate --- src/main/python/shapes/shapes.py | 3 ++- src/main/python/utils/canvas.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/python/shapes/shapes.py b/src/main/python/shapes/shapes.py index f47ea28..01ec196 100644 --- a/src/main/python/shapes/shapes.py +++ b/src/main/python/shapes/shapes.py @@ -674,7 +674,8 @@ class NodeItem(QGraphicsSvgItem): "pos": (self.pos().x(), self.pos().y()), "lineGripItems": [(hex(id(i)), i.m_index) for i in self.lineGripItems], "label": self.label, - "rotation": self.rotation + "rotation": self.rotation, + "flipstate": self.flipState } def __setstate__(self, dict): diff --git a/src/main/python/utils/canvas.py b/src/main/python/utils/canvas.py index a75570c..b60f11c 100644 --- a/src/main/python/utils/canvas.py +++ b/src/main/python/utils/canvas.py @@ -166,6 +166,7 @@ class canvas(customView): graphicLabel.__setstate__(item['label']) self.painter.addItem(graphicLabel) graphic.rotation = item['rotation'] + graphic.flipH, graphic.flipV = item['flipstate'] for item in dict['lines']: line = shapes.Line(QPointF(*item['startPoint']), QPointF(*item['endPoint'])) -- cgit From c7f8e571cb9d7be21eb5b2a7c3c0e4704ffdce9c Mon Sep 17 00:00:00 2001 From: Blaine Date: Thu, 18 Jun 2020 20:28:29 +0530 Subject: ignore build --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9aeb547..0e61b94 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ src/main/python/experimental/* *.pfd .vscode .vscode/*.jsons -.idea/* \ No newline at end of file +.idea/* +target/* -- cgit From 025cd829d956f5081e034f60b69413ec4432e0e8 Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 19 Jun 2020 03:03:41 +0530 Subject: rework flip feature --- src/main/python/shapes/shapes.py | 40 +++++++++++----------------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/src/main/python/shapes/shapes.py b/src/main/python/shapes/shapes.py index 01ec196..aae8f47 100644 --- a/src/main/python/shapes/shapes.py +++ b/src/main/python/shapes/shapes.py @@ -466,44 +466,26 @@ class NodeItem(QGraphicsSvgItem): def flipV(self): return self.flipState[1] - @flipH.setter - def flipH(self, state): + def flip(self): transform = QTransform() - if self.flipV and state: - self.flipState = [False, False] - self.rotation = self.rotation % 4 - transform.scale(1, 1) - else: - self.flipState[0] = state - if state: - transform.scale(-1, 1) - else: - transform.scale(1, 1) + h = -1 if self.flipH else 1 + w = -1 if self.flipV else 1 + transform.scale(h, w) self.setTransform(transform) for i in self.lineGripItems: i.updatePosition() for j in i.lines: j.createPath() + + @flipH.setter + def flipH(self, state): + self.flipState[0] = state + self.flip() @flipV.setter def flipV(self, state): - transform = QTransform() - if self.flipH and state: - self.flipState = [False, False] - self.rotation = self.rotation % 4 - transform.scale(1, 1) - else: - self.flipState[1] = state - transform = QTransform() - if state: - transform.scale(1, -1) - else: - transform.scale(1, 1) - self.setTransform(transform) - for i in self.lineGripItems: - i.updatePosition() - for j in i.lines: - j.createPath() + self.flipState[1] = state + self.flip() @property def rotation(self): -- cgit From 6a306ce01a9501f5580e81e9263cd34987ba7bc4 Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 19 Jun 2020 11:40:28 +0530 Subject: unified transform --- src/main/python/shapes/shapes.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/main/python/shapes/shapes.py b/src/main/python/shapes/shapes.py index aae8f47..3efabbc 100644 --- a/src/main/python/shapes/shapes.py +++ b/src/main/python/shapes/shapes.py @@ -244,6 +244,8 @@ class LineGripItem(QGraphicsPathItem): @property def m_location(self): + if self.parentItem().__class__ == Line: + return directionsEnum[self._m_location] index = (self._m_location + self.parentItem().rotation) if index%2: index = (index + 2*self.parentItem().flipH)%4 @@ -466,26 +468,27 @@ class NodeItem(QGraphicsSvgItem): def flipV(self): return self.flipState[1] - def flip(self): + def updateTransformation(self): transform = QTransform() h = -1 if self.flipH else 1 w = -1 if self.flipV else 1 + transform.rotate(90*self.rotation) transform.scale(h, w) self.setTransform(transform) + self.setTransform(transform) for i in self.lineGripItems: + i.setTransform(transform) i.updatePosition() - for j in i.lines: - j.createPath() @flipH.setter def flipH(self, state): self.flipState[0] = state - self.flip() + self.updateTransformation() @flipV.setter def flipV(self, state): self.flipState[1] = state - self.flip() + self.updateTransformation() @property def rotation(self): @@ -494,14 +497,7 @@ class NodeItem(QGraphicsSvgItem): @rotation.setter def rotation(self, rotation): self._rotation = rotation % 4 - transform = QTransform() - transform.rotate(90*rotation) - self.setTransform(transform) - for i in self.lineGripItems: - i.setTransform(transform) - i.updatePosition() - for j in i.lines: - j.createPath() + self.updateTransformation() def boundingRect(self): """Overrides QGraphicsSvgItem's boundingRect() virtual public function and -- cgit From adbe155abe6604b07c8ef076311f3e9e7a8268b0 Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 19 Jun 2020 11:42:46 +0530 Subject: undo add --- src/main/python/main.py | 2 +- src/main/python/utils/graphics.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/python/main.py b/src/main/python/main.py index 4cf1791..43dcbf3 100644 --- a/src/main/python/main.py +++ b/src/main/python/main.py @@ -77,8 +77,8 @@ class appWindow(QMainWindow): graphic = getattr(shapes, object['object'])(*map(lambda x: int(x) if x.isdigit() else x, object['args'])) # graphic.setPen(QPen(Qt.black, 2)) # graphic.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable) - currentDiagram.addItemPlus(graphic) graphic.setPos(20, 20) + currentDiagram.addItemPlus(graphic) def newProject(self): #call to create a new file inside mdi area diff --git a/src/main/python/utils/graphics.py b/src/main/python/utils/graphics.py index 3e1d193..0dca012 100644 --- a/src/main/python/utils/graphics.py +++ b/src/main/python/utils/graphics.py @@ -49,8 +49,8 @@ class customView(QGraphicsView): graphic = getattr(shapes, obj[0])(*map(lambda x: int(x) if x.isdigit() else x, obj[1:])) # graphic.setPen(QPen(Qt.black, 2)) # graphic.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable) - self.scene().addItemPlus(graphic) graphic.setPos(QDropEvent.pos().x(), QDropEvent.pos().y()) + self.scene().addItemPlus(graphic) QDropEvent.acceptProposedAction() def wheelEvent(self, QWheelEvent): -- cgit From cadf8eade6e9d1f5f1a1fe3205bccef41ca5e3e8 Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 19 Jun 2020 11:43:58 +0530 Subject: undo add 2 --- src/main/python/utils/undo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/python/utils/undo.py b/src/main/python/utils/undo.py index 8426494..92a8de8 100644 --- a/src/main/python/utils/undo.py +++ b/src/main/python/utils/undo.py @@ -26,7 +26,7 @@ class addCommand(QUndoCommand): self.scene = scene self.diagramItem = addItem self.itemPos = addItem.pos() - self.setText(f"Add {objectName(self.diagramItem)}") + self.setText(f"Add {objectName(self.diagramItem)} at {self.itemPos.x()}, {self.itemPos.y()}") def undo(self): self.scene.removeItem(self.diagramItem) -- cgit From ddcfa2fd395be6d0ac261851ff6eb31be2164728 Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 19 Jun 2020 12:17:59 +0530 Subject: update shortcuts --- src/main/python/main.py | 46 ++++++++++++++++----------------------- src/main/python/utils/graphics.py | 7 +++--- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/main/python/main.py b/src/main/python/main.py index 43dcbf3..7a73c7a 100644 --- a/src/main/python/main.py +++ b/src/main/python/main.py @@ -2,7 +2,7 @@ import sys from fbs_runtime.application_context.PyQt5 import ApplicationContext from PyQt5.QtCore import QObject, Qt, pyqtSignal, QSize, QPoint -from PyQt5.QtGui import QBrush, QColor, QImage, QPainter, QPalette, QPen +from PyQt5.QtGui import QBrush, QColor, QImage, QPainter, QPalette, QPen, QKeySequence from PyQt5.QtWidgets import (QComboBox, QFileDialog, QFormLayout, QVBoxLayout, QHBoxLayout, QLabel, QMainWindow, QMenu, QPushButton, QWidget, QMdiArea, QSplitter, QGraphicsItem) @@ -29,19 +29,29 @@ class appWindow(QMainWindow): # self.mainWidget.setObjectName("Main Widget") self.menuFile = titleMenu.addMenu('File') #File Menu - self.menuFile.addAction("New", self.newProject) - self.menuFile.addAction("Open", self.openProject) - self.menuFile.addAction("Save", self.saveProject) + newAction = self.menuFile.addAction("New", self.newProject) + openAction = self.menuFile.addAction("Open", self.openProject) + saveAction = self.menuFile.addAction("Save", self.saveProject) + + newAction.setShortcut(QKeySequence.New) + openAction.setShortcut(QKeySequence.Open) + saveAction.setShortcut(QKeySequence.Save) self.menuEdit = titleMenu.addMenu('Edit') - self.undo = self.menuEdit.addAction("Undo", lambda x=self: x.activeScene.painter.undoAction.trigger()) - self.redo = self.menuEdit.addAction("Redo", lambda x=self: x.activeScene.painter.redoAction.trigger()) + undoAction = self.undo = self.menuEdit.addAction("Undo", lambda x=self: x.activeScene.painter.undoAction.trigger()) + redoAction = self.redo = self.menuEdit.addAction("Redo", lambda x=self: x.activeScene.painter.redoAction.trigger()) + + undoAction.setShortcut(QKeySequence.Undo) + redoAction.setShortcut(QKeySequence.Redo) self.menuEdit.addAction("Show Undo Stack", lambda x=self: x.activeScene.painter.createUndoView(self) ) self.menuGenerate = titleMenu.addMenu('Generate') #Generate menu - self.menuGenerate.addAction("Image", self.saveImage) - self.menuGenerate.addAction("Report", self.generateReport) + imageAction = self.menuGenerate.addAction("Image", self.saveImage) + reportAction = self.menuGenerate.addAction("Report", self.generateReport) + + imageAction.setShortcut(QKeySequence("Ctrl+P")) + reportAction.setShortcut(QKeySequence("Ctrl+R")) self.mdi = QMdiArea(self) #create area for files to be displayed self.mdi.setObjectName('mdi area') @@ -185,25 +195,7 @@ class appWindow(QMainWindow): def keyPressEvent(self, event): #overload key press event for custom keyboard shortcuts if event.modifiers() & Qt.ControlModifier: - if event.key() == Qt.Key_N: - self.newProject() - - elif event.key() == Qt.Key_S: - self.saveProject() - - elif event.key() == Qt.Key_O: - self.openProject() - - elif event.key() == Qt.Key_W: - self.close() - - elif event.key() == Qt.Key_P: - if Qt.AltModifier: - self.saveImage() - else: - self.generateReport() - - elif event.key() == Qt.Key_A: + if event.key() == Qt.Key_A: #todo implement selectAll for item in self.mdi.activeSubWindow().tabber.currentWidget().items: item.setSelected(True) diff --git a/src/main/python/utils/graphics.py b/src/main/python/utils/graphics.py index 0dca012..a9820a2 100644 --- a/src/main/python/utils/graphics.py +++ b/src/main/python/utils/graphics.py @@ -22,9 +22,10 @@ class customView(QGraphicsView): self.setAcceptDrops(True) #sets ability to accept drops if scene: #create necessary undo redo actions to accept keyboard shortcuts - self.addAction(scene.undoAction) - self.addAction(scene.redoAction) - self.addAction(scene.deleteAction) + # self.addAction(scene.undoAction) + # self.addAction(scene.redoAction) + # self.addAction(scene.deleteAction) + pass #following four functions are required to be overridden for drag-drop functionality def dragEnterEvent(self, QDragEnterEvent): -- cgit From 7e779cddeb822b727e944e1d54237bed4b5692a9 Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 19 Jun 2020 13:44:01 +0530 Subject: camelCase to PascalCase for class names --- src/main/python/main.py | 6 +++--- src/main/python/utils/canvas.py | 6 +++--- src/main/python/utils/fileWindow.py | 12 ++++++------ src/main/python/utils/graphics.py | 16 ++++++++-------- src/main/python/utils/tabs.py | 12 ++++++------ src/main/python/utils/toolbar.py | 6 +++--- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/main/python/main.py b/src/main/python/main.py index 7a73c7a..9683f00 100644 --- a/src/main/python/main.py +++ b/src/main/python/main.py @@ -8,7 +8,7 @@ from PyQt5.QtWidgets import (QComboBox, QFileDialog, QFormLayout, QVBoxLayout, QPushButton, QWidget, QMdiArea, QSplitter, QGraphicsItem) from utils.canvas import canvas -from utils.fileWindow import fileWindow +from utils.fileWindow import FileWindow from utils.data import ppiList, sheetDimensionList from utils import dialogs from utils.toolbar import toolbar @@ -92,7 +92,7 @@ class appWindow(QMainWindow): def newProject(self): #call to create a new file inside mdi area - project = fileWindow(self.mdi) + project = FileWindow(self.mdi) project.setObjectName("New Project") self.mdi.addSubWindow(project) if not project.tabList: # important when unpickling a file instead @@ -110,7 +110,7 @@ class appWindow(QMainWindow): for files in name[0]: with open(files,'r') as file: projectData = load(file) - project = fileWindow(self.mdi) + project = FileWindow(self.mdi) self.mdi.addSubWindow(project) project.__setstate__(projectData) project.resizeHandler() diff --git a/src/main/python/utils/canvas.py b/src/main/python/utils/canvas.py index b60f11c..8608f7b 100644 --- a/src/main/python/utils/canvas.py +++ b/src/main/python/utils/canvas.py @@ -4,14 +4,14 @@ from PyQt5.QtWidgets import (QFileDialog, QApplication, QHBoxLayout, QMenu, QTabWidget, QWidget, QSpacerItem, QStyle, QGraphicsProxyWidget) from . import dialogs -from .graphics import customView, customScene +from .graphics import CustomView, CustomScene from .data import paperSizes, ppiList, sheetDimensionList from .app import memMap from .streamTable import streamTable, moveRect import shapes -class canvas(customView): +class canvas(CustomView): """ Defines the work area for a single sheet. Contains a QGraphicScene along with necessary properties for context menu and dialogs. @@ -30,7 +30,7 @@ class canvas(customView): # when we will draw items on this, this might be changed if QGraphicScene is subclassed. #set layout and background color - self.painter = customScene() + self.painter = CustomScene() self.painter.labelAdded.connect(self.updateStreamTable) self.painter.setBackgroundBrush(QBrush(Qt.white)) #set white background self.setScene(self.painter) diff --git a/src/main/python/utils/fileWindow.py b/src/main/python/utils/fileWindow.py index e85d513..b2f5304 100644 --- a/src/main/python/utils/fileWindow.py +++ b/src/main/python/utils/fileWindow.py @@ -5,14 +5,14 @@ from PyQt5.QtWidgets import (QFileDialog, QHBoxLayout, QSplitter, QWidget, QStyle, QSizePolicy) from os import path from . import dialogs -from .graphics import customView +from .graphics import CustomView from .canvas import canvas -from .tabs import customTabWidget +from .tabs import CustomTabWidget from .undo import resizeCommand from .app import dump, loads, JSON_Typer, version -class fileWindow(QMdiSubWindow): +class FileWindow(QMdiSubWindow): """ This defines a single file, inside the application, consisting of multiple tabs that contain canvases. Pre-Defined so that a file can be instantly created without defining the structure again. @@ -21,14 +21,14 @@ class fileWindow(QMdiSubWindow): tabChangeEvent = pyqtSignal() def __init__(self, parent = None, title = 'New Project', size = 'A0', ppi = '72'): - super(fileWindow, self).__init__(parent) + super(FileWindow, self).__init__(parent) self._sideViewTab = None self.index = None self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) #Uses a custom QTabWidget that houses a custom new Tab Button, used to house the seperate # diagrams inside a file - self.tabber = customTabWidget(self) + self.tabber = CustomTabWidget(self) self.tabber.setObjectName(title) #store title as object name for pickling self.tabber.tabCloseRequested.connect(self.closeTab) # Show save alert on tab close self.tabber.currentChanged.connect(self.changeTab) # placeholder just to detect tab change @@ -67,7 +67,7 @@ class fileWindow(QMdiSubWindow): def createSideViewArea(self): #creates the side view widgets and sets them to invisible # self.splitter = QSplitter(Qt.Vertical ,self) - self.sideView = customView(parent = self) + self.sideView = CustomView(parent = self) self.sideView.setInteractive(False) self.sideViewCloseButton = QPushButton('×', self.sideView) self.sideViewCloseButton.setObjectName("sideViewCloseButton") diff --git a/src/main/python/utils/graphics.py b/src/main/python/utils/graphics.py index a9820a2..05ffc46 100644 --- a/src/main/python/utils/graphics.py +++ b/src/main/python/utils/graphics.py @@ -7,16 +7,16 @@ from .dialogs import showUndoDialog import shapes -class customView(QGraphicsView): +class CustomView(QGraphicsView): """ Defines custom QGraphicsView with zoom features and drag-drop accept event, overriding wheel event """ def __init__(self, scene = None, parent=None): if scene is not None: #overloaded constructor - super(customView, self).__init__(scene, parent) + super(CustomView, self).__init__(scene, parent) else: - super(customView, self).__init__(parent) + super(CustomView, self).__init__(parent) self._zoom = 1 self.setDragMode(True) #sets pannable using mouse self.setAcceptDrops(True) #sets ability to accept drops @@ -67,7 +67,7 @@ class customView(QGraphicsView): self.zoom += QWheelEvent.angleDelta().y() QWheelEvent.accept() # accept event so that scrolling doesnt happen simultaneously else: - return super(customView, self).wheelEvent(QWheelEvent) # scroll if ctrl not pressed + return super(CustomView, self).wheelEvent(QWheelEvent) # scroll if ctrl not pressed @property def zoom(self): @@ -81,14 +81,14 @@ class customView(QGraphicsView): self._zoom = value self.scale(self.zoom / temp, self.zoom / temp) -class customScene(QGraphicsScene): +class CustomScene(QGraphicsScene): """ Extends QGraphicsScene with undo-redo functionality """ labelAdded = pyqtSignal(shapes.QGraphicsItem) def __init__(self, *args, parent=None): - super(customScene, self).__init__(*args, parent=parent) + super(CustomScene, self).__init__(*args, parent=parent) self.undoStack = QUndoStack(self) #Used to store undo-redo moves self.createActions() #creates necessary actions that need to be called for undo-redo @@ -133,7 +133,7 @@ class customScene(QGraphicsScene): if self.movingItem and event.button() == Qt.LeftButton: self.oldPos = self.movingItem.pos() #if left click is held, then store old pos self.clearSelection() #clears selected items - return super(customScene, self).mousePressEvent(event) + return super(CustomScene, self).mousePressEvent(event) def mouseReleaseEvent(self, event): # overloaded mouse release event to check if an item was moved @@ -142,4 +142,4 @@ class customScene(QGraphicsScene): #if item pos had changed, when mouse was realeased, emit itemMoved signal self.itemMoved(self.movingItem, self.oldPos) self.movingItem = None #clear movingitem reference - return super(customScene, self).mouseReleaseEvent(event) + return super(CustomScene, self).mouseReleaseEvent(event) diff --git a/src/main/python/utils/tabs.py b/src/main/python/utils/tabs.py index 1186191..cedb298 100644 --- a/src/main/python/utils/tabs.py +++ b/src/main/python/utils/tabs.py @@ -1,7 +1,7 @@ from PyQt5.QtWidgets import QTabBar, QPushButton, QTabWidget, QInputDialog from PyQt5.QtCore import pyqtSignal, QSize, Qt -class tabBarPlus(QTabBar): +class TabBarPlus(QTabBar): """ Just implemented to overload resize and layout change to emit a signal """ @@ -26,17 +26,17 @@ class tabBarPlus(QTabBar): self.nameChanged.emit(index, newName) -class customTabWidget(QTabWidget): +class CustomTabWidget(QTabWidget): """ QTabWidget with a new tab button, also catches layoutChange signal by - the tabBarPlus to dynamically move the button to the correct location + the TabBarPlus to dynamically move the button to the correct location """ plusClicked = pyqtSignal() def __init__(self, parent=None): - super(customTabWidget, self).__init__(parent) + super(CustomTabWidget, self).__init__(parent) - self.tab = tabBarPlus() - self.setTabBar(self.tab) #set tabBar to our custom tabBarPlus + self.tab = TabBarPlus() + self.setTabBar(self.tab) #set tabBar to our custom TabBarPlus self.plusButton = QPushButton('+', self) #create the new tab button #style the new tab button diff --git a/src/main/python/utils/toolbar.py b/src/main/python/utils/toolbar.py index a175aff..3f6338b 100644 --- a/src/main/python/utils/toolbar.py +++ b/src/main/python/utils/toolbar.py @@ -107,7 +107,7 @@ class toolbar(QDockWidget): #helper functions to create required buttons for itemClass in itemClasses: self.toolbarButtonDict[itemClass] = {} - label = sectionLabel(itemClass) + label = SectionLabel(itemClass) self.toolbarLabelDict[itemClass] = label for item in toolbarItems[itemClass].keys(): obj = toolbarItems[itemClass][item] @@ -165,7 +165,7 @@ class toolbarButton(QToolButton): #defines button size return QSize(55, 55) -class sectionLabel(QLabel): +class SectionLabel(QLabel): def __init__(self, *args): - super(sectionLabel, self).__init__(*args) \ No newline at end of file + super(SectionLabel, self).__init__(*args) \ No newline at end of file -- cgit From 56471d91921916feb033fba55c6902800ac52b65 Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 19 Jun 2020 15:39:45 +0530 Subject: refined stylesheet --- src/main/resources/base/app.qss | 77 ++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/src/main/resources/base/app.qss b/src/main/resources/base/app.qss index 1d21ab4..866d977 100644 --- a/src/main/resources/base/app.qss +++ b/src/main/resources/base/app.qss @@ -37,40 +37,39 @@ QLineEdit { QLineEdit:focus{ border-color: #7cabf9; } -tabBarPlus { - qproperty-drawBase: 0; +TabBarPlus { + /* qpropery-drawBase: 0; */ left: 5px; background-color: transparent; font-size: 15px; } -tabBarPlus:focus { +TabBarPlus:focus { border: 0px transparent black; } -tabBarPlus::close-button { +TabBarPlus::close-button { padding: 0px; margin: 0px; border-radius: 2px; - background-image: url("src/main/resources/base/ui/close.png"); background-position: center center; background-repeat: none; } -tabBarPlus::close-button:hover { +TabBarPlus::close-button:hover { background-color: #006666; } -tabBarPlus::close-button:pressed { +TabBarPlus::close-button:pressed { background-color: #adc5ed; } -tabBarPlus::scroller { /* the width of the scroll buttons */ +TabBarPlus::scroller { /* the width of the scroll buttons */ width: 20px; } -tabBarPlus::tab:top, -tabBarPlus::tab:bottom { +TabBarPlus::tab:top, +TabBarPlus::tab:bottom { color: black; border: 1px solid #b6b6b6; border-left-color: #e6e6e6; @@ -80,61 +79,60 @@ tabBarPlus::tab:bottom { width: 100px; } -tabBarPlus::tab:top:first, -tabBarPlus::tab:bottom:first { +TabBarPlus::tab:top:first, +TabBarPlus::tab:bottom:first { border-top-left-radius: 6px; border-bottom-left-radius: 6px; } -tabBarPlus::tab:top:last, -tabBarPlus::tab:bottom:last { +TabBarPlus::tab:top:last, +TabBarPlus::tab:bottom:last { border-top-right-radius: 6px; border-bottom-right-radius: 6px; border-right-width: 1px; } -tabBarPlus::tab:top:selected, -tabBarPlus::tab:bottom:selected { +TabBarPlus::tab:top:selected, +TabBarPlus::tab:bottom:selected { color: white; background-color: qlineargradient(spread:pad, x1:1, y1:0.545, x2:1, y2:0, stop:0 #00b0b0, stop:1 #00BaBa); border-color: #006666; } -tabBarPlus::tab:top:!selected:hover, -tabBarPlus::tab:bottom:!selected:hover { +TabBarPlus::tab:top:!selected:hover, +TabBarPlus::tab:bottom:!selected:hover { color: black; } -tabBarPlus::tab:top:only-one , -tabBarPlus::tab:bottom:only-one { +TabBarPlus::tab:top:only-one , +TabBarPlus::tab:bottom:only-one { border: 1px solid #1b3774; border-radius: 6px; } -fileWindow { +FileWindow { border-width: 2px; outline: 0; color: white; background-color: white; } -fileWindow::title{ +FileWindow::title{ color: black; border-width: 0px; text-align: center; background-color: white; } -fileWindow::close-button { +FileWindow::close-button { padding: 0px; margin: 0px; border-radius: 2px; - background-image: url("src/main/resources/base/ui/close.png"); background-position: center center; background-repeat: none; } -fileWindow::close-button:hover { +FileWindow::close-button:hover { background-color: #004646; } -fileWindow::close-button:pressed { +FileWindow::close-button:pressed { background-color: #adc5ed; } @@ -156,8 +154,6 @@ QDockWidget::float-button { background: transparent; subcontrol-origin: padding; subcontrol-position: right center; -} -QDockWidget::float-button { right: 4px; } @@ -166,7 +162,6 @@ QDockWidget::float-button:hover { } QDockWidget::float-button:pressed { - /*padding: 1px -1px -1px 1px;*/ background-color: #e0e0e0; } @@ -210,7 +205,7 @@ QToolButton:checked { border-color: #004646; } -sectionLabel{ +SectionLabel{ border-color: #00B8B8; border-style: solid; border-width: 0px 0px 2px 0px; @@ -222,11 +217,11 @@ sectionLabel{ color: gray; } -sectionLabel:first{ +SectionLabel:first{ margin-top: 0px; } -customView QPushButton{ +CustomView QPushButton{ background-color: rgba(214, 54, 40, 50%); border: 1px dashed white; border-radius: 2px; @@ -235,21 +230,21 @@ customView QPushButton{ padding: 1px 2px 3px 3px; color: rgba(255, 255, 255, 50%); } -customView QPushButton:Hover{ +CustomView QPushButton:Hover{ background-color: rgba(214, 54, 40, 90%); } -customTabWidget QPushButton{ +CustomTabWidget QPushButton{ background: rgba(230, 230, 227, 0%); padding: 1px; border: 0px solid #E6E6E3; border-radius: 10px; } -customTabWidget QPushButton:hover{ +CustomTabWidget QPushButton:hover{ background: rgba(230, 230, 227, 60%); } -customTabWidget::pane { +CustomTabWidget::pane { margin: 0px,1px,1px,1px; border: 2px solid #E6E6E3; border-radius: 7px; @@ -260,14 +255,14 @@ canvas{ bottom: -20px; } -customTabWidget customView { +CustomTabWidget CustomView { border-width: 0px; padding: 5px; border-radius: 2px; } -QHeaderView { +/* QHeaderView { qproperty-defaultAlignment: AlignHCenter; -} +} */ QHeaderView::section { padding: 4px; @@ -275,10 +270,6 @@ QHeaderView::section { background-color: white; } -/* QHeaderView::section:first{ - border-bottom: 1px solid black; -} */ - QTableView { border: none; } -- cgit From 26dbadc8840cbc5130411ac43f781f82e276bf7e Mon Sep 17 00:00:00 2001 From: Blaine Date: Fri, 19 Jun 2020 17:29:20 +0530 Subject: cleanup shapes context menu --- src/main/python/shapes/shapes.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/python/shapes/shapes.py b/src/main/python/shapes/shapes.py index 3efabbc..ca6490c 100644 --- a/src/main/python/shapes/shapes.py +++ b/src/main/python/shapes/shapes.py @@ -634,15 +634,12 @@ class NodeItem(QGraphicsSvgItem): """ # create a menu and add action contextMenu = QMenu() - addLableAction = contextMenu.addAction("add Label") # add action for text label - contextMenu.addAction("Rotate right", lambda : setattr(self, "rotation", self.rotation + 1)) - contextMenu.addAction("Rotate left", lambda : setattr(self, "rotation", self.rotation - 1)) + contextMenu.addAction("Add Label", lambda : setattr(self, "label", ItemLabel(self))) + contextMenu.addAction("Rotate right(E)", lambda : setattr(self, "rotation", self.rotation + 1)) + contextMenu.addAction("Rotate left(Q)", lambda : setattr(self, "rotation", self.rotation - 1)) contextMenu.addAction("Flip Horizontally", lambda: setattr(self, "flipH", not self.flipH)) contextMenu.addAction("Flip Vertically", lambda: setattr(self, "flipV", not self.flipV)) action = contextMenu.exec_(event.screenPos()) - # check for label action and add text label as child - if action == addLableAction: - self.label = ItemLabel(self) # text label as child def __getstate__(self): return { -- cgit From ea0deb05ec943e78652c7c637cbf171e1a697b63 Mon Sep 17 00:00:00 2001 From: Blaine Date: Sat, 20 Jun 2020 02:33:58 +0530 Subject: new symbol dialog --- src/main/python/main.py | 8 +- src/main/python/utils/custom.py | 236 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 src/main/python/utils/custom.py diff --git a/src/main/python/main.py b/src/main/python/main.py index 9683f00..db938b5 100644 --- a/src/main/python/main.py +++ b/src/main/python/main.py @@ -45,6 +45,8 @@ class appWindow(QMainWindow): redoAction.setShortcut(QKeySequence.Redo) self.menuEdit.addAction("Show Undo Stack", lambda x=self: x.activeScene.painter.createUndoView(self) ) + self.menuEdit.addSeparator() + self.menuEdit.addAction("Add new symbols", self.addSymbolWindow) self.menuGenerate = titleMenu.addMenu('Generate') #Generate menu imageAction = self.menuGenerate.addAction("Image", self.saveImage) @@ -89,7 +91,11 @@ class appWindow(QMainWindow): # graphic.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable) graphic.setPos(20, 20) currentDiagram.addItemPlus(graphic) - + + def addSymbolWindow(self): + from utils.custom import ShapeDialog + ShapeDialog(self).exec() + def newProject(self): #call to create a new file inside mdi area project = FileWindow(self.mdi) diff --git a/src/main/python/utils/custom.py b/src/main/python/utils/custom.py new file mode 100644 index 0000000..c21017e --- /dev/null +++ b/src/main/python/utils/custom.py @@ -0,0 +1,236 @@ +from PyQt5.QtCore import QRectF, Qt +from PyQt5.QtGui import ( + QBrush, QImage, QPainter, QPainterPath, QPen, QPixmap, QTransform) +from PyQt5.QtSvg import QGraphicsSvgItem +from PyQt5.QtWidgets import (QBoxLayout, QDialog, QFileDialog, + QGraphicsEllipseItem, QGraphicsItem, + QGraphicsScene, QGraphicsView, QGridLayout, + QLabel, QLineEdit, QPushButton, QInputDialog, QTextEdit) + +from shapes import SizeGripItem, directionsEnum + +from .app import fileImporter + +class ShapeDialog(QDialog): + + def __init__(self, parent=None): + super(ShapeDialog, self).__init__(parent) + self.resize(500, 300) + self.setWindowTitle("Add New Shapes") + self.createLayout() + self.graphic = None + + def createLayout(self): + importButton = QPushButton("Import", self) + importButton.clicked.connect(self.importSVG) + + saveButton = QPushButton("Save", self) + saveButton.clicked.connect(self.saveEvent) + + self.symbolName = QLineEdit(self) + self.symbolName.setPlaceholderText("Enter Symbol Name") + symbolNameLabel = QLabel("Symbol Name") + symbolNameLabel.setBuddy(self.symbolName) + + self.symbolClass = QLineEdit(self) + self.symbolClass.setPlaceholderText("Enter Symbol Class Name") + symbolClassLabel = QLabel("Symbol Class Name") + symbolClassLabel.setBuddy(self.symbolClass) + + self.symbolCategory = QLineEdit(self) + self.symbolCategory.setPlaceholderText("Enter Symbol Category") + symbolCategoryLabel = QLabel("Symbol Category") + symbolCategoryLabel.setBuddy(self.symbolCategory) + + addGripItem = QPushButton("Add Grip Item", self) + addGripItem.clicked.connect(self.addGrip) + addLineGripItem = QPushButton("Add Line Grip Item", self) + addLineGripItem.clicked.connect(self.addLineGrip) + + self.painter = QGraphicsScene() + view = QGraphicsView(self.painter) + + layout = QGridLayout(self) + + subLayout = QBoxLayout(QBoxLayout.LeftToRight) + subLayout.addWidget(importButton) + subLayout.addWidget(saveButton) + subLayout.addStretch(1) + + layout.addLayout(subLayout, 0, 0, 1, -1) + + subLayout2 = QBoxLayout(QBoxLayout.LeftToRight) + subLayout2.addWidget(view, stretch=1) + + subLayout3 = QBoxLayout(QBoxLayout.TopToBottom) + subLayout3.addWidget(symbolNameLabel) + subLayout3.addWidget(self.symbolName) + subLayout3.addWidget(symbolClassLabel) + subLayout3.addWidget(self.symbolClass) + subLayout3.addWidget(symbolCategoryLabel) + subLayout3.addWidget(self.symbolCategory) + subLayout3.addStretch(1) + subLayout3.addWidget(addGripItem) + subLayout3.addWidget(addLineGripItem) + subLayout2.addLayout(subLayout3) + + layout.addLayout(subLayout2, 1, 0, -1, -1) + self.setLayout(layout) + + def importSVG(self): + name = QFileDialog.getOpenFileName(self, 'Open SVG File', '', 'Scalable Vector Graphics (*svg)') + if name: + self.graphic = QGraphicsSvgItem(name[0]) + self.graphic.setZValue(-1) + self.painter.addItem(self.graphic) + + def saveEvent(self): + if self.graphic is None: + return + + itemName = self.symbolName.text() + if itemName is '': + return + + className = self.symbolClass.text() + if className is '': + return + + category = self.symbolCategory.text() + if category == "": + category = "misc" + + graphicRect = self.graphic.boundingRect() + + image = QImage(64, 64, QImage.Format_ARGB32) + printer = QPainter(image) + self.graphic.renderer().render(printer, graphicRect) + printer.end() + + #save file + name = QFileDialog.getSaveFileName(self, 'Save Icon', className, 'PNG (*.png)') + if name: + image.save(name[0], "PNG") + else: + return + + gripList = [] + x, y, w, h = graphicRect.getRect() + for i in self.grips: + pos = i.pos() + entry = [abs((x-pos.x())/w), abs((y-pos.y())/h), i.location] + if isinstance(i, gripRect): + if i.location in ["top", "bottom"]: + entry.append(h) + else: + entry.append(w) + gripList.append(entry) + + temp = QDialog(self) + tempLayout = QBoxLayout(QBoxLayout.TopToBottom) + output = OutputBox(temp, f""" + class {className}(NodeItem): + def __init__(self): + super({className}, self).__init__("svg/{category}/{name[0]}") + self.grips = {gripList} + """) + tempLayout.addWidget(output) + temp.setLayout(tempLayout) + temp.exec() + + @property + def grips(self): + return [i for i in self.painter.items() if isinstance(i, gripAbstract)] + + def addGrip(self): + grip = gripDot() + self.painter.addItem(grip) + + def addLineGrip(self): + rect = gripRect() + self.painter.addItem(rect) + +class gripAbstract(QGraphicsItem): + + def __init__(self): + super(gripAbstract, self).__init__() + self.location = "top" + self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable) + + def mouseDoubleClickEvent(self, event): + self.location, _ = QInputDialog.getItem(None, "Change location", "Select location", directionsEnum, + directionsEnum.index(self.location), False) + +class gripRect(gripAbstract): + def __init__(self, x=0, y=0, w=80, h=10 ): + super(gripRect, self).__init__() + self.rotation = 0 + self.sizeGripItems = [] + self.width = w + self.height = h + self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) + + def boundingRect(self): + return QRectF(-self.width / 2, -self.height / 2, self.width, self.height) + + def paint(self, painter, option, index): + painter.setPen(QPen(Qt.black, 1, Qt.SolidLine)) + painter.setBrush(QBrush(Qt.red)) + painter.drawRect(self.boundingRect()) + + def addGripItems(self): + for i, (direction) in enumerate((Qt.Vertical, + Qt.Horizontal, + Qt.Vertical, + Qt.Horizontal)): + self.sizeGripItems.append(SizeGripItem(i, direction, parent=self)) + + def updateSizeGripItem(self, index_no_updates=None): + index_no_updates = index_no_updates or [] + for i, item in enumerate(self.sizeGripItems): + if i not in index_no_updates: + item.updatePosition() + + def itemChange(self, change, value): + if change == QGraphicsItem.ItemPositionHasChanged: + # update grips + self.updateSizeGripItem() + return + # check if item is add on scene + if change == QGraphicsItem.ItemSceneHasChanged and self.scene(): + # add grips and update them + self.addGripItems() + self.updateSizeGripItem() + return + return super(gripRect, self).itemChange(change, value) + + def resize(self, index, movement): + self.prepareGeometryChange() + if index in [0, 1]: + self.width -= movement.x() + self.height -= movement.y() + else: + self.width += movement.x() + self.height += movement.y() + transform = QTransform() + transform.translate(movement.x() / 2, movement.y() / 2) + self.setTransform(transform, True) + self.updateSizeGripItem([index]) + +class gripDot(gripAbstract): + def boundingRect(self): + return QRectF(0, 0, 10, 10) + + def paint(self, painter, option, index): + painter.setPen(QPen(Qt.black, 1, Qt.SolidLine)) + painter.setBrush(QBrush(Qt.red)) + painter.drawEllipse(self.boundingRect()) + +class OutputBox(QTextEdit): + + def __init__(self, parent, text): + super(OutputBox, self).__init__(parent) + self.setReadOnly(True) + self.resize(600, 300) + self.text = text + self.setMarkdown("```python\n"+text+"\n```") \ No newline at end of file -- cgit From 4911198e0e84aca58ca2fbe468415e6e98928e48 Mon Sep 17 00:00:00 2001 From: Blaine Date: Sat, 20 Jun 2020 03:09:27 +0530 Subject: create build script --- build.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 build.py diff --git a/build.py b/build.py new file mode 100644 index 0000000..9b5a543 --- /dev/null +++ b/build.py @@ -0,0 +1,13 @@ +from fbs.cmdline import command +from os.path import dirname +import subprocess + +import fbs.cmdline + +@command +def compileResources(): + subprocess.call("pyrcc5 -o src/main/python/resources/resources.py src/main/ui/resources.rcc", shell=True) + +if __name__ == '__main__': + project_dir = dirname(__file__) + fbs.cmdline.main(project_dir) \ No newline at end of file -- cgit From 85e80194d39f8059f90da32ffba2760a46db56a8 Mon Sep 17 00:00:00 2001 From: Blaine Date: Sat, 20 Jun 2020 03:09:52 +0530 Subject: migrate requirements to requirements/base --- requirements.txt | 2 -- requirements/base.txt | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 requirements.txt create mode 100644 requirements/base.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 0c683ba..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -PyQt5 -fbs diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 0000000..2800691 --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1,3 @@ +PyQt5 +fbs +qstylizer \ No newline at end of file -- cgit From d6946c9a3659eae563e33b3d5ac3aa026cf5b75a Mon Sep 17 00:00:00 2001 From: Blaine Date: Sat, 20 Jun 2020 03:10:28 +0530 Subject: use Qresource to implement close icon --- src/main/python/resources/__init__.py | 0 src/main/python/resources/resources.py | 65 +++++++++++++++++++++++++++++++++ src/main/python/utils/app.py | 4 +- src/main/resources/base/app.qss | 8 ++-- src/main/resources/base/ui/close.png | Bin 255 -> 0 bytes src/main/ui/close.png | Bin 0 -> 255 bytes src/main/ui/resources.rcc | 5 +++ 7 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 src/main/python/resources/__init__.py create mode 100644 src/main/python/resources/resources.py delete mode 100644 src/main/resources/base/ui/close.png create mode 100644 src/main/ui/close.png create mode 100644 src/main/ui/resources.rcc diff --git a/src/main/python/resources/__init__.py b/src/main/python/resources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/python/resources/resources.py b/src/main/python/resources/resources.py new file mode 100644 index 0000000..51952b7 --- /dev/null +++ b/src/main/python/resources/resources.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created by: The Resource Compiler for PyQt5 (Qt v5.15.0) +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore + +qt_resource_data = b"\ +\x00\x00\x00\xff\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x0e\x00\x00\x00\x0e\x08\x06\x00\x00\x00\x1f\x48\x2d\xd1\ +\x00\x00\x00\x06\x62\x4b\x47\x44\x00\x00\x00\x00\x00\x00\xf9\x43\ +\xbb\x7f\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ +\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\ +\xdf\x06\x09\x0b\x33\x34\x04\x7e\xc0\x14\x00\x00\x00\x1d\x69\x54\ +\x58\x74\x43\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x72\ +\x65\x61\x74\x65\x64\x20\x77\x69\x74\x68\x20\x47\x49\x4d\x50\x64\ +\x2e\x65\x07\x00\x00\x00\x63\x49\x44\x41\x54\x28\x53\x63\x60\x18\ +\x48\xe0\x03\xb4\x1c\x84\x71\x01\x14\x79\x26\x2c\xaa\xb0\x69\xc6\ +\x10\x63\x44\xd3\x88\xac\x60\x0b\x54\x0e\x9b\x18\x03\xba\x46\x90\ +\x5a\x0c\xd3\xa1\x06\xc0\x0c\x02\x73\xb1\x69\xc4\xa6\x19\x45\x13\ +\x48\x01\x36\x3f\x42\x2d\xc0\x4f\x61\xb3\x91\x2c\xa7\x62\x0b\x08\ +\x6c\x62\x28\x7e\xc4\xaa\x00\xea\x60\x0c\x39\x6c\x7e\xc4\x08\x08\ +\xa0\x66\x6c\x62\xf8\x03\x81\xea\xb2\x00\xa1\x91\x0c\x3d\x2a\x6a\ +\x45\xe6\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +" + +qt_resource_name = b"\ +\x00\x09\ +\x06\x99\x2e\x3e\ +\x00\x63\ +\x00\x6c\x00\x6f\x00\x73\x00\x65\x00\x49\x00\x63\x00\x6f\x00\x6e\ +" + +qt_resource_struct_v1 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +" + +qt_resource_struct_v2 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x72\xa2\x8c\x44\x86\ +" + +qt_version = [int(v) for v in QtCore.qVersion().split('.')] +if qt_version < [5, 8, 0]: + rcc_version = 1 + qt_resource_struct = qt_resource_struct_v1 +else: + rcc_version = 2 + qt_resource_struct = qt_resource_struct_v2 + +def qInitResources(): + QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/src/main/python/utils/app.py b/src/main/python/utils/app.py index 60ae47f..275ef7e 100644 --- a/src/main/python/utils/app.py +++ b/src/main/python/utils/app.py @@ -3,12 +3,14 @@ Declare fbs application so that it can be imported in other modules. """ from fbs_runtime.application_context.PyQt5 import ApplicationContext -from PyQt5.QtCore import QSettings, pyqtProperty +from PyQt5.QtCore import QSettings, pyqtProperty, QResource from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QWidget from json import JSONEncoder, dumps, loads, dump, load from os.path import join +from resources import resources + app = ApplicationContext() settings = QSettings(QSettings.IniFormat, QSettings.UserScope ,"FOSSEE", "Chemical-PFD") version = app.build_settings['version'] diff --git a/src/main/resources/base/app.qss b/src/main/resources/base/app.qss index 866d977..cd11034 100644 --- a/src/main/resources/base/app.qss +++ b/src/main/resources/base/app.qss @@ -38,7 +38,7 @@ QLineEdit:focus{ border-color: #7cabf9; } TabBarPlus { - /* qpropery-drawBase: 0; */ + qproperty-drawBase: 0; left: 5px; background-color: transparent; font-size: 15px; @@ -49,6 +49,7 @@ TabBarPlus:focus { } TabBarPlus::close-button { + background-image: url(:/closeIcon); padding: 0px; margin: 0px; border-radius: 2px; @@ -260,9 +261,10 @@ CustomTabWidget CustomView { padding: 5px; border-radius: 2px; } -/* QHeaderView { + +QHeaderView { qproperty-defaultAlignment: AlignHCenter; -} */ +} QHeaderView::section { padding: 4px; diff --git a/src/main/resources/base/ui/close.png b/src/main/resources/base/ui/close.png deleted file mode 100644 index 8771a0b..0000000 Binary files a/src/main/resources/base/ui/close.png and /dev/null differ diff --git a/src/main/ui/close.png b/src/main/ui/close.png new file mode 100644 index 0000000..8771a0b Binary files /dev/null and b/src/main/ui/close.png differ diff --git a/src/main/ui/resources.rcc b/src/main/ui/resources.rcc new file mode 100644 index 0000000..b7dcdfc --- /dev/null +++ b/src/main/ui/resources.rcc @@ -0,0 +1,5 @@ + + + close.png + + -- cgit From fc4db2978591b1ce62f2f7399e2a74c07b046d62 Mon Sep 17 00:00:00 2001 From: Blaine Date: Sat, 20 Jun 2020 11:47:19 +0530 Subject: improve custom icon image + class grip list --- src/main/python/utils/custom.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/main/python/utils/custom.py b/src/main/python/utils/custom.py index c21017e..5625ea4 100644 --- a/src/main/python/utils/custom.py +++ b/src/main/python/utils/custom.py @@ -1,16 +1,18 @@ -from PyQt5.QtCore import QRectF, Qt -from PyQt5.QtGui import ( - QBrush, QImage, QPainter, QPainterPath, QPen, QPixmap, QTransform) +from PyQt5.QtCore import QRectF, Qt, QSize +from PyQt5.QtGui import (QBrush, QIcon, QImage, QPainter, QPainterPath, QPen, + QPixmap, QTransform) from PyQt5.QtSvg import QGraphicsSvgItem from PyQt5.QtWidgets import (QBoxLayout, QDialog, QFileDialog, QGraphicsEllipseItem, QGraphicsItem, QGraphicsScene, QGraphicsView, QGridLayout, - QLabel, QLineEdit, QPushButton, QInputDialog, QTextEdit) + QInputDialog, QLabel, QLineEdit, QPushButton, + QTextEdit) from shapes import SizeGripItem, directionsEnum from .app import fileImporter + class ShapeDialog(QDialog): def __init__(self, parent=None): @@ -78,9 +80,9 @@ class ShapeDialog(QDialog): self.setLayout(layout) def importSVG(self): - name = QFileDialog.getOpenFileName(self, 'Open SVG File', '', 'Scalable Vector Graphics (*svg)') - if name: - self.graphic = QGraphicsSvgItem(name[0]) + self.name = QFileDialog.getOpenFileName(self, 'Open SVG File', '', 'Scalable Vector Graphics (*svg)') + if self.name: + self.graphic = QGraphicsSvgItem(self.name[0]) self.graphic.setZValue(-1) self.painter.addItem(self.graphic) @@ -102,15 +104,12 @@ class ShapeDialog(QDialog): graphicRect = self.graphic.boundingRect() - image = QImage(64, 64, QImage.Format_ARGB32) - printer = QPainter(image) - self.graphic.renderer().render(printer, graphicRect) - printer.end() + QIcon #save file name = QFileDialog.getSaveFileName(self, 'Save Icon', className, 'PNG (*.png)') if name: - image.save(name[0], "PNG") + QIcon(self.name[0]).pixmap(QSize(64, 64)).toImage().save(name[0]) else: return @@ -118,20 +117,19 @@ class ShapeDialog(QDialog): x, y, w, h = graphicRect.getRect() for i in self.grips: pos = i.pos() - entry = [abs((x-pos.x())/w), abs((y-pos.y())/h), i.location] + entry = [abs((x-pos.x())/w)*100, abs((y-pos.y())/h)*100, i.location] if isinstance(i, gripRect): if i.location in ["top", "bottom"]: - entry.append(h) + entry.append(i.height) else: - entry.append(w) + entry.append(i.width) gripList.append(entry) temp = QDialog(self) tempLayout = QBoxLayout(QBoxLayout.TopToBottom) - output = OutputBox(temp, f""" - class {className}(NodeItem): + output = OutputBox(temp, f"""class {className}(NodeItem): def __init__(self): - super({className}, self).__init__("svg/{category}/{name[0]}") + super({className}, self).__init__("svg/{category}/{str.split(name[0], "/")[-1][:-4]}") self.grips = {gripList} """) tempLayout.addWidget(output) @@ -233,4 +231,4 @@ class OutputBox(QTextEdit): self.setReadOnly(True) self.resize(600, 300) self.text = text - self.setMarkdown("```python\n"+text+"\n```") \ No newline at end of file + self.setMarkdown("```python\n"+text+"\n```") -- cgit From 7ff2d30a1c16216f3bfaebc8f71924d6a6f8ec8d Mon Sep 17 00:00:00 2001 From: Blaine Date: Sat, 20 Jun 2020 12:35:09 +0530 Subject: complete string formatting --- src/main/python/utils/custom.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/main/python/utils/custom.py b/src/main/python/utils/custom.py index 5625ea4..7e6b197 100644 --- a/src/main/python/utils/custom.py +++ b/src/main/python/utils/custom.py @@ -125,13 +125,31 @@ class ShapeDialog(QDialog): entry.append(i.width) gripList.append(entry) + + grips = ",\n ".join([str(i) for i in gripList]) if gripList else "" + if grips: + grips = "self.grips = [" + grips + "]\n" temp = QDialog(self) tempLayout = QBoxLayout(QBoxLayout.TopToBottom) - output = OutputBox(temp, f"""class {className}(NodeItem): - def __init__(self): - super({className}, self).__init__("svg/{category}/{str.split(name[0], "/")[-1][:-4]}") - self.grips = {gripList} - """) + output = OutputBox(temp, f""" + Class Definition: +
+class {className}(NodeItem):
+    def __init__(self):
+        super({className}, self).__init__("svg/{category}/{str.split(name[0], "/")[-1][:-4]}")
+    {grips}
+
+ Items.json entry: +
+"{category}": {{
+    "{itemName}": {{
+        "name": "{itemName}",
+        "icon": ".\\{category}\\{str.split(name[0], "/")[-1]}",
+        "class": "{category}",
+        "object": "{className}",
+        "args": []
+    }}
+}}
""") tempLayout.addWidget(output) temp.setLayout(tempLayout) temp.exec() @@ -229,6 +247,5 @@ class OutputBox(QTextEdit): def __init__(self, parent, text): super(OutputBox, self).__init__(parent) self.setReadOnly(True) - self.resize(600, 300) - self.text = text - self.setMarkdown("```python\n"+text+"\n```") + self.setFontWeight(10) + self.setHtml(text) \ No newline at end of file -- cgit From f8519766d7f7fc70180411f912ed8ca15abb6a95 Mon Sep 17 00:00:00 2001 From: Blaine Date: Sat, 20 Jun 2020 14:43:57 +0530 Subject: refactor and comment code --- src/main/python/main.py | 63 +++++++++++++++-------------- src/main/python/shapes/shapes.py | 3 ++ src/main/python/utils/app.py | 18 +++++---- src/main/python/utils/canvas.py | 25 +++++++----- src/main/python/utils/custom.py | 64 +++++++++++++++++++++++------ src/main/python/utils/dialogs.py | 1 + src/main/python/utils/fileWindow.py | 13 +++--- src/main/python/utils/graphics.py | 8 ---- src/main/python/utils/layout.py | 16 ++++++++ src/main/python/utils/streamTable.py | 78 +++++++++++++++++++++++++----------- src/main/python/utils/tabs.py | 3 +- src/main/python/utils/toolbar.py | 2 - src/main/python/utils/undo.py | 1 + 13 files changed, 192 insertions(+), 103 deletions(-) diff --git a/src/main/python/main.py b/src/main/python/main.py index db938b5..35c95c3 100644 --- a/src/main/python/main.py +++ b/src/main/python/main.py @@ -25,8 +25,30 @@ class appWindow(QMainWindow): super(appWindow, self).__init__(parent) #create the menu bar + self.createMenuBar() + + self.mdi = QMdiArea(self) #create area for files to be displayed + self.mdi.setObjectName('mdi area') + + #create toolbar and add the toolbar plus mdi to layout + self.createToolbar() + + #set flags so that window doesnt look weird + self.mdi.setOption(QMdiArea.DontMaximizeSubWindowOnActivation, True) + self.mdi.setTabsClosable(True) + self.mdi.setTabsMovable(True) + self.mdi.setDocumentMode(False) + + #declare main window layout + self.setCentralWidget(self.mdi) + # self.resize(1280, 720) #set collapse dim + self.mdi.subWindowActivated.connect(self.tabSwitched) + self.readSettings() + + def createMenuBar(self): + # Fetches a reference to the menu bar in the main window, and adds actions to it. + titleMenu = self.menuBar() #fetch reference to current menu bar - # self.mainWidget.setObjectName("Main Widget") self.menuFile = titleMenu.addMenu('File') #File Menu newAction = self.menuFile.addAction("New", self.newProject) @@ -54,25 +76,7 @@ class appWindow(QMainWindow): imageAction.setShortcut(QKeySequence("Ctrl+P")) reportAction.setShortcut(QKeySequence("Ctrl+R")) - - self.mdi = QMdiArea(self) #create area for files to be displayed - self.mdi.setObjectName('mdi area') - - #create toolbar and add the toolbar plus mdi to layout - self.createToolbar() - - #set flags so that window doesnt look weird - self.mdi.setOption(QMdiArea.DontMaximizeSubWindowOnActivation, True) - self.mdi.setTabsClosable(True) - self.mdi.setTabsMovable(True) - self.mdi.setDocumentMode(False) - - #declare main window layout - self.setCentralWidget(self.mdi) - # self.resize(1280, 720) #set collapse dim - self.mdi.subWindowActivated.connect(self.tabSwitched) - self.readSettings() - + def createToolbar(self): #place holder for toolbar with fixed width, layout may change self.toolbar = toolbar(self) @@ -83,16 +87,16 @@ class appWindow(QMainWindow): self.toolbar.populateToolbar() def toolButtonClicked(self, object): + # To add the corresponding symbol for the clicked button to active scene. if self.mdi.currentSubWindow(): currentDiagram = self.mdi.currentSubWindow().tabber.currentWidget().painter if currentDiagram: graphic = getattr(shapes, object['object'])(*map(lambda x: int(x) if x.isdigit() else x, object['args'])) - # graphic.setPen(QPen(Qt.black, 2)) - # graphic.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable) - graphic.setPos(20, 20) + graphic.setPos(50, 50) currentDiagram.addItemPlus(graphic) def addSymbolWindow(self): + # Opens the add symbol window when requested from utils.custom import ShapeDialog ShapeDialog(self).exec() @@ -103,7 +107,6 @@ class appWindow(QMainWindow): self.mdi.addSubWindow(project) if not project.tabList: # important when unpickling a file instead project.newDiagram() #create a new tab in the new file - # project.resizeHandler() project.fileCloseEvent.connect(self.fileClosed) #closed file signal to switch to sub window view if self.count > 1: #switch to tab view if needed self.mdi.setViewMode(QMdiArea.TabbedView) @@ -118,6 +121,7 @@ class appWindow(QMainWindow): projectData = load(file) project = FileWindow(self.mdi) self.mdi.addSubWindow(project) + #create blank window and set its state project.__setstate__(projectData) project.resizeHandler() project.fileCloseEvent.connect(self.fileClosed) @@ -127,7 +131,7 @@ class appWindow(QMainWindow): self.mdi.setViewMode(QMdiArea.TabbedView) def saveProject(self): - #pickle all files in mdi area + #serialize all files in mdi area for j, i in enumerate(self.activeFiles): #get list of all windows with atleast one tab if i.tabCount: name = QFileDialog.getSaveFileName(self, 'Save File', f'New Diagram {j}', 'Process Flow Diagram (*.pfd)') @@ -170,6 +174,7 @@ class appWindow(QMainWindow): self.mdi.setViewMode(QMdiArea.SubWindowView) def writeSettings(self): + # write window state on window close settings.beginGroup("MainWindow") settings.setValue("maximized", self.isMaximized()) if not self.isMaximized(): @@ -178,6 +183,7 @@ class appWindow(QMainWindow): settings.endGroup() def readSettings(self): + # read window state when app launches settings.beginGroup("MainWindow") self.resize(settings.value("size", QSize(1280, 720))) self.move(settings.value("pos", QPoint(320, 124))) @@ -185,6 +191,7 @@ class appWindow(QMainWindow): self.showMaximized() settings.endGroup() + #useful one liner properties for getting data @property def activeFiles(self): return [i for i in self.mdi.subWindowList() if i.tabCount] @@ -219,11 +226,9 @@ class appWindow(QMainWindow): if self.mdi.activeSubWindow() and self.mdi.activeSubWindow().tabber.currentWidget(): for item in self.mdi.activeSubWindow().tabber.currentWidget().painter.selectedItems(): item.rotation += 1 - - - + if __name__ == '__main__': # 1. Instantiate ApplicationContext main = appWindow() main.show() - exit_code = app.app.exec_() # 2. Invoke appctxt.app.exec_() + exit_code = app.app.exec_() # 2. Invoke app.app.exec_() sys.exit(exit_code) diff --git a/src/main/python/shapes/shapes.py b/src/main/python/shapes/shapes.py index ca6490c..5876f6e 100644 --- a/src/main/python/shapes/shapes.py +++ b/src/main/python/shapes/shapes.py @@ -12,6 +12,7 @@ from PyQt5.QtWidgets import (QGraphicsColorizeEffect, QGraphicsEllipseItem, from .line import Line, findIndex from utils.app import fileImporter +# enum for all directions for line grip items directionsEnum = [ "top", "right", @@ -19,6 +20,7 @@ directionsEnum = [ "left" ] +# orientation enum for size grip items orientationEnum = [ Qt.Horizontal, Qt.Vertical @@ -469,6 +471,7 @@ class NodeItem(QGraphicsSvgItem): return self.flipState[1] def updateTransformation(self): + # update transformation on flipstate or rotation change transform = QTransform() h = -1 if self.flipH else 1 w = -1 if self.flipV else 1 diff --git a/src/main/python/utils/app.py b/src/main/python/utils/app.py index 275ef7e..39c3674 100644 --- a/src/main/python/utils/app.py +++ b/src/main/python/utils/app.py @@ -1,5 +1,5 @@ """ -Declare fbs application so that it can be imported in other modules. +Declare fbs application and various contextual variables so that it can be imported in other modules. """ from fbs_runtime.application_context.PyQt5 import ApplicationContext @@ -9,7 +9,7 @@ from PyQt5.QtWidgets import QWidget from json import JSONEncoder, dumps, loads, dump, load from os.path import join -from resources import resources +from resources import resources #application resources defined in resources.qrc app = ApplicationContext() settings = QSettings(QSettings.IniFormat, QSettings.UserScope ,"FOSSEE", "Chemical-PFD") @@ -19,11 +19,14 @@ def fileImporter(*file): # Helper function to fetch files from src/main/resources return app.get_resource(join(*file)) +#set application stylesheet with open(fileImporter("app.qss"), "r") as stylesheet: app.app.setStyleSheet(stylesheet.read()) class JSON_Encoder: - + """ + Defines serialization methods for differnt data types for json module + """ def _encode(obj): if isinstance(obj, dict): ## We'll need to iterate not just the value that default() usually gets passed @@ -50,7 +53,9 @@ class JSON_Encoder: return obj class JSON_Typer(JSONEncoder): - + """ + derived class for redirecting encode calls + """ def default(self, o): return o.__getstate__() @@ -60,7 +65,4 @@ class JSON_Typer(JSONEncoder): def encode(self, obj): return super(JSON_Typer, self).encode(self._encode(obj)) - -importer = pyqtProperty(str, fileImporter) - -memMap = {} \ No newline at end of file +memMap = {} #memory map for id references for loading projects \ No newline at end of file diff --git a/src/main/python/utils/canvas.py b/src/main/python/utils/canvas.py index 8608f7b..68b0902 100644 --- a/src/main/python/utils/canvas.py +++ b/src/main/python/utils/canvas.py @@ -42,6 +42,9 @@ class canvas(CustomView): self.customContextMenuRequested.connect(self.sideViewContextMenu) def addStreamTable(self, pos=QPointF(0, 0), table=None): + """ + build stream table at pos with table, if table is not passed, builds a blank table + """ self.streamTable = table if table else streamTable(self.labelItems, canvas=self) self.streamTableRect = moveRect() @@ -53,10 +56,16 @@ class canvas(CustomView): self.streamTableRect.setPos(pos) def updateStreamTable(self, item): + """ + updates stream table with any new line labels added + """ if self.streamTable: self.streamTable.model.insertColumn(item = item) def sideViewContextMenu(self, pos): + """ + shows the context menu for the side view + """ self.parentFileWindow.sideViewContextMenu(self.mapTo(self.parentFileWindow, pos)) def resizeView(self, w, h): @@ -69,11 +78,6 @@ class canvas(CustomView): frameWidth = self.frameWidth() #update view size self.setSceneRect(0, 0, width - frameWidth*2, height) - - def resizeEvent(self, event): - #overloaded function to also view size on window update - # self.adjustView() - pass def setCanvasSize(self, size): """ @@ -152,6 +156,7 @@ class canvas(CustomView): self.landscape = dict['landscape'] self.setObjectName(dict['ObjectName']) + #load symbols from the file, while building the memory map as well. for item in dict['symbols']: graphic = getattr(shapes, item['_classname_'])() graphic.__setstate__(dict = item) @@ -168,6 +173,7 @@ class canvas(CustomView): graphic.rotation = item['rotation'] graphic.flipH, graphic.flipV = item['flipstate'] + #load lines from the file, while using and building the memory map. for item in dict['lines']: line = shapes.Line(QPointF(*item['startPoint']), QPointF(*item['endPoint'])) memMap[item['id']] = line @@ -192,14 +198,11 @@ class canvas(CustomView): line.updateLine() line.addGrabber() + # add streamtable if it existed in the scene. if dict['streamTable']: table = streamTable(self.labelItems, self) self.addStreamTable(QPointF(*dict['streamTable'][1]), table) table.__setstate__(dict['streamTable'][0]) - memMap.clear() - self.painter.advance() - - - - \ No newline at end of file + memMap.clear() #clear out memory map as we now have no use for it. Hopefully garbage collected + self.painter.advance() #request collision detection \ No newline at end of file diff --git a/src/main/python/utils/custom.py b/src/main/python/utils/custom.py index 7e6b197..fedecdb 100644 --- a/src/main/python/utils/custom.py +++ b/src/main/python/utils/custom.py @@ -1,3 +1,6 @@ +""" +Holds the custom window to generate new symbols, can be called while running or throught the build interface. +""" from PyQt5.QtCore import QRectF, Qt, QSize from PyQt5.QtGui import (QBrush, QIcon, QImage, QPainter, QPainterPath, QPen, QPixmap, QTransform) @@ -12,17 +15,19 @@ from shapes import SizeGripItem, directionsEnum from .app import fileImporter - class ShapeDialog(QDialog): - + """ + The main dialog box for the custom symbol window. + """ def __init__(self, parent=None): super(ShapeDialog, self).__init__(parent) - self.resize(500, 300) + self.resize(500, 300) # resize to a fixed dim self.setWindowTitle("Add New Shapes") self.createLayout() self.graphic = None def createLayout(self): + #build layout for the dialog box importButton = QPushButton("Import", self) importButton.clicked.connect(self.importSVG) @@ -80,6 +85,7 @@ class ShapeDialog(QDialog): self.setLayout(layout) def importSVG(self): + # Imports svg file through user input, adds it to the scene and stores it as a reference self.name = QFileDialog.getOpenFileName(self, 'Open SVG File', '', 'Scalable Vector Graphics (*svg)') if self.name: self.graphic = QGraphicsSvgItem(self.name[0]) @@ -87,6 +93,9 @@ class ShapeDialog(QDialog): self.painter.addItem(self.graphic) def saveEvent(self): + # executes the build procedure + + #check if all necessary values are there, each is seperate to show qalerts later on if self.graphic is None: return @@ -102,10 +111,9 @@ class ShapeDialog(QDialog): if category == "": category = "misc" + # get rect for calculating grip positions graphicRect = self.graphic.boundingRect() - - QIcon - + #save file name = QFileDialog.getSaveFileName(self, 'Save Icon', className, 'PNG (*.png)') if name: @@ -113,6 +121,7 @@ class ShapeDialog(QDialog): else: return + #calculate grip positions and build a list gripList = [] x, y, w, h = graphicRect.getRect() for i in self.grips: @@ -125,10 +134,12 @@ class ShapeDialog(QDialog): entry.append(i.width) gripList.append(entry) - + # format list in class definition flavor grips = ",\n ".join([str(i) for i in gripList]) if gripList else "" if grips: grips = "self.grips = [" + grips + "]\n" + + # build output dialog box temp = QDialog(self) tempLayout = QBoxLayout(QBoxLayout.TopToBottom) output = OutputBox(temp, f""" @@ -159,15 +170,19 @@ class {className}(NodeItem): return [i for i in self.painter.items() if isinstance(i, gripAbstract)] def addGrip(self): + #adds a grip dot to the scene grip = gripDot() self.painter.addItem(grip) def addLineGrip(self): + #adds a line grip item rect = gripRect() self.painter.addItem(rect) class gripAbstract(QGraphicsItem): - + """ + Abstract class for mouse click behaviour + """ def __init__(self): super(gripAbstract, self).__init__() self.location = "top" @@ -178,6 +193,10 @@ class gripAbstract(QGraphicsItem): directionsEnum.index(self.location), False) class gripRect(gripAbstract): + """ + simulates line grip item with resizeablity + (Haha grip items on grip items. Progress) + """ def __init__(self, x=0, y=0, w=80, h=10 ): super(gripRect, self).__init__() self.rotation = 0 @@ -185,7 +204,8 @@ class gripRect(gripAbstract): self.width = w self.height = h self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) - + + #bounding rect and paint need to be implemented def boundingRect(self): return QRectF(-self.width / 2, -self.height / 2, self.width, self.height) @@ -193,8 +213,9 @@ class gripRect(gripAbstract): painter.setPen(QPen(Qt.black, 1, Qt.SolidLine)) painter.setBrush(QBrush(Qt.red)) painter.drawRect(self.boundingRect()) - + def addGripItems(self): + # adds the resizeable line grip items from the shape file :D for i, (direction) in enumerate((Qt.Vertical, Qt.Horizontal, Qt.Vertical, @@ -202,12 +223,14 @@ class gripRect(gripAbstract): self.sizeGripItems.append(SizeGripItem(i, direction, parent=self)) def updateSizeGripItem(self, index_no_updates=None): + #update positions for existing grip items index_no_updates = index_no_updates or [] for i, item in enumerate(self.sizeGripItems): if i not in index_no_updates: item.updatePosition() def itemChange(self, change, value): + #do the needful when item is updated if change == QGraphicsItem.ItemPositionHasChanged: # update grips self.updateSizeGripItem() @@ -221,6 +244,7 @@ class gripRect(gripAbstract): return super(gripRect, self).itemChange(change, value) def resize(self, index, movement): + #resize method self.prepareGeometryChange() if index in [0, 1]: self.width -= movement.x() @@ -233,7 +257,10 @@ class gripRect(gripAbstract): self.setTransform(transform, True) self.updateSizeGripItem([index]) -class gripDot(gripAbstract): +class gripDot(gripAbstract): + """ + class for circular grips + """ def boundingRect(self): return QRectF(0, 0, 10, 10) @@ -243,9 +270,20 @@ class gripDot(gripAbstract): painter.drawEllipse(self.boundingRect()) class OutputBox(QTextEdit): - + """ + Defines a read only text box for class output + """ def __init__(self, parent, text): super(OutputBox, self).__init__(parent) self.setReadOnly(True) self.setFontWeight(10) - self.setHtml(text) \ No newline at end of file + self.setHtml(text) + +if __name__ == '__main__': # 1. Instantiate ApplicationContext + #if app is launched directly + from .app import app + import sys + main = ShapeDialog() + main.show() + exit_code = app.app.exec_() # 2. Invoke app.app.exec_() + sys.exit(exit_code) \ No newline at end of file diff --git a/src/main/python/utils/dialogs.py b/src/main/python/utils/dialogs.py index f2c4eb9..043597c 100644 --- a/src/main/python/utils/dialogs.py +++ b/src/main/python/utils/dialogs.py @@ -110,6 +110,7 @@ def saveEvent(parent = None): return True def showUndoDialog(undoView, parent): + # helper function to show a dialog box containing the undo view dialogBox = QDialog(parent) dialogBox.resize(400, 400) layout = QHBoxLayout(dialogBox) diff --git a/src/main/python/utils/fileWindow.py b/src/main/python/utils/fileWindow.py index b2f5304..7ea5619 100644 --- a/src/main/python/utils/fileWindow.py +++ b/src/main/python/utils/fileWindow.py @@ -39,6 +39,7 @@ class FileWindow(QMdiSubWindow): layout = QHBoxLayout(self.mainWidget) self.createSideViewArea() #create the side view objects + # set size policies for window left = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) left.setHorizontalStretch(1) self.tabber.setSizePolicy(left) @@ -47,8 +48,8 @@ class FileWindow(QMdiSubWindow): right.setHorizontalStretch(1) self.sideView.setSizePolicy(right) + #build widget layout layout.addWidget(self.tabber) - # layout.addWidget(self.splitter) layout.addWidget(self.sideView) self.mainWidget.setLayout(layout) self.setWidget(self.mainWidget) @@ -66,7 +67,6 @@ class FileWindow(QMdiSubWindow): def createSideViewArea(self): #creates the side view widgets and sets them to invisible - # self.splitter = QSplitter(Qt.Vertical ,self) self.sideView = CustomView(parent = self) self.sideView.setInteractive(False) self.sideViewCloseButton = QPushButton('×', self.sideView) @@ -75,7 +75,6 @@ class FileWindow(QMdiSubWindow): self.sideViewCloseButton.setFixedSize(20, 20) self.moveSideViewCloseButton() self.sideViewCloseButton.clicked.connect(lambda: setattr(self, 'sideViewTab', None)) - # self.splitter.setVisible(False) self.sideView.setVisible(False) self.sideView.setContextMenuPolicy(Qt.CustomContextMenu) self.sideView.customContextMenuRequested.connect(self.sideViewContextMenu) @@ -127,13 +126,11 @@ class FileWindow(QMdiSubWindow): def sideViewToggle(self): #Function checks if current side view tab is set, and toggles view as required if self.sideViewTab: - # self.splitter.setVisible(True) self.sideView.setVisible(True) self.sideView.setScene(self.tabber.currentWidget().painter) self.resizeHandler() return True - else: - # self.splitter.setVisible(False) + else: self.sideView.setVisible(False) self.resizeHandler() return False @@ -163,6 +160,7 @@ class FileWindow(QMdiSubWindow): menu.exec_(self.sideView.mapToGlobal(point)) def sideViewSwitchCMenu(self, index): + # helper function to swtich side view tab self.sideViewTab = self.tabber.widget(index) def sideViewSwitchTab(self): @@ -234,8 +232,7 @@ class FileWindow(QMdiSubWindow): else: event.ignore() - #following 2 methods are defined for correct pickling of the scene. may be changed to json or xml later so as - # to not have a binary file. + #following 2 methods are defined for correct serialization of the scene. def __getstate__(self) -> dict: return { "_classname_": self.__class__.__name__, diff --git a/src/main/python/utils/graphics.py b/src/main/python/utils/graphics.py index 05ffc46..bb113ce 100644 --- a/src/main/python/utils/graphics.py +++ b/src/main/python/utils/graphics.py @@ -20,12 +20,6 @@ class CustomView(QGraphicsView): self._zoom = 1 self.setDragMode(True) #sets pannable using mouse self.setAcceptDrops(True) #sets ability to accept drops - if scene: - #create necessary undo redo actions to accept keyboard shortcuts - # self.addAction(scene.undoAction) - # self.addAction(scene.redoAction) - # self.addAction(scene.deleteAction) - pass #following four functions are required to be overridden for drag-drop functionality def dragEnterEvent(self, QDragEnterEvent): @@ -48,8 +42,6 @@ class CustomView(QGraphicsView): #QDropEvent.mimeData().text() defines intended drop item, the pos values define position obj = QDropEvent.mimeData().text().split('/') graphic = getattr(shapes, obj[0])(*map(lambda x: int(x) if x.isdigit() else x, obj[1:])) - # graphic.setPen(QPen(Qt.black, 2)) - # graphic.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable) graphic.setPos(QDropEvent.pos().x(), QDropEvent.pos().y()) self.scene().addItemPlus(graphic) QDropEvent.acceptProposedAction() diff --git a/src/main/python/utils/layout.py b/src/main/python/utils/layout.py index b977a68..e1354b0 100644 --- a/src/main/python/utils/layout.py +++ b/src/main/python/utils/layout.py @@ -2,6 +2,10 @@ from PyQt5.QtCore import Qt, QRect, QPoint, QSize from PyQt5.QtWidgets import QLayout, QSizePolicy class flowLayout(QLayout): + """ + Custom layout that flows horizontally first, then vertically. + From Qt examples. + """ def __init__(self, parent=None, margin=0, spacing=12): super(flowLayout, self).__init__(parent) @@ -12,46 +16,57 @@ class flowLayout(QLayout): self.itemList = [] def __del__(self): + # Delete layout call item = self.takeAt(0) while item: item = self.takeAt(0) def addItem(self, item): + # add item to layout self.itemList.append(item) def count(self): + # return a list of items return len(self.itemList) def itemAt(self, index): + # return item at index if index >= 0 and index < len(self.itemList): return self.itemList[index] return None def takeAt(self, index): + # pop item at index if index >= 0 and index < len(self.itemList): return self.itemList.pop(index) return None def expandingDirections(self): + # define orientation return Qt.Orientations(Qt.Orientation(0)) def hasHeightForWidth(self): + # height for width flag, height value for a particular width return True def heightForWidth(self, width): + # returns the height for a given width height = self.doLayout(QRect(0, 0, width, 0), True) return height def setGeometry(self, rect): + # change layout geometry super(flowLayout, self).setGeometry(rect) self.doLayout(rect, False) def sizeHint(self): + # returns the expected size return self.minimumSize() def minimumSize(self): + # calucalate minimum possible size below which, resize is impossible size = QSize() for item in self.itemList: @@ -63,6 +78,7 @@ class flowLayout(QLayout): return size def doLayout(self, rect, testOnly): + # build layout, testOnly defines if the geometry needs to be changed or not x = rect.x() y = rect.y() lineHeight = 0 diff --git a/src/main/python/utils/streamTable.py b/src/main/python/utils/streamTable.py index c8e682c..8b3b220 100644 --- a/src/main/python/utils/streamTable.py +++ b/src/main/python/utils/streamTable.py @@ -5,13 +5,17 @@ from PyQt5.QtWidgets import QTableView, QMenu, QGraphicsRectItem, QInputDialog, from collections import defaultdict class streamTableModel(QAbstractTableModel): + """ + Defines the table model for the table view + """ updateEvent = pyqtSignal() def __init__(self, parent, list, header, *args): super(streamTableModel, self).__init__(parent, *args) self.list = list self.header = header - + + # column count, row count, data and setdata are important methods to be overloaded def columnCount(self, parent=None): return len(self.list) @@ -19,31 +23,41 @@ class streamTableModel(QAbstractTableModel): return len(self.header) def data(self, index, role): + # retunrs data at index if role == Qt.TextAlignmentRole: return Qt.AlignHCenter + if role == Qt.BackgroundColorRole: return Qt.white + if not index.isValid(): return None + elif role != Qt.DisplayRole: return None + if index.row() == 0: - return self.list[index.column()].toPlainText() + return self.list[index.column()].toPlainText() # return label name else: - return self.list[index.column()].values[self.header[index.row()]] + return self.list[index.column()].values[self.header[index.row()]] # retunr label values def setData(self, index, value, role): + # defines how to manipulate data at a given index with value returns sucess value if not index.isValid(): return False + elif role != Qt.EditRole: return False + if index.row() == 0: - self.list[index.column()].setPlainText(value) + self.list[index.column()].setPlainText(value) # change label text else: - self.list[index.column()].values[self.header[index.row()]] = value + self.list[index.column()].values[self.header[index.row()]] = value #change label values + return True def insertColumn(self, int=None, item=None): + # inserting a label item int = int if int else self.rowCount()+1 self.beginInsertColumns(QModelIndex(), int, int) self.list.insert(int, item) @@ -52,54 +66,65 @@ class streamTableModel(QAbstractTableModel): self.updateEvent.emit() def insertRow(self, int=None, name="newVal"): + # inserting a header property self.beginInsertRows(QModelIndex(), int, int) self.header.insert(int, name) self.endInsertRows() self.updateEvent.emit() def deleteRow(self, row): + # removing a property self.beginRemoveRows(QModelIndex(), row, row) - valName = self.header.pop(row) + valName = self.header.pop(row) # remove from header self.endRemoveRows() + for i in self.list: - i.values.pop(valName) - self.updateEvent.emit() + i.values.pop(valName) #clear dictionary + + self.updateEvent.emit() # update request def headerData(self, col, orientation, role): + # definds how to fetch header data if orientation == Qt.Vertical and role == Qt.DisplayRole: return self.header[col] return None def flags(self, index): + # defines item editable flag return (super(streamTableModel, self).flags(index) | Qt.ItemIsEditable) class streamTable(QTableView): - + """ + subclasses stream table to display data properly + """ def __init__(self, itemLabels=[], canvas=None, parent=None): super(streamTable, self).__init__(parent=parent) self.canvas = canvas + for i in itemLabels: - i.nameChanged.connect(self.repaint) - header = ["name", "val1", "val2", "val3", "val4", "val5"] + i.nameChanged.connect(self.repaint) # connect repaint requests on name change + + header = ["name", "val1", "val2", "val3", "val4", "val5"] # prepare header names self.model = streamTableModel(self, itemLabels, header) - self.setShowGrid(False) + self.setShowGrid(False) # disable table grid - self.horizontalHeader().hide() - header = verticalHeader(Qt.Vertical, self) + self.horizontalHeader().hide() # remove horizontal header + + header = verticalHeader(Qt.Vertical, self) #create custom vertical header self.setVerticalHeader(header) header.labelChangeRequested.connect(self.labelChange) - self.setModel(self.model) - self.borderThickness = defaultdict(lambda: False) + self.setModel(self.model) #declare model + self.borderThickness = defaultdict(lambda: False) #thickness bool dict self.model.updateEvent.connect(self.resizeHandler) self.setItemDelegateForRow(0, drawBorderDelegate(self)) - self.borderThickness[0] = True + self.borderThickness[0] = True # set border true for name row def mousePressEvent(self, event): + # handle context menu request if event.button() == Qt.RightButton: point = event.pos() - # col = self.getCol(point.x()) index = self.indexAt(point) menu = QMenu("Context Menu", self) menu.addAction("Toggle bottom border thickness", lambda x=index.row(): self.changeRowBorder(x)) @@ -110,6 +135,7 @@ class streamTable(QTableView): return super(streamTable, self).mousePressEvent(event) def changeRowBorder(self, row): + # toggle column border thicnkess if self.borderThickness[row]: self.borderThickness.pop(row) self.setItemDelegateForRow(row, QStyledItemDelegate(self)) @@ -119,6 +145,7 @@ class streamTable(QTableView): self.verticalHeader().repaint() def labelChange(self, index): + # label name change newName, bool = QInputDialog.getText(self, "Change Property Name", "Enter new name", text = self.model.header[index]) if bool: @@ -128,6 +155,7 @@ class streamTable(QTableView): self.repaint() def insertRowBottom(self, row): + # dialog box for new property name, bool = QInputDialog.getText(self, "New Property", "Enter name", text = "newVal") if bool: @@ -162,10 +190,9 @@ class streamTable(QTableView): self.repaint() class drawBorderDelegate(QStyledItemDelegate): - - # def __init__(self, parent): - # super(drawBorderDelegate, self).__init__(parent) - + """ + class for drawing border line + """ def paint(self, painter, option, index): rect = option.rect painter.drawLine(rect.bottomLeft(), rect.bottomRight()) @@ -173,7 +200,9 @@ class drawBorderDelegate(QStyledItemDelegate): super(drawBorderDelegate, self).paint(painter, option, index) class moveRect(QGraphicsRectItem): - + """ + use to move the table on the scene + """ def __init__(self, sideLength = 15, *args): super(moveRect, self).__init__(-sideLength, -sideLength, sideLength, sideLength) self.setBrush(Qt.transparent) @@ -190,6 +219,9 @@ class moveRect(QGraphicsRectItem): return super(moveRect, self).hoverLeaveEvent(event) class verticalHeader(QHeaderView): + """ + Custom Vertical header for the table, with line border against corresponding rows + """ labelChangeRequested = pyqtSignal(int) def mouseDoubleClickEvent(self, event): diff --git a/src/main/python/utils/tabs.py b/src/main/python/utils/tabs.py index cedb298..70f9def 100644 --- a/src/main/python/utils/tabs.py +++ b/src/main/python/utils/tabs.py @@ -3,7 +3,7 @@ from PyQt5.QtCore import pyqtSignal, QSize, Qt class TabBarPlus(QTabBar): """ - Just implemented to overload resize and layout change to emit a signal + Just implemented to overload resize and layout change to emit a signal and change tab names. """ layoutChanged = pyqtSignal() nameChanged = pyqtSignal(int, str) @@ -16,6 +16,7 @@ class TabBarPlus(QTabBar): self.layoutChanged.emit() def mouseDoubleClickEvent(self, event): + # tab name change request if event.button() != Qt.LeftButton: return super().mouseDoubleClickEvent() index = self.currentIndex() diff --git a/src/main/python/utils/toolbar.py b/src/main/python/utils/toolbar.py index 3f6338b..f2bbd5a 100644 --- a/src/main/python/utils/toolbar.py +++ b/src/main/python/utils/toolbar.py @@ -9,8 +9,6 @@ from .data import toolbarItems from .app import fileImporter, app from .layout import flowLayout -# resourceManager = ApplicationContext() #Used to load images, mainly toolbar icons - class toolbar(QDockWidget): """ Defines the right side toolbar, using QDockWidget. diff --git a/src/main/python/utils/undo.py b/src/main/python/utils/undo.py index 92a8de8..6a46b27 100644 --- a/src/main/python/utils/undo.py +++ b/src/main/python/utils/undo.py @@ -91,6 +91,7 @@ class moveCommand(QUndoCommand): class resizeCommand(QUndoCommand): """ + Defines the resize event for the custom scene. """ def __init__(self, new, canvas, widget, parent = None): super(resizeCommand, self).__init__(parent) -- cgit From 54c3ce344d8d4b263e6c6f98dd072293a1a74b55 Mon Sep 17 00:00:00 2001 From: Blaine Date: Sat, 20 Jun 2020 14:55:08 +0530 Subject: add build scripts --- build.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/build.py b/build.py index 9b5a543..d096792 100644 --- a/build.py +++ b/build.py @@ -3,11 +3,21 @@ from os.path import dirname import subprocess import fbs.cmdline +import fbs @command def compileResources(): subprocess.call("pyrcc5 -o src/main/python/resources/resources.py src/main/ui/resources.rcc", shell=True) +@command +def symbolGen(): + exec(open('src/main/python/utils/custom.py').read()) + +@command +def build(): + compileResources() + subprocess.call("fbs freeze", shell=True) + if __name__ == '__main__': project_dir = dirname(__file__) fbs.cmdline.main(project_dir) \ No newline at end of file -- cgit From 3a8e328002ad4c86ec0dccef1efe18ccc2742077 Mon Sep 17 00:00:00 2001 From: Blaine Date: Sat, 20 Jun 2020 15:07:20 +0530 Subject: remove qstylizer as requirement --- requirements/base.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 2800691..cd978ff 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,3 +1,2 @@ PyQt5 -fbs -qstylizer \ No newline at end of file +fbs \ No newline at end of file -- cgit From d67b4b853ffd4677d2a96704cb0ed5581c59c444 Mon Sep 17 00:00:00 2001 From: Blaine Date: Sat, 20 Jun 2020 15:14:30 +0530 Subject: symbol gen script --- build.py | 7 +++++-- src/main/python/utils/custom.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/build.py b/build.py index d096792..91a66fb 100644 --- a/build.py +++ b/build.py @@ -1,17 +1,20 @@ from fbs.cmdline import command from os.path import dirname import subprocess - +import sys import fbs.cmdline import fbs +sys.path.append('src/main/python/') + @command def compileResources(): subprocess.call("pyrcc5 -o src/main/python/resources/resources.py src/main/ui/resources.rcc", shell=True) @command def symbolGen(): - exec(open('src/main/python/utils/custom.py').read()) + from utils import custom + custom.main() @command def build(): diff --git a/src/main/python/utils/custom.py b/src/main/python/utils/custom.py index fedecdb..2c8ffc6 100644 --- a/src/main/python/utils/custom.py +++ b/src/main/python/utils/custom.py @@ -279,7 +279,7 @@ class OutputBox(QTextEdit): self.setFontWeight(10) self.setHtml(text) -if __name__ == '__main__': # 1. Instantiate ApplicationContext +def main(): # 1. Instantiate ApplicationContext #if app is launched directly from .app import app import sys -- cgit From 5afc44d63266bb1e8a57a880d64b33f95b29e3d8 Mon Sep 17 00:00:00 2001 From: Blaine Date: Sat, 20 Jun 2020 16:06:24 +0530 Subject: Add readmen --- README.md | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- requirements.txt | 2 + 2 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 requirements.txt diff --git a/README.md b/README.md index 77f8e4c..7122393 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,170 @@ -# Chemical-PFD +# Chemical PFD Tool ## +Repository for a Process Flow Diagram Software + +                                     +![Python](https://img.shields.io/badge/python-v3.6-blue.svg) +![version](https://img.shields.io/badge/version-0.0.1-blue) +[![python-mixedCase-style](https://img.shields.io/badge/code%20style-mixed-brightgreen.svg?style=flat)](https://wiki.c2.com/?CamelCase) +[![Dependencies](https://img.shields.io/badge/dependencies-up%20to%20date-brightgreen.svg)](https://github.com/FOSSEE/Chemical-PFD/network/dependencies) +[![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](https://opensource.org/licenses/GPL-3.0) + +## Screenshots +> Main window +

+ +## Tech/framework used + +#### Built with +- [PyQt5](https://www.riverbankcomputing.com/software/pyqt/) +- [FBS](https://build-system.fman.io/) + +## Features #### +> 1. Drag and Drop symbols from toolbar to scene +> 2. Undo Redo action +> 3. Save and Load files +> 4. Connect symbols with lines, and add labels +> 5. Create stream table +> 6. Use various paper sizes +> 7. Work on multiple diagrams and file at once +> 8. View diagrams side by side +> 9. Zoom In/out on the scene + +## Code overview # + +> #### src/main/python/main.py +> main application entry point, defines the main window and runs it. + +> #### src/main/python/shapes +> Contains the shape and line definitions and logic. + +> #### src/main/python/utils +> contains the sub window definitions along with various utility methods. + + +## Installation +#### clone this repository by running +```bash +git clone https://github.com/FOSSEE/Chemical-PFD.git +``` +or by simply pressing the Clone or Download button and using your own preferred way of obtaining a working copy of the repository +#### requirements can be installed using (PIP should be up-to-date! tested on 20.0.2) +```bash +pip install --upgrade pip +pip install -r requirements.txt +``` +#### Then run using +```bash +fbs run +``` +or +```bash +python3 build.py run +``` +additionally, if fbs doesnt work, you can manually run the program as +```bash +python3 ./src/main/python/main.py +``` +that is run the **main.py** file located in **./src/main/python/main.py** +any output generated when run this way would be saved in **./src/main/python/** + + + +## Building +There are two methods of doing this, +### Manually +#### Compiling the resources +Using pyrcc5, the resource.qrc can be compiled to a python file +```bash +pyrcc5 -o src/main/python/resources/resources.py src/main/ui/resources.rcc +``` + +#### Building binaries +Using fbs's freeze feature. +```bash +fbs freeze +``` + +#### Releasing +One can now build installer using fbs, +```bash +fbs release +``` +note: Windows user will need [nsis](https://sourceforge.net/projects/nsis/) installed on their system and added to env path. +Additionally multiple things might need to be done, follow the onscreen instruction. + + +### Build.py script + +#### Resource compilation and building binaries +can be done, by using build.py build +```bash +python3 build.py build +``` + +#### TODO + +## Adding symbols +The process of adding symbols, is simple. + +### Obtain svg for symbol +It is necessary that the symbol is in svg format. +One can use any of the many svg tools out there. +We recommend [Inkscape](https://inkscape.org/) + +### Preparing class entries + +#### Class definition +Under src/main/python/shapes/shape.py, using the following as an example, one can create his own class definition for the symbol. The grip list is the percentage position of the grip item object along with the parent's width and height, the third value is its position and the fourth value if specified is the width/height if the grip is a line grip item. +```python +class HorizontalVessel(NodeItem): + def __init__(self): + super(HorizontalVessel, self).__init__("svg/Process Vessels/Horizontal Vessel") + self.grips = [ + [50, 100, "top", 87.08554680344], + [0, 50, "left"], + [100, 50, "right"], + [50, 0, "bottom", 87.08554680344] + ] +``` + +#### Items.json Entry +The items.json is present in src/main/resources/base/config/items.json +Once the class name and category has been decided, an items.json entry can be configured as follows, +```javascript +{ + "Process Vessels": { + "Horizontal Vessel": { + "name": "Horizontal Vessel", + "icon": ".\\Process Vessels\\Horizontal Vessel.png", + "class": "Process Vessels", + "object": "HorizontalVessel", + "args": [] + }, + } +} +``` + +#### Toolbar icon +A 64x64 toolbar icon as a png needs to be prepared to be shown in the toolbar. It needs to be placed in src/main/resources/base/toolbar/ in the corresponding folder in the json file. + +### Automating +Most of the above process can be automated, following are a few procedures one can use + +#### Python Script(For making icons and items.json entries for multiple svgs) +You can find the script [here](https://gist.github.com/Blakeinstein/c349216ac1de86c66024d607140c9dfb) + +#### Using the symbol generator (For class + items.json) +You can launch the tool while running the app, by clicking Add new Symbols in the edit menu. +Or by directly launching the edit symbol menu using build.py + +```bash +python3 build.py symbolGen +``` + +## API Reference + +The QtForPython docs were used to implement the program, one can reference them here +- [QtForPython](https://doc.qt.io/qtforpython/contents.html) +The docs for Qt for C++ library can be found here +- [Qt](https://doc.qt.io/) -Repository for Process Flow Diagram Software diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cd978ff --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +PyQt5 +fbs \ No newline at end of file -- cgit