def handle_pipeline_steps_refreshed(self, refreshed):
        """
        This loads the menu with values returned when the _steps_model returns data_refreshed
        """
        if not self._pipeline_steps_menu:
            self._pipeline_steps_menu = shotgun_menus.ShotgunMenu(
                self._tray_frame.pipeline_filter_button,
            )
            self._pipeline_steps_menu.setObjectName("pipeline_steps_menu")
            self._tray_frame.pipeline_filter_button.setMenu(self._pipeline_steps_menu)

            # Sadly, because this button didn't have a menu at the time that
            # the app-level styling was applied, it won't inherit menu-indicator
            # styling there. We have to set it here as a result.
            self._tray_frame.pipeline_filter_button.setStyleSheet(
                """QPushButton::menu-indicator {
                        image: url(:tk-rv-shotgunreview/arrow.png);
                        subcontrol-position: right center;
                        subcontrol-origin: padding;
                        width: 10px;
                        right: -2px;
                        top: -1px;
                    }
                """
            )      
            self._pipeline_steps_menu.triggered.connect(self.handle_pipeline_menu)
        
        menu = self._pipeline_steps_menu
        menu.clear()

        # XXX latest in pipeline means an empty steps list?
        action = QtGui.QAction(self._tray_frame.pipeline_filter_button)
        action.setCheckable(True)
        action.setChecked(False)
        action.setText('Latest in Pipeline')
        # XXX what object do we want here?
        action.setData( { 'cached_display_name' : 'Latest in Pipeline' } )
        self._steps_proxyModel.sort(0, QtCore.Qt.DescendingOrder)
        rows = self._steps_proxyModel.rowCount()
        actions = [action]

        for x in range(0, rows):
            item = self._steps_proxyModel.index(x, 0)
            sg = shotgun_model.get_sg_data(item)
            action = QtGui.QAction(self._tray_frame.pipeline_filter_button)
            action.setCheckable(True)
            action.setChecked(False)
            action.setText(sg['cached_display_name'])
            action.setData(sg)
            actions.append(action)

        menu.add_group(actions, title="Pipeline Steps Priority")
        self.check_pipeline_menu()
Example #2
0
    def eventFilter(self, widget, event):
        if widget == self and event.type() == QtCore.QEvent.ContextMenu:
            menu = self.createStandardContextMenu()
            actions = menu.actions()
            for action in actions:
                action.setShortcut(QtGui.QKeySequence())
                action_string = action.text()
                pos = action_string.rfind("\t")
                if pos > 0:
                    action_string = action_string[:pos]
                    action.setText(action_string)

            action_before = None
            if len(actions) > 0:
                action_before = actions[0]
            clear = QtGui.QAction("Clear Shortcut", menu)
            menu.insertAction(action_before, clear)
            menu.insertSeparator(action_before)
            clear.setEnabled(not self.__key_sequence.isEmpty())
            clear.triggered.connect(self.clear_shortcut)
            menu.exec_(event.globalPos())
            del menu
            event.accept()
            return True

        return QtGui.QLineEdit.eventFilter(self, widget, event)
Example #3
0
    def _add_menu_item(self, name, parent_menu, callback, properties=None):
        action = QtGui.QAction(name, parent_menu)
        parent_menu.addAction(action)
        action.triggered.connect(callback)

        if properties:
            if "tooltip" in properties:
                action.setTooltip(properties["tooltip"])
                action.setStatustip(properties["tooltip"])
            if "enable_callback" in properties:
                action.setEnabled(properties["enable_callback"]())

        return action
