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