From 75d55b9ac5aee789ed6f6c1cb41677b67a34ad4e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 14 Jan 2022 18:09:41 +0100 Subject: [PATCH] OP-1117 - added context menu to Launcher actions to skip opening last workfile Choice is persistent in Local Setting (only in DB, not exposed to GUI yet.) --- openpype/hooks/pre_global_host_data.py | 1 + openpype/hooks/pre_non_python_host_launch.py | 2 +- openpype/lib/applications.py | 5 ++ openpype/tools/launcher/actions.py | 4 +- openpype/tools/launcher/constants.py | 1 + openpype/tools/launcher/delegates.py | 13 ++- openpype/tools/launcher/models.py | 86 +++++++++++++++++++- openpype/tools/launcher/widgets.py | 39 ++++++++- 8 files changed, 145 insertions(+), 6 deletions(-) diff --git a/openpype/hooks/pre_global_host_data.py b/openpype/hooks/pre_global_host_data.py index 6b08cdb444..bae967e25f 100644 --- a/openpype/hooks/pre_global_host_data.py +++ b/openpype/hooks/pre_global_host_data.py @@ -43,6 +43,7 @@ class GlobalHostDataHook(PreLaunchHook): "env": self.launch_context.env, + "start_last_workfile": self.data.get("start_last_workfile"), "last_workfile_path": self.data.get("last_workfile_path"), "log": self.log diff --git a/openpype/hooks/pre_non_python_host_launch.py b/openpype/hooks/pre_non_python_host_launch.py index 29e40d28c8..8aa61a9027 100644 --- a/openpype/hooks/pre_non_python_host_launch.py +++ b/openpype/hooks/pre_non_python_host_launch.py @@ -40,7 +40,7 @@ class NonPythonHostHook(PreLaunchHook): ) # Add workfile path if exists workfile_path = self.data["last_workfile_path"] - if os.path.exists(workfile_path): + if workfile_path and os.path.exists(workfile_path): new_launch_args.append(workfile_path) # Append as whole list as these areguments should not be separated diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index d0438e12a6..ab20d812e7 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -1490,6 +1490,11 @@ def _prepare_last_workfile(data, workdir): import avalon.api log = data["log"] + + if not data.get("start_last_workfile", True): + log.info("Explicitly forbidden to open last workfile, skipping") + return + _workdir_data = data.get("workdir_data") if not _workdir_data: log.info( diff --git a/openpype/tools/launcher/actions.py b/openpype/tools/launcher/actions.py index 4d86970f9c..fbaef05261 100644 --- a/openpype/tools/launcher/actions.py +++ b/openpype/tools/launcher/actions.py @@ -62,6 +62,7 @@ class ApplicationAction(api.Action): icon = None color = None order = 0 + data = {} _log = None required_session_keys = ( @@ -103,7 +104,8 @@ class ApplicationAction(api.Action): self.application.launch( project_name=project_name, asset_name=asset_name, - task_name=task_name + task_name=task_name, + **self.data ) except ApplictionExecutableNotFound as exc: diff --git a/openpype/tools/launcher/constants.py b/openpype/tools/launcher/constants.py index 7f394cb5ac..3747b0f0a4 100644 --- a/openpype/tools/launcher/constants.py +++ b/openpype/tools/launcher/constants.py @@ -7,6 +7,7 @@ VARIANT_GROUP_ROLE = QtCore.Qt.UserRole + 2 ACTION_ID_ROLE = QtCore.Qt.UserRole + 3 ANIMATION_START_ROLE = QtCore.Qt.UserRole + 4 ANIMATION_STATE_ROLE = QtCore.Qt.UserRole + 5 +FORCE_NOT_OPEN_WORKFILE_ROLE = QtCore.Qt.UserRole + 6 # Animation length in seconds ANIMATION_LEN = 7 diff --git a/openpype/tools/launcher/delegates.py b/openpype/tools/launcher/delegates.py index cef0f5e1a2..7b53658727 100644 --- a/openpype/tools/launcher/delegates.py +++ b/openpype/tools/launcher/delegates.py @@ -2,7 +2,8 @@ import time from Qt import QtCore, QtWidgets, QtGui from .constants import ( ANIMATION_START_ROLE, - ANIMATION_STATE_ROLE + ANIMATION_STATE_ROLE, + FORCE_NOT_OPEN_WORKFILE_ROLE ) @@ -69,6 +70,16 @@ class ActionDelegate(QtWidgets.QStyledItemDelegate): self._draw_animation(painter, option, index) super(ActionDelegate, self).paint(painter, option, index) + + if index.data(FORCE_NOT_OPEN_WORKFILE_ROLE): + rect = QtCore.QRectF(option.rect.x(), option.rect.height(), + 5, 5) + painter.setPen(QtCore.Qt.transparent) + painter.setBrush(QtGui.QColor(200, 0, 0)) + painter.drawEllipse(rect) + + painter.setBrush(self.extender_bg_brush) + is_group = False for group_role in self.group_roles: is_group = index.data(group_role) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 427475cb4b..d3fd7ac3b9 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -8,7 +8,8 @@ from .constants import ( ACTION_ROLE, GROUP_ROLE, VARIANT_GROUP_ROLE, - ACTION_ID_ROLE + ACTION_ID_ROLE, + FORCE_NOT_OPEN_WORKFILE_ROLE ) from .actions import ApplicationAction from Qt import QtCore, QtGui @@ -16,6 +17,8 @@ from avalon.vendor import qtawesome from avalon import style, api from openpype.lib import ApplicationManager +from openpype.settings.lib import get_local_settings, save_local_settings + log = logging.getLogger(__name__) @@ -75,7 +78,8 @@ class ActionModel(QtGui.QStandardItemModel): "group": None, "icon": app.icon, "color": getattr(app, "color", None), - "order": getattr(app, "order", None) or 0 + "order": getattr(app, "order", None) or 0, + "data": {} } ) @@ -179,11 +183,20 @@ class ActionModel(QtGui.QStandardItemModel): self.beginResetModel() + local_settings = get_local_settings() items = [] for order in sorted(items_by_order.keys()): for item in items_by_order[order]: item_id = str(uuid.uuid4()) item.setData(item_id, ACTION_ID_ROLE) + + if self.is_force_not_open_workfile(item, + local_settings): + label = item.text() + label += " (Not opening last workfile)" + item.setData(label, QtCore.Qt.ToolTipRole) + item.setData(True, FORCE_NOT_OPEN_WORKFILE_ROLE) + self.items_by_id[item_id] = item items.append(item) @@ -222,6 +235,75 @@ class ActionModel(QtGui.QStandardItemModel): key=lambda action: (action.order, action.name) ) + def update_force_not_open_workfile_settings(self, is_checked, action): + """Store/remove config for forcing to skip opening last workfile. + + Args: + is_checked (bool): True to add, False to remove + action (ApplicationAction) + """ + local_settings = get_local_settings() + + actual_data = self._prepare_compare_data(action) + + force_not_open_workfile = local_settings.get("force_not_open_workfile", + []) + final_local_sett = local_settings + if is_checked: + if not force_not_open_workfile: + final_local_sett["force_not_open_workfile"] = [] + + final_local_sett["force_not_open_workfile"].append(actual_data) + else: + final_local_sett["force_not_open_workfile"] = [] + for config in force_not_open_workfile: + if config != actual_data: + final_local_sett["force_not_open_workfile"].append(config) + + if not final_local_sett["force_not_open_workfile"]: + final_local_sett.pop("force_not_open_workfile") + + save_local_settings(final_local_sett) + + def is_force_not_open_workfile(self, item, local_settings): + """Checks if application for task is marked to not open workfile + + There might be specific tasks where is unwanted to open workfile right + always (broken file, low performance). This allows artist to mark to + skip opening for combination (project, asset, task_name, app) + + Args: + item (QStandardItem) + local_settings (dict) + """ + action = item.data(ACTION_ROLE) + actual_data = self._prepare_compare_data(action) + for config in local_settings.get("force_not_open_workfile", []): + if config == actual_data: + return True + + return False + + def _prepare_compare_data(self, action): + if isinstance(action, list) and action: + action = action[0] + + _session = copy.deepcopy(self.dbcon.Session) + session = { + key: value + for key, value in _session.items() + if value + } + + actual_data = { + "app_label": action.label.lower(), + "project_name": session["AVALON_PROJECT"], + "asset": session["AVALON_ASSET"], + "task_name": session["AVALON_TASK"] + } + + return actual_data + class ProjectModel(QtGui.QStandardItemModel): """List of projects""" diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py index edda8d08b5..0c21fcb33d 100644 --- a/openpype/tools/launcher/widgets.py +++ b/openpype/tools/launcher/widgets.py @@ -15,7 +15,8 @@ from .constants import ( ACTION_ID_ROLE, ANIMATION_START_ROLE, ANIMATION_STATE_ROLE, - ANIMATION_LEN + ANIMATION_LEN, + FORCE_NOT_OPEN_WORKFILE_ROLE ) @@ -96,6 +97,7 @@ class ActionBar(QtWidgets.QWidget): view.setViewMode(QtWidgets.QListView.IconMode) view.setResizeMode(QtWidgets.QListView.Adjust) view.setSelectionMode(QtWidgets.QListView.NoSelection) + view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) view.setEditTriggers(QtWidgets.QListView.NoEditTriggers) view.setWrapping(True) view.setGridSize(QtCore.QSize(70, 75)) @@ -135,6 +137,7 @@ class ActionBar(QtWidgets.QWidget): project_handler.projects_refreshed.connect(self._on_projects_refresh) view.clicked.connect(self.on_clicked) + view.customContextMenuRequested.connect(self.on_context_menu) def discover_actions(self): if self._animation_timer.isActive(): @@ -181,6 +184,38 @@ class ActionBar(QtWidgets.QWidget): self._animated_items.add(action_id) self._animation_timer.start() + def on_context_menu(self, point): + """Creates menu to force skip opening last workfile.""" + index = self.view.indexAt(point) + if not index.isValid(): + return + + action_item = index.data(ACTION_ROLE) + menu = QtWidgets.QMenu(self.view) + checkbox = QtWidgets.QCheckBox("Force not open last workfile", + menu) + if index.data(FORCE_NOT_OPEN_WORKFILE_ROLE): + checkbox.setChecked(True) + + checkbox.stateChanged.connect( + lambda: self.on_checkbox_changed(checkbox.isChecked(), + action_item)) + action = QtWidgets.QWidgetAction(menu) + action.setDefaultWidget(checkbox) + + menu.addAction(action) + + global_point = self.mapToGlobal(point) + action = menu.exec_(global_point) + if not action or not action.data(): + return + + return + + def on_checkbox_changed(self, is_checked, action): + self.model.update_force_not_open_workfile_settings(is_checked, action) + self.discover_actions() # repaint + def on_clicked(self, index): if not index or not index.isValid(): return @@ -189,6 +224,8 @@ class ActionBar(QtWidgets.QWidget): is_variant_group = index.data(VARIANT_GROUP_ROLE) if not is_group and not is_variant_group: action = index.data(ACTION_ROLE) + if index.data(FORCE_NOT_OPEN_WORKFILE_ROLE): + action.data["start_last_workfile"] = False self._start_animation(index) self.action_clicked.emit(action) return