├── icon_256.png ├── resources ├── copy.png ├── plus.png ├── block.png ├── folder.png ├── sg_logo.png ├── resources.qrc ├── build_resources.sh └── dialog.ui ├── screenshots ├── foto_obs_logo.png ├── foto-multi-namingconvention.png ├── foto-multi-namingconvention-registering.png └── foto-multi-namingconvention-instructions.png ├── .gitattributes ├── .gitignore ├── python ├── app │ ├── ui │ │ ├── __init__.py │ │ ├── dragdroplineedit.py │ │ └── dialog.py │ ├── __init__.py │ └── dialog.py └── __init__.py ├── style.qss ├── app.py ├── info.yml ├── README.md └── LICENSE /icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottb08/foto-multi-namingconvention/HEAD/icon_256.png -------------------------------------------------------------------------------- /resources/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottb08/foto-multi-namingconvention/HEAD/resources/copy.png -------------------------------------------------------------------------------- /resources/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottb08/foto-multi-namingconvention/HEAD/resources/plus.png -------------------------------------------------------------------------------- /resources/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottb08/foto-multi-namingconvention/HEAD/resources/block.png -------------------------------------------------------------------------------- /resources/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottb08/foto-multi-namingconvention/HEAD/resources/folder.png -------------------------------------------------------------------------------- /resources/sg_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottb08/foto-multi-namingconvention/HEAD/resources/sg_logo.png -------------------------------------------------------------------------------- /screenshots/foto_obs_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottb08/foto-multi-namingconvention/HEAD/screenshots/foto_obs_logo.png -------------------------------------------------------------------------------- /screenshots/foto-multi-namingconvention.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottb08/foto-multi-namingconvention/HEAD/screenshots/foto-multi-namingconvention.png -------------------------------------------------------------------------------- /screenshots/foto-multi-namingconvention-registering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottb08/foto-multi-namingconvention/HEAD/screenshots/foto-multi-namingconvention-registering.png -------------------------------------------------------------------------------- /screenshots/foto-multi-namingconvention-instructions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottb08/foto-multi-namingconvention/HEAD/screenshots/foto-multi-namingconvention-instructions.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior for line endings. 2 | * text eol=lf 3 | 4 | # Denote all files that are truly binary and should not be modified. 5 | *.png binary 6 | *.jpg binary 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | sg_logo.png 4 | block.png 5 | copy.png 6 | folder.png 7 | plus.png 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | .idea 10 | dist 11 | build 12 | eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | __pycache__ 22 | 23 | # Installer logs 24 | pip-log.txt 25 | 26 | # Unit test / coverage reports 27 | .coverage 28 | .tox 29 | nosetests.xml 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | -------------------------------------------------------------------------------- /python/app/ui/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | -------------------------------------------------------------------------------- /python/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | from . import app -------------------------------------------------------------------------------- /python/app/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | from . import dialog 12 | -------------------------------------------------------------------------------- /style.qss: -------------------------------------------------------------------------------- 1 | /******************************************************************************************** 2 | 3 | QT Stylesheet for the app. This file will be read every time an app dialog is created via 4 | the show_dialog, show_modal or show_panel methods. 5 | 6 | Certain keywords will be resolved, for example {{SG_HIGHLIGHT_COLOR}}. 7 | For a full list of keywords, call the app.style_constants property at runtime. 8 | 9 | For more info about QT stylesheets, please see http://doc.qt.io/qt-4.8/stylesheet.html 10 | 11 | ********************************************************************************************/ 12 | 13 | 14 | /* Example: 15 | 16 | QListView, QTableView, QScrollArea { 17 | border: 2px solid {{SG_HIGHLIGHT_COLOR}}; 18 | } 19 | 20 | */ 21 | 22 | -------------------------------------------------------------------------------- /python/app/ui/dragdroplineedit.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from sgtk.platform.qt import QtCore, QtGui 3 | 4 | 5 | class DragDropLineEdit(QtGui.QLineEdit): 6 | def __init__(self, parent): 7 | super(DragDropLineEdit, self).__init__(parent) 8 | 9 | self.setDragEnabled(True) 10 | 11 | def dragEnterEvent(self, event): 12 | data = event.mimeData() 13 | urls = data.urls() 14 | if urls and urls[0].scheme() == 'file': 15 | event.acceptProposedAction() 16 | 17 | def dragMoveEvent(self, event): 18 | data = event.mimeData() 19 | urls = data.urls() 20 | if urls and urls[0].scheme() == 'file': 21 | event.acceptProposedAction() 22 | 23 | def dropEvent(self, event): 24 | data = event.mimeData() 25 | urls = data.urls() 26 | if urls and urls[0].scheme() == 'file': 27 | # for some reason, this doubles up the intro slash 28 | filepath = str(urls[0].path())[1:] # Windows/OSX returns a "/" at start of file path ie: "/X:/temp" 29 | 30 | if sys.platform == 'linux2': # Linux doesn't return "/" at start of file path 31 | filepath = str(urls[0].path()) 32 | 33 | self.setText(filepath) 34 | -------------------------------------------------------------------------------- /resources/build_resources.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright (c) 2013 Shotgun Software Inc. 4 | # 5 | # CONFIDENTIAL AND PROPRIETARY 6 | # 7 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 8 | # Source Code License included in this distribution package. See LICENSE. 9 | # By accessing, using, copying or modifying this work you indicate your 10 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 11 | # not expressly granted therein are reserved by Shotgun Software Inc. 12 | 13 | # The path to output all built .py files to: 14 | UI_PYTHON_PATH=../python/app/ui 15 | 16 | 17 | # Helper functions to build UI files 18 | function build_qt { 19 | echo " > Building " $2 20 | 21 | # compile ui to python 22 | $1 $2 > $UI_PYTHON_PATH/$3.py 23 | 24 | # replace PySide imports with tank.platform.qt and remove line containing Created by date 25 | sed -i -e "s/from PySide import/from tank.platform.qt import/g" -e "/# Created:/d" $UI_PYTHON_PATH/$3.py 26 | } 27 | 28 | function build_ui { 29 | build_qt "pyside2-uic --from-imports" "$1.ui" "$1" 30 | } 31 | 32 | function build_res { 33 | build_qt "pyside2-rcc" "$1.qrc" "$1_rc" 34 | } 35 | 36 | 37 | # build UI's: 38 | echo "building user interfaces..." 39 | build_ui dialog 40 | # add any additional .ui files you want converted here! 41 | 42 | # build resources 43 | echo "building resources..." 44 | build_res resources 45 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | 12 | from sgtk.platform import Application 13 | import re 14 | 15 | 16 | class StgkStarterApp(Application): 17 | """ 18 | The app entry point. This class is responsible for intializing and tearing down 19 | the application, handle menu registration etc. 20 | """ 21 | 22 | def init_app(self): 23 | """ 24 | Called as the application is being initialized 25 | """ 26 | 27 | # first, we use the special import_module command to access the app module 28 | # that resides inside the python folder in the app. This is where the actual UI 29 | # and business logic of the app is kept. By using the import_module command, 30 | # toolkit's code reload mechanism will work properly. 31 | app_payload = self.import_module("app") 32 | 33 | # now register a *command*, which is normally a menu entry of some kind on a Shotgun 34 | # menu (but it depends on the engine). The engine will manage this command and 35 | # whenever the user requests the command, it will call out to the callback. 36 | 37 | # first, set up our callback, calling out to a method inside the app module contained 38 | # in the python folder of the app 39 | menu_callback = lambda: app_payload.dialog.show_dialog(self) 40 | 41 | display_name = self.get_setting("display_name") 42 | # "Naming Convention" ---> naming_convention 43 | command_name = display_name.lower() 44 | # replace all non alphanumeric characters by '_' 45 | command_name = re.sub('[^0-9a-zA-Z]+', '_', command_name) 46 | 47 | # register command 48 | menu_options = { 49 | "short_name": command_name, 50 | "description": "Studio Naming Convention Tool", 51 | } 52 | 53 | # now register the command with the engine 54 | self.engine.register_command(display_name, menu_callback, menu_options) 55 | -------------------------------------------------------------------------------- /info.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # Metadata defining the behaviour and requirements for this engine 12 | 13 | # expected fields in the configuration file for this engine 14 | configuration: 15 | display_name: 16 | type: str 17 | description: The name of the app 18 | allows_empty: False 19 | default_value: 'Naming Convention' 20 | 21 | template_definitions: 22 | type: dict 23 | allows_empty: True 24 | description: "A dictionary that defines a definition for a Toolkit template entry. This helps artists know what a template should be used for." 25 | 26 | restrict_entity_types_by_link: 27 | type: dict 28 | allows_empty: True 29 | description: "Specify what entries should show up in the list of links when using the auto completer. 30 | dictionary should contain two key value pairs; entity name and entity field name. 31 | entity: 32 | field: 33 | 34 | For the simple case where you just want to show a given set of 35 | entity types, use :meth:`restrict_entity_types`. This method is 36 | a more complex restriction suitable for workflows around publishing 37 | and review. 38 | 39 | This method will look at the given link field (e.g. ``PublishedFile.entity``) 40 | and inspect the shotgun schema to see which entity types are valid connections 41 | to this field (e.g. in this example which entity types can you can associate 42 | a publish with) and those types will appear in the list of items shown by the 43 | auto completer. 44 | 45 | This is useful when you want to use the context widget in conjunction with 46 | workflows related to for example publishes, versions or notes and you want to 47 | restrict the entities displayed by the auto completer to the ones that have been 48 | configured in the shotgun site schema to be able to associate with the given type." 49 | default_value: {} 50 | 51 | restrict_entity_types: 52 | type: list 53 | description: Restrict which entity types should show up in the list of matches. List of entity names 54 | values: 55 | type: str 56 | allows_empty: True 57 | default_value: [] 58 | 59 | custom_entity_name_remap: 60 | type: dict 61 | allows_empty: True 62 | description: "" 63 | default_value: {} 64 | 65 | tk-engines: 66 | type: dict 67 | allows_empty: False 68 | description: "A list of tk-engines (as defined in the TK schema, can be unsupported engines)" 69 | 70 | # this app works in all engines - it does not contain 71 | # any host application specific commands 72 | supported_engines: 73 | 74 | # the Shotgun fields that this engine needs in order to operate correctly 75 | requires_shotgun_fields: 76 | 77 | # More verbose description of this item 78 | display_name: "Naming Convention Tool" 79 | description: "Toolkit app to helps artists with non-TK DCCs resolve file and directory paths for work files, renders, etc using TK templates" 80 | 81 | # Required minimum versions for this item to run 82 | requires_shotgun_version: 83 | requires_core_version: "v0.14.28" 84 | requires_engine_version: 85 | 86 | # the frameworks required to run this app 87 | frameworks: 88 | - {"name": "tk-framework-qtwidgets", "version": "v2.x.x"} 89 | - {"name": "tk-framework-shotgunutils", "version": "v5.x.x"} 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Naming Convention App for Shotgun Toolkit 2 | ![foto_obs_logo.png](./screenshots/foto_obs_logo.png) 3 | #### The Griffith Observatory and Friends Of The Observatory Satellite Studio is proud to share the Toolkit Naming Convention app that was developed for _Signs of Life_. This new show premieres in May, 2020, in the Samuel Oschin Planetarium at Griffith Observatory, Los Angeles. 4 | #### Astronomically yours! 5 | http://www.griffithobservatory.org/ 6 |
http://www.friendsoftheobservatory.com 7 | *** 8 | ![tk-multi-namingconvention.png](./screenshots/foto-multi-namingconvention.png) 9 | 10 | ##### The Naming Convention Toolkit App provides a convenient and easy way for artists to work with the studio defined Toolkit naming conventions and directory structures. 11 | ##### This is particularly useful for DCC applications that don't have Toolkit integration but need to have files put into the proper directory and follow a certain naming convention. 12 | ##### The "Copy File to File Path" feature allows the artist to copy a single file to the "File Path" defined above. Image sequences are not currently supported. 13 | ##### It can also be useful as a stop gap tool while the pipeline is being built out but production is moving forward. 14 | 15 | ##### Feel free to reach out to me with any questions or use case issues. 16 | 17 | ##### Cheers, 18 | ##### Scott Ballard 19 | * scott@scottballard.net 20 | * https://www.linkedin.com/in/scottballard/ 21 | *** 22 | ## Using the Naming Convention App 23 | 24 | ![tk-multi-namingconvention instructions.png](./screenshots/foto-multi-namingconvention-instructions.png) 25 | 26 | #### Step 1 - Select Shotgun Context (Task) 27 | Using the Toolkit context widget, select the Shotgun Task you are working with that you want to create the associated file or directory 28 | #### Step 2 - Select Application 29 | Select the DCC application you are working with. When you do, Toolkit will register the selected context and application. 30 | ![foto-multi-namingconvention registering.png](./screenshots/foto-multi-namingconvention-registering.png) 31 | #### Step 3 - Select Toolkit Template 32 | Select the associated Toolkit template. The description (below) might be helpful to know which template to use 33 | #### Step 4 - Toolkit Template Description 34 | This field may provide a further description of the Toolkit template selected above and how to use it 35 | #### Step 5 - Toolkit Template Tokens 36 | * Some Toolkit templates require extra token data that can be changed by the artist. Default values are usually provided. 37 | The description above may provide additional information about the tokens 38 | * The artist may need to provide additional values for the extra tokens 39 | #### Step 6 - File Name 40 | * The basename of the file derived from the Toolkit template 41 | #### Step 7 - Directory Path 42 | * The directory derived from the selected Toolkit template 43 | #### Step 8 - File Path 44 | * The full file path derived from the selected Toolkit template 45 | #### Step 9 - Copy File to File Path 46 | * Use this to copy a file into the same file path that is defined in the "File Path" above. 47 | * Drag & Drop a file onto the widget or use the file browser button to the right. 48 | #### Step 10 - Copy File Name 49 | * Button will copy the "File Name" 50 | #### Step 11 - File Browse / Copy Directory Path 51 | * "Browse" button (first) will open a File Explorer or Finder window to the "Directory Path" 52 | * "Copy" button (second) will copy the "Directory Path" 53 | #### Step 12 - Copy File to File Path 54 | * "Plus" button (first) will create the "File Path" on disk 55 | * "Browse" button (second) will open a File Explorer or Finder window to the "File Path" 56 | * "Copy" button (second) will copy the "File Path" 57 | #### Step 13 - Copy File to File Path 58 | * "Browse" button (first) will open a File selection dialog to select the file you want to copy 59 | 60 | *** 61 | ## Installation 62 | **Important:** The Toolkit template path keys are a bit inconsistent. To use this tool your template keys must be in the 63 | format "< entity >_< engine >". 64 | 65 | examples: 66 | shot_maya_work 67 | shot_maya_playblast 68 | shot_maya_render_folder 69 | 70 | #### Setup 71 | If you want to get up and running quickly, follow this simple step: 72 | * tank install_app Project tk-desktop https://github.com/scottb08/foto-multi-namingconvention.git 73 | 74 | #### Known Issues 75 | * Toolkit's context selector widget has a known issue that if the context isn't registered with TK 76 | the "name" of the entity isn't returned internally and the widget fails to populate any values. 77 | 78 | ## Optional Configuration Fields 79 | 80 | #### template_definitions 81 | type: dict 82 | allows_empty: True 83 | description: Toolkit itself does not provide a method of defining descriptions for template definitions, that would be useful for artists. 84 | The Naming Convention tool allows regular expressions to be defined and associated descriptions, 85 | which can be used to define descriptions for those template entries. 86 | 87 | The key is a regex that matches a template key, the value is the description you want to provide the artist. 88 | 89 | examples: 90 | work$: The artist "working" file where you should write your file to 91 | work area$: This is the artist "working" directory where you should store your working files 92 | pub$: The publish file for the selected application 93 | pub area$: The publish directory for the selected application 94 | render$: The render output directory for the selected application 95 | camera pub$: The camera publish path for the selected application 96 | 97 | #### restrict_entity_types_by_link 98 | type: dict 99 | allows_empty: True 100 | description: Specify what entries should show up in the list of links when using the auto completer. 101 | dictionary should contain two key value pairs; entity name and entity field name. 102 | entity: 103 | field: 104 | 105 | For the simple case where you just want to show a given set of 106 | entity types, use :meth:`restrict_entity_types`. This method is 107 | a more complex restriction suitable for workflows around publishing 108 | and review. 109 | 110 | This method will look at the given link field (e.g. ``PublishedFile.entity``) 111 | and inspect the shotgun schema to see which entity types are valid connections 112 | to this field (e.g. in this example which entity types can you can associate 113 | a publish with) and those types will appear in the list of items shown by the 114 | auto completer. 115 | 116 | This is useful when you want to use the context widget in conjunction with 117 | workflows related to for example publishes, versions or notes and you want to 118 | restrict the entities displayed by the auto completer to the ones that have been 119 | configured in the shotgun site schema to be able to associate with the given type. 120 | default_value: {} 121 | 122 | examples: 123 | entity: PublishedFile 124 | field: entity 125 | 126 | #### restrict_entity_types 127 | type: list 128 | description: Restrict which entity types should show up in the list of matches. List of entity names 129 | values: 130 | type: str 131 | allows_empty: True 132 | default_value: [] 133 | 134 | example: 135 | - Asset 136 | - MocapTake 137 | - Shot 138 | 139 | #### custom_entity_name_remap 140 | type: dict 141 | allows_empty: True 142 | description: Remap the SG internal entity name to match the entity token in the TK template definition. 143 | Value gets converted to lower case internally to match TK template definition. 144 | default_value: {} 145 | 146 | examples: 147 | MocapTake: Take (ie: TK template definition take_maya_work) 148 | Shot: MyShot (ie: TK template definition myshot_maya_work) 149 | 150 | #### tk-engines 151 | type: dict 152 | allows_empty: False 153 | description: A dict of tk-engines (as defined in the TK schema, can be unsupported engines) 154 | 155 | examples: 156 | AfterEffects: tk-aftereffects 157 | Data: data <-- non-tk-engine, matches template definition ex: shot_data_asset_element (_) 158 | Mari: tk-mari 159 | Maya: tk-maya 160 | Motion Builder: tk-motionbuilder 161 | Photoshop: tk-ps 162 | Nuke: tk-nuke 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | SHOTGUN PIPELINE TOOLKIT SOURCE CODE LICENSE 2 | 3 | Version: 7/07/2013 4 | 5 | Shotgun Software Inc. ("Company") provides the Shotgun Pipeline Toolkit, 6 | software, including source code, in this package or repository folder (the 7 | "Shotgun Toolkit Code") subject to your acceptance of and compliance with 8 | the following terms and conditions (the "License Terms"). By accessing, 9 | downloading, copying, using or modifying any of the Shotgun Toolkit Code, 10 | you agree to these License Terms. 11 | 12 | Eligibility 13 | 14 | The following license to the Shotgun Toolkit Code is valid only if and while 15 | you are a customer of Company in good standing with either: (a) a current, 16 | paid-up (or free-for-evaluation) subscription or fixed-term license for 17 | Company's Shotgun Platform; or (b) a perpetual license and current, paid-up 18 | maintenance and support contract for the Shotgun Platform. 19 | 20 | Shotgun Toolkit Code License 21 | 22 | Subject to the eligibility criteria above and your compliance with these 23 | License Terms, Company grants to you a non-exclusive, limited license to 24 | reproduce, use, and make derivative works of (including by compiling object 25 | code versions of) the Shotgun Toolkit Code solely for your non-commercial or 26 | internal business purposes in connection with your authorized use of the 27 | Shotgun Platform. 28 | 29 | Company reserves all rights in the Shotgun Toolkit Code not expressly granted 30 | above. These License Terms do not grant or require Company to grant, by 31 | implication, estoppel, or otherwise, any other licenses or rights with respect 32 | to the Shotgun Toolkit Code or any of Company's other software or intellectual 33 | property rights. You agree not to take any action with respect to the Shotgun 34 | Toolkit Code that is not expressly authorized above. 35 | 36 | You must keep intact (and, in the case of copies, reproduce) all copyright 37 | and other proprietary notices, including all references to and copies of these 38 | License Terms, as originally included on, in, or with the Shotgun Toolkit 39 | Code. You must ensure that all derivative works you make of the Shotgun 40 | Toolkit Code contain or are accompanied by comparable and conspicuous notices 41 | that the underlying Shotgun Toolkit Code is the confidential information of 42 | Company and is subject to Company's copyrights and these License Terms. 43 | 44 | No Redistribution or Disclosure 45 | 46 | You acknowledge that the Shotgun Toolkit Code is and contains proprietary and 47 | trade-secret information of Company. You may not distribute, disclose to any 48 | third party, operate for the benefit of third parties (for example, on a 49 | hosted basis), or otherwise commercially exploit the Shotgun Toolkit Code or 50 | any portion or derivative work thereof without Company's separate and express 51 | written consent. For purposes of this restriction, third parties do not 52 | include your employees or agents acting on your behalf who are bound to abide 53 | by these License Terms. 54 | 55 | No Warranties or Support 56 | 57 | The Shotgun Toolkit Code is provided "AS IS" and with all faults. Company 58 | makes no warranties whatsoever, whether express, implied, or otherwise, 59 | concerning the Shotgun Toolkit Code. Company has no obligation to provide 60 | maintenance or technical support for the Shotgun Toolkit Code (unless 61 | otherwise expressly agreed in a separate written agreement between you and 62 | Company). 63 | 64 | Liability 65 | 66 | You agree to be solely responsible for your use and modifications of the 67 | Shotgun Toolkit Code, and for any harm or liability arising out of such use 68 | or modifications, including but not limited to any liability for infringement 69 | of third-party intellectual property rights. 70 | 71 | To the fullest extent permitted under applicable law, you agree that: (a) 72 | Company will not be liable under these License Terms or otherwise for any 73 | direct, indirect, incidental, special, consequential, or exemplary damages, 74 | including but not limited to damages for loss of profits, goodwill, use, data 75 | or other intangible losses, in relation to the Shotgun Toolkit Code or your 76 | use or inability to use the Shotgun Toolkit Code, even if Company has been 77 | advised of the possibility of such damages; and (b) in any event, Company's 78 | aggregate liability under these License Terms or in connection with the 79 | Shotgun Toolkit Code, regardless of the form of action and under any theory 80 | (whether in contract, tort, statutory, or otherwise), will not exceed the 81 | greater of $50 or the amount (if any) that you actually paid for access to 82 | the Shotgun Toolkit Code. 83 | 84 | Ownership 85 | 86 | Company retains sole and exclusive ownership of the Shotgun Toolkit Code and 87 | all copyright and other intellectual property rights therein. You will own any 88 | derivative works you make to the Shotgun Toolkit Code, subject to: (a) the 89 | preceding sentence; and (b) the provisions below regarding ownership of any 90 | code you elect to contribute to Company. 91 | 92 | Contributions 93 | 94 | The following terms apply to any derivative works of the Shotgun Toolkit Code 95 | (or any other materials) that you choose to contribute to Company. 96 | 97 | For good and valuable consideration, receipt of which is acknowledged, you 98 | hereby transfer and assign to Company your entire right, title, and interest 99 | (including all rights under copyright) in: (a) any software code, 100 | documentation, and/or other materials that you deliver to Company for 101 | inclusion in, improvement of, use with, or documentation of Company's software 102 | program(s), including but not limited to any code, documentation, and/or other 103 | materials identified in a contribution form you submit to Company in an 104 | applicable form designated by Company; and (b) any future revisions of such 105 | code, documentation, and/or other materials that you make hereafter. The code, 106 | documentation, other materials, and future revisions described above are 107 | collectively referred to below as the "Contribution." 108 | 109 | As used below, the "Company Programs" means and includes the Company software 110 | program(s) identified on any contribution form you submit to Company, and any 111 | other software into which Company incorporates or with which Company uses or 112 | distributes the Contribution or any version or portion thereof. 113 | 114 | Company grants you a non-exclusive right to continue to modify, make 115 | derivative works of, reproduce, and use the Contribution for your 116 | non-commercial or internal business purposes, and to further Company's 117 | development of Company Programs. This grant does not: (a) limit Company's 118 | rights, (b) grant you any rights with respect to the Company Programs; nor 119 | (c) permit you to distribute, operate for the benefit of third parties (for 120 | example, on a hosted basis), or otherwise commercially exploit the 121 | Contribution. 122 | 123 | You acknowledge that if Company elects to distribute the Contribution or any 124 | version or portion thereof, it may do so on any basis that it chooses 125 | (including under any proprietary or open-source licensing terms), without 126 | further compensation to you. 127 | 128 | You agree that if you have or acquire hereafter any patent or interface 129 | copyright or other intellectual property interest dominating the Contribution 130 | or any Company Programs (or use thereof), such dominating interest will not be 131 | used to undermine the effect of the assignment set forth above. Accordingly, 132 | Company and its direct and indirect licensees are licensed to make, use, sell, 133 | distribute, and otherwise exploit, in the Company Programs and their future 134 | versions and derivative works, without royalty or limitation, the subject 135 | matter of the dominating interest. This license provision will be binding on 136 | you and on any assignees of, or other successors to, the dominating interest. 137 | 138 | You hereby represent and warrant that you are the sole copyright holder for 139 | the Contribution and that you have the right and power to enter into this 140 | contract. You shall indemnify and hold harmless Company and its officers, 141 | employees, and agents against any and all claims, actions or damages 142 | (including attorney's reasonable fees) asserted by or paid to any party on 143 | account of a breach or alleged breach of the foregoing warranty. You make no 144 | other express or implied warranty (including without limitation any warranty 145 | of merchantability or fitness for a particular purpose) regarding the 146 | Contribution. -------------------------------------------------------------------------------- /resources/dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 658 10 | 302 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Qt::Horizontal 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Select Application 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Extra Tokens: 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 0 57 | 0 58 | 59 | 60 | 61 | Application: 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Select Template 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | Toolkit Template: 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 0 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | Template Description: 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | Qt::Horizontal 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | Qt::Horizontal 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | true 131 | 132 | 133 | 134 | 135 | 136 | 137 | color: rgb(67, 131, 168); 138 | font: 75 10pt "Arial"; 139 | 140 | 141 | Directory Path: 142 | 143 | 144 | 145 | 146 | 147 | 148 | color: rgb(67, 131, 168); 149 | font: 75 10pt "Arial"; 150 | 151 | 152 | File Name: 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 0 161 | 0 162 | 163 | 164 | 165 | 166 | 35 167 | 16777215 168 | 169 | 170 | 171 | Copy Path to Clipboard 172 | 173 | 174 | 175 | 176 | 177 | 178 | :/res/copy.png:/res/copy.png 179 | 180 | 181 | 182 | 183 | 184 | 185 | color: rgb(67, 131, 168); 186 | font: 75 10pt "Arial"; 187 | 188 | 189 | File Path: 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 0 198 | 0 199 | 200 | 201 | 202 | 203 | 35 204 | 16777215 205 | 206 | 207 | 208 | Copy Path to Clipboard 209 | 210 | 211 | 212 | 213 | 214 | 215 | :/res/copy.png:/res/copy.png 216 | 217 | 218 | 219 | 220 | 221 | 222 | Create File on Disk 223 | 224 | 225 | 226 | 227 | 228 | 229 | :/res/plus.png:/res/plus.png 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 0 238 | 0 239 | 240 | 241 | 242 | 243 | 35 244 | 16777215 245 | 246 | 247 | 248 | Copy Path to Clipboard 249 | 250 | 251 | 252 | 253 | 254 | 255 | :/res/copy.png:/res/copy.png 256 | 257 | 258 | 259 | 260 | 261 | 262 | Open in File Browser 263 | 264 | 265 | 266 | 267 | 268 | 269 | :/res/folder.png:/res/folder.png 270 | 271 | 272 | 273 | 274 | 275 | 276 | Open in File Browser 277 | 278 | 279 | 280 | 281 | 282 | 283 | :/res/folder.png:/res/folder.png 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | true 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | true 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | Qt::Horizontal 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | color: rgb(67, 131, 168); 323 | font: 75 10pt "Arial"; 324 | 325 | 326 | Copy File To File Path 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | Browse to a file to copy 337 | 338 | 339 | 340 | 341 | 342 | 343 | :/res/folder.png:/res/folder.png 344 | 345 | 346 | 347 | 348 | 349 | 350 | Copy given file (left) to the file path described above 351 | 352 | 353 | 354 | 355 | 356 | 357 | :/res/copy.png:/res/copy.png 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | -------------------------------------------------------------------------------- /python/app/ui/dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'dialog.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 5.15.2 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide2.QtCore import * 12 | from PySide2.QtGui import * 13 | from PySide2.QtWidgets import * 14 | 15 | from . import resources_rc 16 | 17 | class Ui_Form(object): 18 | def setupUi(self, Form): 19 | if not Form.objectName(): 20 | Form.setObjectName(u"Form") 21 | Form.resize(658, 302) 22 | self.verticalLayout = QVBoxLayout(Form) 23 | self.verticalLayout.setObjectName(u"verticalLayout") 24 | self.contentWidgetHolder = QWidget(Form) 25 | self.contentWidgetHolder.setObjectName(u"contentWidgetHolder") 26 | self.contentWidgetVerticalLayout = QVBoxLayout(self.contentWidgetHolder) 27 | self.contentWidgetVerticalLayout.setContentsMargins(0, 0, 0, 0) 28 | self.contentWidgetVerticalLayout.setObjectName(u"contentWidgetVerticalLayout") 29 | 30 | self.verticalLayout.addWidget(self.contentWidgetHolder) 31 | 32 | self.line_3 = QFrame(Form) 33 | self.line_3.setObjectName(u"line_3") 34 | self.line_3.setFrameShape(QFrame.HLine) 35 | self.line_3.setFrameShadow(QFrame.Sunken) 36 | 37 | self.verticalLayout.addWidget(self.line_3) 38 | 39 | self.widget_2 = QWidget(Form) 40 | self.widget_2.setObjectName(u"widget_2") 41 | self.gridLayout = QGridLayout(self.widget_2) 42 | self.gridLayout.setObjectName(u"gridLayout") 43 | self.appComboBox = QComboBox(self.widget_2) 44 | self.appComboBox.addItem("") 45 | self.appComboBox.setObjectName(u"appComboBox") 46 | 47 | self.gridLayout.addWidget(self.appComboBox, 0, 1, 1, 1) 48 | 49 | self.label_3 = QLabel(self.widget_2) 50 | self.label_3.setObjectName(u"label_3") 51 | 52 | self.gridLayout.addWidget(self.label_3, 4, 0, 1, 1) 53 | 54 | self.label = QLabel(self.widget_2) 55 | self.label.setObjectName(u"label") 56 | sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) 57 | sizePolicy.setHorizontalStretch(0) 58 | sizePolicy.setVerticalStretch(0) 59 | sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) 60 | self.label.setSizePolicy(sizePolicy) 61 | 62 | self.gridLayout.addWidget(self.label, 0, 0, 1, 1) 63 | 64 | self.tkTemplateComboBox = QComboBox(self.widget_2) 65 | self.tkTemplateComboBox.addItem("") 66 | self.tkTemplateComboBox.setObjectName(u"tkTemplateComboBox") 67 | 68 | self.gridLayout.addWidget(self.tkTemplateComboBox, 1, 1, 1, 1) 69 | 70 | self.label_7 = QLabel(self.widget_2) 71 | self.label_7.setObjectName(u"label_7") 72 | 73 | self.gridLayout.addWidget(self.label_7, 1, 0, 1, 1) 74 | 75 | self.extraTokensWidget = QWidget(self.widget_2) 76 | self.extraTokensWidget.setObjectName(u"extraTokensWidget") 77 | self.extraTokensWidgetLayout = QGridLayout(self.extraTokensWidget) 78 | self.extraTokensWidgetLayout.setContentsMargins(0, 0, 0, 0) 79 | self.extraTokensWidgetLayout.setObjectName(u"extraTokensWidgetLayout") 80 | 81 | self.gridLayout.addWidget(self.extraTokensWidget, 4, 1, 1, 1) 82 | 83 | self.label_8 = QLabel(self.widget_2) 84 | self.label_8.setObjectName(u"label_8") 85 | 86 | self.gridLayout.addWidget(self.label_8, 2, 0, 1, 1) 87 | 88 | self.descriptionLabel = QLabel(self.widget_2) 89 | self.descriptionLabel.setObjectName(u"descriptionLabel") 90 | 91 | self.gridLayout.addWidget(self.descriptionLabel, 2, 1, 1, 1) 92 | 93 | self.line_4 = QFrame(self.widget_2) 94 | self.line_4.setObjectName(u"line_4") 95 | self.line_4.setFrameShape(QFrame.HLine) 96 | self.line_4.setFrameShadow(QFrame.Sunken) 97 | 98 | self.gridLayout.addWidget(self.line_4, 3, 0, 1, 2) 99 | 100 | 101 | self.verticalLayout.addWidget(self.widget_2) 102 | 103 | self.line_2 = QFrame(Form) 104 | self.line_2.setObjectName(u"line_2") 105 | self.line_2.setFrameShape(QFrame.HLine) 106 | self.line_2.setFrameShadow(QFrame.Sunken) 107 | 108 | self.verticalLayout.addWidget(self.line_2) 109 | 110 | self.filePathWidgetGroup = QWidget(Form) 111 | self.filePathWidgetGroup.setObjectName(u"filePathWidgetGroup") 112 | self.gridLayout_3 = QGridLayout(self.filePathWidgetGroup) 113 | self.gridLayout_3.setObjectName(u"gridLayout_3") 114 | self.filePathLineEdit = QLineEdit(self.filePathWidgetGroup) 115 | self.filePathLineEdit.setObjectName(u"filePathLineEdit") 116 | self.filePathLineEdit.setReadOnly(True) 117 | 118 | self.gridLayout_3.addWidget(self.filePathLineEdit, 2, 1, 1, 1) 119 | 120 | self.label_5 = QLabel(self.filePathWidgetGroup) 121 | self.label_5.setObjectName(u"label_5") 122 | self.label_5.setStyleSheet(u"color: rgb(67, 131, 168);\n" 123 | "font: 75 10pt \"Arial\";") 124 | 125 | self.gridLayout_3.addWidget(self.label_5, 1, 0, 1, 1) 126 | 127 | self.label_4 = QLabel(self.filePathWidgetGroup) 128 | self.label_4.setObjectName(u"label_4") 129 | self.label_4.setStyleSheet(u"color: rgb(67, 131, 168);\n" 130 | "font: 75 10pt \"Arial\";") 131 | 132 | self.gridLayout_3.addWidget(self.label_4, 0, 0, 1, 1) 133 | 134 | self.directoryPathCopyButton = QPushButton(self.filePathWidgetGroup) 135 | self.directoryPathCopyButton.setObjectName(u"directoryPathCopyButton") 136 | sizePolicy1 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 137 | sizePolicy1.setHorizontalStretch(0) 138 | sizePolicy1.setVerticalStretch(0) 139 | sizePolicy1.setHeightForWidth(self.directoryPathCopyButton.sizePolicy().hasHeightForWidth()) 140 | self.directoryPathCopyButton.setSizePolicy(sizePolicy1) 141 | self.directoryPathCopyButton.setMaximumSize(QSize(35, 16777215)) 142 | icon = QIcon() 143 | icon.addFile(u":/res/copy.png", QSize(), QIcon.Normal, QIcon.Off) 144 | self.directoryPathCopyButton.setIcon(icon) 145 | 146 | self.gridLayout_3.addWidget(self.directoryPathCopyButton, 1, 4, 1, 1) 147 | 148 | self.label_6 = QLabel(self.filePathWidgetGroup) 149 | self.label_6.setObjectName(u"label_6") 150 | self.label_6.setStyleSheet(u"color: rgb(67, 131, 168);\n" 151 | "font: 75 10pt \"Arial\";") 152 | 153 | self.gridLayout_3.addWidget(self.label_6, 2, 0, 1, 1) 154 | 155 | self.fileNameCopyButton = QPushButton(self.filePathWidgetGroup) 156 | self.fileNameCopyButton.setObjectName(u"fileNameCopyButton") 157 | sizePolicy1.setHeightForWidth(self.fileNameCopyButton.sizePolicy().hasHeightForWidth()) 158 | self.fileNameCopyButton.setSizePolicy(sizePolicy1) 159 | self.fileNameCopyButton.setMaximumSize(QSize(35, 16777215)) 160 | self.fileNameCopyButton.setIcon(icon) 161 | 162 | self.gridLayout_3.addWidget(self.fileNameCopyButton, 0, 4, 1, 1) 163 | 164 | self.createFileButton = QPushButton(self.filePathWidgetGroup) 165 | self.createFileButton.setObjectName(u"createFileButton") 166 | icon1 = QIcon() 167 | icon1.addFile(u":/res/plus.png", QSize(), QIcon.Normal, QIcon.Off) 168 | self.createFileButton.setIcon(icon1) 169 | 170 | self.gridLayout_3.addWidget(self.createFileButton, 2, 2, 1, 1) 171 | 172 | self.filePathCopyButton = QPushButton(self.filePathWidgetGroup) 173 | self.filePathCopyButton.setObjectName(u"filePathCopyButton") 174 | sizePolicy1.setHeightForWidth(self.filePathCopyButton.sizePolicy().hasHeightForWidth()) 175 | self.filePathCopyButton.setSizePolicy(sizePolicy1) 176 | self.filePathCopyButton.setMaximumSize(QSize(35, 16777215)) 177 | self.filePathCopyButton.setIcon(icon) 178 | 179 | self.gridLayout_3.addWidget(self.filePathCopyButton, 2, 4, 1, 1) 180 | 181 | self.filePathOpenButton = QPushButton(self.filePathWidgetGroup) 182 | self.filePathOpenButton.setObjectName(u"filePathOpenButton") 183 | icon2 = QIcon() 184 | icon2.addFile(u":/res/folder.png", QSize(), QIcon.Normal, QIcon.Off) 185 | self.filePathOpenButton.setIcon(icon2) 186 | 187 | self.gridLayout_3.addWidget(self.filePathOpenButton, 2, 3, 1, 1) 188 | 189 | self.directoryPathOpenButton = QPushButton(self.filePathWidgetGroup) 190 | self.directoryPathOpenButton.setObjectName(u"directoryPathOpenButton") 191 | self.directoryPathOpenButton.setIcon(icon2) 192 | 193 | self.gridLayout_3.addWidget(self.directoryPathOpenButton, 1, 3, 1, 1) 194 | 195 | self.dirPathLineEdit = QLineEdit(self.filePathWidgetGroup) 196 | self.dirPathLineEdit.setObjectName(u"dirPathLineEdit") 197 | self.dirPathLineEdit.setReadOnly(True) 198 | 199 | self.gridLayout_3.addWidget(self.dirPathLineEdit, 1, 1, 1, 2) 200 | 201 | self.fileNameLineEdit = QLineEdit(self.filePathWidgetGroup) 202 | self.fileNameLineEdit.setObjectName(u"fileNameLineEdit") 203 | self.fileNameLineEdit.setReadOnly(True) 204 | 205 | self.gridLayout_3.addWidget(self.fileNameLineEdit, 0, 1, 1, 3) 206 | 207 | 208 | self.verticalLayout.addWidget(self.filePathWidgetGroup) 209 | 210 | self.line_5 = QFrame(Form) 211 | self.line_5.setObjectName(u"line_5") 212 | self.line_5.setFrameShape(QFrame.HLine) 213 | self.line_5.setFrameShadow(QFrame.Sunken) 214 | 215 | self.verticalLayout.addWidget(self.line_5) 216 | 217 | self.horizontalLayout = QHBoxLayout() 218 | self.horizontalLayout.setObjectName(u"horizontalLayout") 219 | self.label_9 = QLabel(Form) 220 | self.label_9.setObjectName(u"label_9") 221 | self.label_9.setStyleSheet(u"color: rgb(67, 131, 168);\n" 222 | "font: 75 10pt \"Arial\";") 223 | 224 | self.horizontalLayout.addWidget(self.label_9) 225 | 226 | self.copyFileLineEdit = QLineEdit(Form) 227 | self.copyFileLineEdit.setObjectName(u"copyFileLineEdit") 228 | 229 | self.horizontalLayout.addWidget(self.copyFileLineEdit) 230 | 231 | self.copyFilePathOpenButton = QPushButton(Form) 232 | self.copyFilePathOpenButton.setObjectName(u"copyFilePathOpenButton") 233 | self.copyFilePathOpenButton.setIcon(icon2) 234 | 235 | self.horizontalLayout.addWidget(self.copyFilePathOpenButton) 236 | 237 | self.copyFileToFileButton = QPushButton(Form) 238 | self.copyFileToFileButton.setObjectName(u"copyFileToFileButton") 239 | self.copyFileToFileButton.setIcon(icon) 240 | 241 | self.horizontalLayout.addWidget(self.copyFileToFileButton) 242 | 243 | 244 | self.verticalLayout.addLayout(self.horizontalLayout) 245 | 246 | 247 | self.retranslateUi(Form) 248 | 249 | QMetaObject.connectSlotsByName(Form) 250 | # setupUi 251 | 252 | def retranslateUi(self, Form): 253 | Form.setWindowTitle(QCoreApplication.translate("Form", u"Form", None)) 254 | self.appComboBox.setItemText(0, QCoreApplication.translate("Form", u"Select Application", None)) 255 | 256 | self.label_3.setText(QCoreApplication.translate("Form", u"Extra Tokens:", None)) 257 | self.label.setText(QCoreApplication.translate("Form", u"Application:", None)) 258 | self.tkTemplateComboBox.setItemText(0, QCoreApplication.translate("Form", u"Select Template", None)) 259 | 260 | self.label_7.setText(QCoreApplication.translate("Form", u"Toolkit Template:", None)) 261 | self.label_8.setText(QCoreApplication.translate("Form", u"Template Description:", None)) 262 | self.descriptionLabel.setText("") 263 | self.filePathLineEdit.setText("") 264 | self.label_5.setText(QCoreApplication.translate("Form", u"Directory Path:", None)) 265 | self.label_4.setText(QCoreApplication.translate("Form", u"File Name:", None)) 266 | #if QT_CONFIG(tooltip) 267 | self.directoryPathCopyButton.setToolTip(QCoreApplication.translate("Form", u"Copy Path to Clipboard", None)) 268 | #endif // QT_CONFIG(tooltip) 269 | self.directoryPathCopyButton.setText("") 270 | self.label_6.setText(QCoreApplication.translate("Form", u"File Path:", None)) 271 | #if QT_CONFIG(tooltip) 272 | self.fileNameCopyButton.setToolTip(QCoreApplication.translate("Form", u"Copy Path to Clipboard", None)) 273 | #endif // QT_CONFIG(tooltip) 274 | self.fileNameCopyButton.setText("") 275 | #if QT_CONFIG(tooltip) 276 | self.createFileButton.setToolTip(QCoreApplication.translate("Form", u"Create File on Disk", None)) 277 | #endif // QT_CONFIG(tooltip) 278 | self.createFileButton.setText("") 279 | #if QT_CONFIG(tooltip) 280 | self.filePathCopyButton.setToolTip(QCoreApplication.translate("Form", u"Copy Path to Clipboard", None)) 281 | #endif // QT_CONFIG(tooltip) 282 | self.filePathCopyButton.setText("") 283 | #if QT_CONFIG(tooltip) 284 | self.filePathOpenButton.setToolTip(QCoreApplication.translate("Form", u"Open in File Browser", None)) 285 | #endif // QT_CONFIG(tooltip) 286 | self.filePathOpenButton.setText("") 287 | #if QT_CONFIG(tooltip) 288 | self.directoryPathOpenButton.setToolTip(QCoreApplication.translate("Form", u"Open in File Browser", None)) 289 | #endif // QT_CONFIG(tooltip) 290 | self.directoryPathOpenButton.setText("") 291 | self.dirPathLineEdit.setText("") 292 | self.fileNameLineEdit.setText("") 293 | self.label_9.setText(QCoreApplication.translate("Form", u"Copy File To File Path", None)) 294 | #if QT_CONFIG(tooltip) 295 | self.copyFilePathOpenButton.setToolTip(QCoreApplication.translate("Form", u"Browse to a file to copy", None)) 296 | #endif // QT_CONFIG(tooltip) 297 | self.copyFilePathOpenButton.setText("") 298 | #if QT_CONFIG(tooltip) 299 | self.copyFileToFileButton.setToolTip(QCoreApplication.translate("Form", u"Copy given file (left) to the file path described above", None)) 300 | #endif // QT_CONFIG(tooltip) 301 | self.copyFileToFileButton.setText("") 302 | # retranslateUi 303 | 304 | -------------------------------------------------------------------------------- /python/app/dialog.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import sys 12 | import os 13 | import re 14 | import shutil 15 | import subprocess 16 | from pprint import pprint, pformat 17 | 18 | import sgtk 19 | # # by importing QT from sgtk rather than directly, we ensure that 20 | # # the code will be compatible with both PySide and PyQt. 21 | from sgtk.platform.qt import QtCore, QtGui 22 | from .ui.dialog import Ui_Form 23 | 24 | # import the context_selector module from the qtwidgets framework 25 | context_selector = sgtk.platform.import_framework("tk-framework-qtwidgets", "context_selector") 26 | 27 | # import the context_selector module from the qtwidgets framework 28 | overlay_widget = sgtk.platform.import_framework("tk-framework-qtwidgets", "overlay_widget") 29 | 30 | # import the task_manager module from shotgunutils framework 31 | task_manager = sgtk.platform.import_framework("tk-framework-shotgunutils", "task_manager") 32 | 33 | # import the shotgun_globals module from shotgunutils framework 34 | shotgun_globals = sgtk.platform.import_framework("tk-framework-shotgunutils", "shotgun_globals") 35 | 36 | 37 | def show_dialog(app_instance): 38 | """ 39 | Shows the main dialog window. 40 | """ 41 | # in order to handle UIs seamlessly, each toolkit engine has methods for launching 42 | # different types of windows. By using these methods, your windows will be correctly 43 | # decorated and handled in a consistent fashion by the system. 44 | 45 | # we pass the dialog class to this method and leave the actual construction 46 | # to be carried out by toolkit. 47 | app_instance.engine.show_dialog("Naming Convention Tool", app_instance, AppDialog) 48 | 49 | 50 | class AppDialog(QtGui.QWidget): 51 | """ 52 | Main application dialog window 53 | """ 54 | 55 | def __init__(self): 56 | """ 57 | Constructor 58 | """ 59 | # first, call the base class and let it do its thing. 60 | QtGui.QWidget.__init__(self) 61 | 62 | self.context = None 63 | self.template = None 64 | self.fields = None 65 | self.ctx = None 66 | 67 | # Logging 68 | self.log = sgtk.platform.get_logger(__name__) 69 | 70 | # now load in the UI that was created in the UI designer 71 | self.ui = Ui_Form() 72 | self.ui.setupUi(self) 73 | 74 | # most of the useful accessors are available through the Application class instance 75 | # it is often handy to keep a reference to this. You can get it via the following method: 76 | self._app = sgtk.platform.current_bundle() 77 | 78 | # Get Application definitions 79 | self.applications = self._app.get_setting("tk-engines") 80 | 81 | self.template_definitions = self._app.get_setting("template_definitions") 82 | 83 | self.custom_entity_name_remap = self._app.get_setting("custom_entity_name_remap") 84 | 85 | self.restrict_entity_types = self._app.get_setting("restrict_entity_types") 86 | if self.restrict_entity_types: 87 | self.log.info('restrict_entity_types to: {}'.format(', '.join(self.restrict_entity_types))) 88 | 89 | self.restrict_entity_types_by_link = self._app.get_setting("restrict_entity_types_by_link") 90 | 91 | if self.restrict_entity_types_by_link: 92 | if 'entity' not in self.restrict_entity_types_by_link or 'field' not in self.restrict_entity_types_by_link: 93 | raise ValueError('"entity" or "field" key not defined for restrict_entity_types_by_link') 94 | 95 | self.log.info('restrict_entity_types_by_link to entity: {}, field: {}'.format(self.restrict_entity_types_by_link['entity'], 96 | self.restrict_entity_types_by_link['field'])) 97 | 98 | if self.restrict_entity_types and self.restrict_entity_types_by_link: 99 | raise ValueError('Please use either restrict_entity_types_by_link OR restrict_entity_types, but not both.') 100 | 101 | # via the self._app handle we can for example access: 102 | # - The engine, via self._app.engine 103 | # - A Shotgun API instance, via self._app.shotgun 104 | # - A tk API instance, via self._app.tk 105 | 106 | # create a background task manager for each of our components to use 107 | self._task_manager = task_manager.BackgroundTaskManager(self) 108 | self._task_manager.start_processing() 109 | 110 | # register it with the globals module so that it can 111 | # use it to fetch data 112 | shotgun_globals.register_bg_task_manager(self._task_manager) 113 | 114 | self._context_widget = context_selector.ContextWidget(self) 115 | self._context_widget.set_up(self._task_manager) 116 | 117 | # Disable "Link" widget in context selector so end user have to use "Task" widget instead 118 | self._context_widget.ui.link_label.setEnabled(True) 119 | self._context_widget.ui.link_search.setEnabled(True) 120 | self._context_widget.ui.link_display.setEnabled(True) 121 | self._context_widget.ui.link_search_btn.setEnabled(True) 122 | 123 | self._context_widget.set_task_tooltip('Type in the name of the Asset or Shot you are working on') 124 | 125 | self._overlay_widget = overlay_widget.ShotgunOverlayWidget(self) 126 | 127 | # Specify what entries should show up in the list of links when using 128 | # the auto completer. In this case, we only show entity types that are 129 | # allowed for the PublishedFile.entity field. You can provide an 130 | # explicit list with the `restrict_entity_types()` method. 131 | 132 | if self.restrict_entity_types_by_link: 133 | self._context_widget.restrict_entity_types_by_link(self.restrict_entity_types_by_link['entity'], 134 | self.restrict_entity_types_by_link['field']) 135 | 136 | if self.restrict_entity_types: 137 | self._context_widget.restrict_entity_types(self.restrict_entity_types) 138 | 139 | # You can set the tooltip for each sub widget for context selection. 140 | # This helps describe to the user why they're choosing a task or link. 141 | self._context_widget.set_task_tooltip( 142 | "

