Warning, file /graphics/krita/plugins/python/krita_script_starter/krita_script_starter.py was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 #
0002 #  SPDX-License-Identifier: GPL-3.0-or-later
0003 #
0004 
0005 """
0006 BBD's Krita script starter
0007 
0008 This script does the boring stuff involved in creating a script for Krita.
0009 it creates
0010 * a directory for the script in the correct Krita resources subdirectory,
0011 * populates that directory with:
0012 -- a __init__.py file,
0013 -- a skeleton file for the script proper
0014 -- a Manual.html file
0015 * creates a .desktop file for the script
0016 It also:
0017 * correctly imports the script proper nto __init__.py, creates
0018 * creates basic skeleton code depending on whether the script is intended
0019 to be an extension or a docker
0020 * creates skeleton code in the Manual.html file
0021 * (optionally) automatically enables the script in the Krita menu
0022 
0023 Script can be run from the command line. This can be used to
0024 bootstrap the script into a Krita menu entry - create a new script
0025 called Krita Script Starter, then copy the script (and the .ui file)
0026 into the directory you have just created, overwriting the existing
0027 files.
0028 
0029 BBD
0030 16 March 2018
0031 """
0032 
0033 import os
0034 import sys
0035 from PyQt5.QtCore import QStandardPaths, QSettings
0036 from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox
0037 import PyQt5.uic as uic
0038 
0039 try:
0040     import krita
0041     CONTEXT_KRITA = True
0042     EXTENSION = krita.Extension
0043 
0044 except ImportError:
0045     # script being run in testing environment without Krita
0046     CONTEXT_KRITA = False
0047     EXTENSION = QWidget
0048 
0049 # TESTING = True
0050 TESTING = False
0051 
0052 MAIN_KRITA_ID = "Krita Script Starter"
0053 MAIN_KRITA_MENU_ENTRY = "Krita Script Starter"
0054 
0055 SCRIPT_NAME = "script_name"
0056 SCRIPT_COMMENT = "script_comment"
0057 KRITA_ID = "krita_id"
0058 LIBRARY_NAME = "library_name"
0059 MENU_ENTRY = "menu_entry"
0060 SCRIPT_TYPE = "script_type"  # extension v docker
0061 PYTHON_FILE_NAME = "python_file"
0062 CLASS_NAME = "class_name"
0063 
0064 # from LIBRARY_NAME get:
0065 # the name of the directory
0066 # the name of the main python file
0067 # the name of the class
0068 
0069 SCRIPT_EXTENSION = "Extension"
0070 SCRIPT_DOCKER = "Docker`"
0071 
0072 SCRIPT_SETTINGS = 'python'
0073 
0074 UI_FILE = "bbdkss.ui"
0075 
0076 
0077 def load_ui(ui_file):
0078     """If this script has been distributed with a ui file in the same
0079     directory, then find that directory (since it will likely be
0080     different from krita's current working directory) and use that to
0081     load the ui file.
0082 
0083     return the loaded ui
0084     """
0085     abs_path = os.path.dirname(os.path.realpath(__file__))
0086     ui_file = os.path.join(abs_path, UI_FILE)
0087     return uic.loadUi(ui_file)
0088 
0089 
0090 DESKTOP_TEMPLATE = """[Desktop Entry]
0091 Type=Service
0092 ServiceTypes=Krita/PythonPlugin
0093 X-KDE-Library={library_name}
0094 X-Python-2-Compatible=false
0095 X-Krita-Manual=Manual.html
0096 Name={script_name}
0097 Comment={script_comment}
0098 """
0099 
0100 INIT_TEMPLATE_EXTENSION = """from .{library_name} import {class_name}
0101 
0102 # And add the extension to Krita's list of extensions:
0103 app = Krita.instance()
0104 # Instantiate your class:
0105 extension = {class_name}(parent = app)
0106 app.addExtension(extension)
0107 """
0108 
0109 INIT_TEMPLATE_DOCKER = """from .{library_name} import {class_name}
0110 """
0111 
0112 
0113 EXTENSION_TEMPLATE = """# BBD's Krita Script Starter Feb 2018
0114 
0115 from krita import Extension
0116 
0117 EXTENSION_ID = '{krita_id}'
0118 MENU_ENTRY = '{menu_entry}'
0119 
0120 
0121 class {class_name}(Extension):
0122 
0123     def __init__(self, parent):
0124         # Always initialise the superclass.
0125         # This is necessary to create the underlying C++ object
0126         super().__init__(parent)
0127 
0128     def setup(self):
0129         pass
0130 
0131     def createActions(self, window):
0132         action = window.createAction(EXTENSION_ID, MENU_ENTRY, "tools/scripts")
0133         # parameter 1 = the name that Krita uses to identify the action
0134         # parameter 2 = the text to be added to the menu entry for this script
0135         # parameter 3 = location of menu entry
0136         action.triggered.connect(self.action_triggered)
0137 
0138     def action_triggered(self):
0139         pass  # your active code goes here.
0140 """
0141 
0142 DOCKER_TEMPLATE = """#BBD's Krita Script Starter Feb 2018
0143 from krita import DockWidget, DockWidgetFactory, DockWidgetFactoryBase
0144 
0145 DOCKER_NAME = '{script_name}'
0146 DOCKER_ID = '{krita_id}'
0147 
0148 
0149 class {class_name}(DockWidget):
0150 
0151     def __init__(self):
0152         super().__init__()
0153         self.setWindowTitle(DOCKER_NAME)
0154 
0155     def canvasChanged(self, canvas):
0156         pass
0157 
0158 
0159 instance = Krita.instance()
0160 dock_widget_factory = DockWidgetFactory(DOCKER_ID,
0161                                         DockWidgetFactoryBase.DockRight,
0162                                         {class_name})
0163 
0164 instance.addDockWidgetFactory(dock_widget_factory)
0165 """
0166 
0167 MANUAL_TEMPLATE = """<?xml version="1.0" encoding="utf-8"?>
0168 <!DOCTYPE html>
0169 
0170 <html xmlns="http://www.w3.org/1999/xhtml">
0171 <!--BBD's Krita Script Starter, Feb 2018 -->
0172 <head><title>{script_name}</title>
0173 </head>
0174 <body>
0175 <h3>{script_name}</h3>
0176 Tell people about what your script does here.
0177 This is an html document so you can format it with html tags.
0178 <h3>Usage</h3>
0179 Tell people how to use your script here.
0180 
0181 </body>
0182 </html>"""
0183 
0184 
0185 class KritaScriptStarter(EXTENSION):
0186 
0187     def __init__(self, parent):
0188         super().__init__(parent)
0189 
0190     def setup(self):
0191         self.script_abs_path = os.path.dirname(os.path.realpath(__file__))
0192         self.ui_file = os.path.join(self.script_abs_path, UI_FILE)
0193         self.ui = load_ui(self.ui_file)
0194 
0195         self.ui.e_name_of_script.textChanged.connect(self.name_change)
0196         self.ui.cancel_button.clicked.connect(self.cancel)
0197         self.ui.create_button.clicked.connect(self.create)
0198 
0199         target_directory = Krita.instance().getAppDataLocation()
0200         if not CONTEXT_KRITA:
0201             target_directory = os.path.join(target_directory, "krita")
0202         target_directory = os.path.join(target_directory, "pykrita")
0203         self.target_directory = target_directory
0204 
0205     def createActions(self, window):
0206         """ Called by Krita to create actions."""
0207         action = window.createAction(
0208             MAIN_KRITA_ID, MAIN_KRITA_MENU_ENTRY, "tools/scripts")
0209         # parameter 1 = the name that Krita uses to identify the action
0210         # parameter 2 = the text to be added to the menu entry for this script
0211         # parameter 3 = location of menu entry
0212         action.triggered.connect(self.action_triggered)
0213 
0214     def action_triggered(self):
0215         self.ui.show()
0216         self.ui.activateWindow()
0217 
0218     def cancel(self):
0219         self.ui.close()
0220 
0221     def create(self):
0222         """Go ahead and create the relevant files. """
0223 
0224         if self.ui.e_name_of_script.text().strip() == "":
0225             # Don't create script with empty name
0226             return
0227 
0228         def full_dir(path):
0229             # convenience function
0230             return os.path.join(self.target_directory, path)
0231 
0232         # should already be done, but just in case:
0233         self.calculate_file_names(self.ui.e_name_of_script.text())
0234 
0235         menu_entry = self.ui.e_menu_entry.text()
0236         if menu_entry.strip() == "":
0237             menu_entry = self.ui.e_name_of_script.text()
0238 
0239         comment = self.ui.e_comment.text()
0240         if comment.strip() == "":
0241             comment = "Replace this text with your description"
0242 
0243         values = {
0244             SCRIPT_NAME: self.ui.e_name_of_script.text(),
0245             SCRIPT_COMMENT: comment,
0246             KRITA_ID: "pykrita_" + self.package_name,
0247             SCRIPT_TYPE: SCRIPT_DOCKER if self.ui.rb_docker.isChecked() else SCRIPT_EXTENSION,  # noqa: E501
0248             MENU_ENTRY: menu_entry,
0249             LIBRARY_NAME: self.package_name,
0250             CLASS_NAME: self.class_name
0251         }
0252 
0253         try:
0254             # create package directory
0255             package_directory = full_dir(self.package_name)
0256             # needs to be lowercase and no spaces
0257             os.mkdir(package_directory)
0258         except FileExistsError:
0259             # if package directory exists write into it, overwriting
0260             # existing files.
0261             pass
0262 
0263         # create desktop file
0264         fn = full_dir(self.desktop_fn)
0265         with open(fn, 'w+t') as f:
0266             f.write(DESKTOP_TEMPLATE.format(**values))
0267 
0268         fn = full_dir(self.init_name)
0269         with open(fn, 'w+t') as f:
0270             if self.ui.rb_docker.isChecked():
0271                 f.write(INIT_TEMPLATE_DOCKER.format(**values))
0272             else:
0273                 f.write(INIT_TEMPLATE_EXTENSION.format(**values))
0274 
0275 
0276         # create main package file
0277         fn = full_dir(self.package_file)
0278 
0279         if self.ui.rb_docker.isChecked():
0280             with open(fn, 'w+t') as f:
0281                 f.write(DOCKER_TEMPLATE.format(**values))
0282         else:
0283             # Default to extension type
0284             with open(fn, 'w+t') as f:
0285                 f.write(EXTENSION_TEMPLATE.format(**values))
0286 
0287         # create manual file.
0288         fn = full_dir(self.manual_file)
0289         with open(fn, 'w+t') as f:
0290             f.write(MANUAL_TEMPLATE.format(**values))
0291         # enable script in krita settings (!)
0292 
0293         if self.ui.cb_enable_script.isChecked():
0294             Application.writeSetting(SCRIPT_SETTINGS, 'enable_'+self.package_name, 'true')
0295 
0296         # notify success
0297         # Assemble message
0298         title = "Krita Script files created"
0299         message = []
0300         message.append("<h3>Directory</h3>")
0301         message.append("Project files were created in the directory<p>%s"
0302                        % self.target_directory)
0303         message.append(
0304             "<h3>Files Created</h3>The following files were created:<p>")
0305         for f in self.files:
0306             message.append("%s<p>" % f)
0307         message.append("%s<p>" % self.manual_file)
0308         message.append("<h3>Location of script</h3>")
0309         message.append("Open this file to edit your script:<p>")
0310         script_path = os.path.join(self.target_directory, self.package_file)
0311         message.append("%s<p>" % script_path)
0312         message.append("Open this file to edit your Manual:<p>")
0313         script_path = os.path.join(self.target_directory, self.manual_file)
0314         message.append("%s<p>" % script_path)
0315         message.append("<h3>Record these locations</h3>")
0316         message.append(
0317             "Make a note of these locations before you click ok.<p>")
0318         message = "\n".join(message)
0319 
0320         # Display message box
0321         if CONTEXT_KRITA:
0322             msgbox = QMessageBox()
0323         else:
0324             msgbox = QMessageBox(self)
0325         msgbox.setWindowTitle(title)
0326         msgbox.setText(message)
0327         msgbox.setStandardButtons(QMessageBox.Ok)
0328         msgbox.setDefaultButton(QMessageBox.Ok)
0329         msgbox.setIcon(QMessageBox.Information)
0330         msgbox.exec()
0331 
0332         self.ui.close()
0333 
0334     def name_change(self, text):
0335         """
0336         name of script has changed,
0337         * calculate consequential names
0338         * update the e_files_display edit
0339         """
0340 
0341         text = text.strip()
0342         if len(text) == 0:
0343             return
0344 
0345         self.calculate_file_names(text)
0346         edit_text = self.calculate_edit_text()
0347         self.ui.e_files_display.setText(edit_text)
0348 
0349     def calculate_file_names(self, text):
0350         # name of class
0351 
0352         spam = text.split(" ")
0353         for i, s in enumerate(spam):
0354             s = s.strip()
0355             s = s.lower()
0356             try:
0357                 spam[i] = s[0].upper()+s[1:]
0358             except IndexError:
0359                 continue
0360         self.class_name = "".join(spam)
0361 
0362         # Normalise library name
0363         if TESTING:
0364             self.package_name = "bbdsss_"+text.lower().replace(" ", "_")
0365         else:
0366             self.package_name = text.lower().replace(" ", "_")
0367 
0368         # create desktop file
0369         self.desktop_fn = self.package_name+'.desktop'
0370 
0371         self.init_name = os.path.join(self.package_name, "__init__.py")
0372         self.package_file = os.path.join(
0373             self.package_name, self.package_name + ".py")
0374         self.manual_file = os.path.join(self.package_name, "Manual.html")
0375         self.files = [self.desktop_fn, self.init_name,
0376                       self.package_name, self.package_file,
0377                       self.manual_file]
0378 
0379     def calculate_edit_text(self):
0380         """
0381        Determine if any of the intended files will collide with existing files.
0382 
0383         """
0384         conflict_template = "%s - FILE ALREADY EXISTS<p>"
0385         non_conflict_template = "%s<p>"
0386         file_conflict = False
0387 
0388         html = ["<h3>Will create the following files:</h3>"]
0389         for path in self.files:
0390             target_file = os.path.join(self.target_directory, path)
0391             if os.path.exists(path):
0392                 html.append(conflict_template % target_file)
0393                 file_conflict = True
0394             else:
0395                 html.append(non_conflict_template % target_file)
0396 
0397         if file_conflict:
0398             html.append("""<h2><span style="color:red;font-weight:bold">
0399             Warning:</span></h2><p>
0400             <span style="color:red;font-weight:bold">
0401             One or more of the files to be created already exists.
0402             If you click "Create Script" those files will be deleted
0403             and new files created in their place.</span><p>""")
0404 
0405         return "\n".join(html)
0406 
0407 
0408 if __name__ == "__main__":
0409     # this includes when the script is run from the command line or
0410     # from the Scripter plugin.
0411     if CONTEXT_KRITA:
0412         # scripter plugin
0413         # give up - has the wrong context to create widgets etc.
0414         # maybe in the future change this.
0415         pass
0416     else:
0417         app = QApplication([])
0418 
0419         extension = KritaScriptStarter(None)
0420         extension.setup()
0421         extension.action_triggered()
0422         sys.exit(app.exec_())