diff options
author | pravindalve | 2020-06-22 16:37:47 +0530 |
---|---|---|
committer | GitHub | 2020-06-22 16:37:47 +0530 |
commit | cca55e231036850b2ea235ef0e1a1dc05db2a4b0 (patch) | |
tree | 8ff6b465e177e632a544471e1ec4b4add9279045 /src | |
parent | c3ca9374cfc2b91238e431055ab1cc2a0074a511 (diff) | |
parent | 5afc44d63266bb1e8a57a880d64b33f95b29e3d8 (diff) | |
download | Chemical-PFD-cca55e231036850b2ea235ef0e1a1dc05db2a4b0.tar.gz Chemical-PFD-cca55e231036850b2ea235ef0e1a1dc05db2a4b0.tar.bz2 Chemical-PFD-cca55e231036850b2ea235ef0e1a1dc05db2a4b0.zip |
Merge pull request #24 from Blakeinstein/master
Final PR
Diffstat (limited to 'src')
-rw-r--r-- | src/main/python/main.py | 105 | ||||
-rw-r--r-- | src/main/python/resources/__init__.py | 0 | ||||
-rw-r--r-- | src/main/python/resources/resources.py | 65 | ||||
-rw-r--r-- | src/main/python/shapes/line.py | 18 | ||||
-rw-r--r-- | src/main/python/shapes/shapes.py | 69 | ||||
-rw-r--r-- | src/main/python/utils/app.py | 21 | ||||
-rw-r--r-- | src/main/python/utils/canvas.py | 60 | ||||
-rw-r--r-- | src/main/python/utils/custom.py | 289 | ||||
-rw-r--r-- | src/main/python/utils/dialogs.py | 1 | ||||
-rw-r--r-- | src/main/python/utils/fileWindow.py | 25 | ||||
-rw-r--r-- | src/main/python/utils/graphics.py | 25 | ||||
-rw-r--r-- | src/main/python/utils/layout.py | 16 | ||||
-rw-r--r-- | src/main/python/utils/streamTable.py | 104 | ||||
-rw-r--r-- | src/main/python/utils/tabs.py | 15 | ||||
-rw-r--r-- | src/main/python/utils/toolbar.py | 8 | ||||
-rw-r--r-- | src/main/python/utils/undo.py | 3 | ||||
-rw-r--r-- | src/main/resources/base/app.qss | 73 | ||||
-rw-r--r-- | src/main/ui/close.png (renamed from src/main/resources/base/ui/close.png) | bin | 255 -> 255 bytes | |||
-rw-r--r-- | src/main/ui/resources.rcc | 5 |
19 files changed, 690 insertions, 212 deletions
diff --git a/src/main/python/main.py b/src/main/python/main.py index 4cf1791..35c95c3 100644 --- a/src/main/python/main.py +++ b/src/main/python/main.py @@ -2,13 +2,13 @@ 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) 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 @@ -25,23 +25,7 @@ class appWindow(QMainWindow): super(appWindow, self).__init__(parent) #create the menu bar - titleMenu = self.menuBar() #fetch reference to current menu bar - # 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) - - 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()) - - 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) + self.createMenuBar() self.mdi = QMdiArea(self) #create area for files to be displayed self.mdi.setObjectName('mdi area') @@ -60,7 +44,39 @@ class appWindow(QMainWindow): # 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.menuFile = titleMenu.addMenu('File') #File Menu + 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') + 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.menuEdit.addSeparator() + self.menuEdit.addAction("Add new symbols", self.addSymbolWindow) + + self.menuGenerate = titleMenu.addMenu('Generate') #Generate menu + imageAction = self.menuGenerate.addAction("Image", self.saveImage) + reportAction = self.menuGenerate.addAction("Report", self.generateReport) + + imageAction.setShortcut(QKeySequence("Ctrl+P")) + reportAction.setShortcut(QKeySequence("Ctrl+R")) + def createToolbar(self): #place holder for toolbar with fixed width, layout may change self.toolbar = toolbar(self) @@ -71,23 +87,26 @@ 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(50, 50) currentDiagram.addItemPlus(graphic) - graphic.setPos(20, 20) - + + def addSymbolWindow(self): + # Opens the add symbol window when requested + from utils.custom import ShapeDialog + ShapeDialog(self).exec() + 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 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) @@ -100,8 +119,9 @@ 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) + #create blank window and set its state project.__setstate__(projectData) project.resizeHandler() project.fileCloseEvent.connect(self.fileClosed) @@ -111,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)') @@ -154,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(): @@ -162,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))) @@ -169,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] @@ -185,25 +208,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) @@ -221,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/resources/__init__.py b/src/main/python/resources/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/main/python/resources/__init__.py 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/shapes/line.py b/src/main/python/shapes/line.py index c893fe6..856869d 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'].items(): + self.values[key] = value def findIndex(line, pos): @@ -1008,8 +1011,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 { @@ -1022,8 +1026,14 @@ 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.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..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 @@ -244,7 +246,14 @@ class LineGripItem(QGraphicsPathItem): @property def m_location(self): - return directionsEnum[(self._m_location + self.parentItem().rotation)%4] + 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 + else: + index = (index + 2*self.parentItem().flipV)%4 + return directionsEnum[index] @m_location.setter def m_location(self, location): @@ -267,7 +276,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() @@ -451,22 +460,47 @@ class NodeItem(QGraphicsSvgItem): self.sizeGripItems = [] self.label = None self._rotation = 0 - + self.flipState = [False, False] + @property - def rotation(self): - return self._rotation + def flipH(self): + return self.flipState[0] - @rotation.setter - def rotation(self, rotation): - self._rotation = rotation % 4 + @property + def flipV(self): + return self.flipState[1] + + def updateTransformation(self): + # update transformation on flipstate or rotation change transform = QTransform() - transform.rotate(90*rotation) + 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.updateTransformation() + + @flipV.setter + def flipV(self, state): + self.flipState[1] = state + self.updateTransformation() + + @property + def rotation(self): + return self._rotation + + @rotation.setter + def rotation(self, rotation): + self._rotation = rotation % 4 + self.updateTransformation() def boundingRect(self): """Overrides QGraphicsSvgItem's boundingRect() virtual public function and @@ -603,11 +637,12 @@ class NodeItem(QGraphicsSvgItem): """ # create a menu and add action contextMenu = QMenu() - addLableAction = contextMenu.addAction("add Label") # add action for text label + 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 { @@ -616,7 +651,9 @@ 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, + "flipstate": self.flipState } def __setstate__(self, dict): diff --git a/src/main/python/utils/app.py b/src/main/python/utils/app.py index 96a45b0..39c3674 100644 --- a/src/main/python/utils/app.py +++ b/src/main/python/utils/app.py @@ -1,14 +1,16 @@ """ -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 -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 #application resources defined in resources.qrc + app = ApplicationContext() settings = QSettings(QSettings.IniFormat, QSettings.UserScope ,"FOSSEE", "Chemical-PFD") version = app.build_settings['version'] @@ -17,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 @@ -48,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__() @@ -58,8 +65,4 @@ class JSON_Typer(JSONEncoder): def encode(self, obj): return super(JSON_Typer, self).encode(self._encode(obj)) - -importer = pyqtProperty(str, fileImporter) - -shapeGrips = {} -lines = {}
\ 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 47e95a9..68b0902 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 shapeGrips, lines +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) @@ -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): """ @@ -142,7 +146,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.streamTableRect.pos().y())] if self.streamTable else False } def __setstate__(self, dict): @@ -151,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) @@ -159,27 +165,30 @@ 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']) self.painter.addItem(graphicLabel) + 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'])) - 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 + graphic.lineGripItems[index].lines.append(line) if item['endGripItem']: - graphic, index = shapeGrips[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 = 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,10 +198,11 @@ class canvas(customView): line.updateLine() line.addGrabber() - shapeGrips.clear() - lines.clear() - self.painter.advance() - - - -
\ No newline at end of file + # 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() #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 new file mode 100644 index 0000000..2c8ffc6 --- /dev/null +++ b/src/main/python/utils/custom.py @@ -0,0 +1,289 @@ +""" +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) +from PyQt5.QtSvg import QGraphicsSvgItem +from PyQt5.QtWidgets import (QBoxLayout, QDialog, QFileDialog, + QGraphicsEllipseItem, QGraphicsItem, + QGraphicsScene, QGraphicsView, QGridLayout, + QInputDialog, QLabel, QLineEdit, QPushButton, + QTextEdit) + +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) # 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) + + 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): + # 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]) + self.graphic.setZValue(-1) + 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 + + itemName = self.symbolName.text() + if itemName is '': + return + + className = self.symbolClass.text() + if className is '': + return + + category = self.symbolCategory.text() + if category == "": + category = "misc" + + # get rect for calculating grip positions + graphicRect = self.graphic.boundingRect() + + #save file + name = QFileDialog.getSaveFileName(self, 'Save Icon', className, 'PNG (*.png)') + if name: + QIcon(self.name[0]).pixmap(QSize(64, 64)).toImage().save(name[0]) + else: + return + + #calculate grip positions and build a list + gripList = [] + x, y, w, h = graphicRect.getRect() + for i in self.grips: + pos = i.pos() + 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(i.height) + else: + 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""" +<b> Class Definition:</b> +<pre> +class {className}(NodeItem): + def __init__(self): + super({className}, self).__init__("svg/{category}/{str.split(name[0], "/")[-1][:-4]}") + {grips} +</pre> +<b> Items.json entry:</b> +<pre> +"{category}": {{ + "{itemName}": {{ + "name": "{itemName}", + "icon": ".\\{category}\\{str.split(name[0], "/")[-1]}", + "class": "{category}", + "object": "{className}", + "args": [] + }} +}}</pre>""") + 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): + #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" + 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): + """ + 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 + self.sizeGripItems = [] + 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) + + 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): + # adds the resizeable line grip items from the shape file :D + 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): + #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() + 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): + #resize method + 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): + """ + class for circular grips + """ + 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): + """ + 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) + +def 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 e85d513..7ea5619 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 @@ -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,8 +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") @@ -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 3e1d193..bb113ce 100644 --- a/src/main/python/utils/graphics.py +++ b/src/main/python/utils/graphics.py @@ -7,24 +7,19 @@ 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 - if scene: - #create necessary undo redo actions to accept keyboard shortcuts - self.addAction(scene.undoAction) - self.addAction(scene.redoAction) - self.addAction(scene.deleteAction) #following four functions are required to be overridden for drag-drop functionality def dragEnterEvent(self, QDragEnterEvent): @@ -47,10 +42,8 @@ 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) - self.scene().addItemPlus(graphic) graphic.setPos(QDropEvent.pos().x(), QDropEvent.pos().y()) + self.scene().addItemPlus(graphic) QDropEvent.acceptProposedAction() def wheelEvent(self, QWheelEvent): @@ -66,7 +59,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): @@ -80,14 +73,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 @@ -132,7 +125,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 @@ -141,4 +134,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/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 acccdee..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,85 +23,108 @@ 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) + item.nameChanged.connect(self.parent().repaint) self.endInsertColumns() 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() # remove horizontal header - self.horizontalHeader().hide() - header = verticalHeader(Qt.Vertical, self) + 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 # 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)) @@ -108,14 +135,17 @@ class streamTable(QTableView): return super(streamTable, self).mousePressEvent(event) def changeRowBorder(self, row): + # toggle column border thicnkess 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): + # label name change newName, bool = QInputDialog.getText(self, "Change Property Name", "Enter new name", text = self.model.header[index]) if bool: @@ -125,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: @@ -145,12 +176,23 @@ 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'].items(): + self.borderThickness[key] = value + self.model.header = dict['header'] + 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()) @@ -158,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) @@ -175,9 +219,21 @@ 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): 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/python/utils/tabs.py b/src/main/python/utils/tabs.py index 1186191..70f9def 100644 --- a/src/main/python/utils/tabs.py +++ b/src/main/python/utils/tabs.py @@ -1,9 +1,9 @@ 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 + 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() @@ -26,17 +27,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..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. @@ -107,7 +105,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 +163,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 diff --git a/src/main/python/utils/undo.py b/src/main/python/utils/undo.py index 8426494..6a46b27 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) @@ -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) diff --git a/src/main/resources/base/app.qss b/src/main/resources/base/app.qss index d7b25b4..cd11034 100644 --- a/src/main/resources/base/app.qss +++ b/src/main/resources/base/app.qss @@ -37,40 +37,40 @@ QLineEdit { QLineEdit:focus{ border-color: #7cabf9; } -tabBarPlus { +TabBarPlus { qproperty-drawBase: 0; left: 5px; background-color: transparent; font-size: 15px; } -tabBarPlus:focus { +TabBarPlus:focus { border: 0px transparent black; } -tabBarPlus::close-button { +TabBarPlus::close-button { + background-image: url(:/closeIcon); 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 +80,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 +155,6 @@ QDockWidget::float-button { background: transparent; subcontrol-origin: padding; subcontrol-position: right center; -} -QDockWidget::float-button { right: 4px; } @@ -166,7 +163,6 @@ QDockWidget::float-button:hover { } QDockWidget::float-button:pressed { - /*padding: 1px -1px -1px 1px;*/ background-color: #e0e0e0; } @@ -210,7 +206,7 @@ QToolButton:checked { border-color: #004646; } -sectionLabel{ +SectionLabel{ border-color: #00B8B8; border-style: solid; border-width: 0px 0px 2px 0px; @@ -222,11 +218,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 +231,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,11 +256,12 @@ canvas{ bottom: -20px; } -customTabWidget customView { +CustomTabWidget CustomView { border-width: 0px; padding: 5px; border-radius: 2px; } + QHeaderView { qproperty-defaultAlignment: AlignHCenter; } @@ -275,10 +272,6 @@ QHeaderView::section { background-color: white; } -QHeaderView::section:first{ - border-bottom: 1px solid black; -} - QTableView { border: none; } diff --git a/src/main/resources/base/ui/close.png b/src/main/ui/close.png Binary files differindex 8771a0b..8771a0b 100644 --- a/src/main/resources/base/ui/close.png +++ b/src/main/ui/close.png 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 @@ +<RCC> + <qresource> + <file alias="closeIcon">close.png</file> + </qresource> +</RCC> |