Publisher: Explicit save (#4676)

* added save button class

* added save button to window

* workfiles is also part of context in CreateContext to be able check if context changed

* window cares about trigger of convertors

* use abstractproperty with property and abstractmethod decorators

* save changes happens using main window and can be blocked

* fix pyside compatibility

* use create context to get current context names

* Fix docstring label

* added shortcuts for save and reset

* change control string matching for macos

* added 'publish_has_started' property

* allow save only if publishing did not start yet

* rename 'get_selected_convertors' to 'get_selected_legacy_convertors' and added docstrings

* Added Saved changes

* disable instances toggle when publishing started

* Fix reset button tooltip

Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com>

* Use QKeySequence to string for tooltips

* added example output

* use predefined method to emit card message

---------

Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com>
This commit is contained in:
Jakub Trllo 2023-03-23 12:52:25 +01:00 committed by GitHub
parent 04ecb8afa1
commit d0f083ec19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 450 additions and 84 deletions

View file

@ -22,7 +22,7 @@ from openpype.lib.attribute_definitions import (
deserialize_attr_defs,
get_default_values,
)
from openpype.host import IPublishHost
from openpype.host import IPublishHost, IWorkfileHost
from openpype.pipeline import legacy_io
from openpype.pipeline.plugin_discover import DiscoverResult
@ -1374,6 +1374,7 @@ class CreateContext:
self._current_project_name = None
self._current_asset_name = None
self._current_task_name = None
self._current_workfile_path = None
self._host_is_valid = host_is_valid
# Currently unused variable
@ -1503,14 +1504,62 @@ class CreateContext:
return os.environ["AVALON_APP"]
def get_current_project_name(self):
"""Project name which was used as current context on context reset.
Returns:
Union[str, None]: Project name.
"""
return self._current_project_name
def get_current_asset_name(self):
"""Asset name which was used as current context on context reset.
Returns:
Union[str, None]: Asset name.
"""
return self._current_asset_name
def get_current_task_name(self):
"""Task name which was used as current context on context reset.
Returns:
Union[str, None]: Task name.
"""
return self._current_task_name
def get_current_workfile_path(self):
"""Workfile path which was opened on context reset.
Returns:
Union[str, None]: Workfile path.
"""
return self._current_workfile_path
@property
def context_has_changed(self):
"""Host context has changed.
As context is used project, asset, task name and workfile path if
host does support workfiles.
Returns:
bool: Context changed.
"""
project_name, asset_name, task_name, workfile_path = (
self._get_current_host_context()
)
return (
self._current_project_name != project_name
or self._current_asset_name != asset_name
or self._current_task_name != task_name
or self._current_workfile_path != workfile_path
)
project_name = property(get_current_project_name)
@property
@ -1575,6 +1624,28 @@ class CreateContext:
self._collection_shared_data = None
self.refresh_thumbnails()
def _get_current_host_context(self):
project_name = asset_name = task_name = workfile_path = None
if hasattr(self.host, "get_current_context"):
host_context = self.host.get_current_context()
if host_context:
project_name = host_context.get("project_name")
asset_name = host_context.get("asset_name")
task_name = host_context.get("task_name")
if isinstance(self.host, IWorkfileHost):
workfile_path = self.host.get_current_workfile()
# --- TODO remove these conditions ---
if not project_name:
project_name = legacy_io.Session.get("AVALON_PROJECT")
if not asset_name:
asset_name = legacy_io.Session.get("AVALON_ASSET")
if not task_name:
task_name = legacy_io.Session.get("AVALON_TASK")
# ---
return project_name, asset_name, task_name, workfile_path
def reset_current_context(self):
"""Refresh current context.
@ -1593,24 +1664,14 @@ class CreateContext:
are stored. We should store the workfile (if is available) too.
"""
project_name = asset_name = task_name = None
if hasattr(self.host, "get_current_context"):
host_context = self.host.get_current_context()
if host_context:
project_name = host_context.get("project_name")
asset_name = host_context.get("asset_name")
task_name = host_context.get("task_name")
if not project_name:
project_name = legacy_io.Session.get("AVALON_PROJECT")
if not asset_name:
asset_name = legacy_io.Session.get("AVALON_ASSET")
if not task_name:
task_name = legacy_io.Session.get("AVALON_TASK")
project_name, asset_name, task_name, workfile_path = (
self._get_current_host_context()
)
self._current_project_name = project_name
self._current_asset_name = asset_name
self._current_task_name = task_name
self._current_workfile_path = workfile_path
def reset_plugins(self, discover_publish_plugins=True):
"""Reload plugins.

View file

@ -1,4 +1,4 @@
from qtpy import QtCore
from qtpy import QtCore, QtGui
# ID of context item in instance view
CONTEXT_ID = "context"
@ -26,6 +26,9 @@ GROUP_ROLE = QtCore.Qt.UserRole + 7
CONVERTER_IDENTIFIER_ROLE = QtCore.Qt.UserRole + 8
CREATOR_SORT_ROLE = QtCore.Qt.UserRole + 9
ResetKeySequence = QtGui.QKeySequence(
QtCore.Qt.ControlModifier | QtCore.Qt.Key_R
)
__all__ = (
"CONTEXT_ID",

View file

@ -6,7 +6,7 @@ import collections
import uuid
import tempfile
import shutil
from abc import ABCMeta, abstractmethod, abstractproperty
from abc import ABCMeta, abstractmethod
import six
import pyblish.api
@ -964,7 +964,8 @@ class AbstractPublisherController(object):
access objects directly but by using wrappers that can be serialized.
"""
@abstractproperty
@property
@abstractmethod
def log(self):
"""Controller's logger object.
@ -974,13 +975,15 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def event_system(self):
"""Inner event system for publisher controller."""
pass
@abstractproperty
@property
@abstractmethod
def project_name(self):
"""Current context project name.
@ -990,7 +993,8 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def current_asset_name(self):
"""Current context asset name.
@ -1000,7 +1004,8 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def current_task_name(self):
"""Current context task name.
@ -1010,7 +1015,21 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def host_context_has_changed(self):
"""Host context changed after last reset.
'CreateContext' has this option available using 'context_has_changed'.
Returns:
bool: Context has changed.
"""
pass
@property
@abstractmethod
def host_is_valid(self):
"""Host is valid for creation part.
@ -1023,7 +1042,8 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def instances(self):
"""Collected/created instances.
@ -1134,7 +1154,13 @@ class AbstractPublisherController(object):
@abstractmethod
def save_changes(self):
"""Save changes in create context."""
"""Save changes in create context.
Save can crash because of unexpected errors.
Returns:
bool: Save was successful.
"""
pass
@ -1145,7 +1171,19 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def publish_has_started(self):
"""Has publishing finished.
Returns:
bool: If publishing finished and all plugins were iterated.
"""
pass
@property
@abstractmethod
def publish_has_finished(self):
"""Has publishing finished.
@ -1155,7 +1193,8 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def publish_is_running(self):
"""Publishing is running right now.
@ -1165,7 +1204,8 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def publish_has_validated(self):
"""Publish validation passed.
@ -1175,7 +1215,8 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def publish_has_crashed(self):
"""Publishing crashed for any reason.
@ -1185,7 +1226,8 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def publish_has_validation_errors(self):
"""During validation happened at least one validation error.
@ -1195,7 +1237,8 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def publish_max_progress(self):
"""Get maximum possible progress number.
@ -1205,7 +1248,8 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def publish_progress(self):
"""Current progress number.
@ -1215,7 +1259,8 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def publish_error_msg(self):
"""Current error message which cause fail of publishing.
@ -1267,7 +1312,8 @@ class AbstractPublisherController(object):
pass
@abstractproperty
@property
@abstractmethod
def convertor_items(self):
pass
@ -1356,6 +1402,7 @@ class BasePublisherController(AbstractPublisherController):
self._publish_has_validation_errors = False
self._publish_has_crashed = False
# All publish plugins are processed
self._publish_has_started = False
self._publish_has_finished = False
self._publish_max_progress = 0
self._publish_progress = 0
@ -1386,7 +1433,8 @@ class BasePublisherController(AbstractPublisherController):
"show.card.message" - Show card message request (UI related).
"instances.refresh.finished" - Instances are refreshed.
"plugins.refresh.finished" - Plugins refreshed.
"publish.reset.finished" - Publish context reset finished.
"publish.reset.finished" - Reset finished.
"controller.reset.started" - Controller reset started.
"controller.reset.finished" - Controller reset finished.
"publish.process.started" - Publishing started. Can be started from
paused state.
@ -1425,7 +1473,16 @@ class BasePublisherController(AbstractPublisherController):
def _set_host_is_valid(self, value):
if self._host_is_valid != value:
self._host_is_valid = value
self._emit_event("publish.host_is_valid.changed", {"value": value})
self._emit_event(
"publish.host_is_valid.changed", {"value": value}
)
def _get_publish_has_started(self):
return self._publish_has_started
def _set_publish_has_started(self, value):
if value != self._publish_has_started:
self._publish_has_started = value
def _get_publish_has_finished(self):
return self._publish_has_finished
@ -1449,7 +1506,9 @@ class BasePublisherController(AbstractPublisherController):
def _set_publish_has_validated(self, value):
if self._publish_has_validated != value:
self._publish_has_validated = value
self._emit_event("publish.has_validated.changed", {"value": value})
self._emit_event(
"publish.has_validated.changed", {"value": value}
)
def _get_publish_has_crashed(self):
return self._publish_has_crashed
@ -1497,6 +1556,9 @@ class BasePublisherController(AbstractPublisherController):
host_is_valid = property(
_get_host_is_valid, _set_host_is_valid
)
publish_has_started = property(
_get_publish_has_started, _set_publish_has_started
)
publish_has_finished = property(
_get_publish_has_finished, _set_publish_has_finished
)
@ -1526,6 +1588,7 @@ class BasePublisherController(AbstractPublisherController):
"""Reset most of attributes that can be reset."""
self.publish_is_running = False
self.publish_has_started = False
self.publish_has_validated = False
self.publish_has_crashed = False
self.publish_has_validation_errors = False
@ -1645,10 +1708,7 @@ class PublisherController(BasePublisherController):
str: Project name.
"""
if not hasattr(self._host, "get_current_context"):
return legacy_io.active_project()
return self._host.get_current_context()["project_name"]
return self._create_context.get_current_project_name()
@property
def current_asset_name(self):
@ -1658,10 +1718,7 @@ class PublisherController(BasePublisherController):
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"]
return self._create_context.get_current_asset_name()
@property
def current_task_name(self):
@ -1671,10 +1728,11 @@ class PublisherController(BasePublisherController):
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._create_context.get_current_task_name()
return self._host.get_current_context()["task_name"]
@property
def host_context_has_changed(self):
return self._create_context.context_has_changed
@property
def instances(self):
@ -1751,6 +1809,8 @@ class PublisherController(BasePublisherController):
"""Reset everything related to creation and publishing."""
self.stop_publish()
self._emit_event("controller.reset.started")
self.host_is_valid = self._create_context.host_is_valid
self._create_context.reset_preparation()
@ -1992,7 +2052,15 @@ class PublisherController(BasePublisherController):
)
def trigger_convertor_items(self, convertor_identifiers):
self.save_changes()
"""Trigger legacy item convertors.
This functionality requires to save and reset CreateContext. The reset
is needed so Creators can collect converted items.
Args:
convertor_identifiers (list[str]): Identifiers of convertor
plugins.
"""
success = True
try:
@ -2039,13 +2107,33 @@ class PublisherController(BasePublisherController):
self._on_create_instance_change()
return success
def save_changes(self):
"""Save changes happened during creation."""
def save_changes(self, show_message=True):
"""Save changes happened during creation.
Trigger save of changes using host api. This functionality does not
validate anything. It is required to do checks before this method is
called to be able to give user actionable response e.g. check of
context using 'host_context_has_changed'.
Args:
show_message (bool): Show message that changes were
saved successfully.
Returns:
bool: Save of changes was successful.
"""
if not self._create_context.host_is_valid:
return
# TODO remove
# Fake success save when host is not valid for CreateContext
# this is for testing as experimental feature
return True
try:
self._create_context.save_changes()
if show_message:
self.emit_card_message("Saved changes..")
return True
except CreatorsOperationFailed as exc:
self._emit_event(
@ -2056,16 +2144,17 @@ class PublisherController(BasePublisherController):
}
)
return False
def remove_instances(self, instance_ids):
"""Remove instances based on instance ids.
Args:
instance_ids (List[str]): List of instance ids to remove.
"""
# QUESTION Expect that instances are really removed? In that case save
# reset is not required and save changes too.
self.save_changes()
# QUESTION Expect that instances are really removed? In that case reset
# is not required.
self._remove_instances_from_context(instance_ids)
self._on_create_instance_change()
@ -2136,12 +2225,22 @@ class PublisherController(BasePublisherController):
self._publish_comment_is_set = True
def publish(self):
"""Run publishing."""
"""Run publishing.
Make sure all changes are saved before method is called (Call
'save_changes' and check output).
"""
self._publish_up_validation = False
self._start_publish()
def validate(self):
"""Run publishing and stop after Validation."""
"""Run publishing and stop after Validation.
Make sure all changes are saved before method is called (Call
'save_changes' and check output).
"""
if self.publish_has_validated:
return
self._publish_up_validation = True
@ -2152,10 +2251,8 @@ class PublisherController(BasePublisherController):
if self.publish_is_running:
return
# Make sure changes are saved
self.save_changes()
self.publish_is_running = True
self.publish_has_started = True
self._emit_event("publish.process.started")

View file

@ -4,8 +4,9 @@ from .icons import (
get_icon
)
from .widgets import (
StopBtn,
SaveBtn,
ResetBtn,
StopBtn,
ValidateBtn,
PublishBtn,
CreateNextPageOverlay,
@ -25,8 +26,9 @@ __all__ = (
"get_pixmap",
"get_icon",
"StopBtn",
"SaveBtn",
"ResetBtn",
"StopBtn",
"ValidateBtn",
"PublishBtn",
"CreateNextPageOverlay",

View file

@ -164,6 +164,11 @@ class BaseGroupWidget(QtWidgets.QWidget):
def _on_widget_selection(self, instance_id, group_id, selection_type):
self.selected.emit(instance_id, group_id, selection_type)
def set_active_toggle_enabled(self, enabled):
for widget in self._widgets_by_id.values():
if isinstance(widget, InstanceCardWidget):
widget.set_active_toggle_enabled(enabled)
class ConvertorItemsGroupWidget(BaseGroupWidget):
def update_items(self, items_by_id):
@ -437,6 +442,9 @@ class InstanceCardWidget(CardWidget):
self.update_instance_values()
def set_active_toggle_enabled(self, enabled):
self._active_checkbox.setEnabled(enabled)
def set_active(self, new_value):
"""Set instance as active."""
checkbox_value = self._active_checkbox.isChecked()
@ -551,6 +559,7 @@ class InstanceCardView(AbstractInstanceView):
self._context_widget = None
self._convertor_items_group = None
self._active_toggle_enabled = True
self._widgets_by_group = {}
self._ordered_groups = []
@ -667,6 +676,9 @@ class InstanceCardView(AbstractInstanceView):
group_widget.update_instances(
instances_by_group[group_name]
)
group_widget.set_active_toggle_enabled(
self._active_toggle_enabled
)
self._update_ordered_group_names()
@ -1091,3 +1103,10 @@ class InstanceCardView(AbstractInstanceView):
self._explicitly_selected_groups = selected_groups
self._explicitly_selected_instance_ids = selected_instances
def set_active_toggle_enabled(self, enabled):
if self._active_toggle_enabled is enabled:
return
self._active_toggle_enabled = enabled
for group_widget in self._widgets_by_group.values():
group_widget.set_active_toggle_enabled(enabled)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -198,6 +198,9 @@ class InstanceListItemWidget(QtWidgets.QWidget):
self.instance["active"] = new_value
self.active_changed.emit(self.instance.id, new_value)
def set_active_toggle_enabled(self, enabled):
self._active_checkbox.setEnabled(enabled)
class ListContextWidget(QtWidgets.QFrame):
"""Context (or global attributes) widget."""
@ -302,6 +305,9 @@ class InstanceListGroupWidget(QtWidgets.QFrame):
else:
self.expand_btn.setArrowType(QtCore.Qt.RightArrow)
def set_active_toggle_enabled(self, enabled):
self.toggle_checkbox.setEnabled(enabled)
class InstanceTreeView(QtWidgets.QTreeView):
"""View showing instances and their groups."""
@ -461,6 +467,8 @@ class InstanceListView(AbstractInstanceView):
self._instance_model = instance_model
self._proxy_model = proxy_model
self._active_toggle_enabled = True
def _on_expand(self, index):
self._update_widget_expand_state(index, True)
@ -667,6 +675,9 @@ class InstanceListView(AbstractInstanceView):
widget = InstanceListItemWidget(
instance, self._instance_view
)
widget.set_active_toggle_enabled(
self._active_toggle_enabled
)
widget.active_changed.connect(self._on_active_changed)
self._instance_view.setIndexWidget(proxy_index, widget)
self._widgets_by_id[instance.id] = widget
@ -802,6 +813,9 @@ class InstanceListView(AbstractInstanceView):
proxy_index = self._proxy_model.mapFromSource(index)
group_name = group_item.data(GROUP_ROLE)
widget = InstanceListGroupWidget(group_name, self._instance_view)
widget.set_active_toggle_enabled(
self._active_toggle_enabled
)
widget.expand_changed.connect(self._on_group_expand_request)
widget.toggle_requested.connect(self._on_group_toggle_request)
self._group_widgets[group_name] = widget
@ -1051,3 +1065,16 @@ class InstanceListView(AbstractInstanceView):
QtCore.QItemSelectionModel.Select
| QtCore.QItemSelectionModel.Rows
)
def set_active_toggle_enabled(self, enabled):
if self._active_toggle_enabled is enabled:
return
self._active_toggle_enabled = enabled
for widget in self._widgets_by_id.values():
if isinstance(widget, InstanceListItemWidget):
widget.set_active_toggle_enabled(enabled)
for widget in self._group_widgets.values():
if isinstance(widget, InstanceListGroupWidget):
widget.set_active_toggle_enabled(enabled)

View file

@ -17,6 +17,7 @@ class OverviewWidget(QtWidgets.QFrame):
active_changed = QtCore.Signal()
instance_context_changed = QtCore.Signal()
create_requested = QtCore.Signal()
convert_requested = QtCore.Signal()
anim_end_value = 200
anim_duration = 200
@ -132,6 +133,9 @@ class OverviewWidget(QtWidgets.QFrame):
controller.event_system.add_callback(
"publish.process.started", self._on_publish_start
)
controller.event_system.add_callback(
"controller.reset.started", self._on_controller_reset_start
)
controller.event_system.add_callback(
"publish.reset.finished", self._on_publish_reset
)
@ -336,13 +340,31 @@ class OverviewWidget(QtWidgets.QFrame):
self.instance_context_changed.emit()
def _on_convert_requested(self):
_, _, convertor_identifiers = self.get_selected_items()
self._controller.trigger_convertor_items(convertor_identifiers)
self.convert_requested.emit()
def get_selected_items(self):
"""Selected items in current view widget.
Returns:
tuple[list[str], bool, list[str]]: Selected items. List of
instance ids, context is selected, list of selected legacy
convertor plugins.
"""
view = self._subset_views_layout.currentWidget()
return view.get_selected_items()
def get_selected_legacy_convertors(self):
"""Selected legacy convertor identifiers.
Returns:
list[str]: Selected legacy convertor identifiers.
Example: ['io.openpype.creators.houdini.legacy']
"""
_, _, convertor_identifiers = self.get_selected_items()
return convertor_identifiers
def _change_view_type(self):
idx = self._subset_views_layout.currentIndex()
new_idx = (idx + 1) % self._subset_views_layout.count()
@ -391,9 +413,19 @@ class OverviewWidget(QtWidgets.QFrame):
self._create_btn.setEnabled(False)
self._subset_attributes_wrap.setEnabled(False)
for idx in range(self._subset_views_layout.count()):
widget = self._subset_views_layout.widget(idx)
widget.set_active_toggle_enabled(False)
def _on_controller_reset_start(self):
"""Controller reset started."""
for idx in range(self._subset_views_layout.count()):
widget = self._subset_views_layout.widget(idx)
widget.set_active_toggle_enabled(True)
def _on_publish_reset(self):
"""Context in controller has been refreshed."""
"""Context in controller has been reseted."""
self._create_btn.setEnabled(True)
self._subset_attributes_wrap.setEnabled(True)

View file

@ -34,7 +34,8 @@ from .icons import (
)
from ..constants import (
VARIANT_TOOLTIP
VARIANT_TOOLTIP,
ResetKeySequence,
)
@ -198,12 +199,26 @@ class CreateBtn(PublishIconBtn):
self.setLayoutDirection(QtCore.Qt.RightToLeft)
class SaveBtn(PublishIconBtn):
"""Save context and instances information."""
def __init__(self, parent=None):
icon_path = get_icon_path("save")
super(SaveBtn, self).__init__(icon_path, parent)
self.setToolTip(
"Save changes ({})".format(
QtGui.QKeySequence(QtGui.QKeySequence.Save).toString()
)
)
class ResetBtn(PublishIconBtn):
"""Publish reset button."""
def __init__(self, parent=None):
icon_path = get_icon_path("refresh")
super(ResetBtn, self).__init__(icon_path, parent)
self.setToolTip("Refresh publishing")
self.setToolTip(
"Reset & discard changes ({})".format(ResetKeySequence.toString())
)
class StopBtn(PublishIconBtn):
@ -348,6 +363,19 @@ class AbstractInstanceView(QtWidgets.QWidget):
"{} Method 'set_selected_items' is not implemented."
).format(self.__class__.__name__))
def set_active_toggle_enabled(self, enabled):
"""Instances are disabled for changing enabled state.
Active state should stay the same until is "unset".
Args:
enabled (bool): Instance state can be changed.
"""
raise NotImplementedError((
"{} Method 'set_active_toggle_enabled' is not implemented."
).format(self.__class__.__name__))
class ClickableLineEdit(QtWidgets.QLineEdit):
"""QLineEdit capturing left mouse click.
@ -1533,7 +1561,7 @@ class SubsetAttributesWidget(QtWidgets.QWidget):
attributes Thumbnail TOP
Family Publish
Creator Publish
attributes plugin BOTTOM
attributes

View file

@ -13,6 +13,7 @@ from openpype.tools.utils import (
PixmapLabel,
)
from .constants import ResetKeySequence
from .publish_report_viewer import PublishReportViewerWidget
from .control_qt import QtPublisherController
from .widgets import (
@ -22,8 +23,9 @@ from .widgets import (
PublisherTabsWidget,
StopBtn,
SaveBtn,
ResetBtn,
StopBtn,
ValidateBtn,
PublishBtn,
@ -121,6 +123,7 @@ class PublisherWindow(QtWidgets.QDialog):
"Attach a comment to your publish"
)
save_btn = SaveBtn(footer_widget)
reset_btn = ResetBtn(footer_widget)
stop_btn = StopBtn(footer_widget)
validate_btn = ValidateBtn(footer_widget)
@ -129,6 +132,7 @@ class PublisherWindow(QtWidgets.QDialog):
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(save_btn, 0)
footer_bottom_layout.addWidget(reset_btn, 0)
footer_bottom_layout.addWidget(stop_btn, 0)
footer_bottom_layout.addWidget(validate_btn, 0)
@ -250,7 +254,11 @@ class PublisherWindow(QtWidgets.QDialog):
overview_widget.create_requested.connect(
self._on_create_request
)
overview_widget.convert_requested.connect(
self._on_convert_requested
)
save_btn.clicked.connect(self._on_save_clicked)
reset_btn.clicked.connect(self._on_reset_clicked)
stop_btn.clicked.connect(self._on_stop_clicked)
validate_btn.clicked.connect(self._on_validate_clicked)
@ -330,8 +338,9 @@ class PublisherWindow(QtWidgets.QDialog):
self._comment_input = comment_input
self._footer_spacer = footer_spacer
self._stop_btn = stop_btn
self._save_btn = save_btn
self._reset_btn = reset_btn
self._stop_btn = stop_btn
self._validate_btn = validate_btn
self._publish_btn = publish_btn
@ -388,7 +397,9 @@ class PublisherWindow(QtWidgets.QDialog):
def closeEvent(self, event):
self._window_is_visible = False
self._uninstall_app_event_listener()
self.save_changes()
# TODO capture changes and ask user if wants to save changes on close
if not self._controller.host_context_has_changed:
self._save_changes(False)
self._reset_on_show = True
self._controller.clear_thumbnail_temp_dir_path()
super(PublisherWindow, self).closeEvent(event)
@ -421,6 +432,19 @@ class PublisherWindow(QtWidgets.QDialog):
if event.key() == QtCore.Qt.Key_Escape:
event.accept()
return
if event.matches(QtGui.QKeySequence.Save):
if not self._controller.publish_has_started:
self._save_changes(True)
event.accept()
return
if event.matches(ResetKeySequence):
if not self.controller.publish_is_running:
self.reset()
event.accept()
return
super(PublisherWindow, self).keyPressEvent(event)
def _on_overlay_message(self, event):
@ -455,8 +479,65 @@ class PublisherWindow(QtWidgets.QDialog):
self._reset_on_show = False
self.reset()
def save_changes(self):
self._controller.save_changes()
def _checks_before_save(self, explicit_save):
"""Save of changes may trigger some issues.
Check if context did change and ask user if he is really sure the
save should happen. A dialog can be shown during this method.
Args:
explicit_save (bool): Method was called when user explicitly asked
for save. Value affects shown message.
Returns:
bool: Save can happen.
"""
if not self._controller.host_context_has_changed:
return True
title = "Host context changed"
if explicit_save:
message = (
"Context has changed since Publisher window was refreshed last"
" time.\n\nAre you sure you want to save changes?"
)
else:
message = (
"Your action requires save of changes but context has changed"
" since Publisher window was refreshed last time.\n\nAre you"
" sure you want to continue and save changes?"
)
result = QtWidgets.QMessageBox.question(
self,
title,
message,
QtWidgets.QMessageBox.Save | QtWidgets.QMessageBox.Cancel
)
return result == QtWidgets.QMessageBox.Save
def _save_changes(self, explicit_save):
"""Save changes of Creation part.
All possible triggers of save changes were moved to main window (here),
so it can handle possible issues with save at one place. Do checks,
so user don't accidentally save changes to different file or using
different context.
Moving responsibility to this place gives option to show the dialog and
wait for user's response without breaking action he wanted to do.
Args:
explicit_save (bool): Method was called when user explicitly asked
for save. Value affects shown message.
Returns:
bool: Save happened successfully.
"""
if not self._checks_before_save(explicit_save):
return False
return self._controller.save_changes()
def reset(self):
self._controller.reset()
@ -491,15 +572,18 @@ class PublisherWindow(QtWidgets.QDialog):
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()
if hasattr(QtWidgets.QApplication, "desktop"):
desktop = QtWidgets.QApplication.desktop()
screen_idx = desktop.screenNumber(window)
screen_geo = desktop.screenGeometry(screen_idx)
else:
screen = window.screen()
screen_geo = 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()
diff = dialog_right - screen_geo.right()
if diff > 0:
dialog_x -= diff
@ -549,6 +633,14 @@ class PublisherWindow(QtWidgets.QDialog):
def _on_create_request(self):
self._go_to_create_tab()
def _on_convert_requested(self):
if not self._save_changes(False):
return
convertor_identifiers = (
self._overview_widget.get_selected_legacy_convertors()
)
self._controller.trigger_convertor_items(convertor_identifiers)
def _set_current_tab(self, identifier):
self._tabs_widget.set_current_tab(identifier)
@ -599,8 +691,10 @@ class PublisherWindow(QtWidgets.QDialog):
self._publish_frame.setVisible(visible)
self._update_publish_frame_rect()
def _on_save_clicked(self):
self._save_changes(True)
def _on_reset_clicked(self):
self.save_changes()
self.reset()
def _on_stop_clicked(self):
@ -610,14 +704,17 @@ class PublisherWindow(QtWidgets.QDialog):
self._controller.set_comment(self._comment_input.text())
def _on_validate_clicked(self):
self._set_publish_comment()
self._controller.validate()
if self._save_changes(False):
self._set_publish_comment()
self._controller.validate()
def _on_publish_clicked(self):
self._set_publish_comment()
self._controller.publish()
if self._save_changes(False):
self._set_publish_comment()
self._controller.publish()
def _set_footer_enabled(self, enabled):
self._save_btn.setEnabled(True)
self._reset_btn.setEnabled(True)
if enabled:
self._stop_btn.setEnabled(False)

View file

@ -247,7 +247,7 @@ class TrayPublishWindow(PublisherWindow):
def _on_project_select(self, project_name):
# TODO register project specific plugin paths
self._controller.save_changes()
self._controller.save_changes(False)
self._controller.reset_project_data_cache()
self.reset()