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