""" BBD's Krita script starter This script does the boring stuff involved in creating a script for Krita. it creates * a directory for the script in the correct Krita resources subdirectory, * populates that directory with: -- a __init__.py file, -- a skeleton file for the script proper -- a Manual.html file * creates a .desktop file for the script It also: * correctly imports the script proper nto __init__.py, creates * creates basic skeleton code depending on whether the script is intended to be an extension or a docker * creates skeleton code in the Manual.html file * (optionally) automatically enables the script in the Krita menu Script can be run from the command line. This can be used to bootstrap the script into a Krita menu entry - create a new script called Krita Script Starter, then copy the script (and the .ui file) into the directory you have just created, overwriting the existing files. BBD 16 March 2018 """ import os, sys from PyQt5.QtCore import QStandardPaths, QSettings from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox import PyQt5.uic as uic try: import krita CONTEXT_KRITA = True Krita = Krita # to stop Eric ide complaining about unknown Krita EXTENSION = krita.Extension except ImportError: # script being run in testing environment without Krita CONTEXT_KRITA = False EXTENSION = QWidget #TESTING = True TESTING = False MAIN_KRITA_ID = "Krita Script Starter" MAIN_KRITA_MENU_ENTRY = "Krita Script Starter" SCRIPT_NAME = "script_name" SCRIPT_COMMENT = "script_comment" KRITA_ID = "krita_id" LIBRARY_NAME = "library_name" MENU_ENTRY = "menu_entry" SCRIPT_TYPE = "script_type" # extension v docker PYTHON_FILE_NAME = "python_file" CLASS_NAME = "class_name" # from LIBRARY_NAME get: # the name of the directory # the name of the main python file # the name of the class SCRIPT_EXTENSION = "Extension" SCRIPT_DOCKER = "Docker`" SCRIPT_SETTINGS = 'python' UI_FILE = "bbdkss.ui" def load_ui(ui_file): """ if this script has been distributed with a ui file in the same directory, then find that directory (since it will likely be different from krita's current working directory) and use that to load the ui file return the loaded ui """ abs_path = os.path.dirname(os.path.realpath(__file__)) ui_file = os.path.join(abs_path, UI_FILE) return uic.loadUi(ui_file) DESKTOP_TEMPLATE = """[Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library={library_name} X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name={script_name} Comment={script_comment} """ INIT_TEMPLATE = """from .{library_name} import *""" EXTENSION_TEMPLATE = """#BBD's Krita Script Starter Feb 2018 from krita import Extension EXTENSION_ID = '{krita_id}' MENU_ENTRY = '{menu_entry}' class {class_name}(Extension): def __init__(self, parent): #Always initialise the superclass, This is necessary to create the underlying C++ object super().__init__(parent) def setup(self): pass def createActions(self, window): action = window.createAction(EXTENSION_ID, MENU_ENTRY, "tools/scripts") # parameter 1 = the name that Krita uses to identify the action # parameter 2 = the text to be added to the menu entry for this script # parameter 3 = location of menu entry action.triggered.connect(self.action_triggered) def action_triggered(self): pass # your active code goes here. # And add the extension to Krita's list of extensions: app=Krita.instance() extension={class_name}(parent=app) #instantiate your class app.addExtension(extension) """ DOCKER_TEMPLATE = """#BBD's Krita Script Starter Feb 2018 from krita import DockWidget, DockWidgetFactory, DockWidgetFactoryBase DOCKER_NAME = '{script_name}' DOCKER_ID = '{krita_id}' class {class_name}(DockWidget): def __init__(self): super().__init__() self.setWindowTitle(DOCKER_NAME) def canvasChanged(self, canvas): pass instance = Krita.instance() dock_widget_factory = DockWidgetFactory(DOCKER_ID, DockWidgetFactoryBase.DockRight, {class_name}) instance.addDockWidgetFactory(dock_widget_factory) """ MANUAL_TEMPLATE = """ {script_name}

{script_name}

Tell people about what your script does here. This is an html document so you can format it with html tags.

Usage

