From ae4c7a3ab4357183029bbc6d3bb9894987b6f07a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Feb 2022 16:58:01 +0100 Subject: [PATCH] fixed few issues in publisher ui --- openpype/tools/publisher/control.py | 15 +- .../tools/publisher/widgets/create_dialog.py | 28 +++- .../tools/publisher/widgets/publish_widget.py | 6 +- .../publisher/widgets/validations_widget.py | 139 ++++++++++++++---- openpype/tools/publisher/widgets/widgets.py | 16 +- openpype/tools/publisher/window.py | 5 +- 6 files changed, 162 insertions(+), 47 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 2ce0eaad62..ab2dffd489 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -42,18 +42,23 @@ class MainThreadProcess(QtCore.QObject): This approach gives ability to update UI meanwhile plugin is in progress. """ - timer_interval = 3 + count_timeout = 2 def __init__(self): super(MainThreadProcess, self).__init__() self._items_to_process = collections.deque() timer = QtCore.QTimer() - timer.setInterval(self.timer_interval) + timer.setInterval(0) timer.timeout.connect(self._execute) self._timer = timer + self._switch_counter = self.count_timeout + + def process(self, func, *args, **kwargs): + item = MainThreadItem(func, *args, **kwargs) + self.add_item(item) def add_item(self, item): self._items_to_process.append(item) @@ -62,6 +67,12 @@ class MainThreadProcess(QtCore.QObject): if not self._items_to_process: return + if self._switch_counter > 0: + self._switch_counter -= 1 + return + + self._switch_counter = self.count_timeout + item = self._items_to_process.popleft() item.process() diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index f9f8310e09..c5b77eca8b 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -174,6 +174,8 @@ class CreatorDescriptionWidget(QtWidgets.QWidget): class CreateDialog(QtWidgets.QDialog): + default_size = (900, 500) + def __init__( self, controller, asset_name=None, task_name=None, parent=None ): @@ -262,11 +264,16 @@ class CreateDialog(QtWidgets.QDialog): mid_layout.addLayout(form_layout, 0) mid_layout.addWidget(create_btn, 0) + splitter_widget = QtWidgets.QSplitter(self) + splitter_widget.addWidget(context_widget) + splitter_widget.addWidget(mid_widget) + splitter_widget.addWidget(pre_create_widget) + splitter_widget.setStretchFactor(0, 1) + splitter_widget.setStretchFactor(1, 1) + splitter_widget.setStretchFactor(2, 1) + layout = QtWidgets.QHBoxLayout(self) - layout.setSpacing(10) - layout.addWidget(context_widget, 1) - layout.addWidget(mid_widget, 1) - layout.addWidget(pre_create_widget, 1) + layout.addWidget(splitter_widget, 1) prereq_timer = QtCore.QTimer() prereq_timer.setInterval(50) @@ -289,6 +296,8 @@ class CreateDialog(QtWidgets.QDialog): controller.add_plugins_refresh_callback(self._on_plugins_refresh) + self._splitter_widget = splitter_widget + self._pre_create_widget = pre_create_widget self._context_widget = context_widget @@ -308,6 +317,7 @@ class CreateDialog(QtWidgets.QDialog): self.create_btn = create_btn self._prereq_timer = prereq_timer + self._first_show = True def _context_change_is_enabled(self): return self._context_widget.isEnabled() @@ -643,6 +653,16 @@ class CreateDialog(QtWidgets.QDialog): def showEvent(self, event): super(CreateDialog, self).showEvent(event) + if self._first_show: + self._first_show = False + width, height = self.default_size + self.resize(width, height) + + third_size = int(width / 3) + self._splitter_widget.setSizes( + [third_size, third_size, width - (2 * third_size)] + ) + if self._last_pos is not None: self.move(self._last_pos) diff --git a/openpype/tools/publisher/widgets/publish_widget.py b/openpype/tools/publisher/widgets/publish_widget.py index e4f3579978..80d0265dd3 100644 --- a/openpype/tools/publisher/widgets/publish_widget.py +++ b/openpype/tools/publisher/widgets/publish_widget.py @@ -213,7 +213,6 @@ class PublishFrame(QtWidgets.QFrame): close_report_btn.setIcon(close_report_icon) details_layout = QtWidgets.QVBoxLayout(details_widget) - details_layout.setContentsMargins(0, 0, 0, 0) details_layout.addWidget(report_view) details_layout.addWidget(close_report_btn) @@ -495,10 +494,11 @@ class PublishFrame(QtWidgets.QFrame): def _on_show_details(self): self._change_bg_property(2) self._main_layout.setCurrentWidget(self._details_widget) - logs = self.controller.get_publish_report() - self._report_view.set_report(logs) + report_data = self.controller.get_publish_report() + self._report_view.set_report_data(report_data) def _on_close_report_clicked(self): + self._report_view.close_details_popup() if self.controller.get_publish_crash_error(): self._change_bg_property() diff --git a/openpype/tools/publisher/widgets/validations_widget.py b/openpype/tools/publisher/widgets/validations_widget.py index bb88e1783c..798c1f9d92 100644 --- a/openpype/tools/publisher/widgets/validations_widget.py +++ b/openpype/tools/publisher/widgets/validations_widget.py @@ -10,6 +10,9 @@ from openpype.tools.utils import BaseClickableFrame from .widgets import ( IconValuePixmapLabel ) +from ..constants import ( + INSTANCE_ID_ROLE +) class ValidationErrorInstanceList(QtWidgets.QListView): @@ -22,19 +25,20 @@ class ValidationErrorInstanceList(QtWidgets.QListView): self.setObjectName("ValidationErrorInstanceList") + self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.setSelectionMode(QtWidgets.QListView.ExtendedSelection) def minimumSizeHint(self): - result = super(ValidationErrorInstanceList, self).minimumSizeHint() - result.setHeight(self.sizeHint().height()) - return result + return self.sizeHint() def sizeHint(self): + result = super(ValidationErrorInstanceList, self).sizeHint() row_count = self.model().rowCount() height = 0 if row_count > 0: height = self.sizeHintForRow(0) * row_count - return QtCore.QSize(self.width(), height) + result.setHeight(height) + return result class ValidationErrorTitleWidget(QtWidgets.QWidget): @@ -47,6 +51,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): if there is a list (Valdation error may happen on context). """ selected = QtCore.Signal(int) + instance_changed = QtCore.Signal(int) def __init__(self, index, error_info, parent): super(ValidationErrorTitleWidget, self).__init__(parent) @@ -64,32 +69,38 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): toggle_instance_btn.setArrowType(QtCore.Qt.RightArrow) toggle_instance_btn.setMaximumWidth(14) - exception = error_info["exception"] - label_widget = QtWidgets.QLabel(exception.title, title_frame) + label_widget = QtWidgets.QLabel(error_info["title"], title_frame) title_frame_layout = QtWidgets.QHBoxLayout(title_frame) title_frame_layout.addWidget(toggle_instance_btn) title_frame_layout.addWidget(label_widget) instances_model = QtGui.QStandardItemModel() - instances = error_info["instances"] + error_info = error_info["error_info"] + + help_text_by_instance_id = {} context_validation = False if ( - not instances - or (len(instances) == 1 and instances[0] is None) + not error_info + or (len(error_info) == 1 and error_info[0][0] is None) ): context_validation = True toggle_instance_btn.setArrowType(QtCore.Qt.NoArrow) + description = self._prepare_description(error_info[0][1]) + help_text_by_instance_id[None] = description else: items = [] - for instance in instances: + for instance, exception in error_info: label = instance.data.get("label") or instance.data.get("name") item = QtGui.QStandardItem(label) item.setFlags( QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable ) - item.setData(instance.id) + item.setData(label, QtCore.Qt.ToolTipRole) + item.setData(instance.id, INSTANCE_ID_ROLE) items.append(item) + description = self._prepare_description(exception) + help_text_by_instance_id[instance.id] = description instances_model.invisibleRootItem().appendRows(items) @@ -114,17 +125,64 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): if not context_validation: toggle_instance_btn.clicked.connect(self._on_toggle_btn_click) + instances_view.selectionModel().selectionChanged.connect( + self._on_seleciton_change + ) + self._title_frame = title_frame self._toggle_instance_btn = toggle_instance_btn + self._view_layout = view_layout + self._instances_model = instances_model self._instances_view = instances_view + self._context_validation = context_validation + self._help_text_by_instance_id = help_text_by_instance_id + + def sizeHint(self): + result = super().sizeHint() + expected_width = 0 + for idx in range(self._view_layout.count()): + expected_width += self._view_layout.itemAt(idx).sizeHint().width() + + if expected_width < 200: + expected_width = 200 + + if result.width() < expected_width: + result.setWidth(expected_width) + return result + + def minimumSizeHint(self): + return self.sizeHint() + + def _prepare_description(self, exception): + dsc = exception.description + detail = exception.detail + if detail: + dsc += "

