diff --git a/openpype/tools/republisher/republisher_dialog/control.py b/openpype/tools/republisher/republisher_dialog/control.py index 8c4e0313fe..242ca475e7 100644 --- a/openpype/tools/republisher/republisher_dialog/control.py +++ b/openpype/tools/republisher/republisher_dialog/control.py @@ -1,17 +1,42 @@ +import re import collections -from openpype.client import get_projects, get_assets + +from openpype.client import ( + get_projects, + get_assets, + get_asset_by_id, + get_subset_by_id, + get_version_by_id, + get_representations, +) +from openpype.settings import get_project_settings +from openpype.lib import prepare_template_data from openpype.lib.events import EventSystem +from openpype.pipeline.create import ( + SUBSET_NAME_ALLOWED_SYMBOLS, + get_subset_name_template, +) class AssetItem: - def __init__(self, entity_id, name, icon, parent_id): + def __init__( + self, + entity_id, + name, + icon_name, + icon_color, + parent_id, + has_children + ): self.id = entity_id self.name = name - self.icon = icon + self.icon_name = icon_name + self.icon_color = icon_color self.parent_id = parent_id + self.has_children = has_children @classmethod - def from_doc(cls, asset_doc): + def from_doc(cls, asset_doc, has_children=True): parent_id = asset_doc["data"].get("visualParent") if parent_id is not None: parent_id = str(parent_id) @@ -19,7 +44,9 @@ class AssetItem: str(asset_doc["_id"]), asset_doc["name"], asset_doc["data"].get("icon"), - parent_id + asset_doc["data"].get("color"), + parent_id, + has_children ) @@ -76,6 +103,9 @@ class EntitiesModel: self.refresh_assets(project_name) return dict(self._assets_by_project[project_name]) + def get_asset_by_id(self, project_name, asset_id): + return self._assets_by_project[project_name].get(asset_id) + def get_tasks(self, project_name, asset_id): if not project_name or not asset_id: return [] @@ -85,6 +115,8 @@ class EntitiesModel: all_task_items = self._tasks_by_asset_id[project_name] asset_task_items = all_task_items.get(asset_id) + if not asset_task_items: + return [] return list(asset_task_items) def refresh_projects(self, force=False): @@ -113,12 +145,25 @@ class EntitiesModel: return project_doc = self._project_docs_by_name[project_name] + asset_docs_by_parent_id = collections.defaultdict(list) for asset_doc in get_assets(project_name): - asset_item = AssetItem.from_doc(asset_doc) + parent_id = asset_doc["data"].get("visualParent") + asset_docs_by_parent_id[parent_id].append(asset_doc) + + hierarchy_queue = collections.deque() + for asset_doc in asset_docs_by_parent_id[None]: + hierarchy_queue.append(asset_doc) + + while hierarchy_queue: + asset_doc = hierarchy_queue.popleft() + children = asset_docs_by_parent_id[asset_doc["_id"]] + asset_item = AssetItem.from_doc(asset_doc, len(children) > 0) asset_items_by_id[asset_item.id] = asset_item task_items_by_asset_id[asset_item.id] = ( TaskItem.from_asset_doc(asset_doc, project_doc) ) + for child in children: + hierarchy_queue.append(child) def refresh_assets(self, project_name, force=False): self._event_system.emit( @@ -184,21 +229,330 @@ class SelectionModel: ) -class RepublisherDialogController: - def __init__(self): +class UserPublishValues: + """Helper object to validate values required for push to different project. + + Args: + event_system (EventSystem): Event system to catch and emit events. + new_asset_name (str): Name of new asset name. + variant (str): Variant for new subset name in new project. + """ + + asset_name_regex = re.compile("^[a-zA-Z0-9_.]+$") + variant_regex = re.compile("^[{}]+$".format(SUBSET_NAME_ALLOWED_SYMBOLS)) + + def __init__(self, event_system): + self._event_system = event_system + self._new_asset_name = None + self._variant = None + self._comment = None + self._is_variant_valid = False + self._is_new_asset_name_valid = False + + self.set_new_asset("") + self.set_variant("") + self.set_comment("") + + @property + def new_asset_name(self): + return self._new_asset_name + + @property + def variant(self): + return self._variant + + @property + def comment(self): + return self._comment + + @property + def is_variant_valid(self): + return self._is_variant_valid + + @property + def is_new_asset_name_valid(self): + return self._is_new_asset_name_valid + + @property + def is_valid(self): + return self.is_variant_valid and self.is_new_asset_name_valid + + def set_variant(self, variant): + if variant == self._variant: + return + + old_variant = self._variant + old_is_valid = self._is_variant_valid + + self._variant = variant + is_valid = False + if variant: + is_valid = self.variant_regex.match(variant) is not None + self._is_variant_valid = is_valid + + changes = { + key: {"new": new, "old": old} + for key, old, new in ( + ("variant", old_variant, variant), + ("is_valid", old_is_valid, is_valid) + ) + } + + self._event_system.emit( + "variant.changed", + { + "variant": variant, + "is_valid": self._is_variant_valid, + "changes": changes + }, + "user_values" + ) + + def set_new_asset(self, asset_name): + if self._new_asset_name == asset_name: + return + old_asset_name = self._new_asset_name + old_is_valid = self._is_new_asset_name_valid + self._new_asset_name = asset_name + is_valid = True + if asset_name: + is_valid = ( + self.asset_name_regex.match(asset_name) is not None + ) + self._is_new_asset_name_valid = is_valid + changes = { + key: {"new": new, "old": old} + for key, old, new in ( + ("new_asset_name", old_asset_name, asset_name), + ("is_valid", old_is_valid, is_valid) + ) + } + + self._event_system.emit( + "new_asset_name.changed", + { + "new_asset_name": self._new_asset_name, + "is_valid": self._is_new_asset_name_valid, + "changes": changes + }, + "user_values" + ) + + def set_comment(self, comment): + if comment == self._comment: + return + old_comment = self._comment + self._comment = comment + self._event_system.emit( + "comment.changed", + { + "new_asset_name": comment, + "changes": { + "comment": {"new": comment, "old": old_comment} + } + }, + "user_values" + ) + + +class PushToContextController: + def __init__(self, project_name=None, version_id=None): + self._src_project_name = None + self._src_version_id = None + self._src_asset_doc = None + self._src_subset_doc = None + self._src_version_doc = None + event_system = EventSystem() entities_model = EntitiesModel(event_system) selection_model = SelectionModel(event_system) + user_values = UserPublishValues(event_system) self._event_system = event_system self._entities_model = entities_model self._selection_model = selection_model - - self.dst_project_name = None - self.dst_asset_id = None - self.dst_task_name = None + self._user_values = user_values event_system.add_callback("project.changed", self._on_project_change) + event_system.add_callback("asset.changed", self._invalidate) + event_system.add_callback("variant.changed", self._invalidate) + event_system.add_callback("new_asset_name.changed", self._invalidate) + + self._submission_enabled = False + + self.set_source(project_name, version_id) + + def _get_task_info_from_repre_docs(self, asset_doc, repre_docs): + asset_tasks = asset_doc["data"].get("tasks") or {} + found_comb = [] + for repre_doc in repre_docs: + context = repre_doc["context"] + task_info = context.get("task") + if task_info is None: + continue + + task_name = None + task_type = None + if isinstance(task_info, str): + task_name = task_info + asset_task_info = asset_tasks.get(task_info) or {} + task_type = asset_task_info.get("type") + + elif isinstance(task_info, dict): + task_name = task_info.get("name") + task_type = task_info.get("type") + + if task_name and task_type: + return task_name, task_type + + if task_name: + found_comb.append((task_name, task_type)) + + for task_name, task_type in found_comb: + return task_name, task_type + return None, None + + def _get_src_variant(self): + project_name = self._src_project_name + version_doc = self._src_version_doc + asset_doc = self._src_asset_doc + repre_docs = get_representations( + project_name, version_ids=[version_doc["_id"]] + ) + task_name, task_type = self._get_task_info_from_repre_docs( + asset_doc, repre_docs + ) + + project_settings = get_project_settings(project_name) + subset_doc = self.src_subset_doc + family = subset_doc["data"].get("family") + if not family: + family = subset_doc["data"]["families"][0] + template = get_subset_name_template( + self._src_project_name, + family, + task_name, + task_type, + None, + project_settings=project_settings + ) + template_low = template.lower() + variant_placeholder = "{variant}" + if ( + variant_placeholder not in template_low + or (not task_name and "{task" in template_low) + ): + return "" + + idx = template_low.index(variant_placeholder) + template_s = template[:idx] + template_e = template[idx + len(variant_placeholder):] + fill_data = prepare_template_data({ + "family": family, + "task": task_name + }) + try: + subset_s = template_s.format(**fill_data) + subset_e = template_e.format(**fill_data) + except Exception as exc: + print("Failed format", exc) + return "" + + subset_name = self.src_subset_doc["name"] + if ( + (subset_s and not subset_name.startswith(subset_s)) + or (subset_e and not subset_name.endswith(subset_e)) + ): + return "" + + if subset_s: + subset_name = subset_name[len(subset_s):] + if subset_e: + subset_name = subset_name[:len(subset_e)] + return subset_name + + def set_source(self, project_name, version_id): + if ( + project_name == self._src_project_name + and version_id == self._src_version_id + ): + return + + self._src_project_name = project_name + self._src_version_id = version_id + asset_doc = None + subset_doc = None + version_doc = None + if project_name and version_id: + version_doc = get_version_by_id(project_name, version_id) + + if version_doc: + subset_doc = get_subset_by_id(project_name, version_doc["parent"]) + + if subset_doc: + asset_doc = get_asset_by_id(project_name, subset_doc["parent"]) + + self._src_asset_doc = asset_doc + self._src_subset_doc = subset_doc + self._src_version_doc = version_doc + if asset_doc: + self.user_values.set_new_asset(asset_doc["name"]) + variant = self._get_src_variant() + if variant: + self.user_values.set_variant(variant) + + comment = version_doc["data"].get("comment") + if comment: + self.user_values.set_comment(comment) + + self._event_system.emit( + "source.changed", { + "project_name": project_name, + "version_id": version_id + }, + "controller" + ) + + @property + def src_project_name(self): + return self._src_project_name + + @property + def src_version_id(self): + return self._src_version_id + + @property + def src_label(self): + if not self._src_project_name or not self._src_version_id: + return "Source is not defined" + + asset_doc = self.src_asset_doc + if not asset_doc: + return "Source is invalid" + + asset_path_parts = list(asset_doc["data"]["parents"]) + asset_path_parts.append(asset_doc["name"]) + asset_path = "/".join(asset_path_parts) + subset_doc = self.src_subset_doc + version_doc = self.src_version_doc + return "Source: {}/{}/{}/v{:0>3}".format( + self._src_project_name, + asset_path, + subset_doc["name"], + version_doc["name"] + ) + + @property + def src_version_doc(self): + return self._src_version_doc + + @property + def src_subset_doc(self): + return self._src_subset_doc + + @property + def src_asset_doc(self): + return self._src_asset_doc @property def event_system(self): @@ -212,11 +566,60 @@ class RepublisherDialogController: def selection_model(self): return self._selection_model + @property + def user_values(self): + return self._user_values + + @property + def submission_enabled(self): + return self._submission_enabled + def _on_project_change(self, event): project_name = event["project_name"] self.model.refresh_assets(project_name) + self._invalidate() + + def _invalidate(self): + submission_enabled = self._check_submit_validations() + if submission_enabled == self._submission_enabled: + return + self._submission_enabled = submission_enabled + self._event_system.emit( + "submission.enabled.changed", + {"enabled": submission_enabled}, + "controller" + ) + + def _check_submit_validations(self): + if not self._user_values.is_valid: + return False + + if not self.selection_model.project_name: + return False + + if ( + not self._user_values.new_asset_name + and not self.selection_model.asset_id + ): + return False + + return True + + def get_selected_asset_name(self): + project_name = self._selection_model.project_name + asset_id = self._selection_model.asset_id + if not project_name or not asset_id: + return None + asset_item = self._entities_model.get_asset_by_id( + project_name, asset_id + ) + if asset_item: + return asset_item.name + return None def submit(self): + if not self.submission_enabled: + return project_name = self.selection_model.project_name asset_id = self.selection_model.asset_id task_name = self.selection_model.task_name diff --git a/openpype/tools/republisher/republisher_dialog/window.py b/openpype/tools/republisher/republisher_dialog/window.py index 593a95be17..cfc236ad27 100644 --- a/openpype/tools/republisher/republisher_dialog/window.py +++ b/openpype/tools/republisher/republisher_dialog/window.py @@ -1,17 +1,23 @@ +import re import collections from qtpy import QtWidgets, QtGui, QtCore -from openpype.style import load_stylesheet +from openpype.style import load_stylesheet, get_app_icon_path +from openpype.tools.utils import ( + PlaceholderLineEdit, + SeparatorWidget, + get_asset_icon_by_name, + set_style_property, +) -from .control import RepublisherDialogController +from .control import PushToContextController PROJECT_NAME_ROLE = QtCore.Qt.UserRole + 1 ASSET_NAME_ROLE = QtCore.Qt.UserRole + 2 -ASSET_ICON_ROLE = QtCore.Qt.UserRole + 3 -ASSET_ID_ROLE = QtCore.Qt.UserRole + 4 -TASK_NAME_ROLE = QtCore.Qt.UserRole + 5 -TASK_TYPE_ROLE = QtCore.Qt.UserRole + 6 +ASSET_ID_ROLE = QtCore.Qt.UserRole + 3 +TASK_NAME_ROLE = QtCore.Qt.UserRole + 4 +TASK_TYPE_ROLE = QtCore.Qt.UserRole + 5 class ProjectsModel(QtGui.QStandardItemModel): @@ -19,6 +25,8 @@ class ProjectsModel(QtGui.QStandardItemModel): refreshing_text = "< Refreshing >" select_project_text = "< Select Project >" + refreshed = QtCore.Signal() + def __init__(self, controller): super(ProjectsModel, self).__init__() self._controller = controller @@ -59,7 +67,7 @@ class ProjectsModel(QtGui.QStandardItemModel): if project_name is None: continue item = self._items.pop(project_name) - root_item.removeRow(item.row()) + root_item.takeRow(item.row()) for project_name in project_names: if project_name in self._items: @@ -70,6 +78,7 @@ class ProjectsModel(QtGui.QStandardItemModel): if new_items: root_item.appendRows(new_items) + self.refreshed.emit() class ProjectProxyModel(QtCore.QSortFilterProxyModel): @@ -94,6 +103,7 @@ class ProjectProxyModel(QtCore.QSortFilterProxyModel): class AssetsModel(QtGui.QStandardItemModel): + items_changed = QtCore.Signal() empty_text = "< Empty >" def __init__(self, controller): @@ -151,6 +161,7 @@ class AssetsModel(QtGui.QStandardItemModel): self._last_project = project_name self._clear() + self.items_changed.emit() def _on_refresh_start(self, event): pass @@ -165,11 +176,13 @@ class AssetsModel(QtGui.QStandardItemModel): if project_name is None: if None not in self._items: self._clear() + self.items_changed.emit() return asset_items_by_id = self._controller.model.get_assets(project_name) if not asset_items_by_id: self._clear() + self.items_changed.emit() return assets_by_parent_id = collections.defaultdict(list) @@ -201,9 +214,12 @@ class AssetsModel(QtGui.QStandardItemModel): elif item.parent() is not parent_item: new_items.append(item) + icon = get_asset_icon_by_name( + asset_item.icon_name, asset_item.icon_color + ) item.setData(asset_item.name, QtCore.Qt.DisplayRole) + item.setData(icon, QtCore.Qt.DecorationRole) item.setData(asset_item.id, ASSET_ID_ROLE) - item.setData(asset_item.icon, ASSET_ICON_ROLE) hierarchy_queue.append((asset_item.id, item)) @@ -216,10 +232,13 @@ class AssetsModel(QtGui.QStandardItemModel): continue parent = item.parent() if parent is not None: - parent.removeRow(item.row()) + parent.takeRow(item.row()) + + self.items_changed.emit() class TasksModel(QtGui.QStandardItemModel): + items_changed = QtCore.Signal() empty_text = "< Empty >" def __init__(self, controller): @@ -277,6 +296,7 @@ class TasksModel(QtGui.QStandardItemModel): self._last_project = project_name self._clear() + self.items_changed.emit() def _on_asset_refresh_finish(self, event): self._refresh(event["project_name"]) @@ -293,6 +313,7 @@ class TasksModel(QtGui.QStandardItemModel): if project_name is None: if None not in self._items: self._clear() + self.items_changed.emit() return asset_id = self._controller.selection_model.asset_id @@ -301,6 +322,7 @@ class TasksModel(QtGui.QStandardItemModel): ) if not task_items: self._clear() + self.items_changed.emit() return root_item = self.invisibleRootItem() @@ -338,74 +360,153 @@ class TasksModel(QtGui.QStandardItemModel): if parent is not None: parent.removeRow(item.row()) + self.items_changed.emit() -class RepublisherDialogWindow(QtWidgets.QWidget): + +class PushToContextSelectWindow(QtWidgets.QWidget): def __init__(self, controller=None): - super(RepublisherDialogWindow, self).__init__() + super(PushToContextSelectWindow, self).__init__() if controller is None: - controller = RepublisherDialogController() + controller = PushToContextController() self._controller = controller - main_splitter = QtWidgets.QSplitter(self) + self.setWindowTitle("Push to project (select context)") + self.setWindowIcon(QtGui.QIcon(get_app_icon_path())) - left_widget = QtWidgets.QWidget(main_splitter) + header_widget = QtWidgets.QWidget(self) - project_combobox = QtWidgets.QComboBox(left_widget) + header_label = QtWidgets.QLabel(controller.src_label, header_widget) + + header_layout = QtWidgets.QHBoxLayout(header_widget) + header_layout.setContentsMargins(0, 0, 0, 0) + header_layout.addWidget(header_label) + + main_splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal, self) + + context_widget = QtWidgets.QWidget(main_splitter) + + project_combobox = QtWidgets.QComboBox(context_widget) project_model = ProjectsModel(controller) project_proxy = ProjectProxyModel() project_proxy.setSourceModel(project_model) + project_proxy.setDynamicSortFilter(True) project_delegate = QtWidgets.QStyledItemDelegate() project_combobox.setItemDelegate(project_delegate) project_combobox.setModel(project_proxy) - asset_view = QtWidgets.QTreeView(left_widget) + asset_task_splitter = QtWidgets.QSplitter( + QtCore.Qt.Vertical, context_widget + ) + + asset_view = QtWidgets.QTreeView(asset_task_splitter) asset_view.setHeaderHidden(True) asset_model = AssetsModel(controller) - asset_view.setModel(asset_model) + asset_proxy = QtCore.QSortFilterProxyModel() + asset_proxy.setSourceModel(asset_model) + asset_proxy.setDynamicSortFilter(True) + asset_view.setModel(asset_proxy) - left_layout = QtWidgets.QVBoxLayout(left_widget) - left_layout.setContentsMargins(0, 0, 0, 0) - left_layout.addWidget(project_combobox, 0) - left_layout.addWidget(asset_view, 1) - - right_widget = QtWidgets.QWidget(main_splitter) - - task_view = QtWidgets.QListView(right_widget) + task_view = QtWidgets.QListView(asset_task_splitter) task_proxy = QtCore.QSortFilterProxyModel() task_model = TasksModel(controller) task_proxy.setSourceModel(task_model) + task_proxy.setDynamicSortFilter(True) task_view.setModel(task_proxy) - right_layout = QtWidgets.QVBoxLayout(right_widget) - right_layout.setContentsMargins(0, 0, 0, 0) - right_layout.addWidget(task_view, 1) + asset_task_splitter.addWidget(asset_view) + asset_task_splitter.addWidget(task_view) - main_splitter.addWidget(left_widget) - main_splitter.addWidget(right_widget) + context_layout = QtWidgets.QVBoxLayout(context_widget) + context_layout.setContentsMargins(0, 0, 0, 0) + context_layout.addWidget(project_combobox, 0) + context_layout.addWidget(asset_task_splitter, 1) + # --- Inputs widget --- + inputs_widget = QtWidgets.QWidget(main_splitter) + + asset_name_input = PlaceholderLineEdit(inputs_widget) + asset_name_input.setPlaceholderText("< Name of new asset >") + asset_name_input.setObjectName("ValidatedLineEdit") + + variant_input = PlaceholderLineEdit(inputs_widget) + variant_input.setPlaceholderText("< Variant >") + variant_input.setObjectName("ValidatedLineEdit") + + comment_input = PlaceholderLineEdit(inputs_widget) + comment_input.setPlaceholderText("< Publish comment >") + + inputs_layout = QtWidgets.QFormLayout(inputs_widget) + inputs_layout.setContentsMargins(0, 0, 0, 0) + inputs_layout.addRow("New asset name", asset_name_input) + inputs_layout.addRow("Variant", variant_input) + inputs_layout.addRow("Comment", comment_input) + + main_splitter.addWidget(context_widget) + main_splitter.addWidget(inputs_widget) + + # --- Buttons widget --- btns_widget = QtWidgets.QWidget(self) - close_btn = QtWidgets.QPushButton("Close", btns_widget) - select_btn = QtWidgets.QPushButton("Select", btns_widget) + cancel_btn = QtWidgets.QPushButton("Cancel", btns_widget) + publish_btn = QtWidgets.QPushButton("Publish", btns_widget) btns_layout = QtWidgets.QHBoxLayout(btns_widget) btns_layout.setContentsMargins(0, 0, 0, 0) btns_layout.addStretch(1) - btns_layout.addWidget(close_btn, 0) - btns_layout.addWidget(select_btn, 0) + btns_layout.addWidget(cancel_btn, 0) + btns_layout.addWidget(publish_btn, 0) - main_layout = QtWidgets.QHBoxLayout(self) + sep_1 = SeparatorWidget(parent=self) + sep_2 = SeparatorWidget(parent=self) + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.addWidget(header_widget, 0) + main_layout.addWidget(sep_1, 0) main_layout.addWidget(main_splitter, 1) + main_layout.addWidget(sep_2, 0) main_layout.addWidget(btns_widget, 0) + show_timer = QtCore.QTimer() + show_timer.setInterval(1) + + user_input_changed_timer = QtCore.QTimer() + user_input_changed_timer.setInterval(200) + user_input_changed_timer.setSingleShot(True) + + show_timer.timeout.connect(self._on_show_timer) + user_input_changed_timer.timeout.connect(self._on_user_input_timer) + asset_name_input.textChanged.connect(self._on_new_asset_change) + variant_input.textChanged.connect(self._on_variant_change) + comment_input.textChanged.connect(self._on_comment_change) + project_model.refreshed.connect(self._on_projects_refresh) project_combobox.currentIndexChanged.connect(self._on_project_change) asset_view.selectionModel().selectionChanged.connect( self._on_asset_change ) + asset_model.items_changed.connect(self._on_asset_model_change) task_view.selectionModel().selectionChanged.connect( self._on_task_change ) - select_btn.clicked.connect(self._on_select_click) - close_btn.clicked.connect(self._on_close_click) + task_model.items_changed.connect(self._on_task_model_change) + publish_btn.clicked.connect(self._on_select_click) + cancel_btn.clicked.connect(self._on_close_click) + + controller.event_system.add_callback( + "new_asset_name.changed", self._on_controller_new_asset_change + ) + controller.event_system.add_callback( + "variant.changed", self._on_controller_variant_change + ) + controller.event_system.add_callback( + "comment.changed", self._on_controller_comment_change + ) + controller.event_system.add_callback( + "submission.enabled.changed", self._on_submission_change + ) + controller.event_system.add_callback( + "source.changed", self._on_controller_source_change + ) + + self._header_label = header_label + self._main_splitter = main_splitter self._project_combobox = project_combobox self._project_model = project_model @@ -414,17 +515,153 @@ class RepublisherDialogWindow(QtWidgets.QWidget): self._asset_view = asset_view self._asset_model = asset_model + self._asset_proxy_model = asset_proxy self._task_view = task_view + self._task_proxy_model = task_proxy + self._variant_input = variant_input + self._asset_name_input = asset_name_input + self._comment_input = comment_input + + self._publish_btn = publish_btn + + self._user_input_changed_timer = user_input_changed_timer + # Store current value on input text change + # The value is unset when is passed to controller + # The goal is to have controll over changes happened during user change + # in UI and controller auto-changes + self._variant_input_text = None + self._new_asset_name_input_text = None + self._comment_input_text = None + self._show_timer = show_timer + self._show_counter = 2 self._first_show = True + publish_btn.setEnabled(False) + + if controller.user_values.new_asset_name: + asset_name_input.setText(controller.user_values.new_asset_name) + if controller.user_values.variant: + variant_input.setText(controller.user_values.variant) + self._invalidate_variant() + self._invalidate_new_asset_name() + + @property + def controller(self): + return self._controller + def showEvent(self, event): - super(RepublisherDialogWindow, self).showEvent(event) + super(PushToContextSelectWindow, self).showEvent(event) if self._first_show: self._first_show = False - self._controller.model.refresh_projects() self.setStyleSheet(load_stylesheet()) + self._invalidate_variant() + self._show_timer.start() + + def _on_show_timer(self): + if self._show_counter == 0: + self._show_timer.stop() + return + + self._show_counter -= 1 + if self._show_counter == 1: + width = 740 + height = 640 + inputs_width = 360 + self.resize(width, height) + self._main_splitter.setSizes([width - inputs_width, inputs_width]) + + if self._show_counter > 0: + return + + self._controller.model.refresh_projects() + + def _on_new_asset_change(self, text): + self._new_asset_name_input_text = text + self._user_input_changed_timer.start() + + def _on_variant_change(self, text): + self._variant_input_text = text + self._user_input_changed_timer.start() + + def _on_comment_change(self, text): + self._comment_input_text = text + self._user_input_changed_timer.start() + + def _on_user_input_timer(self): + asset_name = self._new_asset_name_input_text + if asset_name is not None: + self._new_asset_name_input_text = None + self._controller.user_values.set_new_asset(asset_name) + + variant = self._variant_input_text + if variant is not None: + self._variant_input_text = None + self._controller.user_values.set_variant(variant) + + comment = self._comment_input_text + if comment is not None: + self._comment_input_text = None + self._controller.user_values.set_comment(comment) + + def _on_controller_new_asset_change(self, event): + asset_name = event["changes"]["new_asset_name"]["new"] + if ( + self._new_asset_name_input_text is None + and asset_name != self._asset_name_input.text() + ): + self._asset_name_input.setText(asset_name) + + self._invalidate_new_asset_name() + + def _on_controller_variant_change(self, event): + is_valid_changes = event["changes"]["is_valid"] + variant = event["changes"]["variant"]["new"] + if ( + self._variant_input_text is None + and variant != self._variant_input.text() + ): + self._variant_input.setText(variant) + + if is_valid_changes["old"] != is_valid_changes["new"]: + self._invalidate_variant() + + def _on_controller_comment_change(self, event): + comment = event["comment"] + if ( + self._comment_input_text is None + and comment != self._comment_input.text() + ): + self._comment_input.setText(comment) + + def _on_controller_source_change(self): + self._header_label.setText(self._controller.src_label) + + def _invalidate_new_asset_name(self): + asset_name = self._controller.user_values.new_asset_name + self._task_view.setVisible(not asset_name) + + valid = None + if asset_name: + valid = self._controller.user_values.is_new_asset_name_valid + + state = "" + if valid is True: + state = "valid" + elif valid is False: + state = "invalid" + set_style_property(self._asset_name_input, "state", state) + + def _invalidate_variant(self): + valid = self._controller.user_values.is_variant_valid + state = "invalid" + if valid is True: + state = "valid" + set_style_property(self._variant_input, "state", state) + + def _on_projects_refresh(self): + self._project_proxy.sort(0, QtCore.Qt.AscendingOrder) def _on_project_change(self): idx = self._project_combobox.currentIndex() @@ -445,6 +682,12 @@ class RepublisherDialogWindow(QtWidgets.QWidget): asset_id = model.data(index, ASSET_ID_ROLE) self._controller.selection_model.select_asset(asset_id) + def _on_asset_model_change(self): + self._asset_proxy_model.sort(0, QtCore.Qt.AscendingOrder) + + def _on_task_model_change(self): + self._task_proxy_model.sort(0, QtCore.Qt.AscendingOrder) + def _on_task_change(self): indexes = self._task_view.selectedIndexes() index = next(iter(indexes), None) @@ -454,6 +697,9 @@ class RepublisherDialogWindow(QtWidgets.QWidget): task_name = model.data(index, TASK_NAME_ROLE) self._controller.selection_model.select_task(task_name) + def _on_submission_change(self, event): + self._publish_btn.setEnabled(event["enabled"]) + def _on_close_click(self): self.close() @@ -464,14 +710,29 @@ class RepublisherDialogWindow(QtWidgets.QWidget): def main(): app = QtWidgets.QApplication.instance() if not app: + # 'AA_EnableHighDpiScaling' must be set before app instance creation + high_dpi_scale_attr = getattr( + QtCore.Qt, "AA_EnableHighDpiScaling", None + ) + if high_dpi_scale_attr is not None: + QtWidgets.QApplication.setAttribute(high_dpi_scale_attr) + app = QtWidgets.QApplication([]) + for attr_name in ( + "AA_UseHighDpiPixmaps", + ): + attr = getattr(QtCore.Qt, attr_name, None) + if attr is not None: + app.setAttribute(attr) + # TODO find way how to get these project_name = None - representation_id = None + version_id = None # Show window dialog - window = RepublisherDialogWindow() + window = PushToContextSelectWindow() + window.controller.set_source(project_name, version_id) window.show() app.exec_()