diff --git a/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt b/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt index 32add6472a..9afa87cd33 100644 --- a/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt +++ b/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt @@ -1,106 +1,106 @@ # Copyright (C) 2012, 2013 Shaheed Haque # Copyright (C) 2013 Alex Turbov # Copyright (C) 2014-2016 Boudewijn Rempt # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. include(CMakeParseArguments) # # Simple helper function to install plugin and related files # having only a name of the plugin... # (just to reduce syntactic noise when a lot of plugins get installed) # function(install_pykrita_plugin name) set(_options) set(_one_value_args) set(_multi_value_args PATTERNS FILE) cmake_parse_arguments(install_pykrita_plugin "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN}) if(NOT name) message(FATAL_ERROR "Plugin filename is not given") endif() if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py) install(FILES kritapykrita_${name}.desktop DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) foreach(_f ${name}.py ${name}.ui ${install_pykrita_plugin_FILE}) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${_f}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${_f} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) endif() endforeach() elseif(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${name}) install(FILES ${name}/kritapykrita_${name}.desktop DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita) install( DIRECTORY ${name} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "*.py" PATTERN "*.ui" PATTERN "__pycache__*" EXCLUDE ) # TODO Is there any way to form a long PATTERN options string # and use it in a single install() call? # NOTE Install specified patterns one-by-one... foreach(_pattern ${install_pykrita_plugin_PATTERNS}) install( DIRECTORY ${name} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "${_pattern}" PATTERN "__pycache__*" EXCLUDE ) endforeach() else() message(FATAL_ERROR "Do not know what to do with ${name}") endif() endfunction() install_pykrita_plugin(hello) install_pykrita_plugin(assignprofiledialog) install_pykrita_plugin(scripter) install_pykrita_plugin(colorspace) install_pykrita_plugin(documenttools) install_pykrita_plugin(filtermanager) install_pykrita_plugin(exportlayers) #install_pykrita_plugin(highpass) install_pykrita_plugin(tenbrushes) +install_pykrita_plugin(tenscripts) install( FILES tenbrushes/tenbrushes.action tenscripts/tenscripts.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) install_pykrita_plugin(palette_docker) install_pykrita_plugin(quick_settings_docker) install_pykrita_plugin(lastdocumentsdocker) -install_pykrita_plugin(tenscripts) # if(PYTHON_VERSION_MAJOR VERSION_EQUAL 3) # install_pykrita_plugin(cmake_utils) # install_pykrita_plugin(js_utils PATTERNS "*.json") # install_pykrita_plugin(expand PATTERNS "*.expand" "templates/*.tpl") # endif() install( DIRECTORY libkritapykrita DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita FILES_MATCHING PATTERN "*.py" PATTERN "__pycache__*" EXCLUDE ) diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/runaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/runaction.py index cedb3de7a0..0aead1acab 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/runaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/runaction.py @@ -1,66 +1,67 @@ from PyQt5.QtWidgets import QAction, QMessageBox from PyQt5.QtGui import QIcon, QKeySequence from PyQt5.QtCore import Qt import sys from . import docwrapper import os from scripter import resources_rc import importlib 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 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()) output.write("======================================\n") sys.stdout = output sys.stderr = output script = self.editor.document().toPlainText() document = self.scripter.documentcontroller.activeDocument + try: if document: spec = importlib.util.spec_from_file_location("users_script", document.filePath) users_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(users_module) users_module.main() else: code = compile(script, '', 'exec') exec(script, {}) except Exception as e: self.scripter.uicontroller.showException(str(e)) sys.stdout = stdout sys.stderr = stderr # scroll to bottom of output - max = self.output.verticalScrollBar().maximum() - self.output.verticalScrollBar().setValue(max) + bottom = self.output.verticalScrollBar().maximum() + self.output.verticalScrollBar().setValue(bottom) diff --git a/plugins/extensions/pykrita/plugin/plugins/tenscripts/tenscripts.py b/plugins/extensions/pykrita/plugin/plugins/tenscripts/tenscripts.py index 3284b65999..4e0a2aae2a 100644 --- a/plugins/extensions/pykrita/plugin/plugins/tenscripts/tenscripts.py +++ b/plugins/extensions/pykrita/plugin/plugins/tenscripts/tenscripts.py @@ -1,57 +1,73 @@ -from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QListView, QFormLayout, - QHBoxLayout, QPushButton, QLineEdit, QListWidget, - QScrollArea, QGridLayout, QFileDialog, QKeySequenceEdit, - QLabel, QAction, QDialogButtonBox) -from PyQt5.QtCore import QObject, Qt +from PyQt5.QtWidgets import QMessageBox import krita from tenscripts import uitenscripts +import importlib class TenScriptsExtension(krita.Extension): def __init__(self, parent): super(TenScriptsExtension, self).__init__(parent) self.actions = [] self.scripts = [] def setup(self): action = Application.createAction("ten_scripts", "Ten Scripts") action.setToolTip("Assign ten scripts to ten shortcuts.") action.triggered.connect(self.initialize) self.readSettings() self.loadActions() def initialize(self): self.uitenscripts = uitenscripts.UITenScripts() self.uitenscripts.initialize(self) - def _executeScript(self): - print('script executed') - def readSettings(self): self.scripts = Application.readSetting("tenscripts", "scripts", "").split(',') def writeSettings(self): saved_scripts = self.uitenscripts.saved_scripts() for index, script in enumerate(saved_scripts): self.actions[index].script = script Application.writeSetting("tenscripts", "scripts", ','.join(map(str, saved_scripts))) def loadActions(self): for index, item in enumerate(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']): action = Application.createAction("execute_script_" + item, "Execute Script " + item) - action.setMenu("None") action.script = None + action.setMenu("None") action.triggered.connect(self._executeScript) if index < len(self.scripts): action.script = self.scripts[index] self.actions.append(action) - print(action.shortcut()) + + def _executeScript(self): + script = self.sender().script + if script: + try: + spec = importlib.util.spec_from_file_location("users_script", script) + users_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(users_module) + + if hasattr(users_module, 'main') and callable(users_module.main): + users_module.main() + + self.showMessage('script {0} executed'.format(self.sender().script)) + except Exception as e: + self.showMessage(str(e)) + else: + self.showMessage("You don't assign a script to that action") + + def showMessage(self, message): + self.msgBox = QMessageBox(Application.activeWindow().qwindow()) + self.msgBox.setText(message) + self.msgBox.exec_() + Scripter.addExtension(TenScriptsExtension(Application)) diff --git a/plugins/extensions/pykrita/plugin/plugins/tenscripts/uitenscripts.py b/plugins/extensions/pykrita/plugin/plugins/tenscripts/uitenscripts.py index 01143608bc..871c355d78 100644 --- a/plugins/extensions/pykrita/plugin/plugins/tenscripts/uitenscripts.py +++ b/plugins/extensions/pykrita/plugin/plugins/tenscripts/uitenscripts.py @@ -1,87 +1,98 @@ -from PyQt5.QtCore import Qt, QSize -from PyQt5.QtGui import QPixmap, QIcon, QKeySequence -from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QListView, QFormLayout, - QHBoxLayout, QPushButton, QLineEdit, QListWidget, - QScrollArea, QGridLayout, QFileDialog, QKeySequenceEdit, - QLabel, QAction, QDialogButtonBox) +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, + QLineEdit, QScrollArea, QGridLayout, QFileDialog, + QLabel, QDialogButtonBox) from tenscripts import tenscriptsdialog import krita class UITenScripts(object): def __init__(self): self.kritaInstance = krita.Krita.instance() self.mainDialog = tenscriptsdialog.TenScriptsDialog(self, self.kritaInstance.activeWindow().qwindow()) self.buttonBox = QDialogButtonBox(self.mainDialog) self.layout = QVBoxLayout(self.mainDialog) self.baseWidget = QWidget() self.baseArea = QWidget() self.scrollArea = QScrollArea() self.scriptsLayout = QGridLayout() self.buttonBox.accepted.connect(self.mainDialog.accept) self.buttonBox.rejected.connect(self.mainDialog.reject) self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.scrollArea.setWidgetResizable(True) def initialize(self, tenscripts): self.tenscripts = tenscripts self._loadGridLayout() + self._fillScripts() self.baseArea.setLayout(self.scriptsLayout) self.scrollArea.setWidget(self.baseArea) self.layout.addWidget(self.scrollArea) self.layout.addWidget(self.buttonBox) self.mainDialog.show() self.mainDialog.activateWindow() self.mainDialog.exec_() def addNewRow(self, key): rowPosition = self.scriptsLayout.rowCount() rowLayout = QHBoxLayout() label = QLabel() directoryTextField = QLineEdit() directoryDialogButton = QPushButton("...") directoryTextField.setReadOnly(True) label.setText("Ctrl+Shift+{0}".format(key)) directoryTextField.setToolTip("Selected Path") directoryDialogButton.setToolTip("Select the script") directoryDialogButton.clicked.connect(self._selectScript) self.scriptsLayout.addWidget(label, rowPosition, 0, Qt.AlignLeft|Qt.AlignTop) self.scriptsLayout.addWidget(directoryTextField, rowPosition, 1, Qt.AlignLeft|Qt.AlignTop) self.scriptsLayout.addWidget(directoryDialogButton, rowPosition, 2, Qt.AlignLeft|Qt.AlignTop) def saved_scripts(self): _saved_scripts = [] index = 0 for row in range(self.scriptsLayout.rowCount()-1): textField = self.scriptsLayout.itemAt(index + 1).widget() if textField.text(): _saved_scripts.append(textField.text()) index += 3 return _saved_scripts def _selectScript(self): dialog = QFileDialog(self.mainDialog) dialog.setNameFilter('Python files (*.py)') if dialog.exec(): selectedFile = dialog.selectedFiles()[0] obj = self.mainDialog.sender() textField = self.scriptsLayout.itemAt(self.scriptsLayout.indexOf(obj)-1).widget() textField.setText(selectedFile) def _loadGridLayout(self): for item in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']: self.addNewRow(item) + + def _fillScripts(self): + scripts = self.tenscripts.scripts + index = 0 + + for row in range(self.scriptsLayout.rowCount()-1): + if row >= len(scripts): + return + + textField = self.scriptsLayout.itemAt(index + 1).widget() + textField.setText(scripts[row]) + index += 3