Example #4
0
    def _add_menu_item(self, name, parent_menu, callback, properties=None):
        action = QtGui.QAction(name, parent_menu)
        parent_menu.addAction(action)
        if callback:
            action.triggered.connect(Callback(callback))

        if properties:
            self._engine.log_debug(properties)
            if "tooltip" in properties:
                action.setToolTip(properties["tooltip"])
                action.setStatusIip(properties["tooltip"])
            elif "description" in properties:
                action.setToolTip(properties["description"])
                action.setStatusTip(properties["description"])
            if "enable_callback" in properties:
                action.setEnabled(properties["enable_callback"]())
            if "checkable" in properties:
                action.setCheckable(True)
                action.setChecked(properties.get("checkable"))
            if "icon" in properties:
                action.setIcon(QtGui.QIcon(properties["icon"]))
        return action
    def trigger_register_command(self, name, properties, groups):
        """ GUI side handler for the add_command call. """
        from tank.platform.qt import QtGui

        self._engine.log_debug("register_command(%s, %s)", name, properties)

        command_type = properties.get("type")
        command_icon = properties.get("icon")
        command_tooltip = properties.get("description")

        icon = None
        if command_icon is not None:
            if os.path.exists(command_icon):
                icon = QtGui.QIcon(command_icon)
            else:
                self._engine.log_error(
                    "Icon for command '%s' not found: '%s'" % (name, command_icon))

        title = properties.get("title", name)

        if command_type == "context_menu":
            # Add the command to the project menu
            action = QtGui.QAction(self.desktop_window)
            if icon is not None:
                action.setIcon(icon)
            if command_tooltip is not None:
                action.setToolTip(command_tooltip)
            action.setText(title)

            def action_triggered():
                # make sure to pass in that we are not expecting a response
                # Especially for the engine restart command, the connection
                # itself gets reset and so there isn't a channel to get a
                # response back.
                self.refresh_user_credentials()
                self.proxy.call_no_response("trigger_callback", "__commands", name)
            action.triggered.connect(action_triggered)
            self.desktop_window.add_to_project_menu(action)
        else:
            # Default is to add an icon/label for the command

            # figure out what the button should be labeled
            # default is that the button has no menu and is labeled
            # the display name of the command
            menu_name = None
            button_name = title
            for collapse_rule in self._collapse_rules:
                template = DisplayNameTemplate(collapse_rule["match"])
                match = template.match(title)
                if match is not None:
                    self._engine.log_debug("matching %s against %s" % (title, collapse_rule["match"]))
                    if collapse_rule["menu_label"] == "None":
                        menu_name = None
                    else:
                        menu_name = string.Template(collapse_rule["menu_label"]).safe_substitute(match)
                    button_name = string.Template(collapse_rule["button_label"]).safe_substitute(match)
                    break

            self.desktop_window._project_command_model.add_command(
                name, button_name, menu_name, icon, command_tooltip, groups)
            self.desktop_window._project_command_proxy.invalidate()
    def process_result(self, result):
        
        entity_data = result["associated_entity"]
        tasks = result["tasks"]
        can_create_tasks = result["can_create_tasks"]
        
        entity_str = "%s %s" % (entity_data.get("type", "Unknown"), entity_data.get("code", "Unknown"))

        if len(tasks) == 0:
            msg = ""
            if can_create_tasks:
                msg = "No Tasks found!\nClick the 'New Task' button below to create a Task."
            else:
                msg = ("No Tasks found!\nYou can create tasks by navigating to '%s' "
                       "inside of Shotgun, selecting the tasks tab and then clicking "
                       "the + button." % entity_str)
            self.set_message(msg)
            
        else:

            current_task = result.get("current_task")
            item_to_select = None        
            
            for d in tasks:
                i = self.add_item(browser_widget.ListItem)
                
                details = []
                details.append("<b>Task: %s</b>" % d.get("content", ""))
                
                # now try to look up the proper status name
                status_short_name = d.get("sg_status_list")
                # get the long name, fall back on short name if not found
                status_name = self._status_name_lookup.get(status_short_name, status_short_name)
                details.append("Status: %s" % status_name)
                
                names = [ x.get("name", "Unknown") for x in d.get("task_assignees", []) ]
                names_str = ", ".join(names)
                details.append("Assigned to: %s" % names_str)
                
                i.set_details("<br>".join(details))
                
                i.sg_data = d
                i.setToolTip("Double click to set context.")
                
                # add a grab task action
                if self._current_user: # not None
                    if d.get("task_assignees"):
                        assigned_user_ids = [ x["id"] for x in d.get("task_assignees") ]
                    else:
                        assigned_user_ids = []
                        
                    if self._current_user["id"] not in assigned_user_ids:
                        # are are not assigned to this task. add ability to grab it                
                        i.grab_action = QtGui.QAction("Add %s as an assignee to this task." % self._current_user["name"], i)
                        i.grab_action.triggered.connect(self.grab_task)                       
                        i.addAction(i.grab_action)
                        i.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)                
                
                # finally look up the thumbnail for the first user assigned to the task
                task_assignees = d.get("task_assignees", [])
                if len(task_assignees) > 0:
                    user_id = task_assignees[0]["id"]
                    # is this user id in our users dict? In that case we have their thumb!
                    for u in result["users"]:
                        if u["id"] == user_id:
                            # if they have a thumb, assign!
                            if u.get("image"):
                                i.set_thumbnail(u.get("image"))
                            break            
                
                if d and current_task and d["id"] == current_task.get("id"):
                    item_to_select = i
            
            if item_to_select:
                self.select(item_to_select)
 def _add_divider(self, parent_menu):
     divider = QtGui.QAction(parent_menu)
     divider.setSeparator(True)
     parent_menu.addAction(divider)
     return divider
