summaryrefslogtreecommitdiff
path: root/src/main/python/utils/fileWindow.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/python/utils/fileWindow.py')
-rw-r--r--src/main/python/utils/fileWindow.py255
1 files changed, 255 insertions, 0 deletions
diff --git a/src/main/python/utils/fileWindow.py b/src/main/python/utils/fileWindow.py
new file mode 100644
index 0000000..e549568
--- /dev/null
+++ b/src/main/python/utils/fileWindow.py
@@ -0,0 +1,255 @@
+import pickle
+
+from PyQt5.QtCore import Qt, pyqtSignal, QPoint
+from PyQt5.QtGui import QIcon
+from PyQt5.QtWidgets import (QFileDialog, QHBoxLayout,
+ QMdiSubWindow, QMenu, QPushButton, QSizePolicy,
+ QSplitter, QWidget, QStyle)
+
+from . import dialogs
+from .graphics import customView
+from .canvas import canvas
+from .tabs import customTabWidget
+
+
+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.
+ """
+ fileCloseEvent = pyqtSignal(int)
+
+ def __init__(self, parent = None, title = 'New Project', size = 'A4', ppi = '72'):
+ 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.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
+ self.tabber.plusClicked.connect(self.newDiagram) #connect the new tab button to add a new tab
+
+ #assign layout to widget
+ self.mainWidget = QWidget(self)
+ layout = QHBoxLayout(self.mainWidget)
+ self.createSideViewArea() #create the side view objects
+ layout.addWidget(self.tabber)
+ layout.addWidget(self.splitter)
+ layout.addWidget(self.sideView)
+ self.mainWidget.setLayout(layout)
+ self.setWidget(self.mainWidget)
+ self.setWindowTitle(title)
+
+ #This is done so that a right click menu is shown on right click
+ self.tabber.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.tabber.customContextMenuRequested.connect(self.contextMenu)
+
+ # self.setAttribute(Qt.WA_DeleteOnClose, True)
+ self.setWindowFlag(Qt.CustomizeWindowHint, True)
+ self.setWindowFlag(Qt.WindowMinimizeButtonHint, False)
+ self.setWindowFlag(Qt.WindowMaximizeButtonHint, False)
+
+ def createSideViewArea(self):
+ #creates the side view widgets and sets them to invisible
+ self.splitter = QSplitter(Qt.Vertical ,self)
+ self.sideView = customView(parent = self)
+ self.sideView.setInteractive(False)
+ self.sideViewCloseButton = QPushButton('×', self.sideView)
+ self.sideViewCloseButton.setFlat(True)
+ self.sideViewCloseButton.setStyleSheet("""QPushButton{
+ background: rgba(214, 54, 40, 50%);
+ border: 1px groove white;
+ border-radius: 2px;
+ font-size: 18px;
+ font-weight: Bold;
+ padding: 1px 2px 3px 3px;
+ color: rgba(255, 255, 255, 50%);
+ }
+ QPushButton:Hover{
+ background: rgba(214, 54, 40, 90%);
+ color: rgba(255, 255, 255, 90%);
+ }
+ """)
+ 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)
+
+ def resizeHandler(self):
+ # resize Handler to handle resize cases.
+ parentRect = self.mdiArea().size()
+ current = self.tabber.currentWidget()
+ width, height = current.dimensions
+
+ # if side view is visible, set width to maximum possible, else use minimum requirement
+ if self.sideViewTab:
+ width = parentRect.width()
+ height = parentRect.height()
+ self.moveSideViewCloseButton()
+
+ else:
+ width = min(parentRect.width(), width + 100)
+ height = min(parentRect.height(), height + 200)
+
+ if len(self.parent().parent().subWindowList()) > 1:
+ height -= 20
+
+ # set element dimensions
+ self.setFixedSize(width, height)
+ self.tabber.resize(width, height)
+ self.tabber.currentWidget().adjustView()
+
+ def contextMenu(self, point):
+ #function to display the right click menu at point of right click
+ menu = QMenu("Context Menu", self)
+ menu.addAction("Adjust Canvas", self.adjustCanvasDialog)
+ menu.addAction("Remove Side View" if self.sideViewTab == self.tabber.currentWidget() else "View Side-By-Side",
+ self.sideViewMode)
+ menu.addAction("Reset Zoom", lambda : setattr(self.tabber.currentWidget().view, 'zoom', 1))
+ menu.exec_(self.tabber.mapToGlobal(point))
+
+ def sideViewMode(self):
+ #helper context menu function to toggle side view
+ self.sideViewTab = self.tabber.currentWidget()
+
+ def adjustCanvasDialog(self):
+ #helper context menu function to the context menu dialog box
+ currentTab = self.tabber.currentWidget()
+ result = dialogs.paperDims(self, currentTab._canvasSize, currentTab._ppi, currentTab.objectName()).exec_()
+ if result is not None:
+ currentTab.canvasSize, currentTab.ppi = result
+ return self.resizeHandler()
+ else:
+ return None
+
+ 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.moveSideViewCloseButton()
+ self.resizeHandler()
+ return True
+ else:
+ self.splitter.setVisible(False)
+ self.sideView.setVisible(False)
+ self.resizeHandler()
+ return False
+
+ def moveSideViewCloseButton(self):
+ # used to place side view close button at appropriate position
+ self.sideViewCloseButton.move(5, 5)
+
+ def sideViewContextMenu(self, point):
+ # context menu for side view
+ menu = QMenu("Context Menu", self.sideView)
+ menu.addAction("Reset Zoom", lambda : setattr(self.sideView, 'zoom', 1))
+ menu.addSection('Change Side View Tab')
+ if self.tabCount > 5:
+ # just show switch dialog box, if there are 6 or more tabs open
+ menu.addAction("Show switch menu", self.sideViewSwitchTab)
+ else:
+ # enumerate all tabs from side view.
+ for i in range(self.tabCount):
+ j = self.tabber.widget(i)
+ if j == self.sideViewTab:
+ continue
+ # evaluate i as index, weird lambda behaviour
+ #see https://stackoverflow.com/a/33984811/7799568
+ menu.addAction(f'{i}. {j.objectName()}', lambda index=i: self.sideViewSwitchCMenu(index))
+ menu.addAction("Remove side view", lambda : setattr(self, 'sideViewTab', None))
+ menu.exec_(self.sideView.mapToGlobal(point))
+
+ def sideViewSwitchCMenu(self, index):
+ self.sideViewTab = self.tabber.widget(index)
+
+ def sideViewSwitchTab(self):
+ # displays a side view switch dialog box
+ tabList = [f'{i}. {j.objectName()}' for i, j in enumerate(self.tabList)] #names and index of all tabs
+ initial = self.tabList.index(self.sideViewTab) # current side view tab
+ result = dialogs.sideViewSwitchDialog(self.tabber, tabList, initial).exec_() #call dialog box
+ if result != initial:
+ self.sideViewTab = self.tabber.widget(result) if result<self.tabCount else None
+
+ @property
+ def sideViewTab(self):
+ #returns current active if sideViewTab otherwise None
+ return self._sideViewTab
+
+ @property
+ def tabList(self):
+ #returns a list of tabs in the given window
+ return [self.tabber.widget(i) for i in range(self.tabCount)]
+
+ @property
+ def tabCount(self):
+ #returns the number of tabs in the given window only
+ return self.tabber.count()
+
+ @sideViewTab.setter
+ def sideViewTab(self, tab):
+ #setter for side view. Also toggles view as necessary
+ self._sideViewTab = None if tab == self.sideViewTab else tab
+ return self.sideViewToggle()
+
+ def changeTab(self, currentIndex):
+ #placeholder function to detect tab change
+ self.resizeHandler()
+
+ def closeTab(self, currentIndex):
+ #show save alert on tab close
+ if dialogs.saveEvent(self):
+ self.tabber.widget(currentIndex).deleteLater()
+ self.tabber.removeTab(currentIndex)
+
+ def newDiagram(self):
+ # helper function to add a new tab on pressing new tab button, using the add tab method on QTabWidget
+ diagram = canvas(self.tabber)
+ diagram.setObjectName("New")
+ index = self.tabber.addTab(diagram, "New")
+ self.tabber.setCurrentIndex(index)
+
+ def saveProject(self, name = None):
+ # called by dialog.saveEvent, saves the current file
+ name = QFileDialog.getSaveFileName(self, 'Save File', f'New Diagram', 'Process Flow Diagram (*.pfd)') if not name else name
+ if name[0]:
+ with open(name[0],'wb') as file:
+ pickle.dump(self, file)
+ return True
+ else:
+ return False
+
+ def closeEvent(self, event):
+ # handle save alert on file close, check if current file has no tabs aswell.
+ if self.tabCount==0 or dialogs.saveEvent(self):
+ event.accept()
+ self.deleteLater()
+ self.fileCloseEvent.emit(self.index)
+ 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.
+ def __getstate__(self) -> dict:
+ return {
+ "_classname_": self.__class__.__name__,
+ "ObjectName": self.objectName(),
+ "ppi": self._ppi,
+ "canvasSize": self._canvasSize,
+ "tabs": [i.__getstate__() for i in self.tabList]
+ }
+
+ def __setstate__(self, dict):
+ self.__init__(title = dict['ObjectName'])
+ for i in dict['tabs']:
+ diagram = canvas(self.tabber, size = dict['canvasSize'], ppi = dict['ppi'], fileWindow = self)
+ diagram.__setstate__(i)
+ self.tabber.addTab(diagram, i['ObjectName'])