{}".format(detail) + + description = dsc + if commonmark: + description = commonmark.commonmark(dsc) + return description + def _mouse_release_callback(self): """Mark this widget as selected on click.""" self.set_selected(True) + def current_desctiption_text(self): + if self._context_validation: + return self._help_text_by_instance_id[None] + index = self._instances_view.currentIndex() + # TODO make sure instance is selected + if not index.isValid(): + index = self._instances_model.index(0, 0) + + indence_id = index.data(INSTANCE_ID_ROLE) + return self._help_text_by_instance_id[indence_id] + @property def is_selected(self): """Is widget marked a selected""" @@ -167,6 +225,9 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): else: self._toggle_instance_btn.setArrowType(QtCore.Qt.RightArrow) + def _on_seleciton_change(self): + self.instance_changed.emit(self._index) + class ActionButton(BaseClickableFrame): """Plugin's action callback button. @@ -185,13 +246,15 @@ class ActionButton(BaseClickableFrame): action_label = action.label or action.__name__ action_icon = getattr(action, "icon", None) label_widget = QtWidgets.QLabel(action_label, self) + icon_label = None if action_icon: icon_label = IconValuePixmapLabel(action_icon, self) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(5, 0, 5, 0) layout.addWidget(label_widget, 1) - layout.addWidget(icon_label, 0) + if icon_label: + layout.addWidget(icon_label, 0) self.setSizePolicy( QtWidgets.QSizePolicy.Minimum, @@ -231,6 +294,7 @@ class ValidateActionsWidget(QtWidgets.QFrame): item = self._content_layout.takeAt(0) widget = item.widget() if widget: + widget.setVisible(False) widget.deleteLater() self._actions_mapping = {} @@ -363,24 +427,23 @@ class ValidationsWidget(QtWidgets.QWidget): errors_scroll.setWidgetResizable(True) errors_widget = QtWidgets.QWidget(errors_scroll) - errors_widget.setFixedWidth(200) errors_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) errors_layout = QtWidgets.QVBoxLayout(errors_widget) errors_layout.setContentsMargins(0, 0, 0, 0) errors_scroll.setWidget(errors_widget) - error_details_widget = QtWidgets.QWidget(self) - error_details_input = QtWidgets.QTextEdit(error_details_widget) + error_details_frame = QtWidgets.QFrame(self) + error_details_input = QtWidgets.QTextEdit(error_details_frame) error_details_input.setObjectName("InfoText") error_details_input.setTextInteractionFlags( QtCore.Qt.TextBrowserInteraction ) actions_widget = ValidateActionsWidget(controller, self) - actions_widget.setFixedWidth(140) + actions_widget.setMinimumWidth(140) - error_details_layout = QtWidgets.QHBoxLayout(error_details_widget) + error_details_layout = QtWidgets.QHBoxLayout(error_details_frame) error_details_layout.addWidget(error_details_input, 1) error_details_layout.addWidget(actions_widget, 0) @@ -389,7 +452,7 @@ class ValidationsWidget(QtWidgets.QWidget): content_layout.setContentsMargins(0, 0, 0, 0) content_layout.addWidget(errors_scroll, 0) - content_layout.addWidget(error_details_widget, 1) + content_layout.addWidget(error_details_frame, 1) top_label = QtWidgets.QLabel("Publish validation report", self) top_label.setObjectName("PublishInfoMainLabel") @@ -403,7 +466,7 @@ class ValidationsWidget(QtWidgets.QWidget): self._top_label = top_label self._errors_widget = errors_widget self._errors_layout = errors_layout - self._error_details_widget = error_details_widget + self._error_details_frame = error_details_frame self._error_details_input = error_details_input self._actions_widget = actions_widget @@ -423,7 +486,7 @@ class ValidationsWidget(QtWidgets.QWidget): widget.deleteLater() self._top_label.setVisible(False) - self._error_details_widget.setVisible(False) + self._error_details_frame.setVisible(False) self._errors_widget.setVisible(False) self._actions_widget.setVisible(False) @@ -434,34 +497,35 @@ class ValidationsWidget(QtWidgets.QWidget): return self._top_label.setVisible(True) - self._error_details_widget.setVisible(True) + self._error_details_frame.setVisible(True) self._errors_widget.setVisible(True) errors_by_title = [] for plugin_info in errors: titles = [] - exception_by_title = {} - instances_by_title = {} + error_info_by_title = {} for error_info in plugin_info["errors"]: exception = error_info["exception"] title = exception.title if title not in titles: titles.append(title) - instances_by_title[title] = [] - exception_by_title[title] = exception - instances_by_title[title].append(error_info["instance"]) + error_info_by_title[title] = [] + error_info_by_title[title].append( + (error_info["instance"], exception) + ) for title in titles: errors_by_title.append({ "plugin": plugin_info["plugin"], - "exception": exception_by_title[title], - "instances": instances_by_title[title] + "error_info": error_info_by_title[title], + "title": title }) for idx, item in enumerate(errors_by_title): widget = ValidationErrorTitleWidget(idx, item, self) widget.selected.connect(self._on_select) + widget.instance_changed.connect(self._on_instance_change) self._errors_layout.addWidget(widget) self._title_widgets[idx] = widget self._error_info[idx] = item @@ -471,6 +535,8 @@ class ValidationsWidget(QtWidgets.QWidget): if self._title_widgets: self._title_widgets[0].set_selected(True) + self.updateGeometry() + def _on_select(self, index): if self._previous_select: if self._previous_select.index == index: @@ -481,10 +547,19 @@ class ValidationsWidget(QtWidgets.QWidget): error_item = self._error_info[index] - dsc = error_item["exception"].description + self._actions_widget.set_plugin(error_item["plugin"]) + + self._update_description() + + def _on_instance_change(self, index): + if self._previous_select and self._previous_select.index != index: + return + self._update_description() + + def _update_description(self): + description = self._previous_select.current_desctiption_text() if commonmark: - html = commonmark.commonmark(dsc) + html = commonmark.commonmark(description) self._error_details_input.setHtml(html) else: - self._error_details_input.setMarkdown(dsc) - self._actions_widget.set_plugin(error_item["plugin"]) + self._error_details_input.setMarkdown(description) diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index a63258efb7..fb1f0e54aa 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -535,6 +535,7 @@ class TasksCombobox(QtWidgets.QComboBox): return self._text = text + self.repaint() def paintEvent(self, event): """Paint custom text without using QLineEdit. @@ -548,6 +549,7 @@ class TasksCombobox(QtWidgets.QComboBox): self.initStyleOption(opt) if self._text is not None: opt.currentText = self._text + style = self.style() style.drawComplexControl( QtWidgets.QStyle.CC_ComboBox, opt, painter, self @@ -609,11 +611,15 @@ class TasksCombobox(QtWidgets.QComboBox): if self._selected_items: is_valid = True + valid_task_names = [] for task_name in self._selected_items: - is_valid = self._model.is_task_name_valid(asset_name, task_name) - if not is_valid: - break + _is_valid = self._model.is_task_name_valid(asset_name, task_name) + if _is_valid: + valid_task_names.append(task_name) + else: + is_valid = _is_valid + self._selected_items = valid_task_names if len(self._selected_items) == 0: self.set_selected_item("") @@ -625,6 +631,7 @@ class TasksCombobox(QtWidgets.QComboBox): if multiselection_text is None: multiselection_text = "|".join(self._selected_items) self.set_selected_item(multiselection_text) + self._set_is_valid(is_valid) def set_selected_items(self, asset_task_combinations=None): @@ -708,8 +715,7 @@ class TasksCombobox(QtWidgets.QComboBox): idx = self.findText(item_name) # Set current index (must be set to -1 if is invalid) self.setCurrentIndex(idx) - if idx < 0: - self.set_text(item_name) + self.set_text(item_name) def reset_to_origin(self): """Change to task names set with last `set_selected_items` call.""" diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 642bd17589..b74e95b227 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -84,7 +84,7 @@ class PublisherWindow(QtWidgets.QDialog): # Content # Subset widget - subset_frame = QtWidgets.QWidget(self) + subset_frame = QtWidgets.QFrame(self) subset_views_widget = BorderedLabelWidget( "Subsets to publish", subset_frame @@ -225,6 +225,9 @@ class PublisherWindow(QtWidgets.QDialog): controller.add_publish_validated_callback(self._on_publish_validated) controller.add_publish_stopped_callback(self._on_publish_stop) + # Store header for TrayPublisher + self._header_layout = header_layout + self.content_stacked_layout = content_stacked_layout self.publish_frame = publish_frame self.subset_frame = subset_frame