From 640b68b89c49b15027c3d12f28109551aafc9561 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:30:57 +0200 Subject: [PATCH 01/18] initial commit of new tool --- openpype/tools/context_dialog/__init__.py | 10 +++++++++ openpype/tools/context_dialog/window.py | 26 +++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 openpype/tools/context_dialog/__init__.py create mode 100644 openpype/tools/context_dialog/window.py diff --git a/openpype/tools/context_dialog/__init__.py b/openpype/tools/context_dialog/__init__.py new file mode 100644 index 0000000000..9b10baf903 --- /dev/null +++ b/openpype/tools/context_dialog/__init__.py @@ -0,0 +1,10 @@ +from .window import ( + ContextDialog, + main +) + + +__all__ = ( + "ContextDialog", + "main" +) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py new file mode 100644 index 0000000000..492384ee42 --- /dev/null +++ b/openpype/tools/context_dialog/window.py @@ -0,0 +1,26 @@ +from Qt import QtWidgets, QtCore, QtGui + + +class ContextDialog(QtWidgets.QDialog): + """Dialog to select a context. + + Context has 3 parts: + - Project + - Aseet + - Task + + It is possible to predefine project and asset. In that case their widgets + will have passed preselected values and will be disabled. + """ + + +def main( + path_to_store, + project_name=None, + asset_name=None, + strict=True +): + app = QtWidgets.QApplication([]) + window = ContextDialog() + window.show() + app.exec_() From b23d01793610d6cf8e5a79f5df6991f7186ce4f9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:31:25 +0200 Subject: [PATCH 02/18] added new cli argument 'contextselection' --- openpype/cli.py | 28 ++++++++++++++++++++++++++++ openpype/pype_commands.py | 6 ++++++ 2 files changed, 34 insertions(+) diff --git a/openpype/cli.py b/openpype/cli.py index 8438703bd3..512bd4663b 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -282,6 +282,34 @@ def projectmanager(): PypeCommands().launch_project_manager() +@main.command() +@click.argument("output_path") +@click.option("--project", help="Define project context") +@click.option("--asset", help="Define asset in project (project must be set)") +@click.option( + "--strict", + is_flag=True, + help="Full context must be set otherwise dialog can't be closed." +) +def contextselection( + output_path, + project, + asset, + strict +): + """Show Qt dialog to select context. + + Context is project name, asset name and task name. The result is stored + into json file which path is passed in first argument. + """ + PypeCommands.contextselection( + output_path, + project, + asset, + strict + ) + + @main.command( context_settings=dict( ignore_unknown_options=True, diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index e2869a956d..a850200abe 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -293,6 +293,12 @@ class PypeCommands: project_manager.main() + @staticmethod + def contextselection(output_path, project_name, asset_name, strict): + from openpype.tools.context_dialog import main + + main(output_path, project_name, asset_name, strict) + def texture_copy(self, project, asset, path): pass From 52801768d8a5427716ce8b027fb17ee14e9d4eef Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:32:39 +0200 Subject: [PATCH 03/18] moved task model into tools/utils/models.py --- openpype/tools/utils/constants.py | 6 ++ openpype/tools/utils/models.py | 165 ++++++++++++++++++++++++++++++ openpype/tools/workfiles/app.py | 8 +- openpype/tools/workfiles/model.py | 143 -------------------------- 4 files changed, 176 insertions(+), 146 deletions(-) create mode 100644 openpype/tools/utils/constants.py diff --git a/openpype/tools/utils/constants.py b/openpype/tools/utils/constants.py new file mode 100644 index 0000000000..74883cb522 --- /dev/null +++ b/openpype/tools/utils/constants.py @@ -0,0 +1,6 @@ +from Qt import QtCore + + +TASK_NAME_ROLE = QtCore.Qt.UserRole + 301 +TASK_TYPE_ROLE = QtCore.Qt.UserRole + 302 +TASK_ORDER_ROLE = QtCore.Qt.UserRole + 403 diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index c5e1ce1b12..1bbad8187e 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -8,6 +8,11 @@ from Qt import QtCore, QtGui from avalon.vendor import qtawesome from avalon import style, io from . import lib +from .constants import ( + TASK_ORDER_ROLE, + TASK_TYPE_ROLE, + TASK_NAME_ROLE +) log = logging.getLogger(__name__) @@ -498,3 +503,163 @@ class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): return super( RecursiveSortFilterProxyModel, self ).filterAcceptsRow(row, parent) + + +class TasksModel(QtGui.QStandardItemModel): + """A model listing the tasks combined for a list of assets""" + def __init__(self, dbcon, parent=None): + super(TasksModel, self).__init__(parent=parent) + self.dbcon = dbcon + self._default_icon = qtawesome.icon( + "fa.male", + color=style.colors.default + ) + self._no_tasks_icon = qtawesome.icon( + "fa.exclamation-circle", + color=style.colors.mid + ) + self._cached_icons = {} + self._project_task_types = {} + + self._last_asset_id = None + + self.refresh() + + def refresh(self): + if self.dbcon.Session.get("AVALON_PROJECT"): + self._refresh_task_types() + self.set_asset_id(self._last_asset_id) + else: + self.clear() + + def _refresh_task_types(self): + # Get the project configured icons from database + project = self.dbcon.find_one( + {"type": "project"}, + {"config.tasks"} + ) + tasks = project["config"].get("tasks") or {} + self._project_task_types = tasks + + def _try_get_awesome_icon(self, icon_name): + icon = None + if icon_name: + try: + icon = qtawesome.icon( + "fa.{}".format(icon_name), + color=style.colors.default + ) + + except Exception: + pass + return icon + + def headerData(self, section, orientation, role): + # Show nice labels in the header + if ( + role == QtCore.Qt.DisplayRole + and orientation == QtCore.Qt.Horizontal + ): + if section == 0: + return "Tasks" + + return super(TasksModel, self).headerData(section, orientation, role) + + def _get_icon(self, task_icon, task_type_icon): + if task_icon in self._cached_icons: + return self._cached_icons[task_icon] + + icon = self._try_get_awesome_icon(task_icon) + if icon is not None: + self._cached_icons[task_icon] = icon + return icon + + if task_type_icon in self._cached_icons: + icon = self._cached_icons[task_type_icon] + self._cached_icons[task_icon] = icon + return icon + + icon = self._try_get_awesome_icon(task_type_icon) + if icon is None: + icon = self._default_icon + + self._cached_icons[task_icon] = icon + self._cached_icons[task_type_icon] = icon + + return icon + + def set_asset_id(self, asset_id): + asset_doc = None + if asset_id: + asset_doc = self.dbcon.find_one( + {"_id": asset_id}, + {"data.tasks": True} + ) + self.set_asset(asset_doc) + + def set_asset(self, asset_doc): + """Set assets to track by their database id + + Arguments: + asset_doc (dict): Asset document from MongoDB. + """ + self.clear() + + if not asset_doc: + self._last_asset_id = None + return + + self._last_asset_id = asset_doc["_id"] + + asset_tasks = asset_doc.get("data", {}).get("tasks") or {} + items = [] + for task_name, task_info in asset_tasks.items(): + task_icon = task_info.get("icon") + task_type = task_info.get("type") + task_order = task_info.get("order") + task_type_info = self._project_task_types.get(task_type) or {} + task_type_icon = task_type_info.get("icon") + icon = self._get_icon(task_icon, task_type_icon) + + label = "{} ({})".format(task_name, task_type or "type N/A") + item = QtGui.QStandardItem(label) + item.setData(task_name, TASK_NAME_ROLE) + item.setData(task_type, TASK_TYPE_ROLE) + item.setData(task_order, TASK_ORDER_ROLE) + item.setData(icon, QtCore.Qt.DecorationRole) + item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable) + items.append(item) + + if not items: + item = QtGui.QStandardItem("No task") + item.setData(self._no_tasks_icon, QtCore.Qt.DecorationRole) + item.setFlags(QtCore.Qt.NoItemFlags) + items.append(item) + + self.invisibleRootItem().appendRows(items) + + +class TasksProxyModel(QtCore.QSortFilterProxyModel): + def lessThan(self, x_index, y_index): + x_order = x_index.data(TASK_ORDER_ROLE) + y_order = y_index.data(TASK_ORDER_ROLE) + if x_order is not None and y_order is not None: + if x_order < y_order: + return True + if x_order > y_order: + return False + + elif x_order is None and y_order is not None: + return True + + elif y_order is None and x_order is not None: + return False + + x_name = x_index.data(QtCore.Qt.DisplayRole) + y_name = y_index.data(QtCore.Qt.DisplayRole) + if x_name == y_name: + return True + + if x_name == tuple(sorted((x_name, y_name)))[0]: + return False + return True diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 1679a18241..c1988d8d45 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -14,13 +14,15 @@ from avalon.tools import lib as tools_lib from avalon.tools.widgets import AssetWidget from avalon.tools.delegates import PrettyTimeDelegate -from .model import ( +from openpype.tools.utils.constants import ( TASK_NAME_ROLE, - TASK_TYPE_ROLE, - FilesModel, + TASK_TYPE_ROLE +) +from openpype.tools.utils.model import ( TasksModel, TasksProxyModel ) +from .model import FilesModel from .view import FilesView from openpype.lib import ( diff --git a/openpype/tools/workfiles/model.py b/openpype/tools/workfiles/model.py index 92fbf76b95..583f495606 100644 --- a/openpype/tools/workfiles/model.py +++ b/openpype/tools/workfiles/model.py @@ -9,10 +9,6 @@ from avalon.tools.models import TreeModel, Item log = logging.getLogger(__name__) -TASK_NAME_ROLE = QtCore.Qt.UserRole + 1 -TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2 -TASK_ORDER_ROLE = QtCore.Qt.UserRole + 3 - class FilesModel(TreeModel): """Model listing files with specified extensions in a root folder""" @@ -155,142 +151,3 @@ class FilesModel(TreeModel): return "Date modified" return super(FilesModel, self).headerData(section, orientation, role) - - -class TasksProxyModel(QtCore.QSortFilterProxyModel): - def lessThan(self, x_index, y_index): - x_order = x_index.data(TASK_ORDER_ROLE) - y_order = y_index.data(TASK_ORDER_ROLE) - if x_order is not None and y_order is not None: - if x_order < y_order: - return True - if x_order > y_order: - return False - - elif x_order is None and y_order is not None: - return True - - elif y_order is None and x_order is not None: - return False - - x_name = x_index.data(QtCore.Qt.DisplayRole) - y_name = y_index.data(QtCore.Qt.DisplayRole) - if x_name == y_name: - return True - - if x_name == tuple(sorted((x_name, y_name)))[0]: - return False - return True - - -class TasksModel(QtGui.QStandardItemModel): - """A model listing the tasks combined for a list of assets""" - def __init__(self, dbcon, parent=None): - super(TasksModel, self).__init__(parent=parent) - self.dbcon = dbcon - self._default_icon = qtawesome.icon( - "fa.male", - color=style.colors.default - ) - self._no_tasks_icon = qtawesome.icon( - "fa.exclamation-circle", - color=style.colors.mid - ) - self._cached_icons = {} - self._project_task_types = {} - - self._refresh_task_types() - - def _refresh_task_types(self): - # Get the project configured icons from database - project = self.dbcon.find_one( - {"type": "project"}, - {"config.tasks"} - ) - tasks = project["config"].get("tasks") or {} - self._project_task_types = tasks - - def _try_get_awesome_icon(self, icon_name): - icon = None - if icon_name: - try: - icon = qtawesome.icon( - "fa.{}".format(icon_name), - color=style.colors.default - ) - - except Exception: - pass - return icon - - def headerData(self, section, orientation, role): - # Show nice labels in the header - if ( - role == QtCore.Qt.DisplayRole - and orientation == QtCore.Qt.Horizontal - ): - if section == 0: - return "Tasks" - - return super(TasksModel, self).headerData(section, orientation, role) - - def _get_icon(self, task_icon, task_type_icon): - if task_icon in self._cached_icons: - return self._cached_icons[task_icon] - - icon = self._try_get_awesome_icon(task_icon) - if icon is not None: - self._cached_icons[task_icon] = icon - return icon - - if task_type_icon in self._cached_icons: - icon = self._cached_icons[task_type_icon] - self._cached_icons[task_icon] = icon - return icon - - icon = self._try_get_awesome_icon(task_type_icon) - if icon is None: - icon = self._default_icon - - self._cached_icons[task_icon] = icon - self._cached_icons[task_type_icon] = icon - - return icon - - def set_asset(self, asset_doc): - """Set assets to track by their database id - - Arguments: - asset_doc (dict): Asset document from MongoDB. - """ - self.clear() - - if not asset_doc: - return - - asset_tasks = asset_doc.get("data", {}).get("tasks") or {} - items = [] - for task_name, task_info in asset_tasks.items(): - task_icon = task_info.get("icon") - task_type = task_info.get("type") - task_order = task_info.get("order") - task_type_info = self._project_task_types.get(task_type) or {} - task_type_icon = task_type_info.get("icon") - icon = self._get_icon(task_icon, task_type_icon) - - label = "{} ({})".format(task_name, task_type or "type N/A") - item = QtGui.QStandardItem(label) - item.setData(task_name, TASK_NAME_ROLE) - item.setData(task_type, TASK_TYPE_ROLE) - item.setData(task_order, TASK_ORDER_ROLE) - item.setData(icon, QtCore.Qt.DecorationRole) - item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable) - items.append(item) - - if not items: - item = QtGui.QStandardItem("No task") - item.setData(self._no_tasks_icon, QtCore.Qt.DecorationRole) - item.setFlags(QtCore.Qt.NoItemFlags) - items.append(item) - - self.invisibleRootItem().appendRows(items) From 796f0db8f306ed221e3ac273a84cb1cb16dabcde Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:33:48 +0200 Subject: [PATCH 04/18] define dialog window flags --- openpype/tools/context_dialog/window.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index 492384ee42..3d21bfe84b 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -1,4 +1,5 @@ from Qt import QtWidgets, QtCore, QtGui +from openpype import style class ContextDialog(QtWidgets.QDialog): @@ -12,6 +13,18 @@ class ContextDialog(QtWidgets.QDialog): It is possible to predefine project and asset. In that case their widgets will have passed preselected values and will be disabled. """ + def __init__(self, parent=None): + super(ContextDialog, self).__init__(parent) + + self.setWindowTitle("Select Context") + self.setWindowIcon(QtGui.QIcon(style.app_icon_path())) + + # Enable minimize and maximize for app + window_flags = QtCore.Qt.Window + if not parent: + window_flags |= QtCore.Qt.WindowStaysOnTopHint + self.setWindowFlags(window_flags) + self.setFocusPolicy(QtCore.Qt.StrongFocus) def main( From 95e45d0ced00dfccd4c2ec9fd7225dbfe0e06509 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:35:24 +0200 Subject: [PATCH 05/18] added base for context storing --- openpype/tools/context_dialog/window.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index 3d21bfe84b..8193b8149e 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -1,3 +1,6 @@ +import os +import json + from Qt import QtWidgets, QtCore, QtGui from openpype import style @@ -26,6 +29,16 @@ class ContextDialog(QtWidgets.QDialog): self.setWindowFlags(window_flags) self.setFocusPolicy(QtCore.Qt.StrongFocus) + # Output of dialog + self._context_to_store = { + "project": None, + "asset": None, + "task": None + } + + def get_context(self): + return self._context_to_store + def main( path_to_store, @@ -37,3 +50,12 @@ def main( window = ContextDialog() window.show() app.exec_() + + data = window.get_context() + + file_dir = os.path.dirname(path_to_store) + if not os.path.exists(file_dir): + os.makedirs(file_dir) + + with open(path_to_store, "w") as stream: + json.dump(data, stream) From b37c2497f8e4f70421ea40940e73b296a584015d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:36:26 +0200 Subject: [PATCH 06/18] create mongo connection in initialiazation --- openpype/tools/context_dialog/window.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index 8193b8149e..e6f1b8dec2 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -2,6 +2,8 @@ import os import json from Qt import QtWidgets, QtCore, QtGui +from avalon.api import AvalonMongoDB + from openpype import style @@ -29,6 +31,8 @@ class ContextDialog(QtWidgets.QDialog): self.setWindowFlags(window_flags) self.setFocusPolicy(QtCore.Qt.StrongFocus) + dbcon = AvalonMongoDB() + # Output of dialog self._context_to_store = { "project": None, From c1263a6a4746039dc66d3f3d7818672b6304daa6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:43:12 +0200 Subject: [PATCH 07/18] fix ordering of tasks --- openpype/tools/utils/models.py | 4 ++-- openpype/tools/workfiles/app.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 1bbad8187e..f719495b97 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -661,5 +661,5 @@ class TasksProxyModel(QtCore.QSortFilterProxyModel): return True if x_name == tuple(sorted((x_name, y_name)))[0]: - return False - return True + return True + return False diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index c1988d8d45..0ca5dca650 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -18,7 +18,7 @@ from openpype.tools.utils.constants import ( TASK_NAME_ROLE, TASK_TYPE_ROLE ) -from openpype.tools.utils.model import ( +from openpype.tools.utils.models import ( TasksModel, TasksProxyModel ) @@ -361,6 +361,7 @@ class TasksWidget(QtWidgets.QWidget): self._last_selected_task = current self._tasks_model.set_asset(asset_doc) + self._tasks_proxy.sort(0, QtCore.Qt.AscendingOrder) if self._last_selected_task: self.select_task(self._last_selected_task) From 45ea46ddf68fb781ef0078e8c2c63d915f6a5cc5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:43:27 +0200 Subject: [PATCH 08/18] added copy of project model into utils --- openpype/tools/utils/constants.py | 4 + openpype/tools/utils/models.py | 151 ++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) diff --git a/openpype/tools/utils/constants.py b/openpype/tools/utils/constants.py index 74883cb522..0e940a5595 100644 --- a/openpype/tools/utils/constants.py +++ b/openpype/tools/utils/constants.py @@ -1,6 +1,10 @@ from Qt import QtCore +DEFAULT_PROJECT_LABEL = "< Default >" +PROJECT_NAME_ROLE = QtCore.Qt.UserRole + 101 +PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 102 + TASK_NAME_ROLE = QtCore.Qt.UserRole + 301 TASK_TYPE_ROLE = QtCore.Qt.UserRole + 302 TASK_ORDER_ROLE = QtCore.Qt.UserRole + 403 diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index f719495b97..c488743f36 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -9,6 +9,9 @@ from avalon.vendor import qtawesome from avalon import style, io from . import lib from .constants import ( + PROJECT_IS_ACTIVE_ROLE, + PROJECT_NAME_ROLE, + DEFAULT_PROJECT_LABEL, TASK_ORDER_ROLE, TASK_TYPE_ROLE, TASK_NAME_ROLE @@ -505,6 +508,154 @@ class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): ).filterAcceptsRow(row, parent) +class ProjectModel(QtGui.QStandardItemModel): + def __init__( + self, dbcon=None, only_active=True, add_default_project=False, + *args, **kwargs + ): + super(ProjectModel, self).__init__(*args, **kwargs) + + self.dbcon = dbcon + + self._only_active = only_active + self._add_default_project = add_default_project + + self._default_item = None + self._items_by_name = {} + # Model was at least once refreshed + # - for `set_dbcon` method + self._refreshed = False + + def set_default_project_available(self, available=True): + if available is None: + available = not self._add_default_project + + if self._add_default_project == available: + return + + self._add_default_project = available + if not available and self._default_item is not None: + root_item = self.invisibleRootItem() + root_item.removeRow(self._default_item.row()) + self._default_item = None + + def set_only_active(self, only_active=True): + if only_active is None: + only_active = not self._only_active + + if self._only_active == only_active: + return + + self._only_active = only_active + + if self._refreshed: + self.refresh() + + def set_dbcon(self, dbcon): + """Change mongo connection.""" + self.dbcon = dbcon + # Trigger refresh if was already refreshed + if self._refreshed: + self.refresh() + + def project_name_is_available(self, project_name): + """Check availability of project name in current items.""" + return project_name in self._items_by_name + + def refresh(self): + # Change '_refreshed' state + self._refreshed = True + new_items = [] + # Add default item to model if should + if self._add_default_project and self._default_item is None: + item = QtGui.QStandardItem(DEFAULT_PROJECT_LABEL) + item.setData(None, PROJECT_NAME_ROLE) + item.setData(True, PROJECT_IS_ACTIVE_ROLE) + new_items.append(item) + self._default_item = item + + project_names = set() + if self.dbcon is not None: + for project_doc in self.dbcon.projects( + projection={"name": 1, "data.active": 1}, + only_active=self._only_active + ): + project_name = project_doc["name"] + project_names.add(project_name) + if project_name in self._items_by_name: + item = self._items_by_name[project_name] + else: + item = QtGui.QStandardItem(project_name) + + self._items_by_name[project_name] = item + new_items.append(item) + + is_active = project_doc.get("data", {}).get("active", True) + item.setData(project_name, PROJECT_NAME_ROLE) + item.setData(is_active, PROJECT_IS_ACTIVE_ROLE) + + if not is_active: + font = item.font() + font.setItalic(True) + item.setFont(font) + + root_item = self.invisibleRootItem() + for project_name in tuple(self._items_by_name.keys()): + if project_name not in project_names: + item = self._items_by_name.pop(project_name) + root_item.removeRow(item.row()) + + if new_items: + root_item.appendRows(new_items) + + +class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): + def __init__(self, *args, **kwargs): + super(ProjectSortFilterProxy, self).__init__(*args, **kwargs) + self._filter_enabled = True + + def lessThan(self, left_index, right_index): + if left_index.data(PROJECT_NAME_ROLE) is None: + return True + + if right_index.data(PROJECT_NAME_ROLE) is None: + return False + + left_is_active = left_index.data(PROJECT_IS_ACTIVE_ROLE) + right_is_active = right_index.data(PROJECT_IS_ACTIVE_ROLE) + if right_is_active == left_is_active: + return super(ProjectSortFilterProxy, self).lessThan( + left_index, right_index + ) + + if left_is_active: + return True + return False + + def filterAcceptsRow(self, source_row, source_parent): + index = self.sourceModel().index(source_row, 0, source_parent) + if self._filter_enabled: + result = self._custom_index_filter(index) + if result is not None: + return result + + return super(ProjectSortFilterProxy, self).filterAcceptsRow( + source_row, source_parent + ) + + def _custom_index_filter(self, index): + is_active = bool(index.data(PROJECT_IS_ACTIVE_ROLE)) + + return is_active + + def is_filter_enabled(self): + return self._filter_enabled + + def set_filter_enabled(self, value): + self._filter_enabled = value + self.invalidateFilter() + + class TasksModel(QtGui.QStandardItemModel): """A model listing the tasks combined for a list of assets""" def __init__(self, dbcon, parent=None): From 1277e666a36e670886e95b90bc5e73d76cc50b1a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:46:49 +0200 Subject: [PATCH 09/18] ui initialization --- openpype/tools/context_dialog/window.py | 114 ++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index e6f1b8dec2..b3128ed159 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -5,6 +5,17 @@ from Qt import QtWidgets, QtCore, QtGui from avalon.api import AvalonMongoDB from openpype import style +from openpype.tools.utils.widgets import AssetWidget +from openpype.tools.utils.constants import ( + TASK_NAME_ROLE, + PROJECT_NAME_ROLE +) +from openpype.tools.utils.models import ( + ProjectModel, + ProjectSortFilterProxy, + TasksModel, + TasksProxyModel +) class ContextDialog(QtWidgets.QDialog): @@ -33,6 +44,88 @@ class ContextDialog(QtWidgets.QDialog): dbcon = AvalonMongoDB() + # UI initialization + main_splitter = QtWidgets.QSplitter(self) + + left_side_widget = QtWidgets.QWidget(main_splitter) + + project_combobox = QtWidgets.QComboBox(left_side_widget) + project_delegate = QtWidgets.QStyledItemDelegate(project_combobox) + project_combobox.setItemDelegate(project_delegate) + project_model = ProjectModel( + dbcon, + only_active=True, + add_default_project=False + ) + project_proxy = ProjectSortFilterProxy() + project_proxy.setSourceModel(project_model) + project_combobox.setModel(project_proxy) + + # Assets widget + assets_widget = AssetWidget( + dbcon, multiselection=False, parent=left_side_widget + ) + + left_side_layout = QtWidgets.QVBoxLayout(left_side_widget) + left_side_layout.setContentsMargins(0, 0, 0, 0) + left_side_layout.addWidget(project_combobox) + left_side_layout.addWidget(assets_widget) + + task_view = QtWidgets.QListView(main_splitter) + task_model = TasksModel(dbcon) + task_proxy = TasksProxyModel() + task_proxy.setSourceModel(task_model) + task_view.setModel(task_proxy) + + main_splitter.addWidget(left_side_widget) + main_splitter.addWidget(task_view) + main_splitter.setStretchFactor(0, 7) + main_splitter.setStretchFactor(1, 3) + + ok_btn = QtWidgets.QPushButton("OK", self) + + buttons_layout = QtWidgets.QHBoxLayout() + buttons_layout.setContentsMargins(0, 0, 0, 0) + buttons_layout.addStretch(1) + buttons_layout.addWidget(ok_btn, 0) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.addWidget(main_splitter, 1) + main_layout.addLayout(buttons_layout, 0) + + assets_timer = QtCore.QTimer() + assets_timer.setInterval(50) + assets_timer.setSingleShot(True) + + assets_timer.timeout.connect(self._on_asset_refresh_timer) + + project_combobox.currentIndexChanged.connect( + self._on_project_combo_change + ) + assets_widget.selection_changed.connect(self._on_asset_change) + assets_widget.refresh_triggered.connect(self._on_asset_refresh_trigger) + task_view.selectionModel().selectionChanged.connect( + self._on_task_change + ) + ok_btn.clicked.connect(self._on_ok_click) + + self._dbcon = dbcon + + self._project_combobox = project_combobox + self._project_model = project_model + self._project_proxy = project_proxy + self._project_delegate = project_delegate + + self._assets_widget = assets_widget + + self._task_view = task_view + self._task_model = task_model + self._task_proxy = task_proxy + + self._ok_btn = ok_btn + + self._strict = False + # Output of dialog self._context_to_store = { "project": None, @@ -40,6 +133,27 @@ class ContextDialog(QtWidgets.QDialog): "task": None } + def _on_asset_refresh_timer(self): + self._assets_widget.refresh() + + def _on_asset_refresh_trigger(self): + self._on_asset_change() + + def _on_asset_change(self): + self._set_asset_to_task_model() + + def _on_task_change(self): + pass + def _confirm_values(self): + self._context_to_store["project"] = self.get_selected_project() + self._context_to_store["asset"] = self.get_selected_asset() + self._context_to_store["task"] = self.get_selected_task() + + def _on_ok_click(self): + self._confirm_values() + self.accept() + + def get_context(self): return self._context_to_store From baa0f543df881cd3ac043e2f8b1d4b88a8b708c4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:47:26 +0200 Subject: [PATCH 10/18] give ability to set context of dialog --- openpype/tools/context_dialog/window.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index b3128ed159..393ac5b8a8 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -126,6 +126,10 @@ class ContextDialog(QtWidgets.QDialog): self._strict = False + # Values set by `set_context` method + self._set_context_project = None + self._set_context_asset = None + # Output of dialog self._context_to_store = { "project": None, @@ -154,6 +158,18 @@ class ContextDialog(QtWidgets.QDialog): self.accept() + def set_context(self, project_name=None, asset_name=None): + if project_name is None: + asset_name = None + + self._set_context_project = project_name + self._set_context_asset = asset_name + + self._context_to_store["project"] = project_name + self._context_to_store["asset"] = asset_name + + self._set_refresh_on_next_show() + def get_context(self): return self._context_to_store @@ -166,6 +182,7 @@ def main( ): app = QtWidgets.QApplication([]) window = ContextDialog() + window.set_context(project_name, asset_name) window.show() app.exec_() From 9552b41bc47bd3fef6e57470a783bb682ba3ab3f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:49:00 +0200 Subject: [PATCH 11/18] added base of strictness validation --- openpype/tools/context_dialog/window.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index 393ac5b8a8..07263e85fc 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -137,6 +137,10 @@ class ContextDialog(QtWidgets.QDialog): "task": None } + def set_strict(self, strict): + self._strict = strict + self._validate_strict() + def _on_asset_refresh_timer(self): self._assets_widget.refresh() @@ -158,6 +162,15 @@ class ContextDialog(QtWidgets.QDialog): self.accept() + def _validate_strict(self): + if not self._strict: + if not self._ok_btn.isEnabled(): + self._ok_btn.setEnabled(True) + return + + enabled = True + self._ok_btn.setEnabled(enabled) + def set_context(self, project_name=None, asset_name=None): if project_name is None: asset_name = None From a88485ef1842590746c2173d2579f2acaa41a08f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:49:51 +0200 Subject: [PATCH 12/18] set style and resize on first show --- openpype/tools/context_dialog/window.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index 07263e85fc..55dc40f745 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -130,6 +130,8 @@ class ContextDialog(QtWidgets.QDialog): self._set_context_project = None self._set_context_asset = None + self._first_show = True + # Output of dialog self._context_to_store = { "project": None, @@ -141,6 +143,13 @@ class ContextDialog(QtWidgets.QDialog): self._strict = strict self._validate_strict() + def showEvent(self, event): + super(ContextDialog, self).showEvent(event) + if self._first_show: + self._first_show = False + self.setStyleSheet(style.load_stylesheet()) + self.resize(600, 700) + def _on_asset_refresh_timer(self): self._assets_widget.refresh() From fb56e901f01ab323bb254348c78e0e1ff4ffc740 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:51:59 +0200 Subject: [PATCH 13/18] empty refresh method with setting when and how should refresh --- openpype/tools/context_dialog/window.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index 55dc40f745..c36e1ac017 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -131,6 +131,7 @@ class ContextDialog(QtWidgets.QDialog): self._set_context_asset = None self._first_show = True + self._refresh_on_next_show = True # Output of dialog self._context_to_store = { @@ -143,6 +144,14 @@ class ContextDialog(QtWidgets.QDialog): self._strict = strict self._validate_strict() + def _set_refresh_on_next_show(self): + if self._refresh_on_next_show: + return + + self._refresh_on_next_show = True + if self.isVisible(): + self.refresh() + def showEvent(self, event): super(ContextDialog, self).showEvent(event) if self._first_show: @@ -150,6 +159,13 @@ class ContextDialog(QtWidgets.QDialog): self.setStyleSheet(style.load_stylesheet()) self.resize(600, 700) + if self._refresh_on_next_show: + self.refresh() + + def refresh(self): + """Load assets from database""" + self._refresh_on_next_show = False + def _on_asset_refresh_timer(self): self._assets_widget.refresh() From acd4b052ed40523a132601bf52e95d025c681f71 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 18:53:08 +0200 Subject: [PATCH 14/18] refreshing should work as expected --- openpype/tools/context_dialog/window.py | 117 +++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index c36e1ac017..b8687f45a6 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -104,6 +104,7 @@ class ContextDialog(QtWidgets.QDialog): ) assets_widget.selection_changed.connect(self._on_asset_change) assets_widget.refresh_triggered.connect(self._on_asset_refresh_trigger) + assets_widget.refreshed.connect(self._on_asset_widget_refresh_finished) task_view.selectionModel().selectionChanged.connect( self._on_task_change ) @@ -130,6 +131,13 @@ class ContextDialog(QtWidgets.QDialog): self._set_context_project = None self._set_context_asset = None + # Requirements for asset widget refresh + self._assets_timer = assets_timer + self._rerefresh_assets = True + self._assets_refreshing = False + + # Helper attributes for handling of refresh + self._ignore_value_changes = False self._first_show = True self._refresh_on_next_show = True @@ -152,6 +160,12 @@ class ContextDialog(QtWidgets.QDialog): if self.isVisible(): self.refresh() + def _refresh_assets(self): + if self._assets_refreshing: + self._rerefresh_assets = True + return + self._assets_widget.refresh() + def showEvent(self, event): super(ContextDialog, self).showEvent(event) if self._first_show: @@ -166,17 +180,96 @@ class ContextDialog(QtWidgets.QDialog): """Load assets from database""" self._refresh_on_next_show = False + self._ignore_value_changes = True + + select_project_name = self._dbcon.Session.get("AVALON_PROJECT") + self._project_model.refresh() + self._project_proxy.sort(0) + + if self._set_context_project: + select_project_name = self._set_context_project + self._project_combobox.setEnabled(False) + else: + self._project_combobox.setEnabled(True) + if ( + select_project_name is None + and self._project_proxy.rowCount() > 0 + ): + index = self._project_proxy.index(0, 0) + select_project_name = index.data(PROJECT_NAME_ROLE) + + self._ignore_value_changes = False + + idx = self._project_combobox.findText(select_project_name) + if idx >= 0: + self._project_combobox.setCurrentIndex(idx) + self._dbcon.Session["AVALON_PROJECT"] = ( + self._project_combobox.currentText() + ) + + self._refresh_assets() + def _on_asset_refresh_timer(self): self._assets_widget.refresh() + def _on_asset_widget_refresh_finished(self): + self._assets_refreshing = False + if self._rerefresh_assets: + self._rerefresh_assets = False + self._assets_timer.start() + return + + self._ignore_value_changes = True + if self._set_context_asset: + self._dbcon.Session["AVALON_ASSET"] = self._set_context_asset + self._assets_widget.setEnabled(False) + self._assets_widget.select_assets(self._set_context_asset) + self._set_asset_to_task_model() + else: + self._assets_widget.setEnabled(True) + self._assets_widget.set_current_asset_btn_visibility(False) + + self._task_model.refresh() + self._task_proxy.sort(0, QtCore.Qt.AscendingOrder) + + self._ignore_value_changes = False + + self._validate_strict() + + def _on_project_combo_change(self): + if self._ignore_value_changes: + return + project_name = self._project_combobox.currentText() + + if self._dbcon.Session.get("AVALON_PROJECT") == project_name: + return + + self._dbcon.Session["AVALON_PROJECT"] = project_name + self._refresh_assets() + self._validate_strict() + def _on_asset_refresh_trigger(self): + self._assets_refreshing = True self._on_asset_change() def _on_asset_change(self): + """Selected assets have changed""" + if self._ignore_value_changes: + return self._set_asset_to_task_model() def _on_task_change(self): - pass + self._validate_strict() + + def _set_asset_to_task_model(self): + # filter None docs they are silo + asset_docs = self._assets_widget.get_selected_assets() + asset_ids = [asset_doc["_id"] for asset_doc in asset_docs] + asset_id = None + if asset_ids: + asset_id = asset_ids[0] + self._task_model.set_asset_id(asset_id) + def _confirm_values(self): self._context_to_store["project"] = self.get_selected_project() self._context_to_store["asset"] = self.get_selected_asset() @@ -186,6 +279,22 @@ class ContextDialog(QtWidgets.QDialog): self._confirm_values() self.accept() + def get_selected_project(self): + return self._project_combobox.currentText() + + def get_selected_asset(self): + asset_name = None + for asset_doc in self._assets_widget.get_selected_assets(): + asset_name = asset_doc["name"] + break + return asset_name + + def get_selected_task(self): + task_name = None + index = self._task_view.selectionModel().currentIndex() + if index.isValid(): + task_name = index.data(TASK_NAME_ROLE) + return task_name def _validate_strict(self): if not self._strict: @@ -194,6 +303,12 @@ class ContextDialog(QtWidgets.QDialog): return enabled = True + if not self._set_context_project and not self.get_selected_project(): + enabled = False + elif not self._set_context_asset and not self.get_selected_asset(): + enabled = False + elif not self.get_selected_task(): + enabled = False self._ok_btn.setEnabled(enabled) def set_context(self, project_name=None, asset_name=None): From 7816351b24c5443a402403404c656aa9ae948284 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 19:03:32 +0200 Subject: [PATCH 15/18] ignore close event if in strict mode --- openpype/tools/context_dialog/window.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index b8687f45a6..197a87caba 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -148,6 +148,15 @@ class ContextDialog(QtWidgets.QDialog): "task": None } + def closeEvent(self, event): + if self._strict and not self._ok_btn.isEnabled(): + event.ignore() + return + + if self._strict: + self._confirm_values() + super(ContextDialog, self).closeEvent(event) + def set_strict(self, strict): self._strict = strict self._validate_strict() @@ -163,8 +172,8 @@ class ContextDialog(QtWidgets.QDialog): def _refresh_assets(self): if self._assets_refreshing: self._rerefresh_assets = True - return - self._assets_widget.refresh() + else: + self._on_asset_refresh_timer() def showEvent(self, event): super(ContextDialog, self).showEvent(event) @@ -245,6 +254,7 @@ class ContextDialog(QtWidgets.QDialog): return self._dbcon.Session["AVALON_PROJECT"] = project_name + self._refresh_assets() self._validate_strict() From 884078359b6fbe4606c70d6bb7b57170a63377a2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 19:03:40 +0200 Subject: [PATCH 16/18] set strict mode --- openpype/tools/context_dialog/window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index 197a87caba..b2056e0b5f 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -345,6 +345,7 @@ def main( ): app = QtWidgets.QApplication([]) window = ContextDialog() + window.set_strict(strict) window.set_context(project_name, asset_name) window.show() app.exec_() From 822873f56ead91cdd3a67bf9de53b609701c5292 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 19:08:54 +0200 Subject: [PATCH 17/18] added ability to center a dialog --- openpype/tools/context_dialog/window.py | 3 +++ openpype/tools/utils/lib.py | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index b2056e0b5f..47d87cb0d2 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -5,6 +5,7 @@ from Qt import QtWidgets, QtCore, QtGui from avalon.api import AvalonMongoDB from openpype import style +from openpype.tools.utils.lib import center_window from openpype.tools.utils.widgets import AssetWidget from openpype.tools.utils.constants import ( TASK_NAME_ROLE, @@ -179,8 +180,10 @@ class ContextDialog(QtWidgets.QDialog): super(ContextDialog, self).showEvent(event) if self._first_show: self._first_show = False + # Set stylesheet and resize self.setStyleSheet(style.load_stylesheet()) self.resize(600, 700) + center_window(self) if self._refresh_on_next_show: self.refresh() diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index d01dbbd169..720c73e16b 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -13,6 +13,16 @@ from openpype.api import get_project_settings from openpype.lib import filter_profiles +def center_window(window): + """Move window to center of it's screen.""" + desktop = QtWidgets.QApplication.desktop() + screen_idx = desktop.screenNumber(window) + screen_geo = desktop.screenGeometry(screen_idx) + geo = window.frameGeometry() + geo.moveCenter(screen_geo.center()) + window.move(geo.topLeft()) + + def format_version(value, hero_version=False): """Formats integer to displayable version name""" label = "v{0:03d}".format(value) From b61a7a9364c57c857acfa8af39bfa1ab8d6f8c7b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Oct 2021 19:23:05 +0200 Subject: [PATCH 18/18] added few docstrings --- openpype/tools/context_dialog/window.py | 70 +++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/openpype/tools/context_dialog/window.py b/openpype/tools/context_dialog/window.py index 47d87cb0d2..124a1beda3 100644 --- a/openpype/tools/context_dialog/window.py +++ b/openpype/tools/context_dialog/window.py @@ -48,16 +48,20 @@ class ContextDialog(QtWidgets.QDialog): # UI initialization main_splitter = QtWidgets.QSplitter(self) + # Left side widget containt project combobox and asset widget left_side_widget = QtWidgets.QWidget(main_splitter) project_combobox = QtWidgets.QComboBox(left_side_widget) + # Styled delegate to propagate stylessheet project_delegate = QtWidgets.QStyledItemDelegate(project_combobox) project_combobox.setItemDelegate(project_delegate) + # Project model with only active projects without default item project_model = ProjectModel( dbcon, only_active=True, add_default_project=False ) + # Sorting proxy model project_proxy = ProjectSortFilterProxy() project_proxy.setSourceModel(project_model) project_combobox.setModel(project_proxy) @@ -72,17 +76,22 @@ class ContextDialog(QtWidgets.QDialog): left_side_layout.addWidget(project_combobox) left_side_layout.addWidget(assets_widget) + # Right side of window contains only tasks task_view = QtWidgets.QListView(main_splitter) task_model = TasksModel(dbcon) task_proxy = TasksProxyModel() task_proxy.setSourceModel(task_model) task_view.setModel(task_proxy) + # Add widgets to main splitter main_splitter.addWidget(left_side_widget) main_splitter.addWidget(task_view) + + # Set stretch of both sides main_splitter.setStretchFactor(0, 7) main_splitter.setStretchFactor(1, 3) + # Add confimation button to bottom right ok_btn = QtWidgets.QPushButton("OK", self) buttons_layout = QtWidgets.QHBoxLayout() @@ -94,6 +103,10 @@ class ContextDialog(QtWidgets.QDialog): main_layout.addWidget(main_splitter, 1) main_layout.addLayout(buttons_layout, 0) + # Timer which will trigger asset refresh + # - this is needed because asset widget triggers + # finished refresh before hides spin box so we need to trigger + # refreshing in small offset if we want re-refresh asset widget assets_timer = QtCore.QTimer() assets_timer.setInterval(50) assets_timer.setSingleShot(True) @@ -137,9 +150,11 @@ class ContextDialog(QtWidgets.QDialog): self._rerefresh_assets = True self._assets_refreshing = False + # Set stylehseet and resize window on first show + self._first_show = True + # Helper attributes for handling of refresh self._ignore_value_changes = False - self._first_show = True self._refresh_on_next_show = True # Output of dialog @@ -150,6 +165,7 @@ class ContextDialog(QtWidgets.QDialog): } def closeEvent(self, event): + """Ignore close event if is in strict state and context is not done.""" if self._strict and not self._ok_btn.isEnabled(): event.ignore() return @@ -159,24 +175,32 @@ class ContextDialog(QtWidgets.QDialog): super(ContextDialog, self).closeEvent(event) def set_strict(self, strict): + """Change strictness of dialog.""" self._strict = strict self._validate_strict() def _set_refresh_on_next_show(self): - if self._refresh_on_next_show: - return + """Refresh will be called on next showEvent. + If window is already visible then just execute refresh. + """ self._refresh_on_next_show = True if self.isVisible(): self.refresh() def _refresh_assets(self): + """Trigger refreshing of asset widget. + + This will set mart to rerefresh asset when current refreshing is done + or do it immidietely if asset widget is not refreshing at the time. + """ if self._assets_refreshing: self._rerefresh_assets = True else: self._on_asset_refresh_timer() def showEvent(self, event): + """Override show event to do some callbacks.""" super(ContextDialog, self).showEvent(event) if self._first_show: self._first_show = False @@ -189,19 +213,30 @@ class ContextDialog(QtWidgets.QDialog): self.refresh() def refresh(self): - """Load assets from database""" + """Refresh all widget one by one. + + When asset refresh is triggered we have to wait when is done so + this method continues with `_on_asset_widget_refresh_finished`. + """ + # Change state of refreshing (no matter how refresh was called) self._refresh_on_next_show = False + # Ignore changes of combobox and asset widget self._ignore_value_changes = True + # Get current project name to be able set it afterwards select_project_name = self._dbcon.Session.get("AVALON_PROJECT") + # Trigger project refresh self._project_model.refresh() + # Sort projects self._project_proxy.sort(0) + # Disable combobox if project was passed to `set_context` if self._set_context_project: select_project_name = self._set_context_project self._project_combobox.setEnabled(False) else: + # Find new project to select self._project_combobox.setEnabled(True) if ( select_project_name is None @@ -219,12 +254,20 @@ class ContextDialog(QtWidgets.QDialog): self._project_combobox.currentText() ) + # Trigger asset refresh self._refresh_assets() def _on_asset_refresh_timer(self): + """This is only way how to trigger refresh asset widget. + + Use `_refresh_assets` method to refresh asset widget. + """ self._assets_widget.refresh() def _on_asset_widget_refresh_finished(self): + """Catch when asset widget finished refreshing.""" + # If should refresh again then skip all other callbacks and trigger + # assets timer directly. self._assets_refreshing = False if self._rerefresh_assets: self._rerefresh_assets = False @@ -241,7 +284,9 @@ class ContextDialog(QtWidgets.QDialog): self._assets_widget.setEnabled(True) self._assets_widget.set_current_asset_btn_visibility(False) + # Refresh tasks self._task_model.refresh() + # Sort tasks self._task_proxy.sort(0, QtCore.Qt.AscendingOrder) self._ignore_value_changes = False @@ -282,20 +327,26 @@ class ContextDialog(QtWidgets.QDialog): if asset_ids: asset_id = asset_ids[0] self._task_model.set_asset_id(asset_id) + self._task_proxy.sort(0, QtCore.Qt.AscendingOrder) def _confirm_values(self): + """Store values to output.""" self._context_to_store["project"] = self.get_selected_project() self._context_to_store["asset"] = self.get_selected_asset() self._context_to_store["task"] = self.get_selected_task() def _on_ok_click(self): + # Store values to output self._confirm_values() + # Close dialog self.accept() def get_selected_project(self): + """Get selected project.""" return self._project_combobox.currentText() def get_selected_asset(self): + """Currently selected asset in asset widget.""" asset_name = None for asset_doc in self._assets_widget.get_selected_assets(): asset_name = asset_doc["name"] @@ -303,6 +354,7 @@ class ContextDialog(QtWidgets.QDialog): return asset_name def get_selected_task(self): + """Currently selected task.""" task_name = None index = self._task_view.selectionModel().currentIndex() if index.isValid(): @@ -325,6 +377,7 @@ class ContextDialog(QtWidgets.QDialog): self._ok_btn.setEnabled(enabled) def set_context(self, project_name=None, asset_name=None): + """Set context which will be used and locked in dialog.""" if project_name is None: asset_name = None @@ -337,6 +390,7 @@ class ContextDialog(QtWidgets.QDialog): self._set_refresh_on_next_show() def get_context(self): + """Result of dialog.""" return self._context_to_store @@ -346,18 +400,24 @@ def main( asset_name=None, strict=True ): - app = QtWidgets.QApplication([]) + # Run Qt application + app = QtWidgets.QApplication.instance() + if app is None: + app = QtWidgets.QApplication([]) window = ContextDialog() window.set_strict(strict) window.set_context(project_name, asset_name) window.show() app.exec_() + # Get result from window data = window.get_context() + # Make sure json filepath directory exists file_dir = os.path.dirname(path_to_store) if not os.path.exists(file_dir): os.makedirs(file_dir) + # Store result into json file with open(path_to_store, "w") as stream: json.dump(data, stream)