The task that the selected item will be associated with the Shotgun entity being acted upon.

") 143 | 144 | self._context_widget.set_link_tooltip( 145 | "

The link that the selected item will be associated with the Shotgun entity being acted upon.

") 146 | 147 | # connect the signal emitted by the selector widget when a context is 148 | # selected. The connected callable should accept a context object. 149 | # self._context_widget.context_changed.connect(self._on_item_context_change) 150 | 151 | self.ui.contentWidgetVerticalLayout.addWidget(self._context_widget) 152 | 153 | # you can set a context using the `set_context()` method. Here we set it 154 | # to the current bundle's context 155 | self._context_widget.set_context(sgtk.platform.current_bundle().context) 156 | self._context_widget.context_changed.connect(self.define_tk_context) 157 | # self._context_widget.context_changed.connect(self.updateUi) 158 | 159 | # Connections 160 | self.ui.appComboBox.currentIndexChanged.connect(self.update_ui) 161 | self.ui.tkTemplateComboBox.currentIndexChanged.connect(self.update_ui) 162 | 163 | func = lambda x=self.ui.fileNameLineEdit: self.copy_path_to_clipboard(x) 164 | self.ui.fileNameCopyButton.released.connect(func) 165 | 166 | func = lambda x=self.ui.dirPathLineEdit: self.copy_path_to_clipboard(x) 167 | self.ui.directoryPathCopyButton.released.connect(func) 168 | 169 | func = lambda x=self.ui.filePathLineEdit: self.copy_path_to_clipboard(x) 170 | self.ui.filePathCopyButton.released.connect(func) 171 | 172 | func = lambda x=self.ui.dirPathLineEdit: self.open_file_path(x) 173 | self.ui.directoryPathOpenButton.released.connect(func) 174 | 175 | func = lambda x=self.ui.filePathLineEdit: self.open_file_path(x) 176 | self.ui.filePathOpenButton.released.connect(func) 177 | 178 | func = lambda x=self.ui.filePathLineEdit: self.create_file_on_disk(x) 179 | self.ui.createFileButton.released.connect(func) 180 | 181 | self.ui.copyFilePathOpenButton.released.connect(self.browse_file) 182 | self.ui.copyFileToFileButton.released.connect(self.copy_file_to_file_path) 183 | 184 | self.update_applications() 185 | 186 | def update_applications(self): 187 | self.ui.appComboBox.clear() 188 | 189 | self.ui.appComboBox.addItem(QtGui.QIcon(':/res/block.png'), 'Select Application') 190 | 191 | app_keys = list(self.applications.keys()) 192 | app_keys.sort() 193 | 194 | for appKey in app_keys: 195 | self.ui.appComboBox.addItem(QtGui.QIcon(':/res/sg_logo.png'), appKey, self.applications[appKey]) 196 | 197 | self.ui.appComboBox.currentIndexChanged.connect(self.update_tk_context) 198 | self.ui.appComboBox.currentIndexChanged.connect(self.update_tk_templates) 199 | 200 | def update_tk_templates(self): 201 | if not self.context: 202 | self.log.error('TK context is not defined, unable to update TK templates.') 203 | return 204 | 205 | tk = self._app.context.sgtk 206 | 207 | if not tk: 208 | self.log.error('Unable to get TK instance, unable to update TK templates.') 209 | return 210 | 211 | templates = tk.templates 212 | 213 | # Support non-engine template definitions. If None, then use engine key name instead. ie: Data > data > take_data 214 | app_name = str(self.ui.appComboBox.itemData(self.ui.appComboBox.currentIndex())) 215 | if app_name: 216 | app_name = app_name.lower().replace(' ', '').replace('tk-', '') 217 | else: 218 | app_name = self.ui.appComboBox.currentText().lower() 219 | 220 | entity_type = self.context.entity['type'] 221 | 222 | # Remap entity name 223 | entity_type = self.custom_entity_name_remap.get(entity_type, entity_type).lower() 224 | 225 | regex = '%s_%s' % (entity_type, app_name) 226 | 227 | active_templates = {} 228 | 229 | for k, v in templates.iteritems(): 230 | if re.search(regex, k): 231 | active_templates[k] = v 232 | 233 | self.ui.tkTemplateComboBox.clear() 234 | 235 | active_keys = active_templates.keys() 236 | active_keys.sort() 237 | 238 | self.ui.tkTemplateComboBox.addItem(QtGui.QIcon(':/res/block.png'), 'Select Template') 239 | 240 | for key in active_keys: 241 | key_title = key.replace(entity_type, '').replace('_', ' ').title() 242 | self.ui.tkTemplateComboBox.addItem(QtGui.QIcon(':/res/sg_logo.png'), key_title, active_templates[key]) 243 | 244 | # Default to "work" template if available 245 | index = self.ui.tkTemplateComboBox.findText('Work', QtCore.Qt.MatchContains) 246 | if index > -1: 247 | self.ui.tkTemplateComboBox.setCurrentIndex(index) 248 | 249 | self.ui.tkTemplateComboBox.currentIndexChanged.connect(self.update_template_definition) 250 | 251 | self.update_template_definition() 252 | 253 | def update_template_definition(self): 254 | """ 255 | Show template definition if a match 256 | :return: None 257 | """ 258 | 259 | curr_templ = self.ui.tkTemplateComboBox.itemText(self.ui.tkTemplateComboBox.currentIndex()) 260 | self.ui.descriptionLabel.setText('') 261 | 262 | for k, v in self.template_definitions.iteritems(): 263 | if re.search(k, curr_templ, re.IGNORECASE): 264 | self.ui.descriptionLabel.setText(v) 265 | 266 | def define_tk_context(self, context): 267 | if not context.task: 268 | return 269 | 270 | self.ui.appComboBox.setCurrentIndex(0) 271 | self.ui.tkTemplateComboBox.setCurrentIndex(0) 272 | self.clear_extra_token_widgets() 273 | 274 | self.context = context 275 | self.log.info('Context, source: %s' % self.context.source_entity) 276 | self.log.info('Context, task: %s' % self.context.task) 277 | 278 | self.update_ui() 279 | 280 | def update_tk_context(self): 281 | """ 282 | Register selected context with Toolkit 283 | :return: 284 | """ 285 | 286 | if self.ui.appComboBox.currentIndex() == 0: 287 | return 288 | 289 | tkengine = self.ui.appComboBox.itemData(self.ui.appComboBox.currentIndex()) 290 | 291 | # Try to get as deep into valid context 292 | if self.context.task: 293 | typ = self.context.task['type'] 294 | idd = self.context.task['id'] 295 | 296 | elif self.context.step: 297 | typ = self.context.step['type'] 298 | idd = self.context.step['id'] 299 | 300 | elif self.context.source_entity: 301 | typ = self.context.source_entity['type'] 302 | idd = self.context.source_entity['id'] 303 | 304 | try: 305 | self._overlay_widget.show_message('

Registering context with Toolkit, please wait...

') 306 | QtGui.QApplication.instance().processEvents() 307 | self.log.info('Creating folder structure on disk') 308 | self._app.sgtk.create_filesystem_structure(typ, idd, engine=tkengine) 309 | 310 | except: 311 | pass 312 | 313 | finally: 314 | self._overlay_widget.hide() 315 | 316 | self.ctx = self._app.sgtk.context_from_entity(typ, idd) 317 | 318 | def update_template_output_paths(self): 319 | if self.ui.tkTemplateComboBox.currentIndex() == 0: 320 | return 321 | 322 | self.template = self.ui.tkTemplateComboBox.itemData(self.ui.tkTemplateComboBox.currentIndex()) 323 | 324 | if not self.template: 325 | return 326 | 327 | # Update template definition label 328 | self.ui.tkTemplateComboBox.setToolTip(self.template.definition) 329 | 330 | self.fields = self.ctx.as_template_fields(self.template) 331 | 332 | missing_keys = self.template.missing_keys(self.fields) 333 | 334 | self.update_extra_tokens_widgets(self.template, missing_keys) 335 | 336 | def update_extra_tokens_widgets(self, template, missing_keys): 337 | missing_keys_dict = {} 338 | 339 | for key in missing_keys: 340 | if key in template.keys: 341 | keyObj = template.keys[key] 342 | missing_keys_dict[key] = keyObj 343 | 344 | parent = self.ui.extraTokensWidget 345 | parent_lay = self.ui.extraTokensWidgetLayout 346 | 347 | self.clear_extra_token_widgets() 348 | 349 | keys = missing_keys_dict.keys() 350 | keys.sort() 351 | 352 | row_cnt = 0 353 | 354 | if not keys: 355 | label = QtGui.QLabel('No Extra Keys found...', parent=parent) 356 | parent_lay.addWidget(label, row_cnt, 0) 357 | 358 | else: 359 | for key in keys: 360 | label = QtGui.QLabel(str(key), parent=parent) 361 | lineedit = QtGui.QLineEdit(str(missing_keys_dict[key].default), parent=parent) 362 | lineedit.data = missing_keys_dict[key] # Not ideal to store to object, but QLabel has no data storage method 363 | 364 | lineedit.editingFinished.connect(self.update_template_file_path) 365 | 366 | parent_lay.addWidget(label, row_cnt, 0) 367 | parent_lay.addWidget(lineedit, row_cnt, 1) 368 | 369 | row_cnt += 1 370 | 371 | def get_extra_token_definitions(self): 372 | """ 373 | Loop through all child qlabels gathering user input values 374 | :return: 375 | """ 376 | parent = self.ui.extraTokensWidget 377 | 378 | missing_keys = {} 379 | valid = True 380 | 381 | for widget in parent.children(): 382 | if type(widget) == QtGui.QLineEdit: 383 | 384 | value = widget.text() 385 | 386 | if not value: 387 | widget.setStyleSheet('border: 1px solid red; border-radius: 6px;') 388 | valid = False 389 | else: 390 | widget.setStyleSheet('') 391 | 392 | if type(widget.data) == sgtk.templatekey.IntegerKey: 393 | missing_keys[widget.data.name] = int(value) 394 | 395 | elif type(widget.data) == sgtk.templatekey.StringKey: 396 | missing_keys[widget.data.name] = value 397 | 398 | return missing_keys, valid 399 | 400 | def clear_extra_token_widgets(self): 401 | parent = self.ui.extraTokensWidget 402 | 403 | # Delete child widgets of parent 404 | for widget in parent.children(): 405 | if type(widget) == QtGui.QGridLayout: 406 | continue 407 | widget.deleteLater() 408 | 409 | def update_ui(self): 410 | # Disable widgets from user input 411 | self.ui.filePathWidgetGroup.setEnabled(False) 412 | self.ui.appComboBox.setEnabled(False) 413 | self.ui.tkTemplateComboBox.setEnabled(False) 414 | self.ui.fileNameLineEdit.setText('') 415 | self.ui.dirPathLineEdit.setText('') 416 | self.ui.filePathLineEdit.setText('') 417 | self.ui.copyFileLineEdit.setText('') 418 | 419 | if self.context: 420 | # Application combobox 421 | self.ui.appComboBox.setEnabled(True) 422 | if self.ui.appComboBox.currentIndex() < 1: 423 | self.ui.appComboBox.setStyleSheet('border: 1px solid blue; border-radius: 6px;') 424 | return 425 | else: 426 | self.ui.appComboBox.setStyleSheet('') 427 | 428 | # Template combobox 429 | self.clear_extra_token_widgets() 430 | self.ui.tkTemplateComboBox.setEnabled(True) 431 | if self.ui.tkTemplateComboBox.currentIndex() < 1: 432 | self.ui.tkTemplateComboBox.setStyleSheet('border: 1px solid blue; border-radius: 6px;') 433 | return 434 | else: 435 | self.ui.tkTemplateComboBox.setStyleSheet('') 436 | 437 | self.update_template_output_paths() 438 | 439 | self.update_template_file_path() 440 | 441 | def update_template_file_path(self): 442 | missing_keys, valid = self.get_extra_token_definitions() 443 | 444 | # Invalid state means that not all extra tokens are valid or missing 445 | if not valid: 446 | self.ui.filePathWidgetGroup.setEnabled(False) 447 | return 448 | 449 | self.ui.filePathWidgetGroup.setEnabled(True) 450 | 451 | self.fields.update(missing_keys) 452 | 453 | filepath = self.template.apply_fields(self.fields) 454 | 455 | # replace any frame padding with value 456 | try: 457 | filepath = filepath % 1 458 | 459 | except: 460 | pass 461 | 462 | if self.is_path_file(filepath): 463 | self.ui.fileNameLineEdit.setText(os.path.basename(filepath)) 464 | else: 465 | self.ui.fileNameLineEdit.setText('Template is a directory') 466 | 467 | self.ui.dirPathLineEdit.setText(os.path.dirname(filepath)) 468 | self.ui.filePathLineEdit.setText(filepath) 469 | 470 | def sanitize_file_path(self, path): 471 | illegalchars = ['<', '>', '|', '*', '"', '?'] 472 | 473 | for x in illegalchars: 474 | path = path.replace(x, '') 475 | 476 | return path 477 | 478 | def copy_path_to_clipboard(self, widget): 479 | """ 480 | 481 | :param widget: Widget to get path from to copy to clipboard 482 | :return: 483 | """ 484 | cb = QtGui.QApplication.clipboard() 485 | cb.clear(mode=cb.Clipboard) 486 | cb.setText(self.sanitize_file_path(widget.text()), mode=cb.Clipboard) 487 | 488 | def open_file_path(self, widget): 489 | 490 | path = widget.text() 491 | path = self.sanitize_file_path(path) 492 | 493 | if os.path.isfile(path): 494 | path = os.path.dirname(path) 495 | 496 | while True: 497 | if os.path.exists(path): 498 | break 499 | path = os.path.dirname(path) 500 | 501 | if sys.platform == 'darwin': 502 | subprocess.call(["open", "-R", path]) 503 | 504 | elif sys.platform == 'win32': 505 | os.startfile(path) 506 | 507 | elif sys.platform == 'linux2': 508 | subprocess.Popen(['xdg-open', path]) 509 | 510 | def browse_file(self): 511 | file_name = QtGui.QFileDialog.getOpenFileName() 512 | self.ui.copyFileLineEdit.setText(file_name[0]) 513 | 514 | def create_file_on_disk(self, widget): 515 | path = widget.text() 516 | path = self.sanitize_file_path(path) 517 | 518 | is_file = self.is_path_file(path) 519 | 520 | try: 521 | if not os.path.exists(path): 522 | 523 | if is_file: 524 | dirpath = os.path.dirname(path) 525 | else: 526 | dirpath = path 527 | 528 | # Create directory structure first 529 | if not os.path.exists(dirpath): 530 | os.makedirs(dirpath) 531 | self.log.info('Created directories: %s' % dirpath) 532 | 533 | if is_file: 534 | file(path, 'w').close() 535 | self.log.info('Created file: %s' % path) 536 | 537 | QtGui.QMessageBox.information(self, 'Success!', 538 | '

Created directory/file:


{}
'.format(path), 539 | QtGui.QMessageBox.Ok) 540 | 541 | except Exception as err: 542 | QtGui.QMessageBox.critical(self, 'Failure!', 543 | '

The directory/file failed to be created: {}


{}
'.format(path, 544 | str(err)), 545 | QtGui.QMessageBox.Ok) 546 | self.log.error('Failed to create directory/file: {}'.format(path)) 547 | self.log.error(str(err)) 548 | 549 | @staticmethod 550 | def is_path_file(path): 551 | """ 552 | Check if the path has a file extension, assume its a file, if not its a directory 553 | :param path: str - directory/file path 554 | :return: bool 555 | """ 556 | tmp = os.path.splitext(path) 557 | if len(tmp) > 1 and tmp[1]: 558 | return True 559 | 560 | return False 561 | 562 | def copy_file_to_file_path(self): 563 | src_path = self.ui.copyFileLineEdit.text() 564 | dst_path = self.ui.filePathLineEdit.text() 565 | 566 | if not os.path.exists(src_path): 567 | self.log.warn('File doesnt not exist on disk, unable to copy: %s' % src_path) 568 | return 569 | 570 | # Check if file exists 571 | if os.path.exists(dst_path): 572 | reply = QtGui.QMessageBox.question(self, 'File Exists', 573 | 'The file already exists, do you want to overwrite it?', 574 | QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) 575 | 576 | if reply == QtGui.QMessageBox.Yes: 577 | try: 578 | os.remove(dst_path) 579 | self.log.info('Removed existing file: {}'.format(dst_path)) 580 | except Exception as err: 581 | self.log.error('Failed to remove existing file: %s'.format(dst_path)) 582 | self.log.error(str(err)) 583 | else: 584 | return 585 | 586 | # Copy file 587 | try: 588 | self.log.info('Copying: {} > {}'.format(src_path, dst_path)) 589 | 590 | dirname = os.path.dirname(dst_path) 591 | if not os.path.exists(dirname): 592 | os.makedirs(dirname) 593 | 594 | shutil.copyfile(src_path, dst_path) 595 | 596 | QtGui.QMessageBox.information(self, 'Success!', 597 | '

The file was copied successfully

', 598 | QtGui.QMessageBox.Ok) 599 | 600 | self.ui.copyFileLineEdit.clear() 601 | 602 | except Exception as err: 603 | QtGui.QMessageBox.critical(self, 'Failure!', 604 | '

The file failed to be copied


{}
'.format(str(err)), 605 | QtGui.QMessageBox.Ok) 606 | self.log.error('Failed to copy: {} > {}'.format(src_path, dst_path)) 607 | self.log.error(str(err)) 608 | 609 | def closeEvent(self, event): 610 | """ 611 | Executed when the main dialog is closed. 612 | All worker threads and other things which need a proper shutdown 613 | need to be called here. 614 | """ 615 | 616 | self.log.debug("CloseEvent Received. Begin shutting down UI.") 617 | 618 | # register the data fetcher with the global schema manager 619 | shotgun_globals.unregister_bg_task_manager(self._task_manager) 620 | 621 | try: 622 | # shut down main threadpool 623 | self._task_manager.shut_down() 624 | except Exception: 625 | self.log.exception("Error running closeEvent()") 626 | 627 | # ensure the context widget's recent contexts are saved 628 | self._context_widget.save_recent_contexts() 629 | 630 | # def _on_item_context_change(self, context): 631 | # """ 632 | # This method is connected above to the `context_changed` signal emitted 633 | # by the context selector widget. 634 | # 635 | # For demo purposes, we simply display the context in a label. 636 | # """ 637 | # self._context_lbl.setText("Context set to: %s" % (context,)) 638 | # 639 | # # typically the context would be set by some external process. for now, 640 | # # we'll just re-set the context based on what was selected. this will 641 | # # have the added effect of populating the "recent" items in the drop 642 | # # down list 643 | # self._context_widget.set_context(context) 644 | 645 | # def _enable_editing(self, checked): 646 | # """ 647 | # This method is connected above to the toggle button to show switching 648 | # between enabling and disabling editing of the context. 649 | # """ 650 | # 651 | # self._context_lbl.setText("") 652 | # 653 | # if checked: 654 | # # enable editing and show a message to the user 655 | # self._context_widget.enable_editing( 656 | # True, 657 | # "Editing is now enabled." 658 | # ) 659 | # else: 660 | # # disable editing and show a message to the user 661 | # self._context_widget.enable_editing( 662 | # False, 663 | # "Editing is now disabled." 664 | # ) 665 | --------------------------------------------------------------------------------