diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff6e521 --- /dev/null +++ b/.gitignore @@ -0,0 +1,104 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[co] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +#lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# Jetbrains +.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5172753 --- /dev/null +++ b/LICENSE @@ -0,0 +1,146 @@ +SHOTGUN PIPELINE TOOLKIT SOURCE CODE LICENSE + +Version: 7/07/2013 + +Shotgun Software Inc. ("Company") provides the Shotgun Pipeline Toolkit, +software, including source code, in this package or repository folder (the +"Shotgun Toolkit Code") subject to your acceptance of and compliance with +the following terms and conditions (the "License Terms"). By accessing, +downloading, copying, using or modifying any of the Shotgun Toolkit Code, +you agree to these License Terms. + +Eligibility + +The following license to the Shotgun Toolkit Code is valid only if and while +you are a customer of Company in good standing with either: (a) a current, +paid-up (or free-for-evaluation) subscription or fixed-term license for +Company's Shotgun Platform; or (b) a perpetual license and current, paid-up +maintenance and support contract for the Shotgun Platform. + +Shotgun Toolkit Code License + +Subject to the eligibility criteria above and your compliance with these +License Terms, Company grants to you a non-exclusive, limited license to +reproduce, use, and make derivative works of (including by compiling object +code versions of) the Shotgun Toolkit Code solely for your non-commercial or +internal business purposes in connection with your authorized use of the +Shotgun Platform. + +Company reserves all rights in the Shotgun Toolkit Code not expressly granted +above. These License Terms do not grant or require Company to grant, by +implication, estoppel, or otherwise, any other licenses or rights with respect +to the Shotgun Toolkit Code or any of Company's other software or intellectual +property rights. You agree not to take any action with respect to the Shotgun +Toolkit Code that is not expressly authorized above. + +You must keep intact (and, in the case of copies, reproduce) all copyright +and other proprietary notices, including all references to and copies of these +License Terms, as originally included on, in, or with the Shotgun Toolkit +Code. You must ensure that all derivative works you make of the Shotgun +Toolkit Code contain or are accompanied by comparable and conspicuous notices +that the underlying Shotgun Toolkit Code is the confidential information of +Company and is subject to Company's copyrights and these License Terms. + +No Redistribution or Disclosure + +You acknowledge that the Shotgun Toolkit Code is and contains proprietary and +trade-secret information of Company. You may not distribute, disclose to any +third party, operate for the benefit of third parties (for example, on a +hosted basis), or otherwise commercially exploit the Shotgun Toolkit Code or +any portion or derivative work thereof without Company's separate and express +written consent. For purposes of this restriction, third parties do not +include your employees or agents acting on your behalf who are bound to abide +by these License Terms. + +No Warranties or Support + +The Shotgun Toolkit Code is provided "AS IS" and with all faults. Company +makes no warranties whatsoever, whether express, implied, or otherwise, +concerning the Shotgun Toolkit Code. Company has no obligation to provide +maintenance or technical support for the Shotgun Toolkit Code (unless +otherwise expressly agreed in a separate written agreement between you and +Company). + +Liability + +You agree to be solely responsible for your use and modifications of the +Shotgun Toolkit Code, and for any harm or liability arising out of such use +or modifications, including but not limited to any liability for infringement +of third-party intellectual property rights. + +To the fullest extent permitted under applicable law, you agree that: (a) +Company will not be liable under these License Terms or otherwise for any +direct, indirect, incidental, special, consequential, or exemplary damages, +including but not limited to damages for loss of profits, goodwill, use, data +or other intangible losses, in relation to the Shotgun Toolkit Code or your +use or inability to use the Shotgun Toolkit Code, even if Company has been +advised of the possibility of such damages; and (b) in any event, Company's +aggregate liability under these License Terms or in connection with the +Shotgun Toolkit Code, regardless of the form of action and under any theory +(whether in contract, tort, statutory, or otherwise), will not exceed the +greater of $50 or the amount (if any) that you actually paid for access to +the Shotgun Toolkit Code. + +Ownership + +Company retains sole and exclusive ownership of the Shotgun Toolkit Code and +all copyright and other intellectual property rights therein. You will own any +derivative works you make to the Shotgun Toolkit Code, subject to: (a) the +preceding sentence; and (b) the provisions below regarding ownership of any +code you elect to contribute to Company. + +Contributions + +The following terms apply to any derivative works of the Shotgun Toolkit Code +(or any other materials) that you choose to contribute to Company. + +For good and valuable consideration, receipt of which is acknowledged, you +hereby transfer and assign to Company your entire right, title, and interest +(including all rights under copyright) in: (a) any software code, +documentation, and/or other materials that you deliver to Company for +inclusion in, improvement of, use with, or documentation of Company's software +program(s), including but not limited to any code, documentation, and/or other +materials identified in a contribution form you submit to Company in an +applicable form designated by Company; and (b) any future revisions of such +code, documentation, and/or other materials that you make hereafter. The code, +documentation, other materials, and future revisions described above are +collectively referred to below as the "Contribution." + +As used below, the "Company Programs" means and includes the Company software +program(s) identified on any contribution form you submit to Company, and any +other software into which Company incorporates or with which Company uses or +distributes the Contribution or any version or portion thereof. + +Company grants you a non-exclusive right to continue to modify, make +derivative works of, reproduce, and use the Contribution for your +non-commercial or internal business purposes, and to further Company's +development of Company Programs. This grant does not: (a) limit Company's +rights, (b) grant you any rights with respect to the Company Programs; nor +(c) permit you to distribute, operate for the benefit of third parties (for +example, on a hosted basis), or otherwise commercially exploit the +Contribution. + +You acknowledge that if Company elects to distribute the Contribution or any +version or portion thereof, it may do so on any basis that it chooses +(including under any proprietary or open-source licensing terms), without +further compensation to you. + +You agree that if you have or acquire hereafter any patent or interface +copyright or other intellectual property interest dominating the Contribution +or any Company Programs (or use thereof), such dominating interest will not be +used to undermine the effect of the assignment set forth above. Accordingly, +Company and its direct and indirect licensees are licensed to make, use, sell, +distribute, and otherwise exploit, in the Company Programs and their future +versions and derivative works, without royalty or limitation, the subject +matter of the dominating interest. This license provision will be binding on +you and on any assignees of, or other successors to, the dominating interest. + +You hereby represent and warrant that you are the sole copyright holder for +the Contribution and that you have the right and power to enter into this +contract. You shall indemnify and hold harmless Company and its officers, +employees, and agents against any and all claims, actions or damages +(including attorney's reasonable fees) asserted by or paid to any party on +account of a breach or alleged breach of the foregoing warranty. You make no +other express or implied warranty (including without limitation any warranty +of merchantability or fitness for a particular purpose) regarding the +Contribution. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..243024b --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# tk-vred +TK VRED diff --git a/engine.py b/engine.py new file mode 100644 index 0000000..178c8a4 --- /dev/null +++ b/engine.py @@ -0,0 +1,471 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2015 Shotgun Software Inc. +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +import os +import sys +import errno +import random + +from shiboken2 import wrapInstance +from PySide2 import QtWidgets +from PySide2 import QtCore, QtGui + +import tank +from tank import TankError +import vrVredUi +import vrFileIO +import vrController +import webbrowser + +import vrRenderSettings + +class VREDEngine(tank.platform.Engine): + ''' + The VRED engine. + + The default shotgun menu actions are: + * 'Upload Files' + * 'File Open...' + * 'File Save...' + * 'Jump to Screening Room in RV' + * 'Jump to Screening Room Web Player' + * 'Reload and Restart' + * 'Open Log Folder' + * 'Shotgun Panel...' + * 'Publish...' + * 'Snapshot History...' + * 'About...' + * 'Snapshot...' + * 'Work Area Info...' + ''' + render_path = None + cmd_and_callback_dict = {} + shotgun_menu_text = u'S&hotgun' + + def _can_show_menu(self): + file_is_open = self.get_current_file() + has_task = self.context.task + has_asset = self.context.entity and self.context.entity.get("type") == "Asset" + + return file_is_open or (has_asset and has_task) + + def get_project_title(self): + '''Load information to infer and return the project title''' + # Project menu title + has_task = self.context.task + has_asset = self.context.entity and self.context.entity.get("type") == "Asset" + + # Asset, Task + if has_asset and has_task: + title = "{}, {}".format( + self.context.entity.get("name"), + self.context.task.get("name") + ) + # Asset + elif has_asset and not has_task: + title = self.context.entity.get("name") + # Project + else: + title = self.context.project.get("name") + return title + + def make_action(self, title, actions_store={}, custom_action=None, show=True): + '''Make an action just passing title and callbacks storage''' + action = custom_action + fallback = self.cmd_and_callback_dict.get(title.replace('&', ''), lambda: None) + action = custom_action or actions_store.get(title.replace('&', ''), fallback) + return { + 'type': 'action', + 'name': title.replace(' ', '_').replace('.', '').lower(), + 'title': title, + 'action': action, + 'show': show + } + + def _create_shotgun_menu(self): + ''' + Creates the shotgun menu based on the loaded apps. + To create the menu we use menu structure with next contracts: + { + 'type': 'action|menu', + 'name': '', + 'title': 'Reload and &Restart', + 'show': True|False, + 'action': // Just for type=action + 'items': // Just for type=menu + } + or + {'type': 'separator', 'show':True|False}, + + To enable debug create environ variable VRED_DEBUG: + - Windows: "SET VRED_DEBUG=1" + - UNIX: "export VRED_DEBUG=1" + ''' + try: + window = self._window + menubar = window.menuBar() + menu = QtWidgets.QMenu() + menu.setTitle(self.shotgun_menu_text) + cmd_and_callback_dict = dict() + for name, details in self.commands.items(): + cmd_and_callback_dict[name] = details["callback"] + self.log_info("{}".format(cmd_and_callback_dict)) + keys_count = len(self.cmd_and_callback_dict.keys()) + if keys_count == 0: + self.cmd_and_callback_dict = cmd_and_callback_dict + self.log_info("Creating Menu") + show_in_menu = self._can_show_menu() + use_debug = os.getenv('VRED_DEBUG', False) + self.log_info("\n\nos.environ {0}\n\n".format(os.environ)) + menu_items = [ + { + 'type': 'menu', + 'name': 'project_menu', + 'title': self.get_project_title(), + 'show': True, + 'items': [ + self.make_action('Jump to Shot&gun', custom_action=self.jump_to_shotgun), + self.make_action('Jump to File S&ystem', custom_action=self.jump_to_file_system), + {'type': 'separator', 'show':True}, + self.make_action('Jump to Screening &Room Web Player', cmd_and_callback_dict), + self.make_action('Jump to Screening Room in RV', cmd_and_callback_dict), + self.make_action('Open Log Folder', cmd_and_callback_dict, show=use_debug), + self.make_action('Reload and &Restart', cmd_and_callback_dict, show=use_debug), + self.make_action('Work Area &Info...', cmd_and_callback_dict), + ] + }, + {'type': 'separator', 'show':True}, + self.make_action('File &Open...', cmd_and_callback_dict), + self.make_action('Snaps&hot...', cmd_and_callback_dict, show=show_in_menu), + self.make_action('File &Save...', cmd_and_callback_dict, show=show_in_menu), + self.make_action('Publish...', cmd_and_callback_dict, show=show_in_menu), + {'type': 'separator', 'show':True}, + self.make_action('A&bout...', cmd_and_callback_dict, show=True), + self.make_action('&Load...', cmd_and_callback_dict, show=show_in_menu), + self.make_action('Scene Brea&kdown...', cmd_and_callback_dict, show=show_in_menu), + { + 'type': 'menu', + 'name': 'scene_snapshot', + 'title': 'Scene Snaps&hot', + 'show': show_in_menu, + 'items': [ + self.make_action('Snap&shot...', cmd_and_callback_dict), + self.make_action('Snapshot &History...', cmd_and_callback_dict) + ] + }, + self.make_action('Shotgun &Panel...', cmd_and_callback_dict, show=True), + { + 'type': 'menu', + 'name': 'shotgun_workfiles', + 'title': 'Shotgun &Workfiles', + 'show': True, + 'items': [ + self.make_action('File &Open...', cmd_and_callback_dict), + self.make_action('File &Save...', cmd_and_callback_dict) + ] + } + ] + + self.existent = {} + self.prepare_menu_from_structure(menu_items, menu, self.existent) + for menubar_action in menubar.actions(): + if menubar_action.text() == u'&Help': + menubar.insertMenu(menubar_action, menu) + break + + except Exception as error: + self.log_info('Error creating menu: {0}'.format(error)) + + def prepare_menu_from_structure(self, menu_items, parentmenu, existent_elements): + '''Create menu elements and actions accorfing passed structures''' + try: + for item in menu_items: + show_item = item.get('show', None) + elm_name = item.get('name', 'elm_{0}'.format(random.randint(0, 1000))) + if not show_item: + continue + elm_type = item['type'] + elm_key = '{0}_{1}'.format(elm_type, elm_name) + self.log_info("Checking {0}".format(elm_key)) + if not existent_elements.has_key(elm_key): + if elm_type == 'menu': + existent_elements[elm_key] = QtWidgets.QMenu() + existent_elements[elm_key].setTitle(item['title']) + self.prepare_menu_from_structure(item['items'], + existent_elements[elm_key], + existent_elements) + parentmenu.addMenu(existent_elements[elm_key]) + self.log_info("Adding Menu {0}".format(elm_key)) + elif elm_type == 'action': + parentmenu.addAction( + item['title'], + item['action'] + ) + self.log_info("Adding Action {0}".format(elm_key)) + elif elm_type == 'separator': + parentmenu.addSeparator() + except Exception as err: + self.log_info("{0}".format(err)) + + def _rm_shotgun_menu(self): + """ + Checks for the presence of a shotgun menu. + Removes the shotgun menu if it exists. + """ + # Remove the shotgun menu. + self.log_info("Remove Shotgun Menu") + + window = self._window + menu_bar = window.menuBar() + + for menu_elm in menu_bar.actions(): + if menu_elm.text() == self.shotgun_menu_text: + menu_bar.removeAction(menu_elm) + + def pre_app_init(self): + """ + Runs after the engine is set up but before any apps have been initialized. + We use this method to set up basic things that will be needed through + the lifecycle of the Engine, such as the logger and instance variables. + """ + self.log_info("Pre App Initalization") + # self._initialize_dark_look_and_feel() + # tell QT to interpret C strings as utf-8 + utf8 = tank.platform.qt.QtCore.QTextCodec.codecForName("utf-8") + tank.platform.qt.QtCore.QTextCodec.setCodecForCStrings(utf8) + self.logger.debug("set utf-8 codec for widget text") + + def post_app_init(self): + """ + Runs when all apps have initialized. + """ + from tank.platform.qt import QtGui + + self.log_info("Post App Initalization") + + QtGui.QApplication.instance().aboutToQuit.connect(self.quit) + + self._window = self.get_vred_main_window() + + # If the app was launched to open a file, do so + file_to_open = os.environ.get("TANK_FILE_TO_OPEN", None) + if file_to_open: + self.reset_scene() + self.load_file(file_to_open) + + # Create the Shotgun menu + self.rebuild_shotgun_menu() + + def destroy_engine(self): + """ + Called when the engine should tear itself down. + """ + self.log_info("Destroying engine") + + @property + def context_change_allowed(self): + """ + Overriding the engine base class property to allow + context switch without a restart of this engine + """ + # see: http://developer.shotgunsoftware.com/tk-core/platform.html?highlight=context_change_allowed#sgtk.platform.Engine.context_change_allowed + return True + + def jump_to_shotgun(self): + """ + Function to goto the current project page. + """ + webbrowser.open(self.context.shotgun_url) + + def jump_to_file_system(self): + # launch one window for each location on disk + paths = self.context.filesystem_locations + + for disk_location in paths: + # get the setting + system = sys.platform + + # run the app + if system == "linux2": + cmd = 'xdg-open "%s"' % disk_location + elif system == "darwin": + cmd = 'open "%s"' % disk_location + elif system == "win32": + cmd = 'cmd.exe /C start "Folder" "%s"' % disk_location + else: + raise Exception("Platform '%s' is not supported." % system) + + exit_code = os.system(cmd) + + if exit_code != 0: + self.log_error("Failed to launch '%s'!" % cmd) + + def rebuild_shotgun_menu(self): + """ + This will remove and rebuild the menu. + """ + try: + self._rm_shotgun_menu() + except Exception as error: + self.log_info("Error Clearing Menu {0}".format(error)) + self._create_shotgun_menu() + + def get_current_file(self): + """ + Get the current file. + """ + return vrFileIO.getFileIOFilePath() + + def load_file(self, file_path): + """ + Load a new file into VRED. This will reset the workspace. + """ + decision = self.execute_hook_method("file_usage_hook", "file_attempt_open", path=file_path) + if decision: + self.log_info("Load File: {}".format(file_path)) + vrFileIO.load(file_path) + self.prepare_render_path(file_path) + else: + # The user chose not to open the file + pass + + def reset_scene(self): + """ + Resets the Scene in VRED. + """ + self.log_info("Reset Scene") + self.current_file_closed() + vrController.newScene() + + def log_info(self, message): + """ + Log debugging information. + """ + self.logger.info(message) + + def log_debug(self, message): + """ + Log a debug message + """ + self.logger.debug(message) + + def log_error(self, message): + """ + Log Error debugging information. + """ + self.logger.error(message) + + def save_current_file(self, file_path): + """ + Tell VRED to save the out the current project as a file. + """ + # Save the actual file + self.log_info("Save File: {}".format(file_path)) + + vrFileIO.save(file_path) + if not os.path.exists(file_path.decode('utf-8')): + msg = "VRED Failed to save file {}".format(file_path) + self.log_error(msg) + raise TankError(msg) + + allowed_to_open = self.execute_hook_method("file_usage_hook", "file_attempt_open", path=file_path) + if not allowed_to_open: + raise TankError("Can't save file: a lock for this path already exists") + + self.prepare_render_path(file_path) + + def post_context_change(self, old_context, new_context): + """ + Called right after a context switch, let's send the new + context to the VRED engine + """ + self.log_info("post_context_change called with context {}".format(new_context)) + if self.context_change_allowed: + self.rebuild_shotgun_menu() + + def get_vred_main_window(self): + """ + Gets the main window. + """ + window = wrapInstance(long(vrVredUi.getMainWindow()), QtWidgets.QMainWindow) + + return window + + def current_file_closed(self): + """ + Called when the current file is closed. + """ + path = self.get_current_file() + if path: + self.execute_hook_method("file_usage_hook", "file_closed", path=path) + + def quit(self): + self.current_file_closed() + + def get_render_path(self, file_path): + """ + Get render path when the file is selected or saved + """ + self.log_info('Generating render path') + + try: + shotgun_root = self.sgtk.project_path + template = self.get_template_by_name(self.get_setting('render_template')) + scene_name = file_path.split(os.path.sep)[-1].replace('.vpb', '') + sg_asset_type_key = 'entity.Asset.sg_asset_type' + fallback_sg_type = self.context.entity_locations[0] + fallback_sg_type = fallback_sg_type.split('assets')[1].split(os.path.sep)[1] + fields = { + 'shotgun_root': shotgun_root, + 'sg_asset_type': self.context.source_entity.get(sg_asset_type_key, fallback_sg_type), + 'Asset': self.context.entity['name'].replace(' ', '-'), + 'Step': self.context.step['name'].replace(' ', '-').lower(), + 'scene_name': scene_name + } + path = template.apply_fields(fields) + self.log_info('Path value: {0}'.format(path)) + try: + os.makedirs(path) + except OSError as exc: + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: + raise exc + path = os.path.sep.join([path, scene_name+'.png']) + self.log_info('\nFull path value: {0}\n'.format(path)) + except Exception as err: + self.log_info("\n\nError generating render path: {0}\n\n".format(err)) + path = None + + return path + + def prepare_render_path(self, file_path): + """ + Prepare render path when the file selected or saved + """ + path = self.get_render_path(file_path) + + if path: + self.render_path = path + vrRenderSettings.setRenderFilename(self.render_path) + + def save_after_publish(self, path): + """ + Save the scene after publish in order to get a new version in the workfiles folder + """ + self.save_current_file(path) + + def _get_dialog_parent(self): + """ + Get the QWidget parent for all dialogs created through + show_dialog & show_modal. + """ + return self.get_vred_main_window() diff --git a/hooks/default_file_usage.py b/hooks/default_file_usage.py new file mode 100644 index 0000000..8eebd0c --- /dev/null +++ b/hooks/default_file_usage.py @@ -0,0 +1,32 @@ +# Copyright (c) 2013 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. +""" +The default file_usage hook. +""" +import sgtk + + +HookBaseClass = sgtk.get_hook_baseclass() + + +class DefaultFileUsageHook(HookBaseClass): + + def file_opened(self, path): + """ + Called when a file is opened. + """ + pass + + def file_closed(self, path): + """ + Called when a file is closed, either because the user opened another + file or closed the application. + """ + pass \ No newline at end of file diff --git a/hooks/post_publish.py b/hooks/post_publish.py new file mode 100644 index 0000000..90c010d --- /dev/null +++ b/hooks/post_publish.py @@ -0,0 +1,84 @@ +# Copyright (c) 2013 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +from tank import Hook +from tank import TankError + + +class PostPublishHook(Hook): + """ + Hook that implements post-publish functionality + """ + + def execute(self, work_template, primary_task, secondary_tasks, progress_cb, + user_data, **kwargs): + """ + Main hook entry point + + :param work_template: template + This is the template defined in the config that + represents the current work file + + :param primary_task: The primary task that was published by the primary publish hook. Passed + in here for reference. + + :param secondary_tasks: The list of secondary taskd that were published by the secondary + publish hook. Passed in here for reference. + + :param progress_cb: Function + A progress callback to log progress during pre-publish. Call: + + progress_cb(percentage, msg) + + to report progress to the UI + + :param user_data: A dictionary containing any data shared by other hooks run prior to + this hook. Additional data may be added to this dictionary that will + then be accessible from user_data in any hooks run after this one. + + :returns: None + :raises: Raise a TankError to notify the user of a problem + """ + + self.parent.engine.log_info("Starting post publish") + + # import maya.cmds as cmds + + progress_cb(0, "Versioning up the scene file") + + # get the current scene path: + scene_path = self.parent.engine.get_current_file() + + # increment version and construct new file name: + progress_cb(25, "Finding next version number") + fields = work_template.get_fields(scene_path) + next_version = self._get_next_work_file_version(work_template, fields) + fields["version"] = next_version + new_scene_path = work_template.apply_fields(fields) + + # log info + self.parent.log_debug("Version up work file %s --> %s..." % (scene_path, new_scene_path)) + + # rename and save the file + progress_cb(50, "Saving the scene file") + self.parent.engine.save_current_file(new_scene_path) + + progress_cb(100) + return None + + def _get_next_work_file_version(self, work_template, fields): + """ + Find the next available version for the specified work_template and fields. + """ + existing_versions = self.parent.tank.paths_from_template(work_template, fields, ["version"]) + version_numbers = [work_template.get_fields(v).get("version") for v in existing_versions] + curr_v_no = fields["version"] + max_v_no = max(version_numbers) + return max(curr_v_no, max_v_no) + 1 diff --git a/hooks/primary_pre_publish.py b/hooks/primary_pre_publish.py new file mode 100644 index 0000000..48a5914 --- /dev/null +++ b/hooks/primary_pre_publish.py @@ -0,0 +1,117 @@ +# Copyright (c) 2013 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +import os + +import tank +from tank import Hook +from tank import TankError + +class PrimaryPrePublishHook(Hook): + """ + Single hook that implements pre-publish of the primary task + """ + def execute(self, task, work_template, progress_cb, user_data, **kwargs): + """ + Main hook entry point + :param task: Primary task to be pre-published. This is a + dictionary containing the following keys: + { + item: Dictionary + This is the item returned by the scan hook + { + name: String + description: String + type: String + other_params: Dictionary + } + + output: Dictionary + This is the output as defined in the configuration - the + primary output will always be named 'primary' + { + name: String + publish_template: template + tank_type: String + } + } + :param work_template: template + This is the template defined in the config that + represents the current work file + + :param progress_cb: Function + A progress callback to log progress during pre-publish. Call: + + progress_cb(percentage, msg) + + to report progress to the UI + + :param user_data: A dictionary containing any data shared by other hooks run prior to + this hook. Additional data may be added to this dictionary that will + then be accessible from user_data in any hooks run after this one. + + :returns: List + A list of non-critical problems that should be + reported to the user but not stop the publish. + + :raises: Hook should raise a TankError if the primary task + can't be published! + """ + self.parent.engine.log_info("Starting primary pre-publish") + + # Check for versioning of file here. Compare this to Maya. + scene_file = self.parent.engine.get_current_file() + # validate it: + scene_errors = self._validate_work_file(scene_file, work_template, task["output"], progress_cb) + progress_cb(100) + return scene_errors + + + def _validate_work_file(self, path, work_template, output, progress_cb): + """ + Validate that the given path is a valid work file and that + the published version of it doesn't already exist. + + Return the new version number that the scene should be + up'd to after publish + """ + errors = [] + + progress_cb(25, "Validating work file") + + if not work_template.validate(path): + raise TankError("File '%s' is not a valid work path, unable to publish!" % path) + + progress_cb(50, "Validating publish path") + + # find the publish path: + fields = work_template.get_fields(path) + fields["TankType"] = output["tank_type"] + publish_template = output["publish_template"] + publish_path = publish_template.apply_fields(fields) + + if os.path.exists(publish_path): + raise TankError("A published file named '%s' already exists!" % publish_path) + + progress_cb(75, "Validating current version") + + # check the version number against existing work file versions to avoid accidentally + # bypassing more recent work! + existing_versions = self.parent.tank.paths_from_template(work_template, fields, ["version"]) + version_numbers = [ work_template.get_fields(v).get("version") for v in existing_versions] + curr_v_no = fields["version"] + max_v_no = max(version_numbers) + if max_v_no > curr_v_no: + # there is a higher version number - this means that someone is working + # on an old version of the file. Warn them about upgrading. + errors.append("Your current work file is v%03d, however a more recent version (v%03d) already exists. " + "After publishing, this file will become v%03d, replacing any more recent work from v%03d!" + % (curr_v_no, max_v_no, max_v_no + 1, max_v_no)) + return errors diff --git a/hooks/primary_publish.py b/hooks/primary_publish.py new file mode 100644 index 0000000..6ed1898 --- /dev/null +++ b/hooks/primary_publish.py @@ -0,0 +1,227 @@ +# Copyright (c) 2013 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. +import os + +import tank +import vrFileIO +from tank import Hook +from tank import TankError + +# Internal imports +import commands + +class PrimaryPublishHook(Hook): + """ + Single hook that implements publish of the primary task + """ + def execute( + self, task, work_template, comment, thumbnail_path, sg_task, progress_cb, + user_data, **kwargs + ): + """ + Main hook entry point + :param task: Primary task to be published. This is a + dictionary containing the following keys: + { + item: Dictionary + This is the item returned by the scan hook + { + name: String + description: String + type: String + other_params: Dictionary + } + + output: Dictionary + This is the output as defined in the configuration - the + primary output will always be named 'primary' + { + name: String + publish_template: template + tank_type: String + } + } + + :param work_template: template + This is the template defined in the config that + represents the current work file + + :param comment: String + The comment provided for the publish + + :param thumbnail: Path string + The default thumbnail provided for the publish + + :param sg_task: Dictionary (shotgun entity description) + The shotgun task to use for the publish + + :param progress_cb: Function + A progress callback to log progress during pre-publish. Call: + + progress_cb(percentage, msg) + + to report progress to the UI + + :param user_data: A dictionary containing any data shared by other hooks run prior to + this hook. Additional data may be added to this dictionary that will + then be accessible from user_data in any hooks run after this one. + + :returns: Path String + Hook should return the path of the primary publish so that it + can be passed as a dependency to all secondary publishes + + :raises: Hook should raise a TankError if publish of the + primary task fails + """ + self.parent.engine.log_info("Starting primary publish") + # get scene path + scene_path = self.parent.engine.get_current_file() + + self.parent.engine.log_info("Template: {}".format(str(work_template))) + + if not work_template.validate(scene_path): + raise TankError("File '%s' is not a valid work path, unable to publish!" % scene_path) + + # use templates to convert to publish path: + output = task["output"] + fields = work_template.get_fields(scene_path) + fields["TankType"] = output["tank_type"] + publish_template = output["publish_template"] + publish_path = publish_template.apply_fields(fields) + + if os.path.exists(publish_path): + raise TankError("The published file named '%s' already exists!" % publish_path) + + # save the scene: + progress_cb(10.0, "Saving the scene") + self.parent.log_debug("Saving the scene...") + + # Save the actual file + self.parent.engine.save_current_file(scene_path) + + # copy the file: + progress_cb(50.0, "Copying the file") + try: + publish_folder = os.path.dirname(publish_path) + self.parent.ensure_folder_exists(publish_folder) + self.parent.log_debug("Copying %s --> %s..." % (scene_path, publish_path)) + self.parent.copy_file(scene_path, publish_path, task) + except Exception, e: + raise TankError("Failed to copy file from %s to %s - %s" % (scene_path, publish_path, e)) + + # work out publish name: + publish_name = self._get_publish_name(publish_path, publish_template, fields) + + # finally, register the publish: + progress_cb(75.0, "Registering the publish") + self._register_publish(publish_path, + publish_name, + sg_task, + fields["version"], + output["tank_type"], + comment, + thumbnail_path, + []) + + progress_cb(100) + + return publish_path + + def _get_publish_name(self, path, template, fields=None): + """ + Return the 'name' to be used for the file - if possible + this will return a 'versionless' name + """ + # first, extract the fields from the path using the template: + fields = fields.copy() if fields else template.get_fields(path) + if "name" in fields and fields["name"]: + # well, that was easy! + name = fields["name"] + else: + # find out if version is used in the file name: + template_name, _ = os.path.splitext(os.path.basename(template.definition)) + version_in_name = "{version}" in template_name + + # extract the file name from the path: + name, _ = os.path.splitext(os.path.basename(path)) + delims_str = "_-. " + if version_in_name: + # looks like version is part of the file name so we + # need to isolate it so that we can remove it safely. + # First, find a dummy version whose string representation + # doesn't exist in the name string + version_key = template.keys["version"] + dummy_version = 9876 + while True: + test_str = version_key.str_from_value(dummy_version) + if test_str not in name: + break + dummy_version += 1 + + # now use this dummy version and rebuild the path + fields["version"] = dummy_version + path = template.apply_fields(fields) + name, _ = os.path.splitext(os.path.basename(path)) + + # we can now locate the version in the name and remove it + dummy_version_str = version_key.str_from_value(dummy_version) + + v_pos = name.find(dummy_version_str) + # remove any preceeding 'v' + pre_v_str = name[:v_pos].rstrip("v") + post_v_str = name[v_pos + len(dummy_version_str):] + + if (pre_v_str and post_v_str + and pre_v_str[-1] in delims_str + and post_v_str[0] in delims_str): + # only want one delimiter - strip the second one: + post_v_str = post_v_str.lstrip(delims_str) + + versionless_name = pre_v_str + post_v_str + versionless_name = versionless_name.strip(delims_str) + + if versionless_name: + # great - lets use this! + name = versionless_name + else: + # likely that version is only thing in the name so + # instead, replace the dummy version with #'s: + zero_version_str = version_key.str_from_value(0) + new_version_str = "#" * len(zero_version_str) + name = name.replace(dummy_version_str, new_version_str) + + return name + + + def _register_publish(self, path, name, sg_task, publish_version, tank_type, comment, thumbnail_path, dependency_paths): + """ + Helper method to register publish using the + specified publish info. + """ + # construct args: + args = { + "tk": self.parent.tank, + "context": self.parent.context, + "comment": comment, + "path": path, + "name": name, + "version_number": publish_version, + "thumbnail_path": thumbnail_path, + "task": sg_task, + "dependency_paths": dependency_paths, + "published_file_type":tank_type, + } + + self.parent.log_debug("Register publish in shotgun: %s" % str(args)) + + # register publish; + sg_data = tank.util.register_publish(**args) + + return sg_data diff --git a/hooks/publish_scan_scene.py b/hooks/publish_scan_scene.py new file mode 100644 index 0000000..5838eb1 --- /dev/null +++ b/hooks/publish_scan_scene.py @@ -0,0 +1,105 @@ +# Copyright (c) 2013 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. +import os + +import tank +import vrScenegraph +from tank import Hook +from tank import TankError + +from pprint import pformat + + + +class ScanSceneHook(Hook): + """ + Hook to scan scene for items to publish + """ + + def execute(self, **kwargs): + """ + Main hook entry point + :returns: A list of any items that were found to be published. + Each item in the list should be a dictionary containing + the following keys: + { + type: String + This should match a scene_item_type defined in + one of the outputs in the configuration and is + used to determine the outputs that should be + published for the item + + name: String + Name to use for the item in the UI + + description: String + Description of the item to use in the UI + + selected: Bool + Initial selected state of item in the UI. + Items are selected by default. + + required: Bool + Required state of item in the UI. If True then + item will not be deselectable. Items are not + required by default. + + other_params: Dictionary + Optional dictionary that will be passed to the + pre-publish and publish hooks + } + """ + # Setup Data Objects + engine = tank.platform.current_engine() + app = self.parent + + publish_template = app.get_template('primary_publish_template') + if publish_template is None: + raise TankError("Configuration Error: Could not find template specified with primary_publish_template") + + # Get and Check Main File + file = self.parent.engine.get_current_file() + if not file: + raise TankError("Please Save your file before Publishing") + + # Main File + retFileList = [ + { + 'type': 'vred_file', + 'name': os.path.basename(file), + 'description': '', + 'selected': True, + 'required': True, + 'other_params': {} + } + ] + + + # Add Secondary Outputs - Geometry Only + rootNode = vrScenegraph.getRootNode() + for n in range(0,rootNode.getNChildren()): + childNode = rootNode.getChild(n) + if childNode.getType() == "Geometry": + # Add node Info + fieldAcc = childNode.fields() + newNode = { + 'type': 'Geometry', + 'name': childNode.getName(), + 'description': 'Geometry Node', + 'selected': False, + 'required': False, + 'other_params': { + 'NodeID': fieldAcc.getID() + } + } + retFileList.append(newNode) + + # Return File List + return retFileList \ No newline at end of file diff --git a/hooks/scene_operation_tk-vred.py b/hooks/scene_operation_tk-vred.py new file mode 100644 index 0000000..b56ec84 --- /dev/null +++ b/hooks/scene_operation_tk-vred.py @@ -0,0 +1,100 @@ +import sgtk +import vrFileIO +import vrController +import vrScript +import vrScenegraph +from sgtk import TankError +import vrFieldAccess + +HookClass = sgtk.get_hook_baseclass() +class SceneOperation(HookClass): + def execute(self, operation, file_path=None, context=None, parent_action=None, file_version=None, read_only=None, **kwargs): + if operation == "current_path": + # Get the current file path. + current_path = self.parent.engine.get_current_file() + if current_path == None: + return "" #it's a new file + else: + return current_path + else: + if operation == "open": + self.parent.engine.log_info("Scene Operation, Open file: "+file_path) + self.parent.engine.reset_scene() + self.parent.engine.load_file(file_path) + elif operation == "save": + # If file path not specified save in place. + if file_path == None: + file_path = self.parent.engine.get_current_file() + self.parent.engine.log_info("Scene Operation, Save file: "+file_path) + self.parent.engine.save_current_file(file_path) + elif operation == "save_as": + self.parent.engine.log_info("Scene Operation, Save_as file: "+file_path) + self.parent.engine.save_current_file(file_path) + elif operation == "reset": + self.parent.engine.log_info("Scene Operation, Reset Scene") + # Reset the Scene in VRED + self.parent.engine.reset_scene() + + sgtk.platform.current_engine().rebuild_shotgun_menu() + return True + + def scan_scene(self): + """ + The scan scene method is executed once at startup and its purpose is + to analyze the current scene and return a list of references that are + to be potentially operated on. + The return data structure is a list of dictionaries. Each scene reference + that is returned should be represented by a dictionary with three keys: + - "node": The name of the 'node' that is to be operated on. Most DCCs have + a concept of a node, path or some other way to address a particular + object in the scene. + - "type": The object type that this is. This is later passed to the + update method so that it knows how to handle the object. + - "path": Path on disk to the referenced object. + Toolkit will scan the list of items, see if any of the objects matches + any templates and try to determine if there is a more recent version + available. Any such versions are then displayed in the UI as out of date. + """ + return_ref_list = [] + _nodeList = vrScenegraph.getAllNodes() + for _node in _nodeList: + _filePath = None + if _node.hasAttachment("FileInfo"): + _att = _node.getAttachment("FileInfo") + _filePath = vrFieldAccess.vrFieldAccess(_att).getString("filename") + if _filePath is not None: + return_ref_list.append({ + "node": _node.getName(), + "type": _node.getType(), + "path": _filePath, + "oldpath": _filePath + }) + return return_ref_list + + def update(self, items): + """ + Perform replacements given a number of scene items passed from the app. + Once a selection has been performed in the main UI and the user clicks + the update button, this method is called. + The items parameter is a list of dictionaries on the same form as was + generated by the scan_scene hook above. The path key now holds + the that each node should be updated *to* rather than the current path. + """ + app = self.parent.engine + app.log_info("update scene") + app.log_info(items) + _nodeList = vrScenegraph.getAllNodes() + + # Perform update for each item selected. + for item in items: + for _node in _nodeList: + _filePath = None + if _node.hasAttachment("FileInfo"): + _att = _node.getAttachment("FileInfo") + _filePath = vrFieldAccess.vrFieldAccess(_att).getString("filename") + if _filePath is not None and item["node"] == _node.getName(): + # Delete the node + if vrScenegraph.deleteNode(_node,True): + # Load the new node. + self.parent.engine.load_file(item["path"]) + \ No newline at end of file diff --git a/hooks/secondary_pre_publish.py b/hooks/secondary_pre_publish.py new file mode 100644 index 0000000..feadd63 --- /dev/null +++ b/hooks/secondary_pre_publish.py @@ -0,0 +1,97 @@ +# Copyright (c) 2013 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +import os + +import tank +from tank import Hook +from tank import TankError + +class PrePublishHook(Hook): + """ + Single hook that implements pre-publish functionality + """ + def execute(self, tasks, work_template, progress_cb, user_data, **kwargs): + """ + Main hook entry point + :param tasks: List of tasks to be pre-published. Each task is be a + dictionary containing the following keys: + { + item: Dictionary + This is the item returned by the scan hook + { + name: String + description: String + type: String + other_params: Dictionary + } + + output: Dictionary + This is the output as defined in the configuration - the + primary output will always be named 'primary' + { + name: String + publish_template: template + tank_type: String + } + } + + :param work_template: template + This is the template defined in the config that + represents the current work file + + :param progress_cb: Function + A progress callback to log progress during pre-publish. Call: + + progress_cb(percentage, msg) + + to report progress to the UI + + :param user_data: A dictionary containing any data shared by other hooks run prior to + this hook. Additional data may be added to this dictionary that will + then be accessible from user_data in any hooks run after this one. + + :returns: A list of any tasks that were found which have problems that + need to be reported in the UI. Each item in the list should + be a dictionary containing the following keys: + { + task: Dictionary + This is the task that was passed into the hook and + should not be modified + { + item:... + output:... + } + + errors: List + A list of error messages (strings) to report + } + """ + self.parent.engine.log_info("Starting secondary pre-publish") + + prevNamesList = [] + dubNamesList = [] + + for task in tasks: + if task["item"]["name"] in prevNamesList: + dubNamesList.append(task["item"]["name"]) + prevNamesList.append(task["item"]["name"]) + + dubErrorList = [] + for dubName in dubNamesList: + for task in tasks: + if task["item"]["name"] == dubName: + dubErrorList.append({ + "task":task, + "errors": ["Duplicate Node Name Found."] + }) + + return dubErrorList + diff --git a/hooks/secondary_publish.py b/hooks/secondary_publish.py new file mode 100644 index 0000000..7c79344 --- /dev/null +++ b/hooks/secondary_publish.py @@ -0,0 +1,262 @@ +# Copyright (c) 2013 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +import os + +import tank +from tank import Hook +from tank import TankError +from os.path import isfile,basename,dirname,splitext +from os import remove +from subprocess import check_call,CalledProcessError +from string import split +from pprint import pformat +from collections import namedtuple +import vrScenegraph +import vrFileIO + +class PublishHook(Hook): + """ + Single hook that implements publish functionality for secondary tasks + """ + def execute( + self, tasks, work_template, comment, thumbnail_path, sg_task, primary_task, + primary_publish_path, progress_cb, user_data, **kwargs): + """ + Main hook entry point + :param tasks: List of secondary tasks to be published. Each task is a + dictionary containing the following keys: + { + item: Dictionary + This is the item returned by the scan hook + { + name: String + description: String + type: String + other_params: Dictionary + } + + output: Dictionary + This is the output as defined in the configuration - the + primary output will always be named 'primary' + { + name: String + publish_template: template + tank_type: String + } + } + + :param work_template: template + This is the template defined in the config that + represents the current work file + + :param comment: String + The comment provided for the publish + + :param thumbnail: Path string + The default thumbnail provided for the publish + + :param sg_task: Dictionary (shotgun entity description) + The shotgun task to use for the publish + + :param primary_publish_path: Path string + This is the path of the primary published file as returned + by the primary publish hook + + :param progress_cb: Function + A progress callback to log progress during pre-publish. Call: + + progress_cb(percentage, msg) + + to report progress to the UI + + :param primary_task: The primary task that was published by the primary publish hook. Passed + in here for reference. This is a dictionary in the same format as the + secondary tasks above. + + :param user_data: A dictionary containing any data shared by other hooks run prior to + this hook. Additional data may be added to this dictionary that will + then be accessible from user_data in any hooks run after this one. + + :returns: A list of any tasks that had problems that need to be reported + in the UI. Each item in the list should be a dictionary containing + the following keys: + { + task: Dictionary + This is the task that was passed into the hook and + should not be modified + { + item:... + output:... + } + + errors: List + A list of error messages (strings) to report + } + """ + self.parent.engine.log_info("Starting secondary publish") + + scene_path = self.parent.engine.get_current_file() + fields = work_template.get_fields(scene_path) + + returnErrors = [] + + for task in tasks: + # Log info + self.parent.engine.log_info("Publish Node "+task["item"]["name"]) + self.parent.engine.log_info("Template: {}".format(str(task["output"]["publish_template"]))) + + # Get the publish path + progress_cb(10.0, "Get the publish path",task) + try: + fields["nodeName"] = task["item"]["name"] + publish_path = task["output"]["publish_template"].apply_fields(fields) + except: + returnErrors.append({ + "task":task, + "errors":["Failed to get Publish Path"] + }) + return returnErrors + + # Get and select the node to save based on the id and node name + progress_cb(30.0, "Get and select the node to save based on the id and node name",task) + _rootNode = vrScenegraph.getRootNode() + vrNodePtr = None + for _n in range(0,_rootNode.getNChildren()): + _childNode = _rootNode.getChild(_n) + if _childNode.getType() == "Geometry": + if _childNode.getName() == task["item"]["name"] and _childNode.fields().getID() == task["item"]["other_params"]["NodeID"]: + vrNodePtr = _childNode + break + + if vrNodePtr is None: + returnErrors.append({ + "task":task, + "errors": ["Failed to Get Node "+task["item"]["name"]] + }) + else: + # Save file to publish path + progress_cb(60.0, "Save file to publish path",task) + try: + vrFileIO.saveGeometry(vrNodePtr, publish_path) + except: + returnErrors.append({ + "task":task, + "errors":["Failed to save Node ( "+task["item"]["name"]+" )Geometry OSB file for "+publish_path] + }) + + # Register the published file + progress_cb(90.0, "Register the published file",task) + + self._register_publish( + publish_path, + self._get_publish_name(publish_path, task["output"]["publish_template"], fields), + sg_task, + fields["version"], + task["output"]["tank_type"], + comment, + thumbnail_path, + []) + + progress_cb(100.0,"Task Complete",task) + return returnErrors + + + def _register_publish(self, path, name, sg_task, publish_version, tank_type, comment, thumbnail_path, dependency_paths): + """ + Helper method to register publish using the + specified publish info. + """ + # construct args: + args = { + "tk": self.parent.tank, + "context": self.parent.context, + "comment": comment, + "path": path, + "name": name, + "version_number": publish_version, + "thumbnail_path": thumbnail_path, + "task": sg_task, + "dependency_paths": dependency_paths, + "published_file_type":tank_type, + } + + self.parent.log_debug("Register publish in shotgun: %s" % str(args)) + + # register publish; + sg_data = tank.util.register_publish(**args) + + return sg_data + + + def _get_publish_name(self, path, template, fields=None): + """ + Return the 'name' to be used for the file - if possible + this will return a 'versionless' name + """ + # first, extract the fields from the path using the template: + fields = fields.copy() if fields else template.get_fields(path) + if "name" in fields and fields["name"]: + # well, that was easy! + name = fields["name"] + else: + # find out if version is used in the file name: + template_name, _ = os.path.splitext(os.path.basename(template.definition)) + version_in_name = "{version}" in template_name + + # extract the file name from the path: + name, _ = os.path.splitext(os.path.basename(path)) + delims_str = "_-. " + if version_in_name: + # looks like version is part of the file name so we + # need to isolate it so that we can remove it safely. + # First, find a dummy version whose string representation + # doesn't exist in the name string + version_key = template.keys["version"] + dummy_version = 9876 + while True: + test_str = version_key.str_from_value(dummy_version) + if test_str not in name: + break + dummy_version += 1 + + # now use this dummy version and rebuild the path + fields["version"] = dummy_version + path = template.apply_fields(fields) + name, _ = os.path.splitext(os.path.basename(path)) + + # we can now locate the version in the name and remove it + dummy_version_str = version_key.str_from_value(dummy_version) + + v_pos = name.find(dummy_version_str) + # remove any preceeding 'v' + pre_v_str = name[:v_pos].rstrip("v") + post_v_str = name[v_pos + len(dummy_version_str):] + + if (pre_v_str and post_v_str + and pre_v_str[-1] in delims_str + and post_v_str[0] in delims_str): + # only want one delimiter - strip the second one: + post_v_str = post_v_str.lstrip(delims_str) + + versionless_name = pre_v_str + post_v_str + versionless_name = versionless_name.strip(delims_str) + + if versionless_name: + # great - lets use this! + name = versionless_name + else: + # likely that version is only thing in the name so + # instead, replace the dummy version with #'s: + zero_version_str = version_key.str_from_value(0) + new_version_str = "#" * len(zero_version_str) + name = name.replace(dummy_version_str, new_version_str) + + return name+"-"+fields["nodeName"] \ No newline at end of file diff --git a/hooks/tk-multi-publish2/basic/collector.py b/hooks/tk-multi-publish2/basic/collector.py new file mode 100644 index 0000000..0ebfc79 --- /dev/null +++ b/hooks/tk-multi-publish2/basic/collector.py @@ -0,0 +1,96 @@ +# Copyright (c) 2017 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +import os + +import sgtk + +HookBaseClass = sgtk.get_hook_baseclass() + + +class VREDSessionCollector(HookBaseClass): + """ + Collector that operates on the vred session. Should inherit from the basic + collector hook. + """ + @property + def settings(self): + collector_settings = super(VREDSessionCollector, self).settings or {} + vred_session_settings = { + "Work Template": { + "type": "template", + "default": None, + "description": "Template path for artist work files. Should " + "correspond to a template defined in " + "templates.yml. If configured, is made available" + "to publish plugins via the collected item's " + "properties. ", + }, + } + + collector_settings.update(vred_session_settings) + + return collector_settings + + def process_current_session(self, settings, parent_item): + """ + Analyzes the current scene open in a DCC and parents a subtree of items + under the parent_item passed in. + + :param dict settings: Configured settings for this collector + :param parent_item: Root item instance + """ + + publisher = self.parent + engine = publisher.engine + path = engine.get_current_file() + + if path: + file_info = publisher.util.get_file_path_components(path) + display_name = file_info["filename"] + else: + display_name = "Current VRED Session" + + session_item = super(VREDSessionCollector, self)._collect_file(parent_item, path, frame_sequence=True) + + # get the icon path to display for this item + icon_path = os.path.join( + self.disk_location, + os.pardir, + "icons", + "vred.png" + ) + session_item.set_icon_from_path(icon_path) + session_item.type = "vred.session" + session_item.name = display_name + session_item.display_type = "VRED Session" + + self._collect_session_renders(session_item) + + def _collect_session_renders(self, parent_item): + """ + Creates items for session renders to be exported. + + :param parent_item: Parent Item instance + """ + publisher = self.parent + engine = publisher.engine + base_dir = os.path.dirname(engine.get_render_path(parent_item.properties.get("path"))) + files = os.listdir(base_dir) + + if not files: + return + + for file_name in files: + path = os.path.join(base_dir, file_name) + item = super(VREDSessionCollector, self)._collect_file(parent_item, path) + item.type = "vred.session.renders" + item.name = file_name + item.display_type = "VRED Session Render" diff --git a/hooks/tk-multi-publish2/basic/publish_version.py b/hooks/tk-multi-publish2/basic/publish_version.py new file mode 100644 index 0000000..a20c3b2 --- /dev/null +++ b/hooks/tk-multi-publish2/basic/publish_version.py @@ -0,0 +1,66 @@ +# Copyright (c) 2017 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +import sgtk + +HookBaseClass = sgtk.get_hook_baseclass() + + +class PublishVersionPlugin(HookBaseClass): + @property + def name(self): + """ + One line display name describing the plugin + """ + return "Create a version" + + @property + def item_filters(self): + """ + List of item types that this plugin is interested in. + + Only items matching entries in this list will be presented to the + accept() method. Strings can contain glob patters such as *, for example + ["maya.*", "file.maya"] + """ + return ["*"] + + def accept(self, settings, item): + """ + Method called by the publisher to determine if an item is of any + interest to this plugin. Only items matching the filters defined via the + item_filters property will be presented to this method. + + A publish task will be generated for each item accepted here. Returns a + dictionary with the following booleans: + + - accepted: Indicates if the plugin is interested in this value at + all. Required. + - enabled: If True, the plugin will be enabled in the UI, otherwise + it will be disabled. Optional, True by default. + - visible: If True, the plugin will be visible in the UI, otherwise + it will be hidden. Optional, True by default. + - checked: If True, the plugin will be checked in the UI, otherwise + it will be unchecked. Optional, True by default. + + :param settings: Dictionary of Settings. The keys are strings, matching + the keys returned in the settings property. The values are `Setting` + instances. + :param item: Item to process + + :returns: dictionary with boolean keys accepted, required and enabled + """ + + return { + "accepted": True, + "visible": True, + "checked": True, + "enabled": False + } diff --git a/hooks/tk-multi-publish2/basic/vred_publish_file_obs.py b/hooks/tk-multi-publish2/basic/vred_publish_file_obs.py new file mode 100644 index 0000000..a355cfd --- /dev/null +++ b/hooks/tk-multi-publish2/basic/vred_publish_file_obs.py @@ -0,0 +1,91 @@ +# Copyright (c) 2017 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +import sgtk + +HookBaseClass = sgtk.get_hook_baseclass() + + +class VREDPublishOBSFilePlugin(HookBaseClass): + @property + def settings(self): + # inherit the settings from the base publish plugin + base_settings = super(VREDPublishOBSFilePlugin, self).settings or {} + + # settings specific to this class + publish_settings = { + "Publish Template": { + "type": "template", + "default": None, + "description": "Template path for published work files. Should" + "correspond to a template defined in " + "templates.yml.", + } + } + + base_settings.update(publish_settings) + + workfile_settings = { + "Work Template": { + "type": "template", + "default": None, + "description": "Template path for published work files. Should" + "correspond to a template defined in " + "templates.yml.", + } + } + + base_settings.update(workfile_settings) + + translator_settings = { + "Translator": { + "type": "dictionary", + "default": None + } + } + + base_settings.update(translator_settings) + + return base_settings + + def validate(self, settings, item): + publisher = self.parent + + publish_template_setting = settings.get("Publish Template") + publish_template = publisher.engine.get_template_by_name(publish_template_setting.value) + + if not publish_template: + return False + + if publish_template: + item.properties["publish_template"] = publish_template + + workfile_template_setting = settings.get("Work Template") + workfile_template = publisher.engine.get_template_by_name(workfile_template_setting.value) + + if not workfile_template: + return False + + item.properties["work_template"] = workfile_template + + item.properties["translator"] = settings.get("Translator") + + return True + + def accept(self, settings, item): + base_accept = super(VREDPublishOBSFilePlugin, self).accept(settings, item) + + base_accept.update({"checked": False}) + + return base_accept + + @property + def item_filters(self): + return ["vred.session"] diff --git a/hooks/tk-multi-publish2/basic/vred_publish_file_renders.py b/hooks/tk-multi-publish2/basic/vred_publish_file_renders.py new file mode 100644 index 0000000..754a96c --- /dev/null +++ b/hooks/tk-multi-publish2/basic/vred_publish_file_renders.py @@ -0,0 +1,190 @@ +# Copyright (c) 2017 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +import pprint +import sys + +import sgtk + +HookBaseClass = sgtk.get_hook_baseclass() + + +class VREDPublishFileRendersPlugin(HookBaseClass): + @property + def description(self): + """ + Verbose, multi-line description of what the plugin does. This can + contain simple html for formatting. + """ + + return """ +