Tell people how to use your script here. """ class KritaScriptStarter(EXTENSION): def __init__(self, parent): super().__init__(parent) def setup(self): self.script_abs_path = os.path.dirname(os.path.realpath(__file__)) self.ui_file = os.path.join(self.script_abs_path, UI_FILE) self.ui = load_ui(self.ui_file) self.ui.e_name_of_script.textChanged.connect(self.name_change) self.ui.cancel_button.clicked.connect(self.cancel) self.ui.create_button.clicked.connect(self.create) app_data_location = QStandardPaths.AppDataLocation target_directory = QStandardPaths.writableLocation(app_data_location) if not CONTEXT_KRITA: target_directory = os.path.join(target_directory, "krita") target_directory = os.path.join(target_directory, "pykrita") self.target_directory = target_directory # if CONTEXT_KRITA: # app = Krita.instance() # action = app.createAction(MAIN_KRITA_ID, MAIN_KRITA_MENU_ENTRY) # # parameter 1 = the name that Krita uses to identify the action # # parameter 2 = the text to be added to the menu entry for this script # action.triggered.connect(self.action_triggered) def createActions(self, window): """ Called by Krita to create actions.""" action = window.createAction(MAIN_KRITA_ID, MAIN_KRITA_MENU_ENTRY, "tools/scripts") # parameter 1 = the name that Krita uses to identify the action # parameter 2 = the text to be added to the menu entry for this script # parameter 3 = location of menu entry action.triggered.connect(self.action_triggered) def action_triggered(self): self.ui.show() self.ui.activateWindow() def cancel(self): self.ui.close() def create(self): """Go ahead and create the relevant files. """ if self.ui.e_name_of_script.text().strip() == "": #Don't create script with empty name return def full_dir(path): #convenience function return os.path.join(self.target_directory, path) # should already be done, but just in case: self.calculate_file_names(self.ui.e_name_of_script.text()) menu_entry = self.ui.e_menu_entry.text() if menu_entry.strip() == "": menu_entry = self.ui.e_name_of_script.text() comment = self.ui.e_comment.text() if comment.strip()=="": comment = "Replace this text with your description" values = {SCRIPT_NAME : self.ui.e_name_of_script.text(), SCRIPT_COMMENT : comment, KRITA_ID : "pykrita_"+self.package_name, SCRIPT_TYPE :SCRIPT_DOCKER if self.ui.rb_docker.isChecked() else SCRIPT_EXTENSION, MENU_ENTRY:menu_entry, LIBRARY_NAME : self.package_name, CLASS_NAME: self.class_name } try: # create package directory package_directory = full_dir(self.package_name) # needs to be lowercase and no spaces os.mkdir(package_directory) except FileExistsError: pass # if package directory exists write into it, overwriting existing files. # create desktop file fn = full_dir(self.desktop_fn) with open(fn, 'w+t') as f: f.write(DESKTOP_TEMPLATE.format(**values)) fn = full_dir(self.init_name) with open(fn, 'w+t') as f: f.write(INIT_TEMPLATE.format(**values)) # create main package file fn = full_dir(self.package_file) if self.ui.rb_docker.isChecked(): with open(fn, 'w+t') as f: f.write(DOCKER_TEMPLATE.format(**values)) else: # Default to extension type with open(fn, 'w+t') as f: f.write(EXTENSION_TEMPLATE.format(**values)) # create manual file. fn = full_dir(self.manual_file) with open(fn, 'w+t') as f: f.write(MANUAL_TEMPLATE.format(**values)) # enable script in krita settings (!) if self.ui.cb_enable_script.isChecked(): configPath = QStandardPaths.writableLocation(QStandardPaths.GenericConfigLocation) configPath = os.path.join(configPath, 'kritarc' ) settings = QSettings(configPath, QSettings.IniFormat) settings.beginGroup(SCRIPT_SETTINGS) settings.setValue('enable_'+self.package_name, 'true') settings.endGroup() settings.sync() # notify success # Assemble message title = "Krita Script files created" message = [] message.append("

Directory

") message.append("Project files were created in the directory

%s"%self.target_directory) message.append("

Files Created

The following files were created:

") for f in self.files: message.append("%s

"%f) message.append("%s

"%self.manual_file) message.append("

Location of script

") message.append("Open this file to edit your script:

") script_path = os.path.join(self.target_directory, self.package_file) message.append("%s

"%script_path) message.append("Open this file to edit your Manual:

") script_path = os.path.join(self.target_directory, self.manual_file) message.append("%s

"%script_path) message.append("

Record these locations

") message.append("Make a note of these locations before you click ok.

") message = "\n".join(message) # Display message box if CONTEXT_KRITA: msgbox = QMessageBox() else: msgbox = QMessageBox(self) msgbox.setWindowTitle(title) msgbox.setText(message) msgbox.setStandardButtons(QMessageBox.Ok) msgbox.setDefaultButton(QMessageBox.Ok) msgbox.setIcon(QMessageBox.Information) msgbox.exec() self.ui.close() def name_change(self, text): """ name of script has changed, * calculate consequential names * update the e_files_display edit """ # text = self.e_name_of_script.text() text = text.strip() if len(text) == 0 : return self.calculate_file_names(text) edit_text = self.calculate_edit_text() self.ui.e_files_display.setText(edit_text) def calculate_file_names(self, text): # name of class spam = text.split(" ") for i, s in enumerate(spam): s = s.strip() s = s.lower() try: spam[i] = s[0].upper()+s[1:] except IndexError: continue self.class_name = "".join(spam) # Normalise library name if TESTING: self.package_name = "bbdsss_"+text.lower().replace(" ","_") else: self.package_name = text.lower().replace(" ","_") # create desktop file self.desktop_fn = self.package_name+'.desktop' self.init_name = os.path.join(self.package_name, "__init__.py") self.package_file = os.path.join(self.package_name, self.package_name+".py") self.manual_file = os.path.join(self.package_name, "Manual.html") self.files =[self.desktop_fn, self.init_name, self.package_name, self.package_file, self.manual_file] def calculate_edit_text(self): """ Determine if any of the intended files will collide with existing files. """ conflict_template = """%s - FILE ALREADY EXISTS

""" non_conflict_template = "%s

" file_conflict = False html = ["

Will create the following files:

"] for path in self.files: target_file = os.path.join(self.target_directory, path) if os.path.exists(path): html.append(conflict_template%target_file) file_conflict = True else: html.append(non_conflict_template%target_file) if file_conflict: html.append("""

Warning:

One or more of the files to be created already exists. If you click "Create Script" those files will be deleted and new files created in their place.

""") return "\n".join(html) if __name__ == "__main__": # this includes when the script is run from the command line or # from the Scripter plugin. if CONTEXT_KRITA: # scripter plugin # give up - has the wrong context to create widgets etc. # maybe in the future change this. pass else: app = QApplication([]) extension = KritaScriptStarter(None) extension.setup() extension.action_triggered() sys.exit(app.exec_()) elif CONTEXT_KRITA: # And add the extension to Krita's list of extensions: app=Krita.instance() extension=KritaScriptStarter(parent=app) #instantiate your class app.addExtension(extension)