Merge pull request #3936 from pypeclub/feature/OP-4158_Creation-UI-is-part-of-main-window

Publisher: Create dialog is part of main window
This commit is contained in:
Jakub Trllo 2022-10-13 10:12:55 +02:00 committed by GitHub
commit 1ccf2b8d8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 2036 additions and 1644 deletions

View file

@ -312,6 +312,8 @@ class IPublishHost:
required = [
"get_context_data",
"update_context_data",
"get_context_title",
"get_current_context",
]
missing = []
for name in required:

View file

@ -89,8 +89,10 @@
},
"publisher": {
"error": "#AA5050",
"crash": "#FF6432",
"success": "#458056",
"warning": "#ffc671",
"tab-bg": "#16191d",
"list-view-group": {
"bg": "#434a56",
"bg-hover": "rgba(168, 175, 189, 0.3)",

View file

@ -856,6 +856,33 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
}
/* New Create/Publish UI */
PublisherTabsWidget {
background: {color:publisher:tab-bg};
}
PublisherTabBtn {
border-radius: 0px;
background: {color:bg-inputs};
font-size: 9pt;
font-weight: regular;
padding: 0.5em 1em 0.5em 1em;
}
PublisherTabBtn:disabled {
background: {color:bg-inputs};
}
PublisherTabBtn:hover {
background: {color:bg-buttons};
}
PublisherTabBtn[active="1"] {
background: {color:bg};
}
PublisherTabBtn[active="1"]:hover {
background: {color:bg};
}
#CreatorDetailedDescription {
padding-left: 5px;
padding-right: 5px;
@ -865,18 +892,16 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
}
#CreateDialogHelpButton {
background: rgba(255, 255, 255, 31);
background: {color:bg-buttons};
border-top-left-radius: 0.2em;
border-bottom-left-radius: 0.2em;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
font-size: 10pt;
font-weight: bold;
padding: 0px;
}
#CreateDialogHelpButton:hover {
background: rgba(255, 255, 255, 63);
background: {color:bg-button-hover};
}
#CreateDialogHelpButton QWidget {
background: transparent;
@ -944,19 +969,8 @@ VariantInputsWidget QToolButton {
color: {color:publisher:error};
}
#PublishFrame {
background: rgba(0, 0, 0, 127);
}
#PublishFrame[state="1"] {
background: rgb(22, 25, 29);
}
#PublishFrame[state="2"] {
background: {color:bg};
}
#PublishInfoFrame {
background: {color:bg};
border: 2px solid black;
border-radius: 0.3em;
}
@ -965,7 +979,7 @@ VariantInputsWidget QToolButton {
}
#PublishInfoFrame[state="0"] {
background: {color:publisher:error};
background: {color:publisher:crash};
}
#PublishInfoFrame[state="1"] {
@ -989,6 +1003,11 @@ VariantInputsWidget QToolButton {
font-size: 13pt;
}
ValidationArtistMessage QLabel {
font-size: 20pt;
font-weight: bold;
}
#ValidationActionButton {
border-radius: 0.2em;
padding: 4px 6px 4px 6px;
@ -1005,17 +1024,16 @@ VariantInputsWidget QToolButton {
}
#ValidationErrorTitleFrame {
background: {color:bg-inputs};
border-left: 4px solid transparent;
border-radius: 0.2em;
background: {color:bg-buttons};
}
#ValidationErrorTitleFrame:hover {
border-left-color: {color:border};
background: {color:bg-buttons-hover};
}
#ValidationErrorTitleFrame[selected="1"] {
background: {color:bg};
border-left-color: {palette:blue-light};
background: {color:bg-view-selection};
}
#ValidationErrorInstanceList {

View file