This plugin publishes renders for the current session. Any + session render will be exported to the path defined by this plugin's + configured "Publish Template" setting.

+ """ + + @property + def settings(self): + # inherit the settings from the base publish plugin + base_settings = super(VREDPublishFileRendersPlugin, self).settings or {} + + # settings specific to this class + publish_settings = { + "Publish Template": { + "type": "template", + "default": None, + "description": "Template path for published work files. Should" + "correspond to a template defined in " + "templates.yml.", + } + } + + base_settings.update(publish_settings) + + return base_settings + + def validate(self, settings, item): + return True + + def accept(self, settings, item): + base_accept = super(VREDPublishFileRendersPlugin, self).accept(settings, item) + + base_accept.update({"checked": False}) + + return base_accept + + @property + def item_filters(self): + """ + List of item types that this plugin is interested in. + + Only items matching entries in this list will be presented to the + accept() method. Strings can contain glob patters such as *, for example + ["maya.*", "file.maya"] + """ + return ["vred.session.renders"] + + def publish(self, settings, item): + """ + Executes the publish logic for the given item and settings. + + :param settings: Dictionary of Settings. The keys are strings, matching + the keys returned in the settings property. The values are `Setting` + instances. + :param item: Item to process + """ + + publisher = self.parent + path = item.properties["path"] + + # allow the publish name to be supplied via the item properties. this is + # useful for collectors that have access to templates and can determine + # publish information about the item that doesn't require further, fuzzy + # logic to be used here (the zero config way) + publish_name = item.properties.get("publish_name") + if not publish_name: + self.logger.debug("Using path info hook to determine publish name.") + + # use the path's filename as the publish name + path_components = publisher.util.get_file_path_components(path) + publish_name = path_components["filename"] + + self.logger.debug("Publish name: %s" % (publish_name,)) + + self.logger.info("Creating Version...") + version_data = { + "project": item.context.project, + "code": publish_name, + "description": item.description, + "entity": self._get_version_entity(item), + "sg_task": item.context.task + } + + if "sg_publish_data" in item.properties: + publish_data = item.properties["sg_publish_data"] + version_data["published_files"] = [publish_data] + + version_data["sg_path_to_movie"] = path + + # log the version data for debugging + self.logger.debug( + "Populated Version data...", + extra={ + "action_show_more_info": { + "label": "Version Data", + "tooltip": "Show the complete Version data dictionary", + "text": "
%s
" % (pprint.pformat(version_data),) + } + } + ) + + # Create the version + version = publisher.shotgun.create("Version", version_data) + self.logger.info("Version created!") + + # stash the version info in the item just in case + item.properties["sg_version_data"] = version + + thumb = item.get_thumbnail_as_path() + + self.logger.info("Uploading content...") + + # on windows, ensure the path is utf-8 encoded to avoid issues with + # the shotgun api + if sys.platform.startswith("win"): + upload_path = path.decode("utf-8") + else: + upload_path = path + + self.parent.shotgun.upload( + "Version", + version["id"], + upload_path, + "sg_uploaded_movie" + ) + + self.logger.info("Upload complete!") + + def _get_version_entity(self, item): + """ + Returns the best entity to link the version to. + """ + + if item.context.entity: + return item.context.entity + elif item.context.project: + return item.context.project + else: + return None + + def finalize(self, settings, item): + """ + Execute the finalization pass. This pass executes once all the publish + tasks have completed, and can for example be used to version up files. + + :param settings: Dictionary of Settings. The keys are strings, matching + the keys returned in the settings property. The values are `Setting` + instances. + :param item: Item to process + """ + + path = item.properties["path"] + version = item.properties["sg_version_data"] + + self.logger.info( + "Version uploaded for file: %s" % (path,), + extra={ + "action_show_in_shotgun": { + "label": "Show Version", + "tooltip": "Reveal the version in Shotgun.", + "entity": version + } + } + ) diff --git a/hooks/tk-multi-publish2/basic/vred_publish_file_vpb.py b/hooks/tk-multi-publish2/basic/vred_publish_file_vpb.py new file mode 100644 index 0000000..4e5a5ff --- /dev/null +++ b/hooks/tk-multi-publish2/basic/vred_publish_file_vpb.py @@ -0,0 +1,129 @@ +# Copyright (c) 2017 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +import os + +import sgtk + +HookBaseClass = sgtk.get_hook_baseclass() + + +class VREDPublishFilePlugin(HookBaseClass): + @property + def settings(self): + # inherit the settings from the base publish plugin + base_settings = super(VREDPublishFilePlugin, self).settings or {} + + # settings specific to this class + publish_settings = { + "Publish Template": { + "type": "template", + "default": None, + "description": "Template path for published work files. Should" + "correspond to a template defined in " + "templates.yml.", + } + } + + base_settings.update(publish_settings) + + workfile_settings = { + "Work Template": { + "type": "template", + "default": None, + "description": "Template path for published work files. Should" + "correspond to a template defined in " + "templates.yml.", + } + } + + base_settings.update(workfile_settings) + + return base_settings + + def validate(self, settings, item): + publisher = self.parent + + publish_template_setting = settings.get("Publish Template") + publish_template = publisher.engine.get_template_by_name(publish_template_setting.value) + + if not publish_template: + return False + + if publish_template: + item.properties["publish_template"] = publish_template + + workfile_template_setting = settings.get("Work Template") + workfile_template = publisher.engine.get_template_by_name(workfile_template_setting.value) + + if not workfile_template: + return False + + item.properties["work_template"] = workfile_template + path = item.properties["path"] + + (next_version_path, version) = self._get_next_version_info(path, item) + if not next_version_path: + error_msg = "There's not a suitable next version path for this file." + self.logger.error(error_msg) + raise Exception(error_msg) + + if os.path.exists(next_version_path): + error_msg = "The next version of this file already exists on disk." + self.logger.error(error_msg) + raise Exception(error_msg) + + item.properties["next_version_path"] = next_version_path + + return True + + def publish(self, settings, item): + super(VREDPublishFilePlugin, self).publish(settings, item) + self.logger.info("Saving new version!") + publisher = self.parent + engine = publisher.engine + engine.save_after_publish(item.properties["next_version_path"]) + + @property + def item_filters(self): + return ["vred.session"] + + def accept(self, settings, item): + """ + Method called by the publisher to determine if an item is of any + interest to this plugin. Only items matching the filters defined via the + item_filters property will be presented to this method. + + A publish task will be generated for each item accepted here. Returns a + dictionary with the following booleans: + + - accepted: Indicates if the plugin is interested in this value at + all. Required. + - enabled: If True, the plugin will be enabled in the UI, otherwise + it will be disabled. Optional, True by default. + - visible: If True, the plugin will be visible in the UI, otherwise + it will be hidden. Optional, True by default. + - checked: If True, the plugin will be checked in the UI, otherwise + it will be unchecked. Optional, True by default. + + :param settings: Dictionary of Settings. The keys are strings, matching + the keys returned in the settings property. The values are `Setting` + instances. + :param item: Item to process + + :returns: dictionary with boolean keys accepted, required and enabled + """ + + return { + "accepted": True, + "visible": True, + "checked": True, + "enabled": False + } diff --git a/hooks/tk-multi-publish2/icons/vred.png b/hooks/tk-multi-publish2/icons/vred.png new file mode 100644 index 0000000..58c79ba Binary files /dev/null and b/hooks/tk-multi-publish2/icons/vred.png differ diff --git a/hooks/tk-vred_actions.py b/hooks/tk-vred_actions.py new file mode 100644 index 0000000..8181091 --- /dev/null +++ b/hooks/tk-vred_actions.py @@ -0,0 +1,229 @@ +# Copyright (c) 2015 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +""" +Hook that loads defines all the available actions, broken down by publish type. +""" +import sgtk +import os +import commands +import vrScenegraph +from sgtk.platform.qt import QtGui, QtCore + + +HookBaseClass = sgtk.get_hook_baseclass() + +class VredActions(HookBaseClass): + + ############################################################################################################## + # public interface - to be overridden by deriving classes + + def generate_actions(self, sg_publish_data, actions, ui_area): + """ + Returns a list of action instances for a particular publish. + This method is called each time a user clicks a publish somewhere in the UI. + The data returned from this hook will be used to populate the actions menu for a publish. + + The mapping between Publish types and actions are kept in a different place + (in the configuration) so at the point when this hook is called, the loader app + has already established *which* actions are appropriate for this object. + + The hook should return at least one action for each item passed in via the + actions parameter. + + This method needs to return detailed data for those actions, in the form of a list + of dictionaries, each with name, params, caption and description keys. + + Because you are operating on a particular publish, you may tailor the output + (caption, tooltip etc) to contain custom information suitable for this publish. + + The ui_area parameter is a string and indicates where the publish is to be shown. + - If it will be shown in the main browsing area, "main" is passed. + - If it will be shown in the details area, "details" is passed. + - If it will be shown in the history area, "history" is passed. + + Please note that it is perfectly possible to create more than one action "instance" for + an action! You can for example do scene introspection - if the action passed in + is "character_attachment" you may for example scan the scene, figure out all the nodes + where this object can be attached and return a list of action instances: + "attach to left hand", "attach to right hand" etc. In this case, when more than + one object is returned for an action, use the params key to pass additional + data into the run_action hook. + + :param sg_publish_data: Shotgun data dictionary with all the standard publish fields. + :param actions: List of action strings which have been defined in the app configuration. + :param ui_area: String denoting the UI Area (see above). + :returns List of dictionaries, each with keys name, params, caption and description + """ + app = self.parent + app.log_debug("Generate actions called for UI element %s. " + "Actions: %s. Publish Data: %s" % (ui_area, actions, sg_publish_data)) + action_instances = [] + + if "assign_task" in actions: + action_instances.append({ + "name": "assign_task", + "params": None, + "caption": "Assign Task to yourself", + "description": "Assign this task to yourself." + }) + + if "task_to_ip" in actions: + action_instances.append({ + "name": "task_to_ip", + "params": None, + "caption": "Set to In Progress", + "description": "Set the task status to In Progress." + }) + + if "reference" in actions: + action_instances.append({ + "name": "reference", + "params": None, + "caption": "Create Reference", + "description": "This will add the item to the universe as a standard reference." + }) + + if "import" in actions: + action_instances.append({ + "name": "import", + "params": None, + "caption": "Import into VRED Scene", + "description": "This will import the item into the current VRED Scene." + }) + + if "load" in actions: + action_instances.append({ + "name": "load", + "params": None, + "caption": "Load VRED Scene", + "description": "This will load file as a new VRED Scene" + }) + + return action_instances + + def execute_action(self, name, params, sg_publish_data): + """ + Execute a given action. The data sent to this be method will + represent one of the actions enumerated by the generate_actions method. + + :param name: Action name string representing one of the items returned by generate_actions. + :param params: Params data, as specified by generate_actions. + :param sg_publish_data: Shotgun data dictionary with all the standard publish fields. + :returns: No return value expected. + """ + app = self.parent + app.log_debug("Execute action called for action %s. " + "Parameters: %s. Publish Data: %s" % (name, params, sg_publish_data)) + + if name == "assign_task": + if app.context.user is None: + raise Exception("Cannot establish current user!") + + data = app.shotgun.find_one("Task", [["id", "is", sg_publish_data["id"]]], ["task_assignees"] ) + assignees = data["task_assignees"] or [] + assignees.append(app.context.user) + app.shotgun.update("Task", sg_publish_data["id"], {"task_assignees": assignees}) + elif name == "task_to_ip": + app.shotgun.update("Task", sg_publish_data["id"], {"sg_status_list": "ip"}) + else: + # resolve path + path = self.get_publish_path(sg_publish_data) + + if name == "reference": + self._create_reference(path, sg_publish_data) + + if name == "import": + self._do_import(path, sg_publish_data) + + if name == "load": + self._do_load(path, sg_publish_data) + + def execute_multiple_actions(self, actions): + """ + Executes the specified action on a list of items. + + The default implementation dispatches each item from ``actions`` to + the ``execute_action`` method. + + The ``actions`` is a list of dictionaries holding all the actions to execute. + Each entry will have the following values: + + name: Name of the action to execute + sg_publish_data: Publish information coming from Shotgun + params: Parameters passed down from the generate_actions hook. + + .. note:: + This is the default entry point for the hook. It reuses the ``execute_action`` + method for backward compatibility with hooks written for the previous + version of the loader. + + .. note:: + The hook will stop applying the actions on the selection if an error + is raised midway through. + + :param list actions: Action dictionaries. + """ + for single_action in actions: + name = single_action.get("name") + sg_publish_data = single_action.get("sg_publish_data") + params = single_action.get("params") + self.execute_action(name, params, sg_publish_data) + + ############################################################################################################## + # helper methods which can be subclassed in custom hooks to fine tune the behaviour of things + + def _create_reference(self, path, sg_publish_data): + if not os.path.exists(path): + raise Exception("File not found on disk - '%s'" % path) + + namespace = "%s %s" % (sg_publish_data.get("entity").get("name"), sg_publish_data.get("name")) + namespace = namespace.replace(" ", "_") + + self.parent.engine.load_file([path],vrScenegraph.getRootNode(),False,False) + + def _loaded_successfully_messagebox(self, path): + """ + This will display a QMessageBox to notify the successful result + :param path: + :return: + """ + message_box = QtGui.QMessageBox() + message_box.setWindowTitle("Load File") + message_box.setText("File '{!r}' loaded successfully.".format(os.path.basename(path))) + message_box.setIcon(QtGui.QMessageBox.Information) + message_box.setStandardButtons(QtGui.QMessageBox.Ok) + message_box.setDefaultButton(QtGui.QMessageBox.Ok) + message_box.show() + message_box.raise_() + message_box.activateWindow() + message_box.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint | message_box.windowFlags()) + message_box.exec_() + + def _do_load(self, path, sg_publish_data): + """ + This will load a file as a new scene. + """ + if not os.path.exists(path): + raise Exception("File not found on disk - '%s'" % path) + self.parent.engine.reset_scene() + self.parent.engine.load_file(path) + + self._loaded_successfully_messagebox(path) + + def _do_import(self, path, sg_publish_data): + """ + This will import a file into an existing scene. + """ + if not os.path.exists(path): + raise Exception("File not found on disk - '%s'" % path) + self.parent.engine.load_file(path) + + self._loaded_successfully_messagebox(path) diff --git a/icon_256.png b/icon_256.png new file mode 100644 index 0000000..dd5c595 Binary files /dev/null and b/icon_256.png differ diff --git a/icon_design_256.png b/icon_design_256.png new file mode 100644 index 0000000..dcc07d4 Binary files /dev/null and b/icon_design_256.png differ diff --git a/icon_pro_256.png b/icon_pro_256.png new file mode 100644 index 0000000..6deb358 Binary files /dev/null and b/icon_pro_256.png differ diff --git a/info.yml b/info.yml new file mode 100644 index 0000000..41574f5 --- /dev/null +++ b/info.yml @@ -0,0 +1,33 @@ +# Copyright (c) 2015 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +# expected fields in the configuration file for this engine +configuration: + file_usage_hook: + type: hook + description: Hook to customize behaviour on file change and file close. + default_value: default_file_usage + + render_template: + type: template + description: "This template is to set de initial render file path." + allows_empty: true + fields: shotgun_root, sg_asset_type, Asset, Step, scene_name + +# the Shotgun fields that this engine needs in order to operate correctly +requires_shotgun_fields: + +# More verbose description of this item +display_name: "Shotgun Engine for VRED" +description: "Shotgun Integration for VRED" + +# Required minimum versions for this item to run +requires_shotgun_version: +requires_core_version: "v0.18.116" diff --git a/python/__init__.py b/python/__init__.py new file mode 100644 index 0000000..23e1154 --- /dev/null +++ b/python/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + diff --git a/python/startup/__init__.py b/python/startup/__init__.py new file mode 100644 index 0000000..23e1154 --- /dev/null +++ b/python/startup/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + diff --git a/python/startup/bootstrap.py b/python/startup/bootstrap.py new file mode 100644 index 0000000..407d08c --- /dev/null +++ b/python/startup/bootstrap.py @@ -0,0 +1,47 @@ +# Copyright (c) 2016 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. +import os +import sys +import subprocess +from os.path import dirname, abspath, join, expanduser, exists +from shutil import copyfile + +from sgtk.util import prepend_path_to_env_var +from distutils.dir_util import copy_tree + +CORE_SCRIPTS_DIR = r"C:\Program Files\Autodesk\VREDPro-11.0\lib\plugins\WIN64\Scripts" + +def bootstrap(engine_name, context, app_path, app_args, extra_args): + """ + Start engine acording data passed by params. + """ + startup_path = dirname( + abspath(sys.modules[bootstrap.__module__].__file__) + ) + + # Tell VRED to start the engine. + os.environ['SHOTGUN_ENABLE'] = '1' + + # Tell VRED where to find the script + resources_dir = join(dirname(dirname(dirname(__file__))), "resources") + scripts_dir = join(resources_dir, "Shotgun") + + os.environ["VRED_SCRIPT_PLUGINS"] = "{};{}".format(scripts_dir, CORE_SCRIPTS_DIR) + # VRED 2019 BETA SPECIFIC ENV VARIABLE - AVOID IT'S USAGE + # environment variable VRED%YEAR%_%UPDATE%_SCRIPT_PLUGINS + # If is BETA just VRED%YEAR%_SCRIPT_PLUGINS + # os.environ["VRED2019_SCRIPT_PLUGINS"] = "{};{}".format(scripts_dir, CORE_SCRIPTS_DIR) + + app_args = (app_args or "") + app_args += ' -insecure_python' + if(os.environ.get('DISABLE_VRED_OPENGL', '0') == '1'): + app_args += ' -no_opengl' + + return (app_path, app_args) \ No newline at end of file diff --git a/python/startup/vred_bootstrap.py b/python/startup/vred_bootstrap.py new file mode 100644 index 0000000..1b1af66 --- /dev/null +++ b/python/startup/vred_bootstrap.py @@ -0,0 +1,64 @@ +# Copyright (c) 2016 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. +import os +from os.path import dirname, abspath, join, expanduser, exists + +CORE_SCRIPTS_DIR = r"C:\Program Files\Autodesk\VREDPro-11.0\lib\plugins\WIN64\Scripts" + + +def compute_environment(engine_name=None, context=None): + """ + Return the env vars needed to launch the vred plugin. + This will generate a dictionary of environment variables + needed in order to launch the vred plugin. + :returns: dictionary of env var string key/value pairs. + """ + env = {} + + # Tell VRED to start the engine. + os.environ['SHOTGUN_ENABLE'] = '1' + + # Tell VRED where to find the script + resources_dir = join(dirname(dirname(dirname(__file__))), "resources") + scripts_dir = join(resources_dir, "Shotgun") + + os.environ["VRED_SCRIPT_PLUGINS"] = "{};{}".format(scripts_dir, CORE_SCRIPTS_DIR) + # VRED 2019 BETA SPECIFIC ENV VARIABLE - AVOID IT'S USAGE + # environment variable VRED%YEAR%_%UPDATE%_SCRIPT_PLUGINS + # If is BETA just VRED%YEAR%_SCRIPT_PLUGINS + # os.environ["VRED2019_SCRIPT_PLUGINS"] = "{};{}".format(scripts_dir, CORE_SCRIPTS_DIR) + + env['SHOTGUN_ENABLE'] = os.environ['SHOTGUN_ENABLE'] + env["VRED_SCRIPT_PLUGINS"] = os.environ["VRED_SCRIPT_PLUGINS"] + + if engine_name: + os.environ['SGTK_ENGINE'] = engine_name + env['SGTK_ENGINE'] = os.environ['SGTK_ENGINE'] + + if context: + os.environ['TANK_CONTEXT'] = context + env['TANK_CONTEXT'] = os.environ['TANK_CONTEXT'] + + return env + + +def compute_args(app_args): + """ + Return the args needed to launch the vred plugin. + This will generate a dictionary of args + needed in order to launch the vred plugin. + :returns: array of args. + """ + app_args = (app_args or "") + app_args += ' -insecure_python' + if os.environ.get('DISABLE_VRED_OPENGL', '0') == '1': + app_args += ' -no_opengl' + + return app_args \ No newline at end of file diff --git a/resources/Shotgun/vrShotgun.py b/resources/Shotgun/vrShotgun.py new file mode 100644 index 0000000..67faacc --- /dev/null +++ b/resources/Shotgun/vrShotgun.py @@ -0,0 +1,55 @@ +# vrPySideExample + +import os +import sys +import traceback + +import logging +logging.basicConfig(filename=os.path.expanduser('~/AppData/Roaming/Shotgun/logs/vrShotgun.log'), level=logging.DEBUG) + +from PySide2 import QtWidgets +from PySide2 import QtCore +import uiTools +import vrController +import vrFileIO +import vrMovieExport + +logging.info('\n\n\nShotgun Plugin ({})'.format(sys.version)) + +vrShotgun_form, vrShotgun_base = uiTools.loadUiType('vrShotgunGUI.ui') + +class vrShotgun(vrShotgun_form, vrShotgun_base): + + context = None + engine = None + + def __init__(self, parent=None): + super(vrShotgun, self).__init__(parent) + parent.layout().addWidget(self) + self.setupUi(self) + if 'SHOTGUN_ENABLE' in os.environ and os.environ['SHOTGUN_ENABLE'] == '1': + logging.info("Shotgun is enabled") + logging.info("Starting Engine") + + # Make Shotgun libraries visible + sys.path.append(r"C:\Program Files\Shotgun\Python\Lib\site-packages") + + # Launch the Engine + import tank + self.context = tank.context.deserialize(os.environ.get("TANK_CONTEXT")) + self.engine = tank.platform.start_engine('tk-vred', self.context.tank, self.context) + else: + logging.info("Shotgun plugin loaded however shotgun was not enabled!!") + + def __del__(self): + self.destroyMenu() + + def getVredMainWindow(self): + from shiboken2 import wrapInstance + return wrapInstance(VREDMainWindowId, QtWidgets.QMainWindow) + +try: + customMenu = vrShotgun(VREDPluginWidget) +except Exception as w: + v = traceback.format_exc() + logging.error('Error {}:\n{}'.format(w, v)) diff --git a/resources/Shotgun/vrShotgunGUI.ui b/resources/Shotgun/vrShotgunGUI.ui new file mode 100644 index 0000000..dd4ce03 --- /dev/null +++ b/resources/Shotgun/vrShotgunGUI.ui @@ -0,0 +1,34 @@ + + + vrShotgunGUI + + + + 0 + 0 + 228 + 45 + + + + vrShotgun + + + Scripts + + + + + + <h1>Shotgun Plugin 1.0</h1> + + + Qt::AlignCenter + + + + + + + + diff --git a/resources/pyside/bin/pyside2-lupdate.exe b/resources/pyside/bin/pyside2-lupdate.exe new file mode 100644 index 0000000..862ad5e Binary files /dev/null and b/resources/pyside/bin/pyside2-lupdate.exe differ diff --git a/resources/pyside/bin/pyside2-rcc.exe b/resources/pyside/bin/pyside2-rcc.exe new file mode 100644 index 0000000..5a360a8 Binary files /dev/null and b/resources/pyside/bin/pyside2-rcc.exe differ diff --git a/resources/pyside/bin/pyside2-uic b/resources/pyside/bin/pyside2-uic new file mode 100644 index 0000000..b8924a2 --- /dev/null +++ b/resources/pyside/bin/pyside2-uic @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +import sys +import optparse + +from PySide2 import QtCore +from pyside2uic.driver import Driver +from PySide2 import __version__ as PySideVersion +from pyside2uic import __version__ as PySideUicVersion + +Version = "PySide2 User Interface Compiler version %s, running on PySide2 %s." % (PySideUicVersion, PySideVersion) + +def main(): + if sys.hexversion >= 0x03000000: + from pyside2uic.port_v3.invoke import invoke + else: + from pyside2uic.port_v2.invoke import invoke + + parser = optparse.OptionParser(usage="pyside2-uic [options] ", + version=Version) + parser.add_option("-p", "--preview", dest="preview", action="store_true", + default=False, + help="show a preview of the UI instead of generating code") + parser.add_option("-o", "--output", dest="output", default="-", metavar="FILE", + help="write generated code to FILE instead of stdout") + parser.add_option("-x", "--execute", dest="execute", action="store_true", + default=False, + help="generate extra code to test and display the class") + parser.add_option("-d", "--debug", dest="debug", action="store_true", + default=False, help="show debug output") + parser.add_option("-i", "--indent", dest="indent", action="store", type="int", + default=4, metavar="N", + help="set indent width to N spaces, tab if N is 0 (default: 4)") + + g = optparse.OptionGroup(parser, title="Code generation options") + g.add_option("--from-imports", dest="from_imports", action="store_true", + default=False, help="generate imports relative to '.'") + parser.add_option_group(g) + + opts, args = parser.parse_args() + + if len(args) != 1: + sys.stderr.write("Error: one input ui-file must be specified\n") + sys.exit(1) + + sys.exit(invoke(Driver(opts, args[0]))) + +if __name__ == "__main__": + main() diff --git a/resources/pyside/bin/pyside2.dll b/resources/pyside/bin/pyside2.dll new file mode 100644 index 0000000..a1b67f6 Binary files /dev/null and b/resources/pyside/bin/pyside2.dll differ diff --git a/resources/pyside/bin/shiboken2.dll b/resources/pyside/bin/shiboken2.dll new file mode 100644 index 0000000..ac8ec26 Binary files /dev/null and b/resources/pyside/bin/shiboken2.dll differ diff --git a/resources/pyside/bin/shiboken2.exe b/resources/pyside/bin/shiboken2.exe new file mode 100644 index 0000000..72c9fdb Binary files /dev/null and b/resources/pyside/bin/shiboken2.exe differ diff --git a/resources/pyside/lib/cmake/PySide2-2.0.0/PySide2Config.cmake b/resources/pyside/lib/cmake/PySide2-2.0.0/PySide2Config.cmake new file mode 100644 index 0000000..fb52c57 --- /dev/null +++ b/resources/pyside/lib/cmake/PySide2-2.0.0/PySide2Config.cmake @@ -0,0 +1,18 @@ +# PYSIDE_INCLUDE_DIR - Directories to include to use PySide2 +# PYSIDE_LIBRARY - Files to link against to use PySide2 +# PYSIDE_PYTHONPATH - Path to where the PySide2 Python module files could be found +# PYSIDE_TYPESYSTEMS - Type system files that should be used by other bindings extending PySide2 + +SET(PYSIDE_INCLUDE_DIR "C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/include/PySide2") +# Platform specific library names +if(MSVC) + SET(PYSIDE_LIBRARY "C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/lib/pyside2.lib") +elseif(CYGWIN) + SET(PYSIDE_LIBRARY "C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/lib/pyside2.lib") +elseif(WIN32) + SET(PYSIDE_LIBRARY "C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/bin/pyside2.dll") +else() + SET(PYSIDE_LIBRARY "C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/lib/pyside2.dll") +endif() +SET(PYSIDE_PYTHONPATH "C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/Lib/site-packages") +SET(PYSIDE_TYPESYSTEMS "C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/share/PySide2/typesystems") diff --git a/resources/pyside/lib/cmake/PySide2-2.0.0/PySide2ConfigVersion.cmake b/resources/pyside/lib/cmake/PySide2-2.0.0/PySide2ConfigVersion.cmake new file mode 100644 index 0000000..a8d3bd1 --- /dev/null +++ b/resources/pyside/lib/cmake/PySide2-2.0.0/PySide2ConfigVersion.cmake @@ -0,0 +1,10 @@ +set(PACKAGE_VERSION 2.0.0) + +if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" ) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" ) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if( "${PACKAGE_FIND_VERSION}" STREQUAL "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_EXACT TRUE) + endif( "${PACKAGE_FIND_VERSION}" STREQUAL "${PACKAGE_VERSION}") +endif("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" ) diff --git a/resources/pyside/lib/cmake/Shiboken2-2.0.0/Shiboken2Config.cmake b/resources/pyside/lib/cmake/Shiboken2-2.0.0/Shiboken2Config.cmake new file mode 100644 index 0000000..f97d974 --- /dev/null +++ b/resources/pyside/lib/cmake/Shiboken2-2.0.0/Shiboken2Config.cmake @@ -0,0 +1,29 @@ +# SHIBOKEN_INCLUDE_DIR - Directories to include to use SHIBOKEN +# SHIBOKEN_LIBRARY - Files to link against to use SHIBOKEN +# SHIBOKEN_BINARY - Executable name +# SHIBOKEN_BUILD_TYPE - Tells if Shiboken was compiled in Release or Debug mode. +# SHIBOKEN_PYTHON_INTERPRETER - Python interpreter (regular or debug) to be used with the bindings. +# SHIBOKEN_PYTHON_LIBRARIES - Python libraries (regular or debug) Shiboken is linked against. + +SET(SHIBOKEN_INCLUDE_DIR "C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/include/shiboken2") +if(MSVC) + SET(SHIBOKEN_LIBRARY "C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/lib/shiboken2.lib") +elseif(CYGWIN) + SET(SHIBOKEN_LIBRARY "C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/lib/shiboken2.lib") +elseif(WIN32) + SET(SHIBOKEN_LIBRARY "C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/bin/shiboken2.dll") +else() + SET(SHIBOKEN_LIBRARY "C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/lib/shiboken2.dll") +endif() +SET(SHIBOKEN_PYTHON_INCLUDE_DIR "C:/Python27/include") +SET(SHIBOKEN_PYTHON_INCLUDE_DIR "C:/Python27/include") +SET(SHIBOKEN_PYTHON_INTERPRETER "C:/Python27/python.exe") +SET(SHIBOKEN_PYTHON_VERSION_MAJOR "2") +SET(SHIBOKEN_PYTHON_VERSION_MINOR "7") +SET(SHIBOKEN_PYTHON_VERSION_PATCH "10") +SET(SHIBOKEN_PYTHON_LIBRARIES "C:/Python27/libs/python27.lib") +SET(SHIBOKEN_PYTHON_EXTENSION_SUFFIX "") +message(STATUS "libshiboken built for Release") + + +set(SHIBOKEN_BINARY "C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/bin/shiboken2") diff --git a/resources/pyside/lib/cmake/Shiboken2-2.0.0/Shiboken2ConfigVersion.cmake b/resources/pyside/lib/cmake/Shiboken2-2.0.0/Shiboken2ConfigVersion.cmake new file mode 100644 index 0000000..a8d3bd1 --- /dev/null +++ b/resources/pyside/lib/cmake/Shiboken2-2.0.0/Shiboken2ConfigVersion.cmake @@ -0,0 +1,10 @@ +set(PACKAGE_VERSION 2.0.0) + +if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" ) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" ) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if( "${PACKAGE_FIND_VERSION}" STREQUAL "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_EXACT TRUE) + endif( "${PACKAGE_FIND_VERSION}" STREQUAL "${PACKAGE_VERSION}") +endif("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" ) diff --git a/resources/pyside/lib/pkgconfig/pyside2.pc b/resources/pyside/lib/pkgconfig/pyside2.pc new file mode 100644 index 0000000..81d9de0 --- /dev/null +++ b/resources/pyside/lib/pkgconfig/pyside2.pc @@ -0,0 +1,14 @@ +prefix=C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release +exec_prefix=C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release +libdir=C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/lib +includedir=C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/include/PySide2 +typesystemdir=C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/share/PySide2/typesystems +pythonpath=C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/Lib/site-packages + +Name: PySide2 +Description: Support library for Python bindings of Qt5-based libraries. +Version: 2.0.0~alpha0 +Libs: -L${libdir} -lpyside2 +Cflags: -I${includedir} +Requires: shiboken2 + diff --git a/resources/pyside/lib/pkgconfig/shiboken2.pc b/resources/pyside/lib/pkgconfig/shiboken2.pc new file mode 100644 index 0000000..c411896 --- /dev/null +++ b/resources/pyside/lib/pkgconfig/shiboken2.pc @@ -0,0 +1,13 @@ +prefix=C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release +exec_prefix=C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release +libdir=C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/lib +includedir=C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/include/shiboken2 +generator_location=C:/pyside/pySide/pyside2_install/py2.7-qt5.6.1-64bit-release/bin/shiboken2 +python_interpreter=C:/Python27/python.exe +python_include_dir=C:/Python27/include + +Name: shiboken2 +Description: Support library for Python bindings created with the Shiboken2 generator. +Version: 2.0.0 +Libs: C:/Python27/libs/python27.lib -L${libdir} -lshiboken2 +Cflags: -IC:/Python27/include -I${includedir}/ diff --git a/resources/pyside/lib/pyside2.lib b/resources/pyside/lib/pyside2.lib new file mode 100644 index 0000000..acf1164 Binary files /dev/null and b/resources/pyside/lib/pyside2.lib differ diff --git a/resources/pyside/lib/shiboken2.lib b/resources/pyside/lib/shiboken2.lib new file mode 100644 index 0000000..4481450 Binary files /dev/null and b/resources/pyside/lib/shiboken2.lib differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtCore.pyd b/resources/pyside/lib/site-packages/PySide2/QtCore.pyd new file mode 100644 index 0000000..2180474 Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtCore.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtGui.pyd b/resources/pyside/lib/site-packages/PySide2/QtGui.pyd new file mode 100644 index 0000000..8e2c892 Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtGui.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtHelp.pyd b/resources/pyside/lib/site-packages/PySide2/QtHelp.pyd new file mode 100644 index 0000000..46d0162 Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtHelp.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtMultimedia.pyd b/resources/pyside/lib/site-packages/PySide2/QtMultimedia.pyd new file mode 100644 index 0000000..c0ff640 Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtMultimedia.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtNetwork.pyd b/resources/pyside/lib/site-packages/PySide2/QtNetwork.pyd new file mode 100644 index 0000000..fa3df18 Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtNetwork.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtOpenGL.pyd b/resources/pyside/lib/site-packages/PySide2/QtOpenGL.pyd new file mode 100644 index 0000000..9d24111 Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtOpenGL.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtPrintSupport.pyd b/resources/pyside/lib/site-packages/PySide2/QtPrintSupport.pyd new file mode 100644 index 0000000..702ce87 Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtPrintSupport.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtQml.pyd b/resources/pyside/lib/site-packages/PySide2/QtQml.pyd new file mode 100644 index 0000000..5bbf87a Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtQml.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtQuick.pyd b/resources/pyside/lib/site-packages/PySide2/QtQuick.pyd new file mode 100644 index 0000000..31ec84e Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtQuick.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtQuickWidgets.pyd b/resources/pyside/lib/site-packages/PySide2/QtQuickWidgets.pyd new file mode 100644 index 0000000..ac63b42 Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtQuickWidgets.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtScript.pyd b/resources/pyside/lib/site-packages/PySide2/QtScript.pyd new file mode 100644 index 0000000..79e7e05 Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtScript.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtSql.pyd b/resources/pyside/lib/site-packages/PySide2/QtSql.pyd new file mode 100644 index 0000000..269f3ec Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtSql.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtSvg.pyd b/resources/pyside/lib/site-packages/PySide2/QtSvg.pyd new file mode 100644 index 0000000..f62d83b Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtSvg.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtTest.pyd b/resources/pyside/lib/site-packages/PySide2/QtTest.pyd new file mode 100644 index 0000000..22afc9f Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtTest.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtUiTools.pyd b/resources/pyside/lib/site-packages/PySide2/QtUiTools.pyd new file mode 100644 index 0000000..870e5f6 Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtUiTools.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtWebChannel.pyd b/resources/pyside/lib/site-packages/PySide2/QtWebChannel.pyd new file mode 100644 index 0000000..758454d Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtWebChannel.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtWebEngineWidgets.pyd b/resources/pyside/lib/site-packages/PySide2/QtWebEngineWidgets.pyd new file mode 100644 index 0000000..477865c Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtWebEngineWidgets.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtWebSockets.pyd b/resources/pyside/lib/site-packages/PySide2/QtWebSockets.pyd new file mode 100644 index 0000000..d77b883 Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtWebSockets.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtWidgets.pyd b/resources/pyside/lib/site-packages/PySide2/QtWidgets.pyd new file mode 100644 index 0000000..4fb3a64 Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtWidgets.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtXml.pyd b/resources/pyside/lib/site-packages/PySide2/QtXml.pyd new file mode 100644 index 0000000..7ebce0c Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtXml.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/QtXmlPatterns.pyd b/resources/pyside/lib/site-packages/PySide2/QtXmlPatterns.pyd new file mode 100644 index 0000000..7d9355c Binary files /dev/null and b/resources/pyside/lib/site-packages/PySide2/QtXmlPatterns.pyd differ diff --git a/resources/pyside/lib/site-packages/PySide2/__init__.py b/resources/pyside/lib/site-packages/PySide2/__init__.py new file mode 100644 index 0000000..09749cc --- /dev/null +++ b/resources/pyside/lib/site-packages/PySide2/__init__.py @@ -0,0 +1,41 @@ +__all__ = ['QtCore', 'QtGui', 'QtNetwork', 'QtOpenGL', 'QtSql', 'QtSvg', 'QtTest', 'QtWebKit', 'QtScript'] +__version__ = "2.0.0~alpha0" +__version_info__ = (2, 0, 0, "alpha", 0) + + +def _setupQtDirectories(): + import sys + import os + from . import _utils + + pysideDir = _utils.get_pyside_dir() + + # Register PySide qt.conf to override the built-in + # configuration variables, if there is no default qt.conf in + # executable folder + prefix = pysideDir.replace('\\', '/') + _utils.register_qt_conf(prefix=prefix, + binaries=prefix, + plugins=prefix+"/plugins", + imports=prefix+"/imports", + translations=prefix+"/translations") + + # On Windows add the PySide\openssl folder (if it exists) to the + # PATH so the SSL DLLs can be found when Qt tries to dynamically + # load them. Tell Qt to load them and then reset the PATH. + if sys.platform == 'win32': + opensslDir = os.path.join(pysideDir, 'openssl') + if os.path.exists(opensslDir): + path = os.environ['PATH'] + try: + os.environ['PATH'] = opensslDir + os.pathsep + path + try: + from . import QtNetwork + except ImportError: + pass + else: + QtNetwork.QSslSocket.supportsSsl() + finally: + os.environ['PATH'] = path + +_setupQtDirectories() diff --git a/resources/pyside/lib/site-packages/PySide2/_utils.py b/resources/pyside/lib/site-packages/PySide2/_utils.py new file mode 100644 index 0000000..0b4fc02 --- /dev/null +++ b/resources/pyside/lib/site-packages/PySide2/_utils.py @@ -0,0 +1,287 @@ +############################################################################# +## +## Copyright (C) 2016 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of PySide2. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +import os +import fnmatch + + +if sys.platform == 'win32': + # On Windows get the PySide package path in case sensitive format. + # Even if the file system on Windows is case insensitive, + # some parts in Qt environment such as qml imports path, + # requires to be in case sensitive format. + import ctypes + from ctypes import POINTER, WinError, sizeof, byref, create_unicode_buffer + from ctypes.wintypes import MAX_PATH, LPCWSTR, LPWSTR, DWORD + + GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW + GetShortPathNameW.argtypes = [LPCWSTR, LPWSTR, DWORD] + GetShortPathNameW.restype = DWORD + + GetLongPathNameW = ctypes.windll.kernel32.GetLongPathNameW + GetLongPathNameW.argtypes = [LPCWSTR, LPWSTR, DWORD] + GetLongPathNameW.restype = DWORD + + PY_2 = sys.version_info[0] < 3 + + if PY_2: + def u(x): + return unicode(x) + def u_fs(x): + return unicode(x, sys.getfilesystemencoding()) + else: + def u(x): + return x + def u_fs(x): + return x + + def _get_win32_short_name(s): + """ Returns short name """ + buf_size = MAX_PATH + for i in range(2): + buf = create_unicode_buffer(u('\0') * (buf_size + 1)) + r = GetShortPathNameW(u_fs(s), buf, buf_size) + if r == 0: + raise WinError() + if r < buf_size: + if PY_2: + return buf.value.encode(sys.getfilesystemencoding()) + return buf.value + buf_size = r + raise WinError() + + def _get_win32_long_name(s): + """ Returns long name """ + buf_size = MAX_PATH + for i in range(2): + buf = create_unicode_buffer(u('\0') * (buf_size + 1)) + r = GetLongPathNameW(u_fs(s), buf, buf_size) + if r == 0: + raise WinError() + if r < buf_size: + if PY_2: + return buf.value.encode(sys.getfilesystemencoding()) + return buf.value + buf_size = r + raise WinError() + + def _get_win32_case_sensitive_name(s): + """ Returns long name in case sensitive format """ + path = _get_win32_long_name(_get_win32_short_name(s)) + return path + + def get_pyside_dir(): + try: + from . import QtCore + except ImportError: + return _get_win32_case_sensitive_name(os.path.abspath(os.path.dirname(__file__))) + else: + return _get_win32_case_sensitive_name(os.path.abspath(os.path.dirname(QtCore.__file__))) + +else: + def get_pyside_dir(): + try: + from . import QtCore + except ImportError: + return os.path.abspath(os.path.dirname(__file__)) + else: + return os.path.abspath(os.path.dirname(QtCore.__file__)) + + +def _filter_match(name, patterns): + for pattern in patterns: + if pattern is None: + continue + if fnmatch.fnmatch(name, pattern): + return True + return False + + +def _dir_contains(dir, filter): + names = os.listdir(dir) + for name in names: + srcname = os.path.join(dir, name) + if not os.path.isdir(srcname) and _filter_match(name, filter): + return True + return False + + +def _rcc_write_number(out, number, width): + dividend = 1 + if width == 2: + dividend = 256 + elif width == 3: + dividend = 65536 + elif width == 4: + dividend = 16777216 + while dividend >= 1: + tmp = int(number / dividend) + out.append("%02x" % tmp) + number -= tmp * dividend + dividend = int(dividend / 256) + + +def _rcc_write_data(out, data): + _rcc_write_number(out, len(data), 4) + for d in data: + _rcc_write_number(out, ord(d), 1) + + +def _get_qt_conf_resource(prefix, binaries, plugins, imports, translations): + """ + Generate Qt resource with embedded qt.conf + """ + qt_conf_template = "\ +[Paths]\x0d\x0a\ +Prefix = %(prefix)s\x0d\x0a\ +Binaries = %(binaries)s\x0d\x0a\ +Imports = %(imports)s\x0d\x0a\ +Plugins = %(plugins)s\x0d\x0a\ +Translations = %(translations)s" + + rc_data_input = qt_conf_template % {"prefix": prefix, + "binaries": binaries, + "plugins": plugins, + "imports": imports, + "translations": translations} + rc_data_ouput = [] + _rcc_write_data(rc_data_ouput, rc_data_input) + + # The rc_struct and rc_name was pre-generated by pyside-rcc from file: + # + # + # qt/etc/qt.conf + # + # + PY_2 = sys.version_info[0] < 3 + if PY_2: + rc_struct = "\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x0a\x00\x02\x00\x00\ +\x00\x01\x00\x00\x00\x03\x00\x00\x00\x16\x00\x00\x00\x00\x00\x01\x00\x00\ +\x00\x00" + rc_name = "\ +\x00\x02\x00\x00\x07\x84\x00q\x00t\x00\x03\x00\x00l\xa3\x00e\x00t\x00c\x00\ +\x07\x08t\xa6\xa6\x00q\x00t\x00.\x00c\x00o\x00n\x00f" + rc_data = "".join(rc_data_ouput).decode('hex') + else: + rc_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x0a\x00\x02\x00\x00\ +\x00\x01\x00\x00\x00\x03\x00\x00\x00\x16\x00\x00\x00\x00\x00\x01\x00\x00\ +\x00\x00" + rc_name = b"\ +\x00\x02\x00\x00\x07\x84\x00q\x00t\x00\x03\x00\x00l\xa3\x00e\x00t\x00c\x00\ +\x07\x08t\xa6\xa6\x00q\x00t\x00.\x00c\x00o\x00n\x00f" + rc_data = bytes.fromhex("".join(rc_data_ouput)) + + return rc_struct, rc_name, rc_data + + +def register_qt_conf(prefix, binaries, plugins, imports, translations, + force=False): + """ + Register qt.conf in Qt resource system to override the built-in + configuration variables, if there is no default qt.conf in + executable folder and another qt.conf is not already registered in + Qt resource system. + """ + try: + from . import QtCore + except ImportError: + return + + # Check folder structure + if not prefix or not os.path.exists(prefix): + if force: + raise RuntimeError("Invalid prefix path specified: %s" % prefix) + else: + return + if not binaries or not os.path.exists(binaries): + if force: + raise RuntimeError("Invalid binaries path specified: %s" % binaries) + else: + return + else: + # Check if required Qt libs exists in binaries folder + if sys.platform == 'win32': + pattern = ["QtCore*.dll"] + else: + pattern = ["libQtCore.so.*"] + if not _dir_contains(binaries, pattern): + if force: + raise RuntimeError("QtCore lib not found in folder: %s" % \ + binaries) + else: + return + if not plugins or not os.path.exists(plugins): + if force: + raise RuntimeError("Invalid plugins path specified: %s" % plugins) + else: + return + if not imports or not os.path.exists(imports): + if force: + raise RuntimeError("Invalid imports path specified: %s" % imports) + else: + return + if not translations or not os.path.exists(translations): + if force: + raise RuntimeError("Invalid translations path specified: %s" \ + % translations) + else: + return + + # Check if there is no default qt.conf in executable folder + exec_prefix = os.path.dirname(sys.executable) + qtconf_path = os.path.join(exec_prefix, 'qt.conf') + if os.path.exists(qtconf_path) and not force: + return + + # Check if another qt.conf is not already registered in Qt resource system + if QtCore.QFile.exists(":/qt/etc/qt.conf") and not force: + return + + rc_struct, rc_name, rc_data = _get_qt_conf_resource(prefix, binaries, + plugins, imports, + translations) + QtCore.qRegisterResourceData(0x01, rc_struct, rc_name, rc_data) + + # Initialize the Qt library by querying the QLibraryInfo + prefixPath = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.PrefixPath) diff --git a/resources/pyside/lib/site-packages/pyside2uic/Compiler/__init__.py b/resources/pyside/lib/site-packages/pyside2uic/Compiler/__init__.py new file mode 100644 index 0000000..6aa6c34 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/Compiler/__init__.py @@ -0,0 +1,22 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2009 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + diff --git a/resources/pyside/lib/site-packages/pyside2uic/Compiler/compiler.py b/resources/pyside/lib/site-packages/pyside2uic/Compiler/compiler.py new file mode 100644 index 0000000..2013170 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/Compiler/compiler.py @@ -0,0 +1,103 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +import sys + +from pyside2uic.properties import Properties +from pyside2uic.uiparser import UIParser +from pyside2uic.Compiler import qtproxies +from pyside2uic.Compiler.indenter import createCodeIndenter, getIndenter, \ + write_code +from pyside2uic.Compiler.qobjectcreator import CompilerCreatorPolicy +from pyside2uic.Compiler.misc import write_import + + +class UICompiler(UIParser): + def __init__(self): + UIParser.__init__(self, qtproxies.QtCore, qtproxies.QtGui, qtproxies.QtWidgets, + CompilerCreatorPolicy()) + + def reset(self): + qtproxies.i18n_strings = [] + UIParser.reset(self) + + def setContext(self, context): + qtproxies.i18n_context = context + + def createToplevelWidget(self, classname, widgetname): + indenter = getIndenter() + indenter.level = 0 + + indenter.write("from PySide2 import QtCore, QtGui, QtWidgets") + indenter.write("") + + indenter.write("class Ui_%s(object):" % self.uiname) + indenter.indent() + indenter.write("def setupUi(self, %s):" % widgetname) + indenter.indent() + w = self.factory.createQObject(classname, widgetname, (), + is_attribute = False, + no_instantiation = True) + w.baseclass = classname + w.uiclass = "Ui_%s" % self.uiname + return w + + def setDelayedProps(self): + write_code("") + write_code("self.retranslateUi(%s)" % self.toplevelWidget) + UIParser.setDelayedProps(self) + + def finalize(self): + indenter = getIndenter() + indenter.level = 1 + indenter.write("") + indenter.write("def retranslateUi(self, %s):" % self.toplevelWidget) + indenter.indent() + + if qtproxies.i18n_strings: + for s in qtproxies.i18n_strings: + indenter.write(s) + else: + indenter.write("pass") + + indenter.dedent() + indenter.dedent() + + # Make a copy of the resource modules to import because the parser will + # reset() before returning. + self._resources = self.resources + + def compileUi(self, input_stream, output_stream, from_imports): + createCodeIndenter(output_stream) + w = self.parse(input_stream) + + indenter = getIndenter() + indenter.write("") + + self.factory._cpolicy._writeOutImports() + + for res in self._resources: + write_import(res, from_imports) + + return {"widgetname": str(w), + "uiclass" : w.uiclass, + "baseclass" : w.baseclass} diff --git a/resources/pyside/lib/site-packages/pyside2uic/Compiler/indenter.py b/resources/pyside/lib/site-packages/pyside2uic/Compiler/indenter.py new file mode 100644 index 0000000..c8700ae --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/Compiler/indenter.py @@ -0,0 +1,59 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2009 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +indentwidth = 4 + +_indenter = None + +class _IndentedCodeWriter(object): + def __init__(self, output): + self.level = 0 + self.output = output + + def indent(self): + self.level += 1 + + def dedent(self): + self.level -= 1 + + def write(self, line): + if line.strip(): + if indentwidth > 0: + indent = " " * indentwidth + line = line.replace("\t", indent) + else: + indent = "\t" + + self.output.write("%s%s\n" % (indent * self.level, line)) + else: + self.output.write("\n") + + +def createCodeIndenter(output): + global _indenter + _indenter = _IndentedCodeWriter(output) + +def getIndenter(): + return _indenter + +def write_code(string): + _indenter.write(string) diff --git a/resources/pyside/lib/site-packages/pyside2uic/Compiler/misc.py b/resources/pyside/lib/site-packages/pyside2uic/Compiler/misc.py new file mode 100644 index 0000000..bd95663 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/Compiler/misc.py @@ -0,0 +1,52 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + + +from pyside2uic.Compiler.indenter import write_code + + +def write_import(module_name, from_imports): + if from_imports: + write_code("from . import %s" % module_name) + else: + write_code("import %s" % module_name) + + +def moduleMember(module, name): + if module: + return "%s.%s" % (module, name) + + return name + + +class Literal(object): + """Literal(string) -> new literal + + string will not be quoted when put into an argument list""" + def __init__(self, string): + self.string = string + + def __str__(self): + return self.string + + def __or__(self, r_op): + return Literal("%s|%s" % (self, r_op)) diff --git a/resources/pyside/lib/site-packages/pyside2uic/Compiler/proxy_type.py b/resources/pyside/lib/site-packages/pyside2uic/Compiler/proxy_type.py new file mode 100644 index 0000000..01a26fb --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/Compiler/proxy_type.py @@ -0,0 +1,59 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +from pyside2uic.Compiler.misc import Literal, moduleMember + + +class ProxyType(type): + def __init__(*args): + type.__init__(*args) + for cls in args[0].__dict__.values(): + if type(cls) is ProxyType: + cls.module = args[0].__name__ + + if not hasattr(args[0], "module"): + args[0].module = "" + + def __getattribute__(cls, name): + try: + return type.__getattribute__(cls, name) + except AttributeError: + # Handle internal (ie. non-PySide) attributes as normal. + if name == "module": + raise + + # Avoid a circular import. + from pyside2uic.Compiler.qtproxies import LiteralProxyClass + + return type(name, (LiteralProxyClass, ), + {"module": moduleMember(type.__getattribute__(cls, "module"), + type.__getattribute__(cls, "__name__"))}) + + def __str__(cls): + return moduleMember(type.__getattribute__(cls, "module"), + type.__getattribute__(cls, "__name__")) + + def __or__(self, r_op): + return Literal("%s|%s" % (self, r_op)) + + def __eq__(self, other): + return str(self) == str(other) diff --git a/resources/pyside/lib/site-packages/pyside2uic/Compiler/qobjectcreator.py b/resources/pyside/lib/site-packages/pyside2uic/Compiler/qobjectcreator.py new file mode 100644 index 0000000..296ac36 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/Compiler/qobjectcreator.py @@ -0,0 +1,165 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + + +import logging + +try: + set() +except NameError: + from sets import Set as set + +from pyside2uic.Compiler.indenter import write_code +from pyside2uic.Compiler.qtproxies import (QtWidgets, QtGui, Literal, + strict_getattr) + + +logger = logging.getLogger(__name__) +DEBUG = logger.debug + + +class _QtGuiWrapper(object): + def search(clsname): + try: + return strict_getattr(QtGui, clsname) + except AttributeError: + return None + + search = staticmethod(search) + + +class _QtWidgetsWrapper(object): + def search(clsname): + try: + return strict_getattr(QtWidgets, clsname) + except AttributeError: + return None + + search = staticmethod(search) + + +class _ModuleWrapper(object): + def __init__(self, name, classes): + if "." in name: + idx = name.rfind(".") + self._package = name[:idx] + self._module = name[idx + 1:] + else: + self._package = None + self._module = name + + self._classes = set(classes) + self._used = False + + def search(self, cls): + if cls in self._classes: + self._used = True + return type(cls, (QtWidgets.QWidget,), {"module": self._module}) + else: + return None + + def _writeImportCode(self): + if self._used: + if self._package is None: + write_code("import %s" % self._module) + else: + write_code("from %s import %s" % (self._package, self._module)) + + +class _CustomWidgetLoader(object): + def __init__(self): + self._widgets = {} + self._usedWidgets = set() + + def addCustomWidget(self, widgetClass, baseClass, module): + assert widgetClass not in self._widgets + self._widgets[widgetClass] = (baseClass, module) + + + def _resolveBaseclass(self, baseClass): + try: + for x in range(0, 10): + try: return strict_getattr(QtWidgets, baseClass) + except AttributeError: pass + + baseClass = self._widgets[baseClass][0] + else: + raise ValueError("baseclass resolve took too long, check custom widgets") + + except KeyError: + raise ValueError("unknown baseclass %s" % baseClass) + + + def search(self, cls): + try: + self._usedWidgets.add(cls) + baseClass = self._resolveBaseclass(self._widgets[cls][0]) + DEBUG("resolved baseclass of %s: %s" % (cls, baseClass)) + + return type(cls, (baseClass,), + {"module" : ""}) + + except KeyError: + return None + + def _writeImportCode(self): + imports = {} + for widget in self._usedWidgets: + _, module = self._widgets[widget] + imports.setdefault(module, []).append(widget) + + for module, classes in imports.items(): + write_code("from %s import %s" % (module, ", ".join(classes))) + + +class CompilerCreatorPolicy(object): + def __init__(self): + self._modules = [] + + def createQtGuiWrapper(self): + return _QtGuiWrapper + + def createQtWidgetsWrapper(self): + return _QtWidgetsWrapper + + def createModuleWrapper(self, name, classes): + mw = _ModuleWrapper(name, classes) + self._modules.append(mw) + return mw + + def createCustomWidgetLoader(self): + cw = _CustomWidgetLoader() + self._modules.append(cw) + return cw + + def instantiate(self, clsObject, objectname, ctor_args, is_attribute=True, no_instantiation=False): + return clsObject(objectname, is_attribute, ctor_args, no_instantiation) + + def invoke(self, rname, method, args): + return method(rname, *args) + + def getSlot(self, object, slotname): + return Literal("%s.%s" % (object, slotname)) + + def _writeOutImports(self): + for module in self._modules: + module._writeImportCode() diff --git a/resources/pyside/lib/site-packages/pyside2uic/Compiler/qtproxies.py b/resources/pyside/lib/site-packages/pyside2uic/Compiler/qtproxies.py new file mode 100644 index 0000000..506c5df --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/Compiler/qtproxies.py @@ -0,0 +1,408 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + + +import sys +import re + +from pyside2uic.Compiler.indenter import write_code +from pyside2uic.Compiler.misc import Literal, moduleMember + +if sys.hexversion >= 0x03000000: + from pyside2uic.port_v3.proxy_base import ProxyBase + from pyside2uic.port_v3.as_string import as_string +else: + from pyside2uic.port_v2.proxy_base import ProxyBase + from pyside2uic.port_v2.as_string import as_string + +i18n_strings = [] +i18n_context = "" + +def i18n_print(string): + i18n_strings.append(string) + +def i18n_void_func(name): + def _printer(self, *args): + i18n_print("%s.%s(%s)" % (self, name, ", ".join(map(as_string, args)))) + return _printer + +def i18n_func(name): + def _printer(self, rname, *args): + i18n_print("%s = %s.%s(%s)" % (rname, self, name, ", ".join(map(as_string, args)))) + return Literal(rname) + + return _printer + +def strict_getattr(module, clsname): + cls = getattr(module, clsname) + if issubclass(cls, LiteralProxyClass): + raise AttributeError(cls) + else: + return cls + + +class i18n_string(object): + def __init__(self, string, disambig): + self.string = string + self.disambig = disambig + + def __str__(self): + if self.disambig is None: + disambig = "None" + else: + disambig = as_string(self.disambig, encode=False) + + return 'QtWidgets.QApplication.translate("%s", %s, %s, -1)' % (i18n_context, as_string(self.string, encode=False), disambig) + + +# Classes with this flag will be handled as literal values. If functions are +# called on these classes, the literal value changes. +# Example: +# the code +# >>> QSize(9,10).expandedTo(...) +# will print just that code. +AS_ARGUMENT = 2 + +# ATTENTION: currently, classes can either be literal or normal. If a class +# should need both kinds of behaviour, the code has to be changed. + +class ProxyClassMember(object): + def __init__(self, proxy, function_name, flags): + self.proxy = proxy + self.function_name = function_name + self.flags = flags + + def __str__(self): + return "%s.%s" % (self.proxy, self.function_name) + + def __call__(self, *args): + func_call = "%s.%s(%s)" % (self.proxy, + self.function_name, + ", ".join(map(as_string, args))) + if self.flags & AS_ARGUMENT: + self.proxy._uic_name = func_call + return self.proxy + else: + needs_translation = False + for arg in args: + if isinstance(arg, i18n_string): + needs_translation = True + if needs_translation: + i18n_print(func_call) + else: + write_code(func_call) + + +class ProxyClass(ProxyBase): + flags = 0 + + def __init__(self, objectname, is_attribute, args=(), noInstantiation=False): + if objectname: + if is_attribute: + objectname = "self." + objectname + + self._uic_name = objectname + else: + self._uic_name = "Unnamed" + + if not noInstantiation: + funcall = "%s(%s)" % \ + (moduleMember(self.module, self.__class__.__name__), + ", ".join(map(str, args))) + + if objectname: + funcall = "%s = %s" % (objectname, funcall) + + write_code(funcall) + + def __str__(self): + return self._uic_name + + def __getattribute__(self, attribute): + try: + return object.__getattribute__(self, attribute) + except AttributeError: + return ProxyClassMember(self, attribute, self.flags) + + +class LiteralProxyClass(ProxyClass): + """LiteralObject(*args) -> new literal class + + a literal class can be used as argument in a function call + + >>> class Foo(LiteralProxyClass): pass + >>> str(Foo(1,2,3)) == "Foo(1,2,3)" + """ + flags = AS_ARGUMENT + def __init__(self, *args): + self._uic_name = "%s(%s)" % \ + (moduleMember(self.module, self.__class__.__name__), + ", ".join(map(as_string, args))) + + +class ProxyNamespace(ProxyBase): + pass + + +# These are all the Qt classes used by pyuic4 in their namespaces. If a class +# is missing, the compiler will fail, normally with an AttributeError. +# +# For adding new classes: +# - utility classes used as literal values do not need to be listed +# because they are created on the fly as subclasses of LiteralProxyClass +# - classes which are *not* QWidgets inherit from ProxyClass and they +# have to be listed explicitly in the correct namespace. These classes +# are created via a ProxyQObjectCreator +# - new QWidget-derived classes have to inherit from qtproxies.QWidget +# If the widget does not need any special methods, it can be listed +# in _qwidgets + +class QtCore(ProxyNamespace): + class Qt(ProxyNamespace): + pass + + ## connectSlotsByName and connect have to be handled as class methods, + ## otherwise they would be created as LiteralProxyClasses and never be + ## printed + class QMetaObject(ProxyClass): + def connectSlotsByName(cls, *args): + ProxyClassMember(cls, "connectSlotsByName", 0)(*args) + connectSlotsByName = classmethod(connectSlotsByName) + + + class QObject(ProxyClass): + def metaObject(self): + class _FakeMetaObject(object): + def className(*args): + return self.__class__.__name__ + return _FakeMetaObject() + + def objectName(self): + return self._uic_name.split(".")[-1] + + def connect(cls, *args): + # Handle slots that have names corresponding to Python keywords. + slot_name = str(args[-1]) + if slot_name.endswith('.raise'): + args = list(args[:-1]) + args.append(Literal(slot_name + '_')) + + ProxyClassMember(cls, "connect", 0)(*args) + connect = classmethod(connect) + +class QtGui(ProxyNamespace): + class QIcon(ProxyClass): pass + class QConicalGradient(ProxyClass): pass + class QLinearGradient(ProxyClass): pass + class QRadialGradient(ProxyClass): pass + class QBrush(ProxyClass): pass + class QPainter(ProxyClass): pass + class QPalette(ProxyClass): pass + class QFont(ProxyClass): pass + +# These sub-class QWidget but aren't themselves sub-classed. +_qwidgets = ("QCalendarWidget", "QDialogButtonBox", "QDockWidget", "QGroupBox", + "QLineEdit", "QMainWindow", "QMenuBar", "QProgressBar", "QStatusBar", + "QToolBar", "QWizardPage") + +class QtWidgets(ProxyNamespace): + class QApplication(QtCore.QObject): + def translate(uiname, text, disambig, encoding): + return i18n_string(text or "", disambig) + translate = staticmethod(translate) + + class QSpacerItem(ProxyClass): pass + class QSizePolicy(ProxyClass): pass + ## QActions inherit from QObject for the metaobject stuff + ## and the hierarchy has to be correct since we have a + ## isinstance(x, QtWidgets.QLayout) call in the ui parser + class QAction(QtCore.QObject): pass + class QActionGroup(QtCore.QObject): pass + class QButtonGroup(QtCore.QObject): pass + class QLayout(QtCore.QObject): + def setMargin(self, v): + ProxyClassMember(self, "setContentsMargins", 0)(v, v, v, v); + + class QGridLayout(QLayout): pass + class QBoxLayout(QLayout): pass + class QHBoxLayout(QBoxLayout): pass + class QVBoxLayout(QBoxLayout): pass + class QFormLayout(QLayout): pass + + class QWidget(QtCore.QObject): + def font(self): + return Literal("%s.font()" % self) + + def minimumSizeHint(self): + return Literal("%s.minimumSizeHint()" % self) + + def sizePolicy(self): + sp = LiteralProxyClass() + sp._uic_name = "%s.sizePolicy()" % self + return sp + + class QDialog(QWidget): pass + class QWizard(QDialog): pass + + class QAbstractSlider(QWidget): pass + class QDial(QAbstractSlider): pass + class QScrollBar(QAbstractSlider): pass + class QSlider(QAbstractSlider): pass + + class QMenu(QWidget): + def menuAction(self): + return Literal("%s.menuAction()" % self) + + class QTabWidget(QWidget): + def addTab(self, *args): + text = args[-1] + + if isinstance(text, i18n_string): + i18n_print("%s.setTabText(%s.indexOf(%s), %s)" % \ + (self._uic_name, self._uic_name, args[0], text)) + args = args[:-1] + ("", ) + + ProxyClassMember(self, "addTab", 0)(*args) + + def indexOf(self, page): + return Literal("%s.indexOf(%s)" % (self, page)) + + class QComboBox(QWidget): pass + class QFontComboBox(QComboBox): pass + + class QAbstractSpinBox(QWidget): pass + class QDoubleSpinBox(QAbstractSpinBox): pass + class QSpinBox(QAbstractSpinBox): pass + + class QDateTimeEdit(QAbstractSpinBox): pass + class QDateEdit(QDateTimeEdit): pass + class QTimeEdit(QDateTimeEdit): pass + + class QFrame(QWidget): pass + class QLabel(QFrame): pass + class QLCDNumber(QFrame): pass + class QSplitter(QFrame): pass + class QStackedWidget(QFrame): pass + + class QToolBox(QFrame): + def addItem(self, *args): + text = args[-1] + + if isinstance(text, i18n_string): + i18n_print("%s.setItemText(%s.indexOf(%s), %s)" % \ + (self._uic_name, self._uic_name, args[0], text)) + args = args[:-1] + ("", ) + + ProxyClassMember(self, "addItem", 0)(*args) + + def indexOf(self, page): + return Literal("%s.indexOf(%s)" % (self, page)) + + def layout(self): + return QtWidgets.QLayout("%s.layout()" % self, + False, (), noInstantiation=True) + + class QAbstractScrollArea(QFrame): pass + class QGraphicsView(QAbstractScrollArea): pass + class QMdiArea(QAbstractScrollArea): pass + class QPlainTextEdit(QAbstractScrollArea): pass + class QScrollArea(QAbstractScrollArea): pass + + class QTextEdit(QAbstractScrollArea): pass + class QTextBrowser(QTextEdit): pass + + class QAbstractItemView(QAbstractScrollArea): pass + class QColumnView(QAbstractItemView): pass + class QHeaderView(QAbstractItemView): pass + class QListView(QAbstractItemView): pass + + class QTableView(QAbstractItemView): + def horizontalHeader(self): + return QtWidgets.QHeaderView("%s.horizontalHeader()" % self, + False, (), noInstantiation=True) + + def verticalHeader(self): + return QtWidgets.QHeaderView("%s.verticalHeader()" % self, + False, (), noInstantiation=True) + + class QTreeView(QAbstractItemView): + def header(self): + return QtWidgets.QHeaderView("%s.header()" % self, + False, (), noInstantiation=True) + + class QListWidgetItem(ProxyClass): pass + + class QListWidget(QListView): + isSortingEnabled = i18n_func("isSortingEnabled") + setSortingEnabled = i18n_void_func("setSortingEnabled") + + def item(self, row): + return QtWidgets.QListWidgetItem("%s.item(%i)" % (self, row), False, + (), noInstantiation=True) + + class QTableWidgetItem(ProxyClass): pass + + class QTableWidget(QTableView): + isSortingEnabled = i18n_func("isSortingEnabled") + setSortingEnabled = i18n_void_func("setSortingEnabled") + + def item(self, row, col): + return QtWidgets.QTableWidgetItem("%s.item(%i, %i)" % (self, row, col), + False, (), noInstantiation=True) + + def horizontalHeaderItem(self, col): + return QtWidgets.QTableWidgetItem("%s.horizontalHeaderItem(%i)" % (self, col), + False, (), noInstantiation=True) + + def verticalHeaderItem(self, row): + return QtWidgets.QTableWidgetItem("%s.verticalHeaderItem(%i)" % (self, row), + False, (), noInstantiation=True) + + class QTreeWidgetItem(ProxyClass): + def child(self, index): + return QtWidgets.QTreeWidgetItem("%s.child(%i)" % (self, index), + False, (), noInstantiation=True) + + class QTreeWidget(QTreeView): + isSortingEnabled = i18n_func("isSortingEnabled") + setSortingEnabled = i18n_void_func("setSortingEnabled") + + def headerItem(self): + return QtWidgets.QWidget("%s.headerItem()" % self, False, (), + noInstantiation=True) + + def topLevelItem(self, index): + return QtWidgets.QTreeWidgetItem("%s.topLevelItem(%i)" % (self, index), + False, (), noInstantiation=True) + + class QAbstractButton(QWidget): pass + class QCheckBox(QAbstractButton): pass + class QRadioButton(QAbstractButton): pass + class QToolButton(QAbstractButton): pass + + class QPushButton(QAbstractButton): pass + class QCommandLinkButton(QPushButton): pass + + # Add all remaining classes. + for _class in _qwidgets: + if _class not in locals(): + locals()[_class] = type(_class, (QWidget, ), {}) diff --git a/resources/pyside/lib/site-packages/pyside2uic/__init__.py b/resources/pyside/lib/site-packages/pyside2uic/__init__.py new file mode 100644 index 0000000..15c9eca --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/__init__.py @@ -0,0 +1,149 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +__all__ = ("compileUi", "compileUiDir", "widgetPluginPath") + +__version__ = "" + +from pyside2uic.Compiler import indenter, compiler + +_header = """# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '%s' +# +# Created: %s +# by: pyside2-uic %s running on PySide2 %s +# +# WARNING! All changes made in this file will be lost! + +""" + +_display_code = """ +if __name__ == "__main__": +\timport sys +\tapp = QtWidgets.QApplication(sys.argv) +\t%(widgetname)s = QtWidgets.%(baseclass)s() +\tui = %(uiclass)s() +\tui.setupUi(%(widgetname)s) +\t%(widgetname)s.show() +\tsys.exit(app.exec_()) +""" + + +def compileUiDir(dir, recurse=False, map=None, **compileUi_args): + """compileUiDir(dir, recurse=False, map=None, **compileUi_args) + + Creates Python modules from Qt Designer .ui files in a directory or + directory tree. + + dir is the name of the directory to scan for files whose name ends with + '.ui'. By default the generated Python module is created in the same + directory ending with '.py'. + recurse is set if any sub-directories should be scanned. The default is + False. + map is an optional callable that is passed the name of the directory + containing the '.ui' file and the name of the Python module that will be + created. The callable should return a tuple of the name of the directory + in which the Python module will be created and the (possibly modified) + name of the module. The default is None. + compileUi_args are any additional keyword arguments that are passed to + the compileUi() function that is called to create each Python module. + """ + + import os + + # Compile a single .ui file. + def compile_ui(ui_dir, ui_file): + # Ignore if it doesn't seem to be a .ui file. + if ui_file.endswith('.ui'): + py_dir = ui_dir + py_file = ui_file[:-3] + '.py' + + # Allow the caller to change the name of the .py file or generate + # it in a different directory. + if map is not None: + py_dir, py_file = map(py_dir, py_file) + + # Make sure the destination directory exists. + try: + os.makedirs(py_dir) + except: + pass + + ui_path = os.path.join(ui_dir, ui_file) + py_path = os.path.join(py_dir, py_file) + + ui_file = open(ui_path, 'r') + py_file = open(py_path, 'w') + + try: + compileUi(ui_file, py_file, **compileUi_args) + finally: + ui_file.close() + py_file.close() + + if recurse: + for root, _, files in os.walk(dir): + for ui in files: + compile_ui(root, ui) + else: + for ui in os.listdir(dir): + if os.path.isfile(os.path.join(dir, ui)): + compile_ui(dir, ui) + + +def compileUi(uifile, pyfile, execute=False, indent=4, from_imports=False): + """compileUi(uifile, pyfile, execute=False, indent=4, from_imports=False) + + Creates a Python module from a Qt Designer .ui file. + + uifile is a file name or file-like object containing the .ui file. + pyfile is the file-like object to which the Python code will be written to. + execute is optionally set to generate extra Python code that allows the + code to be run as a standalone application. The default is False. + indent is the optional indentation width using spaces. If it is 0 then a + tab is used. The default is 4. + from_imports is optionally set to generate import statements that are + relative to '.'. + """ + + from time import ctime + import PySide2 + + try: + uifname = uifile.name + except AttributeError: + uifname = uifile + + indenter.indentwidth = indent + + global PySideToolsVersion + pyfile.write(_header % (uifname, ctime(), __version__, PySide2.__version__)) + + winfo = compiler.UICompiler().compileUi(uifile, pyfile, from_imports) + + if execute: + indenter.write_code(_display_code % winfo) + + +# The list of directories that are searched for widget plugins. +from pyside2uic.objcreator import widgetPluginPath diff --git a/resources/pyside/lib/site-packages/pyside2uic/driver.py b/resources/pyside/lib/site-packages/pyside2uic/driver.py new file mode 100644 index 0000000..7b37509 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/driver.py @@ -0,0 +1,128 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + + +import sys +import logging + +from pyside2uic import compileUi + + +class Driver(object): + """ This encapsulates access to the pyuic functionality so that it can be + called by code that is Python v2/v3 specific. + """ + + LOGGER_NAME = 'PySide2.uic' + + def __init__(self, opts, ui_file): + """ Initialise the object. opts is the parsed options. ui_file is the + name of the .ui file. + """ + + if opts.debug: + logger = logging.getLogger(self.LOGGER_NAME) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(name)s: %(message)s")) + logger.addHandler(handler) + logger.setLevel(logging.DEBUG) + + self._opts = opts + self._ui_file = ui_file + + def invoke(self): + """ Invoke the action as specified by the parsed options. Returns 0 if + there was no error. + """ + + if self._opts.preview: + return self._preview() + + self._generate() + + return 0 + + def _preview(self): + """ Preview the .ui file. Return the exit status to be passed back to + the parent process. + """ + + from PySide2 import QtUiTools + from PySide2 import QtGui + from PySide2 import QtWidgets + + app = QtWidgets.QApplication([self._ui_file]) + widget = QtUiTools.QUiLoader().load(self._ui_file) + widget.show() + + return app.exec_() + + def _generate(self): + """ Generate the Python code. """ + + if sys.hexversion >= 0x03000000: + if self._opts.output == '-': + from io import TextIOWrapper + + pyfile = TextIOWrapper(sys.stdout.buffer, encoding='utf8') + else: + pyfile = open(self._opts.output, 'wt', encoding='utf8') + else: + if self._opts.output == '-': + pyfile = sys.stdout + else: + pyfile = open(self._opts.output, 'wt') + + compileUi(self._ui_file, pyfile, self._opts.execute, self._opts.indent, self._opts.from_imports) + + def on_IOError(self, e): + """ Handle an IOError exception. """ + + sys.stderr.write("Error: %s: \"%s\"\n" % (e.strerror, e.filename)) + + def on_SyntaxError(self, e): + """ Handle a SyntaxError exception. """ + + sys.stderr.write("Error in input file: %s\n" % e) + + def on_NoSuchWidgetError(self, e): + """ Handle a NoSuchWidgetError exception. """ + + if e.args[0].startswith("Q3"): + sys.stderr.write("Error: Q3Support widgets are not supported by PySide2.\n") + else: + sys.stderr.write(str(e) + "\n") + + def on_Exception(self, e): + """ Handle a generic exception. """ + + if logging.getLogger(self.LOGGER_NAME).level == logging.DEBUG: + import traceback + + traceback.print_exception(*sys.exc_info()) + else: + from PySide2 import QtCore + + sys.stderr.write("""An unexpected error occurred. +Check that you are using the latest version of PySide2 and report the error to +http://bugs.openbossa.org, including the ui file used to trigger the error. +""") diff --git a/resources/pyside/lib/site-packages/pyside2uic/exceptions.py b/resources/pyside/lib/site-packages/pyside2uic/exceptions.py new file mode 100644 index 0000000..18bb51a --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/exceptions.py @@ -0,0 +1,31 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2009 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +class NoSuchWidgetError(Exception): + def __str__(self): + return "Unknown Qt widget: %s" % (self.args[0],) + +class UnsupportedPropertyError(Exception): + pass + +class WidgetPluginError(Exception): + pass diff --git a/resources/pyside/lib/site-packages/pyside2uic/icon_cache.py b/resources/pyside/lib/site-packages/pyside2uic/icon_cache.py new file mode 100644 index 0000000..523d207 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/icon_cache.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# This file is part of the PySide project. +# +# Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + + +import os.path + + +class IconCache(object): + """Maintain a cache of icons. If an icon is used more than once by a GUI + then ensure that only one copy is created. + """ + + def __init__(self, object_factory, qtgui_module): + """Initialise the cache.""" + + self._object_factory = object_factory + self._qtgui_module = qtgui_module + self._base_dir = '' + self._cache = [] + + def set_base_dir(self, base_dir): + """ Set the base directory to be used for all relative filenames. """ + + self._base_dir = base_dir + + def get_icon(self, iconset): + """Return an icon described by the given iconset tag.""" + + iset = _IconSet(iconset, self._base_dir) + + try: + idx = self._cache.index(iset) + except ValueError: + idx = -1 + + if idx >= 0: + # Return the icon from the cache. + iset = self._cache[idx] + else: + # Follow uic's naming convention. + name = 'icon' + idx = len(self._cache) + + if idx > 0: + name += str(idx) + + icon = self._object_factory.createQObject("QIcon", name, (), + is_attribute=False) + iset.set_icon(icon, self._qtgui_module) + self._cache.append(iset) + + return iset.icon + + +class _IconSet(object): + """An icon set, ie. the mode and state and the pixmap used for each.""" + + def __init__(self, iconset, base_dir): + """Initialise the icon set from an XML tag.""" + + # Set the pre-Qt v4.4 fallback (ie. with no roles). + self._fallback = self._file_name(iconset.text, base_dir) + self._use_fallback = True + + # Parse the icon set. + self._roles = {} + + for i in iconset: + file_name = i.text + if file_name is not None: + file_name = self._file_name(file_name, base_dir) + + self._roles[i.tag] = file_name + self._use_fallback = False + + # There is no real icon yet. + self.icon = None + + @staticmethod + def _file_name(fname, base_dir): + """ Convert a relative filename if we have a base directory. """ + + fname = fname.replace("\\", "\\\\") + + if base_dir != '' and fname[0] != ':' and not os.path.isabs(fname): + fname = os.path.join(base_dir, fname) + + return fname + + def set_icon(self, icon, qtgui_module): + """Save the icon and set its attributes.""" + + if self._use_fallback: + icon.addFile(self._fallback) + else: + for role, pixmap in self._roles.items(): + if role.endswith("off"): + mode = role[:-3] + state = qtgui_module.QIcon.Off + elif role.endswith("on"): + mode = role[:-2] + state = qtgui_module.QIcon.On + else: + continue + + mode = getattr(qtgui_module.QIcon, mode.title()) + + if pixmap: + icon.addPixmap(qtgui_module.QPixmap(pixmap), mode, state) + else: + icon.addPixmap(qtgui_module.QPixmap(), mode, state) + + self.icon = icon + + def __eq__(self, other): + """Compare two icon sets for equality.""" + + if not isinstance(other, type(self)): + return NotImplemented + + if self._use_fallback: + if other._use_fallback: + return self._fallback == other._fallback + + return False + + if other._use_fallback: + return False + + return self._roles == other._roles diff --git a/resources/pyside/lib/site-packages/pyside2uic/objcreator.py b/resources/pyside/lib/site-packages/pyside2uic/objcreator.py new file mode 100644 index 0000000..1721c8f --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/objcreator.py @@ -0,0 +1,113 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +import sys +import os.path + +from pyside2uic.exceptions import NoSuchWidgetError, WidgetPluginError + +if sys.hexversion >= 0x03000000: + from pyside2uic.port_v3.load_plugin import load_plugin +else: + from pyside2uic.port_v2.load_plugin import load_plugin + + +# The list of directories that are searched for widget plugins. This is +# exposed as part of the API. +widgetPluginPath = [os.path.join(os.path.dirname(__file__), 'widget-plugins')] + + +MATCH = True +NO_MATCH = False +MODULE = 0 +CW_FILTER = 1 + + +class QObjectCreator(object): + def __init__(self, creatorPolicy): + self._cpolicy = creatorPolicy + + self._cwFilters = [] + self._modules = [self._cpolicy.createQtWidgetsWrapper(), + self._cpolicy.createQtGuiWrapper()] + + # Get the optional plugins. + for plugindir in widgetPluginPath: + try: + plugins = os.listdir(plugindir) + except: + plugins = [] + + for filename in plugins: + if not filename.endswith('.py') or filename == '__init__.py': + continue + + filename = os.path.join(plugindir, filename) + + plugin_globals = { + "MODULE": MODULE, + "CW_FILTER": CW_FILTER, + "MATCH": MATCH, + "NO_MATCH": NO_MATCH} + + plugin_locals = {} + + if load_plugin(open(filename), plugin_globals, plugin_locals): + pluginType = plugin_locals["pluginType"] + if pluginType == MODULE: + modinfo = plugin_locals["moduleInformation"]() + self._modules.append(self._cpolicy.createModuleWrapper(*modinfo)) + elif pluginType == CW_FILTER: + self._cwFilters.append(plugin_locals["getFilter"]()) + else: + raise WidgetPluginError("Unknown plugin type of %s" % filename) + + self._customWidgets = self._cpolicy.createCustomWidgetLoader() + self._modules.append(self._customWidgets) + + def createQObject(self, classname, *args, **kwargs): + classType = self.findQObjectType(classname) + if classType: + return self._cpolicy.instantiate(classType, *args, **kwargs) + raise NoSuchWidgetError(classname) + + def invoke(self, rname, method, args=()): + return self._cpolicy.invoke(rname, method, args) + + def findQObjectType(self, classname): + for module in self._modules: + w = module.search(classname) + if w is not None: + return w + return None + + def getSlot(self, obj, slotname): + return self._cpolicy.getSlot(obj, slotname) + + def addCustomWidget(self, widgetClass, baseClass, module): + for cwFilter in self._cwFilters: + match, result = cwFilter(widgetClass, baseClass, module) + if match: + widgetClass, baseClass, module = result + break + + self._customWidgets.addCustomWidget(widgetClass, baseClass, module) diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v2/__init__.py b/resources/pyside/lib/site-packages/pyside2uic/port_v2/__init__.py new file mode 100644 index 0000000..164c865 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v2/__init__.py @@ -0,0 +1,20 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v2/as_string.py b/resources/pyside/lib/site-packages/pyside2uic/port_v2/as_string.py new file mode 100644 index 0000000..23272b2 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v2/as_string.py @@ -0,0 +1,40 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +import re + + +def as_string(obj, encode=True): + if isinstance(obj, basestring): + s = '"' + _escape(obj.encode('UTF-8')) + '"' + return s + + return str(obj) + + +_esc_regex = re.compile(r"(\"|\'|\\)") + +def _escape(text): + # This escapes any escaped single or double quote or backslash. + x = _esc_regex.sub(r"\\\1", text) + + # This replaces any '\n' with an escaped version and a real line break. + return re.sub(r'\n', r'\\n"\n"', x) diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v2/ascii_upper.py b/resources/pyside/lib/site-packages/pyside2uic/port_v2/ascii_upper.py new file mode 100644 index 0000000..05843bf --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v2/ascii_upper.py @@ -0,0 +1,32 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +import string + + +# A translation table for converting ASCII lower case to upper case. +_ascii_trans_table = string.maketrans(string.ascii_lowercase, + string.ascii_uppercase) + + +# Convert a string to ASCII upper case irrespective of the current locale. +def ascii_upper(s): + return s.translate(_ascii_trans_table) diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v2/invoke.py b/resources/pyside/lib/site-packages/pyside2uic/port_v2/invoke.py new file mode 100644 index 0000000..0072c97 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v2/invoke.py @@ -0,0 +1,48 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +from pyside2uic.exceptions import NoSuchWidgetError + + +def invoke(driver): + """ Invoke the given command line driver. Return the exit status to be + passed back to the parent process. + """ + + exit_status = 1 + + try: + exit_status = driver.invoke() + + except IOError, e: + driver.on_IOError(e) + + except SyntaxError, e: + driver.on_SyntaxError(e) + + except NoSuchWidgetError, e: + driver.on_NoSuchWidgetError(e) + + except Exception, e: + driver.on_Exception(e) + + return exit_status diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v2/load_plugin.py b/resources/pyside/lib/site-packages/pyside2uic/port_v2/load_plugin.py new file mode 100644 index 0000000..3e82f9f --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v2/load_plugin.py @@ -0,0 +1,39 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +from pyside2uic.exceptions import WidgetPluginError + + +def load_plugin(plugin, plugin_globals, plugin_locals): + """ Load the given plugin (which is an open file). Return True if the + plugin was loaded, or False if it wanted to be ignored. Raise an exception + if there was an error. + """ + + try: + exec(plugin.read(), plugin_globals, plugin_locals) + except ImportError: + return False + except Exception, e: + raise WidgetPluginError("%s: %s" % (e.__class__, str(e))) + + return True diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v2/proxy_base.py b/resources/pyside/lib/site-packages/pyside2uic/port_v2/proxy_base.py new file mode 100644 index 0000000..b5cacb8 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v2/proxy_base.py @@ -0,0 +1,27 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +from pyside2uic.Compiler.proxy_type import ProxyType + + +class ProxyBase(object): + __metaclass__ = ProxyType diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v2/string_io.py b/resources/pyside/lib/site-packages/pyside2uic/port_v2/string_io.py new file mode 100644 index 0000000..4657b52 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v2/string_io.py @@ -0,0 +1,27 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + + +# Import the StringIO object. +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v3/__init__.py b/resources/pyside/lib/site-packages/pyside2uic/port_v3/__init__.py new file mode 100644 index 0000000..164c865 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v3/__init__.py @@ -0,0 +1,20 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v3/as_string.py b/resources/pyside/lib/site-packages/pyside2uic/port_v3/as_string.py new file mode 100644 index 0000000..51acccf --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v3/as_string.py @@ -0,0 +1,41 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +import re + + +def as_string(obj, encode=True): + if isinstance(obj, str): + s = '"' + _escape(obj) + '"' + + return s + + return str(obj) + + +_esc_regex = re.compile(r"(\"|\'|\\)") + +def _escape(text): + # This escapes any escaped single or double quote or backslash. + x = _esc_regex.sub(r"\\\1", text) + + # This replaces any '\n' with an escaped version and a real line break. + return re.sub(r'\n', r'\\n"\n"', x) diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v3/ascii_upper.py b/resources/pyside/lib/site-packages/pyside2uic/port_v3/ascii_upper.py new file mode 100644 index 0000000..00af8b8 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v3/ascii_upper.py @@ -0,0 +1,30 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + + +# A translation table for converting ASCII lower case to upper case. +_ascii_trans_table = bytes.maketrans(b'abcdefghijklmnopqrstuvwxyz', + b'ABCDEFGHIJKLMNOPQRSTUVWXYZ') + + +# Convert a string to ASCII upper case irrespective of the current locale. +def ascii_upper(s): + return s.translate(_ascii_trans_table) diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v3/invoke.py b/resources/pyside/lib/site-packages/pyside2uic/port_v3/invoke.py new file mode 100644 index 0000000..81edfb4 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v3/invoke.py @@ -0,0 +1,48 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +from pyside2uic.exceptions import NoSuchWidgetError + + +def invoke(driver): + """ Invoke the given command line driver. Return the exit status to be + passed back to the parent process. + """ + + exit_status = 1 + + try: + exit_status = driver.invoke() + + except IOError as e: + driver.on_IOError(e) + + except SyntaxError as e: + driver.on_SyntaxError(e) + + except NoSuchWidgetError as e: + driver.on_NoSuchWidgetError(e) + + except Exception as e: + driver.on_Exception(e) + + return exit_status diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v3/load_plugin.py b/resources/pyside/lib/site-packages/pyside2uic/port_v3/load_plugin.py new file mode 100644 index 0000000..3fc4020 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v3/load_plugin.py @@ -0,0 +1,39 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +from pyside2uic.exceptions import WidgetPluginError + + +def load_plugin(plugin, plugin_globals, plugin_locals): + """ Load the given plugin (which is an open file). Return True if the + plugin was loaded, or False if it wanted to be ignored. Raise an exception + if there was an error. + """ + + try: + exec(plugin.read(), plugin_globals, plugin_locals) + except ImportError: + return False + except Exception as e: + raise WidgetPluginError("%s: %s" % (e.__class__, str(e))) + + return True diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v3/proxy_base.py b/resources/pyside/lib/site-packages/pyside2uic/port_v3/proxy_base.py new file mode 100644 index 0000000..c24f6ab --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v3/proxy_base.py @@ -0,0 +1,27 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +from pyside2uic.Compiler.proxy_type import ProxyType + + +class ProxyBase(metaclass=ProxyType): + pass diff --git a/resources/pyside/lib/site-packages/pyside2uic/port_v3/string_io.py b/resources/pyside/lib/site-packages/pyside2uic/port_v3/string_io.py new file mode 100644 index 0000000..cce6b3a --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/port_v3/string_io.py @@ -0,0 +1,24 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + + +# Import the StringIO object. +from io import StringIO diff --git a/resources/pyside/lib/site-packages/pyside2uic/properties.py b/resources/pyside/lib/site-packages/pyside2uic/properties.py new file mode 100644 index 0000000..f4d3fc2 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/properties.py @@ -0,0 +1,489 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +import logging +import os.path +import sys + +from pyside2uic.exceptions import UnsupportedPropertyError +from pyside2uic.icon_cache import IconCache + +if sys.hexversion >= 0x03000000: + from pyside2uic.port_v3.ascii_upper import ascii_upper +else: + from pyside2uic.port_v2.ascii_upper import ascii_upper + +logger = logging.getLogger(__name__) +DEBUG = logger.debug + +QtCore = None +QtGui = None +QtWidgets = None + + +def int_list(prop): + return [int(child.text) for child in prop] + +def float_list(prop): + return [float(child.text) for child in prop] + +bool_ = lambda v: v == "true" + +def needsWidget(func): + func.needsWidget = True + return func + + +class Properties(object): + def __init__(self, factory, QtCore_mod, QtGui_mod, QtWidgets_mod): + global QtGui, QtCore, QtWidgets + QtWidgets = QtWidgets_mod + QtGui = QtGui_mod + QtCore = QtCore_mod + self.factory = factory + + self._base_dir = '' + + self.reset() + + def set_base_dir(self, base_dir): + """ Set the base directory to be used for all relative filenames. """ + + self._base_dir = base_dir + self.icon_cache.set_base_dir(base_dir) + + def reset(self): + self.buddies = [] + self.delayed_props = [] + self.icon_cache = IconCache(self.factory, QtGui) + + def _pyEnumMember(self, cpp_name): + try: + prefix, membername = cpp_name.split("::") + except ValueError: + prefix = "Qt" + membername = cpp_name + + if prefix == "Qt": + return getattr(QtCore.Qt, membername) + + scope = self.factory.findQObjectType(prefix) + if scope is None: + raise AttributeError("unknown enum %s" % cpp_name) + + return getattr(scope, membername) + + def _set(self, prop): + expr = [self._pyEnumMember(v) for v in prop.text.split('|')] + + value = expr[0] + for v in expr[1:]: + value |= v + + return value + + def _enum(self, prop): + return self._pyEnumMember(prop.text) + + def _number(self, prop): + return int(prop.text) + + _uInt = _longLong = _uLongLong = _number + + def _double(self, prop): + return float(prop.text) + + def _bool(self, prop): + return prop.text == 'true' + + def _stringlist(self, prop): + return [self._string(p, notr='true') for p in prop] + + def _string(self, prop, notr=None): + text = prop.text + + if text is None: + return "" + + if prop.get('notr', notr) == 'true': + return text + + return QtWidgets.QApplication.translate(self.uiname, text, + prop.get('comment'), -1) + + _char = _string + + def _cstring(self, prop): + return str(prop.text) + + def _color(self, prop): + args = int_list(prop) + + # Handle the optional alpha component. + alpha = int(prop.get("alpha", "255")) + + if alpha != 255: + args.append(alpha) + + return QtGui.QColor(*args) + + def _point(self, prop): + return QtCore.QPoint(*int_list(prop)) + + def _pointf(self, prop): + return QtCore.QPointF(*float_list(prop)) + + def _rect(self, prop): + return QtCore.QRect(*int_list(prop)) + + def _rectf(self, prop): + return QtCore.QRectF(*float_list(prop)) + + def _size(self, prop): + return QtCore.QSize(*int_list(prop)) + + def _sizef(self, prop): + return QtCore.QSizeF(*float_list(prop)) + + def _pixmap(self, prop): + if prop.text: + fname = prop.text.replace("\\", "\\\\") + if self._base_dir != '' and fname[0] != ':' and not os.path.isabs(fname): + fname = os.path.join(self._base_dir, fname) + + return QtGui.QPixmap(fname) + + # Don't bother to set the property if the pixmap is empty. + return None + + def _iconset(self, prop): + return self.icon_cache.get_icon(prop) + + def _url(self, prop): + return QtCore.QUrl(prop[0].text) + + def _locale(self, prop): + lang = getattr(QtCore.QLocale, prop.attrib['language']) + country = getattr(QtCore.QLocale, prop.attrib['country']) + return QtCore.QLocale(lang, country) + + def _cursor(self, prop): + return QtGui.QCursor(QtCore.Qt.CursorShape(int(prop.text))) + + def _date(self, prop): + return QtCore.QDate(*int_list(prop)) + + def _datetime(self, prop): + args = int_list(prop) + return QtCore.QDateTime(QtCore.QDate(*args[-3:]), QtCore.QTime(*args[:-3])) + + def _time(self, prop): + return QtCore.QTime(*int_list(prop)) + + def _gradient(self, prop): + name = 'gradient' + + # Create the specific gradient. + gtype = prop.get('type', '') + + if gtype == 'LinearGradient': + startx = float(prop.get('startx')) + starty = float(prop.get('starty')) + endx = float(prop.get('endx')) + endy = float(prop.get('endy')) + gradient = self.factory.createQObject('QLinearGradient', name, + (startx, starty, endx, endy), is_attribute=False) + + elif gtype == 'ConicalGradient': + centralx = float(prop.get('centralx')) + centraly = float(prop.get('centraly')) + angle = float(prop.get('angle')) + gradient = self.factory.createQObject('QConicalGradient', name, + (centralx, centraly, angle), is_attribute=False) + + elif gtype == 'RadialGradient': + centralx = float(prop.get('centralx')) + centraly = float(prop.get('centraly')) + radius = float(prop.get('radius')) + focalx = float(prop.get('focalx')) + focaly = float(prop.get('focaly')) + gradient = self.factory.createQObject('QRadialGradient', name, + (centralx, centraly, radius, focalx, focaly), + is_attribute=False) + + else: + raise UnsupportedPropertyError(prop.tag) + + # Set the common values. + spread = prop.get('spread') + if spread: + gradient.setSpread(getattr(QtGui.QGradient, spread)) + + cmode = prop.get('coordinatemode') + if cmode: + gradient.setCoordinateMode(getattr(QtGui.QGradient, cmode)) + + # Get the gradient stops. + for gstop in prop: + if gstop.tag != 'gradientstop': + raise UnsupportedPropertyError(gstop.tag) + + position = float(gstop.get('position')) + color = self._color(gstop[0]) + + gradient.setColorAt(position, color) + + return name + + def _palette(self, prop): + palette = self.factory.createQObject("QPalette", "palette", (), + is_attribute=False) + + for palette_elem in prop: + sub_palette = getattr(QtGui.QPalette, palette_elem.tag.title()) + for role, color in enumerate(palette_elem): + if color.tag == 'color': + # Handle simple colour descriptions where the role is + # implied by the colour's position. + palette.setColor(sub_palette, + QtGui.QPalette.ColorRole(role), self._color(color)) + elif color.tag == 'colorrole': + role = getattr(QtGui.QPalette, color.get('role')) + brush = self._brush(color[0]) + palette.setBrush(sub_palette, role, brush) + else: + raise UnsupportedPropertyError(color.tag) + + return palette + + def _brush(self, prop): + brushstyle = prop.get('brushstyle') + + if brushstyle in ('LinearGradientPattern', 'ConicalGradientPattern', 'RadialGradientPattern'): + gradient = self._gradient(prop[0]) + brush = self.factory.createQObject("QBrush", "brush", (gradient, ), + is_attribute=False) + else: + color = self._color(prop[0]) + brush = self.factory.createQObject("QBrush", "brush", (color, ), + is_attribute=False) + + brushstyle = getattr(QtCore.Qt, brushstyle) + brush.setStyle(brushstyle) + + return brush + + #@needsWidget + def _sizepolicy(self, prop, widget): + values = [int(child.text) for child in prop] + + if len(values) == 2: + # Qt v4.3.0 and later. + horstretch, verstretch = values + hsizetype = getattr(QtWidgets.QSizePolicy, prop.get('hsizetype')) + vsizetype = getattr(QtWidgets.QSizePolicy, prop.get('vsizetype')) + else: + hsizetype, vsizetype, horstretch, verstretch = values + hsizetype = QtWidgets.QSizePolicy.Policy(hsizetype) + vsizetype = QtWidgets.QSizePolicy.Policy(vsizetype) + + sizePolicy = self.factory.createQObject("QSizePolicy", "sizePolicy", + (hsizetype, vsizetype), is_attribute=False) + sizePolicy.setHorizontalStretch(horstretch) + sizePolicy.setVerticalStretch(verstretch) + sizePolicy.setHeightForWidth(widget.sizePolicy().hasHeightForWidth()) + return sizePolicy + _sizepolicy = needsWidget(_sizepolicy) + + # font needs special handling/conversion of all child elements. + _font_attributes = (("Family", str), + ("PointSize", int), + ("Weight", int), + ("Italic", bool_), + ("Underline", bool_), + ("StrikeOut", bool_), + ("Bold", bool_)) + + def _font(self, prop): + newfont = self.factory.createQObject("QFont", "font", (), + is_attribute = False) + for attr, converter in self._font_attributes: + v = prop.findtext("./%s" % (attr.lower(),)) + if v is None: + continue + + getattr(newfont, "set%s" % (attr,))(converter(v)) + return newfont + + def _cursorShape(self, prop): + return getattr(QtCore.Qt, prop.text) + + def convert(self, prop, widget=None): + try: + func = getattr(self, "_" + prop[0].tag) + except AttributeError: + raise UnsupportedPropertyError(prop[0].tag) + else: + args = {} + if getattr(func, "needsWidget", False): + assert widget is not None + args["widget"] = widget + + return func(prop[0], **args) + + + def _getChild(self, elem_tag, elem, name, default=None): + for prop in elem.findall(elem_tag): + if prop.attrib["name"] == name: + return self.convert(prop) + else: + return default + + def getProperty(self, elem, name, default=None): + return self._getChild("property", elem, name, default) + + def getAttribute(self, elem, name, default=None): + return self._getChild("attribute", elem, name, default) + + def setProperties(self, widget, elem): + try: + self.wclass = elem.attrib["class"] + except KeyError: + pass + for prop in elem.findall("property"): + prop_name = prop.attrib["name"] + DEBUG("setting property %s" % (prop_name,)) + + try: + stdset = bool(int(prop.attrib["stdset"])) + except KeyError: + stdset = True + + if not stdset: + self._setViaSetProperty(widget, prop) + elif hasattr(self, prop_name): + getattr(self, prop_name)(widget, prop) + else: + prop_value = self.convert(prop, widget) + if prop_value is not None: + getattr(widget, "set%s%s" % (ascii_upper(prop_name[0]), prop_name[1:]))(prop_value) + + # SPECIAL PROPERTIES + # If a property has a well-known value type but needs special, + # context-dependent handling, the default behaviour can be overridden here. + + # Delayed properties will be set after the whole widget tree has been + # populated. + def _delayed_property(self, widget, prop): + prop_value = self.convert(prop) + if prop_value is not None: + prop_name = prop.attrib["name"] + self.delayed_props.append((widget, False, + 'set%s%s' % (ascii_upper(prop_name[0]), prop_name[1:]), + prop_value)) + + # These properties will be set with a widget.setProperty call rather than + # calling the set function. + def _setViaSetProperty(self, widget, prop): + prop_value = self.convert(prop) + if prop_value is not None: + widget.setProperty(prop.attrib["name"], prop_value) + + # Ignore the property. + def _ignore(self, widget, prop): + pass + + # Define properties that use the canned handlers. + currentIndex = _delayed_property + currentRow = _delayed_property + + showDropIndicator = _setViaSetProperty + intValue = _setViaSetProperty + value = _setViaSetProperty + + objectName = _ignore + leftMargin = _ignore + topMargin = _ignore + rightMargin = _ignore + bottomMargin = _ignore + horizontalSpacing = _ignore + verticalSpacing = _ignore + + # tabSpacing is actually the spacing property of the widget's layout. + def tabSpacing(self, widget, prop): + prop_value = self.convert(prop) + if prop_value is not None: + self.delayed_props.append((widget, True, 'setSpacing', prop_value)) + + # buddy setting has to be done after the whole widget tree has been + # populated. We can't use delay here because we cannot get the actual + # buddy yet. + def buddy(self, widget, prop): + buddy_name = prop[0].text + if buddy_name: + self.buddies.append((widget, buddy_name)) + + # geometry is handled specially if set on the toplevel widget. + def geometry(self, widget, prop): + if widget.objectName() == self.uiname: + geom = int_list(prop[0]) + widget.resize(geom[2], geom[3]) + else: + widget.setGeometry(self._rect(prop[0])) + + def orientation(self, widget, prop): + # If the class is a QFrame, it's a line. + if widget.metaObject().className() == "QFrame": + widget.setFrameShape( + {"Qt::Horizontal": QtWidgets.QFrame.HLine, + "Qt::Vertical" : QtWidgets.QFrame.VLine}[prop[0].text]) + + # In Qt Designer, lines appear to be sunken, QFormBuilder loads + # them as such, uic generates plain lines. We stick to the look in + # Qt Designer. + widget.setFrameShadow(QtWidgets.QFrame.Sunken) + else: + widget.setOrientation(self._enum(prop[0])) + + # The isWrapping attribute of QListView is named inconsistently, it should + # be wrapping. + def isWrapping(self, widget, prop): + widget.setWrapping(self.convert(prop)) + + # This is a pseudo-property injected to deal with setContentsMargin() + # introduced in Qt v4.3. + def pyuicContentsMargins(self, widget, prop): + widget.setContentsMargins(*int_list(prop)) + + # This is a pseudo-property injected to deal with setHorizontalSpacing() + # and setVerticalSpacing() introduced in Qt v4.3. + def pyuicSpacing(self, widget, prop): + horiz, vert = int_list(prop) + + if horiz == vert: + widget.setSpacing(horiz) + else: + if horiz >= 0: + widget.setHorizontalSpacing(horiz) + + if vert >= 0: + widget.setVerticalSpacing(vert) diff --git a/resources/pyside/lib/site-packages/pyside2uic/uiparser.py b/resources/pyside/lib/site-packages/pyside2uic/uiparser.py new file mode 100644 index 0000000..0fd9481 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/uiparser.py @@ -0,0 +1,884 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +import sys +import logging +import os.path +import re + +try: + from xml.etree.cElementTree import parse, SubElement +except ImportError: + from xml.etree.ElementTree import parse, SubElement + + +from pyside2uic.exceptions import NoSuchWidgetError +from pyside2uic.objcreator import QObjectCreator +from pyside2uic.properties import Properties + + +logger = logging.getLogger(__name__) +DEBUG = logger.debug + +if sys.version_info < (2,4,0): + def reversed(seq): + for i in xrange(len(seq)-1, -1, -1): + yield seq[i] + +QtCore = None +QtGui = None +QtWidgets = None + + +def gridPosition(elem): + """gridPosition(elem) -> tuple + + Return the 4-tuple of (row, column, rowspan, colspan) + for a widget element, or an empty tuple. + """ + try: + return (int(elem.attrib["row"]), + int(elem.attrib["column"]), + int(elem.attrib.get("rowspan", 1)), + int(elem.attrib.get("colspan", 1))) + except KeyError: + return () + + +class WidgetStack(list): + topwidget = None + def push(self, item): + DEBUG("push %s %s" % (item.metaObject().className(), + item.objectName())) + self.append(item) + if isinstance(item, QtWidgets.QWidget): + self.topwidget = item + + def popLayout(self): + layout = list.pop(self) + DEBUG("pop layout %s %s" % (layout.metaObject().className(), + layout.objectName())) + return layout + + def popWidget(self): + widget = list.pop(self) + DEBUG("pop widget %s %s" % (widget.metaObject().className(), + widget.objectName())) + for item in reversed(self): + if isinstance(item, QtWidgets.QWidget): + self.topwidget = item + break + else: + self.topwidget = None + DEBUG("new topwidget %s" % (self.topwidget,)) + return widget + + def peek(self): + return self[-1] + + def topIsLayout(self): + return isinstance(self[-1], QtWidgets.QLayout) + + +class UIParser(object): + def __init__(self, QtCoreModule, QtGuiModule, QtWidgetsModule, creatorPolicy): + self.factory = QObjectCreator(creatorPolicy) + self.wprops = Properties(self.factory, QtCoreModule, QtGuiModule, QtWidgetsModule) + + global QtCore, QtGui, QtWidgets + QtCore = QtCoreModule + QtGui = QtGuiModule + QtWidgets = QtWidgetsModule + + self.reset() + + def uniqueName(self, name): + """UIParser.uniqueName(string) -> string + + Create a unique name from a string. + >>> p = UIParser(QtCore, QtGui, QtWidgets) + >>> p.uniqueName("foo") + 'foo' + >>> p.uniqueName("foo") + 'foo1' + """ + try: + suffix = self.name_suffixes[name] + except KeyError: + self.name_suffixes[name] = 0 + return name + + suffix += 1 + self.name_suffixes[name] = suffix + + return "%s%i" % (name, suffix) + + def reset(self): + try: self.wprops.reset() + except AttributeError: pass + self.toplevelWidget = None + self.stack = WidgetStack() + self.name_suffixes = {} + self.defaults = {"spacing": 6, "margin": 0} + self.actions = [] + self.currentActionGroup = None + self.resources = [] + self.button_groups = [] + self.layout_widget = False + + def setupObject(self, clsname, parent, branch, is_attribute = True): + name = self.uniqueName(branch.attrib.get("name") or clsname[1:].lower()) + if parent is None: + args = () + else: + args = (parent, ) + obj = self.factory.createQObject(clsname, name, args, is_attribute) + self.wprops.setProperties(obj, branch) + obj.setObjectName(name) + if is_attribute: + setattr(self.toplevelWidget, name, obj) + return obj + + def createWidget(self, elem): + self.column_counter = 0 + self.row_counter = 0 + self.item_nr = 0 + self.itemstack = [] + self.sorting_enabled = None + + widget_class = elem.attrib['class'].replace('::', '.') + if widget_class == 'Line': + widget_class = 'QFrame' + + # Ignore the parent if it is a container + parent = self.stack.topwidget + + # if is a Menubar on MacOS + macMenu = (sys.platform == 'darwin') and (widget_class == 'QMenuBar') + + if isinstance(parent, (QtWidgets.QDockWidget, QtWidgets.QMdiArea, + QtWidgets.QScrollArea, QtWidgets.QStackedWidget, + QtWidgets.QToolBox, QtWidgets.QTabWidget, + QtWidgets.QWizard)) or macMenu: + parent = None + + + # See if this is a layout widget. + if widget_class == 'QWidget': + if parent is not None: + if not isinstance(parent, QtWidgets.QMainWindow): + self.layout_widget = True + + self.stack.push(self.setupObject(widget_class, parent, elem)) + + if isinstance(self.stack.topwidget, QtWidgets.QTableWidget): + self.stack.topwidget.setColumnCount(len(elem.findall("column"))) + self.stack.topwidget.setRowCount(len(elem.findall("row"))) + + self.traverseWidgetTree(elem) + widget = self.stack.popWidget() + + self.layout_widget = False + + if isinstance(widget, QtWidgets.QTreeView): + self.handleHeaderView(elem, "header", widget.header()) + + elif isinstance(widget, QtWidgets.QTableView): + self.handleHeaderView(elem, "horizontalHeader", + widget.horizontalHeader()) + self.handleHeaderView(elem, "verticalHeader", + widget.verticalHeader()) + + elif isinstance(widget, QtWidgets.QAbstractButton): + bg_i18n = self.wprops.getAttribute(elem, "buttonGroup") + if bg_i18n is not None: + bg_name = bg_i18n.string + + for bg in self.button_groups: + if bg.objectName() == bg_name: + break + else: + bg = self.factory.createQObject("QButtonGroup", bg_name, + (self.toplevelWidget, )) + bg.setObjectName(bg_name) + self.button_groups.append(bg) + + bg.addButton(widget) + + if self.sorting_enabled is not None: + widget.setSortingEnabled(self.sorting_enabled) + self.sorting_enabled = None + + if self.stack.topIsLayout(): + lay = self.stack.peek() + gp = elem.attrib["grid-position"] + + if isinstance(lay, QtWidgets.QFormLayout): + lay.setWidget(gp[0], self._form_layout_role(gp), widget) + else: + lay.addWidget(widget, *gp) + + topwidget = self.stack.topwidget + + if isinstance(topwidget, QtWidgets.QToolBox): + icon = self.wprops.getAttribute(elem, "icon") + if icon is not None: + topwidget.addItem(widget, icon, self.wprops.getAttribute(elem, "label")) + else: + topwidget.addItem(widget, self.wprops.getAttribute(elem, "label")) + + tooltip = self.wprops.getAttribute(elem, "toolTip") + if tooltip is not None: + topwidget.setItemToolTip(topwidget.indexOf(widget), tooltip) + + elif isinstance(topwidget, QtWidgets.QTabWidget): + icon = self.wprops.getAttribute(elem, "icon") + if icon is not None: + topwidget.addTab(widget, icon, self.wprops.getAttribute(elem, "title")) + else: + topwidget.addTab(widget, self.wprops.getAttribute(elem, "title")) + + tooltip = self.wprops.getAttribute(elem, "toolTip") + if tooltip is not None: + topwidget.setTabToolTip(topwidget.indexOf(widget), tooltip) + + elif isinstance(topwidget, QtWidgets.QWizard): + topwidget.addPage(widget) + + elif isinstance(topwidget, QtWidgets.QStackedWidget): + topwidget.addWidget(widget) + + elif isinstance(topwidget, (QtWidgets.QDockWidget, QtWidgets.QScrollArea)): + topwidget.setWidget(widget) + + elif isinstance(topwidget, QtWidgets.QMainWindow): + if type(widget) == QtWidgets.QWidget: + topwidget.setCentralWidget(widget) + elif isinstance(widget, QtWidgets.QToolBar): + tbArea = self.wprops.getAttribute(elem, "toolBarArea") + + if tbArea is None: + topwidget.addToolBar(widget) + else: + topwidget.addToolBar(tbArea, widget) + + tbBreak = self.wprops.getAttribute(elem, "toolBarBreak") + + if tbBreak: + topwidget.insertToolBarBreak(widget) + + elif isinstance(widget, QtWidgets.QMenuBar): + topwidget.setMenuBar(widget) + elif isinstance(widget, QtWidgets.QStatusBar): + topwidget.setStatusBar(widget) + elif isinstance(widget, QtWidgets.QDockWidget): + dwArea = self.wprops.getAttribute(elem, "dockWidgetArea") + topwidget.addDockWidget(QtCore.Qt.DockWidgetArea(dwArea), + widget) + + def handleHeaderView(self, elem, name, header): + value = self.wprops.getAttribute(elem, name + "Visible") + if value is not None: + header.setVisible(value) + + value = self.wprops.getAttribute(elem, name + "CascadingSectionResizes") + if value is not None: + header.setCascadingSectionResizes(value) + + value = self.wprops.getAttribute(elem, name + "DefaultSectionSize") + if value is not None: + header.setDefaultSectionSize(value) + + value = self.wprops.getAttribute(elem, name + "HighlightSections") + if value is not None: + header.setHighlightSections(value) + + value = self.wprops.getAttribute(elem, name + "MinimumSectionSize") + if value is not None: + header.setMinimumSectionSize(value) + + value = self.wprops.getAttribute(elem, name + "ShowSortIndicator") + if value is not None: + header.setSortIndicatorShown(value) + + value = self.wprops.getAttribute(elem, name + "StretchLastSection") + if value is not None: + header.setStretchLastSection(value) + + def createSpacer(self, elem): + width = elem.findtext("property/size/width") + height = elem.findtext("property/size/height") + + if width is None or height is None: + size_args = () + else: + size_args = (int(width), int(height)) + + sizeType = self.wprops.getProperty(elem, "sizeType", + QtWidgets.QSizePolicy.Expanding) + + policy = (QtWidgets.QSizePolicy.Minimum, sizeType) + + if self.wprops.getProperty(elem, "orientation") == QtCore.Qt.Horizontal: + policy = policy[1], policy[0] + + spacer = self.factory.createQObject("QSpacerItem", + self.uniqueName("spacerItem"), size_args + policy, + is_attribute=False) + + if self.stack.topIsLayout(): + lay = self.stack.peek() + gp = elem.attrib["grid-position"] + + if isinstance(lay, QtWidgets.QFormLayout): + lay.setItem(gp[0], self._form_layout_role(gp), spacer) + else: + lay.addItem(spacer, *gp) + + def createLayout(self, elem): + # Qt v4.3 introduced setContentsMargins() and separate values for each + # of the four margins which are specified as separate properties. This + # doesn't really fit the way we parse the tree (why aren't the values + # passed as attributes of a single property?) so we create a new + # property and inject it. However, if we find that they have all been + # specified and have the same value then we inject a different property + # that is compatible with older versions of Qt. + left = self.wprops.getProperty(elem, 'leftMargin', -1) + top = self.wprops.getProperty(elem, 'topMargin', -1) + right = self.wprops.getProperty(elem, 'rightMargin', -1) + bottom = self.wprops.getProperty(elem, 'bottomMargin', -1) + + # Count the number of properties and if they had the same value. + def comp_property(m, so_far=-2, nr=0): + if m >= 0: + nr += 1 + + if so_far == -2: + so_far = m + elif so_far != m: + so_far = -1 + + return so_far, nr + + margin, nr_margins = comp_property(left) + margin, nr_margins = comp_property(top, margin, nr_margins) + margin, nr_margins = comp_property(right, margin, nr_margins) + margin, nr_margins = comp_property(bottom, margin, nr_margins) + + if nr_margins > 0: + if nr_margins == 4 and margin >= 0: + # We can inject the old margin property. + me = SubElement(elem, 'property', name='margin') + SubElement(me, 'number').text = str(margin) + else: + # We have to inject the new internal property. + cme = SubElement(elem, 'property', name='pyuicContentsMargins') + SubElement(cme, 'number').text = str(left) + SubElement(cme, 'number').text = str(top) + SubElement(cme, 'number').text = str(right) + SubElement(cme, 'number').text = str(bottom) + elif self.layout_widget: + # The layout's of layout widgets have no margin. + me = SubElement(elem, 'property', name='margin') + SubElement(me, 'number').text = '0' + + # In case there are any nested layouts. + self.layout_widget = False + + # We do the same for setHorizontalSpacing() and setVerticalSpacing(). + horiz = self.wprops.getProperty(elem, 'horizontalSpacing', -1) + vert = self.wprops.getProperty(elem, 'verticalSpacing', -1) + + if horiz >= 0 or vert >= 0: + # We inject the new internal property. + cme = SubElement(elem, 'property', name='pyuicSpacing') + SubElement(cme, 'number').text = str(horiz) + SubElement(cme, 'number').text = str(vert) + + classname = elem.attrib["class"] + if self.stack.topIsLayout(): + parent = None + else: + parent = self.stack.topwidget + if "name" not in elem.attrib: + elem.attrib["name"] = classname[1:].lower() + self.stack.push(self.setupObject(classname, parent, elem)) + self.traverseWidgetTree(elem) + + layout = self.stack.popLayout() + self.configureLayout(elem, layout) + + if self.stack.topIsLayout(): + top_layout = self.stack.peek() + gp = elem.attrib["grid-position"] + + if isinstance(top_layout, QtWidgets.QFormLayout): + top_layout.setLayout(gp[0], self._form_layout_role(gp), layout) + else: + top_layout.addLayout(layout, *gp) + + def configureLayout(self, elem, layout): + if isinstance(layout, QtWidgets.QGridLayout): + self.setArray(elem, 'columnminimumwidth', + layout.setColumnMinimumWidth) + self.setArray(elem, 'rowminimumheight', + layout.setRowMinimumHeight) + self.setArray(elem, 'columnstretch', layout.setColumnStretch) + self.setArray(elem, 'rowstretch', layout.setRowStretch) + + elif isinstance(layout, QtWidgets.QBoxLayout): + self.setArray(elem, 'stretch', layout.setStretch) + + def setArray(self, elem, name, setter): + array = elem.attrib.get(name) + if array: + for idx, value in enumerate(array.split(',')): + value = int(value) + if value > 0: + setter(idx, value) + + def handleItem(self, elem): + if self.stack.topIsLayout(): + elem[0].attrib["grid-position"] = gridPosition(elem) + self.traverseWidgetTree(elem) + else: + w = self.stack.topwidget + + if isinstance(w, QtWidgets.QComboBox): + text = self.wprops.getProperty(elem, "text") + icon = self.wprops.getProperty(elem, "icon") + + if icon: + w.addItem(icon, '') + else: + w.addItem('') + + w.setItemText(self.item_nr, text) + + elif isinstance(w, QtWidgets.QListWidget): + text = self.wprops.getProperty(elem, "text") + icon = self.wprops.getProperty(elem, "icon") + flags = self.wprops.getProperty(elem, "flags") + check_state = self.wprops.getProperty(elem, "checkState") + background = self.wprops.getProperty(elem, "background") + foreground = self.wprops.getProperty(elem, "foreground") + + if icon or flags or check_state: + item_name = "item" + else: + item_name = None + + item = self.factory.createQObject("QListWidgetItem", item_name, + (w, ), False) + + if self.item_nr == 0: + self.sorting_enabled = self.factory.invoke("__sortingEnabled", w.isSortingEnabled) + w.setSortingEnabled(False) + + if text: + w.item(self.item_nr).setText(text) + + if icon: + item.setIcon(icon) + + if flags: + item.setFlags(flags) + + if check_state: + item.setCheckState(check_state) + + if background: + item.setBackground(background) + + if foreground: + item.setForeground(foreground) + + elif isinstance(w, QtWidgets.QTreeWidget): + if self.itemstack: + parent, _ = self.itemstack[-1] + _, nr_in_root = self.itemstack[0] + else: + parent = w + nr_in_root = self.item_nr + + item = self.factory.createQObject("QTreeWidgetItem", + "item_%d" % len(self.itemstack), (parent, ), False) + + if self.item_nr == 0 and not self.itemstack: + self.sorting_enabled = self.factory.invoke("__sortingEnabled", w.isSortingEnabled) + w.setSortingEnabled(False) + + self.itemstack.append((item, self.item_nr)) + self.item_nr = 0 + + # We have to access the item via the tree when setting the + # text. + titm = w.topLevelItem(nr_in_root) + for child, nr_in_parent in self.itemstack[1:]: + titm = titm.child(nr_in_parent) + + column = -1 + for prop in elem.findall("property"): + c_prop = self.wprops.convert(prop) + c_prop_name = prop.attrib["name"] + + if c_prop_name == "text": + column += 1 + if c_prop: + titm.setText(column, c_prop) + elif c_prop_name == "icon": + item.setIcon(column, c_prop) + elif c_prop_name == "flags": + item.setFlags(c_prop) + elif c_prop_name == "checkState": + item.setCheckState(column, c_prop) + elif c_prop_name == "background": + item.setBackground(column, c_prop) + elif c_prop_name == "foreground": + item.setForeground(column, c_prop) + + self.traverseWidgetTree(elem) + _, self.item_nr = self.itemstack.pop() + + elif isinstance(w, QtWidgets.QTableWidget): + text = self.wprops.getProperty(elem, "text") + icon = self.wprops.getProperty(elem, "icon") + flags = self.wprops.getProperty(elem, "flags") + check_state = self.wprops.getProperty(elem, "checkState") + background = self.wprops.getProperty(elem, "background") + foreground = self.wprops.getProperty(elem, "foreground") + + item = self.factory.createQObject("QTableWidgetItem", "item", + (), False) + + if self.item_nr == 0: + self.sorting_enabled = self.factory.invoke("__sortingEnabled", w.isSortingEnabled) + w.setSortingEnabled(False) + + row = int(elem.attrib["row"]) + col = int(elem.attrib["column"]) + + if icon: + item.setIcon(icon) + + if flags: + item.setFlags(flags) + + if check_state: + item.setCheckState(check_state) + + if background: + item.setBackground(background) + + if foreground: + item.setForeground(foreground) + + w.setItem(row, col, item) + + if text: + # Text is translated so we don't have access to the item + # attribute when generating code so we must get it from the + # widget after it has been set. + w.item(row, col).setText(text) + + self.item_nr += 1 + + def addAction(self, elem): + self.actions.append((self.stack.topwidget, elem.attrib["name"])) + + def addHeader(self, elem): + w = self.stack.topwidget + + if isinstance(w, QtWidgets.QTreeWidget): + text = self.wprops.getProperty(elem, "text") + icon = self.wprops.getProperty(elem, "icon") + + if text: + w.headerItem().setText(self.column_counter, text) + + if icon: + w.headerItem().setIcon(self.column_counter, icon) + + self.column_counter += 1 + + elif isinstance(w, QtWidgets.QTableWidget): + if len(elem) == 0: + return + + text = self.wprops.getProperty(elem, "text") + icon = self.wprops.getProperty(elem, "icon") + whatsThis = self.wprops.getProperty(elem, "whatsThis") + + item = self.factory.createQObject("QTableWidgetItem", "item", + (), False) + + if elem.tag == "column": + w.setHorizontalHeaderItem(self.column_counter, item) + + if text: + w.horizontalHeaderItem(self.column_counter).setText(text) + + if icon: + item.setIcon(icon) + + if whatsThis: + w.horizontalHeaderItem(self.column_counter).setWhatsThis(whatsThis) + + self.column_counter += 1 + elif elem.tag == "row": + w.setVerticalHeaderItem(self.row_counter, item) + + if text: + w.verticalHeaderItem(self.row_counter).setText(text) + + if icon: + item.setIcon(icon) + + if whatsThis: + w.verticalHeaderItem(self.row_counter).setWhatsThis(whatsThis) + + self.row_counter += 1 + + def createAction(self, elem): + self.setupObject("QAction", self.currentActionGroup or self.toplevelWidget, + elem) + + def createActionGroup(self, elem): + action_group = self.setupObject("QActionGroup", self.toplevelWidget, elem) + self.currentActionGroup = action_group + self.traverseWidgetTree(elem) + self.currentActionGroup = None + + widgetTreeItemHandlers = { + "widget" : createWidget, + "addaction" : addAction, + "layout" : createLayout, + "spacer" : createSpacer, + "item" : handleItem, + "action" : createAction, + "actiongroup": createActionGroup, + "column" : addHeader, + "row" : addHeader, + } + + def traverseWidgetTree(self, elem): + for child in iter(elem): + try: + handler = self.widgetTreeItemHandlers[child.tag] + except KeyError: + continue + + handler(self, child) + + def createUserInterface(self, elem): + # Get the names of the class and widget. + cname = elem.attrib["class"] + wname = elem.attrib["name"] + + # If there was no widget name then derive it from the class name. + if not wname: + wname = cname + + if wname.startswith("Q"): + wname = wname[1:] + + wname = wname[0].lower() + wname[1:] + + self.toplevelWidget = self.createToplevelWidget(cname, wname) + self.toplevelWidget.setObjectName(wname) + DEBUG("toplevel widget is %s", + self.toplevelWidget.metaObject().className()) + self.wprops.setProperties(self.toplevelWidget, elem) + self.stack.push(self.toplevelWidget) + self.traverseWidgetTree(elem) + self.stack.popWidget() + self.addActions() + self.setBuddies() + self.setDelayedProps() + + def addActions(self): + for widget, action_name in self.actions: + if action_name == "separator": + widget.addSeparator() + else: + DEBUG("add action %s to %s", action_name, widget.objectName()) + action_obj = getattr(self.toplevelWidget, action_name) + if isinstance(action_obj, QtWidgets.QMenu): + widget.addAction(action_obj.menuAction()) + elif not isinstance(action_obj, QtWidgets.QActionGroup): + widget.addAction(action_obj) + + def setDelayedProps(self): + for widget, layout, setter, args in self.wprops.delayed_props: + if layout: + widget = widget.layout() + + setter = getattr(widget, setter) + setter(args) + + def setBuddies(self): + for widget, buddy in self.wprops.buddies: + DEBUG("%s is buddy of %s", buddy, widget.objectName()) + try: + widget.setBuddy(getattr(self.toplevelWidget, buddy)) + except AttributeError: + DEBUG("ERROR in ui spec: %s (buddy of %s) does not exist", + buddy, widget.objectName()) + + def classname(self, elem): + DEBUG("uiname is %s", elem.text) + name = elem.text + + if name is None: + name = "" + + self.uiname = name + self.wprops.uiname = name + self.setContext(name) + + def setContext(self, context): + """ + Reimplemented by a sub-class if it needs to know the translation + context. + """ + pass + + def readDefaults(self, elem): + self.defaults["margin"] = int(elem.attrib["margin"]) + self.defaults["spacing"] = int(elem.attrib["spacing"]) + + def setTaborder(self, elem): + lastwidget = None + for widget_elem in elem: + widget = getattr(self.toplevelWidget, widget_elem.text) + + if lastwidget is not None: + self.toplevelWidget.setTabOrder(lastwidget, widget) + + lastwidget = widget + + def readResources(self, elem): + """ + Read a "resources" tag and add the module to import to the parser's + list of them. + """ + for include in elem.getiterator("include"): + loc = include.attrib.get("location") + + # Assume our convention for naming the Python files generated by + # pyrcc4. + if loc and loc.endswith('.qrc'): + self.resources.append(os.path.basename(loc[:-4] + '_rc')) + + def createConnections(self, elem): + def name2object(obj): + if obj == self.uiname: + return self.toplevelWidget + else: + return getattr(self.toplevelWidget, obj) + for conn in iter(elem): + QtCore.QObject.connect(name2object(conn.findtext("sender")), + QtCore.SIGNAL(conn.findtext("signal")), + self.factory.getSlot(name2object(conn.findtext("receiver")), + conn.findtext("slot").split("(")[0])) + QtCore.QMetaObject.connectSlotsByName(self.toplevelWidget) + + def customWidgets(self, elem): + def header2module(header): + """header2module(header) -> string + + Convert paths to C++ header files to according Python modules + >>> header2module("foo/bar/baz.h") + 'foo.bar.baz' + """ + if header.endswith(".h"): + header = header[:-2] + + mpath = [] + for part in header.split('/'): + # Ignore any empty parts or those that refer to the current + # directory. + if part not in ('', '.'): + if part == '..': + # We should allow this for Python3. + raise SyntaxError("custom widget header file name may not contain '..'.") + + mpath.append(part) + + return '.'.join(mpath) + + for custom_widget in iter(elem): + classname = custom_widget.findtext("class") + if classname.startswith("Q3"): + raise NoSuchWidgetError(classname) + self.factory.addCustomWidget(classname, + custom_widget.findtext("extends") or "QWidget", + header2module(custom_widget.findtext("header"))) + + def createToplevelWidget(self, classname, widgetname): + raise NotImplementedError + + # finalize will be called after the whole tree has been parsed and can be + # overridden. + def finalize(self): + pass + + def parse(self, filename, base_dir=''): + self.wprops.set_base_dir(base_dir) + + # The order in which the different branches are handled is important. + # The widget tree handler relies on all custom widgets being known, and + # in order to create the connections, all widgets have to be populated. + branchHandlers = ( + ("layoutdefault", self.readDefaults), + ("class", self.classname), + ("customwidgets", self.customWidgets), + ("widget", self.createUserInterface), + ("connections", self.createConnections), + ("tabstops", self.setTaborder), + ("resources", self.readResources), + ) + + document = parse(filename) + version = document.getroot().attrib["version"] + DEBUG("UI version is %s" % (version,)) + # Right now, only version 4.0 is supported. + assert version in ("4.0",) + for tagname, actor in branchHandlers: + elem = document.find(tagname) + if elem is not None: + actor(elem) + self.finalize() + w = self.toplevelWidget + self.reset() + return w + + @staticmethod + def _form_layout_role(grid_position): + if grid_position[3] > 1: + role = QtWidgets.QFormLayout.SpanningRole + elif grid_position[1] == 1: + role = QtWidgets.QFormLayout.FieldRole + else: + role = QtWidgets.QFormLayout.LabelRole + + return role diff --git a/resources/pyside/lib/site-packages/pyside2uic/widget-plugins/qtdeclarative.py b/resources/pyside/lib/site-packages/pyside2uic/widget-plugins/qtdeclarative.py new file mode 100644 index 0000000..a4b5284 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/widget-plugins/qtdeclarative.py @@ -0,0 +1,32 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2010 Riverbank Computing Limited. +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +# If pluginType is MODULE, the plugin loader will call moduleInformation. The +# variable MODULE is inserted into the local namespace by the plugin loader. +pluginType = MODULE + + +# moduleInformation() must return a tuple (module, widget_list). If "module" +# is "A" and any widget from this module is used, the code generator will write +# "import A". If "module" is "A[.B].C", the code generator will write +# "from A[.B] import C". Each entry in "widget_list" must be unique. +def moduleInformation(): + return "PySide.QtDeclarative", ("QDeclarativeView", ) diff --git a/resources/pyside/lib/site-packages/pyside2uic/widget-plugins/qtwebkit.py b/resources/pyside/lib/site-packages/pyside2uic/widget-plugins/qtwebkit.py new file mode 100644 index 0000000..f5475c9 --- /dev/null +++ b/resources/pyside/lib/site-packages/pyside2uic/widget-plugins/qtwebkit.py @@ -0,0 +1,34 @@ +# This file is part of the PySide project. +# +# Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2009 Riverbank Computing Limited. +# Copyright (C) 2009 Torsten Marek +# +# Contact: PySide team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# version 2 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + + +# If pluginType is MODULE, the plugin loader will call moduleInformation. The +# variable MODULE is inserted into the local namespace by the plugin loader. +pluginType = MODULE + + +# moduleInformation() must return a tuple (module, widget_list). If "module" +# is "A" and any widget from this module is used, the code generator will write +# "import A". If "module" is "A[.B].C", the code generator will write +# "from A[.B] import C". Each entry in "widget_list" must be unique. +def moduleInformation(): + return "PySide.QtWebKit", ("QWebView", ) diff --git a/resources/pyside/lib/site-packages/shiboken2.pyd b/resources/pyside/lib/site-packages/shiboken2.pyd new file mode 100644 index 0000000..9998db4 Binary files /dev/null and b/resources/pyside/lib/site-packages/shiboken2.pyd differ diff --git a/resources/readme.txt b/resources/readme.txt new file mode 100644 index 0000000..479ea85 --- /dev/null +++ b/resources/readme.txt @@ -0,0 +1,5 @@ +VRED Shotgun setup intructions: +Copy Shotgun folder to \lib\plugins\WIN64\Scripts + +For example the vred could look something like this: +C:\Program Files\Autodesk\VREDPro-10.0\lib\plugins\WIN64\Scripts \ No newline at end of file diff --git a/startup.py b/startup.py new file mode 100644 index 0000000..c899a61 --- /dev/null +++ b/startup.py @@ -0,0 +1,165 @@ +# Copyright (c) 2016 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +import os +import sys + +import sgtk +from sgtk.platform import SoftwareLauncher, SoftwareVersion, LaunchInformation + + +class VREDLauncher(SoftwareLauncher): + """ + Handles launching VRED executables. Automatically starts up + a tk-vred engine with the current context in the new session + of VRED. + """ + + # Named regex strings to insert into the executable template paths when + # matching against supplied versions and products. Similar to the glob + # strings, these allow us to alter the regex matching for any of the + # variable components of the path in one place + COMPONENT_REGEX_LOOKUP = { + "version": "[\d.]+", + "product": "(?:Pro|Design)", + "product_bck": "(?:Pro|Design)" + } + + # This dictionary defines a list of executable template strings for each + # of the supported operating systems. The templates are used for both + # globbing and regex matches by replacing the named format placeholders + # with an appropriate glob or regex string. As Side FX adds modifies the + # install path on a given OS for a new release, a new template will need + # to be added here. + EXECUTABLE_TEMPLATES = { + "win32": [ + # C:/Program Files/Autodesk/VREDPro-11.0/bin/WIN64/VREDPro.exe + "C:/Program Files/Autodesk/VRED{product}-{version}/bin/WIN64/VRED{product_bck}.exe", + ] + } + + @property + def minimum_supported_version(self): + """ + The minimum software version that is supported by the launcher. + """ + return "11.0" + + def prepare_launch(self, exec_path, args, file_to_open=None): + """ + Prepares an environment to launch VRED in that will automatically + load Toolkit and the tk-vred engine when VRED starts. + + :param str exec_path: Path to VRED executable to launch. + :param str args: Command line arguments as strings. + :param str file_to_open: (optional) Full path name of a file to open on launch. + :returns: :class:`LaunchInformation` instance + """ + # find the bootstrap script and import it. + # note: all the business logic for how to launch is + # located in the python/startup folder to be compatible + # with older versions of the launch workflow + bootstrap_python_path = os.path.join(self.disk_location, "python", "startup") + sys.path.insert(0, bootstrap_python_path) + import vred_bootstrap + + # determine all environment variables + required_env = vred_bootstrap.compute_environment(self.engine_name, sgtk.context.serialize(self.context)) + # copy the extension across to the deploy folder + args = vred_bootstrap.compute_args(args) + + # Add std context and site info to the env + std_env = self.get_standard_plugin_environment() + required_env.update(std_env) + + return LaunchInformation(exec_path, args, required_env) + + ########################################################################################## + # private methods + + def _icon_from_executable(self, product): + """ + Find the application icon based on the executable path and + current platform. + + :param product: product. + + :returns: Full path to application icon as a string or None. + """ + + # the engine icon in case we need to use it as a fallback + icon_name = "icon_256.png" + if product == 'Pro': + icon_name = "icon_pro_256.png" + elif product == 'Design': + icon_name = "icon_design_256.png" + engine_icon = os.path.join(self.disk_location, icon_name) + if not os.path.exists(engine_icon): + engine_icon = os.path.join(self.disk_location, "icon_256.png") + return engine_icon + + def scan_software(self): + """ + Scan the filesystem for vred executables. + + :return: A list of :class:`SoftwareVersion` objects. + """ + self.logger.debug("Scanning for VRED executables...") + + supported_sw_versions = [] + for sw_version in self._find_software(): + (supported, reason) = self._is_supported(sw_version) + if supported: + supported_sw_versions.append(sw_version) + else: + self.logger.debug( + "SoftwareVersion %s is not supported: %s" % + (sw_version, reason) + ) + + return supported_sw_versions + + def _find_software(self): + """ + Find executables in the default install locations. + """ + + # all the executable templates for the current OS + executable_templates = self.EXECUTABLE_TEMPLATES.get(sys.platform, []) + + # all the discovered executables + sw_versions = [] + + for executable_template in executable_templates: + + self.logger.debug("Processing template %s.", executable_template) + + executable_matches = self._glob_and_match( + executable_template, + self.COMPONENT_REGEX_LOOKUP + ) + + # Extract all products from that executable. + for (executable_path, key_dict) in executable_matches: + + # extract the matched keys form the key_dict (default to None if + # not included) + executable_version = key_dict.get("version") + + sw_versions.append( + SoftwareVersion( + executable_version, + 'VRED {0}'.format(key_dict.get("product")), + executable_path, + self._icon_from_executable(key_dict.get('product')) + ) + ) + + return sw_versions \ No newline at end of file