diff --git a/openpype/tools/new_publisher/widgets/__init__.py b/openpype/tools/new_publisher/widgets/__init__.py index 11e8f1f10e..b5639926dc 100644 --- a/openpype/tools/new_publisher/widgets/__init__.py +++ b/openpype/tools/new_publisher/widgets/__init__.py @@ -1,19 +1,23 @@ from .widgets import ( - PublishOverlayFrame, SubsetAttributesWidget, - InstanceCardView, - InstanceListView + PublishOverlayFrame ) from .create_dialog import ( CreateDialog ) +from .instance_views_widgets import ( + InstanceCardView, + InstanceListView +) + __all__ = ( - "PublishOverlayFrame", "SubsetAttributesWidget", + "PublishOverlayFrame", + + "CreateDialog", + "InstanceCardView", "InstanceListView", - - "CreateDialog" ) diff --git a/openpype/tools/new_publisher/widgets/instance_views_widgets.py b/openpype/tools/new_publisher/widgets/instance_views_widgets.py new file mode 100644 index 0000000000..70746062d6 --- /dev/null +++ b/openpype/tools/new_publisher/widgets/instance_views_widgets.py @@ -0,0 +1,565 @@ +import collections + +from Qt import QtWidgets, QtCore, QtGui + +from constants import ( + INSTANCE_ID_ROLE, + SORT_VALUE_ROLE +) + + +class InstanceCardWidget(QtWidgets.QWidget): + active_changed = QtCore.Signal(str, bool) + + def __init__(self, instance, item, parent): + super(InstanceCardWidget, self).__init__(parent) + + self.instance = instance + self.item = item + + subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) + active_checkbox = QtWidgets.QCheckBox(self) + active_checkbox.setChecked(instance.data["active"]) + + expand_btn = QtWidgets.QToolButton(self) + expand_btn.setObjectName("ArrowBtn") + expand_btn.setArrowType(QtCore.Qt.DownArrow) + expand_btn.setMaximumWidth(14) + expand_btn.setEnabled(False) + + detail_widget = QtWidgets.QWidget(self) + detail_widget.setVisible(False) + self.detail_widget = detail_widget + + top_layout = QtWidgets.QHBoxLayout() + top_layout.addWidget(subset_name_label) + top_layout.addStretch(1) + top_layout.addWidget(active_checkbox) + top_layout.addWidget(expand_btn) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(2, 2, 2, 2) + layout.addLayout(top_layout) + layout.addWidget(detail_widget) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) + active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground) + expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + active_checkbox.stateChanged.connect(self._on_active_change) + expand_btn.clicked.connect(self._on_expend_clicked) + + self.subset_name_label = subset_name_label + self.active_checkbox = active_checkbox + self.expand_btn = expand_btn + + def set_active(self, new_value): + checkbox_value = self.active_checkbox.isChecked() + instance_value = self.instance.data["active"] + + # First change instance value and them change checkbox + # - prevent to trigger `active_changed` signal + if instance_value != new_value: + self.instance.data["active"] = new_value + + if checkbox_value != new_value: + self.active_checkbox.setChecked(new_value) + + def update_instance(self, instance): + self.instance = instance + self.update_instance_values() + + def update_instance_values(self): + self.set_active(self.instance.data["active"]) + + def _set_expanded(self, expanded=None): + if expanded is None: + expanded = not self.detail_widget.isVisible() + self.detail_widget.setVisible(expanded) + self.item.setSizeHint(self.sizeHint()) + + def showEvent(self, event): + super(InstanceCardWidget, self).showEvent(event) + self.item.setSizeHint(self.sizeHint()) + + def _on_active_change(self): + new_value = self.active_checkbox.isChecked() + old_value = self.instance.data["active"] + if new_value == old_value: + return + + self.instance.data["active"] = new_value + self.active_changed.emit(self.instance.data["uuid"], new_value) + + def _on_expend_clicked(self): + self._set_expanded() + + +class _AbstractInstanceView(QtWidgets.QWidget): + selection_changed = QtCore.Signal() + + def refresh(self): + raise NotImplementedError(( + "{} Method 'refresh' is not implemented." + ).format(self.__class__.__name__)) + + def get_selected_instances(self): + raise NotImplementedError(( + "{} Method 'get_selected_instances' is not implemented." + ).format(self.__class__.__name__)) + + def set_selected_instances(self, instances): + raise NotImplementedError(( + "{} Method 'set_selected_instances' is not implemented." + ).format(self.__class__.__name__)) + + +class InstanceCardView(_AbstractInstanceView): + def __init__(self, controller, parent): + super(InstanceCardView, self).__init__(parent) + + self.controller = controller + + list_widget = QtWidgets.QListWidget(self) + list_widget.setSelectionMode( + QtWidgets.QAbstractItemView.ExtendedSelection + ) + list_widget.setResizeMode(QtWidgets.QListView.Adjust) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(list_widget, 1) + + list_widget.selectionModel().selectionChanged.connect( + self._on_selection_change + ) + + self._items_by_id = {} + self._widgets_by_id = {} + + self.list_widget = list_widget + + def refresh(self): + instances_by_id = {} + for instance in self.controller.instances: + instance_id = instance.data["uuid"] + instances_by_id[instance_id] = instance + + for instance_id in tuple(self._items_by_id.keys()): + if instance_id not in instances_by_id: + item = self._items_by_id.pop(instance_id) + self.list_widget.removeItemWidget(item) + widget = self._widgets_by_id.pop(instance_id) + widget.deleteLater() + row = self.list_widget.row(item) + self.list_widget.takeItem(row) + + for instance_id, instance in instances_by_id.items(): + if instance_id in self._items_by_id: + widget = self._widgets_by_id[instance_id] + widget.update_instance(instance) + + else: + item = QtWidgets.QListWidgetItem(self.list_widget) + widget = InstanceCardWidget(instance, item, self) + widget.active_changed.connect(self._on_active_changed) + item.setData(INSTANCE_ID_ROLE, instance_id) + self.list_widget.addItem(item) + self.list_widget.setItemWidget(item, widget) + self._items_by_id[instance_id] = item + self._widgets_by_id[instance_id] = widget + + def refresh_active_state(self): + for widget in self._widgets_by_id.values(): + widget.update_instance_values() + + def _on_active_changed(self, changed_instance_id, new_value): + selected_ids = set() + found = False + for item in self.list_widget.selectedItems(): + instance_id = item.data(INSTANCE_ID_ROLE) + selected_ids.add(instance_id) + if not found and instance_id == changed_instance_id: + found = True + + if not found: + return + + for instance_id in selected_ids: + widget = self._widgets_by_id.get(instance_id) + if widget: + widget.set_active(new_value) + + def _on_selection_change(self, *_args): + self.selection_changed.emit() + + def get_selected_instances(self): + instances = [] + for item in self.list_widget.selectedItems(): + instance_id = item.data(INSTANCE_ID_ROLE) + widget = self._widgets_by_id.get(instance_id) + if widget: + instances.append(widget.instance) + return instances + + def set_selected_instances(self, instances): + indexes = [] + model = self.list_widget.model() + for instance in instances: + instance_id = instance.data["uuid"] + item = self._items_by_id.get(instance_id) + if item: + row = self.list_widget.row(item) + index = model.index(row, 0) + indexes.append(index) + + selection_model = self.list_widget.selectionModel() + first_item = True + for index in indexes: + if first_item: + first_item = False + select_type = QtCore.QItemSelectionModel.ClearAndSelect + else: + select_type = QtCore.QItemSelectionModel.Select + selection_model.select(index, select_type) + + +class InstanceListItemWidget(QtWidgets.QWidget): + active_changed = QtCore.Signal(str, bool) + + def __init__(self, instance, parent): + super(InstanceListItemWidget, self).__init__(parent) + + self.instance = instance + + subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) + active_checkbox = QtWidgets.QCheckBox(self) + active_checkbox.setChecked(instance.data["active"]) + + layout = QtWidgets.QHBoxLayout(self) + content_margins = layout.contentsMargins() + layout.setContentsMargins(content_margins.left() + 2, 0, 2, 0) + layout.addWidget(subset_name_label) + layout.addStretch(1) + layout.addWidget(active_checkbox) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) + active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + active_checkbox.stateChanged.connect(self._on_active_change) + + self.subset_name_label = subset_name_label + self.active_checkbox = active_checkbox + + def set_active(self, new_value): + checkbox_value = self.active_checkbox.isChecked() + instance_value = self.instance.data["active"] + + # First change instance value and them change checkbox + # - prevent to trigger `active_changed` signal + if instance_value != new_value: + self.instance.data["active"] = new_value + + if checkbox_value != new_value: + self.active_checkbox.setChecked(new_value) + + def update_instance(self, instance): + self.instance = instance + self.update_instance_values() + + def update_instance_values(self): + self.set_active(self.instance.data["active"]) + + def _on_active_change(self): + new_value = self.active_checkbox.isChecked() + old_value = self.instance.data["active"] + if new_value == old_value: + return + + self.instance.data["active"] = new_value + self.active_changed.emit(self.instance.data["uuid"], new_value) + + +class InstanceListGroupWidget(QtWidgets.QFrame): + expand_changed = QtCore.Signal(str, bool) + + def __init__(self, family, parent): + super(InstanceListGroupWidget, self).__init__(parent) + self.setObjectName("InstanceListGroupWidget") + + self.family = family + self._expanded = False + + subset_name_label = QtWidgets.QLabel(family, self) + + expand_btn = QtWidgets.QToolButton(self) + expand_btn.setObjectName("ArrowBtn") + expand_btn.setArrowType(QtCore.Qt.RightArrow) + expand_btn.setMaximumWidth(14) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(5, 0, 2, 0) + layout.addWidget( + subset_name_label, 1, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter + ) + layout.addWidget(expand_btn) + + # self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) + expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + expand_btn.clicked.connect(self._on_expand_clicked) + + self.subset_name_label = subset_name_label + self.expand_btn = expand_btn + + def _on_expand_clicked(self): + self.expand_changed.emit(self.family, not self._expanded) + + def set_expanded(self, expanded): + if self._expanded == expanded: + return + + self._expanded = expanded + if expanded: + self.expand_btn.setArrowType(QtCore.Qt.DownArrow) + else: + self.expand_btn.setArrowType(QtCore.Qt.RightArrow) + + +class InstanceListView(_AbstractInstanceView): + def __init__(self, controller, parent): + super(InstanceListView, self).__init__(parent) + + self.controller = controller + + instance_view = QtWidgets.QTreeView(self) + instance_view.setObjectName("InstanceListView") + instance_view.setHeaderHidden(True) + instance_view.setIndentation(0) + instance_view.setSelectionMode( + QtWidgets.QAbstractItemView.ExtendedSelection + ) + + instance_model = QtGui.QStandardItemModel() + + proxy_model = QtCore.QSortFilterProxyModel() + proxy_model.setSourceModel(instance_model) + proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) + proxy_model.setSortRole(SORT_VALUE_ROLE) + proxy_model.setFilterKeyColumn(0) + proxy_model.setDynamicSortFilter(True) + + instance_view.setModel(proxy_model) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(instance_view) + + instance_view.selectionModel().selectionChanged.connect( + self._on_selection_change + ) + instance_view.collapsed.connect(self._on_collapse) + instance_view.expanded.connect(self._on_expand) + + self._group_items = {} + self._group_widgets = {} + self._widgets_by_id = {} + self.instance_view = instance_view + self.instance_model = instance_model + self.proxy_model = proxy_model + + def _on_expand(self, index): + family = index.data(SORT_VALUE_ROLE) + group_widget = self._group_widgets.get(family) + if group_widget: + group_widget.set_expanded(True) + + def _on_collapse(self, index): + family = index.data(SORT_VALUE_ROLE) + group_widget = self._group_widgets.get(family) + if group_widget: + group_widget.set_expanded(False) + + def refresh(self): + instances_by_family = collections.defaultdict(list) + families = set() + for instance in self.controller.instances: + family = instance.data["family"] + families.add(family) + instances_by_family[family].append(instance) + + new_group_items = [] + for family in families: + if family in self._group_items: + continue + + group_item = QtGui.QStandardItem() + group_item.setData(family, SORT_VALUE_ROLE) + group_item.setFlags(QtCore.Qt.ItemIsEnabled) + self._group_items[family] = group_item + new_group_items.append(group_item) + + sort_at_the_end = False + root_item = self.instance_model.invisibleRootItem() + if new_group_items: + sort_at_the_end = True + root_item.appendRows(new_group_items) + + for group_item in new_group_items: + index = self.instance_model.index( + group_item.row(), group_item.column() + ) + proxy_index = self.proxy_model.mapFromSource(index) + family = group_item.data(SORT_VALUE_ROLE) + widget = InstanceListGroupWidget(family, self.instance_view) + widget.expand_changed.connect(self._on_group_expand_request) + self._group_widgets[family] = widget + self.instance_view.setIndexWidget(proxy_index, widget) + + for family in tuple(self._group_items.keys()): + if family in families: + continue + + group_item = self._group_items.pop(family) + root_item.removeRow(group_item.row()) + widget = self._group_widgets.pop(family) + widget.deleteLater() + + for family, group_item in self._group_items.items(): + to_remove = set() + existing_mapping = {} + + group_index = self.instance_model.index( + group_item.row(), group_item.column() + ) + + for idx in range(group_item.rowCount()): + index = self.instance_model.index(idx, 0, group_index) + instance_id = index.data(INSTANCE_ID_ROLE) + to_remove.add(instance_id) + existing_mapping[instance_id] = idx + + new_items = [] + new_items_with_instance = [] + for instance in instances_by_family[family]: + instance_id = instance.data["uuid"] + if instance_id in to_remove: + to_remove.remove(instance_id) + widget = self._widgets_by_id[instance_id] + widget.update_instance(instance) + continue + + item = QtGui.QStandardItem() + item.setData(instance.data["subset"], SORT_VALUE_ROLE) + item.setData(instance.data["uuid"], INSTANCE_ID_ROLE) + new_items.append(item) + new_items_with_instance.append((item, instance)) + + idx_to_remove = [] + for instance_id in to_remove: + idx_to_remove.append(existing_mapping[instance_id]) + + for idx in reversed(sorted(idx_to_remove)): + group_item.removeRows(idx, 1) + + for instance_id in to_remove: + widget = self._widgets_by_id.pop(instance_id) + widget.deleteLater() + + if new_items: + sort_at_the_end = True + + group_item.appendRows(new_items) + + for item, instance in new_items_with_instance: + item_index = self.instance_model.index( + item.row(), + item.column(), + group_index + ) + proxy_index = self.proxy_model.mapFromSource(item_index) + widget = InstanceListItemWidget( + instance, self.instance_view + ) + self.instance_view.setIndexWidget(proxy_index, widget) + self._widgets_by_id[instance.data["uuid"]] = widget + + # Trigger sort at the end of refresh + if sort_at_the_end: + self.proxy_model.sort(0) + + def refresh_active_state(self): + for widget in self._widgets_by_id.values(): + widget.update_instance_values() + + def get_selected_instances(self): + instances = [] + instances_by_id = {} + for instance in self.controller.instances: + instance_id = instance.data["uuid"] + instances_by_id[instance_id] = instance + + for index in self.instance_view.selectionModel().selectedIndexes(): + instance_id = index.data(INSTANCE_ID_ROLE) + if instance_id is not None: + instance = instances_by_id.get(instance_id) + if instance: + instances.append(instance) + + return instances + + def set_selected_instances(self, instances): + model = self.instance_view.model() + instance_ids_by_family = collections.defaultdict(set) + for instance in instances: + family = instance.data["family"] + instance_id = instance.data["uuid"] + instance_ids_by_family[family].add(instance_id) + + indexes = [] + for family, group_item in self._group_items.items(): + selected_ids = instance_ids_by_family[family] + if not selected_ids: + continue + + group_index = self.instance_model.index( + group_item.row(), group_item.column() + ) + proxy_group_index = self.proxy_model.mapFromSource(group_index) + has_indexes = False + for row in range(group_item.rowCount()): + index = model.index(row, 0, proxy_group_index) + instance_id = index.data(INSTANCE_ID_ROLE) + if instance_id in selected_ids: + indexes.append(index) + has_indexes = True + + if has_indexes: + self.instance_view.setExpanded(proxy_group_index, True) + + selection_model = self.instance_view.selectionModel() + first_item = True + for index in indexes: + if first_item: + first_item = False + select_type = QtCore.QItemSelectionModel.ClearAndSelect + else: + select_type = QtCore.QItemSelectionModel.Select + selection_model.select(index, select_type) + + def _on_selection_change(self, *_args): + self.selection_changed.emit() + + def _on_group_expand_request(self, family, expanded): + group_item = self._group_items.get(family) + if not group_item: + return + + group_index = self.instance_model.index( + group_item.row(), group_item.column() + ) + proxy_index = self.proxy_model.mapFromSource(group_index) + self.instance_view.setExpanded(proxy_index, expanded) diff --git a/openpype/tools/new_publisher/widgets/widgets.py b/openpype/tools/new_publisher/widgets/widgets.py index 0d4dbe8f85..fe88ab3f21 100644 --- a/openpype/tools/new_publisher/widgets/widgets.py +++ b/openpype/tools/new_publisher/widgets/widgets.py @@ -4,10 +4,6 @@ import collections from Qt import QtWidgets, QtCore, QtGui from openpype.widgets.attribute_defs import create_widget_for_attr_def -from constants import ( - INSTANCE_ID_ROLE, - SORT_VALUE_ROLE -) from openpype.tools.flickcharm import FlickCharm @@ -596,9 +592,7 @@ class GlobalAttrsWidget(QtWidgets.QWidget): or self.asset_value_widget.has_value_changed() or self.task_value_widget.has_value_changed() ) - - self.cancel_btn.setEnabled(any_changed) - self.submit_btn.setEnabled(any_changed) + self.set_btns_visible(any_changed) def _on_variant_change(self): self._on_value_change() @@ -611,9 +605,16 @@ class GlobalAttrsWidget(QtWidgets.QWidget): def _on_task_change(self): self._on_value_change() + def set_btns_visible(self, visible): + self.cancel_btn.setVisible(visible) + self.submit_btn.setVisible(visible) + + def set_btns_enabled(self, enabled): + self.cancel_btn.setEnabled(enabled) + self.submit_btn.setEnabled(enabled) + def set_current_instances(self, instances): - self.cancel_btn.setEnabled(False) - self.submit_btn.setEnabled(False) + self.set_btns_visible(False) self._current_instances = instances @@ -903,563 +904,6 @@ class ThumbnailWidget(QtWidgets.QWidget): self.current_pix = None -class InstanceCardWidget(QtWidgets.QWidget): - active_changed = QtCore.Signal(str, bool) - - def __init__(self, instance, item, parent): - super(InstanceCardWidget, self).__init__(parent) - - self.instance = instance - self.item = item - - subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) - active_checkbox = QtWidgets.QCheckBox(self) - active_checkbox.setChecked(instance.data["active"]) - - expand_btn = QtWidgets.QToolButton(self) - expand_btn.setObjectName("ArrowBtn") - expand_btn.setArrowType(QtCore.Qt.DownArrow) - expand_btn.setMaximumWidth(14) - expand_btn.setEnabled(False) - - detail_widget = QtWidgets.QWidget(self) - detail_widget.setVisible(False) - self.detail_widget = detail_widget - - top_layout = QtWidgets.QHBoxLayout() - top_layout.addWidget(subset_name_label) - top_layout.addStretch(1) - top_layout.addWidget(active_checkbox) - top_layout.addWidget(expand_btn) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(2, 2, 2, 2) - layout.addLayout(top_layout) - layout.addWidget(detail_widget) - - self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) - active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground) - expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - active_checkbox.stateChanged.connect(self._on_active_change) - expand_btn.clicked.connect(self._on_expend_clicked) - - self.subset_name_label = subset_name_label - self.active_checkbox = active_checkbox - self.expand_btn = expand_btn - - def set_active(self, new_value): - checkbox_value = self.active_checkbox.isChecked() - instance_value = self.instance.data["active"] - - # First change instance value and them change checkbox - # - prevent to trigger `active_changed` signal - if instance_value != new_value: - self.instance.data["active"] = new_value - - if checkbox_value != new_value: - self.active_checkbox.setChecked(new_value) - - def update_instance(self, instance): - self.instance = instance - self.update_instance_values() - - def update_instance_values(self): - self.set_active(self.instance.data["active"]) - - def _set_expanded(self, expanded=None): - if expanded is None: - expanded = not self.detail_widget.isVisible() - self.detail_widget.setVisible(expanded) - self.item.setSizeHint(self.sizeHint()) - - def showEvent(self, event): - super(InstanceCardWidget, self).showEvent(event) - self.item.setSizeHint(self.sizeHint()) - - def _on_active_change(self): - new_value = self.active_checkbox.isChecked() - old_value = self.instance.data["active"] - if new_value == old_value: - return - - self.instance.data["active"] = new_value - self.active_changed.emit(self.instance.data["uuid"], new_value) - - def _on_expend_clicked(self): - self._set_expanded() - - -class _AbstractInstanceView(QtWidgets.QWidget): - selection_changed = QtCore.Signal() - - def refresh(self): - raise NotImplementedError(( - "{} Method 'refresh' is not implemented." - ).format(self.__class__.__name__)) - - def get_selected_instances(self): - raise NotImplementedError(( - "{} Method 'get_selected_instances' is not implemented." - ).format(self.__class__.__name__)) - - def set_selected_instances(self, instances): - raise NotImplementedError(( - "{} Method 'set_selected_instances' is not implemented." - ).format(self.__class__.__name__)) - - -class InstanceCardView(_AbstractInstanceView): - def __init__(self, controller, parent): - super(InstanceCardView, self).__init__(parent) - - self.controller = controller - - list_widget = QtWidgets.QListWidget(self) - list_widget.setSelectionMode( - QtWidgets.QAbstractItemView.ExtendedSelection - ) - list_widget.setResizeMode(QtWidgets.QListView.Adjust) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(list_widget, 1) - - list_widget.selectionModel().selectionChanged.connect( - self._on_selection_change - ) - - self._items_by_id = {} - self._widgets_by_id = {} - - self.list_widget = list_widget - - def refresh(self): - instances_by_id = {} - for instance in self.controller.instances: - instance_id = instance.data["uuid"] - instances_by_id[instance_id] = instance - - for instance_id in tuple(self._items_by_id.keys()): - if instance_id not in instances_by_id: - item = self._items_by_id.pop(instance_id) - self.list_widget.removeItemWidget(item) - widget = self._widgets_by_id.pop(instance_id) - widget.deleteLater() - row = self.list_widget.row(item) - self.list_widget.takeItem(row) - - for instance_id, instance in instances_by_id.items(): - if instance_id in self._items_by_id: - widget = self._widgets_by_id[instance_id] - widget.update_instance(instance) - - else: - item = QtWidgets.QListWidgetItem(self.list_widget) - widget = InstanceCardWidget(instance, item, self) - widget.active_changed.connect(self._on_active_changed) - item.setData(INSTANCE_ID_ROLE, instance_id) - self.list_widget.addItem(item) - self.list_widget.setItemWidget(item, widget) - self._items_by_id[instance_id] = item - self._widgets_by_id[instance_id] = widget - - def refresh_active_state(self): - for widget in self._widgets_by_id.values(): - widget.update_instance_values() - - def _on_active_changed(self, changed_instance_id, new_value): - selected_ids = set() - found = False - for item in self.list_widget.selectedItems(): - instance_id = item.data(INSTANCE_ID_ROLE) - selected_ids.add(instance_id) - if not found and instance_id == changed_instance_id: - found = True - - if not found: - return - - for instance_id in selected_ids: - widget = self._widgets_by_id.get(instance_id) - if widget: - widget.set_active(new_value) - - def _on_selection_change(self, *_args): - self.selection_changed.emit() - - def get_selected_instances(self): - instances = [] - for item in self.list_widget.selectedItems(): - instance_id = item.data(INSTANCE_ID_ROLE) - widget = self._widgets_by_id.get(instance_id) - if widget: - instances.append(widget.instance) - return instances - - def set_selected_instances(self, instances): - indexes = [] - model = self.list_widget.model() - for instance in instances: - instance_id = instance.data["uuid"] - item = self._items_by_id.get(instance_id) - if item: - row = self.list_widget.row(item) - index = model.index(row, 0) - indexes.append(index) - - selection_model = self.list_widget.selectionModel() - first_item = True - for index in indexes: - if first_item: - first_item = False - select_type = QtCore.QItemSelectionModel.ClearAndSelect - else: - select_type = QtCore.QItemSelectionModel.Select - selection_model.select(index, select_type) - - -class InstanceListItemWidget(QtWidgets.QWidget): - active_changed = QtCore.Signal(str, bool) - - def __init__(self, instance, parent): - super(InstanceListItemWidget, self).__init__(parent) - - self.instance = instance - - subset_name_label = QtWidgets.QLabel(instance.data["subset"], self) - active_checkbox = QtWidgets.QCheckBox(self) - active_checkbox.setChecked(instance.data["active"]) - - layout = QtWidgets.QHBoxLayout(self) - content_margins = layout.contentsMargins() - layout.setContentsMargins(content_margins.left() + 2, 0, 2, 0) - layout.addWidget(subset_name_label) - layout.addStretch(1) - layout.addWidget(active_checkbox) - - self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) - active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - active_checkbox.stateChanged.connect(self._on_active_change) - - self.subset_name_label = subset_name_label - self.active_checkbox = active_checkbox - - def set_active(self, new_value): - checkbox_value = self.active_checkbox.isChecked() - instance_value = self.instance.data["active"] - - # First change instance value and them change checkbox - # - prevent to trigger `active_changed` signal - if instance_value != new_value: - self.instance.data["active"] = new_value - - if checkbox_value != new_value: - self.active_checkbox.setChecked(new_value) - - def update_instance(self, instance): - self.instance = instance - self.update_instance_values() - - def update_instance_values(self): - self.set_active(self.instance.data["active"]) - - def _on_active_change(self): - new_value = self.active_checkbox.isChecked() - old_value = self.instance.data["active"] - if new_value == old_value: - return - - self.instance.data["active"] = new_value - self.active_changed.emit(self.instance.data["uuid"], new_value) - - -class InstanceListGroupWidget(QtWidgets.QFrame): - expand_changed = QtCore.Signal(str, bool) - - def __init__(self, family, parent): - super(InstanceListGroupWidget, self).__init__(parent) - self.setObjectName("InstanceListGroupWidget") - - self.family = family - self._expanded = False - - subset_name_label = QtWidgets.QLabel(family, self) - - expand_btn = QtWidgets.QToolButton(self) - expand_btn.setObjectName("ArrowBtn") - expand_btn.setArrowType(QtCore.Qt.RightArrow) - expand_btn.setMaximumWidth(14) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(5, 0, 2, 0) - layout.addWidget( - subset_name_label, 1, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter - ) - layout.addWidget(expand_btn) - - # self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - subset_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground) - expand_btn.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - expand_btn.clicked.connect(self._on_expand_clicked) - - self.subset_name_label = subset_name_label - self.expand_btn = expand_btn - - def _on_expand_clicked(self): - self.expand_changed.emit(self.family, not self._expanded) - - def set_expanded(self, expanded): - if self._expanded == expanded: - return - - self._expanded = expanded - if expanded: - self.expand_btn.setArrowType(QtCore.Qt.DownArrow) - else: - self.expand_btn.setArrowType(QtCore.Qt.RightArrow) - - -class InstanceListView(_AbstractInstanceView): - def __init__(self, controller, parent): - super(InstanceListView, self).__init__(parent) - - self.controller = controller - - instance_view = QtWidgets.QTreeView(self) - instance_view.setObjectName("InstanceListView") - instance_view.setHeaderHidden(True) - instance_view.setIndentation(0) - instance_view.setSelectionMode( - QtWidgets.QAbstractItemView.ExtendedSelection - ) - - instance_model = QtGui.QStandardItemModel() - - proxy_model = QtCore.QSortFilterProxyModel() - proxy_model.setSourceModel(instance_model) - proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - proxy_model.setSortRole(SORT_VALUE_ROLE) - proxy_model.setFilterKeyColumn(0) - proxy_model.setDynamicSortFilter(True) - - instance_view.setModel(proxy_model) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(instance_view) - - instance_view.selectionModel().selectionChanged.connect( - self._on_selection_change - ) - instance_view.collapsed.connect(self._on_collapse) - instance_view.expanded.connect(self._on_expand) - - self._group_items = {} - self._group_widgets = {} - self._widgets_by_id = {} - self.instance_view = instance_view - self.instance_model = instance_model - self.proxy_model = proxy_model - - def _on_expand(self, index): - family = index.data(SORT_VALUE_ROLE) - group_widget = self._group_widgets.get(family) - if group_widget: - group_widget.set_expanded(True) - - def _on_collapse(self, index): - family = index.data(SORT_VALUE_ROLE) - group_widget = self._group_widgets.get(family) - if group_widget: - group_widget.set_expanded(False) - - def refresh(self): - instances_by_family = collections.defaultdict(list) - families = set() - for instance in self.controller.instances: - family = instance.data["family"] - families.add(family) - instances_by_family[family].append(instance) - - new_group_items = [] - for family in families: - if family in self._group_items: - continue - - group_item = QtGui.QStandardItem() - group_item.setData(family, SORT_VALUE_ROLE) - group_item.setFlags(QtCore.Qt.ItemIsEnabled) - self._group_items[family] = group_item - new_group_items.append(group_item) - - sort_at_the_end = False - root_item = self.instance_model.invisibleRootItem() - if new_group_items: - sort_at_the_end = True - root_item.appendRows(new_group_items) - - for group_item in new_group_items: - index = self.instance_model.index( - group_item.row(), group_item.column() - ) - proxy_index = self.proxy_model.mapFromSource(index) - family = group_item.data(SORT_VALUE_ROLE) - widget = InstanceListGroupWidget(family, self.instance_view) - widget.expand_changed.connect(self._on_group_expand_request) - self._group_widgets[family] = widget - self.instance_view.setIndexWidget(proxy_index, widget) - - for family in tuple(self._group_items.keys()): - if family in families: - continue - - group_item = self._group_items.pop(family) - root_item.removeRow(group_item.row()) - widget = self._group_widgets.pop(family) - widget.deleteLater() - - for family, group_item in self._group_items.items(): - to_remove = set() - existing_mapping = {} - - group_index = self.instance_model.index( - group_item.row(), group_item.column() - ) - - for idx in range(group_item.rowCount()): - index = self.instance_model.index(idx, 0, group_index) - instance_id = index.data(INSTANCE_ID_ROLE) - to_remove.add(instance_id) - existing_mapping[instance_id] = idx - - new_items = [] - new_items_with_instance = [] - for instance in instances_by_family[family]: - instance_id = instance.data["uuid"] - if instance_id in to_remove: - to_remove.remove(instance_id) - widget = self._widgets_by_id[instance_id] - widget.update_instance(instance) - continue - - item = QtGui.QStandardItem() - item.setData(instance.data["subset"], SORT_VALUE_ROLE) - item.setData(instance.data["uuid"], INSTANCE_ID_ROLE) - new_items.append(item) - new_items_with_instance.append((item, instance)) - - idx_to_remove = [] - for instance_id in to_remove: - idx_to_remove.append(existing_mapping[instance_id]) - - for idx in reversed(sorted(idx_to_remove)): - group_item.removeRows(idx, 1) - - for instance_id in to_remove: - widget = self._widgets_by_id.pop(instance_id) - widget.deleteLater() - - if new_items: - sort_at_the_end = True - - group_item.appendRows(new_items) - - for item, instance in new_items_with_instance: - item_index = self.instance_model.index( - item.row(), - item.column(), - group_index - ) - proxy_index = self.proxy_model.mapFromSource(item_index) - widget = InstanceListItemWidget( - instance, self.instance_view - ) - self.instance_view.setIndexWidget(proxy_index, widget) - self._widgets_by_id[instance.data["uuid"]] = widget - - # Trigger sort at the end of refresh - if sort_at_the_end: - self.proxy_model.sort(0) - - def refresh_active_state(self): - for widget in self._widgets_by_id.values(): - widget.update_instance_values() - - def get_selected_instances(self): - instances = [] - instances_by_id = {} - for instance in self.controller.instances: - instance_id = instance.data["uuid"] - instances_by_id[instance_id] = instance - - for index in self.instance_view.selectionModel().selectedIndexes(): - instance_id = index.data(INSTANCE_ID_ROLE) - if instance_id is not None: - instance = instances_by_id.get(instance_id) - if instance: - instances.append(instance) - - return instances - - def set_selected_instances(self, instances): - model = self.instance_view.model() - instance_ids_by_family = collections.defaultdict(set) - for instance in instances: - family = instance.data["family"] - instance_id = instance.data["uuid"] - instance_ids_by_family[family].add(instance_id) - - indexes = [] - for family, group_item in self._group_items.items(): - selected_ids = instance_ids_by_family[family] - if not selected_ids: - continue - - group_index = self.instance_model.index( - group_item.row(), group_item.column() - ) - proxy_group_index = self.proxy_model.mapFromSource(group_index) - has_indexes = False - for row in range(group_item.rowCount()): - index = model.index(row, 0, proxy_group_index) - instance_id = index.data(INSTANCE_ID_ROLE) - if instance_id in selected_ids: - indexes.append(index) - has_indexes = True - - if has_indexes: - self.instance_view.setExpanded(proxy_group_index, True) - - selection_model = self.instance_view.selectionModel() - first_item = True - for index in indexes: - if first_item: - first_item = False - select_type = QtCore.QItemSelectionModel.ClearAndSelect - else: - select_type = QtCore.QItemSelectionModel.Select - selection_model.select(index, select_type) - - def _on_selection_change(self, *_args): - self.selection_changed.emit() - - def _on_group_expand_request(self, family, expanded): - group_item = self._group_items.get(family) - if not group_item: - return - - group_index = self.instance_model.index( - group_item.row(), group_item.column() - ) - proxy_index = self.proxy_model.mapFromSource(group_index) - self.instance_view.setExpanded(proxy_index, expanded) - - class PublishOverlayFrame(QtWidgets.QFrame): hide_requested = QtCore.Signal()