Source code for puzzlestream.ui.main

# -*- coding: utf-8 -*-
"""Main window module.

contains PSMainWindow, a subclass of QMainWindow
"""

import gc
import importlib
import inspect
import json
import os
import shutil
import subprocess
import sys
import time
import webbrowser
import zipfile
from distutils.version import LooseVersion
from threading import Thread
from typing import Callable
from urllib.request import urlopen

import pkg_resources
from appdirs import user_config_dir
from pyqode.python.backend import server
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

import puzzlestream.ui.resources as resources
from puzzlestream.backend import notificationsystem
from puzzlestream.backend.status import PSStatus
from puzzlestream.ui import colors
from puzzlestream.ui.about import PSAboutWindow
from puzzlestream.ui.codeeditor import PSCodeEdit
from puzzlestream.ui.dataview import PSDataView
from puzzlestream.ui.editorwidget import PSEditorWidget
from puzzlestream.ui.gittab import PSGitTab
from puzzlestream.ui.graphicsview import PSGraphicsView
from puzzlestream.ui.manager import PSManager
from puzzlestream.ui.module import PSModule
from puzzlestream.ui.notificationtab import PSNotificationTab
from puzzlestream.ui.outputtextedit import PSOutputTextEdit
from puzzlestream.ui.pip import PSPipGUI
from puzzlestream.ui.pipe import PSPipe
from puzzlestream.ui.plotview import PSPlotView
from puzzlestream.ui.preferences import PSPreferencesWindow
from puzzlestream.ui.puzzleitem import PSPuzzleItem
from puzzlestream.ui.switch import PSSlideSwitch
from puzzlestream.ui.translate import changeLanguage

translate = QCoreApplication.translate


