def start_app(engine): """ Run the QApplication for the given tk-desktop engine """ if engine.has_ui: from tank.platform.qt import QtGui app = QtGui.QApplication([]) app.setQuitOnLastWindowClosed(False) app.setApplicationName("%s Python" % engine.context.project["name"]) # set default icon python_icon = os.path.realpath( os.path.join(os.path.dirname(__file__), "..", "..", "resources", "python_icon.png")) app.setWindowIcon(QtGui.QIcon(python_icon)) # Let the engine know we've created the app engine.register_qapplication(app) # use the toolkit look and feel engine._initialize_dark_look_and_feel() result = 0 while True: # loop until we are signaled to close, in case an app accidentally quits the app result = app.exec_() if not engine.connected: # we have been signaled to quit rather than waiting for more commands break return result else: # not engine.has_ui # wait for the engine communication channel to shut down engine.msg_server.join() return 0
def __setup_ui(self, callback): """ Starts a QApplication and initializes the UI. """ from tank.platform.qt import QtCore, QtGui # we got QT capabilities. Start a QT app and fire the command into the app tk_shotgun = self.import_module("tk_shotgun") t = tk_shotgun.Task(self, callback) # start up our QApp now qt_application = QtGui.QApplication([]) qt_application.setWindowIcon(QtGui.QIcon(self.icon_256)) self._initialize_dark_look_and_feel() # when the QApp starts, initialize our task code QtCore.QTimer.singleShot(0, t.run_command) # and ask the main app to exit when the task emits its finished signal t.finished.connect(qt_application.quit) # start the application loop. This will block the process until the task # has completed - this is either triggered by a main window closing or # byt the finished signal being called from the task class above. qt_application.exec_()
def start_qt_app_and_show_modal(title, engine, widget_class, *args, **kwargs): """ Wrapper around the engine.show_modal() call that first starts a QApplication, then shows the window, then quits the QApplication. :param title: Window title :param engine: Engine object to associate with :param widget_class: UI class to create :param args/kwargs: parameters to pass to the UI :returns: The modal dialog return value """ t = QtTask(title, engine, widget_class, args, kwargs) # start up our QApp now qt_application = QtGui.QApplication([]) qt_application.setWindowIcon(QtGui.QIcon(engine.icon_256)) engine._initialize_dark_look_and_feel() # when the QApp starts, initialize our task code QtCore.QTimer.singleShot(0, t.run_command) # and ask the main app to exit when the task emits its finished signal t.finished.connect(qt_application.quit) # start the application loop. This will block the process until the task # has completed - this is either triggered by a main window closing or # by the finished signal being called from the task class above. qt_application.exec_() return t.get_return_data()
def execute_command(self, cmd_key, args): """ Executes a given command. """ cb = self.commands[cmd_key]["callback"] # make sure the number of parameters to the command are correct cb_arg_spec = inspect.getargspec(cb) cb_arg_list = cb_arg_spec[0] cb_var_args = cb_arg_spec[1] if hasattr(cb, "__self__"): # first argument to cb will be class instance: cb_arg_list = cb_arg_list[1:] # ensure the correct/minimum number of arguments have been passed: have_expected_args = False if cb_var_args: have_expected_args = (len(args) >= len(cb_arg_list)) else: have_expected_args = (len(args) == len(cb_arg_list)) if not have_expected_args: expected_args = list(cb_arg_list) if cb_var_args: expected_args.append("*%s" % cb_var_args) raise TankError( "Cannot run command! Expected command arguments (%s)" % ", ".join(expected_args)) if not self._has_qt: # QT not available - just run the command straight return cb(*args) else: from tank.platform.qt import QtCore, QtGui # we got QT capabilities. Start a QT app and fire the command into the app tk_shell = self.import_module("tk_shell") t = tk_shell.Task(self, cb, args) # start up our QApp now qt_application = QtGui.QApplication([]) qt_application.setWindowIcon(QtGui.QIcon(self.icon_256)) self._initialize_dark_look_and_feel() # now we have a working UI! self._has_ui = True # when the QApp starts, initialize our task code QtCore.QTimer.singleShot(0, t.run_command) # and ask the main app to exit when the task emits its finished signal t.finished.connect(qt_application.quit) # start the application loop. This will block the process until the task # has completed - this is either triggered by a main window closing or # byt the finished signal being called from the task class above. qt_application.exec_()
def run(self, splash=None, version=None): """ Run the engine. This method is called from the GUI bootstrap to setup the application and to run the Qt event loop. """ self.app_version = version # Initialize Qt app from tank.platform.qt import QtGui app = QtGui.QApplication.instance() if app is None: app = QtGui.QApplication(sys.argv) # update the app icon icon = QtGui.QIcon(":tk-desktop/default_systray_icon") app.setWindowIcon(icon) splash.set_message("Building UI") # setup the global look and feel self._engine._initialize_dark_look_and_feel() # load custom font QtGui.QFontDatabase.addApplicationFont( ":/tk-desktop/fonts/OpenSans-Bold.ttf") QtGui.QFontDatabase.addApplicationFont( ":/tk-desktop/fonts/OpenSans-Regular.ttf") QtGui.QFontDatabase.addApplicationFont( ":/tk-desktop/fonts/OpenSans-CondLight.ttf") QtGui.QFontDatabase.addApplicationFont( ":/tk-desktop/fonts/OpenSans-Light.ttf") # merge in app specific look and feel css_file = os.path.join(self._engine.disk_location, "resources", "desktop_dark.css") f = open(css_file) css = app.styleSheet() + "\n\n" + f.read() f.close() app.setStyleSheet(css) # initialize System Tray self.desktop_window = desktop_window.DesktopWindow() # make sure we close down our rpc threads app.aboutToQuit.connect(self._engine.destroy_engine) # hide the splash if it exists if splash is not None: splash.hide() # and run the app result = app.exec_() return result
def post_app_init(self): """ Init that runs after all apps have been loaded. """ tk_houdini = self.import_module("tk_houdini") bootstrap = tk_houdini.bootstrap if bootstrap.g_temp_env in os.environ: if self.has_ui: # setup houdini menus menu_file = os.path.join(os.environ[bootstrap.g_temp_env], 'MainMenuCommon') # as of houdini 12.5 add .xml if hou.applicationVersion() > (12, 5, 0): menu_file = menu_file + ".xml" menu = tk_houdini.MenuGenerator(self) if not os.path.exists(menu_file): # just create the xml for the menus menu.create_menu(menu_file) # get map of id to callback self._callback_map = menu.callback_map() # Figure out the tmp OP Library path for this session oplibrary_path = os.environ[bootstrap.g_temp_env].replace( "\\", "/") # Setup the OTLs that need to be loaded for the Toolkit apps self._load_otls(oplibrary_path) if self.has_ui: # startup Qt from tank.platform.qt import QtGui from tank.platform.qt import QtCore app = QtGui.QApplication.instance() if app is None: # create the QApplication sys.argv[0] = 'Shotgun' app = QtGui.QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) app.setApplicationName(sys.argv[0]) # tell QT to interpret C strings as utf-8 utf8 = QtCore.QTextCodec.codecForName("utf-8") QtCore.QTextCodec.setCodecForCStrings(utf8) # set the stylesheet self._initialize_dark_look_and_feel() tk_houdini.python_qt_houdini.exec_(app)
def start_app(engine): """ Run the QApplication for the given tk-desktop engine. """ # If we're running the new engine that knows how to start the app, delegate the # task to it if hasattr(engine, "start_app"): return engine.start_app() # Otherwise run the legacy code. if engine.has_ui: # NOTE # The following code is meant to run for very old verions of tk-desktop. It # should not be edited to support newer features. from tank.platform.qt import QtGui app = QtGui.QApplication([]) app.setQuitOnLastWindowClosed(False) app.setApplicationName("%s Python" % engine.context.project["name"]) # set default icon python_icon = os.path.realpath( os.path.join( os.path.dirname(__file__), "..", "..", "resources", "python_icon.png" ) ) app.setWindowIcon(QtGui.QIcon(python_icon)) # Let the engine know we've created the app engine.register_qapplication(app) # use the toolkit look and feel engine._initialize_dark_look_and_feel() result = 0 while True: # loop until we are signaled to close, in case an app accidentally quits the app result = app.exec_() if not engine.connected: # we have been signaled to quit rather than waiting for more commands break return result else: # not engine.has_ui # wait for the engine communication channel to shut down engine.msg_server.join() return 0
def _initialize_application(self): from tank.platform.qt import QtGui app = QtGui.QApplication([]) # We may launch multiple UI apps, do not quit as soon as the last one closes. app.setQuitOnLastWindowClosed(False) # Make the name pretty for the tray and the task manager. app.setApplicationName("%s Python" % self._engine.context.project["name"]) # set default icon python_icon = os.path.join(self._engine.disk_location, "icon_bg_python.png") app.setWindowIcon(QtGui.QIcon(python_icon)) self.register_qapplication(app) # use the toolkit look and feel self._engine._initialize_dark_look_and_feel() return app
def __setup_ui(self, callback): """ Starts a QApplication and initializes the UI. """ from tank.platform.qt import QtCore, QtGui # we got QT capabilities. Start a QT app and fire the command into the app tk_shotgun = self.import_module("tk_shotgun") t = tk_shotgun.Task(self, callback) # We need to clear Qt library paths on Linux if KDE is the active environment. # This resolves issues with mismatched Qt libraries between the OS and the # application being launched if it is a DCC that comes with a bundled Qt. if sys.platform == "linux2" and os.environ.get( "KDE_FULL_SESSION") is not None: QtGui.QApplication.setLibraryPaths([]) # start up our QApp now qt_application = QtGui.QApplication([]) qt_application.setWindowIcon(QtGui.QIcon(self.icon_256)) # make sure we have a dark theme self._initialize_dark_look_and_feel() # now we have a working UI! self._has_ui = True # when the QApp starts, initialize our task code QtCore.QTimer.singleShot(0, t.run_command) # and ask the main app to exit when the task emits its finished signal t.finished.connect(qt_application.quit) # start the application loop. This will block the process until the task # has completed - this is either triggered by a main window closing or # byt the finished signal being called from the task class above. qt_application.exec_()
def _initialize_application(self): from tank.platform.qt import QtGui if sgtk.util.is_macos(): # If we are on Mac with PySide2, then starting a QApplication even with no Windows # will steal focus from any currently focused application. # So we need to use AppKit if available to stop it from stealing focus. try: import AppKit info = AppKit.NSBundle.mainBundle().infoDictionary() info["LSUIElement"] = "1" except ImportError: # Since AppKit is bundled with the Desktop installer, it's possible we are using # an older version of the installer that doesn't contain this package. In which # case just move on silently. pass app = QtGui.QApplication([]) # We may launch multiple UI apps, do not quit as soon as the last one closes. app.setQuitOnLastWindowClosed(False) # Make the name pretty for the tray and the task manager. app.setApplicationName("%s Python" % self._engine.context.project["name"]) # set default icon python_icon = os.path.join(self._engine.disk_location, "icon_bg_python.png") app.setWindowIcon(QtGui.QIcon(python_icon)) self.register_qapplication(app) # use the toolkit look and feel self._engine._initialize_dark_look_and_feel() return app
def execute_command(self, cmd_key, args): """ Executes a given command. """ cb = self.commands[cmd_key]["callback"] # make sure the number of parameters to the command are correct cb_arg_spec = inspect.getargspec(cb) cb_arg_list = cb_arg_spec[0] cb_var_args = cb_arg_spec[1] if hasattr(cb, "__self__"): # first argument to cb will be class instance: cb_arg_list = cb_arg_list[1:] # ensure the correct/minimum number of arguments have been passed: have_expected_args = False if cb_var_args: have_expected_args = (len(args) >= len(cb_arg_list)) else: have_expected_args = (len(args) == len(cb_arg_list)) if not have_expected_args: expected_args = list(cb_arg_list) if cb_var_args: expected_args.append("*%s" % cb_var_args) raise TankError( "Cannot run command! Expected command arguments (%s)" % ", ".join(expected_args)) if not self._has_qt: # QT not available - just run the command straight return cb(*args) else: from tank.platform.qt import QtCore, QtGui # we got QT capabilities. Start a QT app and fire the command into the app tk_shell = self.import_module("tk_shell") t = tk_shell.Task(self, cb, args) # start up our QApp now, if none is already running qt_application = None if not QtGui.qApp: # We need to clear Qt library paths on Linux if KDE is the active environment. # This resolves issues with mismatched Qt libraries between the OS and the # application being launched if it is a DCC that comes with a bundled Qt. if sys.platform == "linux2" and os.environ.get( "KDE_FULL_SESSION") is not None: QtGui.QApplication.setLibraryPaths([]) qt_application = QtGui.QApplication([]) qt_application.setWindowIcon(QtGui.QIcon(self.icon_256)) self._initialize_dark_look_and_feel() # if we didn't start the QApplication here, let the responsability # to run the exec loop and quit to the initial creator of the QApplication if qt_application: # when the QApp starts, initialize our task code QtCore.QTimer.singleShot(0, t.run_command) # and ask the main app to exit when the task emits its finished signal t.finished.connect(qt_application.quit) # start the application loop. This will block the process until the task # has completed - this is either triggered by a main window closing or # byt the finished signal being called from the task class above. qt_application.exec_() else: # we can run the command now, as the QApp is already started t.run_command()
def run(self, splash, version, **kwargs): """ Run the engine. This method is called from the GUI bootstrap to setup the application and to run the Qt event loop. :param splash: Splash screen widget we can display messages on. Can be ``None`` :param version: Version of the Shotgun Desktop installer code. :param startup_version: Version of the Desktop Startup code. Can be omitted. :param startup_descriptor: Descriptor of the Desktop Startup code. Can be omitted. """ self.app_version = version # Startup version will not be set if we have an old installer invoking # this engine. self.startup_version = kwargs.get("startup_version") self.startup_descriptor = kwargs.get("startup_descriptor") server = kwargs.get("server") # Log usage statistics about the Shotgun Desktop executable and the desktop startup. sgtk.util.log_user_attribute_metric("tk-framework-desktopstartup", self.startup_version) sgtk.util.log_user_attribute_metric("Shotgun Desktop version", self.app_version) # If a server is passed down from the desktop startup, it means we won't be using the engine-based # websocket server. sgtk.util.log_user_attribute_metric("Engine-Websockets", "no" if server else "yes") if self.uses_legacy_authentication(): self._migrate_credentials() # We need to initialize current login # We know for sure there is a default user, since either the migration was done # or we logged in as an actual user with the new installer. human_user = ShotgunAuthenticator( # We don't want to get the script user, but the human user, so tell the # CoreDefaultsManager manager that we are not interested in the script user. Do not use # the regular shotgun_authentication.DefaultsManager to get this user because it will # not know about proxy information. sgtk.util.CoreDefaultsManager(mask_script_user=True )).get_default_user() # Cache the user so we can refresh the credentials before launching a background process self._user = human_user # Retrieve the current logged in user information. This will be used when creating # event log entries. self._current_login = self._engine.sgtk.shotgun.find_one( "HumanUser", [["login", "is", human_user.login]], ["id", "login"]) # Initialize Qt app from tank.platform.qt import QtGui app = QtGui.QApplication.instance() if app is None: app = QtGui.QApplication(sys.argv) # update the app icon icon = QtGui.QIcon(":tk-desktop/default_systray_icon") app.setWindowIcon(icon) if splash: splash.set_message("Building UI") # setup the global look and feel self._engine._initialize_dark_look_and_feel() # load custom font QtGui.QFontDatabase.addApplicationFont( ":/tk-desktop/fonts/OpenSans-Bold.ttf") QtGui.QFontDatabase.addApplicationFont( ":/tk-desktop/fonts/OpenSans-Regular.ttf") QtGui.QFontDatabase.addApplicationFont( ":/tk-desktop/fonts/OpenSans-CondLight.ttf") QtGui.QFontDatabase.addApplicationFont( ":/tk-desktop/fonts/OpenSans-Light.ttf") # merge in app specific look and feel css_file = os.path.join(self._engine.disk_location, "resources", "desktop_dark.css") f = open(css_file) css = app.styleSheet() + "\n\n" + f.read() f.close() app.setStyleSheet(css) # If server is passed down to this method, it means we are running an older version of the # desktop startup code, which runs its own browser integration. # # Sadly, we can't tear down the previous server and restart it. Attempting to tear_down() and # instantiate a new server will raise an error.ReactorNotRestartable exception. So we'll start # our websocket integration only if there is no server running from the desktop startup. # Note that the server argument is set regardless of whether the server launched or crashed, # so we have to actually get its value instead of merely checking for existence. if server is None: # Initialize all of this after the style-sheet has been applied so any prompt are also # styled after the Shotgun Desktop's visual-style. if splash: splash.set_message("Initializing browser integration.") try: desktop_server_framework = sgtk.platform.get_framework( "tk-framework-desktopserver") desktop_server_framework.launch_desktop_server( self._user.host, self._current_login["id"], parent=splash) except Exception: logger.exception( "Unexpected error while trying to launch the browser integration:" ) else: logger.debug("Browser integration was launched successfully.") # hide the splash if it exists if splash is not None: splash.hide() # desktop_window needs to import shotgun_authentication globally. However, doing so # can cause a crash when running Shotgun Desktop installer 1.02 code. We used to # not restart Desktop when upgrading the core, which caused the older version of core # to be kept in memory and the newer core to not be used until the app was reloaded. # # Since pre 0.16 cores didn't have a shotgun_authentication module, we # would have crashed if this had been imported at init time. Note that earlier # in this method we forcefully restarted the application if we noticed # that the core was upgraded without restarting. Which means that if we # end up here, it's now because we're in a good state. from . import desktop_window # initialize System Tray self.desktop_window = desktop_window.DesktopWindow() # We need for the dialog to exist for messages to get to the UI console. if kwargs.get("server") is not None: logger.warning( "You are running an older version of the Shotgun Desktop which is not fully compatible " "with the Shotgun Integrations. Please install the latest version." ) # run the commands that are configured to be executed at startup self._run_startup_commands() # make sure we close down our rpc threads app.aboutToQuit.connect(self._engine.destroy_engine) # and run the app result = app.exec_() return result
def post_app_init(self): """ Init that runs after all apps have been loaded. """ if not self.has_ui: # no UI. everything after this requires the UI! return tk_houdini = self.import_module("tk_houdini") bootstrap = tk_houdini.bootstrap if bootstrap.g_temp_env in os.environ: commands = None enable_sg_menu = self.get_setting("enable_sg_menu", True) enable_sg_shelf = self.get_setting("enable_sg_shelf", True) # menu and/or shelf definitions will be written here xml_tmp_dir = os.environ[bootstrap.g_temp_env] if enable_sg_menu or enable_sg_shelf: # get the list of registered commands to supply to the menu # and/or shelf. The commands returned are AppCommand objects # defined in tk_houdini.ui_generation commands = tk_houdini.get_registered_commands(self) # populate a callback map. this is a map of command ids to a # corresponding callback. these are used by the menu and shelf # for executing installed app commands. self._callback_map = \ dict((cmd.get_id(), cmd.callback) for cmd in commands) if commands and enable_sg_menu: # setup houdini menus menu_file = os.path.join(xml_tmp_dir, "MainMenuCommon") # as of houdini 12.5 add .xml if hou.applicationVersion() > (12, 5, 0): menu_file = menu_file + ".xml" # keep the reference to the menu handler for convenience so # that we can access it from the menu scripts when they get # ahold of the current engine. self._menu = tk_houdini.AppCommandsMenu(self, commands) if not os.path.exists(menu_file): # just create the xml for the menus self._menu.create_menu(menu_file) if commands and enable_sg_shelf: # setup houdini shelf self._shelf = tk_houdini.AppCommandsShelf(self, commands) # cleans up any old tools on an existing shelf -- just in case. # we currently can't programmatically add a shelf to an # existing shelf set, so for now we just leave the shelf and # add/remove tools. self._shelf.destroy_tools() shelf_file = os.path.join(xml_tmp_dir, "sg_shelf.xml") self._shelf.create_shelf(shelf_file) if commands and self._panels_supported(): # Get the list of registered commands to build panels for. The # commands returned are AppCommand objects defined in # tk_houdini.ui_generation panel_commands = tk_houdini.get_registered_panels(self) # expose the wrapped panel method on the engine so that the # panels can call it directly self.get_wrapped_panel_widget = \ tk_houdini.get_wrapped_panel_widget if panel_commands: self._panels_file = os.path.join(xml_tmp_dir, "sg_panels.pypanel") panels = tk_houdini.AppCommandsPanelHandler( self, commands, panel_commands) panels.create_panels(self._panels_file) # Figure out the tmp OP Library path for this session oplibrary_path = os.environ[bootstrap.g_temp_env].replace( "\\", "/") # Setup the OTLs that need to be loaded for the Toolkit apps self._load_otls(oplibrary_path) # no integrated pyside support. need to run custom event loop. see # python/tk_houdini/python_qt_houdini.py if not self._integrated_pyside: # startup Qt from tank.platform.qt import QtGui app = QtGui.QApplication.instance() if app is None: # create the QApplication sys.argv[0] = "Shotgun" app = QtGui.QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) app.setApplicationName(sys.argv[0]) self.log_debug( "No integrated PySide. Starting integrated event loop.") tk_houdini.python_qt_houdini.exec_(app) # tell QT to interpret C strings as utf-8 from tank.platform.qt import QtCore utf8 = QtCore.QTextCodec.codecForName("utf-8") QtCore.QTextCodec.setCodecForCStrings(utf8) self.log_debug("set utf-8 codec for widget text") # Typically we only call this method for engines which don't have a # well defined styling. Houdini appears to use stylesheets to handle # its styling which it conflicts with the toolkit strategy of using a # dark QStyle underneath with additional stylesheets on top, allowing # the qss to be minimized. Calling this method applies a global style, # palette, and default stylesheet which, in addition to some # workarounds when parenting toolkit widgets, allows for the # consistent, intended look and feel of the toolkit widgets. # Surprisingly, calling this does not seem to have any affect on # houdini itself, despite the global nature of the method. self._initialize_dark_look_and_feel()