summaryrefslogtreecommitdiff
path: root/src/main/python/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/python/utils')
-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
11 files changed, 466 insertions, 101 deletions
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)