[docs]class PSMainWindow(QMainWindow): def __init__(self): super().__init__() self.__manager = PSManager(self) self.__manager.configChanged.connect(self.__configChanged) self.__manager.scene.nameChanged.connect(self.__nameChanged) self.__manager.scene.itemDeleted.connect(self.__itemDeleted) self.setupUi() self.__activeModule = None self.__subWindows = [] self.puzzleGraphicsView.setScene(self.__manager.scene) self.puzzleGraphicsView.setConfig(self.__manager.config) currentDir = os.path.dirname(__file__) self.setWindowIcon(QIcon( os.path.join(currentDir, "../icons//Puzzlestream.png"))) # editor initialisation w = self.__newEditorWidget() self.__editorWidgets = [w] self.__editors = [w.editor] w.moduleFileOpened.connect(self.__updateActiveModuleFromFileExplorer) self.horizontalSplitter.insertWidget(0, w) self.btnOpenCloseSecondEditor = QPushButton(self) self.btnOpenCloseSecondEditor.setText("+") self.__editorWidgets[0].editorHeaderLayout.addWidget( self.btnOpenCloseSecondEditor) self.btnOpenCloseSecondEditor.setStyleSheet( "QPushButton { min-width: 1em; max-width: 1em; " + "min-height: 1em; max-height: 1em; }" ) self.btnOpenCloseSecondEditor.clicked.connect( lambda: self.changeRightWidgetMode("editor")) self.__rightWidget = self.verticalSplitter self.__rightWidgetMode = "puzzle" # add active module run / pause / stop self.btnRunPauseActive = QToolButton() self.btnRunPauseActive.clicked.connect(self.__runPauseActiveModuleOnly) self.btnStopActive = QToolButton() self.btnStopActive.clicked.connect(self.__stopActiveModule) w.editorHeaderLayout.insertWidget(0, self.btnRunPauseActive) w.editorHeaderLayout.insertWidget(1, self.btnStopActive) # pre-add second editor for windows performance reasons w = self.__newEditorWidget() self.__editorWidgets.append(w) self.__editors.append(w.editor) # welcome screen self.__welcomeLabel = QLabel(self) font = QFont() font.setPointSize(16) self.__welcomeLabel.setFont(font) self.__welcomeLabel.setTextFormat(Qt.RichText) self.__welcomeLabel.setAlignment(Qt.AlignCenter) self.__welcomeLabel.setContentsMargins(5, 0, 5, 0) self.__welcomeLabel.linkActivated.connect( self.__welcomeLabelLinkActivated) self.horizontalSplitter.insertWidget( 0, self.__welcomeLabel) self.horizontalSplitter.setStretchFactor(0, 1) self.horizontalSplitter.setStretchFactor(1, 6) self.horizontalSplitter.setStretchFactor(2, 3) self.horizontalSplitter.setCollapsible(0, True) self.horizontalSplitter.setCollapsible(1, True) self.verticalSplitter.setStretchFactor(0, 10) self.verticalSplitter.setStretchFactor(1, 1) self.verticalSplitter.setCollapsible(1, True) # create puzzle toolbar actions self.__createPuzzleToolBarActions() for action in self.__getPuzzleToolBarActions(): self.puzzleToolBar.addAction(action) self.__btnAddStatusAbort = QPushButton(self.startToolBar) self.__btnAddStatusAbort.clicked.connect(self.__abortAdding) self.__btnAddStatusAction = self.puzzleToolBar.addWidget( self.__btnAddStatusAbort) self.__btnAddStatusAction.setVisible(False) self.__puzzleLabel = QLabel(self.puzzleToolBar) self.puzzleToolBar.addWidget(self.__puzzleLabel) self.__lblPuzzleLock = QLabel() self.__btnPuzzleLock = PSSlideSwitch() self.__btnPuzzleLock.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.__btnPuzzleLock.setStyleSheet( "* { min-width: 2.2em; max-width: 2.2em; " + "min-height: 1.3em; max-height: 1.3em; }" ) self.__btnPuzzleLock.toggled.connect(self.__togglePuzzleLocked) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) spacer2 = QWidget() spacer2.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) spacer2.setStyleSheet( "QWidget { min-width: 0.2em; max-width: 0.2em; }") self.puzzleToolBar.addWidget(spacer) self.puzzleToolBar.addWidget(self.__lblPuzzleLock) self.puzzleToolBar.addWidget(spacer2) self.puzzleToolBar.addWidget(self.__btnPuzzleLock) # create menu bar self.__createLeftCornerToolbarActions() self.__createRightCornerToolbarActions() self.__createStartToolBarActions() self.__createEditToolBarActions() self.__createAppsToolBarActions() self.__createStreamToolBarActions() self.__createHelpToolBarActions() self.__updateRecentProjects() self.__createLibToolBarActions() # create graphics scene self.__createGraphicsScene() # set active module to None self.__resetActiveModule() # set style self.__currentStyle = None design = self.__manager.config["design"] self.__setStyle(design[1][design[0]]) # shortcuts self.__shortcuts = {} self.addShortcut("F5", self.__runPauseActiveModuleOnly) self.addShortcut("Alt+F5", self.__runPauseActiveModule) self.addShortcut("Ctrl+F5", self.__run) self.addShortcut("F11", self.__toggleFullscreen) self.addShortcut("Ctrl+s", self.__saveOpenFiles) self.addShortcut("Esc", self.__abortAdding) # git self.__gitTab = PSGitTab(self.outputTabWidget) self.outputTabWidget.addTab(self.__gitTab, "Git") self.__manager.updateSignal.connect(self.__gitTab.reload) self.__gitTab.reloadSignal.connect(self.__updateGitTabHeaderAndMenu) self.__gitTab.fileSaveSignal.connect(self.__saveOpenFiles) self.__gitTab.fileUpdateSignal.connect(self.__reloadAllEditors) self.__createGitToolBarActions() # notifications self.__notificationTab = PSNotificationTab( self.outputTabWidget) self.outputTabWidget.addTab(self.__notificationTab, "Notifications (0)") notificationsystem.addReactionMethod( self.__notificationTab.addNotification, throwArchived=True ) self.__notificationTab.setNumberUpdateMethod( self.__updateNotificationHeader) # collect elements that should be activated and deactivated self.__activeElements = [ self.puzzleGraphicsView, self.__newModuleMenu, self.__newPipeAction, self.__newValveAction, self.__undoAction, self.__redoAction, self.__copyAction, self.__cutAction, self.__pasteAction, self.__runAction, self.__pauseAction, self.__stopAction, self.__btnPuzzleLock, self.__dataToolbarAction, self.__plotToolbarAction, self.__cleanToolbarAction ] """ ======================================================================= Start update check """ thr = Thread(target=self.__checkForUpdates) thr.start() self.__updateCheckTimer = QTimer() self.__updateCheckTimer.singleShot(5000, self.__updateCheckFinished) """ ======================================================================= Show window """ self.retranslateUi() self.resize(1200, 800) self.showMaximized() self.__lastWindowState = "maximized" self.__manager.dirty = False @property def __newProjectText(self) -> str: color = colors.get("standard-blue") text = translate( "MainWindow", "<img src=\":/Puzzlestream.png\" width=\"128\" height=\"128\">" + "<p>You may <a href=\"#new_project\">" "<span style=\"color: #177dc9;\">create a new project " + "folder</span></a> or <a href=\"#open_project\">" "<span style=\"color: #177dc9;\">open an existing " + "project</span></a><br>and start working.</p><br><p>" + "<font size=\"-1\">Last projects:</font></p>" ) for item in self.__manager.config["last projects"][::-1]: text += "<a href=\"#last_project:" text += "%s\"><span style=\"color: %s;\">" % (item, color) text += "<font size=\"-2\">%s" % (item) text += "</font></span></a><br>" return text @property def __projectOpenText(self) -> str: return translate( "MainWindow", "Add a new module or select an existing one<br>to edit its " + "source code." ) @property def __newItemText(self) -> str: return translate( "MainWindow", "Click left on the scrollable puzzle region to add a " )
[docs] def setupUi(self): self.setObjectName("MainWindow") self.centralGrid = QWidget(self) self.centralGrid.setObjectName("centralGrid") self.gridLayout = QGridLayout(self.centralGrid) self.gridLayout.setObjectName("gridLayout") self.gridLayout.setContentsMargins(0, 0, 0, 0) self.horizontalSplitter = QSplitter(self.centralGrid) self.horizontalSplitter.setOrientation(Qt.Horizontal) self.horizontalSplitter.setObjectName("horizontalSplitter") self.verticalSplitter = QSplitter(self.horizontalSplitter) self.verticalSplitter.setOrientation(Qt.Vertical) self.verticalSplitter.setObjectName("verticalSplitter") self.verticalSplitter.setContentsMargins(0, 0, 0, 0) self.puzzleWidget = QWidget(self.verticalSplitter) self.puzzleWidget.setObjectName("puzzleWidget") self.puzzleWidgetLayout = QVBoxLayout() self.puzzleWidget.setLayout(self.puzzleWidgetLayout) self.puzzleWidgetLayout.setContentsMargins(0, 0, 0, 0) self.puzzleGraphicsView = PSGraphicsView(self.__manager, self.puzzleWidget) self.puzzleGraphicsView.focusIn.connect(self.__puzzleFocusIn) self.puzzleGraphicsView.setObjectName("puzzleGraphicsView") self.puzzleWidgetLayout.addWidget(self.puzzleGraphicsView) self.outputTabWidget = QTabWidget(self.verticalSplitter) self.outputTabWidget.setObjectName("outputTabWidget") self.textTab = QWidget() self.textTab.setObjectName("textTab") self.textTabGridLayout = QGridLayout(self.textTab) self.textTabGridLayout.setObjectName("textTabGridLayout") self.outputTextEdit = PSOutputTextEdit(self.textTab) self.outputTextEdit.setObjectName("outputTextEdit") self.textTabGridLayout.addWidget(self.outputTextEdit, 0, 0, 1, 1) self.outputTabWidget.addTab(self.textTab, "") self.statisticsTab = QWidget() self.statisticsTab.setObjectName("statisticsTab") self.statisticsTabGridLayout = QGridLayout( self.statisticsTab) self.statisticsTabGridLayout.setObjectName("statisticsTabGridLayout") self.vertPlotTabLayout = QVBoxLayout() self.vertPlotTabLayout.setObjectName("vertPlotTabLayout") self.horPlotSelComboBoxLayout = QHBoxLayout() self.horPlotSelComboBoxLayout.setObjectName("horPlotSelComboBoxLayout") self.vertPlotTabLayout.addLayout(self.horPlotSelComboBoxLayout) self.statisticsTabGridLayout.addLayout( self.vertPlotTabLayout, 0, 0, 1, 1) self.outputTabWidget.addTab(self.statisticsTab, "") self.statisticsTextEdit = QTextEdit(self.statisticsTab) self.statisticsTextEdit.setReadOnly(True) self.statisticsTextEdit.setObjectName("statisticsTextEdit") self.statisticsTabGridLayout.addWidget(self.statisticsTextEdit, 0, 0, 1, 1) self.gridLayout.addWidget(self.horizontalSplitter, 1, 1) self.setCentralWidget(self.centralGrid) self.mainTabWidget = QTabWidget() self.mainTabWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.mainTabWidget.tabBar().setObjectName("mainTabBar") self.startToolBar = QToolBar(self) self.startToolBar.setObjectName("startToolBar") self.startToolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.mainTabWidget.addTab(self.startToolBar, "Start") self.editToolBar = QToolBar(self) self.editToolBar.setObjectName("editToolBar") self.editToolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.mainTabWidget.addTab(self.editToolBar, "Edit") self.puzzleToolBar = QToolBar(self) self.puzzleToolBar.setObjectName("puzzleToolBar") self.puzzleToolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.mainTabWidget.addTab(self.puzzleToolBar, "Puzzle") self.appsToolBar = QToolBar(self) self.appsToolBar.setObjectName("appsToolBar") self.appsToolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.mainTabWidget.addTab(self.appsToolBar, "Apps") self.libToolBar = QToolBar(self) self.libToolBar.setObjectName("libToolBar") self.libToolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.mainTabWidget.addTab(self.libToolBar, "Libraries") self.streamToolBar = QToolBar(self) self.streamToolBar.setObjectName("streamToolBar") self.streamToolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.mainTabWidget.addTab(self.streamToolBar, "Stream") self.gitToolBar = QToolBar(self) self.gitToolBar.setObjectName("gitToolBar") self.gitToolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.mainTabWidget.addTab(self.gitToolBar, "Git") self.helpToolBar = QToolBar(self) self.helpToolBar.setObjectName("helpToolBar") self.helpToolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.mainTabWidget.addTab(self.helpToolBar, "Help") self.gridLayout.addWidget(self.mainTabWidget, 0, 0, 1, 2) self.leftCornerToolBar = QToolBar(self) self.leftCornerToolBar.setObjectName("leftCornerToolBar") self.mainTabWidget.setCornerWidget(self.leftCornerToolBar, Qt.TopLeftCorner) self.rightCornerToolBar = QToolBar(self) self.rightCornerToolBar.setObjectName("rightCornerToolBar") self.mainTabWidget.setCornerWidget(self.rightCornerToolBar, Qt.TopRightCorner)
[docs] def retranslateUi(self): self.setWindowTitle(translate("MainWindow", "Puzzlestream")) self.__updateProjectLoadedStatus() self.outputTabWidget.setTabText( self.outputTabWidget.indexOf(self.textTab), translate( "MainWindow", "Output")) self.outputTabWidget.setTabText( self.outputTabWidget.indexOf(self.statisticsTab), translate( "MainWindow", "Statistics")) self.mainTabWidget.setTabText(0, translate("MainWindow", "Start")) self.mainTabWidget.setTabText(1, translate("MainWindow", "Edit")) self.mainTabWidget.setTabText(2, translate("MainWindow", "Puzzle")) self.mainTabWidget.setTabText(3, translate("MainWindow", "Apps")) self.mainTabWidget.setTabText(4, translate("MainWindow", "Libraries")) self.mainTabWidget.setTabText(5, translate("MainWindow", "Stream")) self.mainTabWidget.setTabText(6, translate("MainWindow", "Git")) self.mainTabWidget.setTabText(7, translate("MainWindow", "Help")) self.btnOpenCloseSecondEditor.setToolTip("Open second editor") self.btnRunPauseActive.setToolTip("Run current module") self.btnStopActive.setToolTip("Stop current module") self.__btnAddStatusAbort.setText(translate("MainWindow", "Abort")) self.__dataToolbarAction.setText(translate("MainWindow", "Show &data")) self.__plotToolbarAction.setText( translate("MainWindow", "Show &plots")) self.__cleanToolbarAction.setText( translate("MainWindow", "&Clean stream")) self.__createLibToolBarActions() self.__fetchToolbarAction.setText( translate("MainWindow", "&Fetch / reload")) self.__pullToolbarAction.setText(translate("MainWindow", "Pull")) self.__pushToolbarAction.setText(translate("MainWindow", "Push")) self.__preferencesToolbarAction.setText( translate("MainWindow", "Pre&ferences")) self.__userGuideToolbarAction.setText( translate("MainWindow", "&User guide")) self.__aboutToolbarAction.setText( translate("MainWindow", "&About Puzzlestream")) self.__websiteToolbarAction.setText( translate("MainWindow", "&Puzzlestream website")) self.__debugToolbarAction.setText( translate("MainWindow", "&Save debug information")) self.__newModuleMenu.setTitle(translate("MainWindow", "New module")) self.__newIntModuleAction.setText( translate("MainWindow", "New internal module")) self.__newExtModuleAction.setText( translate("MainWindow", "New external module")) self.__newTemplateModuleMenu.setTitle( translate("MainWindow", "From template")) self.__newPipeAction.setText(translate("MainWindow", "New pipe")) self.__newValveAction.setText(translate("MainWindow", "New valve")) self.__createGraphicsSceneContextMenu() self.btnOpenCloseSecondEditor.setToolTip( translate("MainWindow", "Open second editor")) if self.__activeModule is not None: self.__updateActiveModule(self.__activeModule) self.__updateNotificationHeader() for m in self.__manager.scene.modules.values(): m.visualStatusUpdate(m) self.__newProjectAction.setText( translate("MainWindow", "New project")) self.__openProjectAction.setText( translate("MainWindow", "Open project")) self.__saveProjectAsAction.setText( translate("MainWindow", "&Save project as...")) self.__recentProjectsMenu.setTitle( translate("MainWindow", "&Recent projects")) self.__closeProjectAction.setText( translate("MainWindow", "&Close project")) self.__saveProjectAction.setText(translate("MainWindow", "Save file")) self.__undoAction.setText(translate("MainWindow", "Back")) self.__redoAction.setText(translate("MainWindow", "Forward")) self.__copyAction.setText(translate("MainWindow", "Copy")) self.__cutAction.setText(translate("MainWindow", "Cut")) self.__pasteAction.setText(translate("MainWindow", "Paste")) self.__autoformatToolbarAction.setText( translate("MainWindow", "&Format code")) self.__sortImportsToolbarAction.setText( translate("MainWindow", "&Sort imports")) self.__runAction.setText(translate("MainWindow", "Run puzzle")) self.__pauseAction.setText(translate("MainWindow", "Pause puzzle")) self.__stopAction.setText(translate("MainWindow", "Stop puzzle")) self.__puzzleViewAction.setText(translate("MainWindow", "Puzzle")) self.__dataViewAction.setText(translate("MainWindow", "Data view")) self.__plotViewAction.setText(translate("MainWindow", "Plot view")) self.__togglePuzzleLocked()
[docs] def changeEvent(self, event: QEvent): if event.type() == QEvent.LanguageChange: self.retranslateUi() return super().changeEvent(event)
def __setStyle(self, style: str): self.__currentStyle = style currentDir = os.path.dirname(__file__) colors.update(os.path.join(currentDir, "style/" + style + ".yml")) self.setStyleSheet( colors.parseQSS(currentDir + "/style/sheet-em.qss")) for e in self.__editors: e.setSyntaxColorScheme(style) for w in self.__subWindows: w.setStyleSheet( colors.parseQSS(currentDir + "/style/sheet-em.qss")) if style == "dark": self.__newProjectAction.setIcon( QIcon(os.path.join(currentDir, "../icons//new_project_white.png"))) self.__openProjectAction.setIcon( QIcon(os.path.join(currentDir, "../icons//folder_white.png"))) self.__saveProjectAction.setIcon( QIcon(os.path.join(currentDir, "../icons//save_blue_in.png"))) self.__undoAction.setIcon( QIcon(os.path.join(currentDir, "../icons//back_white.png"))) self.__redoAction.setIcon( QIcon(os.path.join(currentDir, "../icons//forward_white.png"))) self.__copyAction.setIcon( QIcon(os.path.join(currentDir, "../icons//copy_blue_in.png"))) self.__cutAction.setIcon( QIcon(os.path.join(currentDir, "../icons//cut_blue_in.png"))) self.__pasteAction.setIcon( QIcon(os.path.join(currentDir, "../icons//paste_blue_in.png"))) self.__runAction.setIcon( QIcon(os.path.join(currentDir, "../icons/play_blue_in.png"))) self.__pauseAction.setIcon( QIcon(os.path.join(currentDir, "../icons/pause_blue_in.png"))) self.__stopAction.setIcon( QIcon(os.path.join(currentDir, "../icons//stop_blue_in.png"))) self.btnStopActive.setIcon( QIcon(os.path.join(currentDir, "../icons//stop_blue_in.png"))) elif style == "light": self.__newProjectAction.setIcon( QIcon(os.path.join(currentDir, "../icons//new_project_blue.png"))) self.__openProjectAction.setIcon( QIcon(os.path.join(currentDir, "../icons//folder_blue.png"))) self.__saveProjectAction.setIcon( QIcon(os.path.join(currentDir, "../icons//save_blue_out.png"))) self.__undoAction.setIcon( QIcon(os.path.join(currentDir, "../icons//back_blue.png"))) self.__redoAction.setIcon( QIcon(os.path.join(currentDir, "../icons//forward_blue.png"))) self.__copyAction.setIcon( QIcon(os.path.join(currentDir, "../icons//copy_blue_out.png"))) self.__cutAction.setIcon( QIcon(os.path.join(currentDir, "../icons//cut_blue_out.png"))) self.__pasteAction.setIcon( QIcon(os.path.join(currentDir, "../icons//paste_blue_out.png"))) self.__runAction.setIcon( QIcon(os.path.join(currentDir, "../icons/play_blue_out.png"))) self.__pauseAction.setIcon( QIcon(os.path.join(currentDir, "../icons/pause_blue_out.png"))) self.__stopAction.setIcon( QIcon(os.path.join(currentDir, "../icons//stop_blue_out.png"))) self.btnStopActive.setIcon( QIcon(os.path.join(currentDir, "../icons//stop_blue_out.png"))) self.updateActiveModuleButtons() color = colors.get("corner-toolbar") bgcolor = colors.get("corner-toolbar-background") for w in [ self.rightCornerToolBar.widgetForAction(self.__puzzleViewAction), self.rightCornerToolBar.widgetForAction(self.__dataViewAction), self.rightCornerToolBar.widgetForAction(self.__plotViewAction) ]: w.setStyleSheet( "QToolButton { color: %s; background-color: %s; }" % ( color, bgcolor) ) titleBarColor = colors.get("Qt-title-bar-background") self.leftCornerToolBar.setStyleSheet( "QWidget { background-color: %s; }" % (titleBarColor)) self.rightCornerToolBar.setStyleSheet( "QWidget { background-color: %s; }" % (titleBarColor)) self.centralGrid.setStyleSheet( "QWidget#centralGrid { background-color: %s; }" % (titleBarColor)) self.mainTabWidget.setStyleSheet( "QTabBar { background-color: %s; }" % (titleBarColor)) self.gridLayout.setSpacing(0) self.__manager.scene.setBackgroundBrush( QBrush(QColor(colors.get("Qt-graphicsscene-background")))) def __autoformatFirstEditor(self): self.__editors[0].autoformat() def __sortImportsInFirstEditor(self): self.__editors[0].sortImports() def __saveOpenFiles(self): for e in self.__editors: if self.__manager.config["autoformatOnSave"]: e.autoformat() e.file.save() self.__fileDirty(False) self.__gitTab.reload() def __closeOpenFiles(self): for e in self.__editors: e.file.close() def __reloadAllEditors(self): for e in self.__editors: if e.file.path != "": e.file.reload(e.file.encoding) def __stopAllEditors(self): for e in self.__editors: e.backend.stop() def __autoformatAllEditors(self): for e in self.__editors: e.autoformat() def __sortImportsInAllEditors(self): for e in self.__editors: e.sortImports() def __newEditorWidget(self) -> PSEditorWidget: e = PSEditorWidget() design = self.__manager.config["design"] e.editor.setSyntaxColorScheme(design[1][design[0]]) # editor settings e.editor.save_on_focus_out = self.__manager.config[ "saveOnEditorFocusOut"] e.editor.replace_tabs_by_spaces = True e.editor.dirty_changed.connect(self.__fileDirty) e.editor.textChanged.connect( lambda: self.__editorTextChanged(e.editor)) e.editor.focusIn.connect(self.__editorFocusIn) return e def __editorFocusIn(self): self.mainTabWidget.setCurrentIndex(1) def __puzzleFocusIn(self): self.mainTabWidget.setCurrentIndex(2)
[docs] def openPuzzle(self): self.verticalSplitter.show() self.__enableAddActions() self.__rightWidget = self.verticalSplitter
[docs] def closePuzzle(self): self.verticalSplitter.hide() self.__disableAddActions() self.__rightWidget = None
[docs] def openSecondEditor(self, oldMode: str = "puzzle"): w = self.__editorWidgets[1] self.horizontalSplitter.insertWidget(2, w) i = self.horizontalSplitter.indexOf( self.verticalSplitter) self.__updateEditorModule(self.__activeModule, 1) self.btnOpenCloseSecondEditor.setText("-") self.btnOpenCloseSecondEditor.clicked.disconnect() self.btnOpenCloseSecondEditor.setToolTip( translate("MainWindow", "Close second editor")) self.btnOpenCloseSecondEditor.clicked.connect( lambda: self.changeRightWidgetMode(oldMode)) self.__rightWidget = w self.__rightWidgetMode = "editor" self.__rightWidget.show()
[docs] def closeSecondEditor(self): w = self.__editorWidgets[1] w.editor.file.save() w.hide() w.setParent(None) self.btnOpenCloseSecondEditor.setText("+") self.btnOpenCloseSecondEditor.clicked.disconnect() self.btnOpenCloseSecondEditor.clicked.connect( lambda: self.changeRightWidgetMode("editor")) self.__rightWidget = None
[docs] def openDataview(self): w = PSDataView(self.__manager, self.__activeModule, self) self.__manager.scene.statusChanged.connect(w.statusUpdate) self.horizontalSplitter.insertWidget(2, w) self.__rightWidget = w self.__rightWidgetMode = "dataview"
[docs] def closeDataview(self): self.__rightWidget.close() self.__rightWidget.hide() self.__rightWidget.setParent(None) del self.__rightWidget self.__rightWidget = None
[docs] def openPlotview(self): w = PSPlotView(self.__manager, self.__activeModule, self) self.__manager.scene.statusChanged.connect(w.statusUpdate) self.horizontalSplitter.insertWidget(2, w) self.__rightWidget = w self.__rightWidgetMode = "dataview"
[docs] def closePlotview(self): self.__rightWidget.close() self.__rightWidget.hide() self.__rightWidget.setParent(None) del self.__rightWidget self.__rightWidget = None
[docs] def changeRightWidgetMode(self, mode: str): if mode != self.__rightWidgetMode: self.__manager.addStatus = None self.__puzzleLabel.setText("") self.__btnAddStatusAction.setVisible(False) s = self.horizontalSplitter.sizes() # close old mode if self.__rightWidgetMode == "editor": self.closeSecondEditor() elif self.__rightWidgetMode == "puzzle": self.closePuzzle() elif self.__rightWidgetMode == "dataview": self.closeDataview() elif self.__rightWidgetMode == "plotview": self.closePlotview() # choose new mode if mode == "editor": self.openSecondEditor(self.__rightWidgetMode) for w in [self.__puzzleViewAction, self.__dataViewAction, self.__plotViewAction]: w.setEnabled(True) elif mode == "puzzle": self.openPuzzle() for w in [self.__dataViewAction, self.__plotViewAction]: w.setEnabled(True) self.__puzzleViewAction.setEnabled(False) elif mode == "dataview": self.openDataview() for w in [self.__puzzleViewAction, self.__plotViewAction]: w.setEnabled(True) self.__dataViewAction.setEnabled(False) elif mode == "plotview": self.openPlotview() for w in [self.__puzzleViewAction, self.__dataViewAction]: w.setEnabled(True) self.__plotViewAction.setEnabled(False) self.__rightWidgetMode = mode # restore sizes if len(self.horizontalSplitter.sizes()) == 3: self.horizontalSplitter.setSizes( [0, s[1], sum(s) - s[1]]) else: self.horizontalSplitter.setSizes( [0, s[1], sum(s) - s[1], 0])
def __editorTextChanged(self, editor: PSCodeEdit): if editor.hasFocus(): for e in self.__editors: if e != editor and e.file.path == editor.file.path: e.setPlainText(editor.toPlainText())
[docs] def closeEvent(self, event: QEvent): if not self.__manager.dirty: result = True else: quest = QMessageBox.question( self, translate("MainWindow", "Confirm closing"), translate("MainWindow", "The current project has not been saved. " + "Do you really want to continue?"), QMessageBox.Yes | QMessageBox.No | QMessageBox.Save, QMessageBox.Save ) result = quest in (QMessageBox.Yes, QMessageBox.Save) if quest == QMessageBox.Save: self.__saveOpenFiles() self.__saveProject() if result: if self.__manager.projectPath is not None: self.__manager.stopAllWorkers() self.__manager.close() self.__closeOpenFiles() self.__stopAllEditors() event.accept() QApplication.quit() exit() else: event.ignore()
def __resetUI(self, path: str): self.__editorWidgets[0].hide() self.outputTextEdit.setText("") self.statisticsTextEdit.setText("") def __createGraphicsScene(self): scene = self.__manager.scene scene.stdoutChanged.connect(self.updateText) scene.statusChanged.connect(self.updateStatus) scene.itemAdded.connect(self.__itemAdded) scene.dataViewRequested.connect(self.__showData) scene.plotViewRequested.connect(self.__showPlots) scene.selectionChanged.connect(self.__selectionChanged) def __createGraphicsSceneContextMenu(self): menu = QMenu() newModuleMenu = menu.addMenu( translate("MainWindow", "New module")) newInternalModuleAction = newModuleMenu.addAction( translate("MainWindow", "New internal module")) newExternalModuleAction = newModuleMenu.addAction( translate("MainWindow", "New external module")) newTemplateModuleMenu = newModuleMenu.addMenu( translate("MainWindow", "From template")) newPipeAction = menu.addAction(translate("MainWindow", "New pipe")) newValveAction = menu.addAction(translate("MainWindow", "New valve")) for a in self.__getTemplateActions(): action = newTemplateModuleMenu.addAction(a) action.triggered.connect( lambda _, p=a: self.__newIntModuleFromTemplate(templatePath=p)) action = newTemplateModuleMenu.addAction( translate("MainWindow", "-> Manage templates")) path = os.path.join(user_config_dir("Puzzlestream"), "templates") action.triggered.connect(lambda _, p=path: self.__open_file(p)) newModuleMenu.menuAction().triggered.connect(self.__newIntModule) newInternalModuleAction.triggered.connect(self.__newIntModule) newExternalModuleAction.triggered.connect(self.__newExtModule) newPipeAction.triggered.connect(self.__newPipe) newValveAction.triggered.connect(self.__newValve) self.__manager.scene.setStandardContextMenu(menu)
[docs] def addShortcut(self, sequence: str, target: Callable): sc = QShortcut(QKeySequence(sequence), self) sc.activated.connect(target) self.__shortcuts[sequence] = sc
def __selectionChanged(self): if len(self.__manager.scene.selectedItemList) == 1: puzzleItem = self.__manager.scene.selectedItemList[0] if isinstance(puzzleItem, PSModule): self.__updateActiveModule(puzzleItem) def __updateActiveModule(self, module: PSModule): self.__updateEditorModule(module) self.__activeModule = module for a in self.appsToolBar.actions(): a.setEnabled(self.__activeModule is not None) self.outputTextEdit.setText(module.stdout) cursor = self.outputTextEdit.textCursor() cursor.movePosition(cursor.End) self.outputTextEdit.setTextCursor(cursor) self.outputTextEdit.ensureCursorVisible() font = QFont("Fira Code", pointSize=9) font.setStyleStrategy(QFont.PreferAntialias) self.outputTextEdit.setFont(font) self.statisticsTextEdit.setHtml(module.statistics) self.outputTabWidget.setTabText( 0, translate("MainWindow", "Output") + " - " + module.name) self.outputTabWidget.setTabText( 1, translate("MainWindow", "Statistics") + " - " + module.name) self.__welcomeLabel.hide() self.__editorWidgets[0].show() self.updateActiveModuleButtons() for a in [self.__plotViewAction, self.__dataViewAction]: a.setEnabled(True) if self.__rightWidgetMode in ["dataview", "plotview"]: self.__rightWidget.updatePuzzleItem(module) def __updateActiveModuleFromFileExplorer(self, module: PSModule): self.__updateActiveModule(module) scene = self.__manager.scene scene.selectionChanged.disconnect(self.__selectionChanged) scene.clearSelection() module.setSelected(True) scene.selectionChanged.connect(self.__selectionChanged) def __updateEditorModule(self, module: PSModule, i: int = 0): if not os.path.exists(module.filePath): module.createModuleScript() self.__updateEditorModuleList() self.__editorWidgets[i].openModule(module) def __updateEditorModuleList(self): for e in self.__editorWidgets: e.setModules(self.__manager.scene.modules.values()) def __runPauseActiveModule(self, stopHere: bool = False): if self.__activeModule is not None: if self.__manager.config["saveOnRun"]: self.__saveOpenFiles() if (self.__activeModule.status in [PSStatus.INCOMPLETE, PSStatus.FINISHED, PSStatus.ERROR]): self.__activeModule.run(stopHere=stopHere) elif self.__activeModule.status is PSStatus.PAUSED: self.__activeModule.resume() elif self.__activeModule.status is PSStatus.RUNNING: self.__activeModule.pause() def __runPauseActiveModuleOnly(self): self.__runPauseActiveModule(stopHere=True) def __stopActiveModule(self): self.__activeModule.stop() def __nameChanged(self, module: PSModule): if module == self.__activeModule: self.__updateActiveModule(module) def __fileDirty(self, dirty: bool): if dirty: self.__manager.dirty = True def __dirtyChanged(self, dirty: bool): self.__saveProjectAction.setEnabled(dirty) def __createLeftCornerToolbarActions(self): self.__saveProjectAction = self.leftCornerToolBar.addAction("") self.__saveProjectAction.setEnabled(False) self.__saveProjectAction.triggered.connect(self.__saveProject) self.__manager.dirtyChanged.connect(self.__dirtyChanged) def __createRightCornerToolbarActions(self): self.__puzzleViewAction = self.rightCornerToolBar.addAction("") self.__dataViewAction = self.rightCornerToolBar.addAction("") self.__plotViewAction = self.rightCornerToolBar.addAction("") for a in [self.__puzzleViewAction, self.__dataViewAction, self.__plotViewAction]: a.setEnabled(False) self.__puzzleViewAction.triggered.connect( lambda: self.changeRightWidgetMode("puzzle")) self.__dataViewAction.triggered.connect( lambda: self.changeRightWidgetMode("dataview")) self.__plotViewAction.triggered.connect( lambda: self.changeRightWidgetMode("plotview")) def __createStartToolBarActions(self): self.__newProjectAction = self.startToolBar.addAction("") self.__openProjectAction = self.startToolBar.addAction("") self.__saveProjectAsAction = self.startToolBar.addAction("") self.__closeProjectAction = self.startToolBar.addAction("") self.__newProjectAction.triggered.connect(self.__newProject) self.__openProjectAction.triggered.connect(self.__openProject) self.__saveProjectAsAction.triggered.connect(self.__saveProjectAs) self.__closeProjectAction.triggered.connect(self.__closeProject) self.__recentProjectsMenu = QMenu("") self.startToolBar.insertAction( self.__saveProjectAsAction, self.__recentProjectsMenu.menuAction() ) def __createEditToolBarActions(self): self.__undoAction = self.editToolBar.addAction("") self.__redoAction = self.editToolBar.addAction("") self.__undoRedoSeparator = self.editToolBar.addSeparator() self.__cutAction = self.editToolBar.addAction("") self.__copyAction = self.editToolBar.addAction("") self.__pasteAction = self.editToolBar.addAction("") self.__cutCopyPasteSeparator = self.editToolBar.addSeparator() self.__autoformatToolbarAction = self.editToolBar.addAction("") self.__sortImportsToolbarAction = self.editToolBar.addAction("") self.__autoformatToolbarAction.triggered.connect( self.__autoformatFirstEditor) self.__sortImportsToolbarAction.triggered.connect( self.__sortImportsInFirstEditor) self.__connectEditorActions(self.__editors[0]) def __createAppsToolBarActions(self): self.appsToolBar.clear() for d in os.walk(os.path.join( os.path.dirname(__file__), "../apps/")): if "app.py" in d[2]: try: spec = importlib.util.spec_from_file_location( "app", os.path.join(d[0], "app.py")) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) extClass = getattr(mod, mod.app) a = self.appsToolBar.addAction(mod.name) if self.__activeModule is None: a.setEnabled(False) if hasattr(mod, "icon"): a.setIcon(QIcon(os.path.join(d[0], mod.icon))) a.triggered.connect( lambda trigger, x = extClass: self.__showAppGUI(x)) except Exception as e: print(e) def __createLibToolBarActions(self): self.libToolBar.clear() self.__pipGUIToolbarAction = self.libToolBar.addAction( translate("MainWindow", "pip package manager")) self.__pipGUIToolbarAction.setIcon(QIcon(os.path.join( os.path.dirname(__file__), "../icons/pipManager.png"))) self.__pipGUIToolbarAction.triggered.connect( self.__openPipGUI) self.__pipGUIToolbarAction.setEnabled(False) self.__addLibToolbarAction = self.libToolBar.addAction( translate("MainWindow", "Add lib path")) self.__libSeparator = self.libToolBar.addSeparator() self.__addLibToolbarAction.triggered.connect(self.__addLib) for path in self.__manager.config["libs"]: menu = QMenu(path, self.libToolBar) openAction = menu.addAction(translate("MainWindow", "Open folder")) openAction.triggered.connect(lambda: self.__open_file(path)) deleteAction = menu.addAction(translate("MainWindow", "Delete")) deleteAction.triggered.connect(lambda: self.__deleteLib(path)) self.libToolBar.addAction(menu.menuAction()) if path not in sys.path: sys.path.append(path) def __createStreamToolBarActions(self): self.__dataToolbarAction = self.streamToolBar.addAction("") self.__plotToolbarAction = self.streamToolBar.addAction("") self.streamToolBar.addSeparator() self.__cleanToolbarAction = self.streamToolBar.addAction("") self.__dataToolbarAction.triggered.connect(self.__showStreamDataView) self.__plotToolbarAction.triggered.connect(self.__showStreamPlotView) self.__cleanToolbarAction.triggered.connect(self.__clearStream) def __createGitToolBarActions(self): self.__fetchToolbarAction = self.gitToolBar.addAction("") self.__pullToolbarAction = self.gitToolBar.addAction("") self.__pushToolbarAction = self.gitToolBar.addAction("") self.__fetchToolbarAction.triggered.connect(self.__gitTab.fetch) self.__pullToolbarAction.triggered.connect(self.__gitTab.pull) self.__pushToolbarAction.triggered.connect(self.__gitTab.push) def __createHelpToolBarActions(self): self.__preferencesToolbarAction = self.helpToolBar.addAction("") self.__userGuideToolbarAction = self.helpToolBar.addAction("") self.__aboutToolbarAction = self.helpToolBar.addAction("") self.__websiteToolbarAction = self.helpToolBar.addAction("") self.__debugToolbarAction = self.helpToolBar.addAction("") spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.helpToolBar.addWidget(spacer) self.__preferencesToolbarAction.triggered.connect( self.__showPreferences) self.__userGuideToolbarAction.triggered.connect(self.__showUserGuide) self.__aboutToolbarAction.triggered.connect(self.__showAboutWindow) self.__websiteToolbarAction.triggered.connect(self.__showPSWebsite) self.__debugToolbarAction.triggered.connect( self.__exportDebugInformation) def __getTemplateActions(self): templateDir = os.path.join( user_config_dir("Puzzlestream"), "templates") if not os.path.isdir(templateDir): os.makedirs(templateDir) shutil.copyfile( os.path.join(os.path.abspath(os.path.dirname(__file__)), "../misc/templateReadme.md"), os.path.join(templateDir, "Readme.md") ) actions = [] for f in sorted(os.listdir(templateDir)): if (os.path.isfile(os.path.join(templateDir, f)) and f.endswith(".py")): yield f[:-3] def __toggleFullscreen(self): if self.isFullScreen(): if self.__lastWindowState == "maximized": self.showNormal() self.setWindowState(Qt.WindowMaximized) else: self.showNormal() else: if self.isMaximized(): self.__lastWindowState = "maximized" else: self.__lastWindowState = "normal" self.showFullScreen() def __showPreferences(self): preferences = PSPreferencesWindow(self.__manager.config, self) preferences.show() def __showStreamDataView(self): view = PSDataView(self.__manager) currentDir = os.path.dirname(__file__) view.setStyleSheet( colors.parseQSS(currentDir + "/style/sheet-em.qss")) self.__manager.scene.statusChanged.connect(view.statusUpdate) view.closedSignal.connect(self.__removeFromWindowList) view.showMaximized() self.__subWindows.append(view) def __showStreamPlotView(self): view = PSPlotView(self.__manager, None) currentDir = os.path.dirname(__file__) view.setStyleSheet( colors.parseQSS(currentDir + "/style/sheet-em.qss")) self.__manager.scene.statusChanged.connect(view.statusUpdate) view.closedSignal.connect(self.__removeFromWindowList) view.show() self.__subWindows.append(view) def __showAppGUI(self, AppClass): if self.__activeModule is not None: app = AppClass(self.__activeModule.streamSection.data) app.showGUI(self.__activeModule.id, self.__manager.scene.modules, self.__manager.stream, parent=self) else: notificationsystem.newNotification( translate( "MainWindow", "You have to choose a module before starting an app." ) ) def __removeFromWindowList(self, window: QMainWindow): i = self.__subWindows.index(window) self.__deleteTimer = QTimer() self.__deleteTimer.singleShot( 20, lambda: self.__removeFromWindowListTimed(i)) def __removeFromWindowListTimed(self, index: int): del self.__subWindows[index] gc.collect() def __clearStream(self): reply = QMessageBox.question( self, translate("MainWindow", "Confirm clean up"), translate( "MainWindow", "Are you sure you want to erase ALL data from the stream?" ), QMessageBox.No, QMessageBox.Yes ) if reply == QMessageBox.Yes: self.__manager.stream.clear() def __showUserGuide(self): webbrowser.open_new_tab( "http://documentation.puzzlestream.org/index-user.html") def __showAboutWindow(self): about = PSAboutWindow(self) def __showPSWebsite(self): webbrowser.open_new_tab("https://puzzlestream.org") def __exportDebugInformation(self): path = QFileDialog.getSaveFileName( self, translate("MainWindow", "Save debug information"), filter=translate("MainWindow", "Zip files (*.zip)"), initialFilter="Debug.zip" )[0] if path != "": sys.stdout.flush() sys.stderr.flush() try: with zipfile.ZipFile(path, mode="w") as f: configDir = user_config_dir("Puzzlestream") logDir = os.path.join(configDir, "logs") paths = [os.path.join(logDir, p) for p in os.listdir(logDir)] for p in paths: f.write(p, arcname=os.path.basename(p)) if self.__manager.projectPath is not None: puzzlePath = os.path.join( self.__manager.projectPath, "puzzle.json") if os.path.isfile(puzzlePath): f.write(puzzlePath, arcname="puzzle.json") configPath = os.path.join(configDir, "config.json") if os.path.isfile(configPath): f.write(configPath, arcname="config.json") notificationsystem.newNotification( translate( "MainWindow", "The debug information was successfully saved to" ) + " " + path + "." ) except Exception as e: notificationsystem.newNotification( translate( "MainWindow", "An error occured while saving the debug information:" ) + e ) def __updateRecentProjects(self): self.__recentProjectsMenu.clear() for item in self.__manager.config["last projects"][::-1]: action = self.__recentProjectsMenu.addAction(item) action.triggered.connect( lambda x, item=item: self.__openProject(item)) def __connectEditorActions(self, editor: PSCodeEdit): self.__undoAction.triggered.connect(editor.undo) self.__redoAction.triggered.connect(editor.redo) self.__copyAction.triggered.connect(editor.copy) self.__cutAction.triggered.connect(editor.cut) self.__pasteAction.triggered.connect(editor.paste) def __disconnectEditorActions(self): self.__undoAction.triggered.disconnect() self.__redoAction.triggered.disconnect() self.__copyAction.triggered.disconnect() self.__cutAction.triggered.disconnect() self.__pasteAction.triggered.disconnect() def __createPuzzleToolBarActions(self): self.__runAction = QAction(self) self.__pauseAction = QAction(self) self.__stopAction = QAction(self) self.__newModuleMenu = QMenu("", self) self.__newIntModuleAction = self.__newModuleMenu.addAction("") self.__newExtModuleAction = self.__newModuleMenu.addAction("") self.__newTemplateModuleMenu = self.__newModuleMenu.addMenu("") self.__newPipeAction = QAction("", self) self.__newValveAction = QAction("", self) self.__newTemplateModuleMenu.aboutToShow.connect( self.__createTemplateModuleMenuActions) self.__runAction.triggered.connect(self.__run) self.__pauseAction.triggered.connect(self.__pause) self.__stopAction.triggered.connect(self.__stop) self.__newModuleMenu.menuAction().triggered.connect( self.__newIntModule) self.__newIntModuleAction.triggered.connect(self.__newIntModule) self.__newExtModuleAction.triggered.connect(self.__newExtModule) self.__newPipeAction.triggered.connect(self.__newPipe) self.__newValveAction.triggered.connect(self.__newValve) def __createTemplateModuleMenuActions(self): self.__newTemplateModuleMenu.clear() for a in self.__getTemplateActions(): action = self.__newTemplateModuleMenu.addAction(a) action.triggered.connect( lambda _, p=a: self.__newIntModuleFromTemplate(templatePath=p)) action = self.__newTemplateModuleMenu.addAction( translate("MainWindow", "-> Manage templates")) path = os.path.join(user_config_dir("Puzzlestream"), "templates") action.triggered.connect(lambda _, p=path: self.__open_file(p)) def __getPuzzleToolBarActions(self): return [self.__runAction, self.__pauseAction, self.__stopAction, self.__newModuleMenu.menuAction(), self.__newPipeAction, self.__newValveAction]
[docs] def updateText(self, module: PSModule, text: str): if module == self.__activeModule: if text is None: self.outputTextEdit.setText("") self.outputTextEdit.activateAutoscroll() else: cursor = self.outputTextEdit.textCursor() cursor.movePosition(cursor.End) f = cursor.charFormat() font = QFont("Fira Code", pointSize=9) font.setStyleStrategy(QFont.PreferAntialias) f.setFont(font) cursor.insertText(text, f)
[docs] def updateStatus(self, module: PSModule): if module == self.__activeModule: self.statisticsTextEdit.setHtml(module.statistics) self.updateActiveModuleButtons()
[docs] def updateActiveModuleButtons(self): currentDir = os.path.dirname(__file__) if self.__currentStyle == "dark": if (self.__activeModule is not None and self.__activeModule.status is PSStatus.RUNNING): self.btnRunPauseActive.setIcon(QIcon(os.path.join( currentDir, "../icons/pause_blue_in.png"))) else: self.btnRunPauseActive.setIcon(QIcon(os.path.join( currentDir, "../icons/play_blue_in.png"))) elif (self.__activeModule is not None and self.__currentStyle == "light"): if self.__activeModule.status is PSStatus.RUNNING: self.btnRunPauseActive.setIcon(QIcon(os.path.join( currentDir, "../icons/pause_blue_out.png"))) else: self.btnRunPauseActive.setIcon(QIcon(os.path.join( currentDir, "../icons/play_blue_out.png"))) if self.__activeModule is not None: self.__editorWidgets[0].editorFilePathLabel.setText( self.__activeModule.filePath + " - " + translate("Status", str(self.__activeModule.status)) )
def __updateGitTabHeaderAndMenu(self): self.outputTabWidget.setTabText( 2, "Git - %s (%d)" % (self.__gitTab.activeBranchName, self.__gitTab.numberOfChangedItems)) for a in [self.__pullToolbarAction, self.__pushToolbarAction]: a.setEnabled(self.__gitTab.hasRemote) def __configChanged(self, key: str): if key == "last projects": self.__updateRecentProjects() elif key == "saveOnEditorFocusOut": for e in self.__editors: e.save_on_focus_out = self.__manager.config[ "saveOnEditorFocusOut"] elif key == "design": design = self.__manager.config["design"] self.__setStyle(design[1][design[0]]) def __updateNotificationHeader(self, added=False): self.outputTabWidget.setTabText( 3, translate("MainWindow", "Notifications") + " (%d)" % ( len(self.__notificationTab.notifications)) ) if added: self.outputTabWidget.setCurrentIndex(3) """ reaction routines """ def __deactivate(self): for e in self.__editorWidgets: e.hide() self.__welcomeLabel.setText(self.__newProjectText) self.__welcomeLabel.show() self.__resetActiveModule() for e in self.__activeElements: e.setEnabled(False) def __activate(self): self.__welcomeLabel.setText(self.__projectOpenText) for e in self.__activeElements: e.setEnabled(True) def __updateProjectLoadedStatus(self): if self.__manager.projectPath is None: self.__deactivate() else: self.__activate() self.mainTabWidget.setCurrentIndex(2) def __welcomeLabelLinkActivated(self, link: str): if link == "#new_project": self.__newProject() elif link == "#open_project": self.__openProject() elif link.startswith("#last_project:"): link = link.replace("#last_project:", "") self.__openProject(link) def __newProject(self, path: str = None): if not isinstance(path, str): path = QFileDialog.getExistingDirectory( self, translate("MainWindow", "New project folder") ) if path != "": if len(os.listdir(path)) == 0: self.__manager.newProject(path) self.setWindowTitle( "Puzzlestream - " + self.__manager.projectPath) self.__resetUI(path) else: msg = QMessageBox(self) msg.setText(translate("MainWindow", "Directory not empty.")) msg.show() self.__updateProjectLoadedStatus() self.__gitTab.setRepo(self.__manager.repo) def __openProject(self, path: str = None, start: bool = False): if (path is None or not isinstance(path, str)) and not start: path = QFileDialog.getExistingDirectory( self, translate("MainWindow", "Open project folder") ) if os.path.isdir(path): self.__deactivate() self.__closeProject() self.__welcomeLabel.setText( translate("MainWindow", "Loading project...") + "<br>" + path) QApplication.processEvents() if os.path.isfile(path + "/puzzle.json"): self.__manager.load(path, silent=start) self.setWindowTitle("Puzzlestream - " + path) self.__resetUI(path) for e in self.__editorWidgets: e.fileExplorer.setPath(path) elif not start: msg = QMessageBox(self) msg.setText( translate( "MainWindow", "The chosen project folder is not valid. " + "Please choose another one.") ) msg.exec() self.__openProject() self.__updateProjectLoadedStatus() self.__gitTab.setRepo(self.__manager.repo) if self.__btnPuzzleLock.isChecked() != self.__manager.puzzleLocked: self.__btnPuzzleLock.click() self.__togglePuzzleLocked() self.__manager.dirty = False def __closeProject(self): if self.__manager.projectPath is not None: self.__manager.closeProject() self.__deactivate() self.__gitTab.setRepo(None) self.changeRightWidgetMode("puzzle") self.__manager.dirty = False def __saveProjectAs(self, path: str = None): if not isinstance(path, str): path = QFileDialog.getExistingDirectory( self, translate("MainWindow", "Save project folder") ) if os.path.isdir(path): if len(os.listdir(path)) == 0: self.__manager.saveAs(path) self.setWindowTitle("Puzzlestream - " + path) self.__resetUI(path) else: msg = QMessageBox(self) msg.setText(translate("MainWindow", "Directory not empty.")) msg.show() self.__updateProjectLoadedStatus() self.__manager.dirty = False def __saveProject(self, value: bool = True): self.__saveOpenFiles() self.__manager.save() def __abortAdding(self): self.__manager.addStatus = None self.__btnAddStatusAction.setVisible(False) self.__enableAddActions() self.__puzzleLabel.setText("") self.__welcomeLabel.setText(self.__projectOpenText) def __newIntModule(self): self.__disableAddActions() self.__welcomeLabel.setText(self.__newItemText + "module.") self.__manager.addStatus = "intModule" self.__puzzleLabel.setText( translate( "MainWindow", "Click on a free spot inside the puzzle view to add a new " + "internal module." ) ) self.__btnAddStatusAction.setVisible(True) def __newExtModule(self): self.__disableAddActions() self.__welcomeLabel.setText(self.__newItemText + "module.") self.__manager.addStatus = "extModule" self.__puzzleLabel.setText( translate( "MainWindow", "Click on a free spot inside the puzzle view to add a new " + "external module." ) ) self.__btnAddStatusAction.setVisible(True) def __newIntModuleFromTemplate(self, templatePath: str): self.__disableAddActions() self.__welcomeLabel.setText(self.__newItemText + "module.") self.__manager.addStatus = "template " + templatePath self.__puzzleLabel.setText( translate( "MainWindow", "Click on a free spot inside the puzzle view to add a new " + "internal module from the \"%s\" template." % (templatePath) ) ) self.__btnAddStatusAction.setVisible(True) def __newPipe(self): self.__disableAddActions() self.__welcomeLabel.setText(self.__newItemText + "pipe.") self.__manager.addStatus = "pipe" self.__puzzleLabel.setText( translate( "MainWindow", "Click on a free spot inside the puzzle view to add a new " + "pipe." ) ) self.__btnAddStatusAction.setVisible(True) def __newValve(self): self.__disableAddActions() self.__welcomeLabel.setText(self.__newItemText + "valve.") self.__manager.addStatus = "valve" self.__puzzleLabel.setText( translate( "MainWindow", "Click on a free spot inside the puzzle view to add a new " + "valve." ) ) self.__btnAddStatusAction.setVisible(True) def __resetActiveModule(self): self.__activeModule = None for a in self.appsToolBar.actions(): a.setEnabled(False) for a in [self.__plotViewAction, self.__dataViewAction]: a.setEnabled(False) def __itemAdded(self, item: PSPuzzleItem): self.__enableAddActions() self.__welcomeLabel.setText(self.__projectOpenText) self.__puzzleLabel.setText("") self.__btnAddStatusAction.setVisible(False) if self.__manager.puzzleLocked: self.__btnPuzzleLock.click() self.__togglePuzzleLocked() self.__manager.dirty = True self.__gitTab.reload() def __itemDeleted(self, item: PSPuzzleItem): if item == self.__activeModule: self.__welcomeLabel.setText(self.__projectOpenText) self.__editorWidgets[0].hide() self.__welcomeLabel.show() self.horizontalSplitter.setStretchFactor(0, 0) self.__resetActiveModule() self.__manager.dirty = True self.__gitTab.reload() def __togglePuzzleLocked(self, *args): if self.__btnPuzzleLock.isChecked(): self.__manager.setAllItemsFixed() self.__lblPuzzleLock.setText( translate("MainWindow", "puzzle locked")) else: self.__manager.setAllItemsMovable() self.__lblPuzzleLock.setText( translate("MainWindow", "puzzle unlocked")) self.__manager.dirty = True self.__gitTab.reload() def __enableAddActions(self): for a in self.puzzleToolBar.actions(): if not isinstance(a, QWidgetAction): a.setEnabled(True) self.__btnPuzzleLock.setEnabled(True) for a in (self.__runAction, self.__pauseAction, self.__stopAction): a.setEnabled(True) def __disableAddActions(self): for a in self.puzzleToolBar.actions(): if not isinstance(a, QWidgetAction): a.setEnabled(False) self.__btnPuzzleLock.setEnabled(False) for a in (self.__runAction, self.__pauseAction, self.__stopAction): a.setEnabled(False) def __showData(self, puzzleItem: PSPuzzleItem): view = PSDataView(self.__manager, puzzleItem) currentDir = os.path.dirname(__file__) view.setStyleSheet( colors.parseQSS(currentDir + "/style/sheet-em.qss")) self.__manager.scene.statusChanged.connect(view.statusUpdate) view.closedSignal.connect(self.__removeFromWindowList) view.showMaximized() self.__subWindows.append(view) def __showPlots(self, puzzleItem: PSPuzzleItem): view = PSPlotView(self.__manager, puzzleItem) currentDir = os.path.dirname(__file__) view.setStyleSheet( colors.parseQSS(currentDir + "/style/sheet-em.qss")) self.__manager.scene.statusChanged.connect(view.statusUpdate) view.closedSignal.connect(self.__removeFromWindowList) view.show() self.__subWindows.append(view) def __run(self): if (self.__activeModule is not None and self.__manager.config["saveOnRun"]): self.__saveOpenFiles() for module in self.__manager.scene.modules.values(): if ((module.status is PSStatus.INCOMPLETE or module.status is PSStatus.FINISHED or module.status is PSStatus.ERROR or module.status is PSStatus.TESTFAILED) and not module.hasInput): module.run() else: module.resume() def __pause(self): for module in self.__manager.scene.modules.values(): module.pause() def __stop(self): for module in self.__manager.scene.modules.values(): module.stop() """ =========================================================================== Pip stuff """ def __openPipGUI(self): window = PSPipGUI(parent=self) window.show() """ =========================================================================== Lib stuff """ def __addLib(self): path = QFileDialog.getExistingDirectory( self, translate("MainWindow", "Add lib folder") ) if os.path.isdir(path): self.__manager.addLib(path) args = ["-s" + lib for lib in self.__manager.config["libs"]] for e in self.__editors: e.backend.start( server.__file__, args=args) self.__createLibToolBarActions() def __open_file(self, filename: str): if sys.platform == "win32": os.startfile(filename) else: opener = "open" if sys.platform == "darwin" else "xdg-open" subprocess.call([opener, filename]) def __deleteLib(self, path: str): self.__manager.deleteLib(path) self.__createLibToolBarActions() if path in sys.path: i = sys.path.index(path) del sys.path[i] """ =========================================================================== Update check stuff """ def __checkForUpdates(self): self.__updateStatus = None try: version = pkg_resources.get_distribution("puzzlestream").version with urlopen("https://pypi.org/pypi/puzzlestream/json") as res: versionAvailable = json.loads(res.read())["info"]["version"] self.__updateStatus = (version, versionAvailable) except Exception as e: print(e) def __updateCheckFinished(self): if isinstance(self.__updateStatus, tuple): linkEnding = translate("MainWindow", "get-puzzlestream/") if sys.platform == "linux" or sys.platform == "linux2": linkEnding += "linux" elif sys.platform == "darwin": linkEnding += "mac-os-x" elif sys.platform == "win32" or sys.platform == "win64": linkEnding += "windows" version, versionAvailable = self.__updateStatus if LooseVersion(versionAvailable) > LooseVersion(version): notificationsystem.newNotification( translate("MainWindow", "An update to version ") + str(versionAvailable) + translate( "MainWindow", " is available; you are " + "currently using version " ) + str(version) + ". " + translate( "MainWindow", "Please click <a href=\"https://puzzlestream.org/" ) + linkEnding + translate( "MainWindow", "\">here</a> for update instructions." ) )