diff --git a/plugins/python/scripter/ui_scripter/actions/__init__.py b/plugins/python/scripter/ui_scripter/actions/__init__.py index 1d1366891e..84193798ab 100644 --- a/plugins/python/scripter/ui_scripter/actions/__init__.py +++ b/plugins/python/scripter/ui_scripter/actions/__init__.py @@ -1,9 +1,9 @@ action_classes = ['newaction.newaction.NewAction', 'openaction.openaction.OpenAction', 'reloadaction.reloadaction.ReloadAction', 'saveaction.saveaction.SaveAction', - 'saveasaction.saveasaction.SaveAsAction', + 'saveasaction.saveasaction.SaveAsAction', 'runaction.runaction.RunAction', 'settingsaction.settingsaction.SettingsAction', 'debugaction.debugaction.DebugAction', 'closeaction.closeaction.CloseAction'] diff --git a/plugins/python/scripter/ui_scripter/actions/closeaction/closeaction.py b/plugins/python/scripter/ui_scripter/actions/closeaction/closeaction.py index 65b9e07788..e6090758db 100644 --- a/plugins/python/scripter/ui_scripter/actions/closeaction/closeaction.py +++ b/plugins/python/scripter/ui_scripter/actions/closeaction/closeaction.py @@ -1,54 +1,54 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt class CloseAction(QAction): def __init__(self, scripter, parent=None): super(CloseAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.close) self.setText('Close') self.setObjectName('close') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q)) @property - def parent(self): - return 'File' + def parents(self): + return ('File',) def close(self): msgBox = QMessageBox(self.scripter.uicontroller.mainWidget) msgBox.setInformativeText("Do you want to save the current document?") msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Save) ret = msgBox.exec() if ret == QMessageBox.Cancel: return if ret == QMessageBox.Save: if not self.scripter.uicontroller.invokeAction('save'): return self.scripter.uicontroller.closeScripter() diff --git a/plugins/python/scripter/ui_scripter/actions/debugaction/debugaction.py b/plugins/python/scripter/ui_scripter/actions/debugaction/debugaction.py index e5ae9512ad..5e9ab8d0c0 100644 --- a/plugins/python/scripter/ui_scripter/actions/debugaction/debugaction.py +++ b/plugins/python/scripter/ui_scripter/actions/debugaction/debugaction.py @@ -1,46 +1,46 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction from PyQt5.QtGui import QIcon, QPixmap, QKeySequence from scripter import resources_rc from PyQt5.QtCore import Qt class DebugAction(QAction): def __init__(self, scripter, parent=None): super(DebugAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.debug) self.setText('Debug') self.setToolTip('Debug Ctrl+D') self.setIcon(QIcon(':/icons/debug.svg')) self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_D)) @property - def parent(self): - return 'toolBar' + def parents(self): + return ('toolBar',) def debug(self): if self.scripter.uicontroller.invokeAction('save'): self.scripter.uicontroller.setActiveWidget('Debugger') self.scripter.debugcontroller.start(self.scripter.documentcontroller.activeDocument) widget = self.scripter.uicontroller.findTabWidget('Debugger') widget.startDebugger() diff --git a/plugins/python/scripter/ui_scripter/actions/newaction/newaction.py b/plugins/python/scripter/ui_scripter/actions/newaction/newaction.py index df7749bf5b..58e9c4b691 100644 --- a/plugins/python/scripter/ui_scripter/actions/newaction/newaction.py +++ b/plugins/python/scripter/ui_scripter/actions/newaction/newaction.py @@ -1,56 +1,57 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt class NewAction(QAction): def __init__(self, scripter, parent=None): super(NewAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.new) self.setText('New') self.setObjectName('new') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_N)) + self.setIcon(Krita.instance().icon("document-new")) @property - def parent(self): - return 'File' + def parents(self): + return ('File', 'toolBar') def new(self): msgBox = QMessageBox(self.scripter.uicontroller.mainWidget) msgBox.setText("The document has been modified.") msgBox.setInformativeText("Do you want to save your changes?") msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Save) ret = msgBox.exec() if ret == QMessageBox.Cancel: return if ret == QMessageBox.Save: self.scripter.uicontroller.invokeAction('save') self.scripter.documentcontroller.clearActiveDocument() self.scripter.uicontroller.setStatusBar() self.scripter.uicontroller.clearEditor() diff --git a/plugins/python/scripter/ui_scripter/actions/openaction/openaction.py b/plugins/python/scripter/ui_scripter/actions/openaction/openaction.py index 5baa4a6d14..f97caae315 100644 --- a/plugins/python/scripter/ui_scripter/actions/openaction/openaction.py +++ b/plugins/python/scripter/ui_scripter/actions/openaction/openaction.py @@ -1,56 +1,57 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox -from PyQt5.QtGui import QKeySequence +from PyQt5.QtGui import QKeySequence, QIcon from PyQt5.QtCore import Qt class OpenAction(QAction): def __init__(self, scripter, parent=None): super(OpenAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.open) self.setText('Open') self.setObjectName('open') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_O)) + self.setIcon(Krita.instance().icon("document-open")) @property - def parent(self): - return 'File' + def parents(self): + return ('File', 'toolBar') def open(self): dialog = QFileDialog(self.scripter.uicontroller.mainWidget) dialog.setNameFilter('Python files (*.py)') if dialog.exec(): try: selectedFile = dialog.selectedFiles()[0] fileExtension = selectedFile.rsplit('.', maxsplit=1)[1] if fileExtension == 'py': document = self.scripter.documentcontroller.openDocument(selectedFile) self.scripter.uicontroller.setDocumentEditor(document) self.scripter.uicontroller.setStatusBar(document.filePath) print("open is run") except Exception: QMessageBox.information(self.scripter.uicontroller.mainWidget, 'Invalid File', 'Open files with .py extension') diff --git a/plugins/python/scripter/ui_scripter/actions/reloadaction/reloadaction.py b/plugins/python/scripter/ui_scripter/actions/reloadaction/reloadaction.py index 6b3a57cc2b..d8f3f573ad 100644 --- a/plugins/python/scripter/ui_scripter/actions/reloadaction/reloadaction.py +++ b/plugins/python/scripter/ui_scripter/actions/reloadaction/reloadaction.py @@ -1,46 +1,47 @@ from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt class ReloadAction(QAction): def __init__(self, scripter, parent=None): super(ReloadAction, self).__init__(parent) self.scripter = scripter self.editor = self.scripter.uicontroller.editor self.triggered.connect(self.reloadFile) self.setText('Reload File') self.setObjectName('reloadfile') self.setShortcut(QKeySequence(Qt.ALT + Qt.Key_R)) + self.setIcon(Krita.instance().icon("view-refresh")) @property - def parent(self): - return 'File' + def parents(self): + return ('File', 'toolBar') def reloadFile(self): # get the currently open document's path curr_doc_fpath = '' document = self.scripter.documentcontroller._activeDocument if document is None: QMessageBox.critical(self.scripter.uicontroller.mainWidget, 'No existing document', 'Please specify a document by opening it before reloading') return else: curr_doc_fpath = document.filePath # clear the editor self.scripter.documentcontroller.clearActiveDocument() self.scripter.uicontroller.setStatusBar() self.scripter.uicontroller.clearEditor() # reload the document document = self.scripter.documentcontroller.openDocument(curr_doc_fpath) self.scripter.uicontroller.setDocumentEditor(document) self.scripter.uicontroller.setStatusBar(document.filePath) print("reload is run") return document diff --git a/plugins/python/scripter/ui_scripter/actions/runaction/runaction.py b/plugins/python/scripter/ui_scripter/actions/runaction/runaction.py index b607d5a403..183f27341f 100644 --- a/plugins/python/scripter/ui_scripter/actions/runaction/runaction.py +++ b/plugins/python/scripter/ui_scripter/actions/runaction/runaction.py @@ -1,132 +1,132 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction from PyQt5.QtGui import QIcon, QKeySequence from PyQt5.QtCore import Qt import sys from . import docwrapper import importlib from importlib.machinery import SourceFileLoader import traceback PYTHON33 = sys.version_info.major == 3 and sys.version_info.minor == 3 PYTHON34 = sys.version_info.major == 3 and sys.version_info.minor == 4 EXEC_NAMESPACE = "__main__" # namespace that user scripts will run in class RunAction(QAction): def __init__(self, scripter, parent=None): super(RunAction, self).__init__(parent) self.scripter = scripter self.editor = self.scripter.uicontroller.editor self.output = self.scripter.uicontroller.findTabWidget('OutPut', 'OutPutTextEdit') self.triggered.connect(self.run) self.setText('Run') self.setToolTip('Run Ctrl+R') self.setIcon(QIcon(':/icons/run.svg')) self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_R)) @property - def parent(self): - return 'toolBar' + def parents(self): + return ('toolBar',) def run(self): """ This method execute python code from an activeDocument (file) or direct from editor (ui_scripter/editor/pythoneditor.py). When executing code from a file, we use importlib to load this module/file and with "users_script" name. That's implementation seeks for a "main()" function in the script. When executing code from editor without creating a file, we compile this script to bytecode and we execute this in an empty scope. That's faster than use exec directly and cleaner, because we are using an empty scope. """ self.scripter.uicontroller.setActiveWidget('OutPut') stdout = sys.stdout stderr = sys.stderr output = docwrapper.DocWrapper(self.output.document()) if (self.editor._documentModified is True): output.write("==== Warning: Script not saved! ====\n") else: output.write("======================================\n") sys.stdout = output sys.stderr = output script = self.editor.document().toPlainText() document = self.scripter.documentcontroller.activeDocument try: if document and self.editor._documentModified is False: spec = importlib.util.spec_from_file_location(EXEC_NAMESPACE, document.filePath) try: users_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(users_module) except AttributeError as e: # no module from spec if PYTHON34 or PYTHON33: loader = SourceFileLoader(EXEC_NAMESPACE, document.filePath) users_module = loader.load_module() else: raise e try: # maybe script is to be execed, maybe main needs to be invoked # if there is a main() then execute it, otherwise don't worry... users_module.main() except AttributeError: pass else: code = compile(script, '', 'exec') globals_dict = {"__name__": EXEC_NAMESPACE} exec(code, globals_dict) except Exception as e: """Provide context (line number and text) for an error that is caught. Ordinarily, syntax and Indent errors are caught during initial compilation in exec(), and the traceback traces back to this file. So these need to be treated separately. Other errors trace back to the file/script being run. """ type_, value_, traceback_ = sys.exc_info() if type_ == SyntaxError: errorMessage = "%s\n%s" % (value_.text.rstrip(), " " * (value_.offset - 1) + "^") # rstrip to remove trailing \n, output needs to be fixed width font for the ^ to align correctly errorText = "Syntax Error on line %s" % value_.lineno elif type_ == IndentationError: # (no offset is provided for an IndentationError errorMessage = value_.text.rstrip() errorText = "Unexpected Indent on line %s" % value_.lineno else: errorText = traceback.format_exception_only(type_, value_)[0] format_string = "In file: {0}\nIn function: {2} at line: {1}. Line with error:\n{3}" tbList = traceback.extract_tb(traceback_) tb = tbList[-1] errorMessage = format_string.format(*tb) m = "\n**********************\n%s\n%s\n**********************\n" % (errorText, errorMessage) output.write(m) sys.stdout = stdout sys.stderr = stderr # scroll to bottom of output bottom = self.output.verticalScrollBar().maximum() self.output.verticalScrollBar().setValue(bottom) diff --git a/plugins/python/scripter/ui_scripter/actions/saveaction/saveaction.py b/plugins/python/scripter/ui_scripter/actions/saveaction/saveaction.py index 4fea1c0e89..033f9bd0e8 100644 --- a/plugins/python/scripter/ui_scripter/actions/saveaction/saveaction.py +++ b/plugins/python/scripter/ui_scripter/actions/saveaction/saveaction.py @@ -1,62 +1,63 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction, QFileDialog from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt class SaveAction(QAction): def __init__(self, scripter, parent=None): super(SaveAction, self).__init__(parent) self.scripter = scripter self.editor = self.scripter.uicontroller.editor self.triggered.connect(self.save) self.setText('Save') self.setObjectName('save') self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_S)) + self.setIcon(Krita.instance().icon("document-save-as")) @property - def parent(self): - return 'File' + def parents(self): + return ('File', 'toolBar') def save(self): text = self.editor.toPlainText() fileName = '' if not self.scripter.documentcontroller.activeDocument: fileName = QFileDialog.getSaveFileName(self.scripter.uicontroller.mainWidget, 'Save Python File', '', 'Python File (*.py)')[0] if not fileName: return # don't validate file name - trust user to specify the extension they want # getSaveFileName will add ".py" if there is no extension. # It will strip a trailing period and, in each case, test for file collisions document = self.scripter.documentcontroller.saveDocument(text, fileName) if document: self.scripter.uicontroller.setStatusBar(document.filePath) else: self.scripter.uicontroller.setStatusBar('untitled') self.editor._documentModified = False self.scripter.uicontroller.setStatusModified() return document diff --git a/plugins/python/scripter/ui_scripter/actions/saveasaction/saveasaction.py b/plugins/python/scripter/ui_scripter/actions/saveasaction/saveasaction.py index 97c1e8509f..0ddea60c74 100644 --- a/plugins/python/scripter/ui_scripter/actions/saveasaction/saveasaction.py +++ b/plugins/python/scripter/ui_scripter/actions/saveasaction/saveasaction.py @@ -1,60 +1,61 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction, QFileDialog from PyQt5.QtGui import QKeySequence from PyQt5.QtCore import Qt class SaveAsAction(QAction): def __init__(self, scripter, parent=None): super(SaveAsAction, self).__init__(parent) self.scripter = scripter self.editor = self.scripter.uicontroller.editor self.triggered.connect(self.save) self.setText('Save As') self.setObjectName('saveas') self.setShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_S)) + self.setIcon(Krita.instance().icon("document-save-as")) @property - def parent(self): - return 'File' + def parents(self): + return ('File', 'toolBar') def save(self): text = self.editor.toPlainText() fileName = QFileDialog.getSaveFileName(self.scripter.uicontroller.mainWidget, 'Save Python File', '', 'Python File (*.py)')[0] if not fileName: return # don't validate file name - trust user to specify the extension they want # getSaveFileName will add ".py" if there is no extension. # It will strip a trailing period and, in each case, test for file collisions document = self.scripter.documentcontroller.saveDocument(text, fileName, save_as=True) if document: self.scripter.uicontroller.setStatusBar(document.filePath) else: self.scripter.uicontroller.setStatusBar('untitled') self.editor._documentModified = False self.scripter.uicontroller.setStatusModified() return document diff --git a/plugins/python/scripter/ui_scripter/actions/settingsaction/settingsaction.py b/plugins/python/scripter/ui_scripter/actions/settingsaction/settingsaction.py index b335a6e7b1..543b798238 100644 --- a/plugins/python/scripter/ui_scripter/actions/settingsaction/settingsaction.py +++ b/plugins/python/scripter/ui_scripter/actions/settingsaction/settingsaction.py @@ -1,49 +1,49 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtWidgets import QAction from PyQt5.QtCore import Qt from . import settingsdialog class SettingsAction(QAction): def __init__(self, scripter, parent=None): super(SettingsAction, self).__init__(parent) self.scripter = scripter self.triggered.connect(self.openSettings) self.settingsDialog = settingsdialog.SettingsDialog(self.scripter) self.settingsDialog.setWindowModality(Qt.WindowModal) self.settingsDialog.setFixedSize(400, 250) self.setText('Settings') @property - def parent(self): - return 'File' + def parents(self): + return ('File',) def openSettings(self): self.settingsDialog.show() self.settingsDialog.exec() def readSettings(self): self.settingsDialog.readSettings(self.scripter.settings) def writeSettings(self): self.settingsDialog.writeSettings(self.scripter.settings) diff --git a/plugins/python/scripter/uicontroller.py b/plugins/python/scripter/uicontroller.py index 27e18f3dd7..9036d9f2f9 100644 --- a/plugins/python/scripter/uicontroller.py +++ b/plugins/python/scripter/uicontroller.py @@ -1,262 +1,265 @@ """ Copyright (c) 2017 Eliakin Costa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ from PyQt5.QtGui import QTextCursor, QPalette, QFontInfo from PyQt5.QtWidgets import (QToolBar, QMenuBar, QTabWidget, QLabel, QVBoxLayout, QMessageBox, QSplitter, QSizePolicy) from PyQt5.QtCore import Qt, QObject, QFileInfo, pyqtSlot, QRect from scripter.ui_scripter.syntax import syntax, syntaxstyles from scripter.ui_scripter.editor import pythoneditor from scripter import scripterdialog import importlib KEY_GEOMETRY = "geometry" DEFAULT_GEOMETRY = QRect(600, 200, 400, 500) # essentially randomly placed class Elided_Text_Label(QLabel): mainText = str() def __init__(self, parent=None): super(QLabel, self).__init__(parent) self.setMinimumWidth(self.fontMetrics().width("...")) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) def setMainText(self, text=str()): self.mainText = text self.elideText() def elideText(self): self.setText(self.fontMetrics().elidedText(self.mainText, Qt.ElideRight, self.width())) def resizeEvent(self, event): self.elideText() class UIController(object): documentModifiedText = "" documentStatusBarText = "untitled" def __init__(self): self.mainWidget = scripterdialog.ScripterDialog(self) self.actionToolbar = QToolBar('toolBar', self.mainWidget) self.menu_bar = QMenuBar(self.mainWidget) self.actionToolbar.setObjectName('toolBar') self.menu_bar.setObjectName('menuBar') self.actions = [] self.mainWidget.setWindowModality(Qt.NonModal) def initialize(self, scripter): self.editor = pythoneditor.CodeEditor(scripter) self.tabWidget = QTabWidget() self.statusBar = Elided_Text_Label() self.statusBar.setMainText('untitled') self.splitter = QSplitter() self.splitter.setOrientation(Qt.Vertical) self.highlight = syntax.PythonHighlighter(self.editor.document(), syntaxstyles.DefaultSyntaxStyle()) p = self.editor.palette() p.setColor(QPalette.Base, syntaxstyles.DefaultSyntaxStyle()['background'].foreground().color()) p.setColor(QPalette.Text, syntaxstyles.DefaultSyntaxStyle()['foreground'].foreground().color()) self.editor.setPalette(p) self.scripter = scripter self.loadMenus() self.loadWidgets() self.loadActions() self._readSettings() # sets window size vbox = QVBoxLayout(self.mainWidget) vbox.addWidget(self.menu_bar) vbox.addWidget(self.actionToolbar) self.splitter.addWidget(self.editor) self.splitter.addWidget(self.tabWidget) vbox.addWidget(self.splitter) vbox.addWidget(self.statusBar) self.mainWidget.setWindowTitle("Scripter") self.mainWidget.setSizeGripEnabled(True) self.mainWidget.show() self.mainWidget.activateWindow() self.editor.undoAvailable.connect(self.setStatusModified) def loadMenus(self): self.addMenu('File', 'File') def addMenu(self, menuName, parentName): parent = self.menu_bar.findChild(QObject, parentName) self.newMenu = None if parent: self.newMenu = parent.addMenu(menuName) else: self.newMenu = self.menu_bar.addMenu(menuName) self.newMenu.setObjectName(menuName) return self.newMenu def loadActions(self): module_path = 'scripter.ui_scripter.actions' actions_module = importlib.import_module(module_path) modules = [] for class_path in actions_module.action_classes: _module, _klass = class_path.rsplit('.', maxsplit=1) modules.append(dict(module='{0}.{1}'.format(module_path, _module), klass=_klass)) for module in modules: m = importlib.import_module(module['module']) action_class = getattr(m, module['klass']) obj = action_class(self.scripter) - parent = self.mainWidget.findChild(QObject, obj.parent) - self.actions.append(dict(action=obj, parent=parent)) + parents = [] + for parent in obj.parents: + parents.append(self.mainWidget.findChild(QObject, parent)) + self.actions.append(dict(action=obj, parents=parents)) for action in self.actions: - action['parent'].addAction(action['action']) + for parent in action['parents']: + parent.addAction(action['action']) def loadWidgets(self): modulePath = 'scripter.ui_scripter.tabwidgets' widgetsModule = importlib.import_module(modulePath) modules = [] for classPath in widgetsModule.widgetClasses: _module, _klass = classPath.rsplit('.', maxsplit=1) modules.append(dict(module='{0}.{1}'.format(modulePath, _module), klass=_klass)) for module in modules: m = importlib.import_module(module['module']) widgetClass = getattr(m, module['klass']) obj = widgetClass(self.scripter) self.tabWidget.addTab(obj, obj.objectName()) def invokeAction(self, actionName): for action in self.actions: if action['action'].objectName() == actionName: method = getattr(action['action'], actionName) if method: return method() def findTabWidget(self, widgetName, childName=''): for index in range(self.tabWidget.count()): widget = self.tabWidget.widget(index) if widget.objectName() == widgetName: if childName: widget = widget.findChild(QObject, childName) return widget def showException(self, exception): QMessageBox.critical(self.editor, "Error running script", str(exception)) def setDocumentEditor(self, document): self.editor.clear() self.editor.moveCursor(QTextCursor.Start) self.editor._documentModified = False self.editor.setPlainText(document.data) self.editor.moveCursor(QTextCursor.End) def setStatusBar(self, value='untitled'): self.documentStatusBarText = value self.statusBar.setMainText(self.documentStatusBarText + self.documentModifiedText) def setStatusModified(self): self.documentModifiedText = "" if (self.editor._documentModified is True): self.documentModifiedText = " [Modified]" self.statusBar.setMainText(self.documentStatusBarText + self.documentModifiedText) def setActiveWidget(self, widgetName): widget = self.findTabWidget(widgetName) if widget: self.tabWidget.setCurrentWidget(widget) def setStepped(self, status): self.editor.setStepped(status) def clearEditor(self): self.editor.clear() def repaintDebugArea(self): self.editor.repaintDebugArea() def closeScripter(self): self.mainWidget.close() def _writeSettings(self): """ _writeSettings is a method invoked when the scripter starts, making control inversion. Actions can implement a writeSettings method to save your own settings without this method to know about it. """ self.scripter.settings.beginGroup('scripter') document = self.scripter.documentcontroller.activeDocument if document: self.scripter.settings.setValue('activeDocumentPath', document.filePath) else: self.scripter.settings.setValue('activeDocumentPath', '') self.scripter.settings.setValue('editorFontSize', self.editor.fontInfo().pointSize()) for action in self.actions: writeSettings = getattr(action['action'], "writeSettings", None) if callable(writeSettings): writeSettings() # Window Geometry rect = self.mainWidget.geometry() self.scripter.settings.setValue(KEY_GEOMETRY, rect) self.scripter.settings.endGroup() def _readSettings(self): """ It's similar to _writeSettings, but reading the settings when the ScripterDialog is closed. """ self.scripter.settings.beginGroup('scripter') activeDocumentPath = self.scripter.settings.value('activeDocumentPath', '') if activeDocumentPath: if QFileInfo(activeDocumentPath).exists(): document = self.scripter.documentcontroller.openDocument(activeDocumentPath) self.setStatusBar(document.filePath) self.setDocumentEditor(document) for action in self.actions: readSettings = getattr(action['action'], "readSettings", None) if callable(readSettings): readSettings() pointSize = self.scripter.settings.value('editorFontSize', str(self.editor.fontInfo().pointSize())) self.editor.setFontSize(int(pointSize)) # Window Geometry rect = self.scripter.settings.value(KEY_GEOMETRY, DEFAULT_GEOMETRY) self.mainWidget.setGeometry(rect) self.scripter.settings.endGroup() def _saveSettings(self): self.scripter.settings.sync()