Example #8
0
    def process_result(self, result):
        """
        Process list of tasks retrieved by get_data on the main thread
        
        :param result:  Dictionary containing the various display & grouping options required to build the
                        file list as well as the list of files organised by task.        
        """
        if FileListView.DEBUG_GET_DATA_IN_MAIN_THREAD:
            # gathering of data was not done in the get_data stage so we
            # should do it here instead - this method gets called in the 
            # main thread
            result = self._get_data(result)
        
        task_groups = result["task_groups"]
        task_name_order = result["task_name_order"]
        task_order = result["task_order"]
        current_task_name = result["current_task_name"]
        self._current_filter = result["filter"]
        
        self._update_title()
        
        if not task_groups:
            # build a useful error message using the info we have available:
            msg = ""
            if not result["can_change_work_area"]:
                if not result["have_valid_workarea"]:
                    msg = "The current Work Area is not valid!"
                elif not result["have_valid_configuration"]:
                    msg = ("Shotgun File Manager has not been configured for the environment "
                           "being used by the selected Work Area!")
                elif not result["can_do_new_file"]:
                    msg = "Couldn't find any files in this Work Area!"
                else:
                    msg = "Couldn't find any files!\nClick the New file button to start work."
            else:
                if not result["have_valid_workarea"]:
                    msg = "The current Work Area is not valid!"
                elif not result["have_valid_configuration"]:
                    msg = ("Shotgun File Manager has not been configured for the environment "
                           "being used by the selected Work Area!\n"
                           "Please choose a different Work Area to continue.")
                elif not result["can_do_new_file"]:
                    msg = "Couldn't find any files in this Work Area!\nTry selecting a different Work Area."
                else:
                    msg = "Couldn't find any files!\nClick the New file button to start work."
            self.set_message(msg)
            return
        
        for task_name in task_order:
            name_groups = task_groups[task_name]
        
            if (len(task_groups) > 1 
                or (task_name != current_task_name
                    and task_name != FileListView.NO_TASK_NAME 
                    and current_task_name == None)):
                # add header for task:
                h = self.add_item(browser_widget.ListHeader)
                h.set_title("%s" % (task_name))
            
            ordered_names = task_name_order[task_name]
            for name in ordered_names:
                details = name_groups[name]
                
                files = details["files"]
                highest_local_file = details.get("highest_local_file")
                highest_publish_file = details.get("highest_publish_file")
                thumbnail = details["thumbnail"]
                
                # add new item to list:
                item = self._add_file_item(highest_publish_file, highest_local_file)
                if not item:
                    continue
                
                # set thumbnail if have one:
                if thumbnail:
                    item.set_thumbnail(thumbnail)
                
                # add context menu:
                item.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)

                # if it's a publish then add 'View In Shotgun' item:
                if highest_publish_file:
                    action = QtGui.QAction("View latest Publish in Shotgun", item)
                    # (AD) - the '[()]' syntax in action.triggered[()].connect looks weird right!
                    # 'triggered' is a QtCore.SignalInstance which actually defines multiple
                    # signals: triggered() & triggered(bool).  PySide will correctly determine which
                    # one to use but PyQt gets confused and calls the (bool) version instead which
                    # causes problems for us...  Luckily, Qt lets us use the argument list () to 
                    # index into the SignalInstance object to force the use of the non-bool version - yay!
                    action.triggered[()].connect(lambda f=highest_publish_file: self._on_show_in_shotgun_action_triggered(f))
                    item.addAction(action)

                # build context menu for all publish versions:                
                published_versions = [f.version for f in files.values() if f.is_published and isinstance(f.version, int)]
                if published_versions:
                    
                    published_versions.sort(reverse=True)
                    
                    publishes_action = QtGui.QAction("Open Publish Read-Only", item)
                    publishes_sm = QtGui.QMenu(item)
                    publishes_action.setMenu(publishes_sm)
                    item.addAction(publishes_action)    
                     
                    for v in published_versions[:20]:
                        f = files[v]
                        msg = ("v%03d" % f.version)
                        action = QtGui.QAction(msg, publishes_sm)
                        # see above for explanation of [()] syntax in action.triggered[()].connect...
                        action.triggered[()].connect(lambda f=f: self._on_open_publish_action_triggered(f))
                        publishes_sm.addAction(action)
                     
                # build context menu for all work files:
                wf_versions = [f.version for f in files.values() if f.is_local and isinstance(f.version, int)]
                if wf_versions:
                    
                    wf_versions.sort(reverse=True)
                    
                    wf_action = QtGui.QAction("Open Work File", item)
                    wf_sm = QtGui.QMenu(item)
                    wf_action.setMenu(wf_sm)
                    item.addAction(wf_action)    
                     
                    for v in wf_versions[:20]:
                        f = files[v]
                        msg = ("v%03d" % f.version)
                        action = QtGui.QAction(msg, wf_sm)
                        # see above for explanation of [()] syntax in action.triggered[()].connect...
                        action.triggered[()].connect(lambda f=f: self._on_open_workfile_action_triggered(f))
                        wf_sm.addAction(action)                
    def trigger_register_command(self, name, properties, groups):
        """ GUI side handler for the add_command call. """
        from tank.platform.qt import QtGui

        logger.debug("register_command(%s, %s)", name, properties)

        command_type = properties.get("type")
        command_icon = properties.get("icon")
        command_tooltip = properties.get("description")
        command_group = properties.get("group")
        command_is_menu_default = properties.get("group_default") or False

        icon = None
        if command_icon is not None:
            # Only register an icon for the command if it exists.
            if os.path.exists(command_icon):
                icon = QtGui.QIcon(command_icon)
            else:
                logger.error("Icon for command '%s' not found: '%s'" %
                             (name, command_icon))

        title = properties.get("title", name)

        if command_type == "context_menu":
            # Add the command to the project menu
            action = QtGui.QAction(self.desktop_window)
            if icon is not None:
                action.setIcon(icon)
            if command_tooltip is not None:
                action.setToolTip(command_tooltip)
            action.setText(title)

            def action_triggered():
                # make sure to pass in that we are not expecting a response
                # Especially for the engine restart command, the connection
                # itself gets reset and so there isn't a channel to get a
                # response back.
                self.refresh_user_credentials()
                self.site_comm.call_no_response("trigger_callback",
                                                "__commands", name)

            action.triggered.connect(action_triggered)
            self.desktop_window.add_to_project_menu(action)
        else:
            # Default is to add an icon/label for the command

            # figure out what the button should be labeled
            # default is that the button has no menu and is labeled
            # the display name of the command
            menu_name = None
            button_name = title
            found_collapse_match = False

            # First check for collapse rules specified for this title in the desktop
            # configuration. These take precedence over the group property.
            for collapse_rule in self._collapse_rules:
                template = DisplayNameTemplate(collapse_rule["match"])
                match = template.match(title)
                if match is not None:
                    logger.debug("matching %s against %s" %
                                 (title, collapse_rule["match"]))
                    if collapse_rule["menu_label"] == "None":
                        menu_name = None
                    else:
                        menu_name = string.Template(
                            collapse_rule["menu_label"]).safe_substitute(match)
                    button_name = string.Template(
                        collapse_rule["button_label"]).safe_substitute(match)
                    found_collapse_match = True
                    break

            # If no collapse rules were found for this title, and the group property is
            # not empty, treat the specified group as if it were a collapse rule.
            if not found_collapse_match and command_group:
                button_name = command_group
                menu_name = title

            self.desktop_window.add_project_command(name, button_name,
                                                    menu_name, icon,
                                                    command_tooltip, groups,
                                                    command_is_menu_default)
    def process_result(self, result):

        entity_data = result["associated_entity"]
        tasks = result["tasks"]

        entity_str = "%s %s" % (entity_data.get(
            "type", "Unknonwn"), entity_data.get("code", "Unknonwn"))

        if len(tasks) == 0:
            self.set_message("No Tasks found for %s!" % entity_str)

        else:
            i = self.add_item(browser_widget.ListHeader)

            i.set_title("Tasks for %s" % entity_str)
            for d in tasks:
                i = self.add_item(browser_widget.ListItem)

                details = []
                details.append("<b>Task: %s</b>" % d.get("content", ""))

                details.append("Status: %s" % d.get("sg_status_list"))

                names = [
                    x.get("name", "Unknown")
                    for x in d.get("task_assignees", [])
                ]
                names_str = ", ".join(names)
                details.append("Assigned to: %s" % names_str)

                i.set_details("<br>".join(details))

                i.sg_data = d
                i.setToolTip("Double click to set context.")

                # add a grab task action
                if self._current_user:  # not None
                    if d.get("task_assignees"):
                        assigned_user_ids = [
                            x["id"] for x in d.get("task_assignees")
                        ]
                    else:
                        assigned_user_ids = []

                    if self._current_user["id"] not in assigned_user_ids:
                        # are are not assigned to this task. add ability to grab it
                        i.grab_action = QtGui.QAction(
                            "Add %s as an assignee to this task." %
                            self._current_user["name"], i)
                        i.grab_action.triggered.connect(self.grab_task)
                        i.addAction(i.grab_action)
                        i.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)

                # finally look up the thumbnail for the first user assigned to the task
                task_assignees = d.get("task_assignees", [])
                if len(task_assignees) > 0:
                    user_id = task_assignees[0]["id"]
                    # is this user id in our users dict? In that case we have their thumb!
                    for u in result["users"]:
                        if u["id"] == user_id:
                            # if they have a thumb, assign!
                            if u.get("image"):
                                i.set_thumbnail(u.get("image"))
                            break
    def build_status_menu(self, project_entity=None):

        # might as well make the menu if its not there yet.
        if not self._status_menu:
            self._status_menu = shotgun_menus.ShotgunMenu(
                self._tray_frame.status_filter_button,
            )
            self._status_menu.setObjectName("status_menu")
            self._tray_frame.status_filter_button.setMenu(self._status_menu)

            # Sadly, because this button didn't have a menu at the time that
            # the app-level styling was applied, it won't inherit menu-indicator
            # styling there. We have to set it here as a result.
            self._tray_frame.status_filter_button.setStyleSheet(
                """QPushButton::menu-indicator {
                        image: url(:tk-rv-shotgunreview/arrow.png);
                        subcontrol-position: right center;
                        subcontrol-origin: padding;
                        width: 10px;
                        right: -2px;
                        top: -1px;
                    }
                """
            )       
            self._status_menu.triggered.connect(self.handle_status_menu)

        # theres nothing we can do without getting a project entity.
        if not project_entity:
            self._engine.log_warning('no project entity set.')
            return

        # no need to rebuild if its the same project
        if self._project_entity:
            if project_entity['id'] == self._project_entity['id']:
                return

        # we have a new project! build the menu for reals.
        self._project_entity = project_entity
        menu = self._status_menu
        menu.clear()
        action = QtGui.QAction(self._tray_frame.status_filter_button)
        action.setCheckable(True)
        action.setChecked(False)
        action.setText('Any Status')
        action.setData(None)
        menu.add_group([action])
        self._status_reload = False

        statii = self.get_status_menu(self._project_entity)
        count = 0
        name = None
        actions = []

        for status in statii:
            action = QtGui.QAction(self._tray_frame.status_filter_button)
            action.setCheckable(True)
            for x in status:
                action.setText(status[x])
                if x in self._rv_mode._prefs.status_filter:
                    action.setChecked(True)
                    count = count + 1
                    name = status[x]
            action.setData(status)
            actions.append(action)

        menu.add_group(actions)

        if count == 0:
            self._tray_frame.status_filter_button.setText("Filter by Status")
        if count == 1:
            self._tray_frame.status_filter_button.setText(name)
        if count > 1:
            self._tray_frame.status_filter_button.setText("%d Statuses" % count)
    def create_related_cuts_from_models(self):
        if not self._related_cuts_menu and not self._target_entity:
            self._related_cuts_menu = shotgun_menus.ShotgunMenu(
                self._tray_frame.tray_button_browse_cut,
            )
            self._related_cuts_menu.setObjectName("related_cuts_menu")
            self._tray_frame.tray_button_browse_cut.setMenu(self._related_cuts_menu)        
 
            # Sadly, because this button didn't have a menu at the time that
            # the app-level styling was applied, it won't inherit menu-indicator
            # styling there. We have to set it here as a result.
            self._tray_frame.tray_button_browse_cut.setStyleSheet(
                """QPushButton::menu-indicator {
                        image: url(:tk-rv-shotgunreview/arrow.png);
                        subcontrol-position: right center;
                        subcontrol-origin: padding;
                        width: 10px;
                        right: -2px;
                        top: -1px;
                    }
                """
            )
            self._related_cuts_menu.triggered.connect(self.handle_related_menu)
        else:
            # if we have a menu, and target_entity is None, then we are moving from
            # cut to cut. if no menu and there is a target, then its a version or
            # playlist so we can bail.
            if not self._related_cuts_menu:
                return

        seq_data = self._rv_mode.sequence_data_from_session()
        cut_id = seq_data["target_entity"]["ids"][0] if seq_data else None
        
        self._engine.log_debug("create_related_cuts_from_models, cut_id: %r" % cut_id)

        seq_cuts = self.merge_rel_models_for_menu()
 
        if seq_cuts == self._last_related_cuts:
            actions = self._related_cuts_menu.actions()
            for a in actions:
                a.setChecked(False)
                x = a.data()
                if x:
                    if x['id'] == cut_id:
                        a.setChecked(True)

                if a.menu(): # as in a sub-menu
                    a.setChecked(False)
                    sub_acts = a.menu().actions()
                    for b in sub_acts:
                        b.setChecked(False)
                        bd = b.data()
                        if bd['id'] == cut_id:
                            b.setChecked(True)
                            a.setChecked(True)
            self._engine.log_debug("create_related_cuts_from_models, updating check marks only, %d" % len(seq_cuts) )

            return

        self._last_related_cuts = seq_cuts
        self._related_cuts_menu.clear()

        menu = self._related_cuts_menu
        # action = QtGui.QAction(self._tray_frame.tray_button_browse_cut)
        # action.setText('Related Cuts')
        # menu.addAction(action)
        # menu.addSeparator()

        last_menu = menu
        parent_menu = None
        last_code = None
        en = {}
        actions = []

        for x in seq_cuts:
            action = QtGui.QAction(self._tray_frame.tray_button_browse_cut)
            action.setCheckable(True)
            action.setChecked(False)
            en['id'] = x['id']
            en['type'] = 'Cut'

            if last_code != x['code']: # this is the first time weve seen this code
                if x['count'] > 1: # make a submenu
                    if last_menu is menu:
                        last_menu = shotgun_menus.ShotgunMenu(x['code'])
                        actions.append(last_menu)
                    else:
                        last_menu = last_menu.addMenu(x['code'])
                    a = last_menu.menuAction()
                    a.setCheckable(True)
                    a.setChecked(False)
                    parent_menu = last_menu
                else:
                    last_menu = menu
                    parent_menu = None
 
            if x['id'] == cut_id:
                action.setChecked(True)
                if parent_menu:
                    a = parent_menu.menuAction()
                    a.setCheckable(True)
                    a.setChecked(True)
            else:
                action.setChecked(False)
 
            if last_menu == menu:
                action.setText(x['code'])
            else:
                action.setText(x['cached_display_name'])

            action.setData(en)
 
            if last_menu is menu:
                actions.append(action)
            else:
                last_menu.addAction(action)
            last_code = x['code']

        menu.add_group(actions, title="Related Cuts")