From 43613a30f757c33e7a310ce19d44a6ed0d8c5cbe Mon Sep 17 00:00:00 2001 From: Joseph Yu Date: Thu, 5 Feb 2026 14:23:36 +0000 Subject: [PATCH 1/6] Squashed 804b8e2...0092778 commit 009277877e4fa66fa41865dad2342a898c9e553f Author: Joseph Yu Date: Mon Feb 2 18:53:18 2026 +0000 Default to always fit width commit 4c181b37463220c13a563ae10fbd98745cb97d84 Author: Joseph Yu Date: Fri Jan 30 13:06:21 2026 +0000 Fixed to get sizeHint from widget commit b2403a6ac2678b1d8233deb778621fcca425d4e5 Author: Joseph Yu Date: Fri Jan 30 12:32:09 2026 +0000 Moved into context manager commit f97f279c5798c4b7390401d0f1005367be0b37b4 Author: Joseph Yu Date: Thu Jan 29 21:09:49 2026 +0000 Initial in dialog --- info.yml | 13 +++++ python/tk_multi_publish2/dialog.py | 78 +++++++++++++++++++++++++++--- 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/info.yml b/info.yml index 87f8d7e5..e48a3d47 100644 --- a/info.yml +++ b/info.yml @@ -132,6 +132,19 @@ configuration: "If true the app dialog will be opened in modal window mode, else if false (default) the dialog will be opened in non-modal window mode." + fit_width_to_items: + type: str + default_value: "always" + description: | + Determines when the publish dialog should automatically resize its width + to fit the contents of the items tree. Valid values are: + + - 'never' the dialog will never auto resize its width + - 'initial' (default) the dialog will auto resize its width once, + when the items tree is first populated + - 'always' the dialog will auto resize its width every time the items + tree is modified (items added/removed) + # the Shotgun fields that this app needs in order to operate correctly requires_shotgun_fields: diff --git a/python/tk_multi_publish2/dialog.py b/python/tk_multi_publish2/dialog.py index b285ffaf..9a41c86b 100644 --- a/python/tk_multi_publish2/dialog.py +++ b/python/tk_multi_publish2/dialog.py @@ -8,6 +8,7 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +import contextlib import traceback import sgtk @@ -70,6 +71,7 @@ def __init__(self, parent=None): shotgun_globals.register_bg_task_manager(self._task_manager) self._bundle = sgtk.platform.current_bundle() + sgtk.platform.util.LogManager.global_debug = True self._validation_run = False # set up the UI @@ -412,7 +414,6 @@ def _is_task_selection_homogeneous(self, items): first_task = items[0].get_publish_instance() for item in items: - publish_instance = item.get_publish_instance() # User has mixed different types of publish instances, it's not just a task list. if not isinstance(publish_instance, PublishTask): @@ -856,7 +857,7 @@ def _full_rebuild(self): new_session_items = self._publish_manager.collect_session() logger.debug( - "Refresh: Running collection on all previously collected external " "files" + "Refresh: Running collection on all previously collected external files" ) new_file_items = self._publish_manager.collect_files(previously_collected_files) @@ -988,7 +989,74 @@ def _synchronize_tree(self): # main frame of the publish ui self._progress_handler.progress_details.set_parent(self.ui.main_frame) - self.ui.items_tree.build_tree() + with self._resize_window_for_tree(): + self.ui.items_tree.build_tree() + + @property + def _fit_width_to_items(self): # type: () -> Literal["never", "initial", "always"] + """Calculate appropriate ``fit_width_to_items`` mode from app settings.""" + known_values = ("never", "initial", "always") + default = known_values[1] + + value = self._bundle.get_setting("fit_width_to_items", default).lower() + if value not in known_values: + msg = "fit_width_to_items '%s' is not one of: %r. Defaulting to %s." + logger.warning(msg, value, known_values, default) + value = default + return value + + @contextlib.contextmanager + def _resize_window_for_tree(self): # type: () -> Iterator[None] + """Context that resizes the main window to fit the tree items. + + Only runs on successful exit of the context. Skips if any of: + + - ``fit_width_to_items`` is 'never' + - There are no non-root items in the tree + - ``fit_width_to_items`` is 'initial' and the tree was already populated + + """ + mode = self._fit_width_to_items + if mode == "never": + yield + logger.debug("Skipping resize: fit_width_to_items is 'never'.") + else: + had_items = len(self._get_tree_items()) > 1 + yield + current_items = self._get_tree_items() + + if len(current_items) < 2: + logger.debug("Skipping resize: No non-root items in tree") + elif had_items and mode == "initial": + logger.debug( + "Skipping resize: fit_width_to_items is 'initial' " + "and tree was already populated." + ) + else: + tree_size_hint = QtCore.QSize(0, 0) + for tree_item in current_items: + widget = self.ui.items_tree.itemWidget(tree_item, 0) + tree_size_hint = tree_size_hint.expandedTo(widget.sizeHint()) + + # Add additional padding + tree_size_hint.setWidth(tree_size_hint.width() + 10) + + # Constraint against 70% of the screen size + safe_maximum = self.screen().availableGeometry().size() * 0.7 + tree_size_hint = tree_size_hint.boundedTo(safe_maximum) + + # Set the splitter sizes + old_split_widths = self.ui.splitter.sizes() + split_widths = [tree_size_hint.width(), *old_split_widths[1:]] + logger.debug("Resizing splitter to: %r", split_widths) + self.ui.splitter.setSizes(split_widths) + + # Resize the main window to accommodate the new size + window = self.window() + old_size = window.size() + width_padding = old_size.width() - sum(old_split_widths) + window.resize(width_padding + sum(split_widths), old_size.height()) + logger.debug("Resized window to: %s", window.size()) def _set_tree_items_expanded(self, expanded): """ @@ -1235,7 +1303,7 @@ def do_publish(self): # do_validate returns the number of issues encountered if self.do_validate(is_standalone=False) > 0: self._progress_handler.logger.error( - "Validation errors detected. " "Not proceeding with publish." + "Validation errors detected. Not proceeding with publish." ) self.ui.button_container.show() self.ui.item_settings.setEnabled(True) @@ -1344,7 +1412,6 @@ def do_publish(self): ) self._overlay.show_fail() else: - # Publish succeeded # Log the toolkit "Published" metric try: @@ -1452,7 +1519,6 @@ def _task_generator(self, stage_name): list_items = self._get_tree_items() for ui_item in list_items: - if self._stop_processing_flagged: # jump out of the iteration break From d31633d692644640cccba01d6b950ecaf03d9139 Mon Sep 17 00:00:00 2001 From: Joseph Yu Date: Fri, 6 Feb 2026 17:15:25 +0000 Subject: [PATCH 2/6] Removed global_debug --- python/tk_multi_publish2/dialog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/tk_multi_publish2/dialog.py b/python/tk_multi_publish2/dialog.py index 9a41c86b..8472ff74 100644 --- a/python/tk_multi_publish2/dialog.py +++ b/python/tk_multi_publish2/dialog.py @@ -71,7 +71,6 @@ def __init__(self, parent=None): shotgun_globals.register_bg_task_manager(self._task_manager) self._bundle = sgtk.platform.current_bundle() - sgtk.platform.util.LogManager.global_debug = True self._validation_run = False # set up the UI From fc37a6b3fc712a5d59c2c6a73d29f99db4842f76 Mon Sep 17 00:00:00 2001 From: Joseph Yu Date: Fri, 6 Feb 2026 17:16:55 +0000 Subject: [PATCH 3/6] un-ruff --- python/tk_multi_publish2/dialog.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/tk_multi_publish2/dialog.py b/python/tk_multi_publish2/dialog.py index 8472ff74..d95e2be2 100644 --- a/python/tk_multi_publish2/dialog.py +++ b/python/tk_multi_publish2/dialog.py @@ -413,6 +413,7 @@ def _is_task_selection_homogeneous(self, items): first_task = items[0].get_publish_instance() for item in items: + publish_instance = item.get_publish_instance() # User has mixed different types of publish instances, it's not just a task list. if not isinstance(publish_instance, PublishTask): @@ -856,7 +857,7 @@ def _full_rebuild(self): new_session_items = self._publish_manager.collect_session() logger.debug( - "Refresh: Running collection on all previously collected external files" + "Refresh: Running collection on all previously collected external " "files" ) new_file_items = self._publish_manager.collect_files(previously_collected_files) @@ -1302,7 +1303,7 @@ def do_publish(self): # do_validate returns the number of issues encountered if self.do_validate(is_standalone=False) > 0: self._progress_handler.logger.error( - "Validation errors detected. Not proceeding with publish." + "Validation errors detected. " "Not proceeding with publish." ) self.ui.button_container.show() self.ui.item_settings.setEnabled(True) @@ -1411,6 +1412,7 @@ def do_publish(self): ) self._overlay.show_fail() else: + # Publish succeeded # Log the toolkit "Published" metric try: @@ -1518,6 +1520,7 @@ def _task_generator(self, stage_name): list_items = self._get_tree_items() for ui_item in list_items: + if self._stop_processing_flagged: # jump out of the iteration break From d627da9b7928d9624be74d4bdd655fe4703a456f Mon Sep 17 00:00:00 2001 From: Joseph Yu Date: Fri, 6 Feb 2026 17:18:33 +0000 Subject: [PATCH 4/6] default to never --- info.yml | 8 ++++---- python/tk_multi_publish2/dialog.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/info.yml b/info.yml index e48a3d47..a9b57647 100644 --- a/info.yml +++ b/info.yml @@ -134,15 +134,15 @@ configuration: fit_width_to_items: type: str - default_value: "always" + default_value: "never" description: | Determines when the publish dialog should automatically resize its width to fit the contents of the items tree. Valid values are: - - 'never' the dialog will never auto resize its width - - 'initial' (default) the dialog will auto resize its width once, + - 'never' (default) The dialog will never auto resize its width + - 'initial' The dialog will auto resize its width once, when the items tree is first populated - - 'always' the dialog will auto resize its width every time the items + - 'always' The dialog will auto resize its width every time the items tree is modified (items added/removed) diff --git a/python/tk_multi_publish2/dialog.py b/python/tk_multi_publish2/dialog.py index d95e2be2..ecc4456c 100644 --- a/python/tk_multi_publish2/dialog.py +++ b/python/tk_multi_publish2/dialog.py @@ -996,7 +996,7 @@ def _synchronize_tree(self): def _fit_width_to_items(self): # type: () -> Literal["never", "initial", "always"] """Calculate appropriate ``fit_width_to_items`` mode from app settings.""" known_values = ("never", "initial", "always") - default = known_values[1] + default = known_values[0] value = self._bundle.get_setting("fit_width_to_items", default).lower() if value not in known_values: From 9e7e9a47647879a84becc6b3a0bf97d44a64aa22 Mon Sep 17 00:00:00 2001 From: Joseph Yu Date: Fri, 6 Feb 2026 17:51:22 +0000 Subject: [PATCH 5/6] firt enum pass --- python/tk_multi_publish2/dialog.py | 38 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/python/tk_multi_publish2/dialog.py b/python/tk_multi_publish2/dialog.py index ecc4456c..98e2003e 100644 --- a/python/tk_multi_publish2/dialog.py +++ b/python/tk_multi_publish2/dialog.py @@ -9,6 +9,7 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import contextlib +import enum import traceback import sgtk @@ -992,18 +993,18 @@ def _synchronize_tree(self): with self._resize_window_for_tree(): self.ui.items_tree.build_tree() - @property - def _fit_width_to_items(self): # type: () -> Literal["never", "initial", "always"] - """Calculate appropriate ``fit_width_to_items`` mode from app settings.""" - known_values = ("never", "initial", "always") - default = known_values[0] - - value = self._bundle.get_setting("fit_width_to_items", default).lower() - if value not in known_values: - msg = "fit_width_to_items '%s' is not one of: %r. Defaulting to %s." - logger.warning(msg, value, known_values, default) - value = default - return value + class FitWidthMode(enum.Enum): + """Possible values for the "fit_width_to_items" setting.""" + + NEVER = "never" + INITIAL = "initial" + ALWAYS = "always" + + @classmethod + def _missing_(cls, value): + msg = "%r is not one of: %r. Defaulting to %s." + logger.warning(msg, value, [mem.value for mem in cls], cls.NEVER.value) + return cls.NEVER @contextlib.contextmanager def _resize_window_for_tree(self): # type: () -> Iterator[None] @@ -1016,10 +1017,10 @@ def _resize_window_for_tree(self): # type: () -> Iterator[None] - ``fit_width_to_items`` is 'initial' and the tree was already populated """ - mode = self._fit_width_to_items - if mode == "never": + mode = self.FitWidthMode(self._bundle.get_setting("fit_width_to_items").lower()) + if mode == self.FitWidthMode.NEVER: yield - logger.debug("Skipping resize: fit_width_to_items is 'never'.") + logger.debug("Skipping resize: fit_width_to_items is %r.", mode.value) else: had_items = len(self._get_tree_items()) > 1 yield @@ -1027,10 +1028,11 @@ def _resize_window_for_tree(self): # type: () -> Iterator[None] if len(current_items) < 2: logger.debug("Skipping resize: No non-root items in tree") - elif had_items and mode == "initial": + elif had_items and mode == self.FitWidthMode.INITIAL: logger.debug( - "Skipping resize: fit_width_to_items is 'initial' " - "and tree was already populated." + "Skipping resize: fit_width_to_items is %r " + "and tree was already populated.", + mode.value, ) else: tree_size_hint = QtCore.QSize(0, 0) From 44bde0e65d60a180cf1f2e497e77325f0e8d79c8 Mon Sep 17 00:00:00 2001 From: Joseph Yu Date: Fri, 6 Feb 2026 17:54:27 +0000 Subject: [PATCH 6/6] typing --- python/tk_multi_publish2/dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/tk_multi_publish2/dialog.py b/python/tk_multi_publish2/dialog.py index 98e2003e..02c797ca 100644 --- a/python/tk_multi_publish2/dialog.py +++ b/python/tk_multi_publish2/dialog.py @@ -11,6 +11,7 @@ import contextlib import enum import traceback +from typing import Iterator import sgtk from sgtk.platform.qt import QtCore, QtGui @@ -1007,7 +1008,7 @@ def _missing_(cls, value): return cls.NEVER @contextlib.contextmanager - def _resize_window_for_tree(self): # type: () -> Iterator[None] + def _resize_window_for_tree(self) -> Iterator[None]: """Context that resizes the main window to fit the tree items. Only runs on successful exit of the context. Skips if any of: