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_())