diff --git a/plugins/extensions/pykrita/plugin/krita/__init__.py b/plugins/extensions/pykrita/plugin/krita/__init__.py index fe5ac1a318..9021765627 100644 --- a/plugins/extensions/pykrita/plugin/krita/__init__.py +++ b/plugins/extensions/pykrita/plugin/krita/__init__.py @@ -1,72 +1,72 @@ import pykrita import os import sys import signal signal.signal(signal.SIGINT, signal.SIG_DFL) from .api import * from .decorators import * from .dockwidgetfactory import * from PyKrita import krita krita_path = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, krita_path) print("%s added to PYTHONPATH" % krita_path, file=sys.stderr) # Look for PyQt try: from PyQt5 import QtCore except ImportError: print("Python cannot find the Qt5 bindings.", file=sys.stderr) print("Please make sure, that the needed packages are installed.", file=sys.stderr) raise # Shows nice looking error dialog if an unhandled exception occures. import excepthook excepthook.install() import builtins builtins.i18n = lambda s: unicode(QCoreApplication.translate("PyKrita", s)) builtins.Scripter = Krita.instance() builtins.Application = Krita.instance() builtins.Krita = Krita.instance() + def qDebug(text): '''Use KDE way to show debug info TODO Add a way to control debug output from partucular plugins (?) ''' plugin = sys._getframe(1).f_globals['__name__'] pykrita.qDebug('{}: {}'.format(plugin, text)) @pykritaEventHandler('_pluginLoaded') def on_load(plugin): if plugin in init.functions: # Call registered init functions for the plugin init.fire(plugin=plugin) del init.functions[plugin] return True @pykritaEventHandler('_pluginUnloading') def on_unload(plugin): if plugin in unload.functions: # Deinitialize plugin unload.fire(plugin=plugin) del unload.functions[plugin] return True @pykritaEventHandler('_pykritaLoaded') def on_pykrita_loaded(): qDebug('PYKRITA LOADED') return True @pykritaEventHandler('_pykritaUnloading') def on_pykrita_unloading(): qDebug('UNLOADING PYKRITA') return True - diff --git a/plugins/extensions/pykrita/plugin/krita/api.py b/plugins/extensions/pykrita/plugin/krita/api.py index 66e07e5feb..410d38460c 100644 --- a/plugins/extensions/pykrita/plugin/krita/api.py +++ b/plugins/extensions/pykrita/plugin/krita/api.py @@ -1,50 +1,50 @@ # -*- coding: utf-8 -*- # Copyright (C) 2006 Paul Giannaros # Copyright (C) 2013 Shaheed Haque # Copyright (C) 2013 Alex Turbov # Copyright (C) 2014-2016 Boudewijn Rempt # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Library General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) version 3. # # This library 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 # Library General Public License for more details. # # You should have received a copy of the GNU Library General Public License # along with this library; see the file COPYING.LIB. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. '''Provide shortcuts to access krita internals from plugins''' import contextlib import os import sys from PyKrita.krita import * import pykrita + def objectIsAlive(obj): ''' Test whether an object is alive; that is, whether the pointer to the object still exists. ''' import sip try: - sip.unwrapinstance(obj) + sip.unwrapinstance(obj) except RuntimeError: - return False + return False return True def qDebug(text): '''Use KDE way to show debug info TODO Add a way to control debug output from partucular plugins (?) ''' plugin = sys._getframe(1).f_globals['__name__'] pykrita.qDebug('{}: {}'.format(plugin, text)) - diff --git a/plugins/extensions/pykrita/plugin/krita/attic/mikro.py b/plugins/extensions/pykrita/plugin/krita/attic/mikro.py index eb1b3cec23..bbde02a340 100644 --- a/plugins/extensions/pykrita/plugin/krita/attic/mikro.py +++ b/plugins/extensions/pykrita/plugin/krita/attic/mikro.py @@ -1,438 +1,412 @@ # -*- coding: utf-8 -*- """ Copyright (c) 2016 Boudewijn Rempt This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser 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. """ """ Mini Kross - a scripting solution inspired by Kross (http://kross.dipe.org/) Technically this is one of the most important modules in Scripter. Via the Qt meta object system it provides access to unwrapped objects. This code uses a lot of metaprogramming magic. To fully understand it, you have to know about metaclasses in Python """ import sys import sip from PyQt5.QtCore import QVariant, QMetaObject, Q_RETURN_ARG, Q_ARG, QObject, Qt, QMetaMethod, pyqtSignal from PyQt5.QtGui import QBrush, QFont, QImage, QPalette, QPixmap from PyQt5.QtWidgets import qApp variant_converter = { - "QVariantList": lambda v: v.toList(v), + "QVariantList": lambda v: v.toList(v), "QVariantMap": lambda v: toPyObject(v), "QPoint": lambda v: v.toPoint(), "str": lambda v: v.toString(), "int": lambda v: v.toInt()[0], "double": lambda v: v.toDouble()[0], "char": lambda v: v.toChar(), "QByteArray": lambda v: v.toByteArray(), "QPoint": lambda v: v.toPoint(), "QPointF": lambda v: v.toPointF(), "QSize": lambda v: v.toSize(), "QLine": lambda v: v.toLine(), "QStringList": lambda v: v.toStringList(), "QTime": lambda v: v.toTime(), "QDateTime": lambda v: v.toDateTime(), "QDate": lambda v: v.toDate(), "QLocale": lambda v: v.toLocale(), "QUrl": lambda v: v.toUrl(), "QRect": lambda v: v.toRect(), "QBrush": lambda v: QBrush(v), "QFont": lambda v: QFont(v), "QPalette": lambda v: QPalette(v), "QPixmap": lambda v: QPixmap(v), "QImage": lambda v: QImage(v), "bool": lambda v: v.toBool(), "QObject*": lambda v: wrap_variant_object(v), "QWidget*": lambda v: wrap_variant_object(v), "ActionMap": lambda v: int(v.count()) } + def wrap_variant_object(variant): """ convert a QObject or a QWidget to its wrapped superclass """ o = Krita.fromVariant(variant) return wrap(o, True) + def from_variant(variant): """ convert a QVariant to a Python value """ # Check whether it's really a QVariant if hasattr(variant, '__type__') and not (variant is None or variant.type() is None): typeName = variant.typeName() convert = variant_converter.get(typeName) if not convert: raise ValueError("Could not convert value to %s" % typeName) else: v = convert(variant) return v # Give up and return return variant + def convert_value(value): """ Convert a given value, upcasting to the highest QObject-based class if possible, unpacking lists and dicts. """ # Check whether it's a dict: if so, convert the keys/values if hasattr(value, '__class__') and issubclass(value.__class__, dict) and len(value) > 0: return {convert_value(k): convert_value(v) for k, v in value.items()} # Check whether it's a list: if so, convert the values if hasattr(value, '__class__') and issubclass(value.__class__, list) and len(value) > 0: return [convert_value(v) for v in value] if isinstance(value, str): # prefer Python strings return str(value) elif isinstance(value, PyQtClass): # already wrapped return value # Check whether it's a QObject if hasattr(value, '__class__') and issubclass(value.__class__, QObject): return wrap(value, True) - if hasattr(value, '__type__') and not (value is None or value.type() is None) : - return from_variant(value); + if hasattr(value, '__type__') and not (value is None or value.type() is None): + return from_variant(value) return value qtclasses = {} + def wrap(obj, force=False): """ If a class is not known by PyQt it will be automatically casted to a known wrapped super class. But that limits access to methods and propperties of this super class. So instead this functions returns a wrapper class (PyQtClass) which queries the metaObject and provides access to all slots and all properties. """ if isinstance(obj, str): # prefer Python strings return str(obj) elif isinstance(obj, PyQtClass): # already wrapped return obj elif obj and isinstance(obj, QObject): if force or obj.__class__.__name__ != obj.metaObject().className(): # Ah this is an unwrapped class obj = create_pyqt_object(obj) return obj + def unwrap(obj): """ if wrapped returns the wrapped object """ if hasattr(obj, "qt"): obj = obj.qt return obj - def is_qobject(obj): """ checks if class or wrapped class is a subclass of QObject """ if hasattr(obj, "__bases__") and issubclass(unwrap(obj), QObject): return True else: return False def is_scripter_child(qobj): """ walk up the object tree until Scripter or the root is found """ found = False p = qobj.parent() while p and not found: if str(p.objectName()) == "Krita": found = True break else: p = p.parent() return found - class Error(Exception): + """ Base error classed. Catch this to handle exceptions comming from C++ """ - class PyQtClass(object): + """ Base class """ def __init__(self, instance): self._instance = instance - def __del__(self): """ If this object is deleted it should also delete the wrapped object if it was created explicitly for this use. """ qobj = self._instance if is_scripter_child(qobj): if len(qobj.children()): print("Cannot delete", qobj, "because it has child objects") sip.delete(qobj) - def setProperty(self, name, value): self._instance.setProperty(name, value) - def getProperty(self, name): return wrap(self._instance.property(name)) - def propertyNames(self): return list(self.__class__.__properties__.keys()) - def dynamicPropertyNames(self): return self._instance.dynamicPropertyNames() - def metaObject(self): return self._instance.metaObject() - def connect(self, signal, slot): getattr(self._instance, signal).connect(slot) - def disconnect(self, signal, slot): getattr(self._instance, signal).disconnect(slot) - def parent(self): return wrap(self._instance.parent()) - def children(self): return [wrap(c) for c in self._instance.children()] - @property def qt(self): return self._instance - def __getitem__(self, key): if isinstance(key, int): length = getattr(self, "length", None) if length is not None: # array protocol try: return getattr(self, str(key)) except AttributeError as e: raise IndexError(key) else: return self.children()[key] else: return getattr(self, key) - def __getattr__(self, name): # Make named child objects available as attributes like QtQml # Check whether the object is in the QObject hierarchy for child in self._instance.children(): if str(child.objectName()) == name: obj = wrap(child) # Save found object for faster lookup setattr(self, name, obj) return obj # Check whether it's a property v = self._instance.property(name) return convert_value(v) @property def __members__(self): """ This method is for introspection. Using dir(thispyqtclass_object) returns a list of all children, methods, properties and dynamic properties. """ names = list(self.__dict__.keys()) for c in self._instance.children(): child_name = str(c.objectName()) if child_name: names.append(child_name) for pn in self._instance.dynamicPropertyNames(): names.append(str(pn)) return names - def __enter__(self): print("__enter__", self) - def __exit__(self, exc_type, exc_value, traceback): print("__exit__", self, exc_type, exc_value, traceback) - - class PyQtProperty(object): # slots for more speed __slots__ = ["meta_property", "name", "__doc__", "read_only"] - def __init__(self, meta_property): self.meta_property = meta_property self.name = meta_property.name() self.read_only = not meta_property.isWritable() self.__doc__ = "%s is a %s%s" % ( - self.name, meta_property.typeName(), + self.name, meta_property.typeName(), self.read_only and " (read-only)" or "" - ) - + ) def get(self, obj): return convert_value(self.meta_property.read(obj._instance)) - def set(self, obj, value): self.meta_property.write(obj._instance, value) - - class PyQtMethod(object): __slots__ = ["meta_method", "name", "args", "returnType", "__doc__"] - def __init__(self, meta_method): self.meta_method = meta_method self.name, args = str(meta_method.methodSignature(), encoding="utf-8").split("(", 1) self.args = args[:-1].split(",") self.returnType = str(meta_method.typeName()) types = [str(t, encoding="utf-8") for t in meta_method.parameterTypes()] - names = [str(n, encoding="utf-8") or "arg%i" % (i+1) \ - for i, n in enumerate(meta_method.parameterNames())] + names = [str(n, encoding="utf-8") or "arg%i" % (i + 1) + for i, n in enumerate(meta_method.parameterNames())] params = ", ".join("%s %s" % (t, n) for n, t in zip(types, names)) self.__doc__ = "%s(%s)%s" % ( - self.name, params, + self.name, params, self.returnType and (" -> %s" % self.returnType) or "" ) def instancemethod(self): def wrapper(obj, *args): qargs = [Q_ARG(t, v) for t, v in zip(self.args, args)] invoke_args = [obj._instance, self.name] invoke_args.append(Qt.DirectConnection) rtype = self.returnType if rtype: invoke_args.append(Q_RETURN_ARG(rtype)) invoke_args.extend(qargs) try: result = QMetaObject.invokeMethod(*invoke_args) except RuntimeError as e: raise TypeError( "%s.%s(%r) call failed: %s" % (obj, self.name, args, e)) return wrap(result) wrapper.__doc__ = self.__doc__ return wrapper - - # Cache on-the-fly-created classes for better speed pyqt_classes = {} + def create_pyqt_class(metaobject): class_name = str(metaobject.className()) cls = pyqt_classes.get(class_name) if cls: return cls attrs = {} properties = attrs["__properties__"] = {} for i in range(metaobject.propertyCount()): prop = PyQtProperty(metaobject.property(i)) prop_name = str(prop.name) if prop.read_only: properties[prop_name] = attrs[prop_name] = property(prop.get, doc=prop.__doc__) else: properties[prop_name] = attrs[prop_name] = property( - prop.get, prop.set, doc=prop.__doc__) + prop.get, prop.set, doc=prop.__doc__) methods = attrs["__methods__"] = {} signals = attrs["__signals__"] = {} for i in range(metaobject.methodCount()): meta_method = metaobject.method(i) - if meta_method.methodType() != QMetaMethod.Signal : + if meta_method.methodType() != QMetaMethod.Signal: method = PyQtMethod(meta_method) method_name = method.name if method_name in attrs: # There is already a property with this name # So append an underscore method_name += "_" instance_method = method.instancemethod() instance_method.__doc__ = method.__doc__ methods[method_name] = attrs[method_name] = instance_method - else : + else: method_name = meta_method.name() signal_attrs = [] properties[bytes(method_name).decode('ascii')] = pyqtSignal(meta_method.parameterTypes()) # Dynamically create a class with a base class and a dictionary cls = type(class_name, (PyQtClass,), attrs) pyqt_classes[class_name] = cls return cls - def create_pyqt_object(obj): """ Wrap a QObject and make all slots and properties dynamically available. @type obj: QObject @param obj: an unwrapped QObject @rtype: PyQtClass object @return: dynamically created object with all available properties and slots This is probably the only function you need from this module. Everything else are helper functions and classes. """ cls = create_pyqt_class(obj.metaObject()) return cls(obj) - - - - - diff --git a/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py b/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py index b052bd2bbc..4ff4be1005 100644 --- a/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py +++ b/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py @@ -1,138 +1,128 @@ # -*- coding: utf-8 -*- """ -This module will be a collection of functions to hook into the GUI of Scribus. +This module will be a collection of functions to hook into the GUI of Scribus. Currently it only provides functions to add items to a menubar. Support for the toolbar, statusbar and dockarea have still to be implemented. I have to think about how to provide this stuff to QtQml. """ from PyQt5.QtWidgets import QApplication, QMenu import mikro class MenuHooks(object): + """ - This class lets extension-scripts hook into the main menu of Scribus. + This class lets extension-scripts hook into the main menu of Scribus. """ - def __init__(self, window=None): self.window = window or Scripter.dialogs.mainWindow.qt self.menubar = self.window.menuBar() self.menus = [] - def createMenu(self, title): m = QMenu(title) self.menus.append(m) self.menubar.addMenu(m) return m - def iter_menus(self): for action in self.menubar.actions(): menu = action.menu() if menu: yield menu - + def iter_inner_menus(self, menu): for action in menu.actions(): menu = action.menu() if menu: yield menu - def findMenu(self, title): """ find a menu with a given title @type title: string @param title: English title of the menu @rtype: QMenu @return: None if no menu was found, else the menu with title """ # See also http://pyqt.sourceforge.net/Docs/PyQt5/i18n.html#differences-between-pyqt5-and-qt - title = QApplication.translate(mikro.classname(self.window), title) + title = QApplication.translate(mikro.classname(self.window), title) for menu in self.iter_menus(): if menu.title() == title: return menu for innerMenu in self.iter_inner_menus(menu): if innerMenu.title() == title: return innerMenu - def actionForMenu(self, menu): for action in self.menubar.actions(): if action.menu() == menu: return action - def insertMenuBefore(self, before_menu, new_menu): """ Insert a menu after another menu in the menubar @type: before_menu QMenu instance or title string of menu @param before_menu: menu which should be after the newly inserted menu @rtype: QAction instance @return: action for inserted menu """ if isinstance(before_menu, basestring): before_menu = self.findMenu(before_menu) before_action = self.actionForMenu(before_menu) # I have no clue why QMenuBar::insertMenu only allows # to insert before another menu and not after a menu... new_action = self.menubar.insertMenu(before_action, new_menu) return new_action - def menuAfter(self, menu): # This method is needed for insertMenuAfter because - # QMenuBar.insertMenu can only insert before another menu + # QMenuBar.insertMenu can only insert before another menu previous = None for m in self.iter_menus(): if previous and previous == menu: return m previous = m - def appendMenu(self, menu): """ - Probably not that usefull + Probably not that usefull because it will add a menu after the help menu """ action = self.menubar.addMenu(menu) return action - def insertMenuAfter(self, after_menu, new_menu): """ Insert a menu before another menu in the menubar """ if isinstance(after_menu, basestring): after_menu = self.findMenu(after_menu) after_after_menu = self.menuAfter(after_menu) if after_after_menu: return self.insertMenuBefore(after_after_menu, new_menu) else: return self.appendMenu(new_menu) - def appendItem(self, menu, item, *extra_args): if isinstance(menu, basestring): title = menu menu = self.findMenu(title) if not menu: raise ValueError("Menu %r not found" % title) - if isinstance(item, QMenu): + if isinstance(item, QMenu): action = menu.addMenu(item) else: action = menu.addAction(item, *extra_args) return action - def appendSeparator(self, menu): if isinstance(menu, basestring): menu = self.findMenu(menu) menu.addSeparator() diff --git a/plugins/extensions/pykrita/plugin/krita/decorators.py b/plugins/extensions/pykrita/plugin/krita/decorators.py index b6c0789da7..57c5f432a3 100644 --- a/plugins/extensions/pykrita/plugin/krita/decorators.py +++ b/plugins/extensions/pykrita/plugin/krita/decorators.py @@ -1,109 +1,109 @@ # -*- coding: utf-8 -*- # Copyright (C) 2006 Paul Giannaros # Copyright (C) 2013 Shaheed Haque # Copyright (C) 2013 Alex Turbov # Copyright (C) 2014-2016 Boudewijn Rempt # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Library General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) version 3. # # This library 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 # Library General Public License for more details. # # You should have received a copy of the GNU Library General Public License # along with this library; see the file COPYING.LIB. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. '''Decorators used in plugins''' import functools import inspect import sys import traceback from PyQt5 import QtCore, QtGui, QtWidgets import pykrita from .api import * # # initialization related stuff # + def pykritaEventHandler(event): def _decorator(func): setattr(pykrita, event, func) del func return _decorator def _callAll(plugin, functions, *args, **kwargs): if plugin in functions: for f in functions[plugin]: try: f(*args, **kwargs) except: traceback.print_exc() sys.stderr.write('\n') # TODO Return smth to a caller, so in case of # failed initialization it may report smth to the # C++ level and latter can show an error to the user... continue def _simpleEventListener(func): # automates the most common decorator pattern: calling a bunch # of functions when an event has occurred func.functions = dict() func.fire = functools.partial(_callAll, functions=func.functions) func.clear = func.functions.clear return func def _registerCallback(plugin, event, func): if plugin not in event.functions: event.functions[plugin] = set() event.functions[plugin].add(func) return func @_simpleEventListener def init(func): ''' The function will be called when particular plugin has loaded and the configuration has been initiated ''' plugin = sys._getframe(1).f_globals['__name__'] qDebug('@init: {}/{}'.format(plugin, func.__name__)) return _registerCallback(plugin, init, func) @_simpleEventListener def unload(func): ''' The function will be called when particular plugin is being unloaded from memory. Clean up any widgets that you have added to the interface (toolviews etc). ATTENTION Be really careful trying to access any window, view or document from the @unload handler: in case of application quit everything is dead already! ''' plugin = sys._getframe(1).f_globals['__name__'] qDebug('@unload: {}/{}'.format(plugin, func.__name__)) + def _module_cleaner(): qDebug('@unload/cleaner: {}/{}'.format(plugin, func.__name__)) if plugin in init.functions: qDebug('@unload/init-cleaner: {}/{}'.format(plugin, func.__name__)) del init.functions[plugin] func() return _registerCallback(plugin, unload, _module_cleaner) - - diff --git a/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py b/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py index 205d51b171..164785e19f 100644 --- a/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py +++ b/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py @@ -1,13 +1,14 @@ from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyKrita.krita import * + class DockWidgetFactory(DockWidgetFactoryBase): - def __init__(self, _id, _dockPosition, _klass): - super().__init__(_id, _dockPosition) - self.klass = _klass + def __init__(self, _id, _dockPosition, _klass): + super().__init__(_id, _dockPosition) + self.klass = _klass - def createDockWidget(self): - return self.klass() + def createDockWidget(self): + return self.klass() diff --git a/plugins/extensions/pykrita/plugin/krita/excepthook.py b/plugins/extensions/pykrita/plugin/krita/excepthook.py index f5316dab5d..188422c78d 100644 --- a/plugins/extensions/pykrita/plugin/krita/excepthook.py +++ b/plugins/extensions/pykrita/plugin/krita/excepthook.py @@ -1,85 +1,81 @@ """ Exception hook If some unexpected error occures it can be shown in a nice looking dialog. Especially useful is the traceback view. Things to extend: Clicking on the filename should open an editor. Things to consider: Mail exceptions, copy to clipboard or send to bug tracker. """ import sys import cgitb import atexit from PyQt5.QtCore import pyqtSlot, Qt from PyQt5.QtWidgets import QApplication, QDialog from excepthook_ui import Ui_ExceptHookDialog - def on_error(exc_type, exc_obj, exc_tb): """ This is the callback function for sys.excepthook """ dlg = ExceptHookDialog(exc_type, exc_obj, exc_tb) dlg.show() dlg.exec_() - def show_current_error(title=None): """ Call this function to show the current error. It can be used inside an except-block. """ - dlg = ExceptHookDialog(sys.exc_type, sys.exc_value, sys.exc_traceback, title) + dlg = ExceptHookDialog(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2], title) dlg.show() dlg.exec_() def install(): "activates the error handler" sys.excepthook = on_error - def uninstall(): "removes the error handler" sys.excepthook = sys.__excepthook__ atexit.register(uninstall) class ExceptHookDialog(QDialog): - def __init__(self, exc_type, exc_obj, exc_tb, title=None): QDialog.__init__(self) self.ui = Ui_ExceptHookDialog() self.ui.setupUi(self) if title: self.setWindowTitle(self.windowTitle() + ": " + title) msg = "%s: %s" % (exc_type.__name__, exc_obj) self.ui.exceptionLabel.setText(msg) html = cgitb.text((exc_type, exc_obj, exc_tb)) self.ui.tracebackBrowser.setText(html) - self.resize(650, 350) # give enough space to see the backtrace better + self.resize(650, 350) # give enough space to see the backtrace better @pyqtSlot() def on_closeButton_clicked(self): self.close() if __name__ == "__main__": # Some tests: app = QApplication(sys.argv) install() print("Triggering error 1") try: fail = 1 / 0 except: show_current_error("Using inside except") print("Triggering error 2") fail2 = 1 / 0 print("This will never be reached because excepthook") print("complains about fail2") diff --git a/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py b/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py index 16a80bc65f..4b68f5448f 100644 --- a/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py +++ b/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py @@ -1,48 +1,49 @@ # -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'excepthook.ui' # # Created by: PyQt5 UI code generator 5.6 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_ExceptHookDialog(object): + def setupUi(self, ExceptHookDialog): ExceptHookDialog.setObjectName("ExceptHookDialog") ExceptHookDialog.resize(542, 290) self.verticalLayout = QtWidgets.QVBoxLayout(ExceptHookDialog) self.verticalLayout.setObjectName("verticalLayout") self.gridLayout = QtWidgets.QGridLayout() self.gridLayout.setSpacing(10) self.gridLayout.setObjectName("gridLayout") self.label = QtWidgets.QLabel(ExceptHookDialog) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 0, 0, 1, 1) self.exceptionLabel = QtWidgets.QLabel(ExceptHookDialog) font = QtGui.QFont() font.setBold(True) font.setWeight(75) self.exceptionLabel.setFont(font) self.exceptionLabel.setObjectName("exceptionLabel") self.gridLayout.addWidget(self.exceptionLabel, 1, 0, 1, 1) self.verticalLayout.addLayout(self.gridLayout) self.tracebackBrowser = QtWidgets.QTextBrowser(ExceptHookDialog) self.tracebackBrowser.setMinimumSize(QtCore.QSize(0, 200)) self.tracebackBrowser.setObjectName("tracebackBrowser") self.verticalLayout.addWidget(self.tracebackBrowser) self.closeButton = QtWidgets.QPushButton(ExceptHookDialog) self.closeButton.setObjectName("closeButton") self.verticalLayout.addWidget(self.closeButton) self.retranslateUi(ExceptHookDialog) QtCore.QMetaObject.connectSlotsByName(ExceptHookDialog) def retranslateUi(self, ExceptHookDialog): _translate = QtCore.QCoreApplication.translate ExceptHookDialog.setWindowTitle(_translate("ExceptHookDialog", "Script error")) self.label.setText(_translate("ExceptHookDialog", "An exception occurred while running the script.")) self.exceptionLabel.setText(_translate("ExceptHookDialog", "Exception")) self.closeButton.setText(_translate("ExceptHookDialog", "&Close")) - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py b/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py index 48c0e362a6..77cb2b44b8 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- editor_main_window = None def launch(parent=None): global editor_main_window if not editor_main_window: from sceditor.mainwindow import EditorMainWindow editor_main_window = EditorMainWindow(parent) - editor_main_window.resize(640,480) + editor_main_window.resize(640, 480) editor_main_window.show() - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py b/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py index e524bcbbd6..21f6e942d6 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py @@ -1,155 +1,136 @@ from PyQt5.QtCore import QTimer, Qt from PyQt5.QtWidgets import (qApp, QListWidget, QListWidgetItem, QTextBrowser, QVBoxLayout, QWidget) class PopupWidget(QWidget): - def __init__(self, textedit): flags = Qt.ToolTip flags = Qt.Window | Qt.FramelessWindowHint | \ - Qt.CustomizeWindowHint | Qt.X11BypassWindowManagerHint + Qt.CustomizeWindowHint | Qt.X11BypassWindowManagerHint QWidget.__init__(self, None, flags) self.textedit = textedit self.vlayout = QVBoxLayout(self) self.vlayout.setContentsMargins(0, 0, 0, 0) self.init_popup() self.show() self.hide() self.active = False - def show(self, timeout=0, above=False): self.cursor_start_col = self.textedit.textCursor().columnNumber() desktop = qApp.desktop() screen = desktop.screen(desktop.screenNumber(self)) screen_width = screen.width() screen_height = screen.height() win_width = self.width() win_height = self.height() cursorRect = self.textedit.cursorRect() if above: pos = self.textedit.mapToGlobal(cursorRect.topLeft()) pos.setY(pos.y() - win_height) else: pos = self.textedit.mapToGlobal(cursorRect.bottomLeft()) if pos.y() < 0: pos = self.textedit.mapToGlobal(cursorRect.bottomLeft()) if pos.y() + win_height > screen_height: pos = self.textedit.mapToGlobal(cursorRect.topLeft()) pos.setY(pos.y() - win_height) if pos.x() + win_width > screen_width: pos.setX(screen_width - win_width) self.move(pos) QWidget.show(self) self.active = True if timeout: QTimer.singleShot(timeout * 1000, self.hide) - def hide(self): self.active = False QWidget.hide(self) - - - - class CallTip(PopupWidget): - def init_popup(self): self.browser = QTextBrowser(self) self.layout().addWidget(self.browser) - - class AutoCompleteItem(QListWidgetItem): def __init__(self, item): QListWidgetItem.__init__(self) value = item.name self.setText(value) self.value = value self.kind = item.kind - class AutoComplete(PopupWidget): - def init_popup(self): self.list = QListWidget(self) self.list.itemClicked.connect(self.insertItem) self.layout().addWidget(self.list) self.items = [] - def insertItem(self, item): self.insert() - def insert(self): completition = self.items[self.list.currentRow()].value cursor = self.textedit.textCursor() col = cursor.columnNumber() line = unicode(cursor.block().text()) i = self.cursor_start_col while i > 0: - #print(`line[i:col]`) + # print(`line[i:col]`) if completition.startswith(line[i:col]): - #print("break") + # print("break") break i -= 1 - #print(col,i) - cursor.insertText(completition[col-i:]) + # print(col,i) + cursor.insertText(completition[col - i:]) self.hide() - def setItems(self, proposals): - proposals = sorted(proposals, cmp=lambda p1,p2:cmp(p1.name,p2.name)) + proposals = sorted(proposals, cmp=lambda p1, p2: cmp(p1.name, p2.name)) del self.items[:] self.list.clear() for entry in proposals: i = AutoCompleteItem(entry) self.list.addItem(i) self.items.append(i) - def keyPressEvent(self, event): self.list.keyPressEvent(event) key = event.key() text = event.text() if key in [Qt.Key_Right, Qt.Key_Enter, Qt.Key_Return]: text = "" cursor = self.textedit.textCursor() line = unicode(cursor.block().text()) col = cursor.columnNumber() prefix = line[self.cursor_start_col:col] + unicode(text) found = False for row, item in enumerate(self.items): if item.value.startswith(prefix): current = self.items[self.list.currentRow()].value if not current.startswith(prefix): self.list.setCurrentRow(row) found = True break if not found: self.hide() return if key in [Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown]: return True elif key in [Qt.Key_Tab, Qt.Key_Right, Qt.Key_Enter, Qt.Key_Return]: self.insert() return True elif not text: self.hide() - - - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/console.py b/plugins/extensions/pykrita/plugin/krita/sceditor/console.py index 7b4010aad2..adc88aa00b 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/console.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/console.py @@ -1,524 +1,475 @@ from __future__ import print_function import sys import traceback import re from PyQt5.QtCore import QObject, Qt from PyQt5.QtGui import QTextCursor from PyQt5.QtWidgets import qApp, QApplication, QPlainTextEdit from highlighter import PythonHighlighter, QtQmlHighlighter - - from PyQt5.QtQml import ( QScriptEngine, QScriptValue, QScriptValueIterator) class OutputWidget(QPlainTextEdit): - def __init__(self, parent=None, readonly=True, max_rows=1000, echo=True): QPlainTextEdit.__init__(self, parent) self.echo = echo self.setReadOnly(readonly) self.document().setMaximumBlockCount(max_rows) self.attach() - def attach(self): sys.stdout = sys.stderr = self - - + def __del__(self): self.detach() - def detach(self): sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ - def write(self, s): if self.echo: sys.__stdout__.write(s) doc = self.document() cursor = QTextCursor(doc) cursor.clearSelection() cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) cursor.insertText(s) cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) cursor.clearSelection() self.ensureCursorVisible() qApp.processEvents() - def writelines(self, lines): self.write("\n".join(lines)) - class ConsoleWidget(OutputWidget): - def __init__(self, parent=None, ps1="?", ps2=">"): OutputWidget.__init__(self, parent, readonly=False) self.setTabChangesFocus(False) self.ps1 = ps1 self.ps2 = ps2 self.history_index = 0 self.history = [""] self.tab_state = -1 print(self.ps1, end='') - def focusInEvent(self, event): self.attach() OutputWidget.focusInEvent(self, event) - def mousePressEvent(self, event): self.setFocus() - def push(self, line): return True - def keyPressEvent(self, event): def remove_line(): cursor = self.textCursor() cursor.select(QTextCursor.BlockUnderCursor) cursor.removeSelectedText() key = event.key() modifiers = event.modifiers() l = len(self.ps1) line = unicode(self.document().end().previous().text()) - ps1orps2, line = line[:l-1], line[l:] + ps1orps2, line = line[:l - 1], line[l:] - if not key in [Qt.Key_Tab, Qt.Key_Backtab] and \ len(event.text()): self.tab_state = -1 if key == Qt.Key_Up: if self.history_index + 1 < len(self.history): self.history_index += 1 remove_line() print() print(ps1orps2, self.history[self.history_index], end='') elif key == Qt.Key_Down: if self.history_index > 0: self.history_index -= 1 remove_line() print() print(ps1orps2, self.history[self.history_index], end='') elif key == Qt.Key_Tab: if modifiers & Qt.ControlModifier: print(" " * 4, end='') else: self.tab_state += 1 remove_line() print() print(ps1orps2, end='') print(self.completer.complete(line, self.tab_state) or line, end='') elif key == Qt.Key_Backtab: if self.tab_state >= 0: self.tab_state -= 1 remove_line() print() print(ps1orps2, end='') print(self.completer.complete(line, self.tab_state) or line, end='') elif key in [Qt.Key_Backspace, Qt.Key_Left]: - if self.textCursor().columnNumber() > len(ps1orps2) + 1: + if self.textCursor().columnNumber() > len(ps1orps2) + 1: return OutputWidget.keyPressEvent(self, event) elif key == Qt.Key_Return: self.moveCursor(QTextCursor.EndOfLine, QTextCursor.MoveAnchor) print() if self.push(line): print(self.ps2, end='') else: print(self.ps1, end='') if line and line != self.history[self.history_index]: self.history.insert(1, line) self.history_index = 0 else: return OutputWidget.keyPressEvent(self, event) - class PythonInterpreter(object): - def __init__(self, name="", locals=None): self.name = name self.locals = locals or {} self.locals["__name__"] = self.name self.lines = [] - def run(self, source, locals=None): if locals == None: - locals = self.locals + locals = self.locals code = compile(source, self.name, "exec") try: - exec code in locals + exec(code, locals) except: - self.showtraceback() + self.showtraceback() try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() - except: pass - + except: + pass def push(self, line): if self.lines: if line: self.lines.append(line) - return 1 # want more! + return 1 # want more! else: line = "\n".join(self.lines) + "\n" else: if not line: return 0 try: code = compile(line, self.name, "single") self.lines = [] except SyntaxError as why: if why[0] == "unexpected EOF while parsing": self.lines.append(line) - return 1 # want more! + return 1 # want more! else: self.showtraceback() except: self.showtraceback() else: try: - exec code in self.locals + exec(code, self.locals) except: self.showtraceback() try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() - except: pass + except: + pass return 0 - def showtraceback(self): self.lines = [] - if sys.exc_type == SyntaxError: # and len(sys.exc_value) == 2: + if sys.exc_info()[0] == SyntaxError: # and len(sys.exc_value) == 2: print(" File \"%s\", line %d" % (self.name, sys.exc_value[1][1])) print(" " * (sys.exc_value[1][2] + 2) + "^") - print(str(sys.exc_type) + ":", sys.exc_value[0]) + print(str(sys.exc_info()[0]) + ":", sys.exc_value[0]) else: - traceback.print_tb(sys.exc_traceback, None) - print(sys.exc_type.__name__ + ":", sys.exc_value) - - + traceback.print_tb(sys.exc_info()[2], None) + print(sys.exc_type.__name__ + ":", sys.exc_info()[1]) class PythonCompleter(object): - def __init__(self, namespace): self.namespace = namespace - def complete(self, text, state): if state == 0: if "." in text: self.matches = self.attr_matches(text) else: self.matches = self.global_matches(text) try: return self.matches[state] except IndexError: return None - def global_matches(self, text): - import keyword, __builtin__ + import keyword + import __builtin__ matches = [] n = len(text) for list in [keyword.kwlist, __builtin__.__dict__, self.namespace]: for word in list: if word[:n] == text and word != "__builtins__": matches.append(word) return matches - def attr_matches(self, text): def get_class_members(cls): ret = dir(cls) - if hasattr(cls,'__bases__'): + if hasattr(cls, '__bases__'): for base in cls.__bases__: ret = ret + get_class_members(base) return ret import re m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) if not m: return expr, attr = m.group(1, 3) object = eval(expr, self.namespace) words = dir(object) - if hasattr(object,'__class__'): + if hasattr(object, '__class__'): words.append('__class__') words = words + get_class_members(object.__class__) matches = [] n = len(attr) for word in words: if word[:n] == attr and word != "__builtins__": matches.append("%s.%s" % (expr, word)) return matches - - - - - class PythonConsole(ConsoleWidget): - def __init__(self, parent=None, namespace=None): ConsoleWidget.__init__(self, parent, ps1=">>> ", ps2="... ") self.highlighter = PythonHighlighter(self) self.inter = PythonInterpreter(locals=namespace) self.namespace = self.inter.locals self.completer = PythonCompleter(self.namespace) - #print("Python", sys.version) - #print("Autocomplete with (Shift+)Tab, insert spaces with Ctrl+Tab") + # print("Python", sys.version) + # print("Autocomplete with (Shift+)Tab, insert spaces with Ctrl+Tab") self.push("pass") - def push(self, line): return self.inter.push(line) - def clear(self): - doc = self.document() - doc.setPlainText(self.ps1) - - + doc = self.document() + doc.setPlainText(self.ps1) class QtQmlInterpreter(object): - def __init__(self, locals): self.locals = locals self.engine = self.newEngine() self.code = "" self.state = 0 - def newEngine(self): engine = QScriptEngine() ns = engine.globalObject() for name, value in self.locals.items(): if isinstance(value, QObject): value = engine.newQObject(value) elif callable(value): value = engine.newFunction(value) ns.setProperty(name, value) return engine - def execute(self, code): self.execute_code(code, self.engine) - def execute_code(self, code, engine=None): engine = engine or self.newEngine() result = engine.evaluate(code) try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() - except: pass + except: + pass if engine.hasUncaughtException(): bt = engine.uncaughtExceptionBacktrace() print("Traceback:") print("\n".join([" %s" % l for l in list(bt)])) print(engine.uncaughtException().toString()) else: if not result.isUndefined(): print(result.toString()) - def push(self, line): if not line.strip(): return self.state self.code = self.code + line + "\n" if self.engine.canEvaluate(self.code): self.execute(self.code) self.code = "" self.state = 0 else: self.state = 1 return self.state - + js_words = [ - 'break', + 'break', 'for', 'throw', 'case', 'function', 'try', 'catch', 'if', 'typeof', 'continue', 'in', 'var', 'default', 'instanceof', 'void', 'delete', 'new', 'undefined', 'do', 'return', 'while', 'else', 'switch', 'with', 'finally', 'this', 'NaN', 'Infinity', 'undefined', 'print', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'decodeURI', 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', 'escape', 'unescape', 'version', 'gc', 'Object', 'Function', 'Number', 'Boolean', 'String', 'Date', 'Array', 'RegExp', 'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError', 'eval', 'Math', 'Enumeration', 'Variant', 'QObject', 'QMetaObject'] - class QtQmlCompleter(object): - def __init__(self, engine): self.engine = engine - def complete(self, text, state): if state == 0: if "." in text: self.matches = self.attr_matches(text) else: self.matches = self.global_matches(text) try: return self.matches[state] except IndexError: return None - - def attr_matches(self, text): return [] - - def iter_obj(self, obj): it = QScriptValueIterator(self.engine.globalObject()) while it.hasNext(): yield str(it.name()) it.next() - def global_matches(self, text): words = list(self.iter_obj(self.engine.globalObject())) words.extend(js_words) l = [] n = len(text) for w in words: if w[:n] == text: l.append(w) return l - - - class QtQmlConsole(ConsoleWidget): - def __init__(self, parent=None, namespace=None): ConsoleWidget.__init__(self, parent, ps1=">>> ", ps2="... ") self.highlighter = QtQmlHighlighter(self) namespace = namespace or {} + def console_print(context, engine): for i in range(context.argumentCount()): print(context.argument(i).toString(), end='') print() return QScriptValue() + def dir_context(context, engine): if context.argumentCount() == 0: obj = context.thisObject() else: obj = context.argument(0) l = [] it = QScriptValueIterator(obj) while it.hasNext(): it.next() l.append(str(it.name())) return QScriptValue(engine, repr(l)) namespace["print"] = console_print namespace["dir"] = dir_context namespace["Application"] = qApp try: namespace["Scripter"] = Scripter.qt - except: pass + except: + pass self.inter = QtQmlInterpreter(namespace) self.completer = QtQmlCompleter(self.inter.engine) - - def push(self, line): return self.inter.push(line) - if __name__ == "__main__": app = QApplication(sys.argv) o = QtQmlConsole() - #o = PythonConsole() - o.resize(640,480) + # o = PythonConsole() + o.resize(640, 480) o.attach() o.show() app.exec_() diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py index 2134fd08e3..ded5039429 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py @@ -1,428 +1,390 @@ # Ported from KoDockWidgetTitleBar.cpp which is part of KOffice # Copyright (c) 2007 Marijn Kruisselbrink # Copyright (C) 2007 Thomas Zander # The code is distributed under GPL 2 or any later version import os from PyQt5.QtCore import QPoint, QSize, Qt, QRect, QTimer from PyQt5.QtGui import (QIcon, QPainter) from PyQt5.QtWidgets import (QAbstractButton, QApplication, QComboBox, QDockWidget, QHBoxLayout, QLayout, QMainWindow, QPushButton, QStyle, QStyleOptionDockWidget, QStyleOptionToolButton, QStylePainter, QWidget) import dockwidget_icons def hasFeature(dockwidget, feature): return dockwidget.features() & feature == feature - class DockWidgetTitleBarButton(QAbstractButton): - def __init__(self, titlebar): QAbstractButton.__init__(self, titlebar) self.setFocusPolicy(Qt.NoFocus) - def sizeHint(self): self.ensurePolished() margin = self.style().pixelMetric(QStyle.PM_DockWidgetTitleBarButtonMargin, None, self) if self.icon().isNull(): return QSize(margin, margin) iconSize = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self) pm = self.icon().pixmap(iconSize) return QSize(pm.width() + margin, pm.height() + margin) - def enterEvent(self, event): if self.isEnabled(): self.update() QAbstractButton.enterEvent(self, event) - def leaveEvent(self, event): if self.isEnabled(): self.update() QAbstractButton.leaveEvent(self, event) - - def paintEvent(self, event): p = QPainter(self) r = self.rect() opt = QStyleOptionToolButton() opt.init(self) opt.state |= QStyle.State_AutoRaise if self.isEnabled() and self.underMouse() and \ not self.isChecked() and not self.isDown(): opt.state |= QStyle.State_Raised if self.isChecked(): opt.state |= QStyle.State_On if self.isDown(): opt.state |= QStyle.State_Sunken self.style().drawPrimitive( QStyle.PE_PanelButtonTool, opt, p, self) opt.icon = self.icon() opt.subControls = QStyle.SubControls() opt.activeSubControls = QStyle.SubControls() opt.features = QStyleOptionToolButton.None opt.arrowType = Qt.NoArrow size = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self) opt.iconSize = QSize(size, size) self.style().drawComplexControl(QStyle.CC_ToolButton, opt, p, self) - - class DockWidgetTitleBar(QWidget): # XXX: support QDockWidget.DockWidgetVerticalTitleBar feature - def __init__(self, dockWidget): QWidget.__init__(self, dockWidget) self.openIcon = QIcon(":arrow-down.png") self.closeIcon = QIcon(":arrow-right.png") self.pinIcon = QIcon(":pin.png") q = dockWidget self.floatButton = DockWidgetTitleBarButton(self) self.floatButton.setIcon(q.style().standardIcon( QStyle.SP_TitleBarNormalButton, None, q)) self.floatButton.clicked.connect(self.toggleFloating) self.floatButton.setVisible(True) self.closeButton = DockWidgetTitleBarButton(self) self.closeButton.setIcon(q.style().standardIcon( QStyle.SP_TitleBarCloseButton, None, q)) self.closeButton.clicked.connect(dockWidget.close) self.closeButton.setVisible(True) self.collapseButton = DockWidgetTitleBarButton(self) self.collapseButton.setIcon(self.openIcon) self.collapseButton.clicked.connect(self.toggleCollapsed) self.collapseButton.setVisible(True) self.pinButton = DockWidgetTitleBarButton(self) self.pinButton.setIcon(self.pinIcon) self.pinButton.setCheckable(True) self.pinButton.setChecked(True) self.pinButton.clicked.connect(self.togglePinned) self.pinButton.setVisible(True) dockWidget.featuresChanged.connect(self.featuresChanged) self.featuresChanged(0) - def minimumSizeHint(self): return self.sizeHint() - def sizeHint(self): q = self.parentWidget() mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q) fw = q.style().pixelMetric(QStyle.PM_DockWidgetFrameWidth, None, q) closeSize = QSize(0, 0) if self.closeButton: closeSize = self.closeButton.sizeHint() floatSize = QSize(0, 0) if self.floatButton: floatSize = self.floatButton.sizeHint() hideSize = QSize(0, 0) if self.collapseButton: hideSize = self.collapseButton.sizeHint() pinSize = QSize(0, 0) if self.pinButton: pinSize = self.pinButton.sizeHint() - buttonHeight = max(max(closeSize.height(), floatSize.height()), - hideSize.height(), pinSize.height()) + 2 + buttonHeight = max(max(closeSize.height(), floatSize.height()), + hideSize.height(), pinSize.height()) + 2 buttonWidth = closeSize.width() + floatSize.width() + hideSize.width() + pinSize.width() titleFontMetrics = q.fontMetrics() fontHeight = titleFontMetrics.lineSpacing() + 2 * mw height = max(buttonHeight, fontHeight) width = buttonWidth + height + 4 * mw + 2 * fw if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar): width, height = height, width return QSize(width, height) - def paintEvent(self, event): p = QStylePainter(self) q = self.parentWidget() if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar): fw = 1 or q.isFloating() and q.style().pixelMetric( QStyle.PM_DockWidgetFrameWidth, None, q) or 0 mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q) titleOpt = QStyleOptionDockWidget() titleOpt.initFrom(q) titleOpt.verticalTitleBar = True titleOpt.rect = QRect( - QPoint(fw, fw + mw + \ + QPoint(fw, fw + mw + self.collapseButton.size().height() + self.pinButton.size().height()), QSize( - self.geometry().width() - (fw * 2), - self.geometry().height() - (fw * 2) - \ + self.geometry().width() - (fw * 2), + self.geometry().height() - (fw * 2) - mw - self.collapseButton.size().height() - self.pinButton.size().height())) titleOpt.title = q.windowTitle() titleOpt.closable = hasFeature(q, QDockWidget.DockWidgetClosable) titleOpt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable) p.drawControl(QStyle.CE_DockWidgetTitle, titleOpt) else: fw = q.isFloating() and q.style().pixelMetric( QStyle.PM_DockWidgetFrameWidth, None, q) or 0 mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q) titleOpt = QStyleOptionDockWidget() titleOpt.initFrom(q) titleOpt.rect = QRect( - QPoint(fw + mw + \ + QPoint(fw + mw + self.collapseButton.size().width() + self.pinButton.size().width(), fw), QSize( - self.geometry().width() - (fw * 2) - \ + self.geometry().width() - (fw * 2) - mw - self.collapseButton.size().width() - self.pinButton.size().width(), self.geometry().height() - (fw * 2))) titleOpt.title = q.windowTitle() titleOpt.closable = hasFeature(q, QDockWidget.DockWidgetClosable) titleOpt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable) p.drawControl(QStyle.CE_DockWidgetTitle, titleOpt) - def resizeEvent(self, event): q = self.parentWidget() if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar): fh = q.isFloating() and q.style().pixelMetric( QStyle.PM_DockWidgetFrameWidth, None, q) or 0 opt = QStyleOptionDockWidget() opt.initFrom(q) opt.verticalTitleBar = True opt.rect = QRect( - QPoint(fh, 40), #self.geometry().height() - (fh * 3)), + QPoint(fh, 40), # self.geometry().height() - (fh * 3)), QSize( - self.geometry().width() - (fh * 2), + self.geometry().width() - (fh * 2), fh * 2)) opt.title = q.windowTitle() opt.closable = hasFeature(q, QDockWidget.DockWidgetClosable) opt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable) floatRect = q.style().subElementRect( QStyle.SE_DockWidgetFloatButton, opt, q) if not floatRect.isNull(): self.floatButton.setGeometry(floatRect) closeRect = q.style().subElementRect( QStyle.SE_DockWidgetCloseButton, opt, q) if not closeRect.isNull(): self.closeButton.setGeometry(closeRect) top = fh if not floatRect.isNull(): top = floatRect.x() elif not closeRect.isNull(): top = closeRect.x() size = self.collapseButton.size() if not closeRect.isNull(): size = self.closeButton.size() elif not floatRect.isNull(): size = self.floatButton.size() collapseRect = QRect(QPoint(top, fh), size) self.collapseButton.setGeometry(collapseRect) - pinRect = QRect(QPoint(top, fh+collapseRect.height()+1), size) + pinRect = QRect(QPoint(top, fh + collapseRect.height() + 1), size) self.pinButton.setGeometry(pinRect) else: fw = q.isFloating() and q.style().pixelMetric( QStyle.PM_DockWidgetFrameWidth, None, q) or 0 opt = QStyleOptionDockWidget() opt.initFrom(q) opt.rect = QRect( QPoint(fw, fw), QSize( - self.geometry().width() - (fw * 2), + self.geometry().width() - (fw * 2), self.geometry().height() - (fw * 2))) opt.title = q.windowTitle() opt.closable = hasFeature(q, QDockWidget.DockWidgetClosable) opt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable) floatRect = q.style().subElementRect( QStyle.SE_DockWidgetFloatButton, opt, q) if not floatRect.isNull(): self.floatButton.setGeometry(floatRect) closeRect = q.style().subElementRect( QStyle.SE_DockWidgetCloseButton, opt, q) if not closeRect.isNull(): self.closeButton.setGeometry(closeRect) top = fw if not floatRect.isNull(): top = floatRect.y() elif not closeRect.isNull(): top = closeRect.y() size = self.collapseButton.size() if not closeRect.isNull(): size = self.closeButton.size() elif not floatRect.isNull(): size = self.floatButton.size() collapseRect = QRect(QPoint(fw, top), size) self.collapseButton.setGeometry(collapseRect) pinRect = QRect(QPoint(fw + collapseRect.width() + 1, top), size) self.pinButton.setGeometry(pinRect) - def setCollapsed(self, collapsed): q = self.parentWidget() if q and q.widget() and q.widget().isHidden() != collapsed: self.toggleCollapsed() - def toggleFloating(self): q = self.parentWidget() q.setFloating(not q.isFloating()) - def toggleCollapsed(self): q = self.parentWidget() if not q: return q.toggleCollapsed() self.setCollapsedIcon(q.isCollapsed()) - def setCollapsedIcon(self, flag): self.collapseButton.setIcon(flag and self.openIcon or self.closeIcon) - def togglePinned(self, checked): self.parent().setPinned(checked) - def featuresChanged(self, features): q = self.parentWidget() self.closeButton.setVisible(hasFeature(q, QDockWidget.DockWidgetClosable)) self.floatButton.setVisible(hasFeature(q, QDockWidget.DockWidgetFloatable)) # self.resizeEvent(None) - class DockMainWidgetWrapper(QWidget): - def __init__(self, dockwidget): QWidget.__init__(self, dockwidget) self.widget = None self.hlayout = QHBoxLayout(self) self.hlayout.setSpacing(0) self.hlayout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.hlayout) - def setWidget(self, widget): self.widget = widget self.widget_height = widget.height self.layout().addWidget(widget) - def isCollapsed(self): return self.widget.isVisible() - def setCollapsed(self, flag): if not flag: self.old_size = self.size() self.layout().removeWidget(self.widget) self.widget.hide() if hasFeature(self.parent(), QDockWidget.DockWidgetVerticalTitleBar): self.parent().setMaximumWidth(self.parent().width() - self.width()) else: self.parent().setMaximumHeight(self.parent().height() - self.height()) else: self.setFixedSize(self.old_size) self.parent().setMinimumSize(QSize(1, 1)) self.parent().setMaximumSize(QSize(32768, 32768)) self.widget.show() self.layout().addWidget(self.widget) self.setMinimumSize(QSize(1, 1)) - self.setMaximumSize(QSize(32768, 32768)) - + self.setMaximumSize(QSize(32768, 32768)) class DockWidget(QDockWidget): - def __init__(self, *args): QDockWidget.__init__(self, *args) self.titleBar = DockWidgetTitleBar(self) self.setTitleBarWidget(self.titleBar) self.mainWidget = None self.entered = False self.pinned = True self.shot = False - def enterEvent(self, event): self.entered = True if not self.shot and not self.isPinned() and not self.isFloating(): self.shot = True QTimer.singleShot(500, self.autoshow) return QDockWidget.enterEvent(self, event) - def leaveEvent(self, event): self.entered = False if not self.shot and not self.isPinned() and not self.isFloating(): self.shot = True QTimer.singleShot(1000, self.autohide) return QDockWidget.leaveEvent(self, event) - def autohide(self): self.shot = False - if not self.entered: + if not self.entered: self.setCollapsed(False) - def autoshow(self): self.shot = False if self.entered: self.setCollapsed(True) - def isPinned(self): return self.pinned - def setPinned(self, flag): self.pinned = flag - def setWidget(self, widget): self.mainWidget = DockMainWidgetWrapper(self) self.mainWidget.setWidget(widget) QDockWidget.setWidget(self, self.mainWidget) - def setCollapsed(self, flag): self.mainWidget.setCollapsed(flag) self.titleBarWidget().setCollapsedIcon(flag) - def isCollapsed(self): return self.mainWidget.isCollapsed() - def toggleCollapsed(self): self.setCollapsed(not self.isCollapsed()) - if __name__ == "__main__": import sys from PyQt5.QtGui import QTextEdit app = QApplication(sys.argv) app.setStyle("qtcurve") win = QMainWindow() dock1 = DockWidget("1st dockwidget", win) dock1.setFeatures(dock1.features() | QDockWidget.DockWidgetVerticalTitleBar) - combo = QComboBox(dock1) + combo = QComboBox(dock1) dock1.setWidget(combo) win.addDockWidget(Qt.LeftDockWidgetArea, dock1) dock2 = DockWidget("2nd dockwidget") dock2.setFeatures(dock1.features() | QDockWidget.DockWidgetVerticalTitleBar) button = QPushButton("Hello, world!", dock2) dock2.setWidget(button) win.addDockWidget(Qt.RightDockWidgetArea, dock2) edit = QTextEdit(win) win.setCentralWidget(edit) win.resize(640, 480) win.show() app.exec_() diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py index ce9861f796..9da7eb289a 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py @@ -1,154 +1,156 @@ # -*- coding: utf-8 -*- # Resource object code # # Created: Mi Aug 20 05:23:34 2008 # by: The Resource Compiler for PyQt (Qt v4.3.4) # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore qt_resource_data = "\ \x00\x00\x02\x0d\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\ \x01\x3a\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x8a\x49\x44\ \x41\x54\x78\xda\xdd\x93\x31\x4b\x5c\x41\x14\x85\xcf\xbc\x37\xc3\ \x6e\x50\x57\xc5\x64\xc5\x3f\x91\xca\x56\x48\x13\x42\x04\x8d\x8b\ \x55\xfe\x80\xe8\x3f\x90\x20\x68\x61\x93\xc2\xc2\x5a\xd1\x36\x65\ \x92\x26\x16\x46\xc5\x22\xe5\x6a\x23\x08\x8b\x98\x14\xba\xb3\xef\ \xe5\xed\x2a\x6f\x67\x57\x9d\xf7\xf6\x38\x3c\xd0\xca\x28\x92\x42\ \xf0\xbb\x5c\xb8\x30\x70\x38\x9c\x3b\x57\x90\xc4\xff\xe0\xb9\x7e\ \x5a\x01\x09\x87\x70\x60\x02\x79\x3c\x86\xaf\xb8\xa0\x23\x13\xc0\ \x18\x16\x47\x87\x47\x3f\x59\x61\x61\x5c\xc5\x7e\x0c\xe3\xbb\x49\ \x1a\xd4\x44\x00\x14\x00\x74\x89\xcc\x6f\x91\x45\xf4\x5e\xf6\xa1\ \x32\x52\x59\x01\x30\x95\x09\xc0\x60\x49\xff\xd5\xd3\xa5\x52\xa9\ \x5f\x9f\x6b\x51\x35\x55\x68\xab\x11\x7a\x21\x6a\xac\x01\xaf\x00\ \xbc\x64\xe6\xb7\xa1\x23\xe2\xb7\x67\x70\x95\xce\xdf\x66\xc0\x9f\ \xac\x97\x0f\xcb\x6f\xb7\x76\xb6\xac\x92\x0a\xca\x53\x90\x42\x42\ \x75\x14\x70\x05\xa0\xe5\xba\xee\x3a\x00\x06\x6a\xc5\x34\x08\x83\ \x12\x67\x59\xbd\xc9\x20\x83\x3f\x58\x16\x1f\xc4\x74\x77\x6f\xf7\ \x4a\x61\xa8\xe0\xab\x54\xc1\x4f\x7d\x78\x4d\x0f\x1d\x57\x68\x3b\ \x23\x76\x30\x0d\x8e\xc3\x39\x2e\xd8\xcd\x3b\xb7\xc0\x6f\x5c\xdf\ \xd8\xdd\x58\x8d\xeb\x71\x2a\x13\x09\xaf\xed\x41\x9e\x4b\x20\x02\ \x7a\xa2\x42\xd2\xfc\x63\xbe\xa7\x0b\x97\x9f\xef\x5d\xa3\x7d\x6d\ \x67\xb6\x7f\x6d\xef\xb1\xc5\x54\x5d\x28\xb0\x4e\xe4\xce\x72\x49\ \x27\xe2\x51\x9b\xcd\xc9\x07\xff\x01\xe7\xc9\xf8\x24\x7e\xb7\x77\ \xb0\xdf\x90\x2d\x49\x46\xa4\x4a\x72\xc6\x34\xe2\x37\x74\x6f\x0f\ \x0a\xdc\x84\x1a\x06\xc1\xfb\x13\x7d\x6a\x73\x2a\x9f\x34\x1b\xad\ \x71\x2e\x53\xe3\x0e\xee\xbd\x05\xf1\x51\x4c\xe2\x85\xaf\xb8\x96\ \x7c\xc1\x3f\x78\x06\xc7\x74\x0d\x90\x24\xc3\xdb\x6d\x74\x09\xd1\ \x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x6c\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\x00\x00\x00\x00\x00\xf9\x43\ \xbb\x7f\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x12\x00\x00\ \x0b\x12\x01\xd2\xdd\x7e\xfc\x00\x00\x00\x07\x74\x49\x4d\x45\x07\ \xd2\x0b\x01\x0d\x00\x32\x9c\x41\x83\x23\x00\x00\x01\xe9\x49\x44\ \x41\x54\x78\xda\x85\x93\xbd\x8b\x13\x51\x14\xc5\x7f\x4f\x07\xd7\ \xc4\x04\xb2\x90\x2d\x32\x33\x11\x41\x10\x54\x58\x85\xf8\x0f\xd8\ \x5a\x6a\x61\x65\x63\x21\x06\x02\x3a\x29\xd2\xd8\x29\x62\x13\xc2\ \x42\x94\x34\x61\x43\x48\x1a\x21\x56\xdb\xd8\x68\x65\xa7\x29\x76\ \x41\x50\x41\x0c\x64\x87\xc9\x84\x7c\x1a\x21\xb3\x60\x66\x2c\xb2\ \x6f\x36\x93\x1d\xf0\xc1\x2b\xde\xfd\x38\xe7\xde\x7b\xde\x15\xdd\ \x6e\x17\x00\x21\x04\x42\x08\x34\x4d\xc3\xb2\x2c\xff\xbd\x7a\x57\ \xe3\xe4\x55\x9a\xcd\x66\xc0\x09\x90\x4a\xa5\x00\xe8\x74\x3a\xb4\ \x5a\xad\x80\x6f\x15\xc4\x30\x0c\x84\x69\x9a\x01\x06\x99\x2c\xcf\ \x60\x30\x08\xad\x22\x91\x48\x00\x2c\x2b\x58\x67\x00\x3c\x49\x96\ \x4c\x26\x01\x28\x95\x4a\x7e\x8c\x61\x18\xbe\x1f\xcb\xb2\xe8\xf5\ \x7a\xd8\xb6\x4d\xbf\xdf\xf7\x01\xf6\x5f\xc7\xbc\x63\xa0\x8c\x34\ \x66\x32\x99\x75\x1f\x4a\xa3\xd1\x08\xf4\x25\x4f\x64\xeb\x26\x3f\ \xde\xc2\x95\xfb\x9f\xbe\xc8\xe4\x76\xbb\xcd\xbb\x67\x11\x62\xfa\ \x6d\x60\x6f\xd9\x42\xa1\x50\xe0\x98\xe5\x0e\xf0\x5c\x02\x6c\x24\ \xb7\xb9\x90\xba\xca\x87\x37\x2e\x49\xef\x80\x98\xae\xf2\xe7\xf0\ \x3b\x03\xb1\x8d\x12\x4f\x9f\x0c\x14\xa8\x00\x8f\x4d\xd3\x44\x55\ \x55\x4c\xd3\x44\xd7\x75\x66\x87\x7b\xb8\x7f\x67\x98\xfd\x23\xd4\ \xcd\xdf\x28\x91\x34\x9e\x7b\xc4\xfe\xd7\x2e\x37\xae\xa7\xb9\x7c\ \xeb\x09\x80\x50\x80\x2c\x50\xd5\x34\x2d\x50\xc1\xcf\xcf\x35\x46\ \xdf\xde\x73\xf0\x6b\x41\x7a\xeb\x0c\x00\x9b\x31\x41\xc7\x76\xb9\ \x14\xb9\xeb\xcf\x4b\x14\x8b\xc5\x80\x3c\xf9\x7c\x1e\xc0\xfb\xf8\ \x2a\x4a\xc7\x76\x79\xb8\xe3\x04\x64\x7d\xf1\xe0\x1c\xd7\x2e\x9e\ \xe5\xde\xcb\xf9\xb2\x83\xe1\x70\xc8\x68\x34\x62\x3c\x1e\x33\x99\ \x4c\x7c\x15\x76\x9f\x9e\x3f\xa5\x82\xf4\xed\x3c\xda\x38\x51\xa1\ \x5e\xaf\x87\xfd\x03\xc9\xec\x1b\xcb\xe5\xb2\x8c\x13\xb9\x5c\xce\ \xf3\x87\x38\x9d\x4e\x03\x2d\xc4\xe3\xf1\x00\x90\xe3\x38\xa1\x3f\ \x51\x51\x94\xa5\x8c\xb5\x5a\x2d\xb4\x02\x00\xcb\xb2\xa8\x56\xab\ \xa7\x76\x05\xa0\x52\xa9\x90\xcd\x66\x11\xb3\xd9\x2c\xc0\x10\x8d\ \x46\x99\xcf\xe7\xa1\xdb\xb8\x5e\x09\x80\x70\x1c\x87\xc5\x62\xf1\ \xdf\xb5\x0d\x4b\x06\xf8\x07\xf0\x1d\xb1\x3d\x6a\xe9\x1c\x20\x00\ \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x0f\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\ \x01\x3a\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x8c\x49\x44\ \x41\x54\x78\xda\xa5\x93\x3f\x4b\x9b\x51\x14\x87\x7f\xe7\xfe\x79\ \x93\x5c\xd3\x94\x1a\xad\x28\x2e\x9a\x21\x38\x04\x6a\xa1\x85\x40\ \x1d\x05\xc9\xa4\x11\xa1\x54\x27\x33\xb4\x73\xa5\x73\xe9\x26\x01\ \xc1\xc5\x45\x17\xe9\xd2\x51\xfc\x16\x0e\xfd\x02\x8d\x58\x11\x41\ \x13\x93\x9a\x28\xc6\x44\xdf\x7b\xdf\xa3\x59\x2d\x89\xd1\x3c\x70\ \xa6\x03\x0f\x9c\xdf\x39\x87\x98\x19\xbd\x20\xf0\x00\x9a\xa5\x4c\ \x64\x3e\xb2\xf4\x6c\x01\x18\x4b\x2b\x8b\x2b\x3f\x63\x1f\x63\x5b\ \xf4\x83\xe8\xc9\x82\x16\x66\xc0\x50\x6e\x21\xb7\x3c\x7c\x38\xfc\ \x9b\xa6\xa9\xff\x69\x82\x08\x50\xbc\x2c\xc2\x85\x9d\xc8\xce\x66\ \xdf\x24\x92\x89\x3f\x94\xa1\x49\xb4\x41\xe1\x21\x7d\x40\xb1\x5e\ \x04\xa8\xd5\x54\x22\xfd\x21\x1d\x37\x2f\xcc\x1e\xcd\xd1\x67\xde\ \xe1\xed\x47\x05\xc2\x48\x59\xb2\x25\x04\x41\xd0\x12\x40\x5b\x4d\ \xa3\xe3\xa3\x9e\x0e\xe9\x2d\xef\x93\x97\xf6\x93\xfe\x17\xfe\xce\ \xdc\x5e\x10\x16\xb2\x42\x15\xf8\xe4\x43\x3b\x0d\xe5\x2b\xc8\x86\ \x84\x8e\x68\x95\x1a\x4b\xe5\x0a\xc7\xfb\x6f\x89\xe8\x3d\xdf\xd3\ \x56\x50\xa0\x02\x6c\x60\x81\x06\x40\x17\x04\x51\x13\x70\x65\x07\ \x73\x63\x38\x30\x01\x75\x0c\x51\x7a\x5a\x88\x41\x01\xc4\x01\xbc\ \x04\x38\xca\x70\x21\x87\x57\x43\xfd\xd6\xc6\x83\x5f\xcd\x91\xe6\ \x3b\xe6\x4e\x23\x78\x42\x71\x4b\x70\x05\xc0\x02\xba\xae\xd9\x67\ \xe7\x57\x9b\xb5\xaf\xbc\xee\x36\x1e\x0d\x91\x34\xc9\xfb\x02\x08\ \x88\x22\xea\xc2\x0d\x73\x55\x69\x54\x67\x78\xed\x76\xaf\xab\x35\ \x4a\xad\x24\x9f\x5a\xbc\xae\x0f\x59\x7b\x14\x1c\x54\x4e\xce\xa6\ \x38\xcf\xe5\xae\x0f\x49\x28\x21\xc7\xfe\x25\x70\xfd\xf7\x76\xf7\ \xbc\x5a\x9e\xe0\x55\x2e\xa3\x03\xff\x7d\xa3\xca\x87\xe6\x40\x34\ \x68\xbf\x35\x37\xd1\x05\x3d\xbf\xf3\x1d\x7f\x4b\x95\x33\x4b\xa1\ \xe2\xc5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ " qt_resource_name = "\ \x00\x0e\ \x06\x0c\x0a\x07\ \x00\x61\ \x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2d\x00\x64\x00\x6f\x00\x77\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x07\ \x07\x01\x57\xa7\ \x00\x70\ \x00\x69\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0f\ \x0f\x22\x64\xc7\ \x00\x61\ \x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2d\x00\x72\x00\x69\x00\x67\x00\x68\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct = "\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x22\x00\x00\x00\x00\x00\x01\x00\x00\x02\x11\ \x00\x00\x00\x36\x00\x00\x00\x00\x00\x01\x00\x00\x04\x81\ " + def qInitResources(): QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + def qCleanupResources(): QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) qInitResources() diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py b/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py index 9e66a1cb5f..e1b34e07f3 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py @@ -1,193 +1,189 @@ #!/usr/bin/env python """ highlightedtextedit.py A PyQt custom widget example for Qt Designer. Copyright (C) 2006 David Boddie Copyright (C) 2005-2006 Trolltech ASA. All rights reserved. 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 St, Fifth Floor, Boston, MA 02110-1301 USA """ from PyQt5 import QtCore, QtGui - - class PythonHighlighter(QtGui.QSyntaxHighlighter): keywords = ( "and", "del", "for", "is", "raise", "assert", "elif", "from", "lambda", "return", "break", "else", "global", "not", "try", "class", "except", "if", "or", "while", "continue", "exec", "import", "pass", "yield", "def", "finally", "in", "print" - ) - + ) + def __init__(self, edit): document = edit.document() QtGui.QSyntaxHighlighter.__init__(self, document) base_format = QtGui.QTextCharFormat() base_format.setFont(edit.font()) - + self.base_format = base_format self.document = document - + self.updateHighlighter(base_format.font()) - + def highlightBlock(self, text): - + self.setCurrentBlockState(0) - + if text.trimmed().isEmpty(): self.setFormat(0, len(text), self.empty_format) return - + self.setFormat(0, len(text), self.base_format) - + startIndex = 0 if self.previousBlockState() != 1: startIndex = self.multiLineStringBegin.indexIn(text) - + if startIndex > -1: self.highlightRules(text, 0, startIndex) else: self.highlightRules(text, 0, len(text)) - + while startIndex >= 0: - + endIndex = self.multiLineStringEnd.indexIn(text, - startIndex + len(self.multiLineStringBegin.pattern())) + startIndex + len(self.multiLineStringBegin.pattern())) if endIndex == -1: self.setCurrentBlockState(1) commentLength = text.length() - startIndex else: commentLength = endIndex - startIndex + \ - self.multiLineStringEnd.matchedLength() + self.multiLineStringEnd.matchedLength() self.highlightRules(text, endIndex, len(text)) - + self.setFormat(startIndex, commentLength, self.multiLineStringFormat) startIndex = self.multiLineStringBegin.indexIn(text, - startIndex + commentLength) - + startIndex + commentLength) + def highlightRules(self, text, start, finish): - + for expression, format in self.rules: - + index = expression.indexIn(text, start) while index >= start and index < finish: length = expression.matchedLength() self.setFormat(index, min(length, finish - index), format) index = expression.indexIn(text, index + length) - + def updateFonts(self, font): - + self.base_format.setFont(font) self.empty_format = QtGui.QTextCharFormat(self.base_format) - #self.empty_format.setFontPointSize(font.pointSize()/4.0) - + # self.empty_format.setFontPointSize(font.pointSize()/4.0) + self.keywordFormat = QtGui.QTextCharFormat(self.base_format) self.keywordFormat.setForeground(QtCore.Qt.darkBlue) self.keywordFormat.setFontWeight(QtGui.QFont.Bold) self.callableFormat = QtGui.QTextCharFormat(self.base_format) self.callableFormat.setForeground(QtCore.Qt.darkBlue) self.magicFormat = QtGui.QTextCharFormat(self.base_format) - self.magicFormat.setForeground(QtGui.QColor(224,128,0)) + self.magicFormat.setForeground(QtGui.QColor(224, 128, 0)) self.qtFormat = QtGui.QTextCharFormat(self.base_format) self.qtFormat.setForeground(QtCore.Qt.blue) self.qtFormat.setFontWeight(QtGui.QFont.Bold) self.selfFormat = QtGui.QTextCharFormat(self.base_format) self.selfFormat.setForeground(QtCore.Qt.red) - #self.selfFormat.setFontItalic(True) + # self.selfFormat.setFontItalic(True) self.singleLineCommentFormat = QtGui.QTextCharFormat(self.base_format) self.singleLineCommentFormat.setForeground(QtCore.Qt.darkGreen) self.multiLineStringFormat = QtGui.QTextCharFormat(self.base_format) self.multiLineStringFormat.setBackground( - QtGui.QBrush(QtGui.QColor(127,127,255))) + QtGui.QBrush(QtGui.QColor(127, 127, 255))) self.quotationFormat1 = QtGui.QTextCharFormat(self.base_format) self.quotationFormat1.setForeground(QtCore.Qt.blue) self.quotationFormat2 = QtGui.QTextCharFormat(self.base_format) self.quotationFormat2.setForeground(QtCore.Qt.blue) - + def updateRules(self): - + self.rules = [] - self.rules += map(lambda s: (QtCore.QRegExp(r"\b"+s+r"\b"), + self.rules += map(lambda s: (QtCore.QRegExp(r"\b" + s + r"\b"), self.keywordFormat), self.keywords) - + self.rules.append((QtCore.QRegExp(r"\b[A-Za-z_]+\(.*\)"), self.callableFormat)) self.rules.append((QtCore.QRegExp(r"\b__[a-z]+__\b"), self.magicFormat)) self.rules.append((QtCore.QRegExp(r"\bself\b"), self.selfFormat)) self.rules.append((QtCore.QRegExp(r"\bQ([A-Z][a-z]*)+\b"), self.qtFormat)) - + self.rules.append((QtCore.QRegExp(r"#[^\n]*"), self.singleLineCommentFormat)) - + self.multiLineStringBegin = QtCore.QRegExp(r'\"\"\"') self.multiLineStringEnd = QtCore.QRegExp(r'\"\"\"') - + self.rules.append((QtCore.QRegExp(r'\"[^\n]*\"'), self.quotationFormat1)) self.rules.append((QtCore.QRegExp(r"'[^\n]*'"), self.quotationFormat2)) - + def updateHighlighter(self, font): - + self.updateFonts(font) self.updateRules() self.setDocument(self.document) class QtQmlHighlighter(PythonHighlighter): keywords = """" break for throw case function try catch if typeof continue in var default instanceof void delete new undefined do return while else switch with finally this """.split() + \ - ['NaN', 'Infinity', 'undefined', 'print', 'parseInt', - 'parseFloat', 'isNaN', 'isFinite', 'decodeURI', - 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', - 'escape', 'unescape', 'version', 'gc', 'Object', - 'Function', 'Number', 'Boolean', 'String', 'Date', 'Array', - 'RegExp', 'Error', 'EvalError','RangeError', 'ReferenceError', - 'SyntaxError', 'TypeError', 'URIError', 'eval', 'Math', + ['NaN', 'Infinity', 'undefined', 'print', 'parseInt', + 'parseFloat', 'isNaN', 'isFinite', 'decodeURI', + 'decodeURIComponent', 'encodeURI', 'encodeURIComponent', + 'escape', 'unescape', 'version', 'gc', 'Object', + 'Function', 'Number', 'Boolean', 'String', 'Date', 'Array', + 'RegExp', 'Error', 'EvalError', 'RangeError', 'ReferenceError', + 'SyntaxError', 'TypeError', 'URIError', 'eval', 'Math', 'Enumeration', 'Variant', 'QObject', 'QMetaObject'] - - def __init__(self, edit): - PythonHighlighter.__init__(self, edit) + def __init__(self, edit): + PythonHighlighter.__init__(self, edit) def updateRules(self): - + self.rules = [] - self.rules += map(lambda s: (QtCore.QRegExp(r"\b"+s+r"\b"), + self.rules += map(lambda s: (QtCore.QRegExp(r"\b" + s + r"\b"), self.keywordFormat), self.keywords) self.rules.append((QtCore.QRegExp(r"\b[A-Za-z_]+\(.*\)"), self.callableFormat)) - #self.rules.append((QtCore.QRegExp(r"\b__[a-z]+__\b"), self.magicFormat)) + # self.rules.append((QtCore.QRegExp(r"\b__[a-z]+__\b"), self.magicFormat)) self.rules.append((QtCore.QRegExp(r"\bthis\b"), self.selfFormat)) self.rules.append((QtCore.QRegExp(r"\bQ([A-Z][a-z]*)+\b"), self.qtFormat)) - + self.rules.append((QtCore.QRegExp(r"//[^\n]*"), self.singleLineCommentFormat)) - + # XXX quick hack to support QtQml syntax self.multiLineStringBegin = QtCore.QRegExp(r'/\*') self.multiLineStringEnd = QtCore.QRegExp(r'\*/') self.multiLineStringFormat = self.singleLineCommentFormat self.rules.append((QtCore.QRegExp(r'\"[^\n]*\"'), self.quotationFormat1)) self.rules.append((QtCore.QRegExp(r"'[^\n]*'"), self.quotationFormat2)) - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py b/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py index bd635ddfef..593773bbdb 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py @@ -1,242 +1,244 @@ import re from rope.base import codeanalyze class TextIndenter(object): + """A class for formatting texts""" def __init__(self, editor, indents=4): self.editor = editor self.indents = indents self.line_editor = editor.line_editor() def correct_indentation(self, lineno): """Correct the indentation of a line""" def deindent(self, lineno): """Deindent the a line""" current_indents = self._count_line_indents(lineno) new_indents = max(0, current_indents - self.indents) self._set_line_indents(lineno, new_indents) def indent(self, lineno): """Indent a line""" current_indents = self._count_line_indents(lineno) new_indents = current_indents + self.indents self._set_line_indents(lineno, new_indents) def entering_new_line(self, lineno): """Indent a line Uses `correct_indentation` and last line indents """ last_line = "" if lineno > 1: last_line = self.line_editor.get_line(lineno - 1) if last_line.strip() == '': self._set_line_indents(lineno, len(last_line)) else: self.correct_indentation(lineno) def insert_tab(self, index): """Inserts a tab in the given index""" self.editor.insert(index, ' ' * self.indents) def _set_line_indents(self, lineno, indents): old_indents = self._count_line_indents(lineno) indent_diffs = indents - old_indents self.line_editor.indent_line(lineno, indent_diffs) def _count_line_indents(self, lineno): contents = self.line_editor.get_line(lineno) result = 0 for x in contents: if x == ' ': result += 1 elif x == '\t': result += 8 else: break return result class NormalIndenter(TextIndenter): def __init__(self, editor): super(NormalIndenter, self).__init__(editor) def correct_indentation(self, lineno): prev_indents = 0 if lineno > 1: prev_indents = self._count_line_indents(lineno - 1) self._set_line_indents(lineno, prev_indents) class PythonCodeIndenter(TextIndenter): def __init__(self, editor, indents=4): super(PythonCodeIndenter, self).__init__(editor, indents) def _last_non_blank(self, lineno): current_line = lineno - 1 while current_line != 1 and \ - self.line_editor.get_line(current_line).strip() == '': + self.line_editor.get_line(current_line).strip() == '': current_line -= 1 return current_line def _get_correct_indentation(self, lineno): if lineno == 1: return 0 new_indent = self._get_base_indentation(lineno) prev_lineno = self._last_non_blank(lineno) prev_line = self.line_editor.get_line(prev_lineno) if prev_lineno == lineno or prev_line.strip() == '': new_indent = 0 current_line = self.line_editor.get_line(lineno) new_indent += self._indents_caused_by_current_stmt(current_line) return new_indent def _get_base_indentation(self, lineno): range_finder = _StatementRangeFinder( self.line_editor, self._last_non_blank(lineno)) start = range_finder.get_statement_start() if not range_finder.is_line_continued(): changes = self._indents_caused_by_prev_stmt( (start, self._last_non_blank(lineno))) return self._count_line_indents(start) + changes if range_finder.last_open_parens(): open_parens = range_finder.last_open_parens() parens_line = self.line_editor.get_line(open_parens[0]) if parens_line[open_parens[1] + 1:].strip() == '': if len(range_finder.open_parens) > 1: return range_finder.open_parens[-2][1] + 1 else: return self._count_line_indents(start) + self.indents return range_finder.last_open_parens()[1] + 1 start_line = self.line_editor.get_line(start) if start == lineno - 1: try: equals_index = start_line.index(' = ') + 1 if start_line[equals_index + 1:].strip() == '\\': return self._count_line_indents(start) + self.indents return equals_index + 2 except ValueError: match = re.search(r'(\b )|(\.)', start_line) if match: return match.start() + 1 else: return len(start_line) + 1 else: return self._count_line_indents(self._last_non_blank(lineno)) def _indents_caused_by_prev_stmt(self, stmt_range): first_line = self.line_editor.get_line(stmt_range[0]) last_line = self.line_editor.get_line(stmt_range[1]) new_indent = 0 if self._strip(last_line).endswith(':'): new_indent += self.indents if self._startswith(first_line, ('return', 'raise', 'pass', 'break', 'continue')): new_indent -= self.indents return new_indent def _startswith(self, line, tokens): line = self._strip(line) for token in tokens: if line == token or line.startswith(token + ' '): return True def _strip(self, line): try: numsign = line.rindex('#') comment = line[numsign:] if '\'' not in comment and '\"' not in comment: line = line[:numsign] except ValueError: pass return line.strip() def _indents_caused_by_current_stmt(self, current_line): new_indent = 0 if self._strip(current_line) == 'else:': new_indent -= self.indents if self._strip(current_line) == 'finally:': new_indent -= self.indents if self._startswith(current_line, ('elif',)): new_indent -= self.indents if self._startswith(current_line, ('except',)) and \ self._strip(current_line).endswith(':'): new_indent -= self.indents return new_indent def correct_indentation(self, lineno): """Correct the indentation of the line containing the given index""" self._set_line_indents(lineno, self._get_correct_indentation(lineno)) class _StatementRangeFinder(object): + """A method object for finding the range of a statement""" def __init__(self, lines, lineno): self.lines = lines self.lineno = lineno self.in_string = '' self.open_count = 0 self.explicit_continuation = False self.open_parens = [] self._analyze() def _analyze_line(self, lineno): current_line = self.lines.get_line(lineno) for i, char in enumerate(current_line): if char in '\'"': if self.in_string == '': self.in_string = char if char * 3 == current_line[i:i + 3]: self.in_string = char * 3 elif self.in_string == current_line[i:i + len(self.in_string)] and \ - not (i > 0 and current_line[i - 1] == '\\' and - not (i > 1 and current_line[i - 2:i] == '\\\\')): + not (i > 0 and current_line[i - 1] == '\\' and + not (i > 1 and current_line[i - 2:i] == '\\\\')): self.in_string = '' if self.in_string != '': continue if char == '#': break if char in '([{': self.open_count += 1 self.open_parens.append((lineno, i)) if char in ')]}': self.open_count -= 1 if self.open_parens: self.open_parens.pop() if current_line and char != '#' and current_line.endswith('\\'): self.explicit_continuation = True else: self.explicit_continuation = False def _analyze(self): last_statement = 1 block_start = codeanalyze.get_block_start(self.lines, self.lineno) for current_line_number in range(block_start, self.lineno + 1): if not self.explicit_continuation and \ self.open_count == 0 and self.in_string == '': last_statement = current_line_number self._analyze_line(current_line_number) self.statement_start = last_statement def get_statement_start(self): return self.statement_start def last_open_parens(self): if not self.open_parens: return None return self.open_parens[-1] def is_line_continued(self): return self.open_count != 0 or self.explicit_continuation def get_line_indents(self, line_number): return self._count_line_indents(self.lines.get_line(line_number)) diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py index 8912a8b7e9..47f9bf8e80 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py @@ -1,220 +1,207 @@ from PyQt5.QtCore import pyqtSlot, Qt from PyQt5.QtGui import QCloseEvent from PyQt5.QtWidgets import (QApplication, QFileDialog, QMainWindow, QMessageBox, QSplitter, QTabWidget) from widget import PythonEditorWidget, QtQmlEditorWidget, SaveDialog from console import PythonConsole, QtQmlConsole from mainwindow_ui import Ui_ScriptEditor import traceback import os template_py = """\ # -*- coding: utf-8 -*- from __future__ import with_statement """ -class EditorMainWindow(QMainWindow): +class EditorMainWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.ui = Ui_ScriptEditor() self.ui.setupUi(self) - #self.ui.actionExit.triggered.connect(self.exit) + # self.ui.actionExit.triggered.connect(self.exit) self.splitter = QSplitter(Qt.Vertical, self) self.setCentralWidget(self.splitter) self.edit_tab = QTabWidget(self.splitter) self.console_tab = QTabWidget(self.splitter) self.py_console = PythonConsole(self.console_tab) self.console_tab.addTab(self.py_console, "&Python console") self.js_console = QtQmlConsole(self.console_tab) self.console_tab.addTab(self.js_console, "&QtQml console") self.editors = [] self.on_actionNewPython_triggered() @pyqtSlot() def closeEvent(self, event): - while(self.editors.__len__()): - edit = self.edit_tab.currentWidget() + while(self.editors.__len__()): + edit = self.edit_tab.currentWidget() if edit: - if(edit.isModified()): - saveBox = SaveDialog("You have unsaved script. Save it now?") - prompt = saveBox.exec_() - if(prompt == QMessageBox.Save): - event.ignore() - self.save(True) - elif(prompt == QMessageBox.Cancel): - event.ignore() - return - elif(prompt == QMessageBox.Discard): - event.accept() + if(edit.isModified()): + saveBox = SaveDialog("You have unsaved script. Save it now?") + prompt = saveBox.exec_() + if(prompt == QMessageBox.Save): + event.ignore() + self.save(True) + elif(prompt == QMessageBox.Cancel): + event.ignore() + return + elif(prompt == QMessageBox.Discard): + event.accept() i = self.edit_tab.indexOf(edit) self.edit_tab.removeTab(i) self.editors.remove(edit) - event.accept() - - + event.accept() @pyqtSlot() def on_actionExit_triggered(self): - while(self.editors.__len__()): - edit = self.edit_tab.currentWidget() + while(self.editors.__len__()): + edit = self.edit_tab.currentWidget() if edit: - if(edit.isModified()): - saveBox = SaveDialog("You have unsaved script. Save it now?") - prompt = saveBox.exec_() - if(prompt == QMessageBox.Save): - self.save(True) - elif(prompt == QMessageBox.Cancel): - return - elif(prompt == QMessageBox.Discard): - pass - i = self.edit_tab.indexOf(edit) + if(edit.isModified()): + saveBox = SaveDialog("You have unsaved script. Save it now?") + prompt = saveBox.exec_() + if(prompt == QMessageBox.Save): + self.save(True) + elif(prompt == QMessageBox.Cancel): + return + elif(prompt == QMessageBox.Discard): + pass + i = self.edit_tab.indexOf(edit) self.edit_tab.removeTab(i) self.editors.remove(edit) - self.close() + self.close() @pyqtSlot() def on_actionNewPython_triggered(self): pyedit = PythonEditorWidget(self.edit_tab) pyedit.setPlainText(template_py) self.edit_tab.addTab(pyedit, "Python") self.edit_tab.setCurrentWidget(pyedit) self.editors.append(pyedit) self.py_console.attach() self.console_tab.setCurrentIndex(0) pyedit.setFocus() pyedit.view.setFocus() - @pyqtSlot() def on_actionNewQtQml_triggered(self): jsedit = QtQmlEditorWidget(self.edit_tab) self.edit_tab.addTab(jsedit, "QtQml") self.edit_tab.setCurrentWidget(jsedit) self.editors.append(jsedit) self.js_console.attach() self.console_tab.setCurrentIndex(1) - @pyqtSlot() def on_actionClose_triggered(self): edit = self.edit_tab.currentWidget() if edit: - if(edit.isModified()): - saveBox = SaveDialog("Do you want to save this Script?") - prompt = saveBox.exec_() - if(prompt == QMessageBox.Save): - self.save(True) - elif(prompt == QMessageBox.Cancel): - return - elif(prompt == QMessageBox.Discard): - pass + if(edit.isModified()): + saveBox = SaveDialog("Do you want to save this Script?") + prompt = saveBox.exec_() + if(prompt == QMessageBox.Save): + self.save(True) + elif(prompt == QMessageBox.Cancel): + return + elif(prompt == QMessageBox.Discard): + pass i = self.edit_tab.indexOf(edit) self.edit_tab.removeTab(i) self.editors.remove(edit) - @pyqtSlot() def on_actionClear_triggered(self): - #edit = self.edit_tab.currentWidget() - #edit.setPlainText(template_py) - self.py_console.clear() - + # edit = self.edit_tab.currentWidget() + # edit.setPlainText(template_py) + self.py_console.clear() @pyqtSlot() def on_actionSave_As_triggered(self): - self.save() - + self.save() @pyqtSlot() def on_actionSave_triggered(self): - self.save(True) + self.save(True) - - #Path of the script file in each tab will be stored in tabToolTip - def save(self, Update = False): + # Path of the script file in each tab will be stored in tabToolTip + def save(self, Update=False): edit = self.edit_tab.currentWidget() - contents = str(edit.toPlainText()) - if((Update == False) or (self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python") ): - #Save in its first invocation and Save As will enter - filename = QFileDialog.getSaveFileName(self, "Save File", "", "*.spy") - fil = open(filename , 'w') - if(filename and self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python"): - #Script hasn't been saved before and user specifies a valid filename - self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename+'.spy') - self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename+'.spy'))) - else: - #filename = self.edit_tab.tabText(self.edit_tab.currentIndex()) - filename = self.edit_tab.tabToolTip(self.edit_tab.currentIndex()) - fil = open( filename , 'w') - fil.write(contents) - fil.close() - edit.setModified(False) - + contents = str(edit.toPlainText()) + if((Update == False) or (self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python")): + # Save in its first invocation and Save As will enter + filename = QFileDialog.getSaveFileName(self, "Save File", "", "*.spy") + fil = open(filename, 'w') + if(filename and self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python"): + # Script hasn't been saved before and user specifies a valid filename + self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename + '.spy') + self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename + '.spy'))) + else: + # filename = self.edit_tab.tabText(self.edit_tab.currentIndex()) + filename = self.edit_tab.tabToolTip(self.edit_tab.currentIndex()) + fil = open(filename, 'w') + fil.write(contents) + fil.close() + edit.setModified(False) @pyqtSlot() def on_actionOpen_triggered(self): - filename = QFileDialog.getOpenFileName(self,"Open File","","*.spy") - try: - fil = open(filename , 'r') - except IOError: - return - code = fil.read() - edit = self.edit_tab.currentWidget() - self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename))) - self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename) - edit.setPlainText(code) - fil.close() - + filename = QFileDialog.getOpenFileName(self, "Open File", "", "*.spy") + try: + fil = open(filename, 'r') + except IOError: + return + code = fil.read() + edit = self.edit_tab.currentWidget() + self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename))) + self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename) + edit.setPlainText(code) + fil.close() @pyqtSlot() def on_actionRun_triggered(self): self.run() - @pyqtSlot() def on_actionRunConsole_triggered(self): self.run(True) - def run(self, console=False): edit = self.edit_tab.currentWidget() code = str(edit.toPlainText()) if isinstance(edit, PythonEditorWidget): self.py_console.attach() self.console_tab.setCurrentIndex(0) if console: namespace = self.py_console.namespace else: namespace = {} try: - exec code in namespace + exec(code, namespace) except Exception as e: traceback.print_exc() try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() - except: pass + except: + pass else: self.js_console.attach() self.console_tab.setCurrentIndex(1) if console: self.js_console.inter.execute(code) else: self.js_console.inter.execute_code(code) - - if __name__ == "__main__": import sys app = QApplication(sys.argv) win = EditorMainWindow() win.resize(640, 480) win.show() app.exec_() diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py index 6e52bcf3f7..1fff05218a 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py @@ -1,97 +1,98 @@ # -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'mainwindow.ui' # # Created: Fri Aug 15 04:25:57 2014 # by: PyQt5 UI code generator 5.3.1 # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_ScriptEditor(object): + def setupUi(self, ScriptEditor): ScriptEditor.setObjectName("ScriptEditor") ScriptEditor.resize(624, 449) self.centralwidget = QtWidgets.QWidget(ScriptEditor) self.centralwidget.setObjectName("centralwidget") ScriptEditor.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(ScriptEditor) self.menubar.setGeometry(QtCore.QRect(0, 0, 624, 25)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") self.menu_New = QtWidgets.QMenu(self.menuFile) self.menu_New.setObjectName("menu_New") self.menuRun = QtWidgets.QMenu(self.menubar) self.menuRun.setObjectName("menuRun") ScriptEditor.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(ScriptEditor) self.statusbar.setObjectName("statusbar") ScriptEditor.setStatusBar(self.statusbar) self.actionClose = QtWidgets.QAction(ScriptEditor) self.actionClose.setObjectName("actionClose") self.actionExit = QtWidgets.QAction(ScriptEditor) self.actionExit.setObjectName("actionExit") self.actionRun = QtWidgets.QAction(ScriptEditor) self.actionRun.setObjectName("actionRun") self.actionRunConsole = QtWidgets.QAction(ScriptEditor) self.actionRunConsole.setObjectName("actionRunConsole") self.actionNewPython = QtWidgets.QAction(ScriptEditor) self.actionNewPython.setObjectName("actionNewPython") self.actionNewQtQml = QtWidgets.QAction(ScriptEditor) self.actionNewQtQml.setObjectName("actionNewQtQml") self.actionClear = QtWidgets.QAction(ScriptEditor) self.actionClear.setObjectName("actionClear") self.actionSave_As = QtWidgets.QAction(ScriptEditor) self.actionSave_As.setObjectName("actionSave_As") self.actionOpen = QtWidgets.QAction(ScriptEditor) self.actionOpen.setObjectName("actionOpen") self.actionSave = QtWidgets.QAction(ScriptEditor) self.actionSave.setObjectName("actionSave") self.menu_New.addAction(self.actionNewPython) self.menu_New.addAction(self.actionNewQtQml) self.menuFile.addAction(self.menu_New.menuAction()) self.menuFile.addAction(self.actionOpen) self.menuFile.addAction(self.actionSave) self.menuFile.addAction(self.actionSave_As) self.menuFile.addAction(self.actionClose) self.menuFile.addSeparator() self.menuFile.addAction(self.actionExit) self.menuRun.addAction(self.actionRun) self.menuRun.addAction(self.actionRunConsole) self.menuRun.addAction(self.actionClear) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuRun.menuAction()) self.retranslateUi(ScriptEditor) QtCore.QMetaObject.connectSlotsByName(ScriptEditor) def retranslateUi(self, ScriptEditor): _translate = QtCore.QCoreApplication.translate ScriptEditor.setWindowTitle(_translate("ScriptEditor", "Script Editor")) self.menuFile.setTitle(_translate("ScriptEditor", "&File")) self.menu_New.setTitle(_translate("ScriptEditor", "&New")) self.menuRun.setTitle(_translate("ScriptEditor", "&Run")) self.actionClose.setText(_translate("ScriptEditor", "&Close")) self.actionClose.setShortcut(_translate("ScriptEditor", "Ctrl+W")) self.actionExit.setText(_translate("ScriptEditor", "&Exit")) self.actionRun.setText(_translate("ScriptEditor", "&Run")) self.actionRun.setShortcut(_translate("ScriptEditor", "Ctrl+R")) self.actionRunConsole.setText(_translate("ScriptEditor", "Run script in &console")) self.actionRunConsole.setShortcut(_translate("ScriptEditor", "Ctrl+C")) self.actionNewPython.setText(_translate("ScriptEditor", "Python")) self.actionNewPython.setShortcut(_translate("ScriptEditor", "Ctrl+N")) self.actionNewQtQml.setText(_translate("ScriptEditor", "QtQml")) self.actionClear.setText(_translate("ScriptEditor", "Clear")) self.actionClear.setToolTip(_translate("ScriptEditor", "Clear The Console")) self.actionSave_As.setText(_translate("ScriptEditor", "Save &As")) self.actionSave_As.setToolTip(_translate("ScriptEditor", "Save the script")) self.actionSave_As.setShortcut(_translate("ScriptEditor", "Ctrl+A")) self.actionOpen.setText(_translate("ScriptEditor", "&Open")) self.actionOpen.setToolTip(_translate("ScriptEditor", "Open a script")) self.actionOpen.setShortcut(_translate("ScriptEditor", "Ctrl+O")) self.actionSave.setText(_translate("ScriptEditor", "&Save")) self.actionSave.setToolTip(_translate("ScriptEditor", "Save the current script")) self.actionSave.setShortcut(_translate("ScriptEditor", "Ctrl+S")) - diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py b/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py index 3b3878e460..c331fe6941 100644 --- a/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py +++ b/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py @@ -1,463 +1,415 @@ # -*- coding: utf-8 -*- import re import sys import os # I put the rope package into a ZIP-file to save space # and to keep everything clear path = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.join(path, "rope.zip")) from rope.base.project import get_no_project from rope.contrib.codeassist import code_assist from PyQt5.QtCore import QCoreApplication, QLine, Qt from PyQt5.QtGui import (QBrush, QColor, QFont, QKeyEvent, QTextBlockUserData, QTextCursor, QPainter, QPalette, QPen) from PyQt5.QtWidgets import (QApplication, QFrame, QHBoxLayout, QMessageBox, QPlainTextEdit, QVBoxLayout, QWidget) from indenter import PythonCodeIndenter from assist import AutoComplete, CallTip from highlighter import PythonHighlighter, QtQmlHighlighter - - - - - - - class EditorBlockData(QTextBlockUserData): - + def __init__(self): QTextBlockUserData.__init__(self) - - - - class RopeEditorWrapper(object): - def __init__(self, editview): self.editview = editview - def length(self): return self.editview.length() - def line_editor(self): return self - def _get_block(self, line_no=None): cursor = self.editview.textCursor() row = cursor.blockNumber() if line_no == None: line_no = row block = cursor.block() while row > line_no: block = block.previous() row -= 1 while row < line_no: block = block.next() row += 1 return block - def get_line(self, line_no=None): return unicode(self._get_block(line_no).text()) - def indent_line(self, line_no, indent_length): block = self._get_block(line_no) cursor = QTextCursor(block) cursor.joinPreviousEditBlock() cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) - if indent_length < 0: + if indent_length < 0: for i in range(-indent_length): cursor.deleteChar() else: cursor.insertText(" " * indent_length) if indent_length: cursor.movePosition( QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) line = unicode(cursor.block().text()) if len(line) and line[0] == " ": cursor.movePosition( QTextCursor.NextWord, QTextCursor.MoveAnchor) self.editview.setTextCursor(cursor) cursor.endEditBlock() - - class EditorView(QPlainTextEdit): - def __init__(self, parent=None, text=None, EditorHighlighterClass=PythonHighlighter, indenter=PythonCodeIndenter): QPlainTextEdit.__init__(self, parent) self.setFrameStyle(QFrame.NoFrame) self.setTabStopWidth(4) self.setLineWrapMode(QPlainTextEdit.NoWrap) font = QFont() font.setFamily("lucidasanstypewriter") font.setFixedPitch(True) font.setPointSize(10) self.setFont(font) self.highlighter = EditorHighlighterClass(self) if text: self.setPlainText(text) self.frame_style = self.frameStyle() self.draw_line = True - self.print_width = self.fontMetrics().width("x"*78) + self.print_width = self.fontMetrics().width("x" * 78) self.line_pen = QPen(QColor("lightgrey")) self.last_row = self.last_col = -1 self.last_block = None self.highlight_line = True self.highlight_color = self.palette().highlight().color().light(175) self.highlight_brush = QBrush(QColor(self.highlight_color)) self.cursorPositionChanged.connect(self.onCursorPositionChanged) self.indenter = indenter(RopeEditorWrapper(self)) # True if you want to catch Emacs keys in actions self.disable_shortcuts = False self.prj = get_no_project() self.prj.root = None self.calltip = CallTip(self) self.autocomplete = AutoComplete(self) - def closeEvent(self, event): self.calltip.close() self.autocomplete.close() - def isModified(self): return self.document().isModified() - def setModified(self, flag): self.document().setModified(flag) - def length(self): return self.document().blockCount() - def goto(self, line_no): cursor = self.textCursor() block = cursor.block() row = cursor.blockNumber() while row > line_no: block = block.previous() row -= 1 while row < line_no: block = block.next() row += 1 cursor = QTextCursor(block) self.setTextCursor(cursor) - def move_start_of_doc(self): cursor = self.textCursor() cursor.setPosition(0) self.setTextCursor(cursor) - def move_end_of_doc(self): cursor = self.textCursor() block = cursor.block() while block.isValid(): last_block = block block = block.next() cursor.setPosition(last_block.position()) cursor.movePosition( - QTextCursor.EndOfBlock, QTextCursor.MoveAnchor) + QTextCursor.EndOfBlock, QTextCursor.MoveAnchor) self.setTextCursor(cursor) - def move_start_of_row(self): cursor = self.textCursor() cursor.movePosition( - QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) + QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) self.setTextCursor(cursor) - def move_end_of_row(self): cursor = self.textCursor() cursor.movePosition( - QTextCursor.EndOfBlock, QTextCursor.MoveAnchor) + QTextCursor.EndOfBlock, QTextCursor.MoveAnchor) self.setTextCursor(cursor) - def highline(self, cursor): self.viewport().update() - def onCursorPositionChanged(self): cursor = self.textCursor() row, col = cursor.blockNumber(), cursor.columnNumber() if self.last_row != row: self.last_row = row if self.highlight_line: self.highline(cursor) if col != self.last_col: self.last_col = col self.cursorPositionChanged.emit(row, col) - def _create_line(self): x = self.print_width self.line = QLine(x, 0, x, self.height()) - def resizeEvent(self, event): self._create_line() QPlainTextEdit.resizeEvent(self, event) - def paintEvent(self, event): painter = QPainter(self.viewport()) if self.highlight_line: r = self.cursorRect() r.setX(0) r.setWidth(self.viewport().width()) painter.fillRect(r, self.highlight_brush) if self.draw_line: painter.setPen(self.line_pen) painter.drawLine(self.line) painter.end() QPlainTextEdit.paintEvent(self, event) - def setDocument(self, document): QPlainTextEdit.setDocument(self, document) self.highlighter.setDocument(document) - def indent(self): self.indenter.correct_indentation(self.textCursor().blockNumber()) - def tab_pressed(self): self.indent() - def dedent(self): self.indenter.deindent(self.textCursor().blockNumber()) - def backtab_pressed(self): self.dedent() return True - def backspace_pressed(self): cursor = self.textCursor() text = unicode(cursor.block().text()) col = cursor.columnNumber() if col > 0 and text[:col].strip() == "": self.indenter.deindent(self.textCursor().blockNumber()) return True - def autocomplete_pressed(self): try: items = code_assist(self.prj, unicode(self.toPlainText()), self.textCursor().position()) except Exception as e: items = [] if items: self.autocomplete.setItems(items) self.autocomplete.show() - def after_return_pressed(self): self.indenter.entering_new_line(self.textCursor().blockNumber()) - def keyPressEvent(self, event): if self.autocomplete.active: if self.autocomplete.keyPressEvent(event): return elif self.calltip.active: if self.calltip.keyPressEvent(event): return m = event.modifiers() k = event.key() t = event.text() - # Disable some shortcuts + # Disable some shortcuts if self.disable_shortcuts and \ m & Qt.ControlModifier and k in [Qt.Key_A, Qt.Key_R, Qt.Key_C, Qt.Key_K, Qt.Key_X, Qt.Key_V, Qt.Key_Y, Qt.Key_Z]: new_ev = QKeyEvent(event.type(), k, m, t) event.ignore() QCoreApplication.postEvent(self.parent(), new_ev) return elif k == Qt.Key_Tab: if self.tab_pressed(): return elif k == Qt.Key_Backtab: if self.backtab_pressed(): return elif k == Qt.Key_Backspace: if self.backspace_pressed(): return elif k == Qt.Key_Period or \ - (k == Qt.Key_Space and event.modifiers() == Qt.ControlModifier): + (k == Qt.Key_Space and event.modifiers() == Qt.ControlModifier): QPlainTextEdit.keyPressEvent(self, event) self.autocomplete_pressed() return elif k in [Qt.Key_ParenLeft, Qt.Key_BraceLeft, Qt.Key_BracketLeft]: QPlainTextEdit.keyPressEvent(self, event) self.paren_opened(k) return QPlainTextEdit.keyPressEvent(self, event) if k == Qt.Key_Return or k == Qt.Key_Enter: self.after_return_pressed() - - def paren_opened(self, key): close_char = { Qt.Key_ParenLeft: ")", - Qt.Key_BraceLeft:" }", - Qt.Key_BracketLeft:"]" - } + Qt.Key_BraceLeft: " }", + Qt.Key_BracketLeft: "]" + } cursor = self.textCursor() cursor.insertText(close_char[key]) - cursor.setPosition(cursor.position()-1) + cursor.setPosition(cursor.position() - 1) self.setTextCursor(cursor) - class EditorSidebar(QWidget): - def __init__(self, editor): QWidget.__init__(self, editor) self.editor = editor self.view = editor.view self.doc = editor.view.document self.fm = self.fontMetrics() self.show_line_numbers = True self.setAutoFillBackground(True) - #bg = editor.view.palette().base().color() - #pal = QPalette() - #pal.setColor(self.backgroundRole(), bg) - #self.setPalette(pal) + # bg = editor.view.palette().base().color() + # pal = QPalette() + # pal.setColor(self.backgroundRole(), bg) + # self.setPalette(pal) self.setBackgroundRole(QPalette.Base) self.doc().documentLayout().update.connect(self.update) self.view.verticalScrollBar().valueChanged.connect(self.update) self.first_row = self.last_row = self.rows = 0 width = 10 if self.show_line_numbers: width += self.fm.width("00000") self.setFixedWidth(width) - - def paintEvent(self, event): QWidget.paintEvent(self, event) p = QPainter(self) view = self.view first = view.firstVisibleBlock() first_row = first.blockNumber() block = first row = first_row y = view.contentOffset().y() pageBottom = max( - view.height(), + view.height(), view.verticalScrollBar().value() + view.viewport().height()) fm = self.fm - w = self.width() - 8 + w = self.width() - 8 while block.isValid(): txt = str(row).rjust(5) y = view.blockBoundingGeometry(block).y() if y >= pageBottom: break x = w - fm.width(txt) p.drawText(x, y, txt) row += 1 block = block.next() p.end() - class EditorWidget(QFrame): - def __init__(self, parent=None, text=None, - EditorSidebarClass=EditorSidebar, - EditorViewClass=EditorView): + EditorSidebarClass=EditorSidebar, + EditorViewClass=EditorView): QFrame.__init__(self, parent) self.view = EditorViewClass(self, text) self.sidebar = EditorSidebarClass(self) self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.setLineWidth(2) self.vlayout = QVBoxLayout() self.vlayout.setSpacing(0) self.setLayout(self.vlayout) self.hlayout = QHBoxLayout() self.vlayout.addLayout(self.hlayout) self.hlayout.addWidget(self.sidebar) self.hlayout.addWidget(self.view) self.vlayout.setContentsMargins(2, 2, 2, 2) - def setPlainText(self, text): self.view.document().setPlainText(text) self.view.setModified(False) def isModified(self): return self.view.document().isModified() def toPlainText(self): return unicode(self.view.document().toPlainText()) def setModified(self, flag): self.view.document().setModified(flag) + class PythonEditorWidget(EditorWidget): pass + class QtQmlEditorWidget(QPlainTextEdit): - + def __init__(self, parent): QPlainTextEdit.__init__(self, parent) self.highlighter = QtQmlHighlighter(self) + class SaveDialog(QMessageBox): def __init__(self, msg): - QMessageBox.__init__(self) - self.setWindowTitle("Save") - self.setText(msg) - self.setStandardButtons(QMessageBox.Save |QMessageBox.Discard | QMessageBox.Cancel) - self.setDefaultButton(QMessageBox.Save) + QMessageBox.__init__(self) + self.setWindowTitle("Save") + self.setText(msg) + self.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) + self.setDefaultButton(QMessageBox.Save) if __name__ == "__main__": - if __file__ == "": __file__ = "./widget.py" + if __file__ == "": + __file__ = "./widget.py" import sys app = QApplication(sys.argv) src = open(__file__).read() edit = EditorWidget(text=src) - edit.resize(640,480) + edit.resize(640, 480) edit.show() app.exec_() diff --git a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py index ae06885a17..3ed01792ab 100644 --- a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py +++ b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py @@ -1,44 +1,45 @@ import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * - + + class AssignProfileDialog(Extension): def __init__(self, parent): super().__init__(parent) def assignProfile(self): doc = Application.activeDocument() if doc == None: QMessageBox.information(Application.activeWindow().qwindow(), "Assign Profile", "There is no active document.") return - + self.dialog = QDialog(Application.activeWindow().qwindow()) - + self.cmbProfile = QComboBox(self.dialog) for profile in sorted(Application.profiles(doc.colorModel(), doc.colorDepth())): self.cmbProfile.addItem(profile) - + vbox = QVBoxLayout(self.dialog) vbox.addWidget(self.cmbProfile) self.buttonBox = QDialogButtonBox(self.dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.dialog.accept) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.dialog.reject) vbox.addWidget(self.buttonBox) self.dialog.show() self.dialog.activateWindow() self.dialog.exec_() - + def accept(self): doc = Application.activeDocument() doc.setColorProfile(self.cmbProfile.currentText()) def setup(self): action = Application.createAction("assing_profile_to_image", "Assign Profile to Image") action.triggered.connect(self.assignProfile) Scripter.addExtension(AssignProfileDialog(Application)) diff --git a/plugins/extensions/pykrita/plugin/plugins/hello/hello.py b/plugins/extensions/pykrita/plugin/plugins/hello/hello.py index c4bc7036f6..d9eb4835d2 100644 --- a/plugins/extensions/pykrita/plugin/plugins/hello/hello.py +++ b/plugins/extensions/pykrita/plugin/plugins/hello/hello.py @@ -1,31 +1,35 @@ import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * + def hello(): QMessageBox.information(QWidget(), "Test", "Hello! This is Krita " + Application.version()) + class HelloExtension(Extension): - def __init__(self, parent): - super().__init__(parent) + def __init__(self, parent): + super().__init__(parent) - def setup(self): - qDebug("Hello Setup") - action = Krita.instance().createAction("hello_python", "hello") - action.triggered.connect(hello) + def setup(self): + qDebug("Hello Setup") + action = Krita.instance().createAction("hello_python", "hello") + action.triggered.connect(hello) Scripter.addExtension(HelloExtension(Krita.instance())) + class HelloDocker(DockWidget): - def __init__(self): - super().__init__() - label = QLabel("Hello", self) - self.setWidget(label) - self.label = label - - def canvasChanged(self, canvas): - self.label.setText("Hellodocker: canvas changed"); + + def __init__(self): + super().__init__() + label = QLabel("Hello", self) + self.setWidget(label) + self.label = label + + def canvasChanged(self, canvas): + self.label.setText("Hellodocker: canvas changed") Application.addDockWidgetFactory(DockWidgetFactory("hello", DockWidgetFactoryBase.DockRight, HelloDocker)) diff --git a/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py b/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py index 61abc85096..2cd1c4a683 100644 --- a/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py +++ b/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py @@ -1,117 +1,116 @@ import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * + class HighpassExtension(Extension): def __init__(self, parent): super().__init__(parent) def setup(self): action = Application.createAction("high_pass_filter", "High Pass") action.triggered.connect(self.showDialog) def showDialog(self): doc = Application.activeDocument() if doc == None: QMessageBox.information(Application.activeWindow().qwindow(), "Highpass Filter", "There is no active image.") return - + self.dialog = QDialog(Application.activeWindow().qwindow()) self.intRadius = QSpinBox() self.intRadius.setValue(10) self.intRadius.setRange(2, 200) - + self.cmbMode = QComboBox() self.cmbMode.addItems(["Color", "Preserve DC", "Greyscale", "Greyscale, Apply Chroma", "Redrobes"]) - self.keepOriginal = QCheckBox("Keep Original Layer"); + self.keepOriginal = QCheckBox("Keep Original Layer") self.keepOriginal.setChecked(True) form = QFormLayout() form.addRow("Filter Radius", self.intRadius) form.addRow("Mode", self.cmbMode) form.addRow("", self.keepOriginal) - + self.buttonBox = QDialogButtonBox(self.dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.dialog.accept) self.buttonBox.accepted.connect(self.highpass) self.buttonBox.rejected.connect(self.dialog.reject) - + vbox = QVBoxLayout(self.dialog) vbox.addLayout(form) vbox.addWidget(self.buttonBox) - + self.dialog.show() self.dialog.activateWindow() self.dialog.exec_() - + def highpass(self): # XXX: Start undo macro image = Application.activeDocument() original = image.activeNode() working_layer = original - + # We can only highpass on paint layers if self.keepOriginal.isChecked() or original.type() != "paintlayer": working_layer = image.createNode("working", "paintlayer") working_layer.setColorSpace(original.colorModel(), original.colorSpace(), original.profile()) working_layer.writeBytes(original.readBytes(0, 0, image.width(), image.height()), 0, 0, image.width(), image.height()) - original.parentNode().addChildNode(working_layer, original) # XXX: Unimplemented - + original.parentNode().addChildNode(working_layer, original) # XXX: Unimplemented + image.setActiveNode(working_layer) - colors_layer = None; - + colors_layer = None + # if keeping colors if self.cmbMode.currentIndex() == 1 or self.cmbMode.currentIndex() == 3: - colors_layer = working_layer.duplicate() # XXX: Unimplemented + colors_layer = working_layer.duplicate() # XXX: Unimplemented colors_layer.setName("colors") - original.parentNode().addChildNode(working_layer, colors_layer) # XXX: Unimplemented - + original.parentNode().addChildNode(working_layer, colors_layer) # XXX: Unimplemented + # if greyscale, desature if (self.cmbMode.currentIndex() == 2 or self.cmbMode.currentIndex() == 3): filter = Application.filter("desaturate") filter.apply(working_layer, 0, 0, image.width(), image.height()) - + # Duplicate on top and blur blur_layer = working_layer.duplicate() blur_layer.setName("blur") - original.parentNode().addChildNode(blur_layer, working_layer) # XXX: Unimplemented - + original.parentNode().addChildNode(blur_layer, working_layer) # XXX: Unimplemented + # blur filter = Application.filter("gaussian blur") filter_configuration = filter.configuration() filter_configuration.setProperty("horizRadius", self.intRadius.value()) filter_configuration.setProperty("vertRadius", self.intRadius.value()) filter_configuration.setProperty("lockAspect", true) filter.setConfiguration(filter_configuration) filter.apply(blur_layer, 0, 0, image.width(), image.height()) - - + if self.cmbMode.currentIndex() <= 3: blur_layer.setBlendingMode("grain_extract") working_layer = image.mergeDown(blur_layer) - + # if preserve chroma, change set the mode to value and merge down with the layer we kept earlier. if self.cmbMode.currentIndex() == 3: working_layer.setBlendingMode("value") working_layer = image.mergeDown(working_layer) - + # if preserve DC, change set the mode to overlay and merge down with the average colour of the layer we kept earlier. if self.cmbMode.currentIndex() == 1: # get the average color of the entire image # clear the colors layer to the given color working_layer = image.mergeDown(working_layer) - - else: # Mode == 4, RedRobes + + else: # Mode == 4, RedRobes image.setActiveNode(blur_layer) # Get the average color of the input layer # copy the solid colour layer # copy the blurred layer # XXX: End undo macro Scripter.addExtension(HighpassExtension(Krita.instance())) - diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py index d3e6b52ed2..8281116603 100644 --- a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py @@ -1,213 +1,216 @@ # Description: A Python based docker that allows you to edit KPL color palettes. # By Wolthera # Importing the relevant dependancies: import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.Qt import * import math -from krita import * +from krita import * -#import the exporters +# import the exporters from . import palette_exporter_gimppalette, palette_exporter_inkscapeSVG + class Palette_Docker(DockWidget): -#Init the docker +# Init the docker + def __init__(self): super().__init__() # make base-widget and layout - widget = QWidget() + widget = QWidget() layout = QVBoxLayout() buttonLayout = QHBoxLayout() widget.setLayout(layout) self.setWindowTitle("Python Palette Docker") - #Make a combobox and add palettes + # Make a combobox and add palettes self.cmb_palettes = QComboBox() allPalettes = Application.resources("palette") for palette_name in allPalettes: self.cmb_palettes.addItem(palette_name) self.currentPalette = Palette(allPalettes[list(allPalettes.keys())[0]]) self.cmb_palettes.currentTextChanged.connect(self.slot_paletteChanged) - layout.addWidget(self.cmb_palettes) # add combobox to the layout + layout.addWidget(self.cmb_palettes) # add combobox to the layout self.paletteView = PaletteView() self.paletteView.setPalette(self.currentPalette) layout.addWidget(self.paletteView) self.paletteView.entrySelectedForeGround.connect(self.slot_swatchSelected) - + self.colorComboBox = QComboBox() self.colorList = list() buttonLayout.addWidget(self.colorComboBox) self.addEntry = QPushButton() self.addEntry.setText("A") self.addEntry.setToolTip("Add Entry") self.addEntry.clicked.connect(self.slot_add_entry) buttonLayout.addWidget(self.addEntry) self.addGroup = QPushButton() self.addGroup.clicked.connect(self.slot_add_group) self.addGroup.setText("G") self.addGroup.setToolTip("Add Group") buttonLayout.addWidget(self.addGroup) self.removeEntry = QPushButton() self.removeEntry.setText("R") self.removeEntry.setToolTip("Remove Entry") self.removeEntry.clicked.connect(self.slot_remove_entry) buttonLayout.addWidget(self.removeEntry) - - #QActions + + # QActions self.extra = QToolButton() self.editPaletteData = QAction() self.editPaletteData.setText("Edit Palette Settings") self.editPaletteData.triggered.connect(self.slot_edit_palette_data) self.extra.setDefaultAction(self.editPaletteData) buttonLayout.addWidget(self.extra) - + self.actionMenu = QMenu() self.exportToGimp = QAction() self.exportToGimp.setText("Export as GIMP palette file.") self.exportToGimp.triggered.connect(self.slot_export_to_gimp_palette) self.exportToInkscape = QAction() self.exportToInkscape.setText("Export as Inkscape SVG with swatches.") self.exportToInkscape.triggered.connect(self.slot_export_to_inkscape_svg) self.actionMenu.addAction(self.editPaletteData) self.actionMenu.addAction(self.exportToGimp) self.actionMenu.addAction(self.exportToInkscape) - + self.extra.setMenu(self.actionMenu) - + layout.addLayout(buttonLayout) self.slot_fill_combobox() self.setWidget(widget) # add widget to the docker - + def slot_paletteChanged(self, name): self.currentPalette = Palette(Application.resources("palette")[name]) self.paletteView.setPalette(self.currentPalette) self.slot_fill_combobox() @pyqtSlot('KoColorSetEntry') def slot_swatchSelected(self, entry): - print("entry "+entry.name) + print("entry " + entry.name) if (self.canvas()) is not None: if (self.canvas().view()) is not None: name = entry.name - if len(entry.id)>0: - name = entry.id+" - "+entry.name - if len(name)>0: + if len(entry.id) > 0: + name = entry.id + " - " + entry.name + if len(name) > 0: if name in self.colorList: self.colorComboBox.setCurrentIndex(self.colorList.index(name)) color = self.currentPalette.colorForEntry(entry) self.canvas().view().setForeGroundColor(color) ''' A function for making a combobox with the available colors. We use QCompleter on the colorComboBox so that people can type in the name of a color to select it. This is useful for people with carefully made palettes where the colors are named properly, which makes it easier for them to find colors. ''' + def slot_fill_combobox(self): if self.currentPalette is None: pass palette = self.currentPalette self.colorComboBox.clear() self.colorList.clear() for i in range(palette.colorsCountTotal()): entry = palette.colorSetEntryByIndex(i) color = palette.colorForEntry(entry).colorForCanvas(self.canvas()) colorSquare = QPixmap(12, 12) if entry.spotColor is True: img = colorSquare.toImage() circlePainter = QPainter() img.fill(self.colorComboBox.palette().color(QPalette.Base)) circlePainter.begin(img) brush = QBrush(Qt.SolidPattern) brush.setColor(color) circlePainter.setBrush(brush) circlePainter.drawEllipse(0, 0, 11, 11) circlePainter.end() colorSquare = QPixmap.fromImage(img) else: colorSquare.fill(color) name = entry.name - if len(entry.id)>0: - name = entry.id+" - "+entry.name + if len(entry.id) > 0: + name = entry.id + " - " + entry.name self.colorList.append(name) self.colorComboBox.addItem(QIcon(colorSquare), name) self.colorComboBox.setEditable(True) self.colorComboBox.setInsertPolicy(QComboBox.NoInsert) self.colorComboBox.completer().setCompletionMode(QCompleter.PopupCompletion) self.colorComboBox.completer().setCaseSensitivity(False) self.colorComboBox.completer().setFilterMode(Qt.MatchContains) self.colorComboBox.currentIndexChanged.connect(self.slot_get_color_from_combobox) def slot_get_color_from_combobox(self): if self.currentPalette is not None: entry = self.currentPalette.colorSetEntryByIndex(self.colorComboBox.currentIndex()) self.slot_swatchSelected(entry) - + def slot_add_entry(self): if (self.canvas()) is not None: if (self.canvas().view()) is not None: color = self.canvas().view().foreGroundColor() succes = self.paletteView.addEntryWithDialog(color) if succes is True: self.slot_fill_combobox() - + def slot_add_group(self): succes = self.paletteView.addGroupWithDialog() if succes is True: self.slot_fill_combobox() - + def slot_remove_entry(self): succes = self.paletteView.removeSelectedEntryWithDialog() if succes is True: self.slot_fill_combobox() - + ''' A function for giving a gui to edit palette metadata... I also want this to be the way to edit the settings of the palette docker. ''' + def slot_edit_palette_data(self): dialog = QDialog(self) tabWidget = QTabWidget() dialog.setWindowTitle("Edit Palette Data") dialog.setLayout(QVBoxLayout()) dialog.layout().addWidget(tabWidget) paletteWidget = QWidget() paletteWidget.setLayout(QVBoxLayout()) tabWidget.addTab(paletteWidget, "Palette Data") paletteName = QLineEdit() paletteName.setText(self.cmb_palettes.currentText()) paletteWidget.layout().addWidget(paletteName) paletteColumns = QSpinBox() paletteColumns.setValue(self.currentPalette.columnCount()) paletteWidget.layout().addWidget(paletteColumns) paletteComment = QPlainTextEdit() paletteComment.appendPlainText(self.currentPalette.comment()) paletteWidget.layout().addWidget(paletteComment) buttons = QDialogButtonBox(QDialogButtonBox.Ok) dialog.layout().addWidget(buttons) buttons.accepted.connect(dialog.accept) - #buttons.rejected.connect(dialog.reject()) - - if dialog.exec_()==QDialog.Accepted: + # buttons.rejected.connect(dialog.reject()) + + if dialog.exec_() == QDialog.Accepted: Resource = Application.resources("palette")[self.cmb_palettes.currentText()] - Resource.setName(paletteName.text()); + Resource.setName(paletteName.text()) self.currentPalette = Palette(Resource) print(paletteColumns.value()) self.currentPalette.setColumnCount(paletteColumns.value()) self.paletteView.setPalette(self.currentPalette) self.slot_fill_combobox() self.currentPalette.setComment(paletteComment.toPlainText()) - + def slot_export_to_gimp_palette(self): palette_exporter_gimppalette.gimpPaletteExporter(self.cmb_palettes.currentText()) - + def slot_export_to_inkscape_svg(self): palette_exporter_inkscapeSVG.inkscapeSVGExporter(self.cmb_palettes.currentText()) - + def canvasChanged(self, canvas): pass -#Add docker to the application :) +# Add docker to the application :) Application.addDockWidgetFactory(DockWidgetFactory("palette_docker", DockWidgetFactoryBase.DockRight, Palette_Docker)) - diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_gimppalette.py b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_gimppalette.py index 2b616d79ef..3e8c1af165 100644 --- a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_gimppalette.py +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_gimppalette.py @@ -1,60 +1,61 @@ ''' A script that converts the palette with the given name to a gimp palette at the location asked for. By Wolthera. ''' # Importing the relevant dependancies: import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * import math from krita import * + class gimpPaletteExporter: def __init__(self, name): - #We want people to select a palette and a location to save to... + # We want people to select a palette and a location to save to... self.fileName = QFileDialog.getExistingDirectory() allPalettes = Application.resources("palette") self.paletteName = name self.currentPalette = Palette(allPalettes[self.paletteName]) self.export() done = QMessageBox() done.setWindowTitle("Export succesful") - done.setText(self.paletteName+" has been exported to "+self.fileName+"!") + done.setText(self.paletteName + " has been exported to " + self.fileName + "!") done.exec_() pass - + def export(self): # open the appropriate file... - gplFile = open(self.fileName+"/"+self.paletteName+".gpl", "w") + gplFile = open(self.fileName + "/" + self.paletteName + ".gpl", "w") gplFile.write("GIMP Palette\n") - gplFile.write("Name: "+self.paletteName+"\n") - gplFile.write("Columns: "+str(self.currentPalette.columnCount())+"\n") - gplFile.write("#"+self.currentPalette.comment()+"\n") + gplFile.write("Name: " + self.paletteName + "\n") + gplFile.write("Columns: " + str(self.currentPalette.columnCount()) + "\n") + gplFile.write("#" + self.currentPalette.comment() + "\n") colorCount = self.currentPalette.colorsCountGroup("") - + for i in range(colorCount): entry = self.currentPalette.colorSetEntryFromGroup(i, "") color = self.currentPalette.colorForEntry(entry) - #convert to sRGB + # convert to sRGB color.setColorSpace("RGBA", "U8", "sRGB built-in") - - red = max(min(int(color.componentsOrdered()[0]*255), 255), 0) - green = max(min(int(color.componentsOrdered()[1]*255), 255), 0) - blue = max(min(int(color.componentsOrdered()[2]*255), 255), 0) - gplFile.write(str(red)+" "+str(green)+" "+str(blue)+" "+entry.id+"-"+entry.name+"\n") + + red = max(min(int(color.componentsOrdered()[0] * 255), 255), 0) + green = max(min(int(color.componentsOrdered()[1] * 255), 255), 0) + blue = max(min(int(color.componentsOrdered()[2] * 255), 255), 0) + gplFile.write(str(red) + " " + str(green) + " " + str(blue) + " " + entry.id + "-" + entry.name + "\n") groupNames = self.currentPalette.groupNames() for groupName in groupNames: colorCount = self.currentPalette.colorsCountGroup(groupName) for i in range(colorCount): entry = self.currentPalette.colorSetEntryFromGroup(i, groupName) color = self.currentPalette.colorForEntry(entry) - #convert to sRGB + # convert to sRGB color.setColorSpace("RGBA", "U8", "sRGB built-in") - red = max(min(int(color.componentsOrdered()[0]*255), 255), 0) - green = max(min(int(color.componentsOrdered()[1]*255), 255), 0) - blue = max(min(int(color.componentsOrdered()[2]*255), 255), 0) - gplFile.write(str(red)+" "+str(green)+" "+str(blue)+" "+entry.id+"-"+entry.name+"\n") + red = max(min(int(color.componentsOrdered()[0] * 255), 255), 0) + green = max(min(int(color.componentsOrdered()[1] * 255), 255), 0) + blue = max(min(int(color.componentsOrdered()[2] * 255), 255), 0) + gplFile.write(str(red) + " " + str(green) + " " + str(blue) + " " + entry.id + "-" + entry.name + "\n") gplFile.close() diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_inkscapeSVG.py b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_inkscapeSVG.py index b9141bb130..8bae101a60 100644 --- a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_inkscapeSVG.py +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_exporter_inkscapeSVG.py @@ -1,174 +1,175 @@ ''' A script that converts the palette named "Default" to a SVG so that Inkscape may use the colors The icc-color stuff doesn't work right, because we'd need the ability to get the url of the colorprofile somehow, and then we can make color-profile things in the definitions. By Wolthera. ''' # Importing the relevant dependancies: import sys from PyQt5.QtGui import * from PyQt5.QtXml import * from PyQt5.QtWidgets import * import math from krita import * + class inkscapeSVGExporter: - + def __init__(self, name): - #We want people to select a palette and a location to save to... + # We want people to select a palette and a location to save to... self.fileName = QFileDialog.getExistingDirectory() allPalettes = Application.resources("palette") self.paletteName = name self.currentPalette = Palette(allPalettes[self.paletteName]) self.export() done = QMessageBox() done.setWindowTitle("Export succesful") - done.setText(self.paletteName+" has been exported to "+self.fileName+"!") + done.setText(self.paletteName + " has been exported to " + self.fileName + "!") done.exec_() pass def export(self): # open the appropriate file... - svgFile = open(self.fileName+"/"+self.paletteName+".svg", "w") + svgFile = open(self.fileName + "/" + self.paletteName + ".svg", "w") svgDoc = QDomDocument() svgBaseElement = svgDoc.createElement("svg") svgBaseElement.setAttribute("xmlns:osb", "http://www.openswatchbook.org/uri/2009/osb") svgBaseElement.setAttribute("xmlns:svg", "http://www.w3.org/2000/svg") svgBaseElement.setAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/") - svgBaseElement.setAttribute("xmlns:cc", "http://creativecommons.org/ns#" ) - svgBaseElement.setAttribute("xmlns:rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#" ) + svgBaseElement.setAttribute("xmlns:cc", "http://creativecommons.org/ns#") + svgBaseElement.setAttribute("xmlns:rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#") svgDefs = svgDoc.createElement("defs") svgSwatches = svgDoc.createElement("g") svgSwatches.setAttribute("id", "Swatches") - + svgMeta = svgDoc.createElement("metadata") svgBaseElement.appendChild(svgMeta) rdf = svgDoc.createElement("rdf:RDF") ccwork = svgDoc.createElement("cc:Work") dctitle = svgDoc.createElement("dc:title") dcdescription = svgDoc.createElement("dc:description") dctitle.appendChild(svgDoc.createTextNode(self.paletteName)) dcdescription.appendChild(svgDoc.createTextNode(self.currentPalette.comment())) ccwork.appendChild(dctitle) ccwork.appendChild(dcdescription) rdf.appendChild(ccwork) svgMeta.appendChild(rdf) Row = 0 Column = 0 iccProfileList = [] colorCount = self.currentPalette.colorsCountGroup("") - + for i in range(colorCount): entry = self.currentPalette.colorSetEntryFromGroup(i, "") color = self.currentPalette.colorForEntry(entry) - iccColor = "icc-color("+color.colorProfile() - for c in range(len(color.componentsOrdered())-1): - iccColor = iccColor+","+str(color.componentsOrdered()[c]) - iccColor = iccColor+")" + iccColor = "icc-color(" + color.colorProfile() + for c in range(len(color.componentsOrdered()) - 1): + iccColor = iccColor + "," + str(color.componentsOrdered()[c]) + iccColor = iccColor + ")" if color.colorProfile() not in iccProfileList: iccProfileList.append(color.colorProfile()) - - #convert to sRGB + + # convert to sRGB color.setColorSpace("RGBA", "U8", "sRGB built-in") - red = max(min(int(color.componentsOrdered()[0]*255), 255), 0) - green = max(min(int(color.componentsOrdered()[1]*255), 255), 0) - blue = max(min(int(color.componentsOrdered()[2]*255), 255), 0) - hexcode = "#"+str(format(red, '02x'))+str(format(green, '02x'))+str(format(blue, '02x')) - swatchName = str(i)+"-"+entry.name + red = max(min(int(color.componentsOrdered()[0] * 255), 255), 0) + green = max(min(int(color.componentsOrdered()[1] * 255), 255), 0) + blue = max(min(int(color.componentsOrdered()[2] * 255), 255), 0) + hexcode = "#" + str(format(red, '02x')) + str(format(green, '02x')) + str(format(blue, '02x')) + swatchName = str(i) + "-" + entry.name swatchName = swatchName.replace(" ", "-") swatchName = swatchName.replace("(", "-") swatchName = swatchName.replace(")", "-") swatchMain = svgDoc.createElement("linearGradient") swatchMain.setAttribute("osb:paint", "solid") swatchMain.setAttribute("id", swatchName) swatchSub = svgDoc.createElement("stop") - swatchSub.setAttribute("style", "stop-color: "+hexcode+" "+iccColor+";stop-opacity:1;") + swatchSub.setAttribute("style", "stop-color: " + hexcode + " " + iccColor + ";stop-opacity:1;") swatchMain.appendChild(swatchSub) svgDefs.appendChild(swatchMain) svgSingleSwatch = svgDoc.createElement("rect") - svgSingleSwatch.setAttribute("x", str(int(Column*20))) - svgSingleSwatch.setAttribute("y", str(int(Row*20))) + svgSingleSwatch.setAttribute("x", str(int(Column * 20))) + svgSingleSwatch.setAttribute("y", str(int(Row * 20))) svgSingleSwatch.setAttribute("width", str(int(20))) svgSingleSwatch.setAttribute("height", str(int(20))) - svgSingleSwatch.setAttribute("fill", "url(#"+swatchName+")") - svgSingleSwatch.setAttribute("id", "swatch"+swatchName) + svgSingleSwatch.setAttribute("fill", "url(#" + swatchName + ")") + svgSingleSwatch.setAttribute("id", "swatch" + swatchName) if entry.spotColor is True: svgSingleSwatch.setAttribute("rx", str(10)) svgSingleSwatch.setAttribute("ry", str(10)) svgSwatches.appendChild(svgSingleSwatch) Column += 1 if (Column >= self.currentPalette.columnCount()): Column = 0 - Row +=1 - + Row += 1 + groupNames = self.currentPalette.groupNames() for groupName in groupNames: - Column=0 - Row+=1 + Column = 0 + Row += 1 groupTitle = svgDoc.createElement("text") - groupTitle.setAttribute("x", str(int(Column*20))) - groupTitle.setAttribute("y", str(int(Row*20)+15)) + groupTitle.setAttribute("x", str(int(Column * 20))) + groupTitle.setAttribute("y", str(int(Row * 20) + 15)) groupTitle.appendChild(svgDoc.createTextNode(groupName)) svgSwatches.appendChild(groupTitle) - Row+=1 + Row += 1 colorCount = self.currentPalette.colorsCountGroup(groupName) for i in range(colorCount): entry = self.currentPalette.colorSetEntryFromGroup(i, groupName) color = self.currentPalette.colorForEntry(entry) - iccColor = "icc-color("+color.colorProfile() - for c in range(len(color.componentsOrdered())-1): - iccColor = iccColor+","+str(color.componentsOrdered()[c]) - iccColor = iccColor+")" + iccColor = "icc-color(" + color.colorProfile() + for c in range(len(color.componentsOrdered()) - 1): + iccColor = iccColor + "," + str(color.componentsOrdered()[c]) + iccColor = iccColor + ")" if color.colorProfile() not in iccProfileList: iccProfileList.append(color.colorProfile()) - #convert to sRGB + # convert to sRGB color.setColorSpace("RGBA", "U8", "sRGB built-in") - red = max(min(int(color.componentsOrdered()[0]*255), 255), 0) - green = max(min(int(color.componentsOrdered()[1]*255), 255), 0) - blue = max(min(int(color.componentsOrdered()[2]*255), 255), 0) - hexcode = "#"+str(format(red, '02x'))+str(format(green, '02x'))+str(format(blue, '02x')) - - swatchName = groupName+str(i)+"-"+entry.name + red = max(min(int(color.componentsOrdered()[0] * 255), 255), 0) + green = max(min(int(color.componentsOrdered()[1] * 255), 255), 0) + blue = max(min(int(color.componentsOrdered()[2] * 255), 255), 0) + hexcode = "#" + str(format(red, '02x')) + str(format(green, '02x')) + str(format(blue, '02x')) + + swatchName = groupName + str(i) + "-" + entry.name swatchName = swatchName.replace(" ", "-") swatchName = swatchName.replace("(", "-") swatchName = swatchName.replace(")", "-") swatchMain = svgDoc.createElement("linearGradient") swatchMain.setAttribute("osb:paint", "solid") swatchMain.setAttribute("id", swatchName) swatchSub = svgDoc.createElement("stop") - swatchSub.setAttribute("style", "stop-color: "+hexcode+" "+iccColor+";stop-opacity:1;") + swatchSub.setAttribute("style", "stop-color: " + hexcode + " " + iccColor + ";stop-opacity:1;") swatchMain.appendChild(swatchSub) svgDefs.appendChild(swatchMain) svgSingleSwatch = svgDoc.createElement("rect") - svgSingleSwatch.setAttribute("x", str(int(Column*20))) - svgSingleSwatch.setAttribute("y", str(int(Row*20))) + svgSingleSwatch.setAttribute("x", str(int(Column * 20))) + svgSingleSwatch.setAttribute("y", str(int(Row * 20))) svgSingleSwatch.setAttribute("width", str(int(20))) svgSingleSwatch.setAttribute("height", str(int(20))) - svgSingleSwatch.setAttribute("fill", "url(#"+swatchName+")") - svgSingleSwatch.setAttribute("id", "swatch "+swatchName) + svgSingleSwatch.setAttribute("fill", "url(#" + swatchName + ")") + svgSingleSwatch.setAttribute("id", "swatch " + swatchName) if entry.spotColor is True: svgSingleSwatch.setAttribute("rx", str(10)) svgSingleSwatch.setAttribute("ry", str(10)) svgSwatches.appendChild(svgSingleSwatch) Column += 1 if (Column >= self.currentPalette.columnCount()): Column = 0 - Row +=1 + Row += 1 for profile in iccProfileList: svgProfileDesc = svgDoc.createElement("color-profile") svgProfileDesc.setAttribute("name", profile) - #This is incomplete because python api doesn't have any way to ask for this data yet. - #svgProfileDesc.setAttribute("local", "sRGB") - #svgProfileDesc.setAttribute("xlink:href", colorprofileurl) + # This is incomplete because python api doesn't have any way to ask for this data yet. + # svgProfileDesc.setAttribute("local", "sRGB") + # svgProfileDesc.setAttribute("xlink:href", colorprofileurl) svgProfileDesc.setAttribute("rendering-intent", "perceptual") svgDefs.appendChild(svgProfileDesc) svgBaseElement.appendChild(svgDefs) svgBaseElement.appendChild(svgSwatches) - svgBaseElement.setAttribute("viewBox", "0 0 "+str(self.currentPalette.columnCount()*20)+" "+str(int((Row+1)*20))) + svgBaseElement.setAttribute("viewBox", "0 0 " + str(self.currentPalette.columnCount() * 20) + " " + str(int((Row + 1) * 20))) svgDoc.appendChild(svgBaseElement) svgFile.write(svgDoc.toString()) svgFile.close() diff --git a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py index 86015be5e8..6159c7c23b 100644 --- a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py +++ b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/quick_settings_docker.py @@ -1,160 +1,162 @@ ''' Description: A Python based docker for quickly choosing the brushsize like similar dockers in other drawing programs. By Wolthera @package quick_settings_docker ''' # Importing the relevant dependancies: import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * -from krita import * +from krita import * + class QuickSettingsDocker(DockWidget): -#Init the docker +# Init the docker + def __init__(self): super().__init__() # make base-widget and layout - widget = QWidget() + widget = QWidget() layout = QVBoxLayout() widget.setLayout(layout) self.setWindowTitle("Quick Settings Docker") tabWidget = QTabWidget() - + self.brushSizeTableView = QTableView() self.brushSizeTableView.verticalHeader().hide() self.brushSizeTableView.horizontalHeader().hide() self.brushSizeTableView.setSelectionMode(QTableView.SingleSelection) - + self.brushOpacityTableView = QTableView() self.brushOpacityTableView.verticalHeader().hide() self.brushOpacityTableView.horizontalHeader().hide() self.brushOpacityTableView.setSelectionMode(QTableView.SingleSelection) - + self.brushFlowTableView = QTableView() self.brushFlowTableView.verticalHeader().hide() self.brushFlowTableView.horizontalHeader().hide() self.brushFlowTableView.setSelectionMode(QTableView.SingleSelection) tabWidget.addTab(self.brushSizeTableView, "Size") tabWidget.addTab(self.brushOpacityTableView, "Opacity") tabWidget.addTab(self.brushFlowTableView, "Flow") layout.addWidget(tabWidget) - self.setWidget(widget) #Add the widget to the docker. - - #amount of columns in each row for all the tables. + self.setWidget(widget) # Add the widget to the docker. + + # amount of columns in each row for all the tables. self.columns = 4 - + # We want a grid with possible options to select. # To do this, we'll make a TableView widget and use a standarditemmodel for the entries. # The entries are filled out based on the sizes and opacity lists. - - #Sizes and opacity lists. The former is half-way copied from ptsai, the latter is based on personal experience of useful opacities. + + # Sizes and opacity lists. The former is half-way copied from ptsai, the latter is based on personal experience of useful opacities. self.sizesList = [0.7, 1.0, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 20, 25, 30, 35, 40, 50, 60, 70, 80, 100, 120, 160, 200, 250, 300, 350, 400, 450, 500] self.opacityList = [0.1, 0.5, 1, 5, 10, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100] - self.brushSizeModel = QStandardItemModel( (len(self.sizesList)/self.columns)+1 , self.columns) - self.brushOpacityModel = QStandardItemModel( (len(self.opacityList)/self.columns)+1 , self.columns ) - self.brushFlowModel = QStandardItemModel( (len(self.opacityList)/self.columns)+1 , self.columns ) + self.brushSizeModel = QStandardItemModel((len(self.sizesList) / self.columns) + 1, self.columns) + self.brushOpacityModel = QStandardItemModel((len(self.opacityList) / self.columns) + 1, self.columns) + self.brushFlowModel = QStandardItemModel((len(self.opacityList) / self.columns) + 1, self.columns) self.fillSizesModel() self.fillOpacityModel() - + # Now we're done filling out our tables, we connect the views to the functions that'll change the settings. self.brushSizeTableView.clicked.connect(self.setBrushSize) self.brushOpacityTableView.clicked.connect(self.setBrushOpacity) self.brushFlowTableView.clicked.connect(self.setBrushFlow) - - + def fillSizesModel(self): - #First we empty the old model. We might wanna use this function in the future to fill it with the brushmask of the selected brush, but there's currently no API for recognising changes in the current brush nor is there a way to get its brushmask. + # First we empty the old model. We might wanna use this function in the future to fill it with the brushmask of the selected brush, but there's currently no API for recognising changes in the current brush nor is there a way to get its brushmask. self.brushSizeModel.clear() for s in range(len(self.sizesList)): # we're gonna itterate over our list, and make a new item for each entry. # We need to disable a bunch of stuff to make sure people won't do funny things to our entries. item = QStandardItem() item.setCheckable(False) item.setEditable(False) item.setDragEnabled(False) item.setText(str(self.sizesList[s])) # And from here on we'll make an icon. brushImage = QPixmap(32, 32) img = brushImage.toImage() circlePainter = QPainter() img.fill(self.brushSizeTableView.palette().color(QPalette.Base)) circlePainter.begin(img) brush = QBrush(Qt.SolidPattern) brush.setColor(self.brushSizeTableView.palette().color(QPalette.Text)) circlePainter.setBrush(brush) circlePainter.pen().setWidth(0) - brushSize = max(min((self.sizesList[s]/500)*100, 32), 1) - brushSize = brushSize*0.5 + brushSize = max(min((self.sizesList[s] / 500) * 100, 32), 1) + brushSize = brushSize * 0.5 circlePainter.drawEllipse(QPointF(16, 16), brushSize, brushSize) circlePainter.end() brushImage = QPixmap.fromImage(img) # now we're done with drawing the icon, so we set it on the item. item.setIcon(QIcon(brushImage)) - self.brushSizeModel.setItem(s/4, s%4, item) + self.brushSizeModel.setItem(s / 4, s % 4, item) self.brushSizeTableView.setModel(self.brushSizeModel) self.brushSizeTableView.resizeColumnsToContents() - + def fillOpacityModel(self): self.brushOpacityModel.clear() self.brushFlowModel.clear() for s in range(len(self.opacityList)): # we're gonna itterate over our list, and make a new item for each entry. item = QStandardItem() item.setCheckable(False) item.setEditable(False) item.setDragEnabled(False) item.setText(str(self.opacityList[s])) brushImage = QPixmap(32, 32) img = brushImage.toImage() circlePainter = QPainter() img.fill(self.brushSizeTableView.palette().color(QPalette.Base)) circlePainter.begin(img) brush = QBrush(Qt.SolidPattern) brush.setColor(self.brushSizeTableView.palette().color(QPalette.Text)) circlePainter.setBrush(brush) circlePainter.pen().setWidth(0) - circlePainter.setOpacity(self.opacityList[s]/100) + circlePainter.setOpacity(self.opacityList[s] / 100) circlePainter.drawEllipse(QPointF(16, 16), 16, 16) circlePainter.end() brushImage = QPixmap.fromImage(img) item.setIcon(QIcon(brushImage)) # the flow and opacity models will use virtually the same items, but Qt would like us to make sure we understand # these are not really the same items, so hence the clone. itemFlow = item.clone() - self.brushOpacityModel.setItem(s/4, s%4, item) - self.brushFlowModel.setItem(s/4, s%4, itemFlow) + self.brushOpacityModel.setItem(s / 4, s % 4, item) + self.brushFlowModel.setItem(s / 4, s % 4, itemFlow) self.brushOpacityTableView.setModel(self.brushOpacityModel) self.brushFlowTableView.setModel(self.brushFlowModel) self.brushFlowTableView.resizeColumnsToContents() self.brushOpacityTableView.resizeColumnsToContents() def canvasChanged(self, canvas): pass @pyqtSlot('QModelIndex') def setBrushSize(self, index): - i = index.column()+(index.row()*self.columns) + i = index.column() + (index.row() * self.columns) brushSize = self.sizesList[i] if Application.activeWindow() and len(Application.activeWindow().views()) > 0: - Application.activeWindow().views()[0].setBrushSize(brushSize); + Application.activeWindow().views()[0].setBrushSize(brushSize) + @pyqtSlot('QModelIndex') def setBrushOpacity(self, index): - i = index.column()+(index.row()*self.columns) - brushOpacity = self.opacityList[i]/100 + i = index.column() + (index.row() * self.columns) + brushOpacity = self.opacityList[i] / 100 if Application.activeWindow() and len(Application.activeWindow().views()) > 0: - Application.activeWindow().views()[0].setPaintingOpacity(brushOpacity); + Application.activeWindow().views()[0].setPaintingOpacity(brushOpacity) + @pyqtSlot('QModelIndex') def setBrushFlow(self, index): - i = index.column()+(index.row()*self.columns) - brushOpacity = self.opacityList[i]/100 + i = index.column() + (index.row() * self.columns) + brushOpacity = self.opacityList[i] / 100 if Application.activeWindow() and len(Application.activeWindow().views()) > 0: - Application.activeWindow().views()[0].setPaintingFlow(brushOpacity); - + Application.activeWindow().views()[0].setPaintingFlow(brushOpacity) -#Add docker to the application :) -Application.addDockWidgetFactory(DockWidgetFactory("quick_settings_docker", DockWidgetFactoryBase.DockRight, QuickSettingsDocker)) +# Add docker to the application :) +Application.addDockWidgetFactory(DockWidgetFactory("quick_settings_docker", DockWidgetFactoryBase.DockRight, QuickSettingsDocker)) diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py index 017ca0a0f2..68ea0b0470 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py @@ -1,99 +1,99 @@ import bdb import multiprocessing import asyncio from . import debuggerformatter class Debugger(bdb.Bdb): def __init__(self, scripter, cmd): bdb.Bdb.__init__(self) self.quit = False self.debugq = multiprocessing.Queue() self.scripter = scripter self.applicationq = multiprocessing.Queue() self.filePath = self.scripter.documentcontroller.activeDocument.filePath self.application_data = {} self.exception_data = {} self.debugprocess = multiprocessing.Process(target=self._run, args=(self.filePath,)) self.currentLine = 0 bdb.Bdb.reset(self) def _run(self, filename): try: self.mainpyfile = self.canonic(filename) with open(filename, "rb") as fp: statement = "exec(compile(%r, %r, 'exec'))" % \ (fp.read(), self.mainpyfile) self.run(statement) except Exception as e: raise e def user_call(self, frame, args): name = frame.f_code.co_name or "" def user_line(self, frame): """Handler that executes with every line of code""" co = frame.f_code - if self.filePath!=co.co_filename: + if self.filePath != co.co_filename: return self.currentLine = frame.f_lineno - self.applicationq.put({ "code": { "file": co.co_filename, - "name": co.co_name, - "lineNumber": str(frame.f_lineno) + self.applicationq.put({"code": {"file": co.co_filename, + "name": co.co_name, + "lineNumber": str(frame.f_lineno) }, - "frame": { "firstLineNumber": co.co_firstlineno, - "locals": debuggerformatter.format_data(frame.f_locals), - "globals": debuggerformatter.format_data(frame.f_globals) - }, - "trace": "line" - }) + "frame": {"firstLineNumber": co.co_firstlineno, + "locals": debuggerformatter.format_data(frame.f_locals), + "globals": debuggerformatter.format_data(frame.f_globals) + }, + "trace": "line" + }) if self.quit: return self.set_quit() - if self.currentLine==0: + if self.currentLine == 0: return else: cmd = self.debugq.get() if cmd == "step": return if cmd == "stop": return self.set_quit() def user_return(self, frame, value): name = frame.f_code.co_name or "" if name == '': - self.applicationq.put({ "quit": True}) + self.applicationq.put({"quit": True}) def user_exception(self, frame, exception): - self.applicationq.put({ "exception": str(exception[1])}) + self.applicationq.put({"exception": str(exception[1])}) async def display(self): """Coroutine for updating the UI""" while True: if self.applicationq.empty(): await asyncio.sleep(0.3) else: while not self.applicationq.empty(): self.application_data.update(self.applicationq.get()) self.scripter.uicontroller.repaintDebugArea() return async def start(self): await self.display() async def step(self): self.debugq.put("step") await self.display() async def stop(self): self.debugq.put("stop") - self.applicationq.put({ "quit": True}) + self.applicationq.put({"quit": True}) await self.display() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py index ff3f6a3c93..64c8018b62 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py @@ -1,26 +1,26 @@ import re import inspect def format_data(data): globals()['types'] = __import__('types') exclude_keys = ['copyright', 'credits', 'False', 'True', 'None', 'Ellipsis', 'quit', 'QtCriticalMsg', 'krita_path', 'QtWarningMsg', 'QWIDGETSIZE_MAX', 'QtFatalMsg', 'PYQT_CONFIGURATION', 'on_load', 'PYQT_VERSION', 'on_pykrita_unloading', 'on_unload', 'QT_VERSION', 'QtInfoMsg', 'PYQT_VERSION_STR', 'qApp', 'QtSystemMsg', 'QtDebugMsg', 'on_pykrita_loaded', 'QT_VERSION_STR'] exclude_valuetypes = [types.BuiltinFunctionType, types.BuiltinMethodType, types.ModuleType, types.FunctionType] return [{k: {'value': str(v), 'type': str(type(v))}} for k, v in data.items() if not (k in exclude_keys or - type(v) in exclude_valuetypes or - re.search(r'^(__).*\1$', k) or - inspect.isclass(v) or - inspect.isfunction(v))] + type(v) in exclude_valuetypes or + re.search(r'^(__).*\1$', k) or + inspect.isclass(v) or + inspect.isfunction(v))] diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py b/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py index 5436380a5b..09734cdc5d 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py @@ -1,542 +1,544 @@ # -*- coding: utf-8 -*- # Resource object code # # Created by: The Resource Compiler for PyQt5 (Qt v5.8.0) # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore qt_resource_data = b"\ \x00\x00\x0f\xd9\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\ \x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\ \x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\ \x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\ \x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\ \x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\ \x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x21\x44\x4f\x43\x54\ \x59\x50\x45\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\ \x2d\x2f\x2f\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\ \x31\x2e\x31\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\ \x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\ \x68\x69\x63\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\ \x2f\x73\x76\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0a\x3c\x73\x76\ \x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\ \x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\ \x73\x76\x67\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\ \x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ \x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\ \x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ \x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\ \x22\x20\x79\x3d\x22\x30\x70\x78\x22\x20\x77\x69\x64\x74\x68\x3d\ \x22\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ \x35\x31\x32\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x34\x31\x32\x2e\x37\x36\x20\x34\x31\x32\x2e\x37\ \x36\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\ \x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\ \x30\x20\x30\x20\x34\x31\x32\x2e\x37\x36\x20\x34\x31\x32\x2e\x37\ \x36\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\ \x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0a\x3c\x67\x3e\x0a\x09\x3c\ \x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x33\x37\x36\x2e\x34\x39\x33\ \x2c\x33\x31\x30\x2e\x39\x39\x38\x63\x2d\x31\x2e\x35\x37\x37\x2d\ \x32\x39\x2e\x31\x36\x37\x2d\x32\x34\x2e\x39\x37\x32\x2d\x35\x32\ \x2e\x35\x36\x32\x2d\x35\x34\x2e\x31\x33\x39\x2d\x35\x34\x2e\x31\ \x33\x39\x76\x2d\x32\x31\x2e\x37\x32\x68\x2d\x36\x2e\x32\x33\x35\ \x76\x32\x31\x2e\x37\x32\x20\x20\x20\x63\x2d\x32\x39\x2e\x31\x36\ \x37\x2c\x31\x2e\x35\x37\x37\x2d\x35\x32\x2e\x35\x36\x32\x2c\x32\ \x34\x2e\x39\x37\x32\x2d\x35\x34\x2e\x31\x33\x39\x2c\x35\x34\x2e\ \x31\x33\x39\x68\x2d\x32\x31\x2e\x34\x36\x35\x76\x36\x2e\x32\x33\ \x35\x68\x32\x31\x2e\x34\x36\x35\x63\x31\x2e\x35\x37\x37\x2c\x32\ \x39\x2e\x31\x36\x37\x2c\x32\x34\x2e\x39\x37\x32\x2c\x35\x32\x2e\ \x35\x36\x38\x2c\x35\x34\x2e\x31\x33\x39\x2c\x35\x34\x2e\x31\x34\ \x35\x76\x32\x31\x2e\x37\x31\x35\x68\x36\x2e\x32\x33\x35\x76\x2d\ \x32\x31\x2e\x37\x31\x35\x20\x20\x20\x63\x32\x39\x2e\x31\x36\x37\ \x2d\x31\x2e\x35\x37\x36\x2c\x35\x32\x2e\x35\x36\x32\x2d\x32\x34\ \x2e\x39\x37\x38\x2c\x35\x34\x2e\x31\x33\x39\x2d\x35\x34\x2e\x31\ \x34\x35\x68\x32\x36\x2e\x31\x34\x31\x76\x2d\x36\x2e\x32\x33\x35\ \x48\x33\x37\x36\x2e\x34\x39\x33\x7a\x20\x4d\x33\x31\x36\x2e\x31\ \x31\x39\x2c\x33\x36\x35\x2e\x31\x34\x33\x63\x2d\x32\x35\x2e\x37\ \x33\x33\x2d\x31\x2e\x35\x36\x2d\x34\x36\x2e\x33\x34\x35\x2d\x32\ \x32\x2e\x31\x37\x38\x2d\x34\x37\x2e\x39\x30\x33\x2d\x34\x37\x2e\ \x39\x31\x20\x20\x20\x68\x34\x37\x2e\x39\x30\x33\x56\x33\x36\x35\ \x2e\x31\x34\x33\x7a\x20\x4d\x33\x31\x36\x2e\x31\x31\x39\x2c\x33\ \x31\x30\x2e\x39\x39\x38\x68\x2d\x34\x37\x2e\x39\x30\x33\x63\x31\ \x2e\x35\x35\x39\x2d\x32\x35\x2e\x37\x33\x32\x2c\x32\x32\x2e\x31\ \x37\x2d\x34\x36\x2e\x33\x34\x35\x2c\x34\x37\x2e\x39\x30\x33\x2d\ \x34\x37\x2e\x39\x30\x33\x56\x33\x31\x30\x2e\x39\x39\x38\x7a\x20\ \x4d\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x36\x35\x2e\x31\x34\x33\ \x76\x2d\x34\x37\x2e\x39\x31\x68\x34\x37\x2e\x39\x30\x33\x20\x20\ \x20\x43\x33\x36\x38\x2e\x36\x39\x32\x2c\x33\x34\x32\x2e\x39\x36\ \x35\x2c\x33\x34\x38\x2e\x30\x38\x37\x2c\x33\x36\x33\x2e\x35\x38\ \x33\x2c\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x36\x35\x2e\x31\x34\ \x33\x7a\x20\x4d\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x31\x30\x2e\ \x39\x39\x38\x76\x2d\x34\x37\x2e\x39\x30\x33\x63\x32\x35\x2e\x37\ \x33\x32\x2c\x31\x2e\x35\x35\x39\x2c\x34\x36\x2e\x33\x34\x34\x2c\ \x32\x32\x2e\x31\x37\x31\x2c\x34\x37\x2e\x39\x30\x33\x2c\x34\x37\ \x2e\x39\x30\x33\x48\x33\x32\x32\x2e\x33\x35\x34\x7a\x20\x20\x20\ \x20\x4d\x32\x39\x31\x2e\x33\x33\x36\x2c\x39\x30\x2e\x30\x31\x36\ \x63\x2d\x30\x2e\x37\x35\x35\x2d\x34\x2e\x36\x37\x36\x2c\x32\x2e\ \x34\x33\x2d\x39\x2e\x30\x39\x31\x2c\x37\x2e\x31\x30\x36\x2d\x39\ \x2e\x38\x35\x32\x63\x32\x2e\x30\x36\x34\x2d\x30\x2e\x33\x33\x38\ \x2c\x35\x30\x2e\x35\x39\x35\x2d\x38\x2e\x37\x31\x36\x2c\x35\x33\ \x2e\x35\x31\x32\x2d\x35\x30\x2e\x35\x33\x31\x20\x20\x20\x63\x30\ \x2e\x33\x32\x32\x2d\x34\x2e\x36\x37\x37\x2c\x34\x2e\x33\x36\x35\ \x2d\x38\x2e\x32\x35\x37\x2c\x39\x2e\x31\x35\x31\x2d\x37\x2e\x39\ \x37\x34\x63\x34\x2e\x37\x33\x37\x2c\x30\x2e\x33\x33\x35\x2c\x38\ \x2e\x33\x31\x32\x2c\x34\x2e\x34\x34\x35\x2c\x37\x2e\x39\x38\x39\ \x2c\x39\x2e\x31\x37\x63\x2d\x33\x2e\x38\x37\x39\x2c\x35\x35\x2e\ \x35\x31\x32\x2d\x36\x37\x2e\x32\x39\x31\x2c\x36\x36\x2e\x32\x30\ \x31\x2d\x36\x37\x2e\x39\x33\x31\x2c\x36\x36\x2e\x33\x30\x32\x20\ \x20\x20\x63\x2d\x30\x2e\x34\x33\x38\x2c\x30\x2e\x30\x36\x37\x2d\ \x30\x2e\x39\x30\x31\x2c\x30\x2e\x31\x30\x34\x2d\x31\x2e\x33\x35\ \x38\x2c\x30\x2e\x31\x30\x34\x43\x32\x39\x35\x2e\x35\x37\x34\x2c\ \x39\x37\x2e\x32\x33\x35\x2c\x32\x39\x32\x2e\x30\x31\x32\x2c\x39\ \x34\x2e\x32\x2c\x32\x39\x31\x2e\x33\x33\x36\x2c\x39\x30\x2e\x30\ \x31\x36\x7a\x20\x4d\x33\x32\x2e\x33\x38\x33\x2c\x33\x30\x2e\x38\ \x33\x20\x20\x20\x63\x2d\x30\x2e\x33\x32\x39\x2d\x34\x2e\x37\x32\ \x35\x2c\x33\x2e\x32\x34\x39\x2d\x38\x2e\x38\x33\x35\x2c\x37\x2e\ \x39\x37\x31\x2d\x39\x2e\x31\x36\x34\x63\x34\x2e\x38\x37\x34\x2d\ \x30\x2e\x33\x34\x34\x2c\x38\x2e\x38\x34\x31\x2c\x33\x2e\x32\x39\ \x31\x2c\x39\x2e\x31\x37\x2c\x37\x2e\x39\x36\x38\x63\x32\x2e\x39\ \x32\x2c\x34\x31\x2e\x38\x32\x2c\x35\x31\x2e\x34\x34\x31\x2c\x35\ \x30\x2e\x31\x39\x39\x2c\x35\x33\x2e\x35\x31\x32\x2c\x35\x30\x2e\ \x35\x33\x37\x20\x20\x20\x63\x34\x2e\x36\x37\x2c\x30\x2e\x37\x36\ \x31\x2c\x37\x2e\x38\x35\x38\x2c\x35\x2e\x31\x37\x36\x2c\x37\x2e\ \x31\x30\x39\x2c\x39\x2e\x38\x34\x36\x63\x2d\x30\x2e\x36\x37\x39\ \x2c\x34\x2e\x31\x38\x34\x2d\x34\x2e\x32\x34\x31\x2c\x37\x2e\x32\ \x32\x35\x2d\x38\x2e\x34\x37\x33\x2c\x37\x2e\x32\x32\x35\x63\x2d\ \x30\x2e\x34\x35\x37\x2c\x30\x2d\x30\x2e\x39\x31\x36\x2d\x30\x2e\ \x30\x33\x37\x2d\x31\x2e\x33\x37\x39\x2d\x30\x2e\x31\x31\x20\x20\ \x20\x43\x39\x39\x2e\x36\x36\x38\x2c\x39\x37\x2e\x30\x33\x31\x2c\ \x33\x36\x2e\x32\x35\x35\x2c\x38\x36\x2e\x33\x34\x31\x2c\x33\x32\ \x2e\x33\x38\x33\x2c\x33\x30\x2e\x38\x33\x7a\x20\x4d\x33\x33\x30\ \x2e\x35\x36\x32\x2c\x31\x36\x32\x2e\x35\x33\x32\x63\x32\x2e\x34\ \x34\x38\x2c\x30\x2e\x31\x30\x31\x2c\x36\x30\x2e\x39\x32\x32\x2c\ \x33\x2e\x30\x36\x39\x2c\x37\x32\x2e\x36\x35\x35\x2c\x35\x30\x2e\ \x30\x31\x37\x20\x20\x20\x63\x30\x2e\x35\x35\x35\x2c\x32\x2e\x32\ \x32\x39\x2c\x30\x2e\x32\x31\x34\x2c\x34\x2e\x35\x33\x36\x2d\x30\ \x2e\x39\x36\x32\x2c\x36\x2e\x35\x30\x33\x63\x2d\x31\x2e\x31\x38\ \x31\x2c\x31\x2e\x39\x36\x37\x2d\x33\x2e\x30\x36\x32\x2c\x33\x2e\ \x33\x36\x31\x2d\x35\x2e\x32\x39\x31\x2c\x33\x2e\x39\x31\x63\x2d\ \x30\x2e\x36\x37\x2c\x30\x2e\x31\x38\x32\x2d\x31\x2e\x33\x38\x32\ \x2c\x30\x2e\x32\x36\x38\x2d\x32\x2e\x30\x38\x33\x2c\x30\x2e\x32\ \x36\x38\x20\x20\x20\x63\x2d\x33\x2e\x39\x34\x35\x2c\x30\x2d\x37\ \x2e\x33\x36\x37\x2d\x32\x2e\x36\x38\x2d\x38\x2e\x33\x33\x2d\x36\ \x2e\x35\x30\x34\x63\x2d\x38\x2e\x35\x39\x32\x2d\x33\x34\x2e\x33\ \x38\x32\x2d\x35\x36\x2e\x32\x37\x2d\x33\x37\x2e\x30\x31\x2d\x35\ \x36\x2e\x37\x35\x2d\x33\x37\x2e\x30\x32\x37\x63\x2d\x32\x2e\x32\ \x38\x34\x2d\x30\x2e\x30\x39\x35\x2d\x34\x2e\x34\x30\x33\x2d\x31\ \x2e\x30\x37\x38\x2d\x35\x2e\x39\x35\x36\x2d\x32\x2e\x37\x36\x38\ \x20\x20\x20\x63\x2d\x31\x2e\x35\x35\x33\x2d\x31\x2e\x36\x39\x2d\ \x32\x2e\x33\x35\x36\x2d\x33\x2e\x38\x38\x35\x2d\x32\x2e\x32\x35\ \x39\x2d\x36\x2e\x31\x38\x31\x63\x30\x2e\x31\x39\x35\x2d\x34\x2e\ \x36\x31\x32\x2c\x33\x2e\x39\x33\x39\x2d\x38\x2e\x32\x32\x39\x2c\ \x38\x2e\x35\x33\x31\x2d\x38\x2e\x32\x32\x39\x4c\x33\x33\x30\x2e\ \x35\x36\x32\x2c\x31\x36\x32\x2e\x35\x33\x32\x7a\x20\x4d\x37\x33\ \x2e\x36\x38\x36\x2c\x31\x37\x39\x2e\x36\x39\x38\x20\x20\x20\x63\ \x2d\x30\x2e\x34\x37\x32\x2c\x30\x2e\x30\x32\x34\x2d\x34\x38\x2e\ \x31\x39\x33\x2c\x32\x2e\x37\x36\x31\x2d\x35\x36\x2e\x37\x36\x2c\ \x33\x37\x2e\x30\x32\x37\x63\x2d\x30\x2e\x39\x35\x39\x2c\x33\x2e\ \x38\x32\x34\x2d\x34\x2e\x33\x38\x31\x2c\x36\x2e\x35\x30\x34\x2d\ \x38\x2e\x33\x33\x2c\x36\x2e\x35\x30\x34\x63\x2d\x30\x2e\x37\x30\ \x33\x2c\x30\x2d\x31\x2e\x34\x31\x2d\x30\x2e\x30\x38\x36\x2d\x32\ \x2e\x30\x38\x38\x2d\x30\x2e\x32\x36\x38\x20\x20\x20\x63\x2d\x32\ \x2e\x32\x32\x2d\x30\x2e\x35\x34\x39\x2d\x34\x2e\x30\x39\x35\x2d\ \x31\x2e\x39\x34\x33\x2d\x35\x2e\x32\x38\x32\x2d\x33\x2e\x39\x31\ \x63\x2d\x31\x2e\x31\x37\x39\x2d\x31\x2e\x39\x36\x37\x2d\x31\x2e\ \x35\x32\x2d\x34\x2e\x32\x37\x34\x2d\x30\x2e\x39\x36\x35\x2d\x36\ \x2e\x35\x30\x33\x63\x31\x31\x2e\x37\x33\x34\x2d\x34\x36\x2e\x39\ \x34\x37\x2c\x37\x30\x2e\x32\x30\x38\x2d\x34\x39\x2e\x39\x31\x36\ \x2c\x37\x32\x2e\x36\x39\x32\x2d\x35\x30\x2e\x30\x32\x32\x6c\x30\ \x2e\x33\x35\x39\x2d\x30\x2e\x30\x30\x36\x20\x20\x20\x63\x34\x2e\ \x36\x31\x35\x2c\x30\x2c\x38\x2e\x33\x38\x31\x2c\x33\x2e\x36\x31\ \x31\x2c\x38\x2e\x35\x38\x2c\x38\x2e\x32\x31\x37\x43\x38\x32\x2e\ \x30\x39\x38\x2c\x31\x37\x35\x2e\x34\x37\x31\x2c\x37\x38\x2e\x34\ \x32\x2c\x31\x37\x39\x2e\x34\x38\x37\x2c\x37\x33\x2e\x36\x38\x36\ \x2c\x31\x37\x39\x2e\x36\x39\x38\x7a\x20\x4d\x31\x36\x30\x2e\x39\ \x32\x35\x2c\x34\x39\x2e\x31\x30\x36\x20\x20\x20\x63\x2d\x30\x2e\ \x32\x31\x39\x2d\x31\x2e\x37\x30\x38\x2d\x30\x2e\x33\x33\x32\x2d\ \x33\x2e\x34\x32\x38\x2d\x30\x2e\x33\x33\x32\x2d\x35\x2e\x31\x33\ \x33\x63\x30\x2d\x32\x32\x2e\x32\x38\x39\x2c\x31\x38\x2e\x31\x34\ \x2d\x34\x30\x2e\x34\x32\x39\x2c\x34\x30\x2e\x34\x32\x39\x2d\x34\ \x30\x2e\x34\x32\x39\x63\x32\x32\x2e\x32\x39\x33\x2c\x30\x2c\x34\ \x30\x2e\x34\x32\x36\x2c\x31\x38\x2e\x31\x34\x2c\x34\x30\x2e\x34\ \x32\x36\x2c\x34\x30\x2e\x34\x32\x39\x20\x20\x20\x63\x30\x2c\x31\ \x2e\x37\x30\x35\x2d\x30\x2e\x31\x31\x35\x2c\x33\x2e\x34\x31\x39\ \x2d\x30\x2e\x33\x33\x35\x2c\x35\x2e\x31\x33\x33\x63\x32\x34\x2e\ \x32\x36\x36\x2c\x31\x30\x2e\x36\x39\x32\x2c\x34\x35\x2e\x32\x33\ \x36\x2c\x33\x31\x2e\x31\x31\x33\x2c\x35\x39\x2e\x32\x39\x31\x2c\ \x35\x37\x2e\x37\x37\x31\x6c\x31\x2e\x36\x30\x37\x2c\x33\x2e\x30\ \x35\x34\x6c\x2d\x33\x2e\x30\x36\x39\x2c\x31\x2e\x35\x38\x39\x20\ \x20\x20\x63\x2d\x32\x36\x2e\x33\x34\x38\x2c\x31\x33\x2e\x37\x30\ \x37\x2d\x35\x35\x2e\x37\x35\x32\x2c\x32\x30\x2e\x36\x35\x38\x2d\ \x38\x37\x2e\x33\x39\x32\x2c\x32\x30\x2e\x36\x35\x38\x6c\x30\x2c\ \x30\x63\x2d\x34\x36\x2e\x38\x34\x31\x2c\x30\x2d\x38\x36\x2e\x38\ \x30\x34\x2d\x31\x35\x2e\x30\x31\x33\x2d\x31\x30\x36\x2e\x34\x35\ \x37\x2d\x32\x33\x2e\x39\x36\x37\x6c\x2d\x33\x2e\x33\x39\x38\x2d\ \x31\x2e\x35\x35\x33\x6c\x31\x2e\x38\x32\x37\x2d\x33\x2e\x32\x36\ \x31\x20\x20\x20\x43\x31\x31\x37\x2e\x34\x39\x34\x2c\x37\x38\x2e\ \x34\x39\x33\x2c\x31\x33\x37\x2e\x38\x30\x34\x2c\x35\x39\x2e\x33\ \x30\x36\x2c\x31\x36\x30\x2e\x39\x32\x35\x2c\x34\x39\x2e\x31\x30\ \x36\x7a\x20\x4d\x31\x31\x36\x2e\x30\x30\x35\x2c\x32\x39\x38\x2e\ \x32\x33\x35\x63\x31\x2e\x31\x34\x38\x2c\x34\x2e\x35\x39\x32\x2d\ \x31\x2e\x36\x35\x33\x2c\x39\x2e\x32\x36\x32\x2d\x36\x2e\x32\x34\ \x31\x2c\x31\x30\x2e\x34\x31\x32\x20\x20\x20\x63\x2d\x30\x2e\x34\ \x34\x31\x2c\x30\x2e\x31\x32\x32\x2d\x34\x36\x2e\x30\x39\x38\x2c\ \x31\x32\x2e\x33\x39\x38\x2d\x35\x32\x2e\x30\x30\x38\x2c\x35\x33\ \x2e\x30\x33\x37\x63\x2d\x30\x2e\x36\x31\x32\x2c\x34\x2e\x31\x38\ \x39\x2d\x34\x2e\x32\x36\x35\x2c\x37\x2e\x33\x35\x35\x2d\x38\x2e\ \x34\x39\x34\x2c\x37\x2e\x33\x35\x35\x63\x2d\x30\x2e\x34\x31\x31\ \x2c\x30\x2d\x30\x2e\x38\x32\x38\x2d\x30\x2e\x30\x32\x34\x2d\x31\ \x2e\x32\x34\x35\x2d\x30\x2e\x30\x39\x38\x20\x20\x20\x63\x2d\x34\ \x2e\x36\x38\x33\x2d\x30\x2e\x36\x37\x36\x2d\x37\x2e\x39\x34\x36\ \x2d\x35\x2e\x30\x34\x38\x2d\x37\x2e\x32\x36\x35\x2d\x39\x2e\x37\ \x33\x36\x63\x37\x2e\x36\x31\x38\x2d\x35\x32\x2e\x33\x34\x39\x2c\ \x36\x32\x2e\x35\x30\x32\x2d\x36\x36\x2e\x36\x34\x36\x2c\x36\x34\ \x2e\x38\x33\x34\x2d\x36\x37\x2e\x32\x33\x63\x30\x2e\x36\x37\x39\ \x2d\x30\x2e\x31\x37\x2c\x31\x2e\x33\x38\x38\x2d\x30\x2e\x32\x35\ \x36\x2c\x32\x2e\x30\x39\x35\x2d\x30\x2e\x32\x35\x36\x20\x20\x20\ \x43\x31\x31\x31\x2e\x36\x32\x31\x2c\x32\x39\x31\x2e\x37\x33\x32\ \x2c\x31\x31\x35\x2e\x30\x34\x36\x2c\x32\x39\x34\x2e\x33\x39\x39\ \x2c\x31\x31\x36\x2e\x30\x30\x35\x2c\x32\x39\x38\x2e\x32\x33\x35\ \x7a\x20\x4d\x33\x31\x39\x2e\x32\x33\x36\x2c\x32\x32\x32\x2e\x31\ \x34\x35\x63\x2d\x31\x2e\x34\x31\x39\x2c\x30\x2d\x32\x2e\x38\x30\ \x31\x2c\x30\x2e\x31\x35\x32\x2d\x34\x2e\x32\x30\x31\x2c\x30\x2e\ \x32\x31\x39\x20\x20\x20\x63\x32\x2e\x36\x34\x33\x2d\x31\x31\x2e\ \x39\x37\x31\x2c\x34\x2e\x31\x31\x2d\x32\x34\x2e\x35\x32\x37\x2c\ \x34\x2e\x31\x31\x2d\x33\x37\x2e\x35\x30\x33\x63\x30\x2d\x32\x32\ \x2e\x39\x34\x2d\x34\x2e\x32\x38\x37\x2d\x34\x34\x2e\x38\x38\x2d\ \x31\x32\x2e\x37\x33\x32\x2d\x36\x35\x2e\x32\x31\x35\x6c\x2d\x31\ \x2e\x34\x33\x38\x2d\x33\x2e\x34\x34\x39\x6c\x2d\x33\x2e\x33\x31\ \x38\x2c\x31\x2e\x37\x31\x37\x20\x20\x20\x63\x2d\x32\x31\x2e\x38\ \x39\x36\x2c\x31\x31\x2e\x33\x31\x37\x2d\x35\x37\x2e\x33\x30\x35\ \x2c\x31\x37\x2e\x38\x38\x31\x2d\x38\x33\x2e\x31\x34\x37\x2c\x32\ \x30\x2e\x32\x34\x37\x6c\x2d\x32\x2e\x34\x30\x35\x2c\x30\x2e\x32\ \x31\x36\x6c\x2d\x30\x2e\x36\x30\x33\x2c\x32\x2e\x33\x33\x38\x63\ \x2d\x37\x2e\x30\x32\x31\x2c\x32\x37\x2e\x30\x38\x38\x2d\x31\x31\ \x2e\x31\x36\x32\x2c\x36\x30\x2e\x33\x31\x33\x2d\x31\x33\x2e\x35\ \x39\x34\x2c\x39\x31\x2e\x31\x35\x35\x20\x20\x20\x63\x2d\x33\x2e\ \x38\x35\x31\x2d\x34\x39\x2e\x30\x34\x32\x2d\x39\x2e\x35\x30\x38\ \x2d\x39\x30\x2e\x36\x31\x33\x2d\x39\x2e\x36\x30\x33\x2d\x39\x31\ \x2e\x32\x34\x39\x6c\x2d\x30\x2e\x34\x32\x39\x2d\x32\x2e\x39\x34\ \x37\x63\x2d\x34\x35\x2e\x30\x33\x39\x2d\x34\x2e\x38\x32\x2d\x38\ \x33\x2e\x33\x36\x31\x2d\x32\x30\x2e\x33\x37\x32\x2d\x39\x30\x2e\ \x30\x38\x2d\x32\x33\x2e\x34\x33\x35\x6c\x2d\x33\x2e\x31\x35\x37\ \x2d\x31\x2e\x34\x34\x6c\x2d\x31\x2e\x34\x31\x36\x2c\x33\x2e\x31\ \x36\x39\x20\x20\x20\x63\x2d\x39\x2e\x33\x37\x32\x2c\x32\x31\x2e\ \x30\x32\x39\x2d\x31\x34\x2e\x33\x32\x32\x2c\x34\x34\x2e\x38\x35\ \x2d\x31\x34\x2e\x33\x32\x32\x2c\x36\x38\x2e\x38\x39\x33\x63\x30\ \x2c\x37\x39\x2e\x35\x37\x39\x2c\x35\x32\x2e\x39\x38\x38\x2c\x31\ \x34\x34\x2e\x33\x31\x39\x2c\x31\x31\x38\x2e\x31\x32\x2c\x31\x34\ \x34\x2e\x33\x31\x39\x63\x38\x2e\x36\x37\x35\x2c\x30\x2c\x31\x37\ \x2e\x31\x31\x2d\x31\x2e\x32\x33\x2c\x32\x35\x2e\x32\x34\x36\x2d\ \x33\x2e\x34\x31\x20\x20\x20\x63\x35\x2e\x30\x35\x34\x2c\x34\x36\ \x2e\x38\x33\x38\x2c\x34\x34\x2e\x38\x30\x34\x2c\x38\x33\x2e\x34\ \x34\x35\x2c\x39\x32\x2e\x39\x36\x33\x2c\x38\x33\x2e\x34\x34\x35\ \x63\x35\x31\x2e\x35\x36\x39\x2c\x30\x2c\x39\x33\x2e\x35\x32\x39\ \x2d\x34\x31\x2e\x39\x36\x2c\x39\x33\x2e\x35\x32\x39\x2d\x39\x33\ \x2e\x35\x32\x39\x43\x34\x31\x32\x2e\x37\x36\x2c\x32\x36\x34\x2e\ \x31\x31\x32\x2c\x33\x37\x30\x2e\x38\x31\x32\x2c\x32\x32\x32\x2e\ \x31\x34\x35\x2c\x33\x31\x39\x2e\x32\x33\x36\x2c\x32\x32\x32\x2e\ \x31\x34\x35\x7a\x20\x20\x20\x20\x4d\x33\x31\x39\x2e\x32\x33\x36\ \x2c\x34\x30\x32\x2e\x39\x36\x38\x63\x2d\x34\x38\x2e\x31\x33\x35\ \x2c\x30\x2d\x38\x37\x2e\x32\x39\x34\x2d\x33\x39\x2e\x31\x35\x33\ \x2d\x38\x37\x2e\x32\x39\x34\x2d\x38\x37\x2e\x32\x39\x34\x63\x30\ \x2d\x34\x38\x2e\x31\x32\x39\x2c\x33\x39\x2e\x31\x35\x39\x2d\x38\ \x37\x2e\x32\x39\x34\x2c\x38\x37\x2e\x32\x39\x34\x2d\x38\x37\x2e\ \x32\x39\x34\x73\x38\x37\x2e\x32\x39\x34\x2c\x33\x39\x2e\x31\x36\ \x35\x2c\x38\x37\x2e\x32\x39\x34\x2c\x38\x37\x2e\x32\x39\x34\x20\ \x20\x20\x43\x34\x30\x36\x2e\x35\x33\x2c\x33\x36\x33\x2e\x38\x31\ \x35\x2c\x33\x36\x37\x2e\x33\x36\x35\x2c\x34\x30\x32\x2e\x39\x36\ \x38\x2c\x33\x31\x39\x2e\x32\x33\x36\x2c\x34\x30\x32\x2e\x39\x36\ \x38\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x46\x46\x46\x46\ \x46\x22\x2f\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\ \x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\ \x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\ \x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\ \x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ \x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\ \x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\ \x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\ \x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\ \x0a\x3c\x2f\x73\x76\x67\x3e\x0a\ \x00\x00\x03\x68\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\ \x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\ \x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x39\x2e\ \x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\ \x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\ \x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\ \x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x73\x76\x67\x20\x78\ \x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\ \x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\ \x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\ \x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\ \x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x76\x65\x72\ \x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\x3d\x22\x43\ \x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\x22\x20\x79\ \x3d\x22\x30\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\ \x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\ \x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\ \x30\x20\x30\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\ \x39\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\ \x72\x65\x73\x65\x72\x76\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\ \x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x35\ \x31\x32\x70\x78\x22\x3e\x0a\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\ \x4d\x33\x36\x2e\x30\x36\x38\x2c\x32\x30\x2e\x31\x37\x36\x6c\x2d\ \x32\x39\x2d\x32\x30\x43\x36\x2e\x37\x36\x31\x2d\x30\x2e\x30\x33\ \x35\x2c\x36\x2e\x33\x36\x33\x2d\x30\x2e\x30\x35\x37\x2c\x36\x2e\ \x30\x33\x35\x2c\x30\x2e\x31\x31\x34\x43\x35\x2e\x37\x30\x36\x2c\ \x30\x2e\x32\x38\x37\x2c\x35\x2e\x35\x2c\x30\x2e\x36\x32\x37\x2c\ \x35\x2e\x35\x2c\x30\x2e\x39\x39\x39\x76\x34\x30\x20\x20\x63\x30\ \x2c\x30\x2e\x33\x37\x32\x2c\x30\x2e\x32\x30\x36\x2c\x30\x2e\x37\ \x31\x33\x2c\x30\x2e\x35\x33\x35\x2c\x30\x2e\x38\x38\x36\x63\x30\ \x2e\x31\x34\x36\x2c\x30\x2e\x30\x37\x36\x2c\x30\x2e\x33\x30\x36\ \x2c\x30\x2e\x31\x31\x34\x2c\x30\x2e\x34\x36\x35\x2c\x30\x2e\x31\ \x31\x34\x63\x30\x2e\x31\x39\x39\x2c\x30\x2c\x30\x2e\x33\x39\x37\ \x2d\x30\x2e\x30\x36\x2c\x30\x2e\x35\x36\x38\x2d\x30\x2e\x31\x37\ \x37\x6c\x32\x39\x2d\x32\x30\x20\x20\x63\x30\x2e\x32\x37\x31\x2d\ \x30\x2e\x31\x38\x37\x2c\x30\x2e\x34\x33\x32\x2d\x30\x2e\x34\x39\ \x34\x2c\x30\x2e\x34\x33\x32\x2d\x30\x2e\x38\x32\x33\x53\x33\x36\ \x2e\x33\x33\x38\x2c\x32\x30\x2e\x33\x36\x33\x2c\x33\x36\x2e\x30\ \x36\x38\x2c\x32\x30\x2e\x31\x37\x36\x7a\x20\x4d\x37\x2e\x35\x2c\ \x33\x39\x2e\x30\x39\x35\x56\x32\x2e\x39\x30\x34\x6c\x32\x36\x2e\ \x32\x33\x39\x2c\x31\x38\x2e\x30\x39\x36\x4c\x37\x2e\x35\x2c\x33\ \x39\x2e\x30\x39\x35\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\ \x46\x46\x46\x46\x46\x22\x2f\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ \x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\ \x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\ \x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\ \x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\ \x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\ \x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\ \x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\ \x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\ \x3c\x2f\x73\x76\x67\x3e\x0a\ \x00\x00\x02\xe2\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0d\x0a\x3c\x21\x2d\x2d\ \x20\x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\ \x65\x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x39\ \x2e\x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\ \x20\x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\ \x65\x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\ \x6c\x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\ \x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\ \x64\x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x6d\x6c\x6e\x73\ \x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ \x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ \x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\ \x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ \x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x78\x3d\x22\x30\x70\x78\x22\ \x20\x79\x3d\x22\x30\x70\x78\x22\x0d\x0a\x09\x20\x76\x69\x65\x77\ \x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x35\x38\x20\x35\x38\x22\x20\ \x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\x2d\x62\x61\ \x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\x30\x20\x30\ \x20\x35\x38\x20\x35\x38\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\ \x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0d\x0a\ \x3c\x63\x69\x72\x63\x6c\x65\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\ \x69\x6c\x6c\x3a\x23\x44\x37\x35\x41\x34\x41\x3b\x22\x20\x63\x78\ \x3d\x22\x32\x39\x22\x20\x63\x79\x3d\x22\x32\x39\x22\x20\x72\x3d\ \x22\x32\x39\x22\x2f\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x09\x3c\x72\ \x65\x63\x74\x20\x78\x3d\x22\x31\x36\x22\x20\x79\x3d\x22\x31\x36\ \x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\x3a\x23\x46\ \x46\x46\x46\x46\x46\x3b\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\ \x36\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x36\x22\x2f\x3e\ \x0d\x0a\x09\x3c\x70\x61\x74\x68\x20\x73\x74\x79\x6c\x65\x3d\x22\ \x66\x69\x6c\x6c\x3a\x23\x46\x46\x46\x46\x46\x46\x3b\x22\x20\x64\ \x3d\x22\x4d\x34\x33\x2c\x34\x33\x48\x31\x35\x56\x31\x35\x68\x32\ \x38\x56\x34\x33\x7a\x20\x4d\x31\x37\x2c\x34\x31\x68\x32\x34\x56\ \x31\x37\x48\x31\x37\x56\x34\x31\x7a\x22\x2f\x3e\x0d\x0a\x3c\x2f\ \x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\ \x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\ \x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\ \x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\ \x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\ \x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\ \x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\ \x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\ \x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\ \x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\ \x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\ \x0a\ \x00\x00\x04\x03\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\ \x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\ \x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\ \x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\ \x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\ \x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\ \x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\ \x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x21\x44\x4f\x43\x54\ \x59\x50\x45\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\ \x2d\x2f\x2f\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\ \x31\x2e\x31\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\ \x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\ \x68\x69\x63\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\ \x2f\x73\x76\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0a\x3c\x73\x76\ \x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\ \x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\ \x73\x76\x67\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\ \x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ \x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\ \x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ \x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\ \x22\x20\x79\x3d\x22\x30\x70\x78\x22\x20\x77\x69\x64\x74\x68\x3d\ \x22\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ \x35\x31\x32\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x32\x36\x38\x2e\x38\x33\x32\x20\x32\x36\x38\x2e\ \x38\x33\x32\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\ \x6c\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\ \x77\x20\x30\x20\x30\x20\x32\x36\x38\x2e\x38\x33\x32\x20\x32\x36\ \x38\x2e\x38\x33\x32\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\ \x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0a\x3c\x67\ \x3e\x0a\x09\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x32\x36\x35\ \x2e\x31\x37\x31\x2c\x31\x32\x35\x2e\x35\x37\x37\x6c\x2d\x38\x30\ \x2d\x38\x30\x63\x2d\x34\x2e\x38\x38\x31\x2d\x34\x2e\x38\x38\x31\ \x2d\x31\x32\x2e\x37\x39\x37\x2d\x34\x2e\x38\x38\x31\x2d\x31\x37\ \x2e\x36\x37\x38\x2c\x30\x63\x2d\x34\x2e\x38\x38\x32\x2c\x34\x2e\ \x38\x38\x32\x2d\x34\x2e\x38\x38\x32\x2c\x31\x32\x2e\x37\x39\x36\ \x2c\x30\x2c\x31\x37\x2e\x36\x37\x38\x6c\x35\x38\x2e\x36\x36\x31\ \x2c\x35\x38\x2e\x36\x36\x31\x48\x31\x32\x2e\x35\x20\x20\x20\x63\ \x2d\x36\x2e\x39\x30\x33\x2c\x30\x2d\x31\x32\x2e\x35\x2c\x35\x2e\ \x35\x39\x37\x2d\x31\x32\x2e\x35\x2c\x31\x32\x2e\x35\x63\x30\x2c\ \x36\x2e\x39\x30\x32\x2c\x35\x2e\x35\x39\x37\x2c\x31\x32\x2e\x35\ \x2c\x31\x32\x2e\x35\x2c\x31\x32\x2e\x35\x68\x32\x31\x33\x2e\x36\ \x35\x34\x6c\x2d\x35\x38\x2e\x36\x35\x39\x2c\x35\x38\x2e\x36\x36\ \x31\x63\x2d\x34\x2e\x38\x38\x32\x2c\x34\x2e\x38\x38\x32\x2d\x34\ \x2e\x38\x38\x32\x2c\x31\x32\x2e\x37\x39\x36\x2c\x30\x2c\x31\x37\ \x2e\x36\x37\x38\x20\x20\x20\x63\x32\x2e\x34\x34\x2c\x32\x2e\x34\ \x33\x39\x2c\x35\x2e\x36\x34\x2c\x33\x2e\x36\x36\x31\x2c\x38\x2e\ \x38\x33\x39\x2c\x33\x2e\x36\x36\x31\x73\x36\x2e\x33\x39\x38\x2d\ \x31\x2e\x32\x32\x32\x2c\x38\x2e\x38\x33\x39\x2d\x33\x2e\x36\x36\ \x31\x6c\x37\x39\x2e\x39\x39\x38\x2d\x38\x30\x43\x32\x37\x30\x2e\ \x30\x35\x33\x2c\x31\x33\x38\x2e\x33\x37\x33\x2c\x32\x37\x30\x2e\ \x30\x35\x33\x2c\x31\x33\x30\x2e\x34\x35\x39\x2c\x32\x36\x35\x2e\ \x31\x37\x31\x2c\x31\x32\x35\x2e\x35\x37\x37\x7a\x22\x20\x66\x69\ \x6c\x6c\x3d\x22\x23\x46\x46\x44\x41\x34\x34\x22\x2f\x3e\x0a\x3c\ \x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\ \x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\ \x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\ \x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\ \x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\ \x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\ \x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\ \x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\ \x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x2f\x73\x76\x67\ \x3e\x0a\ \x00\x00\x03\x58\ \x3c\ \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\ \x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\x47\x65\x6e\x65\ \x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\x20\x49\x6c\x6c\ \x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\x30\x2e\x30\x2c\ \x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\x50\x6c\x75\x67\ \x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\x72\x73\x69\x6f\ \x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\x64\x20\x30\x29\ \x20\x20\x2d\x2d\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ \x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ \x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ \x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ \x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ \x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ \x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x73\x76\x67\x20\ \x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\ \x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\x20\x78\x6d\x6c\x6e\x73\ \x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ \x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ \x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\ \x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\ \x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x78\x3d\x22\x30\x70\x78\x22\ \x20\x79\x3d\x22\x30\x70\x78\x22\x0d\x0a\x09\x20\x77\x69\x64\x74\ \x68\x3d\x22\x32\x34\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\ \x22\x32\x34\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\ \x30\x20\x30\x20\x32\x34\x20\x32\x34\x22\x20\x65\x6e\x61\x62\x6c\ \x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3d\x22\x6e\x65\ \x77\x20\x30\x20\x30\x20\x32\x34\x20\x32\x34\x22\x20\x78\x6d\x6c\ \x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\ \x22\x3e\x0d\x0a\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x39\x2e\ \x34\x38\x38\x2c\x33\x2e\x39\x30\x37\x48\x32\x34\x76\x32\x2e\x37\ \x39\x31\x48\x39\x2e\x34\x38\x38\x56\x33\x2e\x39\x30\x37\x7a\x20\ \x4d\x31\x31\x2e\x31\x36\x33\x2c\x38\x2e\x33\x37\x32\x48\x32\x34\ \x76\x32\x2e\x37\x39\x31\x48\x31\x31\x2e\x31\x36\x33\x56\x38\x2e\ \x33\x37\x32\x7a\x20\x4d\x31\x31\x2e\x31\x36\x33\x2c\x31\x32\x2e\ \x38\x33\x37\x48\x32\x34\x76\x32\x2e\x37\x39\x31\x48\x31\x31\x2e\ \x31\x36\x33\x56\x31\x32\x2e\x38\x33\x37\x7a\x0d\x0a\x09\x20\x4d\ \x39\x2e\x34\x38\x38\x2c\x31\x37\x2e\x33\x30\x32\x48\x32\x34\x76\ \x32\x2e\x37\x39\x31\x48\x39\x2e\x34\x38\x38\x56\x31\x37\x2e\x33\ \x30\x32\x7a\x20\x4d\x32\x2e\x37\x39\x31\x2c\x31\x30\x2e\x36\x30\ \x34\x63\x30\x2c\x31\x2e\x32\x33\x33\x2c\x31\x2c\x32\x2e\x32\x33\ \x33\x2c\x32\x2e\x32\x33\x33\x2c\x32\x2e\x32\x33\x33\x48\x36\x2e\ \x31\x34\x76\x2d\x32\x2e\x32\x33\x33\x6c\x33\x2e\x37\x36\x37\x2c\ \x33\x2e\x36\x32\x38\x4c\x36\x2e\x31\x34\x2c\x31\x37\x2e\x38\x36\ \x76\x2d\x32\x2e\x32\x33\x32\x48\x35\x2e\x30\x32\x33\x0d\x0a\x09\ \x43\x32\x2e\x32\x34\x39\x2c\x31\x35\x2e\x36\x32\x38\x2c\x30\x2c\ \x31\x33\x2e\x33\x37\x39\x2c\x30\x2c\x31\x30\x2e\x36\x30\x34\x56\ \x38\x2e\x39\x33\x63\x30\x2d\x32\x2e\x37\x37\x34\x2c\x32\x2e\x32\ \x34\x39\x2d\x35\x2e\x30\x32\x33\x2c\x35\x2e\x30\x32\x33\x2d\x35\ \x2e\x30\x32\x33\x48\x36\x2e\x31\x34\x76\x32\x2e\x37\x39\x31\x48\ \x35\x2e\x30\x32\x33\x63\x2d\x31\x2e\x32\x33\x33\x2c\x30\x2d\x32\ \x2e\x32\x33\x33\x2c\x31\x2d\x32\x2e\x32\x33\x33\x2c\x32\x2e\x32\ \x33\x32\x56\x31\x30\x2e\x36\x30\x34\x7a\x22\x2f\x3e\x0d\x0a\x3c\ \x2f\x73\x76\x67\x3e\x0d\x0a\ " qt_resource_name = b"\ \x00\x05\ \x00\x6f\xa6\x53\ \x00\x69\ \x00\x63\x00\x6f\x00\x6e\x00\x73\ \x00\x09\ \x09\xba\x8f\xa7\ \x00\x64\ \x00\x65\x00\x62\x00\x75\x00\x67\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x07\ \x09\xc1\x5a\x27\ \x00\x72\ \x00\x75\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x08\ \x0b\x63\x55\x87\ \x00\x73\ \x00\x74\x00\x6f\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x0f\ \x0a\x15\x41\x07\ \x00\x64\ \x00\x65\x00\x62\x00\x75\x00\x67\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x73\x00\x76\x00\x67\ \x00\x08\ \x0a\xc3\x55\x87\ \x00\x73\ \x00\x74\x00\x65\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\ " qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x02\ \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xdd\ \x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x16\x2f\ \x00\x00\x00\x76\x00\x00\x00\x00\x00\x01\x00\x00\x1a\x36\ \x00\x00\x00\x3c\x00\x00\x00\x00\x00\x01\x00\x00\x13\x49\ " + def qInitResources(): QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + def qCleanupResources(): QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) qInitResources() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/test.py b/plugins/extensions/pykrita/plugin/plugins/scripter/test.py index 7ec3b3bcf3..13823be57e 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/test.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/test.py @@ -1,24 +1,24 @@ # editor.py from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * import syntax app = QApplication([]) editor = QPlainTextEdit() f = QFont("monospace", 10, QFont.Normal) f.setFixedPitch(True) editor.document().setDefaultFont(f) highlight = syntax.PythonHighlighter(editor.document()) editor.show() # Load syntax.py into the editor for demo purposes -#infile = open('syntax.py', 'r') -#editor.setPlainText(infile.read()) +# infile = open('syntax.py', 'r') +# editor.setPlainText(infile.read()) app.exec_() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py index 3a4d918f86..8769d6540a 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py @@ -1,37 +1,37 @@ 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 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); + 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(); + 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/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py index e2f1798bac..e8e516792d 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py @@ -1,39 +1,39 @@ 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)) @property def parent(self): return 'File' 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); + 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(); + 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/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py index 5b2347ba6e..393478e23a 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py @@ -1,38 +1,38 @@ from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox from PyQt5.QtGui import QKeySequence 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)) @property def parent(self): return 'File' 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': + if fileExtension == 'py': document = self.scripter.documentcontroller.openDocument(selectedFile) self.scripter.uicontroller.setDocumentEditor(document) self.scripter.uicontroller.setStatusBar(document.filePath) except: QMessageBox.information(self.scripter.uicontroller.mainWidget, 'Invalid File', 'Open files with .py extension') diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py index 002b3f5f89..a124b52f7f 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py @@ -1,12 +1,13 @@ from PyQt5.QtGui import QTextCursor + class DocWrapper: def __init__(self, textdocument): self.textdocument = textdocument - def write(self, text, view = None): + def write(self, text, view=None): cursor = QTextCursor(self.textdocument) cursor.clearSelection() cursor.movePosition(QTextCursor.End) cursor.insertText(text) diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py index eb69823d50..f02cfa00a4 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py @@ -1,50 +1,50 @@ from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox 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)) @property def parent(self): return 'File' def save(self): text = self.editor.toPlainText() fileName = '' fileExtension = '' if not self.scripter.documentcontroller.activeDocument: try: fileName = QFileDialog.getSaveFileName(self.scripter.uicontroller.mainWidget, 'Save Python File', '', 'Python File (*.py)')[0] if not fileName: return fileExtension = fileName.rsplit('.', maxsplit=1)[1] except: - if not fileExtension=='py': + if not fileExtension == 'py': QMessageBox.information(self.scripter.uicontroller.mainWidget, 'Invalid File', 'Save files with .py extension') return document = self.scripter.documentcontroller.saveDocument(text, fileName) if document: self.scripter.uicontroller.setStatusBar(document.filePath) else: self.scripter.uicontroller.setStatusBar('untitled') return document diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py index e34aef5525..e5f1f7188a 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py @@ -1,151 +1,150 @@ from PyQt5.QtCore import Qt, QRect, QSize, QPoint from PyQt5.QtWidgets import QPlainTextEdit, QTextEdit from PyQt5.QtGui import QIcon, QColor, QPainter, QTextFormat, QFont, QTextCursor from scripter.ui_scripter.editor import linenumberarea, debugarea from scripter import resources_rc class CodeEditor(QPlainTextEdit): DEBUG_AREA_WIDTH = 20 def __init__(self, scripter, parent=None): super(CodeEditor, self).__init__(parent) self.setLineWrapMode(self.NoWrap) self.scripter = scripter self.lineNumberArea = linenumberarea.LineNumberArea(self) self.debugArea = debugarea.DebugArea(self) self.blockCountChanged.connect(self.updateMarginsWidth) self.updateRequest.connect(self.updateLineNumberArea) self.cursorPositionChanged.connect(self.highlightCurrentLine) self.updateMarginsWidth() self.highlightCurrentLine() self.font = "Monospace" self._stepped = False self.debugArrow = QIcon(':/icons/debug_arrow.svg') - def debugAreaWidth(self): return self.DEBUG_AREA_WIDTH def lineNumberAreaWidth(self): """The lineNumberAreaWidth is the quatity of decimal places in blockCount""" digits = 1 max_ = max(1, self.blockCount()) while (max_ >= 10): max_ /= 10 digits += 1 space = 3 + self.fontMetrics().width('9') * digits return space def resizeEvent(self, event): super(CodeEditor, self).resizeEvent(event) qRect = self.contentsRect() self.debugArea.setGeometry(QRect(qRect.left(), qRect.top(), self.debugAreaWidth(), qRect.height())) self.lineNumberArea.setGeometry(QRect(qRect.left() + self.debugAreaWidth(), - qRect.top(), - self.lineNumberAreaWidth(), - qRect.height())) + qRect.top(), + self.lineNumberAreaWidth(), + qRect.height())) def updateMarginsWidth(self): self.setViewportMargins(self.lineNumberAreaWidth() + self.debugAreaWidth(), 0, 0, 0) def updateLineNumberArea(self, rect, dy): """ This slot is invoked when the editors viewport has been scrolled """ if dy: self.lineNumberArea.scroll(0, dy) self.debugArea.scroll(0, dy) else: self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height()) if rect.contains(self.viewport().rect()): self.updateMarginsWidth() def lineNumberAreaPaintEvent(self, event): """This method draws the current lineNumberArea for while""" painter = QPainter(self.lineNumberArea) painter.fillRect(event.rect(), QColor(Qt.lightGray).darker(300)) block = self.firstVisibleBlock() blockNumber = block.blockNumber() top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top()) bottom = top + int(self.blockBoundingRect(block).height()) while block.isValid() and top <= event.rect().bottom(): if block.isVisible() and bottom >= event.rect().top(): number = str(blockNumber + 1) painter.setPen(QColor(Qt.lightGray)) painter.drawText(0, top, self.lineNumberArea.width(), self.fontMetrics().height(), Qt.AlignRight, number) block = block.next() top = bottom bottom = top + int(self.blockBoundingRect(block).height()) blockNumber += 1 def debugAreaPaintEvent(self, event): if self.scripter.debugcontroller.isActive and self.scripter.debugcontroller.currentLine: lineNumber = self.scripter.debugcontroller.currentLine - block = self.document().findBlockByLineNumber(lineNumber-1) + block = self.document().findBlockByLineNumber(lineNumber - 1) if self._stepped: cursor = QTextCursor(block) self.setTextCursor(cursor) self._stepped = False top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top()) bottom = top + int(self.blockBoundingRect(block).height()) painter = QPainter(self.debugArea) - pixmap = self.debugArrow.pixmap(QSize(self.debugAreaWidth()-3, int(self.blockBoundingRect(block).height()))) + pixmap = self.debugArrow.pixmap(QSize(self.debugAreaWidth() - 3, int(self.blockBoundingRect(block).height()))) painter.drawPixmap(QPoint(0, top), pixmap) def highlightCurrentLine(self): """Highlight current line under cursor""" currentSelection = QTextEdit.ExtraSelection() lineColor = QColor(Qt.gray).darker(250) currentSelection.format.setBackground(lineColor) currentSelection.format.setProperty(QTextFormat.FullWidthSelection, True) currentSelection.cursor = self.textCursor() currentSelection.cursor.clearSelection() self.setExtraSelections([currentSelection]) def wheelEvent(self, e): """When the CTRL is pressed during the wheelEvent, zoomIn and zoomOut slots are invoked""" if e.modifiers() == Qt.ControlModifier: delta = e.angleDelta().y() if delta < 0: self.zoomOut() elif delta > 0: self.zoomIn() else: super(CodeEditor, self).wheelEvent(e) @property def font(self): return self._font @font.setter def font(self, font): self._font = font self.setFont(QFont(font, 10)) def setStepped(self, status): self._stepped = status def repaintDebugArea(self): self.debugArea.repaint() diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py index 7519ff7e87..97abf68ae8 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py @@ -1,156 +1,155 @@ # syntax.py: taken from https://wiki.python.org/moin/PyQt/Python%20syntax%20highlighting import sys from PyQt5.QtCore import QRegExp from PyQt5.QtGui import QSyntaxHighlighter class PythonHighlighter (QSyntaxHighlighter): + """Syntax highlighter for the Python language. """ # Python keywords keywords = [ 'and', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'yield', 'None', 'True', 'False', ] # Python operators operators = [ '=', # Comparison '==', '!=', '<', '<=', '>', '>=', # Arithmetic '\+', '-', '\*', '/', '//', '\%', '\*\*', # In-place '\+=', '-=', '\*=', '/=', '\%=', # Bitwise '\^', '\|', '\&', '\~', '>>', '<<', ] # Python braces braces = [ '\{', '\}', '\(', '\)', '\[', '\]', ] def __init__(self, document, syntaxStyle): QSyntaxHighlighter.__init__(self, document) self.syntaxStyle = syntaxStyle self.document = document # Multi-line strings (expression, flag, style) # FIXME: The triple-quotes in these two lines will mess up the # syntax highlighting from this point onward self.tri_single = (QRegExp(r"""'''(?!")"""), 1, 'string2') self.tri_double = (QRegExp(r'''"""(?!')'''), 2, 'string2') rules = [] # Keyword, operator, and brace rules rules += [(r'\b%s\b' % w, 0, 'keyword') - for w in PythonHighlighter.keywords] + for w in PythonHighlighter.keywords] rules += [(r'%s' % o, 0, 'operator') - for o in PythonHighlighter.operators] + for o in PythonHighlighter.operators] rules += [(r'%s' % b, 0, 'brace') - for b in PythonHighlighter.braces] + for b in PythonHighlighter.braces] # All other rules rules += [ # 'self' (r'\bself\b', 0, 'self'), # Double-quoted string, possibly containing escape sequences (r'"[^"\\]*(\\.[^"\\]*)*"', 0, 'string'), # Single-quoted string, possibly containing escape sequences (r"'[^'\\]*(\\.[^'\\]*)*'", 0, 'string'), # 'def' followed by an identifier (r'\bdef\b\s*(\w+)', 1, 'defclass'), # 'class' followed by an identifier (r'\bclass\b\s*(\w+)', 1, 'defclass'), # From '#' until a newline (r'#[^\n]*', 0, 'comment'), # Numeric literals (r'\b[+-]?[0-9]+[lL]?\b', 0, 'numbers'), (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, 'numbers'), (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, 'numbers'), ] # Build a QRegExp for each pattern self.rules = [(QRegExp(pat), index, identifier) - for (pat, index, identifier) in rules] - + for (pat, index, identifier) in rules] def highlightBlock(self, text): """Apply syntax highlighting to the given block of text.""" # Do other syntax formatting for expression, nth, identifier in self.rules: index = expression.indexIn(text, 0) while index >= 0: # We actually want the index of the nth match index = expression.pos(nth) length = len(expression.cap(nth)) self.setFormat(index, length, self.syntaxStyle[identifier]) index = expression.indexIn(text, index + length) self.setCurrentBlockState(0) # Do multi-line strings in_multiline = self.match_multiline(text, *self.tri_single) if not in_multiline: in_multiline = self.match_multiline(text, *self.tri_double) - def match_multiline(self, text, delimiter, in_state, style): """Do highlighting of multi-line strings. ``delimiter`` should be a ``QRegExp`` for triple-single-quotes or triple-double-quotes, and ``in_state`` should be a unique integer to represent the corresponding state changes when inside those strings. Returns True if we're still inside a multi-line string when this function is finished. """ # If inside triple-single quotes, start at 0 if self.previousBlockState() == in_state: start = 0 add = 0 # Otherwise, look for the delimiter on this line else: start = delimiter.indexIn(text) # Move past this match add = delimiter.matchedLength() # As long as there's a delimiter match on this line... while start >= 0: # Look for the ending delimiter end = delimiter.indexIn(text, start + add) # Ending delimiter on this line? if end >= add: length = end - start + add + delimiter.matchedLength() self.setCurrentBlockState(0) # No; multi-line string else: self.setCurrentBlockState(in_state) length = len(text) - start + add # Apply formatting self.setFormat(start, length, self.syntaxStyle[style]) # Look for the next match start = delimiter.indexIn(text, start + length) # Return True if still inside a multi-line string, False otherwise if self.currentBlockState() == in_state: return True else: return False def getSyntaxStyle(self): return self.syntaxStyle def setSyntaxStyle(self, syntaxStyle): self.syntaxStyle = syntaxStyle diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py index 3aae750bd4..ee1de59309 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py @@ -1,56 +1,57 @@ from PyQt5.QtGui import QColor, QTextCharFormat, QFont def format(color, style='', darker=100, lighter=100): """Return a QTextCharFormat with the given attributes. """ _color = QColor(color) _color = _color.darker(darker) _color = _color.lighter(lighter) _format = QTextCharFormat() _format.setForeground(_color) if 'bold' in style: _format.setFontWeight(QFont.Bold) if 'italic' in style: _format.setFontItalic(True) return _format class DefaultSyntaxStyle(object): # Syntax styles that combines with dark backgrounds STYLES = { 'keyword': format('cyan'), 'operator': format('orange'), 'brace': format('gray'), 'defclass': format('black', 'bold'), 'string': format('magenta'), 'string2': format('darkMagenta'), 'comment': format('darkGreen', 'italic'), 'self': format('black', 'italic'), 'numbers': format('brown'), } def __getitem__(self, key): return self.STYLES[key] class PythonVimSyntaxStyle(object): + """ It based in the colorschemme of the Vim editor for python code http://www.vim.org/scripts/script.php?script_id=790 """ # Syntax styles that combines with dark backgrounds STYLES = { 'keyword': format('yellow', darker=125), 'operator': format('magenta', darker=150), 'brace': format('white'), 'defclass': format('orange', 'bold'), 'string': format('green', lighter=160), 'string2': format('lightGray', 'italic', darker=120), 'comment': format('gray', 'italic'), 'self': format('blue', lighter=170), 'numbers': format('yellow', lighter=130), } def __getitem__(self, key): return self.STYLES[key] diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py index c559d7e342..560555532c 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py @@ -1,2 +1,2 @@ widgetClasses = ['outputwidget.outputwidget.OutPutWidget', - 'debuggerwidget.debuggerwidget.DebuggerWidget',] + 'debuggerwidget.debuggerwidget.DebuggerWidget', ] diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py index f1c1445bfe..f107e76d4b 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py @@ -1,33 +1,33 @@ from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem class DebuggerTable(QTableWidget): def __init__(self, parent=None): super(DebuggerTable, self).__init__(parent) self.setColumnCount(4) tableHeader = ['Scope', 'Name', 'Value', 'Type'] self.setHorizontalHeaderLabels(tableHeader) self.setEditTriggers(self.NoEditTriggers) def updateTable(self, data): self.clearContents() self.setRowCount(0) if data and not data.get('quit') and not data.get('exception'): locals_list = data['frame']['locals'] globals_list = data['frame']['globals'] all_variables = {'locals': locals_list, 'globals': globals_list} for scope_key in all_variables: for item in all_variables[scope_key]: for key, value in item.items(): - row = self.rowCount() + row = self.rowCount() self.insertRow(row) self.setItem(row, 0, QTableWidgetItem(str(scope_key))) self.setItem(row, 1, QTableWidgetItem(key)) self.setItem(row, 2, QTableWidgetItem(value['value'])) self.setItem(row, 3, QTableWidgetItem(value['type'])) diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py b/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py index ff9daec7ac..ac48b4f735 100644 --- a/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py +++ b/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py @@ -1,190 +1,189 @@ from PyQt5.QtGui import QTextCursor from PyQt5.QtWidgets import (QToolBar, QMenuBar, QTabWidget, QLabel, QVBoxLayout, QMessageBox, QSplitter) from PyQt5.QtCore import Qt, QObject from scripter.ui_scripter.syntax import syntax, syntaxstyles from scripter.ui_scripter.editor import pythoneditor from scripter import scripterdialog import os import importlib class UIController(object): 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 = QLabel('untitled') self.splitter = QSplitter() self.splitter.setOrientation(Qt.Vertical) self.highlight = syntax.PythonHighlighter(self.editor.document(), syntaxstyles.DefaultSyntaxStyle()) self.scripter = scripter self.loadMenus() self.loadWidgets() self.loadActions() self._readSettings() 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.resize(400, 500) self.mainWidget.setWindowTitle("Scripter") self.mainWidget.setSizeGripEnabled(True) self.mainWidget.show() self.mainWidget.activateWindow() 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) + _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)) for action in self.actions: action['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) + _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): for index in range(self.tabWidget.count()): widget = self.tabWidget.widget(index) if widget.objectName() == widgetName: 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.insertPlainText(document.data) self.editor.moveCursor(QTextCursor.End) def setStatusBar(self, value='untitled'): self.statusBar.setText(value) 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) for action in self.actions: writeSettings = getattr(action['action'], "writeSettings", None) if callable(writeSettings): writeSettings() 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: 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() self.scripter.settings.endGroup() def _saveSettings(self): self.scripter.settings.sync() diff --git a/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py index ec73b3f554..52b33a8a0e 100644 --- a/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py +++ b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py @@ -1,18 +1,20 @@ from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5 import uic from krita import * import os + class SelectionsBagDocker(DockWidget): - def __init__(self): - super().__init__() - widget = QWidget(self) - uic.loadUi(os.path.dirname(os.path.realpath(__file__)) + '/selectionsbagdocker.ui', widget) - self.setWidget(widget) - self.setWindowTitle("Selections bag") - def canvasChanged(self, canvas): - print("Canvas", canvas) + def __init__(self): + super().__init__() + widget = QWidget(self) + uic.loadUi(os.path.dirname(os.path.realpath(__file__)) + '/selectionsbagdocker.ui', widget) + self.setWidget(widget) + self.setWindowTitle("Selections bag") + + def canvasChanged(self, canvas): + print("Canvas", canvas) diff --git a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py index 09476c168d..e84b8a4705 100644 --- a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py +++ b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py @@ -1,113 +1,112 @@ import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * + class DropButton(QPushButton): def __init__(self, parent): super().__init__(parent) self.setFixedSize(64, 64) self.setIconSize(QSize(64, 64)) self.preset = None def selectPreset(self): self.preset = self.presetChooser.currentPreset().name() self.setIcon(QIcon(QPixmap.fromImage(self.presetChooser.currentPreset().image()))) class TenBrushesExtension(Extension): def __init__(self, parent): super().__init__(parent) self.buttons = [] self.actions = [] def setup(self): action = Application.createAction("ten_brushes", "Ten Brushes") action.setToolTip("Assign ten brush presets to ten shortcuts.") action.triggered.connect(self.showDialog) # Read the ten selected brush presets from the settings selectedPresets = Application.readSetting("", "tenbrushes", "").split(',') allPresets = Application.resources("preset") # Setup up to ten actions and give them default shortcuts j = 0 self.actions = [] for i in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']: action = Application.createAction("activate_preset_" + i, "Activate Preset " + i) - #action.setVisible(False) + # action.setVisible(False) action.setMenu("None") action.triggered.connect(self.activatePreset) if j < len(selectedPresets) and selectedPresets[j] in allPresets: action.preset = selectedPresets[j] else: action.preset = None self.actions.append(action) j = j + 1 - def activatePreset(self): print("activatePreset", self.sender().preset) allPresets = Application.resources("preset") if Application.activeWindow() and len(Application.activeWindow().views()) > 0 and self.sender().preset in allPresets: Application.activeWindow().views()[0].activateResource(allPresets[self.sender().preset]) def showDialog(self): self.dialog = QDialog(Application.activeWindow().qwindow()) self.buttonBox = QDialogButtonBox(self.dialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.dialog.reject) vbox = QVBoxLayout(self.dialog) hbox = QHBoxLayout(self.dialog) self.presetChooser = PresetChooser(self.dialog) allPresets = Application.resources("preset") j = 0 self.buttons = [] for i in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']: buttonBox = QVBoxLayout() button = DropButton(self.dialog) button.setObjectName(i) button.clicked.connect(button.selectPreset) button.presetChooser = self.presetChooser if self.actions[j] and self.actions[j].preset and self.actions[j].preset in allPresets: - p = allPresets[self.actions[j].preset]; + p = allPresets[self.actions[j].preset] button.preset = p.name() button.setIcon(QIcon(QPixmap.fromImage(p.image()))) buttonBox.addWidget(button) label = QLabel("Ctrl+Alt+" + i) label.setAlignment(Qt.AlignHCenter) buttonBox.addWidget(label) hbox.addLayout(buttonBox) self.buttons.append(button) j = j + 1 vbox.addLayout(hbox) vbox.addWidget(self.presetChooser) vbox.addWidget(self.buttonBox) vbox.addWidget(QLabel("Select the brush preset, then click on the button you want to use to select the preset")) self.dialog.show() self.dialog.activateWindow() self.dialog.exec_() - def accept(self): i = 0 presets = [] for button in self.buttons: self.actions[i].preset = button.preset presets.append(button.preset) i = i + 1 Application.writeSetting("", "tenbrushes", ','.join(map(str, presets))) self.dialog.accept() Scripter.addExtension(TenBrushesExtension(Application)) diff --git a/plugins/extensions/pykrita/testapi.py b/plugins/extensions/pykrita/testapi.py index a647187534..6cca36f594 100644 --- a/plugins/extensions/pykrita/testapi.py +++ b/plugins/extensions/pykrita/testapi.py @@ -1,31 +1,31 @@ # # Tests the PyKrita API # import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from krita import * + def __main__(args): print("Arguments:", args) Application.setBatchmode(True) print("Batchmode: ", Application.batchmode()) - print("Profiles:", Application.profiles("GRAYA", "U16")); + print("Profiles:", Application.profiles("GRAYA", "U16")) document = Application.openDocument(args[0]) print("Opened", document.fileName(), "WxH", document.width(), document.height(), "resolution", document.xRes(), document.yRes(), "in ppi", document.resolution()) node = document.rootNode() print("Root", node.name(), "opacity", node.opacity()) for child in node.childNodes(): print("\tChild", child.name(), "opacity", node.opacity(), node.blendingMode()) - #r = child.save(child.name() + ".png", document.xRes(), document.yRes()); - #print("Saving result:", r) + # r = child.save(child.name() + ".png", document.xRes(), document.yRes()); + # print("Saving result:", r) for channel in child.channels(): print("Channel", channel.name(), "contents:", len(channel.pixelData(node.bounds()))) document.close() - + document = Application.createDocument(100, 100, "test", "GRAYA", "U16", "") document.setBatchmode(True) - #document.saveAs("test.kra") - + # document.saveAs("test.kra")