summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorpravindalve2020-06-22 16:37:47 +0530
committerGitHub2020-06-22 16:37:47 +0530
commitcca55e231036850b2ea235ef0e1a1dc05db2a4b0 (patch)
tree8ff6b465e177e632a544471e1ec4b4add9279045 /src
parentc3ca9374cfc2b91238e431055ab1cc2a0074a511 (diff)
parent5afc44d63266bb1e8a57a880d64b33f95b29e3d8 (diff)
downloadChemical-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.py105
-rw-r--r--src/main/python/resources/__init__.py0
-rw-r--r--src/main/python/resources/resources.py65
-rw-r--r--src/main/python/shapes/line.py18
-rw-r--r--src/main/python/shapes/shapes.py69
-rw-r--r--src/main/python/utils/app.py21
-rw-r--r--src/main/python/utils/canvas.py60
-rw-r--r--src/main/python/utils/custom.py289
-rw-r--r--src/main/python/utils/dialogs.py1
-rw-r--r--src/main/python/utils/fileWindow.py25
-rw-r--r--src/main/python/utils/graphics.py25
-rw-r--r--src/main/python/utils/layout.py16
-rw-r--r--src/main/python/utils/streamTable.py104
-rw-r--r--src/main/python/utils/tabs.py15
-rw-r--r--src/main/python/utils/toolbar.py8
-rw-r--r--src/main/python/utils/undo.py3
-rw-r--r--src/main/resources/base/app.qss73
-rw-r--r--src/main/ui/close.png (renamed from src/main/resources/base/ui/close.png)bin255 -> 255 bytes
-rw-r--r--src/main/ui/resources.rcc5
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
index 8771a0b..8771a0b 100644
--- a/src/main/resources/base/ui/close.png
+++ b/src/main/ui/close.png
Binary files 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 @@
+<RCC>
+ <qresource>
+ <file alias="closeIcon">close.png</file>
+ </qresource>
+</RCC>