@ -1,22 +1,17 @@
import os
import copy
import inspect
import logging
import traceback
import collections
import weakref
try:
from weakref import WeakMethod
except Exception:
from openpype.lib.python_2_comp import WeakMethod
import pyblish.api
from openpype.client import get_assets
from openpype.lib.events import EventSystem
from openpype.pipeline import (
PublishValidationError,
registered_host,
legacy_io,
)
from openpype.pipeline.create import CreateContext
@ -107,17 +102,13 @@ class AssetDocsCache:
self._asset_docs = None
self._task_names_by_asset_name = {}
@property
def dbcon(self):
return self._controller.dbcon
def reset(self):
self._asset_docs = None
self._task_names_by_asset_name = {}
def _query(self):
if self._asset_docs is None:
project_name = self.dbcon.active_project()
project_name = self._controller.project_name
asset_docs = get_assets(
project_name, fields=self.projection.keys()
)
@ -360,11 +351,15 @@ class PublisherController:
dbcon (AvalonMongoDB): Connection to mongo with context.
headless (bool): Headless publishing. ATM not implemented or used.
"""
def __init__(self, dbcon=None, headless=False):
self.log = logging.getLogger("PublisherController")
self.host = registered_host()
self.headless = headless
# Inner event system of controller
self._event_system = EventSystem()
self.create_context = CreateContext(
self.host, dbcon, headless=headless, reset=False
)
@ -405,18 +400,6 @@ class PublisherController:
# Plugin iterator
self._main_thread_iter = None
# Variables where callbacks are stored
self._instances_refresh_callback_refs = set()
self._plugins_refresh_callback_refs = set()
self._publish_reset_callback_refs = set()
self._publish_started_callback_refs = set()
self._publish_validated_callback_refs = set()
self._publish_stopped_callback_refs = set()
self._publish_instance_changed_callback_refs = set()
self._publish_plugin_changed_callback_refs = set()
# State flags to prevent executing method which is already in progress
self._resetting_plugins = False
self._resetting_instances = False
@ -426,13 +409,42 @@ class PublisherController:
@property
def project_name(self):
"""Current project context."""
return self.dbcon.Session["AVALON_PROJECT"]
"""Current project context defined by host.
Returns:
str: Project name.
"""
if not hasattr(self.host, "get_current_context"):
return legacy_io.active_project()
return self.host.get_current_context()["project_name"]
@property
def dbcon(self):
"""Pointer to AvalonMongoDB in creator context."""
return self.create_context.dbcon
def current_asset_name(self):
"""Current context asset name defined by host.
Returns:
Union[str, None]: Asset name or None if asset is not set.
"""
if not hasattr(self.host, "get_current_context"):
return legacy_io.Session["AVALON_ASSET"]
return self.host.get_current_context()["asset_name"]
@property
def current_task_name(self):
"""Current context task name defined by host.
Returns:
Union[str, None]: Task name or None if task is not set.
"""
if not hasattr(self.host, "get_current_context"):
return legacy_io.Session["AVALON_TASK"]
return self.host.get_current_context()["task_name"]
@property
def instances(self):
@ -464,58 +476,35 @@ class PublisherController:
"""Publish plugins with possible attribute definitions."""
return self.create_context.plugins_with_defs
def _create_reference(self, callback):
if inspect.ismethod(callback):
ref = WeakMethod(callback)
elif callable(callback):
ref = weakref.ref(callback)
else:
raise TypeError("Expected function or method got {}".format(
str(type(callback))
))
return ref
@property
def event_system(self):
"""Inner event system for publisher controller.
def add_instances_refresh_callback(self, callback):
"""Callbacks triggered on instances refresh."""
ref = self._create_reference(callback)
self._instances_refresh_callback_refs.add(ref)
Known topics:
"show.detailed.help" - Detailed help requested (UI related).
"show.card.message" - Show card message request (UI related).
"instances.refresh.finished" - Instances are refreshed.
"plugins.refresh.finished" - Plugins refreshed.
"publish.reset.finished" - Controller reset finished.
"publish.process.started" - Publishing started. Can be started from
paused state.
"publish.process.validated" - Publishing passed validation.
"publish.process.stopped" - Publishing stopped/paused process.
"publish.process.plugin.changed" - Plugin state has changed.
"publish.process.instance.changed" - Instance state has changed.
def add_plugins_refresh_callback(self, callback):
"""Callbacks triggered on plugins refresh."""
ref = self._create_reference(callback)
self._plugins_refresh_callback_refs.add(ref)
Returns:
EventSystem: Event system which can trigger callbacks for topics.
"""
return self._event_system
def _emit_event(self, topic, data=None):
if data is None:
data = {}
self._event_system.emit(topic, data, "controller")
# --- Publish specific callbacks ---
def add_publish_reset_callback(self, callback):
"""Callbacks triggered on publishing reset."""
ref = self._create_reference(callback)
self._publish_reset_callback_refs.add(ref)
def add_publish_started_callback(self, callback):
"""Callbacks triggered on publishing start."""
ref = self._create_reference(callback)
self._publish_started_callback_refs.add(ref)
def add_publish_validated_callback(self, callback):
"""Callbacks triggered on passing last possible validation order."""
ref = self._create_reference(callback)
self._publish_validated_callback_refs.add(ref)
def add_instance_change_callback(self, callback):
"""Callbacks triggered before next publish instance process."""
ref = self._create_reference(callback)
self._publish_instance_changed_callback_refs.add(ref)
def add_plugin_change_callback(self, callback):
"""Callbacks triggered before next plugin processing."""
ref = self._create_reference(callback)
self._publish_plugin_changed_callback_refs.add(ref)
def add_publish_stopped_callback(self, callback):
"""Callbacks triggered on publishing stop (any reason)."""
ref = self._create_reference(callback)
self._publish_stopped_callback_refs.add(ref)
def get_asset_docs(self):
"""Get asset documents from cache for whole project."""
return self._asset_docs_cache.get_asset_docs()
@ -556,20 +545,6 @@ class PublisherController:
)
return result
def _trigger_callbacks(self, callbacks, *args, **kwargs):
"""Helper method to trigger callbacks stored by their rerence."""
# Trigger reset callbacks
to_remove = set()
for ref in callbacks:
callback = ref()
if callback:
callback(*args, **kwargs)
else:
to_remove.add(ref)
for ref in to_remove:
callbacks.remove(ref)
def reset(self):
"""Reset everything related to creation and publishing."""
# Stop publishing
@ -585,6 +560,8 @@ class PublisherController:
self._reset_publish()
self._reset_instances()
self.emit_card_message("Refreshed..")
def _reset_plugins(self):
"""Reset to initial state."""
if self._resetting_plugins:
@ -596,7 +573,7 @@ class PublisherController:
self._resetting_plugins = False
self._trigger_callbacks(self._plugins_refresh_callback_refs)
self._emit_event("plugins.refresh.finished")
def _reset_instances(self):
"""Reset create instances."""
@ -612,7 +589,10 @@ class PublisherController:
self._resetting_instances = False
self._trigger_callbacks(self._instances_refresh_callback_refs)
self._emit_event("instances.refresh.finished")
def emit_card_message(self, message):
self._emit_event("show.card.message", {"message": message})
def get_creator_attribute_definitions(self, instances):
"""Collect creator attribute definitions for multuple instances.
@ -709,7 +689,7 @@ class PublisherController:
creator = self.creators[creator_identifier]
creator.create(subset_name, instance_data, options)
self._trigger_callbacks(self._instances_refresh_callback_refs)
self._emit_event("instances.refresh.finished")
def save_changes(self):
"""Save changes happened during creation."""
@ -724,7 +704,7 @@ class PublisherController:
self.create_context.remove_instances(instances)
self._trigger_callbacks(self._instances_refresh_callback_refs)
self._emit_event("instances.refresh.finished")
# --- Publish specific implementations ---
@property
@ -793,7 +773,7 @@ class PublisherController:
self._publish_max_progress = len(self.publish_plugins)
self._publish_progress = 0
self._trigger_callbacks(self._publish_reset_callback_refs)
self._emit_event("publish.reset.finished")
def set_comment(self, comment):
self._publish_context.data["comment"] = comment
@ -820,7 +800,8 @@ class PublisherController:
self.save_changes()
self._publish_is_running = True
self._trigger_callbacks(self._publish_started_callback_refs)
self._emit_event("publish.process.started")
self._main_thread_processor.start()
self._publish_next_process()
@ -828,10 +809,12 @@ class PublisherController:
"""Stop or pause publishing."""
self._publish_is_running = False
self._main_thread_processor.stop()
self._trigger_callbacks(self._publish_stopped_callback_refs)
self._emit_event("publish.process.stopped")
def stop_publish(self):
"""Stop publishing process (any reason)."""
if self._publish_is_running:
self._stop_publish()
@ -892,9 +875,7 @@ class PublisherController:
)
# Trigger callbacks when validation stage is passed
if self._publish_validated:
self._trigger_callbacks(
self._publish_validated_callback_refs
)
self._emit_event("publish.process.validated")
# Stop if plugin is over validation order and process
# should process up to validation.
@ -912,9 +893,14 @@ class PublisherController:
self._publish_report.add_plugin_iter(plugin, self._publish_context)
# Trigger callback that new plugin is going to be processed
self._trigger_callbacks(
self._publish_plugin_changed_callback_refs, plugin
plugin_label = plugin.__name__
if hasattr(plugin, "label") and plugin.label:
plugin_label = plugin.label
self._emit_event(
"publish.process.plugin.changed",
{"plugin_label": plugin_label}
)
# Plugin is instance plugin
if plugin.__instanceEnabled__:
instances = pyblish.logic.instances_by_plugin(
@ -928,11 +914,15 @@ class PublisherController:
if instance.data.get("publish") is False:
continue
self._trigger_callbacks(
self._publish_instance_changed_callback_refs,
self._publish_context,
instance
instance_label = (
instance.data.get("label")
or instance.data["name"]
)
self._emit_event(
"publish.process.instance.changed",
{"instance_label": instance_label}
)
yield MainThreadItem(
self._process_and_continue, plugin, instance
)
@ -944,10 +934,14 @@ class PublisherController:
[plugin], families
)
if plugins:
self._trigger_callbacks(
self._publish_instance_changed_callback_refs,
self._publish_context,
None
instance_label = (
self._publish_context.data.get("label")
or self._publish_context.data.get("name")
or "Context"
)
self._emit_event(
"publish.process.instance.changed",
{"instance_label": instance_label}
)
yield MainThreadItem(
self._process_and_continue, plugin, None

View file

@ -331,7 +331,7 @@ class DetailsPopup(QtWidgets.QDialog):
self.closed.emit()
class PublishReportViewerWidget(QtWidgets.QWidget):
class PublishReportViewerWidget(QtWidgets.QFrame):
def __init__(self, parent=None):
super(PublishReportViewerWidget, self).__init__(parent)

View file

@ -3,35 +3,20 @@ from .icons import (
get_pixmap,
get_icon
)
from .border_label_widget import (
BorderedLabelWidget
)
from .widgets import (
SubsetAttributesWidget,
StopBtn,
ResetBtn,
ValidateBtn,
PublishBtn,
CreateInstanceBtn,
RemoveInstanceBtn,
ChangeViewBtn
)
from .publish_widget import (
PublishFrame
)
from .create_dialog import (
CreateDialog
)
from .card_view_widgets import (
InstanceCardView
)
from .list_view_widgets import (
InstanceListView
from .help_widget import (
HelpButton,
HelpDialog,
)
from .publish_frame import PublishFrame
from .tabs_widget import PublisherTabsWidget
from .overview_widget import OverviewWidget
from .validations_widget import ValidationsWidget
__all__ = (
@ -39,22 +24,17 @@ __all__ = (
"get_pixmap",
"get_icon",
"SubsetAttributesWidget",
"BorderedLabelWidget",
"StopBtn",
"ResetBtn",
"ValidateBtn",
"PublishBtn",
"CreateInstanceBtn",
"RemoveInstanceBtn",
"ChangeViewBtn",
"HelpButton",
"HelpDialog",
"PublishFrame",
"CreateDialog",
"InstanceCardView",
"InstanceListView",
"PublisherTabsWidget",
"OverviewWidget",
"ValidationsWidget",
)

View file

@ -13,18 +13,17 @@ from openpype.tools.utils.assets_widget import (
)
class CreateDialogAssetsWidget(SingleSelectAssetsWidget):
class CreateWidgetAssetsWidget(SingleSelectAssetsWidget):
current_context_required = QtCore.Signal()
header_height_changed = QtCore.Signal(int)
def __init__(self, controller, parent):
self._controller = controller
super(CreateDialogAssetsWidget, self).__init__(None, parent)
super(CreateWidgetAssetsWidget, self).__init__(None, parent)
self.set_refresh_btn_visibility(False)
self.set_current_asset_btn_visibility(False)
self._current_asset_name = None
self._last_selection = None
self._enabled = None
@ -42,11 +41,11 @@ class CreateDialogAssetsWidget(SingleSelectAssetsWidget):
self.header_height_changed.emit(height)
def resizeEvent(self, event):
super(CreateDialogAssetsWidget, self).resizeEvent(event)
super(CreateWidgetAssetsWidget, self).resizeEvent(event)
self._check_header_height()
def showEvent(self, event):
super(CreateDialogAssetsWidget, self).showEvent(event)
super(CreateWidgetAssetsWidget, self).showEvent(event)
self._check_header_height()
def _on_current_asset_click(self):
@ -63,19 +62,19 @@ class CreateDialogAssetsWidget(SingleSelectAssetsWidget):
self.select_asset(self._last_selection)
def _select_indexes(self, *args, **kwargs):
super(CreateDialogAssetsWidget, self)._select_indexes(*args, **kwargs)
super(CreateWidgetAssetsWidget, self)._select_indexes(*args, **kwargs)
if self._enabled:
return
self._last_selection = self.get_selected_asset_id()
self._clear_selection()
def set_current_asset_name(self, asset_name):
self._current_asset_name = asset_name
def update_current_asset(self):
# Hide set current asset if there is no one
self.set_current_asset_btn_visibility(asset_name is not None)
asset_name = self._get_current_session_asset()
self.set_current_asset_btn_visibility(bool(asset_name))
def _get_current_session_asset(self):
return self._current_asset_name
return self._controller.current_asset_name
def _create_source_model(self):
return AssetsHierarchyModel(self._controller)

View file

@ -3,11 +3,6 @@ import re
import traceback
import copy
import qtawesome
try:
import commonmark
except Exception:
commonmark = None
from Qt import QtWidgets, QtCore, QtGui
from openpype.client import get_asset_by_name, get_subsets
@ -16,15 +11,14 @@ from openpype.pipeline.create import (
SUBSET_NAME_ALLOWED_SYMBOLS,
TaskNotSetError,
)
from openpype.tools.utils import (
ErrorMessageBox,
MessageOverlayObject,
ClickableFrame,
)
from openpype.tools.utils import ErrorMessageBox
from .widgets import IconValuePixmapLabel
from .assets_widget import CreateDialogAssetsWidget
from .tasks_widget import CreateDialogTasksWidget
from .widgets import (
IconValuePixmapLabel,
CreateBtn,
)
from .assets_widget import CreateWidgetAssetsWidget
from .tasks_widget import CreateWidgetTasksWidget
from .precreate_widget import PreCreateWidget
from ..constants import (
VARIANT_TOOLTIP,
@ -118,8 +112,6 @@ class CreateErrorMessageBox(ErrorMessageBox):
# TODO add creator identifier/label to details
class CreatorShortDescWidget(QtWidgets.QWidget):
height_changed = QtCore.Signal(int)
def __init__(self, parent=None):
super(CreatorShortDescWidget, self).__init__(parent=parent)
@ -158,22 +150,6 @@ class CreatorShortDescWidget(QtWidgets.QWidget):
self._family_label = family_label
self._description_label = description_label
self._last_height = None
def _check_height_change(self):
height = self.height()
if height != self._last_height:
self._last_height = height
self.height_changed.emit(height)
def showEvent(self, event):
super(CreatorShortDescWidget, self).showEvent(event)
self._check_height_change()
def resizeEvent(self, event):
super(CreatorShortDescWidget, self).resizeEvent(event)
self._check_height_change()
def set_plugin(self, plugin=None):
if not plugin:
self._icon_widget.set_icon_def(None)
@ -190,122 +166,14 @@ class CreatorShortDescWidget(QtWidgets.QWidget):
self._description_label.setText(description)
class HelpButton(ClickableFrame):
resized = QtCore.Signal(int)
question_mark_icon_name = "fa.question"
help_icon_name = "fa.question-circle"
hide_icon_name = "fa.angle-left"
def __init__(self, *args, **kwargs):
super(HelpButton, self).__init__(*args, **kwargs)
self.setObjectName("CreateDialogHelpButton")
question_mark_label = QtWidgets.QLabel(self)
help_widget = QtWidgets.QWidget(self)
help_question = QtWidgets.QLabel(help_widget)
help_label = QtWidgets.QLabel("Help", help_widget)
hide_icon = QtWidgets.QLabel(help_widget)
help_layout = QtWidgets.QHBoxLayout(help_widget)
help_layout.setContentsMargins(0, 0, 5, 0)
help_layout.addWidget(help_question, 0)
help_layout.addWidget(help_label, 0)
help_layout.addStretch(1)
help_layout.addWidget(hide_icon, 0)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(question_mark_label, 0)
layout.addWidget(help_widget, 1)
help_widget.setVisible(False)
self._question_mark_label = question_mark_label
self._help_widget = help_widget
self._help_question = help_question
self._hide_icon = hide_icon
self._expanded = None
self.set_expanded()
def set_expanded(self, expanded=None):
if self._expanded is expanded:
if expanded is not None:
return
expanded = False
self._expanded = expanded
self._help_widget.setVisible(expanded)
self._update_content()
def _update_content(self):
width = self.get_icon_width()
if self._expanded:
question_mark_pix = QtGui.QPixmap(width, width)
question_mark_pix.fill(QtCore.Qt.transparent)
else:
question_mark_icon = qtawesome.icon(
self.question_mark_icon_name, color=QtCore.Qt.white
)
question_mark_pix = question_mark_icon.pixmap(width, width)
hide_icon = qtawesome.icon(
self.hide_icon_name, color=QtCore.Qt.white
)
help_question_icon = qtawesome.icon(
self.help_icon_name, color=QtCore.Qt.white
)
self._question_mark_label.setPixmap(question_mark_pix)
self._question_mark_label.setMaximumWidth(width)
self._hide_icon.setPixmap(hide_icon.pixmap(width, width))
self._help_question.setPixmap(help_question_icon.pixmap(width, width))
def get_icon_width(self):
metrics = self.fontMetrics()
return metrics.height()
def set_pos_and_size(self, pos_x, pos_y, width, height):
update_icon = self.height() != height
self.move(pos_x, pos_y)
self.resize(width, height)
if update_icon:
self._update_content()
self.updateGeometry()
def showEvent(self, event):
super(HelpButton, self).showEvent(event)
self.resized.emit(self.height())
def resizeEvent(self, event):
super(HelpButton, self).resizeEvent(event)
self.resized.emit(self.height())
class CreateDialog(QtWidgets.QDialog):
default_size = (1000, 560)
def __init__(
self, controller, asset_name=None, task_name=None, parent=None
):
super(CreateDialog, self).__init__(parent)
class CreateWidget(QtWidgets.QWidget):
def __init__(self, controller, parent=None):
super(CreateWidget, self).__init__(parent)
self.setWindowTitle("Create new instance")
self.controller = controller
self._controller = controller
if asset_name is None:
asset_name = self.dbcon.Session.get("AVALON_ASSET")
if task_name is None:
task_name = self.dbcon.Session.get("AVALON_TASK")
self._asset_name = asset_name
self._task_name = task_name
self._last_pos = None
self._asset_doc = None
self._subset_names = None
self._selected_creator = None
@ -318,12 +186,12 @@ class CreateDialog(QtWidgets.QDialog):
self._name_pattern = name_pattern
self._compiled_name_pattern = re.compile(name_pattern)
overlay_object = MessageOverlayObject(self)
main_splitter_widget = QtWidgets.QSplitter(self)
context_widget = QtWidgets.QWidget(self)
context_widget = QtWidgets.QWidget(main_splitter_widget)
assets_widget = CreateDialogAssetsWidget(controller, context_widget)
tasks_widget = CreateDialogTasksWidget(controller, context_widget)
assets_widget = CreateWidgetAssetsWidget(controller, context_widget)
tasks_widget = CreateWidgetTasksWidget(controller, context_widget)
context_layout = QtWidgets.QVBoxLayout(context_widget)
context_layout.setContentsMargins(0, 0, 0, 0)
@ -332,21 +200,44 @@ class CreateDialog(QtWidgets.QDialog):
context_layout.addWidget(tasks_widget, 1)
# --- Creators view ---
creators_header_widget = QtWidgets.QWidget(self)
header_label_widget = QtWidgets.QLabel(
"Choose family:", creators_header_widget
)
creators_header_layout = QtWidgets.QHBoxLayout(creators_header_widget)
creators_header_layout.setContentsMargins(0, 0, 0, 0)
creators_header_layout.addWidget(header_label_widget, 1)
creators_widget = QtWidgets.QWidget(main_splitter_widget)
creators_view = QtWidgets.QListView(self)
creator_short_desc_widget = CreatorShortDescWidget(creators_widget)
attr_separator_widget = QtWidgets.QWidget(creators_widget)
attr_separator_widget.setObjectName("Separator")
attr_separator_widget.setMinimumHeight(1)
attr_separator_widget.setMaximumHeight(1)
creators_splitter = QtWidgets.QSplitter(creators_widget)
creators_view_widget = QtWidgets.QWidget(creators_splitter)
creator_view_label = QtWidgets.QLabel(
"Choose publish type", creators_view_widget
)
creators_view = QtWidgets.QListView(creators_view_widget)
creators_model = QtGui.QStandardItemModel()
creators_sort_model = QtCore.QSortFilterProxyModel()
creators_sort_model.setSourceModel(creators_model)
creators_view.setModel(creators_sort_model)
variant_widget = VariantInputsWidget(self)
creators_view_layout = QtWidgets.QVBoxLayout(creators_view_widget)
creators_view_layout.setContentsMargins(0, 0, 0, 0)
creators_view_layout.addWidget(creator_view_label, 0)
creators_view_layout.addWidget(creators_view, 1)
# --- Creator attr defs ---
creators_attrs_widget = QtWidgets.QWidget(creators_splitter)
variant_subset_label = QtWidgets.QLabel(
"Create options", creators_attrs_widget
)
variant_subset_widget = QtWidgets.QWidget(creators_attrs_widget)
# Variant and subset input
variant_widget = VariantInputsWidget(creators_attrs_widget)
variant_input = QtWidgets.QLineEdit(variant_widget)
variant_input.setObjectName("VariantInput")
@ -365,39 +256,20 @@ class CreateDialog(QtWidgets.QDialog):
variant_layout.addWidget(variant_input, 1)
variant_layout.addWidget(variant_hints_btn, 0, QtCore.Qt.AlignVCenter)
subset_name_input = QtWidgets.QLineEdit(self)
subset_name_input = QtWidgets.QLineEdit(variant_subset_widget)
subset_name_input.setEnabled(False)
form_layout = QtWidgets.QFormLayout()
form_layout.addRow("Variant:", variant_widget)
form_layout.addRow("Subset:", subset_name_input)
mid_widget = QtWidgets.QWidget(self)
mid_layout = QtWidgets.QVBoxLayout(mid_widget)
mid_layout.setContentsMargins(0, 0, 0, 0)
mid_layout.addWidget(creators_header_widget, 0)
mid_layout.addWidget(creators_view, 1)
mid_layout.addLayout(form_layout, 0)
# ------------
# --- Creator short info and attr defs ---
creator_attrs_widget = QtWidgets.QWidget(self)
creator_short_desc_widget = CreatorShortDescWidget(
creator_attrs_widget
)
attr_separator_widget = QtWidgets.QWidget(self)
attr_separator_widget.setObjectName("Separator")
attr_separator_widget.setMinimumHeight(1)
attr_separator_widget.setMaximumHeight(1)
variant_subset_layout = QtWidgets.QFormLayout(variant_subset_widget)
variant_subset_layout.setContentsMargins(0, 0, 0, 0)
variant_subset_layout.addRow("Variant", variant_widget)
variant_subset_layout.addRow("Subset", subset_name_input)
# Precreate attributes widget
pre_create_widget = PreCreateWidget(creator_attrs_widget)
pre_create_widget = PreCreateWidget(creators_attrs_widget)
# Create button
create_btn_wrapper = QtWidgets.QWidget(creator_attrs_widget)
create_btn = QtWidgets.QPushButton("Create", create_btn_wrapper)
create_btn_wrapper = QtWidgets.QWidget(creators_attrs_widget)
create_btn = CreateBtn(create_btn_wrapper)
create_btn.setEnabled(False)
create_btn_wrap_layout = QtWidgets.QHBoxLayout(create_btn_wrapper)
@ -405,79 +277,45 @@ class CreateDialog(QtWidgets.QDialog):
create_btn_wrap_layout.addStretch(1)
create_btn_wrap_layout.addWidget(create_btn, 0)
creator_attrs_layout = QtWidgets.QVBoxLayout(creator_attrs_widget)
creator_attrs_layout.setContentsMargins(0, 0, 0, 0)
creator_attrs_layout.addWidget(creator_short_desc_widget, 0)
creator_attrs_layout.addWidget(attr_separator_widget, 0)
creator_attrs_layout.addWidget(pre_create_widget, 1)
creator_attrs_layout.addWidget(create_btn_wrapper, 0)
# -------------------------------------
creators_attrs_layout = QtWidgets.QVBoxLayout(creators_attrs_widget)
creators_attrs_layout.setContentsMargins(0, 0, 0, 0)
creators_attrs_layout.addWidget(variant_subset_label, 0)
creators_attrs_layout.addWidget(variant_subset_widget, 0)
creators_attrs_layout.addWidget(pre_create_widget, 1)
creators_attrs_layout.addWidget(create_btn_wrapper, 0)
creators_splitter.addWidget(creators_view_widget)
creators_splitter.addWidget(creators_attrs_widget)
creators_splitter.setStretchFactor(0, 1)
creators_splitter.setStretchFactor(1, 2)
creators_layout = QtWidgets.QVBoxLayout(creators_widget)
creators_layout.setContentsMargins(0, 0, 0, 0)
creators_layout.addWidget(creator_short_desc_widget, 0)
creators_layout.addWidget(attr_separator_widget, 0)
creators_layout.addWidget(creators_splitter, 1)
# ------------
# --- Detailed information about creator ---
# Detailed description of creator
detail_description_widget = QtWidgets.QWidget(self)
detail_placoholder_widget = QtWidgets.QWidget(
detail_description_widget
)
detail_placoholder_widget.setAttribute(
QtCore.Qt.WA_TranslucentBackground
)
detail_description_input = QtWidgets.QTextEdit(
detail_description_widget
)
detail_description_input.setObjectName("CreatorDetailedDescription")
detail_description_input.setTextInteractionFlags(
QtCore.Qt.TextBrowserInteraction
)
detail_description_layout = QtWidgets.QVBoxLayout(
detail_description_widget
)
detail_description_layout.setContentsMargins(0, 0, 0, 0)
detail_description_layout.setSpacing(0)
detail_description_layout.addWidget(detail_placoholder_widget, 0)
detail_description_layout.addWidget(detail_description_input, 1)
detail_description_widget.setVisible(False)
# TODO this has no way how can be showed now
# -------------------------------------------
splitter_widget = QtWidgets.QSplitter(self)
splitter_widget.addWidget(context_widget)
splitter_widget.addWidget(mid_widget)
splitter_widget.addWidget(creator_attrs_widget)
splitter_widget.addWidget(detail_description_widget)
splitter_widget.setStretchFactor(0, 1)
splitter_widget.setStretchFactor(1, 1)
splitter_widget.setStretchFactor(2, 1)
splitter_widget.setStretchFactor(3, 1)
main_splitter_widget.addWidget(context_widget)
main_splitter_widget.addWidget(creators_widget)
main_splitter_widget.setStretchFactor(0, 1)
main_splitter_widget.setStretchFactor(1, 3)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(splitter_widget, 1)
# Floating help button
# - Create this button as last to be fully visible
help_btn = HelpButton(self)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(main_splitter_widget, 1)
prereq_timer = QtCore.QTimer()
prereq_timer.setInterval(50)
prereq_timer.setSingleShot(True)
desc_width_anim_timer = QtCore.QTimer()
desc_width_anim_timer.setInterval(10)
prereq_timer.timeout.connect(self._invalidate_prereq)
desc_width_anim_timer.timeout.connect(self._on_desc_animation)
help_btn.clicked.connect(self._on_help_btn)
help_btn.resized.connect(self._on_help_btn_resize)
assets_widget.header_height_changed.connect(
self._on_asset_filter_height_change
)
create_btn.clicked.connect(self._on_create)
variant_widget.resized.connect(self._on_variant_widget_resize)
variant_input.returnPressed.connect(self._on_create)
@ -492,16 +330,14 @@ class CreateDialog(QtWidgets.QDialog):
self._on_current_session_context_request
)
tasks_widget.task_changed.connect(self._on_task_change)
creator_short_desc_widget.height_changed.connect(
self._on_description_height_change
controller.event_system.add_callback(
"plugins.refresh.finished", self._on_plugins_refresh
)
splitter_widget.splitterMoved.connect(self._on_splitter_move)
controller.add_plugins_refresh_callback(self._on_plugins_refresh)
self._main_splitter_widget = main_splitter_widget
self._overlay_object = overlay_object
self._splitter_widget = splitter_widget
self._creators_splitter = creators_splitter
self._context_widget = context_widget
self._assets_widget = assets_widget
@ -514,7 +350,6 @@ class CreateDialog(QtWidgets.QDialog):
self.variant_hints_menu = variant_hints_menu
self.variant_hints_group = variant_hints_group
self._creators_header_widget = creators_header_widget
self._creators_model = creators_model
self._creators_sort_model = creators_sort_model
self._creators_view = creators_view
@ -524,26 +359,16 @@ class CreateDialog(QtWidgets.QDialog):
self._pre_create_widget = pre_create_widget
self._attr_separator_widget = attr_separator_widget
self._detail_placoholder_widget = detail_placoholder_widget
self._detail_description_widget = detail_description_widget
self._detail_description_input = detail_description_input
self._help_btn = help_btn
self._prereq_timer = prereq_timer
self._first_show = True
# Description animation
self._description_size_policy = detail_description_widget.sizePolicy()
self._desc_width_anim_timer = desc_width_anim_timer
self._desc_widget_step = 0
self._last_description_width = None
self._last_full_width = 0
self._expected_description_width = 0
self._last_desc_max_width = None
self._other_widgets_widths = []
@property
def current_asset_name(self):
return self._controller.current_asset_name
def _emit_message(self, message):
self._overlay_object.add_message(message)
@property
def current_task_name(self):
return self._controller.current_task_name
def _context_change_is_enabled(self):
return self._context_widget.isEnabled()
@ -554,7 +379,7 @@ class CreateDialog(QtWidgets.QDialog):
asset_name = self._assets_widget.get_selected_asset_name()
if asset_name is None:
asset_name = self._asset_name
asset_name = self.current_asset_name
return asset_name
def _get_task_name(self):
@ -566,13 +391,9 @@ class CreateDialog(QtWidgets.QDialog):
task_name = self._tasks_widget.get_selected_task_name()
if not task_name:
task_name = self._task_name
task_name = self.current_task_name
return task_name
@property
def dbcon(self):
return self.controller.dbcon
def _set_context_enabled(self, enabled):
self._assets_widget.set_enabled(enabled)
self._tasks_widget.set_enabled(enabled)
@ -601,7 +422,7 @@ class CreateDialog(QtWidgets.QDialog):
# data
self._refresh_creators()
self._assets_widget.set_current_asset_name(self._asset_name)
self._assets_widget.update_current_asset()
self._assets_widget.select_asset_by_name(asset_name)
self._tasks_widget.set_asset_name(asset_name)
self._tasks_widget.select_task_name(task_name)
@ -611,10 +432,6 @@ class CreateDialog(QtWidgets.QDialog):
def _invalidate_prereq_deffered(self):
self._prereq_timer.start()
def _on_asset_filter_height_change(self, height):
self._creators_header_widget.setMinimumHeight(height)
self._creators_header_widget.setMaximumHeight(height)
def _invalidate_prereq(self):
prereq_available = True
creator_btn_tooltips = []
@ -660,7 +477,7 @@ class CreateDialog(QtWidgets.QDialog):
if asset_name is None:
return
project_name = self.dbcon.active_project()
project_name = self._controller.project_name
asset_doc = get_asset_by_name(project_name, asset_name)
self._asset_doc = asset_doc
@ -689,7 +506,7 @@ class CreateDialog(QtWidgets.QDialog):
# Add new families
new_creators = set()
for identifier, creator in self.controller.manual_creators.items():
for identifier, creator in self._controller.manual_creators.items():
# TODO add details about creator
new_creators.add(identifier)
if identifier in existing_items:
@ -729,8 +546,7 @@ class CreateDialog(QtWidgets.QDialog):
def _on_plugins_refresh(self):
# Trigger refresh only if is visible
if self.isVisible():
self.refresh()
self.refresh()
def _on_asset_change(self):
self._refresh_asset()
@ -746,14 +562,9 @@ class CreateDialog(QtWidgets.QDialog):
def _on_current_session_context_request(self):
self._assets_widget.set_current_session_asset()
if self._task_name:
self._tasks_widget.select_task_name(self._task_name)
def _on_description_height_change(self):
# Use separator's 'y' position as height
height = self._attr_separator_widget.y()
self._detail_placoholder_widget.setMinimumHeight(height)
self._detail_placoholder_widget.setMaximumHeight(height)
task_name = self.current_task_name
if task_name:
self._tasks_widget.select_task_name(task_name)
def _on_creator_item_change(self, new_index, _old_index):
identifier = None
@ -761,196 +572,21 @@ class CreateDialog(QtWidgets.QDialog):
identifier = new_index.data(CREATOR_IDENTIFIER_ROLE)
self._set_creator_by_identifier(identifier)
def _update_help_btn(self):
short_desc_rect = self._creator_short_desc_widget.rect()
# point = short_desc_rect.topRight()
point = short_desc_rect.center()
mapped_point = self._creator_short_desc_widget.mapTo(self, point)
# pos_y = mapped_point.y()
center_pos_y = mapped_point.y()
icon_width = self._help_btn.get_icon_width()
_height = int(icon_width * 2.5)
height = min(_height, short_desc_rect.height())
pos_y = center_pos_y - int(height / 2)
pos_x = self.width() - icon_width
if self._detail_placoholder_widget.isVisible():
pos_x -= (
self._detail_placoholder_widget.width()
+ self._splitter_widget.handle(3).width()
)
width = self.width() - pos_x
self._help_btn.set_pos_and_size(
max(0, pos_x), max(0, pos_y),
width, height
)
def _on_help_btn_resize(self, height):
if self._creator_short_desc_widget.height() != height:
self._update_help_btn()
def _on_splitter_move(self, *args):
self._update_help_btn()
def _on_help_btn(self):
if self._desc_width_anim_timer.isActive():
return
final_size = self.size()
cur_sizes = self._splitter_widget.sizes()
if self._desc_widget_step == 0:
now_visible = self._detail_description_widget.isVisible()
else:
now_visible = self._desc_widget_step > 0
sizes = []
for idx, value in enumerate(cur_sizes):
if idx < 3:
sizes.append(value)
self._last_full_width = final_size.width()
self._other_widgets_widths = list(sizes)
if now_visible:
cur_desc_width = self._detail_description_widget.width()
if cur_desc_width < 1:
cur_desc_width = 2
step_size = int(cur_desc_width / 5)
if step_size < 1:
step_size = 1
step_size *= -1
expected_width = 0
desc_width = cur_desc_width - 1
width = final_size.width() - 1
min_max = desc_width
self._last_description_width = cur_desc_width
else:
self._detail_description_widget.setVisible(True)
handle = self._splitter_widget.handle(3)
desc_width = handle.sizeHint().width()
if self._last_description_width:
expected_width = self._last_description_width
else:
hint = self._detail_description_widget.sizeHint()
expected_width = hint.width()
width = final_size.width() + desc_width
step_size = int(expected_width / 5)
if step_size < 1:
step_size = 1
min_max = 0
if self._last_desc_max_width is None:
self._last_desc_max_width = (
self._detail_description_widget.maximumWidth()
)
self._detail_description_widget.setMinimumWidth(min_max)
self._detail_description_widget.setMaximumWidth(min_max)
self._expected_description_width = expected_width
self._desc_widget_step = step_size
self._desc_width_anim_timer.start()
sizes.append(desc_width)
final_size.setWidth(width)
self._splitter_widget.setSizes(sizes)
self.resize(final_size)
self._help_btn.set_expanded(not now_visible)
def _on_desc_animation(self):
current_width = self._detail_description_widget.width()
desc_width = None
last_step = False
growing = self._desc_widget_step > 0
# Growing
if growing:
if current_width < self._expected_description_width:
desc_width = current_width + self._desc_widget_step
if desc_width >= self._expected_description_width:
desc_width = self._expected_description_width
last_step = True
# Decreasing
elif self._desc_widget_step < 0:
if current_width > self._expected_description_width:
desc_width = current_width + self._desc_widget_step
if desc_width <= self._expected_description_width:
desc_width = self._expected_description_width
last_step = True
if desc_width is None:
self._desc_widget_step = 0
self._desc_width_anim_timer.stop()
return
if last_step and not growing:
self._detail_description_widget.setVisible(False)
QtWidgets.QApplication.processEvents()
width = self._last_full_width
handle_width = self._splitter_widget.handle(3).width()
if growing:
width += (handle_width + desc_width)
else:
width -= self._last_description_width
if last_step:
width -= handle_width
else:
width += desc_width
if not last_step or growing:
self._detail_description_widget.setMaximumWidth(desc_width)
self._detail_description_widget.setMinimumWidth(desc_width)
window_size = self.size()
window_size.setWidth(width)
self.resize(window_size)
if not last_step:
return
self._desc_widget_step = 0
self._desc_width_anim_timer.stop()
if not growing:
return
self._detail_description_widget.setMinimumWidth(0)
self._detail_description_widget.setMaximumWidth(
self._last_desc_max_width
)
self._detail_description_widget.setSizePolicy(
self._description_size_policy
)
sizes = list(self._other_widgets_widths)
sizes.append(desc_width)
self._splitter_widget.setSizes(sizes)
def _set_creator_detailed_text(self, creator):
if not creator:
self._detail_description_input.setPlainText("")
return
detailed_description = creator.get_detail_description() or ""
if commonmark:
html = commonmark.commonmark(detailed_description)
self._detail_description_input.setHtml(html)
else:
self._detail_description_input.setMarkdown(detailed_description)
# TODO implement
description = ""
if creator is not None:
description = creator.get_detail_description() or description
self._controller.event_system.emit(
"show.detailed.help",
{
"message": description
},
"create.widget"
)
def _set_creator_by_identifier(self, identifier):
creator = self.controller.manual_creators.get(identifier)
creator = self._controller.manual_creators.get(identifier)
self._set_creator(creator)
def _set_creator(self, creator):
@ -1034,7 +670,7 @@ class CreateDialog(QtWidgets.QDialog):
self.subset_name_input.setText("< Valid variant >")
return
project_name = self.controller.project_name
project_name = self._controller.project_name
task_name = self._get_task_name()
asset_doc = copy.deepcopy(self._asset_doc)
@ -1116,41 +752,19 @@ class CreateDialog(QtWidgets.QDialog):
self.variant_input.style().polish(self.variant_input)
def _on_first_show(self):
center = self.rect().center()
width, height = self.default_size
self.resize(width, height)
part = int(width / 7)
self._splitter_widget.setSizes(
[part * 2, part * 2, width - (part * 4)]
)
new_pos = self.mapToGlobal(center)
new_pos.setX(new_pos.x() - int(self.width() / 2))
new_pos.setY(new_pos.y() - int(self.height() / 2))
self.move(new_pos)
def moveEvent(self, event):
super(CreateDialog, self).moveEvent(event)
self._last_pos = self.pos()
width = self.width()
part = int(width / 4)
rem_width = width - part
self._main_splitter_widget.setSizes([part, rem_width])
rem_width = rem_width - part
self._creators_splitter.setSizes([part, rem_width])
def showEvent(self, event):
super(CreateDialog, self).showEvent(event)
super(CreateWidget, self).showEvent(event)
if self._first_show:
self._first_show = False
self._on_first_show()
if self._last_pos is not None:
self.move(self._last_pos)
self._update_help_btn()
self.refresh()
def resizeEvent(self, event):
super(CreateDialog, self).resizeEvent(event)
self._update_help_btn()
def _on_create(self):
indexes = self._creators_view.selectedIndexes()
if not indexes or len(indexes) > 1:
@ -1186,7 +800,7 @@ class CreateDialog(QtWidgets.QDialog):
error_msg = None
formatted_traceback = None
try:
self.controller.create(
self._controller.create(
creator_identifier,
subset_name,
instance_data,
@ -1207,7 +821,7 @@ class CreateDialog(QtWidgets.QDialog):
if error_msg is None:
self._set_creator(self._selected_creator)
self._emit_message("Creation finished...")
self._controller.emit_card_message("Creation finished...")
else:
box = CreateErrorMessageBox(
creator_label,

View file

@ -0,0 +1,82 @@
try:
import commonmark
except Exception:
commonmark = None
from Qt import QtWidgets, QtCore
class HelpButton(QtWidgets.QPushButton):
"""Button used to trigger help dialog."""
def __init__(self, parent):
super(HelpButton, self).__init__(parent)
self.setObjectName("CreateDialogHelpButton")
self.setText("?")
class HelpWidget(QtWidgets.QWidget):
"""Widget showing help for single functionality."""
def __init__(self, parent):
super(HelpWidget, self).__init__(parent)
# TODO add hints what to help with?
detail_description_input = QtWidgets.QTextEdit(self)
detail_description_input.setObjectName("CreatorDetailedDescription")
detail_description_input.setTextInteractionFlags(
QtCore.Qt.TextBrowserInteraction
)
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
main_layout.addWidget(detail_description_input, 1)
self._detail_description_input = detail_description_input
self.set_detailed_text()
def set_detailed_text(self, text=None):
if not text:
text = "We didn't prepare help for this part..."
if commonmark:
html = commonmark.commonmark(text)
self._detail_description_input.setHtml(html)
else:
self._detail_description_input.setMarkdown(text)
class HelpDialog(QtWidgets.QDialog):
default_width = 530
default_height = 340
def __init__(self, controller, parent):
super(HelpDialog, self).__init__(parent)
self.setWindowTitle("Help dialog")
help_content = HelpWidget(self)
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.addWidget(help_content, 1)
controller.event_system.add_callback(
"show.detailed.help", self._on_help_request
)
self._controller = controller
self._help_content = help_content
def _on_help_request(self, event):
message = event.get("message")
self.set_detailed_text(message)
def set_detailed_text(self, text=None):
self._help_content.set_detailed_text(text)
def showEvent(self, event):
super(HelpDialog, self).showEvent(event)
self.resize(self.default_width, self.default_height)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before After
Before After

View file

@ -0,0 +1,368 @@
from Qt import QtWidgets, QtCore
from .border_label_widget import BorderedLabelWidget
from .card_view_widgets import InstanceCardView
from .list_view_widgets import InstanceListView
from .widgets import (
SubsetAttributesWidget,
CreateInstanceBtn,
RemoveInstanceBtn,
ChangeViewBtn,
)
from .create_widget import CreateWidget
class OverviewWidget(QtWidgets.QFrame):
active_changed = QtCore.Signal()
instance_context_changed = QtCore.Signal()
create_requested = QtCore.Signal()
anim_end_value = 200
anim_duration = 200
def __init__(self, controller, parent):
super(OverviewWidget, self).__init__(parent)
self._refreshing_instances = False
self._controller = controller
create_widget = CreateWidget(controller, self)
# --- Created Subsets/Instances ---
# Common widget for creation and overview
subset_views_widget = BorderedLabelWidget(
"Subsets to publish", self
)
subset_view_cards = InstanceCardView(controller, subset_views_widget)
subset_list_view = InstanceListView(controller, subset_views_widget)
subset_views_layout = QtWidgets.QStackedLayout()
subset_views_layout.addWidget(subset_view_cards)
subset_views_layout.addWidget(subset_list_view)
subset_views_layout.setCurrentWidget(subset_view_cards)
# Buttons at the bottom of subset view
create_btn = CreateInstanceBtn(self)
delete_btn = RemoveInstanceBtn(self)
change_view_btn = ChangeViewBtn(self)
# --- Overview ---
# Subset details widget
subset_attributes_wrap = BorderedLabelWidget(
"Publish options", self
)
subset_attributes_widget = SubsetAttributesWidget(
controller, subset_attributes_wrap
)
subset_attributes_wrap.set_center_widget(subset_attributes_widget)
# Layout of buttons at the bottom of subset view
subset_view_btns_layout = QtWidgets.QHBoxLayout()
subset_view_btns_layout.setContentsMargins(0, 5, 0, 0)
subset_view_btns_layout.addWidget(create_btn)
subset_view_btns_layout.addSpacing(5)
subset_view_btns_layout.addWidget(delete_btn)
subset_view_btns_layout.addStretch(1)
subset_view_btns_layout.addWidget(change_view_btn)
# Layout of view and buttons
# - widget 'subset_view_widget' is necessary
# - only layout won't be resized automatically to minimum size hint
# on child resize request!
subset_view_widget = QtWidgets.QWidget(subset_views_widget)
subset_view_layout = QtWidgets.QVBoxLayout(subset_view_widget)
subset_view_layout.setContentsMargins(0, 0, 0, 0)
subset_view_layout.addLayout(subset_views_layout, 1)
subset_view_layout.addLayout(subset_view_btns_layout, 0)
subset_views_widget.set_center_widget(subset_view_widget)
# Whole subset layout with attributes and details
subset_content_widget = QtWidgets.QWidget(self)
subset_content_layout = QtWidgets.QHBoxLayout(subset_content_widget)
subset_content_layout.setContentsMargins(0, 0, 0, 0)
subset_content_layout.addWidget(create_widget, 7)
subset_content_layout.addWidget(subset_views_widget, 3)
subset_content_layout.addWidget(subset_attributes_wrap, 7)
# Subset frame layout
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(subset_content_widget, 1)
change_anim = QtCore.QVariantAnimation()
change_anim.setStartValue(0)
change_anim.setEndValue(self.anim_end_value)
change_anim.setDuration(self.anim_duration)
change_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuad)
# --- Calbacks for instances/subsets view ---
create_btn.clicked.connect(self._on_create_clicked)
delete_btn.clicked.connect(self._on_delete_clicked)
change_view_btn.clicked.connect(self._on_change_view_clicked)
change_anim.valueChanged.connect(self._on_change_anim)
change_anim.finished.connect(self._on_change_anim_finished)
# Selection changed
subset_list_view.selection_changed.connect(
self._on_subset_change
)
subset_view_cards.selection_changed.connect(
self._on_subset_change
)
# Active instances changed
subset_list_view.active_changed.connect(
self._on_active_changed
)
subset_view_cards.active_changed.connect(
self._on_active_changed
)
# Instance context has changed
subset_attributes_widget.instance_context_changed.connect(
self._on_instance_context_change
)
# --- Controller callbacks ---
controller.event_system.add_callback(
"publish.process.started", self._on_publish_start
)
controller.event_system.add_callback(
"publish.reset.finished", self._on_publish_reset
)
controller.event_system.add_callback(
"instances.refresh.finished", self._on_instances_refresh
)
self._subset_content_widget = subset_content_widget
self._subset_content_layout = subset_content_layout
self._subset_view_cards = subset_view_cards
self._subset_list_view = subset_list_view
self._subset_views_layout = subset_views_layout
self._delete_btn = delete_btn
self._subset_attributes_widget = subset_attributes_widget
self._create_widget = create_widget
self._subset_views_widget = subset_views_widget
self._subset_attributes_wrap = subset_attributes_wrap
self._change_anim = change_anim
# Start in create mode
self._create_widget_policy = create_widget.sizePolicy()
self._subset_views_widget_policy = subset_views_widget.sizePolicy()
self._subset_attributes_wrap_policy = (
subset_attributes_wrap.sizePolicy()
)
self._max_widget_width = None
self._current_state = "create"
subset_attributes_wrap.setVisible(False)
def set_state(self, new_state, animate):
if new_state == self._current_state:
return
self._current_state = new_state
anim_is_running = (
self._change_anim.state() == self._change_anim.Running
)
if not animate:
self._change_visibility_for_state()
if anim_is_running:
self._change_anim.stop()
return
if self._max_widget_width is None:
self._max_widget_width = self._subset_views_widget.maximumWidth()
if new_state == "create":
direction = self._change_anim.Backward
else:
direction = self._change_anim.Forward
self._change_anim.setDirection(direction)
if not anim_is_running:
view_width = self._subset_views_widget.width()
self._subset_views_widget.setMinimumWidth(view_width)
self._subset_views_widget.setMaximumWidth(view_width)
self._change_anim.start()
def _on_create_clicked(self):
"""Pass signal to parent widget which should care about changing state.
We don't change anything here until the parent will care about it.
"""
self.create_requested.emit()
def _on_delete_clicked(self):
instances, _ = self.get_selected_items()
# Ask user if he really wants to remove instances
dialog = QtWidgets.QMessageBox(self)
dialog.setIcon(QtWidgets.QMessageBox.Question)
dialog.setWindowTitle("Are you sure?")
if len(instances) > 1:
msg = (
"Do you really want to remove {} instances?"
).format(len(instances))
else:
msg = (
"Do you really want to remove the instance?"
)
dialog.setText(msg)
dialog.setStandardButtons(
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel
)
dialog.setDefaultButton(QtWidgets.QMessageBox.Ok)
dialog.setEscapeButton(QtWidgets.QMessageBox.Cancel)
dialog.exec_()
# Skip if OK was not clicked
if dialog.result() == QtWidgets.QMessageBox.Ok:
self._controller.remove_instances(instances)
def _on_change_view_clicked(self):
self._change_view_type()
def _on_subset_change(self, *_args):
# Ignore changes if in middle of refreshing
if self._refreshing_instances:
return
instances, context_selected = self.get_selected_items()
# Disable delete button if nothing is selected
self._delete_btn.setEnabled(len(instances) > 0)
self._subset_attributes_widget.set_current_instances(
instances, context_selected
)
def _on_active_changed(self):
if self._refreshing_instances:
return
self.active_changed.emit()
def _on_change_anim(self, value):
self._create_widget.setVisible(True)
self._subset_attributes_wrap.setVisible(True)
width = (
self._subset_content_widget.width()
- (
self._subset_views_widget.width()
+ (self._subset_content_layout.spacing() * 2)
)
)
subset_attrs_width = int(float(width) / self.anim_end_value) * value
if subset_attrs_width > width:
subset_attrs_width = width
create_width = width - subset_attrs_width
self._create_widget.setMinimumWidth(create_width)
self._create_widget.setMaximumWidth(create_width)
self._subset_attributes_wrap.setMinimumWidth(subset_attrs_width)
self._subset_attributes_wrap.setMaximumWidth(subset_attrs_width)
def _on_change_anim_finished(self):
self._change_visibility_for_state()
self._create_widget.setMinimumWidth(0)
self._create_widget.setMaximumWidth(self._max_widget_width)
self._subset_attributes_wrap.setMinimumWidth(0)
self._subset_attributes_wrap.setMaximumWidth(self._max_widget_width)
self._subset_views_widget.setMinimumWidth(0)
self._subset_views_widget.setMaximumWidth(self._max_widget_width)
self._create_widget.setSizePolicy(
self._create_widget_policy
)
self._subset_attributes_wrap.setSizePolicy(
self._subset_attributes_wrap_policy
)
self._subset_views_widget.setSizePolicy(
self._subset_views_widget_policy
)
def _change_visibility_for_state(self):
self._create_widget.setVisible(
self._current_state == "create"
)
self._subset_attributes_wrap.setVisible(
self._current_state == "publish"
)
def _on_instance_context_change(self):
current_idx = self._subset_views_layout.currentIndex()
for idx in range(self._subset_views_layout.count()):
if idx == current_idx:
continue
widget = self._subset_views_layout.widget(idx)
if widget.refreshed:
widget.set_refreshed(False)
current_widget = self._subset_views_layout.widget(current_idx)
current_widget.refresh_instance_states()
self.instance_context_changed.emit()
def get_selected_items(self):
view = self._subset_views_layout.currentWidget()
return view.get_selected_items()
def _change_view_type(self):
idx = self._subset_views_layout.currentIndex()
new_idx = (idx + 1) % self._subset_views_layout.count()
self._subset_views_layout.setCurrentIndex(new_idx)
new_view = self._subset_views_layout.currentWidget()
if not new_view.refreshed:
new_view.refresh()
new_view.set_refreshed(True)
else:
new_view.refresh_instance_states()
self._on_subset_change()
def _refresh_instances(self):
if self._refreshing_instances:
return
self._refreshing_instances = True
for idx in range(self._subset_views_layout.count()):
widget = self._subset_views_layout.widget(idx)
widget.set_refreshed(False)
view = self._subset_views_layout.currentWidget()
view.refresh()
view.set_refreshed(True)
self._refreshing_instances = False
# Force to change instance and refresh details
self._on_subset_change()
def _on_publish_start(self):
"""Publish started."""
self._subset_attributes_wrap.setEnabled(False)
def _on_publish_reset(self):
"""Context in controller has been refreshed."""
self._subset_attributes_wrap.setEnabled(True)
self._subset_content_widget.setEnabled(self._controller.host_is_valid)
def _on_instances_refresh(self):
"""Controller refreshed instances."""
self._refresh_instances()
# Give a change to process Resize Request
QtWidgets.QApplication.processEvents()
# Trigger update geometry of
widget = self._subset_views_layout.currentWidget()
widget.updateGeometry()

View file

@ -0,0 +1,520 @@
import os
import json
import time
from Qt import QtWidgets, QtCore
from openpype.pipeline import KnownPublishError
from .widgets import (
StopBtn,
ResetBtn,
ValidateBtn,
PublishBtn,
PublishReportBtn,
)
class PublishFrame(QtWidgets.QWidget):
"""Frame showed during publishing.
Shows all information related to publishing. Contains validation error
widget which is showed if only validation error happens during validation.
Processing layer is default layer. Validation error layer is shown if only
validation exception is raised during publishing. Report layer is available
only when publishing process is stopped and must be manually triggered to
change into that layer.
+------------------------------------------------------------------------+
| < Main label > |
| < Label top > |
| (#### 10% <Progress bar> ) |
| <Instance label> <Plugin label> |
| <Report> <Reset><Stop><Validate><Publish> |
+------------------------------------------------------------------------+
"""
details_page_requested = QtCore.Signal()
def __init__(self, controller, borders, parent):
super(PublishFrame, self).__init__(parent)
# Bottom part of widget where process and callback buttons are showed
# - QFrame used to be able set background using stylesheets easily
# and not override all children widgets style
content_frame = QtWidgets.QFrame(self)
content_frame.setObjectName("PublishInfoFrame")
top_content_widget = QtWidgets.QWidget(content_frame)
# Center widget displaying current state (without any specific info)
main_label = QtWidgets.QLabel(top_content_widget)
main_label.setObjectName("PublishInfoMainLabel")
main_label.setAlignment(QtCore.Qt.AlignCenter)
# Supporting labels for main label
# Top label is displayed just under main label
message_label_top = QtWidgets.QLabel(top_content_widget)
message_label_top.setAlignment(QtCore.Qt.AlignCenter)
# Label showing currently processed instance
progress_widget = QtWidgets.QWidget(top_content_widget)
instance_plugin_widget = QtWidgets.QWidget(progress_widget)
instance_label = QtWidgets.QLabel(
"<Instance name>", instance_plugin_widget
)
instance_label.setAlignment(
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
)
# Label showing currently processed plugin
plugin_label = QtWidgets.QLabel(
"<Plugin name>", instance_plugin_widget
)
plugin_label.setAlignment(
QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
)
instance_plugin_layout = QtWidgets.QHBoxLayout(instance_plugin_widget)
instance_plugin_layout.setContentsMargins(0, 0, 0, 0)
instance_plugin_layout.addWidget(instance_label, 1)
instance_plugin_layout.addWidget(plugin_label, 1)
# Progress bar showing progress of publishing
progress_bar = QtWidgets.QProgressBar(progress_widget)
progress_bar.setObjectName("PublishProgressBar")
progress_layout = QtWidgets.QVBoxLayout(progress_widget)
progress_layout.setSpacing(5)
progress_layout.setContentsMargins(0, 0, 0, 0)
progress_layout.addWidget(instance_plugin_widget, 0)
progress_layout.addWidget(progress_bar, 0)
top_content_layout = QtWidgets.QVBoxLayout(top_content_widget)
top_content_layout.setContentsMargins(0, 0, 0, 0)
top_content_layout.setSpacing(5)
top_content_layout.setAlignment(QtCore.Qt.AlignCenter)
top_content_layout.addWidget(main_label)
# TODO stretches should be probably replaced by spacing...
# - stretch in floating frame doesn't make sense
top_content_layout.addWidget(message_label_top)
top_content_layout.addWidget(progress_widget)
# Publishing buttons to stop, reset or trigger publishing
footer_widget = QtWidgets.QWidget(content_frame)
report_btn = PublishReportBtn(footer_widget)
shrunk_main_label = QtWidgets.QLabel(footer_widget)
shrunk_main_label.setObjectName("PublishInfoMainLabel")
shrunk_main_label.setAlignment(
QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft
)
reset_btn = ResetBtn(footer_widget)
stop_btn = StopBtn(footer_widget)
validate_btn = ValidateBtn(footer_widget)
publish_btn = PublishBtn(footer_widget)
report_btn.add_action("Go to details", "go_to_report")
report_btn.add_action("Copy report", "copy_report")
report_btn.add_action("Export report", "export_report")
# Footer on info frame layout
footer_layout = QtWidgets.QHBoxLayout(footer_widget)
footer_layout.setContentsMargins(0, 0, 0, 0)
footer_layout.addWidget(report_btn, 0)
footer_layout.addWidget(shrunk_main_label, 1)
footer_layout.addWidget(reset_btn, 0)
footer_layout.addWidget(stop_btn, 0)
footer_layout.addWidget(validate_btn, 0)
footer_layout.addWidget(publish_btn, 0)
# Info frame content
content_layout = QtWidgets.QVBoxLayout(content_frame)
content_layout.setSpacing(5)
content_layout.addWidget(top_content_widget)
content_layout.addWidget(footer_widget)
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(borders, 0, borders, borders)
main_layout.addWidget(content_frame)
shrunk_anim = QtCore.QVariantAnimation()
shrunk_anim.setDuration(140)
shrunk_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuad)
# Force translucent background for widgets
for widget in (
self,
top_content_widget,
footer_widget,
progress_widget,
instance_plugin_widget,
):
widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
report_btn.triggered.connect(self._on_report_triggered)
reset_btn.clicked.connect(self._on_reset_clicked)
stop_btn.clicked.connect(self._on_stop_clicked)
validate_btn.clicked.connect(self._on_validate_clicked)
publish_btn.clicked.connect(self._on_publish_clicked)
shrunk_anim.valueChanged.connect(self._on_shrunk_anim)
shrunk_anim.finished.connect(self._on_shrunk_anim_finish)
controller.event_system.add_callback(
"publish.reset.finished", self._on_publish_reset
)
controller.event_system.add_callback(
"publish.process.started", self._on_publish_start
)
controller.event_system.add_callback(
"publish.process.validated", self._on_publish_validated
)
controller.event_system.add_callback(
"publish.process.stopped", self._on_publish_stop
)
controller.event_system.add_callback(
"publish.process.instance.changed", self._on_instance_change
)
controller.event_system.add_callback(
"publish.process.plugin.changed", self._on_plugin_change
)
self._shrunk_anim = shrunk_anim
self.controller = controller
self._content_frame = content_frame
self._content_layout = content_layout
self._top_content_layout = top_content_layout
self._top_content_widget = top_content_widget
self._main_label = main_label
self._message_label_top = message_label_top
self._instance_label = instance_label
self._plugin_label = plugin_label
self._progress_bar = progress_bar
self._progress_widget = progress_widget
self._shrunk_main_label = shrunk_main_label
self._reset_btn = reset_btn
self._stop_btn = stop_btn
self._validate_btn = validate_btn
self._publish_btn = publish_btn
self._shrunken = False
self._top_widget_max_height = None
self._top_widget_size_policy = top_content_widget.sizePolicy()
self._last_instance_label = None
self._last_plugin_label = None
def mouseReleaseEvent(self, event):
super(PublishFrame, self).mouseReleaseEvent(event)
self._change_shrunk_state()
def _change_shrunk_state(self):
self.set_shrunk_state(not self._shrunken)
def set_shrunk_state(self, shrunk):
if shrunk is self._shrunken:
return
if self._top_widget_max_height is None:
self._top_widget_max_height = (
self._top_content_widget.maximumHeight()
)
self._shrunken = shrunk
anim_is_running = (
self._shrunk_anim.state() == self._shrunk_anim.Running
)
if not self.isVisible():
if anim_is_running:
self._shrunk_anim.stop()
self._on_shrunk_anim_finish()
return
start = 0
end = 0
if shrunk:
start = self._top_content_widget.height()
else:
if anim_is_running:
start = self._shrunk_anim.currentValue()
hint = self._top_content_widget.minimumSizeHint()
end = hint.height()
self._shrunk_anim.setStartValue(start)
self._shrunk_anim.setEndValue(end)
if not anim_is_running:
self._shrunk_anim.start()
def _on_shrunk_anim(self, value):
diff = self._top_content_widget.height() - value
if not self._top_content_widget.isVisible():
diff -= self._content_layout.spacing()
window_pos = self.pos()
window_pos_y = window_pos.y() + diff
window_height = self.height() - diff
self._top_content_widget.setMinimumHeight(value)
self._top_content_widget.setMaximumHeight(value)
self._top_content_widget.setVisible(True)
self.resize(self.width(), window_height)
self.move(window_pos.x(), window_pos_y)
def _on_shrunk_anim_finish(self):
self._top_content_widget.setVisible(not self._shrunken)
self._top_content_widget.setMinimumHeight(0)
self._top_content_widget.setMaximumHeight(
self._top_widget_max_height
)
self._top_content_widget.setSizePolicy(self._top_widget_size_policy)
if self._shrunken:
self._shrunk_main_label.setText(self._main_label.text())
else:
self._shrunk_main_label.setText("")
if self._shrunken:
content_frame_hint = self._content_frame.sizeHint()
layout = self.layout()
margins = layout.contentsMargins()
window_height = (
content_frame_hint.height()
+ margins.bottom()
+ margins.top()
)
diff = self.height() - window_height
window_pos = self.pos()
window_pos_y = window_pos.y() + diff
self.resize(self.width(), window_height)
self.move(window_pos.x(), window_pos_y)
def _set_main_label(self, message):
self._main_label.setText(message)
if self._shrunken:
self._shrunk_main_label.setText(message)
def _on_publish_reset(self):
self._last_instance_label = None
self._last_plugin_label = None
self._set_success_property()
self._set_progress_visibility(True)
self._main_label.setText("Hit publish (play button)! If you want")
self._message_label_top.setText("")
self._reset_btn.setEnabled(True)
self._stop_btn.setEnabled(False)
self._validate_btn.setEnabled(True)
self._publish_btn.setEnabled(True)
self._progress_bar.setValue(self.controller.publish_progress)
self._progress_bar.setMaximum(self.controller.publish_max_progress)
def _on_publish_start(self):
if self._last_plugin_label:
self._plugin_label.setText(self._last_plugin_label)
if self._last_instance_label:
self._instance_label.setText(self._last_instance_label)
self._set_success_property(-1)
self._set_progress_visibility(True)
self._set_main_label("Publishing...")
self._reset_btn.setEnabled(False)
self._stop_btn.setEnabled(True)
self._validate_btn.setEnabled(False)
self._publish_btn.setEnabled(False)
self.set_shrunk_state(False)
def _on_publish_validated(self):
self._validate_btn.setEnabled(False)
def _on_instance_change(self, event):
"""Change instance label when instance is going to be processed."""
self._last_instance_label = event["instance_label"]
self._instance_label.setText(event["instance_label"])
QtWidgets.QApplication.processEvents()
def _on_plugin_change(self, event):
"""Change plugin label when instance is going to be processed."""
self._last_plugin_label = event["plugin_label"]
self._progress_bar.setValue(self.controller.publish_progress)
self._plugin_label.setText(event["plugin_label"])
QtWidgets.QApplication.processEvents()
def _on_publish_stop(self):
self._progress_bar.setValue(self.controller.publish_progress)
self._reset_btn.setEnabled(True)
self._stop_btn.setEnabled(False)
self._instance_label.setText("")
self._plugin_label.setText("")
validate_enabled = not self.controller.publish_has_crashed
publish_enabled = not self.controller.publish_has_crashed
if validate_enabled:
validate_enabled = not self.controller.publish_has_validated
if publish_enabled:
if (
self.controller.publish_has_validated
and self.controller.publish_has_validation_errors
):
publish_enabled = False
else:
publish_enabled = not self.controller.publish_has_finished
self._validate_btn.setEnabled(validate_enabled)
self._publish_btn.setEnabled(publish_enabled)
error = self.controller.get_publish_crash_error()
validation_errors = self.controller.get_validation_errors()
if error:
self._set_error(error)
elif validation_errors:
self._set_progress_visibility(False)
self._set_validation_errors()
elif self.controller.publish_has_finished:
self._set_finished()
else:
self._set_stopped()
def _set_stopped(self):
main_label = "Publish paused"
if self.controller.publish_has_validated:
main_label += " - Validation passed"
self._set_main_label(main_label)
self._message_label_top.setText(
"Hit publish (play button) to continue."
)
self._set_success_property(-1)
def _set_error(self, error):
self._set_main_label("Error happened")
if isinstance(error, KnownPublishError):
msg = str(error)
else:
msg = (
"Something went wrong. Send report"
" to your supervisor or OpenPype."
)
self._message_label_top.setText(msg)
self._set_success_property(0)
def _set_validation_errors(self):
self._set_main_label("Your publish didn't pass studio validations")
self._message_label_top.setText("Check results above please")
self._set_success_property(2)
def _set_finished(self):
self._set_main_label("Finished")
self._message_label_top.setText("")
self._set_success_property(1)
def _set_progress_visibility(self, visible):
window_height = self.height()
self._progress_widget.setVisible(visible)
# Ignore rescaling and move of widget if is shrunken of progress bar
# should be visible
if self._shrunken or visible:
return
height = self._progress_widget.height()
diff = height + self._top_content_layout.spacing()
window_pos = self.pos()
window_pos_y = self.pos().y() + diff
window_height -= diff
self.resize(self.width(), window_height)
self.move(window_pos.x(), window_pos_y)
def _set_success_property(self, state=None):
if state is None:
state = ""
else:
state = str(state)
for widget in (self._progress_bar, self._content_frame):
if widget.property("state") != state:
widget.setProperty("state", state)
widget.style().polish(widget)
def _copy_report(self):
logs = self.controller.get_publish_report()
logs_string = json.dumps(logs, indent=4)
mime_data = QtCore.QMimeData()
mime_data.setText(logs_string)
QtWidgets.QApplication.instance().clipboard().setMimeData(
mime_data
)
def _export_report(self):
default_filename = "publish-report-{}".format(
time.strftime("%y%m%d-%H-%M")
)
default_filepath = os.path.join(
os.path.expanduser("~"),
default_filename
)
new_filepath, ext = QtWidgets.QFileDialog.getSaveFileName(
self, "Save report", default_filepath, ".json"
)
if not ext or not new_filepath:
return
logs = self.controller.get_publish_report()
full_path = new_filepath + ext
dir_path = os.path.dirname(full_path)
if not os.path.exists(dir_path):
os.makedirs(dir_path)
with open(full_path, "w") as file_stream:
json.dump(logs, file_stream)
def _on_report_triggered(self, identifier):
if identifier == "export_report":
self._export_report()
elif identifier == "copy_report":
self._copy_report()
elif identifier == "go_to_report":
self.details_page_requested.emit()
def _on_reset_clicked(self):
self.controller.reset()
def _on_stop_clicked(self):
self.controller.stop_publish()
def _on_validate_clicked(self):
self.controller.validate()
def _on_publish_clicked(self):
self.controller.publish()

View file

@ -1,519 +0,0 @@
import os
import json
import time
from Qt import QtWidgets, QtCore, QtGui
from openpype.pipeline import KnownPublishError
from .validations_widget import ValidationsWidget
from ..publish_report_viewer import PublishReportViewerWidget
from .widgets import (
StopBtn,
ResetBtn,
ValidateBtn,
PublishBtn,
CopyPublishReportBtn,
SavePublishReportBtn,
ShowPublishReportBtn
)
class ActionsButton(QtWidgets.QToolButton):
def __init__(self, parent=None):
super(ActionsButton, self).__init__(parent)
self.setText("< No action >")
self.setPopupMode(self.MenuButtonPopup)
menu = QtWidgets.QMenu(self)
self.setMenu(menu)
self._menu = menu
self._actions = []
self._current_action = None
self.clicked.connect(self._on_click)
def current_action(self):
return self._current_action
def add_action(self, action):
self._actions.append(action)
action.triggered.connect(self._on_action_trigger)
self._menu.addAction(action)
if self._current_action is None:
self._set_action(action)
def set_action(self, action):
if action not in self._actions:
self.add_action(action)
self._set_action(action)
def _set_action(self, action):
if action is self._current_action:
return
self._current_action = action
self.setText(action.text())
self.setIcon(action.icon())
def _on_click(self):
self._current_action.trigger()
def _on_action_trigger(self):
action = self.sender()
if action not in self._actions:
return
self._set_action(action)
class PublishFrame(QtWidgets.QFrame):
"""Frame showed during publishing.
Shows all information related to publishing. Contains validation error
widget which is showed if only validation error happens during validation.
Processing layer is default layer. Validation error layer is shown if only
validation exception is raised during publishing. Report layer is available
only when publishing process is stopped and must be manually triggered to
change into that layer.
+------------------------------------------------------------------------+
| |
| |
| |
| < Validation error widget > |
| |
| |
| |
| |
+------------------------------------------------------------------------+
| < Main label > |
| < Label top > |
| (#### 10% <Progress bar> ) |
| <Instance label> <Plugin label> |
| Report: <Copy><Save> <Label bottom> <Reset><Stop><Validate><Publish> |
+------------------------------------------------------------------------+
"""
def __init__(self, controller, parent):
super(PublishFrame, self).__init__(parent)
self.setObjectName("PublishFrame")
# Widget showing validation errors. Their details and action callbacks.
validation_errors_widget = ValidationsWidget(controller, self)
# Bottom part of widget where process and callback buttons are showed
# - QFrame used to be able set background using stylesheets easily
# and not override all children widgets style
info_frame = QtWidgets.QFrame(self)
info_frame.setObjectName("PublishInfoFrame")
# Content of info frame
# - separated into QFrame and QWidget (widget has transparent bg)
content_widget = QtWidgets.QWidget(info_frame)
content_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
info_layout = QtWidgets.QVBoxLayout(info_frame)
info_layout.setContentsMargins(0, 0, 0, 0)
info_layout.addWidget(content_widget)
# Center widget displaying current state (without any specific info)
main_label = QtWidgets.QLabel(content_widget)
main_label.setObjectName("PublishInfoMainLabel")
main_label.setAlignment(QtCore.Qt.AlignCenter)
# Supporting labels for main label
# Top label is displayed just under main label
message_label_top = QtWidgets.QLabel(content_widget)
message_label_top.setAlignment(QtCore.Qt.AlignCenter)
# Bottom label is displayed between report and publish buttons
# at bottom part of info frame
message_label_bottom = QtWidgets.QLabel(content_widget)
message_label_bottom.setAlignment(QtCore.Qt.AlignCenter)
# Label showing currently processed instance
instance_label = QtWidgets.QLabel("<Instance name>", content_widget)
instance_label.setAlignment(
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
)
# Label showing currently processed plugin
plugin_label = QtWidgets.QLabel("<Plugin name>", content_widget)
plugin_label.setAlignment(
QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
)
instance_plugin_layout = QtWidgets.QHBoxLayout()
instance_plugin_layout.addWidget(instance_label, 1)
instance_plugin_layout.addWidget(plugin_label, 1)
# Progress bar showing progress of publishing
progress_widget = QtWidgets.QProgressBar(content_widget)
progress_widget.setObjectName("PublishProgressBar")
# Report buttons to be able copy, save or see report
report_btns_widget = QtWidgets.QWidget(content_widget)
report_btns_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
# Hidden by default
report_btns_widget.setVisible(False)
report_label = QtWidgets.QLabel("Report:", report_btns_widget)
copy_report_btn = CopyPublishReportBtn(report_btns_widget)
export_report_btn = SavePublishReportBtn(report_btns_widget)
show_details_btn = ShowPublishReportBtn(report_btns_widget)
report_btns_layout = QtWidgets.QHBoxLayout(report_btns_widget)
report_btns_layout.setContentsMargins(0, 0, 0, 0)
report_btns_layout.addWidget(report_label, 0)
report_btns_layout.addWidget(copy_report_btn, 0)
report_btns_layout.addWidget(export_report_btn, 0)
report_btns_layout.addWidget(show_details_btn, 0)
# Publishing buttons to stop, reset or trigger publishing
reset_btn = ResetBtn(content_widget)
stop_btn = StopBtn(content_widget)
validate_btn = ValidateBtn(content_widget)
publish_btn = PublishBtn(content_widget)
# Footer on info frame layout
info_footer_layout = QtWidgets.QHBoxLayout()
info_footer_layout.addWidget(report_btns_widget, 0)
info_footer_layout.addWidget(message_label_bottom, 1)
info_footer_layout.addWidget(reset_btn, 0)
info_footer_layout.addWidget(stop_btn, 0)
info_footer_layout.addWidget(validate_btn, 0)
info_footer_layout.addWidget(publish_btn, 0)
# Info frame content
content_layout = QtWidgets.QVBoxLayout(content_widget)
content_layout.setSpacing(5)
content_layout.setAlignment(QtCore.Qt.AlignCenter)
content_layout.addWidget(main_label)
content_layout.addStretch(1)
content_layout.addWidget(message_label_top)
content_layout.addStretch(1)
content_layout.addLayout(instance_plugin_layout)
content_layout.addWidget(progress_widget)
content_layout.addStretch(1)
content_layout.addLayout(info_footer_layout)
# Whole widget layout
publish_widget = QtWidgets.QWidget(self)
publish_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
publish_layout = QtWidgets.QVBoxLayout(publish_widget)
publish_layout.addWidget(validation_errors_widget, 1)
publish_layout.addWidget(info_frame, 0)
details_widget = QtWidgets.QWidget(self)
report_view = PublishReportViewerWidget(details_widget)
close_report_btn = QtWidgets.QPushButton(details_widget)
close_report_icon = self._get_report_close_icon()
close_report_btn.setIcon(close_report_icon)
details_layout = QtWidgets.QVBoxLayout(details_widget)
details_layout.addWidget(report_view)
details_layout.addWidget(close_report_btn)
main_layout = QtWidgets.QStackedLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setStackingMode(main_layout.StackOne)
main_layout.addWidget(publish_widget)
main_layout.addWidget(details_widget)
main_layout.setCurrentWidget(publish_widget)
show_details_btn.clicked.connect(self._on_show_details)
copy_report_btn.clicked.connect(self._on_copy_report)
export_report_btn.clicked.connect(self._on_export_report)
reset_btn.clicked.connect(self._on_reset_clicked)
stop_btn.clicked.connect(self._on_stop_clicked)
validate_btn.clicked.connect(self._on_validate_clicked)
publish_btn.clicked.connect(self._on_publish_clicked)
close_report_btn.clicked.connect(self._on_close_report_clicked)
controller.add_publish_reset_callback(self._on_publish_reset)
controller.add_publish_started_callback(self._on_publish_start)
controller.add_publish_validated_callback(self._on_publish_validated)
controller.add_publish_stopped_callback(self._on_publish_stop)
controller.add_instance_change_callback(self._on_instance_change)
controller.add_plugin_change_callback(self._on_plugin_change)
self.controller = controller
self._info_frame = info_frame
self._publish_widget = publish_widget
self._validation_errors_widget = validation_errors_widget
self._main_layout = main_layout
self._main_label = main_label
self._message_label_top = message_label_top
self._instance_label = instance_label
self._plugin_label = plugin_label
self._progress_widget = progress_widget
self._report_btns_widget = report_btns_widget
self._message_label_bottom = message_label_bottom
self._reset_btn = reset_btn
self._stop_btn = stop_btn
self._validate_btn = validate_btn
self._publish_btn = publish_btn
self._details_widget = details_widget
self._report_view = report_view
def _get_report_close_icon(self):
size = 100
pix = QtGui.QPixmap(size, size)
pix.fill(QtCore.Qt.transparent)
half_stroke_size = size / 12
stroke_size = 2 * half_stroke_size
size_part = size / 5
p1 = QtCore.QPoint(half_stroke_size, size_part)
p2 = QtCore.QPoint(size / 2, size_part * 4)
p3 = QtCore.QPoint(size - half_stroke_size, size_part)
painter = QtGui.QPainter(pix)
pen = QtGui.QPen(QtCore.Qt.white)
pen.setWidth(stroke_size)
pen.setCapStyle(QtCore.Qt.RoundCap)
painter.setPen(pen)
painter.setBrush(QtCore.Qt.transparent)
painter.drawLine(p1, p2)
painter.drawLine(p2, p3)
painter.end()
return QtGui.QIcon(pix)
def _on_publish_reset(self):
self._set_success_property()
self._change_bg_property()
self._set_progress_visibility(True)
self._main_label.setText("Hit publish (play button)! If you want")
self._message_label_top.setText("")
self._message_label_bottom.setText("")
self._report_btns_widget.setVisible(False)
self._reset_btn.setEnabled(True)
self._stop_btn.setEnabled(False)
self._validate_btn.setEnabled(True)
self._publish_btn.setEnabled(True)
self._progress_widget.setValue(self.controller.publish_progress)
self._progress_widget.setMaximum(self.controller.publish_max_progress)
def _on_publish_start(self):
self._validation_errors_widget.clear()
self._set_success_property(-1)
self._change_bg_property()
self._set_progress_visibility(True)
self._main_label.setText("Publishing...")
self._report_btns_widget.setVisible(False)
self._reset_btn.setEnabled(False)
self._stop_btn.setEnabled(True)
self._validate_btn.setEnabled(False)
self._publish_btn.setEnabled(False)
def _on_publish_validated(self):
self._validate_btn.setEnabled(False)
def _on_instance_change(self, context, instance):
"""Change instance label when instance is going to be processed."""
if instance is None:
new_name = (
context.data.get("label")
or context.data.get("name")
or "Context"
)
else:
new_name = (
instance.data.get("label")
or instance.data["name"]
)
self._instance_label.setText(new_name)
QtWidgets.QApplication.processEvents()
def _on_plugin_change(self, plugin):
"""Change plugin label when instance is going to be processed."""
plugin_name = plugin.__name__
if hasattr(plugin, "label") and plugin.label:
plugin_name = plugin.label
self._progress_widget.setValue(self.controller.publish_progress)
self._plugin_label.setText(plugin_name)
QtWidgets.QApplication.processEvents()
def _on_publish_stop(self):
self._progress_widget.setValue(self.controller.publish_progress)
self._report_btns_widget.setVisible(True)
self._reset_btn.setEnabled(True)
self._stop_btn.setEnabled(False)
validate_enabled = not self.controller.publish_has_crashed
publish_enabled = not self.controller.publish_has_crashed
if validate_enabled:
validate_enabled = not self.controller.publish_has_validated
if publish_enabled:
if (
self.controller.publish_has_validated
and self.controller.publish_has_validation_errors
):
publish_enabled = False
else:
publish_enabled = not self.controller.publish_has_finished
self._validate_btn.setEnabled(validate_enabled)
self._publish_btn.setEnabled(publish_enabled)
error = self.controller.get_publish_crash_error()
validation_errors = self.controller.get_validation_errors()
if error:
self._set_error(error)
elif validation_errors:
self._set_progress_visibility(False)
self._change_bg_property(1)
self._set_validation_errors(validation_errors)
elif self.controller.publish_has_finished:
self._set_finished()
else:
self._set_stopped()
def _set_stopped(self):
main_label = "Publish paused"
if self.controller.publish_has_validated:
main_label += " - Validation passed"
self._main_label.setText(main_label)
self._message_label_top.setText(
"Hit publish (play button) to continue."
)
self._set_success_property(-1)
def _set_error(self, error):
self._main_label.setText("Error happened")
if isinstance(error, KnownPublishError):
msg = str(error)
else:
msg = (
"Something went wrong. Send report"
" to your supervisor or OpenPype."
)
self._message_label_top.setText(msg)
self._message_label_bottom.setText("")
self._set_success_property(0)
def _set_validation_errors(self, validation_errors):
self._main_label.setText("Your publish didn't pass studio validations")
self._message_label_top.setText("")
self._message_label_bottom.setText("Check results above please")
self._set_success_property(2)
self._validation_errors_widget.set_errors(validation_errors)
def _set_finished(self):
self._main_label.setText("Finished")
self._message_label_top.setText("")
self._message_label_bottom.setText("")
self._set_success_property(1)
def _change_bg_property(self, state=None):
self.setProperty("state", str(state or ""))
self.style().polish(self)
def _set_progress_visibility(self, visible):
self._instance_label.setVisible(visible)
self._plugin_label.setVisible(visible)
self._progress_widget.setVisible(visible)
self._message_label_top.setVisible(visible)
def _set_success_property(self, state=None):
if state is None:
state = ""
else:
state = str(state)
for widget in (self._progress_widget, self._info_frame):
if widget.property("state") != state:
widget.setProperty("state", state)
widget.style().polish(widget)
def _on_copy_report(self):
logs = self.controller.get_publish_report()
logs_string = json.dumps(logs, indent=4)
mime_data = QtCore.QMimeData()
mime_data.setText(logs_string)
QtWidgets.QApplication.instance().clipboard().setMimeData(
mime_data
)
def _on_export_report(self):
default_filename = "publish-report-{}".format(
time.strftime("%y%m%d-%H-%M")
)
default_filepath = os.path.join(
os.path.expanduser("~"),
default_filename
)
new_filepath, ext = QtWidgets.QFileDialog.getSaveFileName(
self, "Save report", default_filepath, ".json"
)
if not ext or not new_filepath:
return
logs = self.controller.get_publish_report()
full_path = new_filepath + ext
dir_path = os.path.dirname(full_path)
if not os.path.exists(dir_path):
os.makedirs(dir_path)
with open(full_path, "w") as file_stream:
json.dump(logs, file_stream)
def _on_show_details(self):
self._change_bg_property(2)
self._main_layout.setCurrentWidget(self._details_widget)
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()
elif self.controller.get_validation_errors():
self._change_bg_property(1)
else:
self._change_bg_property(2)
self._main_layout.setCurrentWidget(self._publish_widget)
def _on_reset_clicked(self):
self.controller.reset()
def _on_stop_clicked(self):
self.controller.stop_publish()
def _on_validate_clicked(self):
self.controller.validate()
def _on_publish_clicked(self):
self.controller.publish()

View file

@ -0,0 +1,95 @@
from Qt import QtWidgets, QtCore
from openpype.tools.utils import set_style_property
class PublisherTabBtn(QtWidgets.QPushButton):
tab_clicked = QtCore.Signal(str)
def __init__(self, identifier, label, parent):
super(PublisherTabBtn, self).__init__(label, parent)
self._identifier = identifier
self._active = False
self.clicked.connect(self._on_click)
def _on_click(self):
self.tab_clicked.emit(self.identifier)
@property
def identifier(self):
return self._identifier
def activate(self):
if self._active:
return
self._active = True
set_style_property(self, "active", "1")
def deactivate(self):
if not self._active:
return
self._active = False
set_style_property(self, "active", "")
class PublisherTabsWidget(QtWidgets.QFrame):
tab_changed = QtCore.Signal(str, str)
def __init__(self, parent=None):
super(PublisherTabsWidget, self).__init__(parent)
btns_widget = QtWidgets.QWidget(self)
btns_layout = QtWidgets.QHBoxLayout(btns_widget)
btns_layout.setContentsMargins(0, 0, 0, 0)
btns_layout.setSpacing(0)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(btns_widget, 0)
layout.addStretch(1)
self._btns_layout = btns_layout
self._current_identifier = None
self._buttons_by_identifier = {}
def is_current_tab(self, identifier):
if isinstance(identifier, PublisherTabBtn):
identifier = identifier.identifier
return self._current_identifier == identifier
def add_tab(self, label, identifier):
button = PublisherTabBtn(identifier, label, self)
button.tab_clicked.connect(self._on_tab_click)
self._btns_layout.addWidget(button, 0)
self._buttons_by_identifier[identifier] = button
if self._current_identifier is None:
self.set_current_tab(identifier)
return button
def set_current_tab(self, identifier):
if isinstance(identifier, PublisherTabBtn):
identifier = identifier.identifier
if identifier == self._current_identifier:
return
new_btn = self._buttons_by_identifier.get(identifier)
if new_btn is None:
return
old_identifier = self._current_identifier
old_btn = self._buttons_by_identifier.get(old_identifier)
self._current_identifier = identifier
if old_btn is not None:
old_btn.deactivate()
new_btn.activate()
self.tab_changed.emit(old_identifier, identifier)
def current_tab(self):
return self._current_identifier
def _on_tab_click(self, identifier):
self.set_current_tab(identifier)

View file

@ -141,10 +141,10 @@ class TasksModel(QtGui.QStandardItemModel):
return super(TasksModel, self).headerData(section, orientation, role)
class CreateDialogTasksWidget(TasksWidget):
class CreateWidgetTasksWidget(TasksWidget):
def __init__(self, controller, parent):
self._controller = controller
super(CreateDialogTasksWidget, self).__init__(None, parent)
super(CreateWidgetTasksWidget, self).__init__(None, parent)
self._enabled = None
@ -164,7 +164,7 @@ class CreateDialogTasksWidget(TasksWidget):
self.task_changed.emit()
def select_task_name(self, task_name):
super(CreateDialogTasksWidget, self).select_task_name(task_name)
super(CreateWidgetTasksWidget, self).select_task_name(task_name)
if not self._enabled:
current = self.get_selected_task_name()
if current:

View file

@ -6,7 +6,7 @@ except Exception:
from Qt import QtWidgets, QtCore, QtGui
from openpype.tools.utils import BaseClickableFrame
from openpype.tools.utils import BaseClickableFrame, ClickableFrame
from .widgets import (
IconValuePixmapLabel
)
@ -60,9 +60,8 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
self._error_info = error_info
self._selected = False
title_frame = BaseClickableFrame(self)
title_frame = ClickableFrame(self)
title_frame.setObjectName("ValidationErrorTitleFrame")
title_frame._mouse_release_callback = self._mouse_release_callback
toggle_instance_btn = QtWidgets.QToolButton(title_frame)
toggle_instance_btn.setObjectName("ArrowBtn")
@ -72,14 +71,15 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
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)
title_frame_layout.addWidget(label_widget, 1)
title_frame_layout.addWidget(toggle_instance_btn, 0)
instances_model = QtGui.QStandardItemModel()
error_info = error_info["error_info"]
help_text_by_instance_id = {}
context_validation = False
items = []
if (
not error_info
or (len(error_info) == 1 and error_info[0][0] is None)
@ -88,8 +88,10 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
toggle_instance_btn.setArrowType(QtCore.Qt.NoArrow)
description = self._prepare_description(error_info[0][1])
help_text_by_instance_id[None] = description
# Add fake item to have minimum size hint of view widget
items.append(QtGui.QStandardItem("Context"))
else:
items = []
for instance, exception in error_info:
label = instance.data.get("label") or instance.data.get("name")
item = QtGui.QStandardItem(label)
@ -102,29 +104,33 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
description = self._prepare_description(exception)
help_text_by_instance_id[instance.id] = description
instances_model.invisibleRootItem().appendRows(items)
if items:
root_item = instances_model.invisibleRootItem()
root_item.appendRows(items)
instances_view = ValidationErrorInstanceList(self)
instances_view.setModel(instances_model)
instances_view.setVisible(False)
self.setLayoutDirection(QtCore.Qt.LeftToRight)
view_layout = QtWidgets.QHBoxLayout()
view_widget = QtWidgets.QWidget(self)
view_layout = QtWidgets.QHBoxLayout(view_widget)
view_layout.setContentsMargins(0, 0, 0, 0)
view_layout.setSpacing(0)
view_layout.addSpacing(14)
view_layout.addWidget(instances_view)
view_layout.addWidget(instances_view, 0)
layout = QtWidgets.QVBoxLayout(self)
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(title_frame)
layout.addLayout(view_layout)
layout.addWidget(title_frame, 0)
layout.addWidget(view_widget, 0)
view_widget.setVisible(False)
if not context_validation:
toggle_instance_btn.clicked.connect(self._on_toggle_btn_click)
title_frame.clicked.connect(self._mouse_release_callback)
instances_view.selectionModel().selectionChanged.connect(
self._on_seleciton_change
)
@ -133,7 +139,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
self._toggle_instance_btn = toggle_instance_btn
self._view_layout = view_layout
self._view_widget = view_widget
self._instances_model = instances_model
self._instances_view = instances_view
@ -141,17 +147,21 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
self._context_validation = context_validation
self._help_text_by_instance_id = help_text_by_instance_id
self._expanded = False
def sizeHint(self):
result = super(ValidationErrorTitleWidget, self).sizeHint()
expected_width = 0
for idx in range(self._view_layout.count()):
expected_width += self._view_layout.itemAt(idx).sizeHint().width()
expected_width = max(
self._view_widget.minimumSizeHint().width(),
self._view_widget.sizeHint().width()
)
if expected_width < 200:
expected_width = 200
if result.width() < expected_width:
result.setWidth(expected_width)
return result
def minimumSizeHint(self):
@ -170,6 +180,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
def _mouse_release_callback(self):
"""Mark this widget as selected on click."""
self.set_selected(True)
def current_desctiption_text(self):
@ -208,25 +219,44 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
if selected is None:
selected = not self._selected
elif selected == self._selected:
if not selected:
self._instances_view.clearSelection()
if selected == self._selected:
return
self._selected = selected
self._change_style_property(selected)
if selected:
self.selected.emit(self._index)
self._set_expanded(True)
def _on_toggle_btn_click(self):
"""Show/hide instances list."""
new_visible = not self._instances_view.isVisible()
self._instances_view.setVisible(new_visible)
if new_visible:
self._set_expanded()
def _set_expanded(self, expanded=None):
if expanded is None:
expanded = not self._expanded
elif expanded is self._expanded:
return
if expanded and self._context_validation:
return
self._expanded = expanded
self._view_widget.setVisible(expanded)
if expanded:
self._toggle_instance_btn.setArrowType(QtCore.Qt.DownArrow)
else:
self._toggle_instance_btn.setArrowType(QtCore.Qt.RightArrow)
def _on_seleciton_change(self):
self.instance_changed.emit(self._index)
sel_model = self._instances_view.selectionModel()
if sel_model.selectedIndexes():
self.instance_changed.emit(self._index)
class ActionButton(BaseClickableFrame):
@ -400,7 +430,21 @@ class VerticallScrollArea(QtWidgets.QScrollArea):
return super(VerticallScrollArea, self).eventFilter(obj, event)
class ValidationsWidget(QtWidgets.QWidget):
class ValidationArtistMessage(QtWidgets.QWidget):
def __init__(self, message, parent):
super(ValidationArtistMessage, self).__init__(parent)
artist_msg_label = QtWidgets.QLabel(message, self)
artist_msg_label.setAlignment(QtCore.Qt.AlignCenter)
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(
artist_msg_label, 1, QtCore.Qt.AlignCenter
)
class ValidationsWidget(QtWidgets.QFrame):
"""Widgets showing validation error.
This widget is shown if validation error/s happened during validation part.
@ -414,16 +458,35 @@ class ValidationsWidget(QtWidgets.QWidget):
Error detail
Publish buttons
"""
def __init__(self, controller, parent):
super(ValidationsWidget, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
# Before publishing
before_publish_widget = ValidationArtistMessage(
"Nothing to report until you run publish", self
)
# After success publishing
publish_started_widget = ValidationArtistMessage(
"Publishing went smoothly", self
)
# After success publishing
publish_stop_ok_widget = ValidationArtistMessage(
"Publishing finished successfully", self
)
# After failed publishing (not with validation error)
publish_stop_fail_widget = ValidationArtistMessage(
"This is not your fault...", self
)
errors_scroll = VerticallScrollArea(self)
# Validation errors
validations_widget = QtWidgets.QWidget(self)
content_widget = QtWidgets.QWidget(validations_widget)
errors_scroll = VerticallScrollArea(content_widget)
errors_scroll.setWidgetResizable(True)
errors_widget = QtWidgets.QWidget(errors_scroll)
@ -433,35 +496,64 @@ class ValidationsWidget(QtWidgets.QWidget):
errors_scroll.setWidget(errors_widget)
error_details_frame = QtWidgets.QFrame(self)
error_details_frame = QtWidgets.QFrame(content_widget)
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 = ValidateActionsWidget(controller, content_widget)
actions_widget.setMinimumWidth(140)
error_details_layout = QtWidgets.QHBoxLayout(error_details_frame)
error_details_layout.addWidget(error_details_input, 1)
error_details_layout.addWidget(actions_widget, 0)
content_layout = QtWidgets.QHBoxLayout()
content_layout = QtWidgets.QHBoxLayout(content_widget)
content_layout.setSpacing(0)
content_layout.setContentsMargins(0, 0, 0, 0)
content_layout.addWidget(errors_scroll, 0)
content_layout.addWidget(error_details_frame, 1)
top_label = QtWidgets.QLabel("Publish validation report", self)
top_label = QtWidgets.QLabel(
"Publish validation report", content_widget
)
top_label.setObjectName("PublishInfoMainLabel")
top_label.setAlignment(QtCore.Qt.AlignCenter)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(top_label)
layout.addLayout(content_layout)
validation_layout = QtWidgets.QVBoxLayout(validations_widget)
validation_layout.setContentsMargins(0, 0, 0, 0)
validation_layout.addWidget(top_label, 0)
validation_layout.addWidget(content_widget, 1)
main_layout = QtWidgets.QStackedLayout(self)
main_layout.addWidget(before_publish_widget)
main_layout.addWidget(publish_started_widget)
main_layout.addWidget(publish_stop_ok_widget)
main_layout.addWidget(publish_stop_fail_widget)
main_layout.addWidget(validations_widget)
main_layout.setCurrentWidget(before_publish_widget)
controller.event_system.add_callback(
"publish.process.started", self._on_publish_start
)
controller.event_system.add_callback(
"publish.reset.finished", self._on_publish_reset
)
controller.event_system.add_callback(
"publish.process.stopped", self._on_publish_stop
)
self._main_layout = main_layout
self._before_publish_widget = before_publish_widget
self._publish_started_widget = publish_started_widget
self._publish_stop_ok_widget = publish_stop_ok_widget
self._publish_stop_fail_widget = publish_stop_fail_widget
self._validations_widget = validations_widget
self._top_label = top_label
self._errors_widget = errors_widget
@ -474,6 +566,8 @@ class ValidationsWidget(QtWidgets.QWidget):
self._error_info = {}
self._previous_select = None
self._controller = controller
def clear(self):
"""Delete all dynamic widgets and hide all wrappers."""
self._title_widgets = {}
@ -537,6 +631,32 @@ class ValidationsWidget(QtWidgets.QWidget):
self.updateGeometry()
def _set_current_widget(self, widget):
self._main_layout.setCurrentWidget(widget)
def _on_publish_start(self):
self._set_current_widget(self._publish_started_widget)
def _on_publish_reset(self):
self._set_current_widget(self._before_publish_widget)
def _on_publish_stop(self):
if self._controller.publish_has_crashed:
self._set_current_widget(self._publish_stop_fail_widget)
return
if self._controller.publish_has_validation_errors:
validation_errors = self._controller.get_validation_errors()
self._set_current_widget(self._validations_widget)
self.set_errors(validation_errors)
return
if self._contoller.publish_has_finished:
self._set_current_widget(self._publish_stop_ok_widget)
return
self._set_current_widget(self._publish_started_widget)
def _on_select(self, index):
if self._previous_select:
if self._previous_select.index == index:
@ -553,8 +673,9 @@ class ValidationsWidget(QtWidgets.QWidget):
def _on_instance_change(self, index):
if self._previous_select and self._previous_select.index != index:
return
self._update_description()
self._title_widgets[index].set_selected(True)
else:
self._update_description()
def _update_description(self):
description = self._previous_select.current_desctiption_text()

View file

@ -2,6 +2,7 @@
import os
import re
import copy
import functools
import collections
from Qt import QtWidgets, QtCore, QtGui
import qtawesome
@ -182,6 +183,16 @@ class PublishIconBtn(IconButton):
return pixmap
class CreateBtn(PublishIconBtn):
"""Create instance button."""
def __init__(self, parent=None):
icon_path = get_icon_path("create")
super(CreateBtn, self).__init__(icon_path, "Create", parent)
self.setToolTip("Create new subset/s")
self.setLayoutDirection(QtCore.Qt.RightToLeft)
class ResetBtn(PublishIconBtn):
"""Publish reset button."""
def __init__(self, parent=None):
@ -222,28 +233,34 @@ class CreateInstanceBtn(PublishIconBtn):
self.setToolTip("Create new instance")
class CopyPublishReportBtn(PublishIconBtn):
"""Copy report button."""
def __init__(self, parent=None):
icon_path = get_icon_path("copy")
super(CopyPublishReportBtn, self).__init__(icon_path, parent)
self.setToolTip("Copy report")
class PublishReportBtn(PublishIconBtn):
"""Publish report button."""
triggered = QtCore.Signal(str)
class SavePublishReportBtn(PublishIconBtn):
"""Save report button."""
def __init__(self, parent=None):
icon_path = get_icon_path("download_arrow")
super(SavePublishReportBtn, self).__init__(icon_path, parent)
self.setToolTip("Export and save report")
class ShowPublishReportBtn(PublishIconBtn):
"""Show report button."""
def __init__(self, parent=None):
icon_path = get_icon_path("view_report")
super(ShowPublishReportBtn, self).__init__(icon_path, parent)
self.setToolTip("Show details")
super(PublishReportBtn, self).__init__(icon_path, parent)
self.setToolTip("Copy report")
self._actions = []
def add_action(self, label, identifier):
action = QtWidgets.QAction(label)
action.setData(identifier)
action.triggered.connect(
functools.partial(self._on_action_trigger, action)
)
self._actions.append(action)
def _on_action_trigger(self, action):
identifier = action.data()
self.triggered.emit(identifier)
def mouseReleaseEvent(self, event):
super(PublishReportBtn, self).mouseReleaseEvent(event)
menu = QtWidgets.QMenu(self)
menu.addActions(self._actions)
menu.exec_(event.globalPos())
class RemoveInstanceBtn(PublishIconBtn):

View file

@ -6,32 +6,35 @@ from openpype import (
)
from openpype.tools.utils import (
PlaceholderLineEdit,
PixmapLabel
MessageOverlayObject,
PixmapLabel,
)
from .publish_report_viewer import PublishReportViewerWidget
from .control import PublisherController
from .widgets import (
BorderedLabelWidget,
OverviewWidget,
ValidationsWidget,
PublishFrame,
SubsetAttributesWidget,
InstanceCardView,
InstanceListView,
CreateDialog,
PublisherTabsWidget,
StopBtn,
ResetBtn,
ValidateBtn,
PublishBtn,
CreateInstanceBtn,
RemoveInstanceBtn,
ChangeViewBtn
HelpButton,
HelpDialog,
)
class PublisherWindow(QtWidgets.QDialog):
"""Main window of publisher."""
default_width = 1200
default_height = 700
default_width = 1300
default_height = 800
footer_border = 8
publish_footer_spacer = 2
def __init__(self, parent=None, reset_on_show=None):
super(PublisherWindow, self).__init__(parent)
@ -58,122 +61,121 @@ class PublisherWindow(QtWidgets.QDialog):
| on_top_flag
)
self._reset_on_show = reset_on_show
self._first_show = True
self._refreshing_instances = False
controller = PublisherController()
help_dialog = HelpDialog(controller, self)
overlay_object = MessageOverlayObject(self)
# Header
header_widget = QtWidgets.QWidget(self)
icon_pixmap = QtGui.QPixmap(resources.get_openpype_icon_filepath())
icon_label = PixmapLabel(icon_pixmap, header_widget)
icon_label.setObjectName("PublishContextLabel")
context_label = QtWidgets.QLabel(header_widget)
context_label.setObjectName("PublishContextLabel")
header_extra_widget = QtWidgets.QWidget(header_widget)
help_btn = HelpButton(header_widget)
header_layout = QtWidgets.QHBoxLayout(header_widget)
header_layout.setContentsMargins(15, 15, 15, 15)
header_layout.setContentsMargins(15, 15, 0, 15)
header_layout.setSpacing(15)
header_layout.addWidget(icon_label, 0)
header_layout.addWidget(context_label, 1)
header_layout.addWidget(context_label, 0)
header_layout.addStretch(1)
header_layout.addWidget(header_extra_widget, 0)
header_layout.addWidget(help_btn, 0)
line_widget = QtWidgets.QWidget(self)
line_widget.setObjectName("Separator")
line_widget.setMinimumHeight(2)
# Tabs widget under header
tabs_widget = PublisherTabsWidget(self)
create_tab = tabs_widget.add_tab("Create", "create")
tabs_widget.add_tab("Publish", "publish")
tabs_widget.add_tab("Report", "report")
tabs_widget.add_tab("Details", "details")
# Content
content_stacked_widget = QtWidgets.QWidget(self)
# Subset widget
subset_frame = QtWidgets.QFrame(content_stacked_widget)
subset_views_widget = BorderedLabelWidget(
"Subsets to publish", subset_frame
)
subset_view_cards = InstanceCardView(controller, subset_views_widget)
subset_list_view = InstanceListView(controller, subset_views_widget)
subset_views_layout = QtWidgets.QStackedLayout()
subset_views_layout.addWidget(subset_view_cards)
subset_views_layout.addWidget(subset_list_view)
# Buttons at the bottom of subset view
create_btn = CreateInstanceBtn(subset_frame)
delete_btn = RemoveInstanceBtn(subset_frame)
change_view_btn = ChangeViewBtn(subset_frame)
# Subset details widget
subset_attributes_wrap = BorderedLabelWidget(
"Publish options", subset_frame
)
subset_attributes_widget = SubsetAttributesWidget(
controller, subset_attributes_wrap
)
subset_attributes_wrap.set_center_widget(subset_attributes_widget)
# Layout of buttons at the bottom of subset view
subset_view_btns_layout = QtWidgets.QHBoxLayout()
subset_view_btns_layout.setContentsMargins(0, 5, 0, 0)
subset_view_btns_layout.addWidget(create_btn)
subset_view_btns_layout.addSpacing(5)
subset_view_btns_layout.addWidget(delete_btn)
subset_view_btns_layout.addStretch(1)
subset_view_btns_layout.addWidget(change_view_btn)
# Layout of view and buttons
# - widget 'subset_view_widget' is necessary
# - only layout won't be resized automatically to minimum size hint
# on child resize request!
subset_view_widget = QtWidgets.QWidget(subset_views_widget)
subset_view_layout = QtWidgets.QVBoxLayout(subset_view_widget)
subset_view_layout.setContentsMargins(0, 0, 0, 0)
subset_view_layout.addLayout(subset_views_layout, 1)
subset_view_layout.addLayout(subset_view_btns_layout, 0)
subset_views_widget.set_center_widget(subset_view_widget)
# Whole subset layout with attributes and details
subset_content_widget = QtWidgets.QWidget(subset_frame)
subset_content_layout = QtWidgets.QHBoxLayout(subset_content_widget)
subset_content_layout.setContentsMargins(0, 0, 0, 0)
subset_content_layout.addWidget(subset_views_widget, 3)
subset_content_layout.addWidget(subset_attributes_wrap, 7)
# Widget where is stacked publish overlay and widgets that should be
# covered by it
under_publish_stack = QtWidgets.QWidget(self)
# Added wrap widget where all widgets under overlay are added
# - this is because footer is also under overlay and the top part
# is faked with floating frame
under_publish_widget = QtWidgets.QWidget(under_publish_stack)
# Footer
comment_input = PlaceholderLineEdit(subset_frame)
footer_widget = QtWidgets.QWidget(under_publish_widget)
footer_bottom_widget = QtWidgets.QWidget(footer_widget)
comment_input = PlaceholderLineEdit(footer_widget)
comment_input.setObjectName("PublishCommentInput")
comment_input.setPlaceholderText(
"Attach a comment to your publish"
)
reset_btn = ResetBtn(subset_frame)
stop_btn = StopBtn(subset_frame)
validate_btn = ValidateBtn(subset_frame)
publish_btn = PublishBtn(subset_frame)
reset_btn = ResetBtn(footer_widget)
stop_btn = StopBtn(footer_widget)
validate_btn = ValidateBtn(footer_widget)
publish_btn = PublishBtn(footer_widget)
footer_layout = QtWidgets.QHBoxLayout()
footer_layout.setContentsMargins(0, 0, 0, 0)
footer_layout.addWidget(comment_input, 1)
footer_layout.addWidget(reset_btn, 0)
footer_layout.addWidget(stop_btn, 0)
footer_layout.addWidget(validate_btn, 0)
footer_layout.addWidget(publish_btn, 0)
footer_bottom_layout = QtWidgets.QHBoxLayout(footer_bottom_widget)
footer_bottom_layout.setContentsMargins(0, 0, 0, 0)
footer_bottom_layout.addStretch(1)
footer_bottom_layout.addWidget(reset_btn, 0)
footer_bottom_layout.addWidget(stop_btn, 0)
footer_bottom_layout.addWidget(validate_btn, 0)
footer_bottom_layout.addWidget(publish_btn, 0)
# Subset frame layout
subset_layout = QtWidgets.QVBoxLayout(subset_frame)
marings = subset_layout.contentsMargins()
# Spacer helps keep distance of Publish Frame when comment input
# is hidden - so when is shrunken it is not overlaying pages
footer_spacer = QtWidgets.QWidget(footer_widget)
footer_spacer.setMinimumHeight(self.publish_footer_spacer)
footer_spacer.setMaximumHeight(self.publish_footer_spacer)
footer_spacer.setVisible(False)
footer_layout = QtWidgets.QVBoxLayout(footer_widget)
footer_margins = footer_layout.contentsMargins()
footer_layout.setContentsMargins(
footer_margins.left() + self.footer_border,
footer_margins.top(),
footer_margins.right() + self.footer_border,
footer_margins.bottom() + self.footer_border
)
footer_layout.addWidget(comment_input, 0)
footer_layout.addWidget(footer_spacer, 0)
footer_layout.addWidget(footer_bottom_widget, 0)
# Content
# - wrap stacked widget under one more widget to be able propagate
# margins (QStackedLayout can't have margins)
content_widget = QtWidgets.QWidget(under_publish_widget)
content_stacked_widget = QtWidgets.QWidget(content_widget)
content_layout = QtWidgets.QVBoxLayout(content_widget)
marings = content_layout.contentsMargins()
marings.setLeft(marings.left() * 2)
marings.setRight(marings.right() * 2)
marings.setTop(marings.top() * 2)
marings.setBottom(marings.bottom() * 2)
subset_layout.setContentsMargins(marings)
subset_layout.addWidget(subset_content_widget, 1)
subset_layout.addLayout(footer_layout, 0)
marings.setBottom(0)
content_layout.setContentsMargins(marings)
content_layout.addWidget(content_stacked_widget, 1)
# Create publish frame
publish_frame = PublishFrame(controller, content_stacked_widget)
# Overview - create and attributes part
overview_widget = OverviewWidget(
controller, content_stacked_widget
)
report_widget = ValidationsWidget(controller, parent)
# Details - Publish details
publish_details_widget = PublishReportViewerWidget(
content_stacked_widget
)
content_stacked_layout = QtWidgets.QStackedLayout(
content_stacked_widget
@ -182,282 +184,348 @@ class PublisherWindow(QtWidgets.QDialog):
content_stacked_layout.setStackingMode(
QtWidgets.QStackedLayout.StackAll
)
content_stacked_layout.addWidget(subset_frame)
content_stacked_layout.addWidget(publish_frame)
content_stacked_layout.addWidget(overview_widget)
content_stacked_layout.addWidget(report_widget)
content_stacked_layout.addWidget(publish_details_widget)
content_stacked_layout.setCurrentWidget(overview_widget)
under_publish_layout = QtWidgets.QVBoxLayout(under_publish_widget)
under_publish_layout.setContentsMargins(0, 0, 0, 0)
under_publish_layout.setSpacing(0)
under_publish_layout.addWidget(content_widget, 1)
under_publish_layout.addWidget(footer_widget, 0)
# Overlay which covers inputs during publishing
publish_overlay = QtWidgets.QFrame(under_publish_stack)
publish_overlay.setObjectName("OverlayFrame")
under_publish_stack_layout = QtWidgets.QStackedLayout(
under_publish_stack
)
under_publish_stack_layout.setContentsMargins(0, 0, 0, 0)
under_publish_stack_layout.setStackingMode(
QtWidgets.QStackedLayout.StackAll
)
under_publish_stack_layout.addWidget(under_publish_widget)
under_publish_stack_layout.addWidget(publish_overlay)
under_publish_stack_layout.setCurrentWidget(under_publish_widget)
# Add main frame to this window
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
main_layout.addWidget(header_widget, 0)
main_layout.addWidget(line_widget, 0)
main_layout.addWidget(content_stacked_widget, 1)
main_layout.addWidget(tabs_widget, 0)
main_layout.addWidget(under_publish_stack, 1)
creator_window = CreateDialog(controller, parent=self)
# Floating publish frame
publish_frame = PublishFrame(controller, self.footer_border, self)
create_btn.clicked.connect(self._on_create_clicked)
delete_btn.clicked.connect(self._on_delete_clicked)
change_view_btn.clicked.connect(self._on_change_view_clicked)
help_btn.clicked.connect(self._on_help_click)
tabs_widget.tab_changed.connect(self._on_tab_change)
overview_widget.active_changed.connect(
self._on_context_or_active_change
)
overview_widget.instance_context_changed.connect(
self._on_context_or_active_change
)
overview_widget.create_requested.connect(
self._on_create_request
)
reset_btn.clicked.connect(self._on_reset_clicked)
stop_btn.clicked.connect(self._on_stop_clicked)
validate_btn.clicked.connect(self._on_validate_clicked)
publish_btn.clicked.connect(self._on_publish_clicked)
# Selection changed
subset_list_view.selection_changed.connect(
self._on_subset_change
publish_frame.details_page_requested.connect(self._go_to_details_tab)
controller.event_system.add_callback(
"instances.refresh.finished", self._on_instances_refresh
)
subset_view_cards.selection_changed.connect(
self._on_subset_change
controller.event_system.add_callback(
"publish.reset.finished", self._on_publish_reset
)
# Active instances changed
subset_list_view.active_changed.connect(
self._on_active_changed
controller.event_system.add_callback(
"publish.process.started", self._on_publish_start
)
subset_view_cards.active_changed.connect(
self._on_active_changed
controller.event_system.add_callback(
"publish.process.validated", self._on_publish_validated
)
# Instance context has changed
subset_attributes_widget.instance_context_changed.connect(
self._on_instance_context_change
controller.event_system.add_callback(
"publish.process.stopped", self._on_publish_stop
)
controller.event_system.add_callback(
"show.card.message", self._on_overlay_message
)
controller.add_instances_refresh_callback(self._on_instances_refresh)
# Store extra header widget for TrayPublisher
# - can be used to add additional widgets to header between context
# label and help button
self._help_dialog = help_dialog
self._help_btn = help_btn
controller.add_publish_reset_callback(self._on_publish_reset)
controller.add_publish_started_callback(self._on_publish_start)
controller.add_publish_validated_callback(self._on_publish_validated)
controller.add_publish_stopped_callback(self._on_publish_stop)
self._header_extra_widget = header_extra_widget
# Store header for TrayPublisher
self._header_layout = header_layout
self._tabs_widget = tabs_widget
self._create_tab = create_tab
self._content_stacked_widget = content_stacked_widget
self.content_stacked_layout = content_stacked_layout
self.publish_frame = publish_frame
self.subset_frame = subset_frame
self.subset_content_widget = subset_content_widget
self._under_publish_stack_layout = under_publish_stack_layout
self.context_label = context_label
self._under_publish_widget = under_publish_widget
self._publish_overlay = publish_overlay
self._publish_frame = publish_frame
self.subset_view_cards = subset_view_cards
self.subset_list_view = subset_list_view
self.subset_views_layout = subset_views_layout
self._content_stacked_layout = content_stacked_layout
self.delete_btn = delete_btn
self._overview_widget = overview_widget
self._report_widget = report_widget
self._publish_details_widget = publish_details_widget
self.subset_attributes_widget = subset_attributes_widget
self._context_label = context_label
self.comment_input = comment_input
self._comment_input = comment_input
self._footer_spacer = footer_spacer
self.stop_btn = stop_btn
self.reset_btn = reset_btn
self.validate_btn = validate_btn
self.publish_btn = publish_btn
self._stop_btn = stop_btn
self._reset_btn = reset_btn
self._validate_btn = validate_btn
self._publish_btn = publish_btn
self.controller = controller
self._overlay_object = overlay_object
self.creator_window = creator_window
self._controller = controller
self._first_show = True
self._reset_on_show = reset_on_show
self._restart_timer = None
self._publish_frame_visible = None
self._set_publish_visibility(False)
@property
def controller(self):
return self._controller
def showEvent(self, event):
super(PublisherWindow, self).showEvent(event)
if self._first_show:
self._first_show = False
self.resize(self.default_width, self.default_height)
self.setStyleSheet(style.load_stylesheet())
if self._reset_on_show:
self.reset()
self._on_first_show()
def resizeEvent(self, event):
super(PublisherWindow, self).resizeEvent(event)
self._update_publish_frame_rect()
def _on_overlay_message(self, event):
self._overlay_object.add_message(event["message"])
def _on_first_show(self):
self.resize(self.default_width, self.default_height)
self.setStyleSheet(style.load_stylesheet())
if not self._reset_on_show:
return
# Detach showing - give OS chance to draw the window
timer = QtCore.QTimer()
timer.setSingleShot(True)
timer.setInterval(1)
timer.timeout.connect(self._on_show_restart_timer)
self._restart_timer = timer
timer.start()
def _on_show_restart_timer(self):
"""Callback for '_restart_timer' timer."""
self._restart_timer = None
self.reset()
def closeEvent(self, event):
self.controller.save_changes()
self._controller.save_changes()
super(PublisherWindow, self).closeEvent(event)
def reset(self):
self.controller.reset()
self._controller.reset()
def set_context_label(self, label):
self.context_label.setText(label)
self._context_label.setText(label)
def get_selected_items(self):
view = self.subset_views_layout.currentWidget()
return view.get_selected_items()
def _on_instance_context_change(self):
current_idx = self.subset_views_layout.currentIndex()
for idx in range(self.subset_views_layout.count()):
if idx == current_idx:
continue
widget = self.subset_views_layout.widget(idx)
if widget.refreshed:
widget.set_refreshed(False)
current_widget = self.subset_views_layout.widget(current_idx)
current_widget.refresh_instance_states()
self._validate_create_instances()
def _change_view_type(self):
idx = self.subset_views_layout.currentIndex()
new_idx = (idx + 1) % self.subset_views_layout.count()
self.subset_views_layout.setCurrentIndex(new_idx)
new_view = self.subset_views_layout.currentWidget()
if not new_view.refreshed:
new_view.refresh()
new_view.set_refreshed(True)
else:
new_view.refresh_instance_states()
self._on_subset_change()
def _on_create_clicked(self):
self.creator_window.show()
def _on_delete_clicked(self):
instances, _ = self.get_selected_items()
# Ask user if he really wants to remove instances
dialog = QtWidgets.QMessageBox(self)
dialog.setIcon(QtWidgets.QMessageBox.Question)
dialog.setWindowTitle("Are you sure?")
if len(instances) > 1:
msg = (
"Do you really want to remove {} instances?"
).format(len(instances))
else:
msg = (
"Do you really want to remove the instance?"
)
dialog.setText(msg)
dialog.setStandardButtons(
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel
)
dialog.setDefaultButton(QtWidgets.QMessageBox.Ok)
dialog.setEscapeButton(QtWidgets.QMessageBox.Cancel)
dialog.exec_()
# Skip if OK was not clicked
if dialog.result() == QtWidgets.QMessageBox.Ok:
self.controller.remove_instances(instances)
def _on_change_view_clicked(self):
self._change_view_type()
def _set_publish_visibility(self, visible):
if visible:
widget = self.publish_frame
publish_frame_visible = True
else:
widget = self.subset_frame
publish_frame_visible = False
self.content_stacked_layout.setCurrentWidget(widget)
self._set_publish_frame_visible(publish_frame_visible)
def _set_publish_frame_visible(self, publish_frame_visible):
"""Publish frame visibility has changed.
Also used in TrayPublisher to be able handle start/end of publish
widget overlay.
"""
# Hide creator dialog if visible
if publish_frame_visible and self.creator_window.isVisible():
self.creator_window.close()
def _on_reset_clicked(self):
self.controller.reset()
def _on_stop_clicked(self):
self.controller.stop_publish()
def _set_publish_comment(self):
if self.controller.publish_comment_is_set:
def _update_publish_details_widget(self, force=False):
if not force and self._tabs_widget.current_tab() != "details":
return
comment = self.comment_input.text()
self.controller.set_comment(comment)
report_data = self.controller.get_publish_report()
self._publish_details_widget.set_report_data(report_data)
def _on_help_click(self):
if self._help_dialog.isVisible():
return
self._help_dialog.show()
window = self.window()
desktop = QtWidgets.QApplication.desktop()
screen_idx = desktop.screenNumber(window)
screen = desktop.screen(screen_idx)
screen_rect = screen.geometry()
window_geo = window.geometry()
dialog_x = window_geo.x() + window_geo.width()
dialog_right = (dialog_x + self._help_dialog.width()) - 1
diff = dialog_right - screen_rect.right()
if diff > 0:
dialog_x -= diff
self._help_dialog.setGeometry(
dialog_x, window_geo.y(),
self._help_dialog.width(), self._help_dialog.height()
)
def _on_tab_change(self, old_tab, new_tab):
if old_tab == "details":
self._publish_details_widget.close_details_popup()
if new_tab in ("create", "publish"):
animate = True
if old_tab not in ("create", "publish"):
animate = False
self._content_stacked_layout.setCurrentWidget(
self._overview_widget
)
self._overview_widget.set_state(new_tab, animate)
elif new_tab == "details":
self._content_stacked_layout.setCurrentWidget(
self._publish_details_widget
)
self._update_publish_details_widget()
elif new_tab == "report":
self._content_stacked_layout.setCurrentWidget(
self._report_widget
)
def _on_context_or_active_change(self):
self._validate_create_instances()
def _on_create_request(self):
self._go_to_create_tab()
def _go_to_create_tab(self):
self._tabs_widget.set_current_tab("create")
def _go_to_details_tab(self):
self._tabs_widget.set_current_tab("details")
def _go_to_report_tab(self):
self._tabs_widget.set_current_tab("report")
def _set_publish_overlay_visibility(self, visible):
if visible:
widget = self._publish_overlay
else:
widget = self._under_publish_widget
self._under_publish_stack_layout.setCurrentWidget(widget)
def _set_publish_visibility(self, visible):
if visible is self._publish_frame_visible:
return
self._publish_frame_visible = visible
self._publish_frame.setVisible(visible)
self._update_publish_frame_rect()
def _on_reset_clicked(self):
self._controller.reset()
def _on_stop_clicked(self):
self._controller.stop_publish()
def _set_publish_comment(self):
if self._controller.publish_comment_is_set:
return
comment = self._comment_input.text()
self._controller.set_comment(comment)
def _on_validate_clicked(self):
self._set_publish_comment()
self._set_publish_visibility(True)
self.controller.validate()
self._controller.validate()
def _on_publish_clicked(self):
self._set_publish_comment()
self._set_publish_visibility(True)
self.controller.publish()
def _refresh_instances(self):
if self._refreshing_instances:
return
self._refreshing_instances = True
for idx in range(self.subset_views_layout.count()):
widget = self.subset_views_layout.widget(idx)
widget.set_refreshed(False)
view = self.subset_views_layout.currentWidget()
view.refresh()
view.set_refreshed(True)
self._refreshing_instances = False
# Force to change instance and refresh details
self._on_subset_change()
def _on_instances_refresh(self):
self._refresh_instances()
self._validate_create_instances()
context_title = self.controller.get_context_title()
self.set_context_label(context_title)
# Give a change to process Resize Request
QtWidgets.QApplication.processEvents()
# Trigger update geometry of
widget = self.subset_views_layout.currentWidget()
widget.updateGeometry()
def _on_subset_change(self, *_args):
# Ignore changes if in middle of refreshing
if self._refreshing_instances:
return
instances, context_selected = self.get_selected_items()
# Disable delete button if nothing is selected
self.delete_btn.setEnabled(len(instances) > 0)
self.subset_attributes_widget.set_current_instances(
instances, context_selected
)
def _on_active_changed(self):
if self._refreshing_instances:
return
self._validate_create_instances()
self._controller.publish()
def _set_footer_enabled(self, enabled):
self.comment_input.setEnabled(enabled)
self.reset_btn.setEnabled(True)
self._reset_btn.setEnabled(True)
if enabled:
self.stop_btn.setEnabled(False)
self.validate_btn.setEnabled(True)
self.publish_btn.setEnabled(True)
self._stop_btn.setEnabled(False)
self._validate_btn.setEnabled(True)
self._publish_btn.setEnabled(True)
else:
self.stop_btn.setEnabled(enabled)
self.validate_btn.setEnabled(enabled)
self.publish_btn.setEnabled(enabled)
self._stop_btn.setEnabled(enabled)
self._validate_btn.setEnabled(enabled)
self._publish_btn.setEnabled(enabled)
def _on_publish_reset(self):
self._create_tab.setEnabled(True)
self._set_comment_input_visiblity(True)
self._set_publish_overlay_visibility(False)
self._set_publish_visibility(False)
self._set_footer_enabled(False)
self._update_publish_details_widget()
def _on_publish_start(self):
self._create_tab.setEnabled(False)
self._reset_btn.setEnabled(False)
self._stop_btn.setEnabled(True)
self._validate_btn.setEnabled(False)
self._publish_btn.setEnabled(False)
self._set_comment_input_visiblity(False)
self._set_publish_visibility(True)
self._set_publish_overlay_visibility(True)
self._publish_details_widget.close_details_popup()
if self._tabs_widget.is_current_tab(self._create_tab):
self._tabs_widget.set_current_tab("publish")
def _on_publish_validated(self):
self._validate_btn.setEnabled(False)
def _on_publish_stop(self):
self._set_publish_overlay_visibility(False)
self._reset_btn.setEnabled(True)
self._stop_btn.setEnabled(False)
validate_enabled = not self._controller.publish_has_crashed
publish_enabled = not self._controller.publish_has_crashed
if validate_enabled:
validate_enabled = not self._controller.publish_has_validated
if publish_enabled:
if (
self._controller.publish_has_validated
and self._controller.publish_has_validation_errors
):
publish_enabled = False
if self._tabs_widget.is_current_tab("publish"):
self._go_to_report_tab()
else:
publish_enabled = not self._controller.publish_has_finished
self._validate_btn.setEnabled(validate_enabled)
self._publish_btn.setEnabled(publish_enabled)
self._update_publish_details_widget()
def _validate_create_instances(self):
if not self.controller.host_is_valid:
if not self._controller.host_is_valid:
self._set_footer_enabled(True)
return
all_valid = None
for instance in self.controller.instances:
for instance in self._controller.instances:
if not instance["active"]:
continue
@ -470,38 +538,29 @@ class PublisherWindow(QtWidgets.QDialog):
self._set_footer_enabled(bool(all_valid))
def _on_publish_reset(self):
self._set_publish_visibility(False)
def _on_instances_refresh(self):
self._validate_create_instances()
self.subset_content_widget.setEnabled(self.controller.host_is_valid)
context_title = self.controller.get_context_title()
self.set_context_label(context_title)
self._update_publish_details_widget()
self._set_footer_enabled(False)
def _set_comment_input_visiblity(self, visible):
self._comment_input.setVisible(visible)
self._footer_spacer.setVisible(not visible)
def _on_publish_start(self):
self.reset_btn.setEnabled(False)
self.stop_btn.setEnabled(True)
self.validate_btn.setEnabled(False)
self.publish_btn.setEnabled(False)
def _update_publish_frame_rect(self):
if not self._publish_frame_visible:
return
def _on_publish_validated(self):
self.validate_btn.setEnabled(False)
window_size = self.size()
size_hint = self._publish_frame.minimumSizeHint()
def _on_publish_stop(self):
self.reset_btn.setEnabled(True)
self.stop_btn.setEnabled(False)
validate_enabled = not self.controller.publish_has_crashed
publish_enabled = not self.controller.publish_has_crashed
if validate_enabled:
validate_enabled = not self.controller.publish_has_validated
if publish_enabled:
if (
self.controller.publish_has_validated
and self.controller.publish_has_validation_errors
):
publish_enabled = False
width = window_size.width()
height = size_hint.height()
else:
publish_enabled = not self.controller.publish_has_finished
self._publish_frame.resize(width, height)
self.validate_btn.setEnabled(validate_enabled)
self.publish_btn.setEnabled(publish_enabled)
self._publish_frame.move(
0, window_size.height() - height
)

View file

@ -6,25 +6,23 @@ Tray publisher can be considered as host implementeation with creators and
publishing plugins.
"""
import platform
from Qt import QtWidgets, QtCore
import qtawesome
import appdirs
from openpype.pipeline import (
install_host,
AvalonMongoDB,
)
from openpype.lib import JSONSettingRegistry
from openpype.pipeline import install_host
from openpype.hosts.traypublisher.api import TrayPublisherHost
from openpype.tools.publisher import PublisherWindow
from openpype.tools.publisher.window import PublisherWindow
from openpype.tools.utils import PlaceholderLineEdit
from openpype.tools.utils.constants import PROJECT_NAME_ROLE
from openpype.tools.utils.models import (
ProjectModel,
ProjectSortFilterProxy
)
from openpype.tools.utils import PlaceholderLineEdit
import appdirs
from openpype.lib import JSONSettingRegistry
class TrayPublisherRegistry(JSONSettingRegistry):
"""Class handling OpenPype general settings registry.
@ -55,14 +53,10 @@ class StandaloneOverlayWidget(QtWidgets.QFrame):
content_widget = QtWidgets.QWidget(middle_frame)
# Create db connection for projects model
dbcon = AvalonMongoDB()
dbcon.install()
header_label = QtWidgets.QLabel("Choose project", content_widget)
header_label.setObjectName("ChooseProjectLabel")
# Create project models and view
projects_model = ProjectModel(dbcon)
projects_model = ProjectModel()
projects_proxy = ProjectSortFilterProxy()
projects_proxy.setSourceModel(projects_model)
projects_proxy.setFilterKeyColumn(0)
@ -137,11 +131,14 @@ class StandaloneOverlayWidget(QtWidgets.QFrame):
src_index = self._projects_model.find_project(project_name)
if src_index is not None:
index = self._projects_proxy.mapFromSource(src_index)
if index:
mode = (
QtCore.QItemSelectionModel.Select
| QtCore.QItemSelectionModel.Rows)
self._projects_view.selectionModel().select(index, mode)
if index is not None:
selection_model = self._projects_view.selectionModel()
selection_model.select(
index,
QtCore.QItemSelectionModel.SelectCurrent
)
self._projects_view.setCurrentIndex(index)
self._cancel_btn.setVisible(self._project_name is not None)
super(StandaloneOverlayWidget, self).showEvent(event)
@ -193,7 +190,7 @@ class TrayPublishWindow(PublisherWindow):
overlay_widget = StandaloneOverlayWidget(self)
btns_widget = QtWidgets.QWidget(self)
btns_widget = self._header_extra_widget
back_to_overlay_btn = QtWidgets.QPushButton(
"Change project", btns_widget
@ -208,8 +205,6 @@ class TrayPublishWindow(PublisherWindow):
btns_layout.addWidget(save_btn, 0)
btns_layout.addWidget(back_to_overlay_btn, 0)
self._header_layout.addWidget(btns_widget, 0)
overlay_widget.project_selected.connect(self._on_project_select)
back_to_overlay_btn.clicked.connect(self._on_back_to_overlay)
save_btn.clicked.connect(self._on_tray_publish_save)
@ -239,22 +234,32 @@ class TrayPublishWindow(PublisherWindow):
def _on_project_select(self, project_name):
# TODO register project specific plugin paths
self.controller.save_changes()
self.controller.reset_project_data_cache()
self._controller.save_changes()
self._controller.reset_project_data_cache()
self.reset()
if not self.controller.instances:
self._on_create_clicked()
if not self._controller.instances:
self._go_to_create_tab()
def _on_tray_publish_save(self):
self.controller.save_changes()
self._controller.save_changes()
print("NOT YET IMPLEMENTED")
def main():
host = TrayPublisherHost()
install_host(host)
app = QtWidgets.QApplication([])
app_instance = QtWidgets.QApplication.instance()
if app_instance is None:
app_instance = QtWidgets.QApplication([])
if platform.system().lower() == "windows":
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
u"traypublisher"
)
window = TrayPublishWindow()
window.show()
app.exec_()
app_instance.exec_()

View file

@ -122,6 +122,16 @@ class OverlayMessageWidget(QtWidgets.QFrame):
self._timeout_timer = timeout_timer
self._hover_timer = hover_timer
def update_message(self, message, message_type=None, timeout=None):
self._label_widget.setText(message)
if timeout:
self._timeout_timer.setInterval(timeout)
if message_type:
set_style_property(self, "type", message_type)
self._timeout_timer.start()
def size_hint_without_word_wrap(self):
"""Size hint in cases that word wrap of label is disabled."""
self._label_widget.setWordWrap(False)
@ -195,7 +205,9 @@ class MessageOverlayObject(QtCore.QObject):
self._move_size_remove = 8
self._default_timeout = default_timeout
def add_message(self, message, message_type=None, timeout=None):
def add_message(
self, message, message_type=None, timeout=None, message_id=None
):
"""Add single message into overlay.
Args:
@ -203,6 +215,12 @@ class MessageOverlayObject(QtCore.QObject):
timeout (int): Message timeout.
message_type (str): Message type can be used as property in
stylesheets.
message_id (str): UUID of already existing message to update
it's message and timeout. Is created with different id if is
not available anymore.
Returns:
str: UUID of message which can be used to update message.
"""
# Skip empty messages
if not message:
@ -212,31 +230,48 @@ class MessageOverlayObject(QtCore.QObject):
timeout = self._default_timeout
# Create unique id of message
label_id = str(uuid.uuid4())
# Create message widget
widget = OverlayMessageWidget(
label_id, message, self._widget, message_type, timeout
)
widget.close_requested.connect(self._on_message_close_request)
widget.show()
widget = None
if message_id is not None:
widget = self._messages.get(message_id)
if message_id is None:
message_id = str(uuid.uuid4())
elif message_id in self._messages_order:
self._messages_order.remove(message_id)
if widget is not None:
# NOTE: Update of message won't change paint order which should be
# ok in most of cases as it matters only when messages are
# animated
widget.update_message(message, message_type, timeout)
else:
# Create message widget
widget = OverlayMessageWidget(
message_id, message, self._widget, message_type, timeout
)
widget.close_requested.connect(self._on_message_close_request)
widget.show()
# Move widget outside of window
pos = widget.pos()
pos.setY(pos.y() - widget.height())
widget.move(pos)
# Store message
self._messages[label_id] = widget
self._messages_order.append(label_id)
self._messages[message_id] = widget
self._messages_order.append(message_id)
# Trigger recalculation timer
self._recalculate_timer.start()
def _on_message_close_request(self, label_id):
return message_id
def _on_message_close_request(self, message_id):
"""Message widget requested removement."""
widget = self._messages.get(label_id)
widget = self._messages.get(message_id)
if widget is not None:
# Add message to closing messages and start recalculation
self._closing_messages.add(label_id)
self._closing_messages.add(message_id)
self._recalculate_timer.start()
def _recalculate_positions(self):