diff --git a/hooks/tk-softimage_actions.py b/hooks/tk-softimage_actions.py
new file mode 100644
index 00000000..0d8a76b2
--- /dev/null
+++ b/hooks/tk-softimage_actions.py
@@ -0,0 +1,80 @@
+# 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.
+
+"""
+Hook that loads defines all the available actions, broken down by publish type.
+"""
+import sgtk
+
+HookBaseClass = sgtk.get_hook_baseclass()
+
+
+class SoftimageActions(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 = []
+ 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))
diff --git a/info.yml b/info.yml
index 8d8ec06e..d39605d7 100644
--- a/info.yml
+++ b/info.yml
@@ -143,3 +143,4 @@ requires_engine_version:
frameworks:
- {"name": "tk-framework-shotgunutils", "version": "v4.x.x"}
- {"name": "tk-framework-qtwidgets", "version": "v2.x.x"}
+ - {"name": "tk-framework-rdo", 'version': "v0.0.x"}
diff --git a/python/tk_multi_loader/delegate_publish_list.py b/python/tk_multi_loader/delegate_publish_list.py
index b31c51bb..ee984f70 100644
--- a/python/tk_multi_loader/delegate_publish_list.py
+++ b/python/tk_multi_loader/delegate_publish_list.py
@@ -372,7 +372,8 @@ def _format_publish(self, model_index, widget):
small_text = "%s by %s at %s" % (pub_type_str,
author_str,
date_str)
-
+ small_text += "
Description: %s" % sg_data.get("description") or "No description given"
+
# and set a tooltip
tooltip = "Name: %s" % (sg_data.get("code") or "No name given.")
tooltip += "
Path: %s" % ((sg_data.get("path") or {}).get("local_path"))
diff --git a/python/tk_multi_loader/delegate_publish_thumb.py b/python/tk_multi_loader/delegate_publish_thumb.py
index da61b247..7ac9d446 100644
--- a/python/tk_multi_loader/delegate_publish_thumb.py
+++ b/python/tk_multi_loader/delegate_publish_thumb.py
@@ -260,7 +260,7 @@ def _on_before_paint(self, widget, model_index, style_options):
# this is a publish!
# example data:
-
+
# {'code': 'aaa_00010_F004_C003_0228F8_v000.%04d.dpx',
# 'created_at': 1425378837.0,
# 'created_by': {'id': 42, 'name': 'Manne Ohrstrom', 'type': 'HumanUser'},
@@ -299,34 +299,11 @@ def _on_before_paint(self, widget, model_index, style_options):
# 'version.Version.sg_status_list': 'rev',
# 'version_number': 2}
- # get the name (lighting v3)
- name_str = "Unnamed"
- if sg_data.get("name"):
- name_str = sg_data.get("name")
-
- if sg_data.get("version_number"):
- name_str += " v%s" % sg_data.get("version_number")
-
- # now we are tracking whether this item has a unique task/name/type combo
- # or not via the specially injected task_uniqueness boolean.
- # If this is true, that means that this is the only item in the listing
- # with this name/type combo, and we can render its display name on two
- # lines, name first and then type, e.g.:
- # MyScene, v3
- # Maya Render
- #
- # However, there can be multiple *different* tasks which have the same
- # name/type combo - in this case, we want to display the task name too
- # since this is what differentiates the data. In that case we display it:
- # MyScene, v3 (Layout)
- # Maya Render
- #
- if sg_data.get("task_uniqueness") == False and sg_data.get("task") is not None:
- name_str += " (%s)" % sg_data["task"]["name"]
-
+ name_str = self._get_name_string(sg_data)
+
# make this the title of the card
header_text = name_str
-
+
# and set a tooltip
tooltip = "Name: %s" % (sg_data.get("code") or "No name given.")
# Version 012 by John Smith at 2014-02-23 10:34
@@ -366,12 +343,81 @@ def _on_before_paint(self, widget, model_index, style_options):
# std publish - render with a name and a publish type
# main_body v3
# Render
- details_text = shotgun_model.get_sanitized_data(model_index,
- SgLatestPublishModel.PUBLISH_TYPE_NAME_ROLE)
+ details_text = self._get_details_text(sg_data)
-
widget.set_text(header_text, details_text, tooltip)
+ def _get_name_string(self, sg_data):
+ # example data:
+
+ # {'code': 'aaa_00010_F004_C003_0228F8_v000.%04d.dpx',
+ # 'created_at': 1425378837.0,
+ # 'created_by': {'id': 42, 'name': 'Manne Ohrstrom', 'type': 'HumanUser'},
+ # 'created_by.HumanUser.image': 'https://...',
+ # 'description': 'testing testing, 1,2,3',
+ # 'entity': {'id': 1660, 'name': 'aaa_00010', 'type': 'Shot'},
+ # 'id': 1340,
+ # 'image': 'https:...',
+ # 'name': 'aaa_00010, F004_C003_0228F8',
+ # 'path': {'content_type': 'image/dpx',
+ # 'id': 24116,
+ # 'link_type': 'local',
+ # 'local_path': '/mnt/projects...',
+ # 'local_path_linux': '/mnt/projects...',
+ # 'local_path_mac': '/mnt/projects...',
+ # 'local_path_windows': 'z:\\mnt\\projects...',
+ # 'local_storage': {'id': 4,
+ # 'name': 'primary',
+ # 'type': 'LocalStorage'},
+ # 'name': 'aaa_00010_F004_C003_0228F8_v000.%04d.dpx',
+ # 'type': 'Attachment',
+ # 'url': 'file:///mnt/projects...'},
+ # 'project': {'id': 289, 'name': 'Climp', 'type': 'Project'},
+ # 'published_file_type': {'id': 53,
+ # 'name': 'Flame Render',
+ # 'type': 'PublishedFileType'},
+ # 'task': None,
+ # 'task.Task.content': None,
+ # 'task.Task.due_date': None,
+ # 'task.Task.sg_status_list': None,
+ # 'task_uniqueness': False,
+ # 'type': 'PublishedFile',
+ # 'version': {'id': 6697,
+ # 'name': 'aaa_00010_F004_C003_0228F8_v000',
+ # 'type': 'Version'},
+ # 'version.Version.sg_status_list': 'rev',
+ # 'version_number': 2}
+
+ # get the name (lighting v3)
+ name_str = "Unnamed"
+ if sg_data.get("name"):
+ name_str = sg_data.get("name")
+
+ if sg_data.get("version_number"):
+ name_str += " v%s" % sg_data.get("version_number")
+
+ # now we are tracking whether this item has a unique task/name/type combo
+ # or not via the specially injected task_uniqueness boolean.
+ # If this is true, that means that this is the only item in the listing
+ # with this name/type combo, and we can render its display name on two
+ # lines, name first and then type, e.g.:
+ # MyScene, v3
+ # Maya Render
+ #
+ # However, there can be multiple *different* tasks which have the same
+ # name/type combo - in this case, we want to display the task name too
+ # since this is what differentiates the data. In that case we display it:
+ # MyScene, v3 (Layout)
+ # Maya Render
+ #
+ if sg_data.get("task_uniqueness") == False and sg_data.get("task") is not None:
+ name_str += " (%s)" % sg_data["task"]["name"]
+
+ return name_str
+
+ def _get_details_text(self, sg_data):
+ return ""
+
def sizeHint(self, style_options, model_index):
"""
Specify the size of the item.
@@ -384,3 +430,4 @@ def sizeHint(self, style_options, model_index):
return PublishThumbWidget.calculate_size(scale_factor)
+
diff --git a/python/tk_multi_loader/dialog.py b/python/tk_multi_loader/dialog.py
index 8edf9368..8e062703 100644
--- a/python/tk_multi_loader/dialog.py
+++ b/python/tk_multi_loader/dialog.py
@@ -33,6 +33,9 @@
overlay_widget = sgtk.platform.import_framework("tk-framework-qtwidgets", "overlay_widget")
task_manager = sgtk.platform.import_framework("tk-framework-shotgunutils", "task_manager")
+app = sgtk.platform.current_bundle()
+rdo_fw_path = app.frameworks.get("tk-framework-rdo").disk_location
+
ShotgunModelOverlayWidget = overlay_widget.ShotgunModelOverlayWidget
class AppDialog(QtGui.QWidget):
@@ -173,7 +176,8 @@ def __init__(self, action_manager, parent=None):
self._publish_model.cache_loaded.connect(self._on_publish_content_change)
self._publish_model.data_refreshed.connect(self._on_publish_content_change)
self._publish_proxy_model.filter_changed.connect(self._on_publish_content_change)
-
+ self._publish_model.data_refreshed.connect(self._on_status_filter_change)
+ self._publish_proxy_model.filter_changed.connect(self._on_status_filter_change)
# hook up view -> proxy model -> model
self.ui.publish_view.setModel(self._publish_proxy_model)
@@ -184,7 +188,11 @@ def __init__(self, action_manager, parent=None):
self._publish_list_delegate = SgPublishListDelegate(self.ui.publish_view, self._action_manager)
# recall which the most recently mode used was and set that
- main_view_mode = self._settings_manager.retrieve("main_view_mode", self.MAIN_VIEW_THUMB)
+ default_view_mode = self.MAIN_VIEW_THUMB
+ # Check value of display_thumbnail in app settings
+ if not sgtk.platform.current_bundle().get_setting("display_thumbnail"):
+ default_view_mode = self.MAIN_VIEW_LIST
+ main_view_mode = self._settings_manager.retrieve("main_view_mode", default_view_mode)
self._set_main_view_mode(main_view_mode)
# whenever the type list is checked, update the publish filters
@@ -206,29 +214,17 @@ def __init__(self, action_manager, parent=None):
self.ui.publish_view.addAction(self._refresh_action)
self.ui.publish_view.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
- #################################################
- # popdown publish filter widget for the main view
- # note:
- # we parent the widget to a frame that flows around the
- # main publish area - this is in order to avoid a scenario
- # where the overlay that sometimes pops up on top of the
- # publish area and the search widget would be competing
- # for the same z-index. The result in some of these cases
- # is that the search widget is hidden under the "publishes
- # not found" overlay. By having it parented to the frame
- # instead, it will always be above the overlay.
- self._search_widget = SearchWidget(self.ui.publish_frame)
- # hook it up with the search button the main toolbar
- self.ui.search_publishes.clicked.connect(self._on_publish_filter_clicked)
- # hook it up so that it signals the publish proxy model whenever the filter changes
- self._search_widget.filter_changed.connect(self._publish_proxy_model.set_search_query)
-
#################################################
# checkboxes, buttons etc
self.ui.show_sub_items.toggled.connect(self._on_show_subitems_toggled)
self.ui.check_all.clicked.connect(self._publish_type_model.select_all)
self.ui.check_none.clicked.connect(self._publish_type_model.select_none)
+
+ #################################################
+ # filter status
+ self._filter_status = QtGui.QComboBox()
+ self._sg_type_ids = None
#################################################
# thumb scaling
@@ -276,6 +272,18 @@ def __init__(self, action_manager, parent=None):
show_details = self._settings_manager.retrieve("show_details", False)
self._set_details_pane_visiblity(show_details)
+ # Add rdo custom UI
+ hlayout = QtGui.QHBoxLayout()
+ spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+ self.ui.middle_area.insertLayout(1, hlayout,)
+ self._add_rdo_status_filter(hlayout)
+ hlayout.addItem(spacerItem)
+
+ # Publishes search widget
+ self._search_widget = SearchWidget(self.ui.publish_view, self.ui.publish_frame)
+ hlayout.addWidget(self._search_widget)
+ self._search_widget.filter_changed.connect(self._publish_proxy_model.set_search_query)
+
# trigger an initial evaluation of filter proxy model
self._apply_type_filters_on_publishes()
@@ -417,17 +425,6 @@ def _on_history_double_clicked(self, model_index):
if default_action:
default_action.trigger()
- def _on_publish_filter_clicked(self):
- """
- Executed when someone clicks the filter button in the main UI
- """
- if self.ui.search_publishes.isChecked():
- self.ui.search_publishes.setIcon(QtGui.QIcon(QtGui.QPixmap(":/res/search_active.png")))
- self._search_widget.enable()
- else:
- self.ui.search_publishes.setIcon(QtGui.QIcon(QtGui.QPixmap(":/res/search.png")))
- self._search_widget.disable()
-
def _on_thumbnail_mode_clicked(self):
"""
Executed when someone clicks the thumbnail mode button
@@ -452,7 +449,9 @@ def _set_main_view_mode(self, mode):
self.ui.thumbnail_mode.setIcon(QtGui.QIcon(QtGui.QPixmap(":/res/mode_switch_thumb.png")))
self.ui.thumbnail_mode.setChecked(False)
self.ui.publish_view.setViewMode(QtGui.QListView.ListMode)
- self.ui.publish_view.setItemDelegate(self._publish_list_delegate)
+ self.ui.publish_view.setItemDelegate(self._publish_list_delegate)
+ self.ui.thumb_scale.setVisible(False)
+ self.ui.label_2.setVisible(False)
elif mode == self.MAIN_VIEW_THUMB:
self.ui.list_mode.setIcon(QtGui.QIcon(QtGui.QPixmap(":/res/mode_switch_card.png")))
@@ -461,6 +460,8 @@ def _set_main_view_mode(self, mode):
self.ui.thumbnail_mode.setChecked(True)
self.ui.publish_view.setViewMode(QtGui.QListView.IconMode)
self.ui.publish_view.setItemDelegate(self._publish_thumb_delegate)
+ self.ui.thumb_scale.setVisible(True)
+ self.ui.label_2.setVisible(True)
else:
raise TankError("Undefined view mode!")
@@ -744,6 +745,7 @@ def _on_home_clicked(self):
# first, try to find the "home" item by looking at the current app context.
found_preset = None
found_item = None
+ found_model = None
# get entity portion of context
ctx = sgtk.platform.current_bundle().context
@@ -757,17 +759,26 @@ def _on_home_clicked(self):
# now see if our context object also exists in the tree of this profile
model = self._entity_presets[p].model
+
item = model.item_from_entity(ctx.entity["type"], ctx.entity["id"])
if item is not None:
# find an absolute match! Break the search.
found_item = item
+ found_model = model
break
if found_preset is None:
# no suitable item found. Use the first tab
found_preset = self.ui.entity_preset_tabs.tabText(0)
+ if found_model and found_item:
+ # store current selection data in case model is refreshed. If this happens selection will be lost
+ # and user will have to press home again. This data will be used in SgEntityModel to restore
+ # selection after new data is fed into model.
+ found_model.reSelector['entity'] = found_item.get_sg_data()
+ found_model.reSelector['found_preset'] = found_preset
+ found_model.reSelector['sel_func'] = self._select_item_in_entity_tree
# select it in the left hand side tree view
self._select_item_in_entity_tree(found_preset, found_item)
@@ -801,9 +812,17 @@ def _apply_type_filters_on_publishes(self):
# go through and figure out which checkboxes are clicked and then
# update the publish proxy model so that only items of that type
# is displayed
- sg_type_ids = self._publish_type_model.get_selected_types()
+ self._sg_type_ids = self._publish_type_model.get_selected_types()
show_folders = self._publish_type_model.get_show_folders()
- self._publish_proxy_model.set_filter_by_type_ids(sg_type_ids, show_folders)
+ self._publish_proxy_model.set_filter_by_type_ids(self._sg_type_ids, show_folders)
+
+
+ def apply_status_filters_on_publishes(self):
+ """
+ Executed when the type listing changes
+ """
+ chosen_status = self._filter_status.currentText()
+ self._publish_proxy_model.set_filter_by_status(chosen_status)
########################################################################################
# publish view
@@ -1122,37 +1141,6 @@ def _load_entity_presets(self):
hlayout = QtGui.QHBoxLayout()
layout.addLayout(hlayout)
- # add search textfield
- search = QtGui.QLineEdit(tab)
- search.setStyleSheet("QLineEdit{ border-width: 1px; "
- "background-image: url(:/res/search.png);"
- "background-repeat: no-repeat;"
- "background-position: center left;"
- "border-radius: 5px; "
- "padding-left:20px;"
- "margin:4px;"
- "height:22px;"
- "}")
- search.setToolTip("Use the search field to narrow down the items displayed in the tree above.")
-
- try:
- # this was introduced in qt 4.7, so try to use it if we can... :)
- search.setPlaceholderText("Search...")
- except:
- pass
-
- hlayout.addWidget(search)
-
- # and add a cancel search button, disabled by default
- clear_search = QtGui.QToolButton(tab)
- icon = QtGui.QIcon()
- icon.addPixmap(QtGui.QPixmap(":/res/clear_search.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
- clear_search.setIcon(icon)
- clear_search.setAutoRaise(True)
- clear_search.clicked.connect( lambda editor=search: editor.setText("") )
- clear_search.setToolTip("Click to clear your current search.")
- hlayout.addWidget(clear_search)
-
# set up data backend
model = SgEntityModel(self,
sg_entity_type,
@@ -1180,8 +1168,6 @@ def _load_entity_presets(self):
self._dynamic_widgets.extend( [tab,
layout,
hlayout,
- search,
- clear_search,
view,
overlay,
action_ea,
@@ -1191,9 +1177,14 @@ def _load_entity_presets(self):
# set up proxy model that we connect our search to
proxy_model = SgEntityProxyModel(self)
proxy_model.setSourceModel(model)
- search.textChanged.connect(lambda text, v=view, pm=proxy_model: self._on_search_text_changed(text, v, pm) )
- self._dynamic_widgets.extend([model, proxy_model])
+ # add search textfield
+ search = SearchWidget(view, tab)
+ hlayout.addWidget(search)
+# search.textChanged.connect(lambda text, v=view, pm=proxy_model: self._on_search_text_changed(text, v, pm) )
+ search.filter_changed.connect(proxy_model.setFilterFixedString)
+
+ self._dynamic_widgets.extend([search, model, proxy_model])
# configure the view
view.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
@@ -1228,32 +1219,17 @@ def _load_entity_presets(self):
# data has properly arrived in the model.
self._on_home_clicked()
- def _on_search_text_changed(self, pattern, tree_view, proxy_model):
- """
- Triggered when the text in a search editor changes.
-
- :param pattern: new contents of search box
- :param tree_view: associated tree view.
- :param proxy_model: associated proxy model
- """
-
- # tell proxy model to reevaulate itself given the new pattern.
- proxy_model.setFilterFixedString(pattern)
-
- # change UI decorations based on new pattern.
- if pattern and len(pattern) > 0:
- # indicate with a blue border that a search is active
- tree_view.setStyleSheet("""QTreeView { border-width: 3px;
- border-style: solid;
- border-color: #2C93E2; }
- QTreeView::item { padding: 6px; }
- """)
- # expand all nodes in the tree
- tree_view.expandAll()
- else:
- # revert to default style sheet
- tree_view.setStyleSheet("QTreeView::item { padding: 6px; }")
-
+ def _add_rdo_status_filter(self, hlayout):
+ '''
+ Add a combo box to sort the latest publishes by status
+ Kind of copying off the _add_rdo_status_filter
+ '''
+ filter_label = QtGui.QLabel("Filter by Status")
+ filter_label.setAlignment(QtCore.Qt.AlignLeft|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self._filter_status.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
+ hlayout.addWidget(filter_label)
+ hlayout.addWidget(self._filter_status)
+ self._filter_status.activated.connect(self.apply_status_filters_on_publishes)
def _on_entity_profile_tab_clicked(self):
"""
@@ -1488,6 +1464,30 @@ def _populate_entity_breadcrumbs(self):
self.ui.entity_breadcrumbs.setText("%s" % breadcrumbs)
+ def _on_status_filter_change(self):
+ """
+ Reload the status filter combo box
+ With correct count in brackets
+ """
+ tmp_index = self._filter_status.currentIndex()
+ tmp_index = 0 if tmp_index< 0 else tmp_index
+
+ publish_items = self._publish_model._publish_items
+ self._filter_status.clear()
+
+ valid_ids = self._publish_proxy_model._valid_type_ids
+ valid_items = [item for item in publish_items if item['type_id'] in valid_ids]
+
+
+ self._filter_status.addItem("All (%d)" % len(valid_items))
+ status_list = list(set(item['sg_item']['sg_status_list'] for item in valid_items))
+ for status in status_list:
+ icon = QtGui.QIcon()
+ count = sum(item['sg_item']['sg_status_list'] == status for item in valid_items)
+ name = "%s (%d) " % (status, count)
+ icon.addPixmap(QtGui.QPixmap("%s/resources/%s.png" % (rdo_fw_path, status)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self._filter_status.addItem(icon, name)
+ self._filter_status.setCurrentIndex(tmp_index)
################################################################################################
# Helper stuff
diff --git a/python/tk_multi_loader/model_entity.py b/python/tk_multi_loader/model_entity.py
index f1eb419c..1d61dfb4 100644
--- a/python/tk_multi_loader/model_entity.py
+++ b/python/tk_multi_loader/model_entity.py
@@ -45,22 +45,38 @@ def __init__(self, parent, entity_type, filters, hierarchy, bg_task_manager):
self._entity_icons["Ticket"] = QtGui.QIcon(QtGui.QPixmap(":/res/icon_Ticket_dark.png"))
self._entity_icons["Version"] = QtGui.QIcon(QtGui.QPixmap(":/res/icon_Version_dark.png"))
- ShotgunModel.__init__(self,
+ ShotgunModel.__init__(self,
parent,
- download_thumbs=False,
- schema_generation=4,
- bg_load_thumbs=True,
- bg_task_manager=bg_task_manager)
+ download_thumbs=False,
+ schema_generation=4,
+ bg_load_thumbs=True,
+ bg_task_manager=bg_task_manager)
fields=["image", "sg_status_list", "description"]
self._load_data(entity_type, filters, hierarchy, fields)
-
-
-
-
+
+ # this dict holds entity which was selected in dialog after model was filled. This selection
+ # is result of self._on_home_clicked() executed at last line of _load_entity_presets()
+ # reSelector will be used only once in data_refreshed_cb to restore selection if items in model
+ # were destroyed and created again as result of differences between local shotgun disc cache
+ # and actual shotgun data
+ # see __on_sg_data_arrived(...) in class ShotgunModel
+ self.reSelector = {'entity': None,
+ 'found_preset': None,
+ 'dialog': parent,
+ 'sel_func': None
+ }
+ self.data_refreshed.connect(self.data_refreshed_cb)
############################################################################################
# public methods
-
+
+ def data_refreshed_cb(self):
+ entity = self.reSelector.get('entity', None)
+ if entity:
+ item = self.item_from_entity(entity.get('type'), entity.get('id'))
+ self.reSelector.get('sel_func')(self.reSelector.get('found_preset'), item)
+ self.reSelector['entity'] = None
+
def async_refresh(self):
"""
Trigger an asynchronous refresh of the model
diff --git a/python/tk_multi_loader/model_latestpublish.py b/python/tk_multi_loader/model_latestpublish.py
index 2f959b3f..b5f3db72 100644
--- a/python/tk_multi_loader/model_latestpublish.py
+++ b/python/tk_multi_loader/model_latestpublish.py
@@ -40,7 +40,7 @@ def __init__(self, parent, publish_type_model, bg_task_manager):
self._publish_type_model = publish_type_model
self._folder_icon = QtGui.QIcon(QtGui.QPixmap(":/res/folder_512x400.png"))
self._loading_icon = QtGui.QIcon(QtGui.QPixmap(":/res/loading_512x400.png"))
-
+ self._publish_items = []
self._associated_items = {}
app = sgtk.platform.current_bundle()
@@ -445,50 +445,33 @@ def _before_data_processing(self, sg_data_list):
# but with different tasks, indicate this with a special boolean flag
unique_data = {}
- name_type_aggregates = defaultdict(int)
for sg_item in sg_data_list:
-
+
# get the associated type
type_id = None
type_link = sg_item[self._publish_type_field]
if type_link:
type_id = type_link["id"]
- # also get the associated task
- task_id = None
- task_link = sg_item["task"]
- if task_link:
- task_id = task_link["id"]
-
# key publishes in dict by type and name
- unique_data[ (sg_item["name"], type_id, task_id) ] = {"sg_item": sg_item, "type_id": type_id}
+ unique_data[ (sg_item["name"], type_id) ] = {"sg_item": sg_item, "type_id": type_id}
- # count how many items of this type we have
- name_type_aggregates[ (sg_item["name"], type_id) ] += 1
# SECOND PASS
# We now have the latest versions only
# Go ahead count types for the aggregate
# and assemble filtered sg data set
new_sg_data = []
+
+ self._publish_items = []
for second_pass_data in unique_data.values():
# get the shotgun data for this guy
sg_item = second_pass_data["sg_item"]
-
- # now add a flag to indicate if this item is "task unique" or not
- # e.g. if there are other items in the listing with the same name
- # and same type but with a different task
- if name_type_aggregates[ (sg_item["name"], second_pass_data["type_id"]) ] > 1:
- # there are more than one item with this same name/type combo!
- sg_item["task_uniqueness"] = False
- else:
- # no other item with this task/name/type combo
- sg_item["task_uniqueness"] = True
-
# append to new sg data
new_sg_data.append(sg_item)
+ self._publish_items.append(second_pass_data)
# update our aggregate counts for the publish type view
type_id = second_pass_data["type_id"]
@@ -500,6 +483,3 @@ def _before_data_processing(self, sg_data_list):
return new_sg_data
-
-
-
diff --git a/python/tk_multi_loader/model_publishhistory.py b/python/tk_multi_loader/model_publishhistory.py
index 80acdb3f..1f6163cb 100644
--- a/python/tk_multi_loader/model_publishhistory.py
+++ b/python/tk_multi_loader/model_publishhistory.py
@@ -65,10 +65,8 @@ def load_data(self, sg_data):
# when we filter out which other publishes are associated with this one,
# to effectively get the "version history", we look for items
# which have the same project, same entity assocation, same name, same type
- # and the same task.
filters = [ ["project", "is", sg_data["project"] ],
["name", "is", sg_data["name"] ],
- ["task", "is", sg_data["task"] ],
["entity", "is", sg_data["entity"] ],
[publish_type_field, "is", sg_data[publish_type_field] ],
]
diff --git a/python/tk_multi_loader/model_publishtype.py b/python/tk_multi_loader/model_publishtype.py
index d3ed9d7c..d613ec50 100644
--- a/python/tk_multi_loader/model_publishtype.py
+++ b/python/tk_multi_loader/model_publishtype.py
@@ -156,8 +156,7 @@ def get_selected_types(self):
type_ids.extend(associated_sg_ids)
return type_ids
-
-
+
def set_active_types(self, type_aggregates):
"""
Specifies which types are currently active. Also adjust the sort role,
@@ -186,7 +185,7 @@ def set_active_types(self, type_aggregates):
for type_id in sg_type_ids:
if type_id in type_aggregates:
total_matches += type_aggregates[type_id]
-
+
if total_matches > 0:
# there are matches for this publish type! Add it to the active section
# of the filter list.
diff --git a/python/tk_multi_loader/proxymodel_latestpublish.py b/python/tk_multi_loader/proxymodel_latestpublish.py
index e7811cd1..86d6a403 100644
--- a/python/tk_multi_loader/proxymodel_latestpublish.py
+++ b/python/tk_multi_loader/proxymodel_latestpublish.py
@@ -28,6 +28,7 @@ def __init__(self, parent):
self._valid_type_ids = None
self._show_folders = True
self._search_filter = ""
+ self._valid_statuses = None
def set_search_query(self, search_filter):
"""
@@ -48,6 +49,12 @@ def set_filter_by_type_ids(self, type_ids, show_folders):
# tell model to repush data
self.invalidateFilter()
self.filter_changed.emit()
+
+ def set_filter_by_status(self, statuses):
+ self._valid_statuses = statuses.split(' (')[0]
+ self.invalidateFilter()
+ self.filter_changed.emit()
+
def filterAcceptsRow(self, source_row, source_parent_idx):
"""
@@ -56,7 +63,26 @@ def filterAcceptsRow(self, source_row, source_parent_idx):
This will check each row as it is passing through the proxy
model and see if we should let it pass or not.
"""
+ # get the search filter, as specified via setFilterFixedString()
+ search_exp = self.filterRegExp()
+ search_exp.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
+
+ model = self.sourceModel()
+ current_item = model.invisibleRootItem().child(source_row) # assume non-tree structure
+ sg_data = current_item.get_sg_data()
+ publish_name = ''
+ if sg_data:
+ publish_name = sg_data.get('name')
+ publish_status = sg_data.get('sg_status_list')
+
+ is_folder = current_item.data(SgLatestPublishModel.IS_FOLDER_ROLE)
+
+ # See if status matches
+ if self._valid_statuses not in (None, 'All'):
+ if publish_status != self._valid_statuses:
+ return False
+
if self._valid_type_ids is None:
# accept all!
return True
diff --git a/python/tk_multi_loader/search_widget.py b/python/tk_multi_loader/search_widget.py
index ba557bf2..45108a43 100644
--- a/python/tk_multi_loader/search_widget.py
+++ b/python/tk_multi_loader/search_widget.py
@@ -10,8 +10,7 @@
import sgtk
from sgtk.platform.qt import QtCore, QtGui
-from .ui.search_widget import Ui_SearchWidget
-from .utils import ResizeEventFilter
+
class SearchWidget(QtGui.QWidget):
"""
@@ -24,112 +23,88 @@ class SearchWidget(QtGui.QWidget):
You can connect to the filter_changed signal to get notified whenever the search
string is changed.
"""
-
- # widget positioning offsets, relative to their parent widget
- LEFT_SIDE_OFFSET = 88
- TOP_OFFSET = 10
-
+
# signal emitted whenever the search filter changes
filter_changed = QtCore.Signal(str)
- def __init__(self, parent):
+ def __init__(self, view, parent):
"""
Constructor
:param parent: QT parent object
"""
QtGui.QWidget.__init__(self, parent)
+ self.view = view
- # make invisible by default
- self.setVisible(False)
-
- # set up the UI
- self._ui = Ui_SearchWidget()
- self._ui.setupUi(self)
-
- # now grab the default background color and use that
- # in addition to that, apply the same styling that the search
- # bar in the tree view is using.
- p = QtGui.QPalette()
- bg_col = p.color(QtGui.QPalette.Active, QtGui.QPalette.Window)
-
- style = """
- QGroupBox
- {
- background-color: rgb(%s, %s, %s);
- border-style: none;
- border-top-left-radius: 0px;
- border-top-right-radius: 0px;
- border-bottom-left-radius: 10px;
- border-bottom-right-radius: 10px;
- }
-
- QLineEdit
- {
- border-width: 1px;
- background-image: url(:/res/search.png);
- background-repeat: no-repeat;
- background-position: center left;
- border-radius: 5px;
- padding-left:20px;
- margin:4px;
- height:22px;
- }
- """ % (bg_col.red(), bg_col.green(), bg_col.blue())
-
- self._ui.group.setStyleSheet(style)
-
- # hook up a listener to the parent window so this widget
- # follows along when the parent window changes size
- filter = ResizeEventFilter(parent)
- filter.resized.connect(self._on_parent_resized)
- parent.installEventFilter(filter)
+ # Setup UI
+ self.__setup_ui()
# set up signals and slots
- self._ui.search.textChanged.connect(self._on_filter_changed)
+ self.search.textChanged.connect(self._on_filter_changed)
+
+ def __setup_ui(self):
+ """Setup UI of search widget"""
+ hlayout = QtGui.QHBoxLayout()
+ hlayout.setContentsMargins(0, 0, 0, 0)
+
+ self.search = QtGui.QLineEdit(self.parent())
+ self.search.setStyleSheet("QLineEdit{ border-width: 1px; "
+ "background-image: url(:/res/search.png);"
+ "background-repeat: no-repeat;"
+ "background-position: center left;"
+ "border-radius: 5px; "
+ "padding-left:20px;"
+ "margin:4px;"
+ "height:22px;"
+ "}")
+ self.search.setToolTip("Enter some text to filter the publishes shown in the view below.
\n"
+ "Click the magnifying glass icon above to disable the filter.")
+ try:
+ # this was introduced in qt 4.7, so try to use it if we can... :)
+ self.search.setPlaceholderText("Search...")
+ except:
+ pass
+
+ clear_search = QtGui.QToolButton(self.parent())
+ icon = QtGui.QIcon()
+ icon.addPixmap(QtGui.QPixmap(":/res/clear_search.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ clear_search.setIcon(icon)
+ clear_search.setAutoRaise(True)
+ clear_search.clicked.connect(lambda editor=self.search: editor.setText(""))
+ clear_search.setToolTip("Click to clear your current search.")
+
+ hlayout.addWidget(self.search)
+ hlayout.addWidget(clear_search)
+
+ self.setLayout(hlayout)
def _on_filter_changed(self):
- """
- Callback for when the text changes
-
- :param new_text: The contents of the filter line edit box
- """
- # emit our custom signal
+ """Callback for when the text changes"""
if self.isVisible():
# emit the search text that is in the view
- search_text = self._ui.search.text()
+ search_text = self.search.text()
else:
# widget is hidden - emit empty search text
search_text = ""
-
- self.filter_changed.emit(search_text)
- def disable(self):
- """
- Disable search widget and clear search query.
- """
- # hide and reset the search
- self.setVisible(False)
- self._on_filter_changed()
+ # Get view class
+ view_class = self.view.__class__.__name__
- def enable(self):
- """
- Enable search widget and focus the keyboard input on it.
- """
- self.setVisible(True)
- self._ui.search.setFocus()
- self._on_filter_changed()
-
- def _on_parent_resized(self):
- """
- Special slot hooked up to the event filter.
- When associated widget is resized this slot is being called.
- """
- # offset the position in such a way that it looks like
- # it is "hanging down" from the adjacent window.
- # these constants are purely aesthetic, decided after some
- # tweaking and trial and error.
- self.move(self.parentWidget().width()-self.width()-self.LEFT_SIDE_OFFSET,
- -self.TOP_OFFSET)
+ if search_text and len(search_text) > 0:
+ # indicate with a blue border that a search is active
+ self.view.setStyleSheet("""%s { border-width: 3px;
+ border-style: solid;
+ border-color: #2C93E2; }
+ %s::item { padding: 6px; }
+ """ % (view_class, view_class))
+ # expand all nodes in the tree
+ if isinstance(self.view, QtGui.QTreeView):
+ self.view.expandAll()
+ else:
+ # revert to default style sheet
+ self.view.setStyleSheet("%s::item { padding: 6px; }" % view_class)
+
+ # emit our custom signal
+ self.filter_changed.emit(search_text)
diff --git a/python/tk_multi_loader/ui/dialog.py b/python/tk_multi_loader/ui/dialog.py
index 008040f5..42732eec 100644
--- a/python/tk_multi_loader/ui/dialog.py
+++ b/python/tk_multi_loader/ui/dialog.py
@@ -196,14 +196,6 @@ def setupUi(self, Dialog):
self.label_5.setText("")
self.label_5.setObjectName("label_5")
self.horizontalLayout_2.addWidget(self.label_5)
- self.search_publishes = QtGui.QToolButton(Dialog)
- self.search_publishes.setMinimumSize(QtCore.QSize(0, 26))
- icon3 = QtGui.QIcon()
- icon3.addPixmap(QtGui.QPixmap(":/res/search.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
- self.search_publishes.setIcon(icon3)
- self.search_publishes.setCheckable(True)
- self.search_publishes.setObjectName("search_publishes")
- self.horizontalLayout_2.addWidget(self.search_publishes)
self.info = QtGui.QToolButton(Dialog)
self.info.setMinimumSize(QtCore.QSize(0, 26))
self.info.setObjectName("info")
@@ -372,7 +364,6 @@ def retranslateUi(self, Dialog):
self.thumbnail_mode.setText(QtGui.QApplication.translate("Dialog", "...", None, QtGui.QApplication.UnicodeUTF8))
self.list_mode.setToolTip(QtGui.QApplication.translate("Dialog", "List Mode", None, QtGui.QApplication.UnicodeUTF8))
self.list_mode.setText(QtGui.QApplication.translate("Dialog", "...", None, QtGui.QApplication.UnicodeUTF8))
- self.search_publishes.setToolTip(QtGui.QApplication.translate("Dialog", "Filter Publishes", None, QtGui.QApplication.UnicodeUTF8))
self.info.setToolTip(QtGui.QApplication.translate("Dialog", "Use this button to toggle details on and off. ", None, QtGui.QApplication.UnicodeUTF8))
self.info.setText(QtGui.QApplication.translate("Dialog", "Show Details", None, QtGui.QApplication.UnicodeUTF8))
self.show_sub_items.setToolTip(QtGui.QApplication.translate("Dialog", "Enables the subfolder mode, displaying a total aggregate of all selected items.", None, QtGui.QApplication.UnicodeUTF8))