mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
visualize instance parenting in list view
This commit is contained in:
parent
b500709379
commit
c8eb0faf3c
1 changed files with 331 additions and 170 deletions
|
|
@ -25,7 +25,7 @@ selection can be enabled disabled using checkbox or keyboard key presses:
|
|||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import typing
|
||||
from typing import Optional
|
||||
|
||||
from qtpy import QtWidgets, QtCore, QtGui
|
||||
|
||||
|
|
@ -33,7 +33,14 @@ from ayon_core.style import get_objected_colors
|
|||
from ayon_core.tools.utils import NiceCheckbox, BaseClickableFrame
|
||||
from ayon_core.tools.utils.lib import html_escape, checkstate_int_to_enum
|
||||
|
||||
from ayon_core.pipeline.create import (
|
||||
InstanceContextInfo,
|
||||
)
|
||||
|
||||
from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend
|
||||
from ayon_core.tools.publisher.models.create import (
|
||||
InstanceItem,
|
||||
)
|
||||
from ayon_core.tools.publisher.constants import (
|
||||
INSTANCE_ID_ROLE,
|
||||
SORT_VALUE_ROLE,
|
||||
|
|
@ -47,9 +54,6 @@ from ayon_core.tools.publisher.constants import (
|
|||
|
||||
from .widgets import AbstractInstanceView
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from ayon_core.tools.publisher.abstract import InstanceItem
|
||||
|
||||
|
||||
class ListItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""Generic delegate for instance group.
|
||||
|
|
@ -121,7 +125,13 @@ class InstanceListItemWidget(QtWidgets.QWidget):
|
|||
active_changed = QtCore.Signal(str, bool)
|
||||
double_clicked = QtCore.Signal()
|
||||
|
||||
def __init__(self, instance, context_info, parent):
|
||||
def __init__(
|
||||
self,
|
||||
instance: InstanceItem,
|
||||
context_info: InstanceContextInfo,
|
||||
parent_is_active: bool,
|
||||
parent: QtWidgets.QWidget,
|
||||
):
|
||||
super().__init__(parent)
|
||||
|
||||
self._instance_id = instance.id
|
||||
|
|
@ -137,8 +147,6 @@ class InstanceListItemWidget(QtWidgets.QWidget):
|
|||
product_name_label.setObjectName("ListViewProductName")
|
||||
|
||||
active_checkbox = NiceCheckbox(parent=self)
|
||||
active_checkbox.setChecked(instance.is_active)
|
||||
active_checkbox.setVisible(not instance.is_mandatory)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(2, 0, 2, 0)
|
||||
|
|
@ -146,20 +154,32 @@ class InstanceListItemWidget(QtWidgets.QWidget):
|
|||
layout.addStretch(1)
|
||||
layout.addWidget(active_checkbox)
|
||||
|
||||
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
product_name_label.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
active_checkbox.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
for widget in (
|
||||
self,
|
||||
product_name_label,
|
||||
active_checkbox,
|
||||
):
|
||||
widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
|
||||
active_checkbox.stateChanged.connect(self._on_active_change)
|
||||
|
||||
self._instance_label_widget = product_name_label
|
||||
self._active_checkbox = active_checkbox
|
||||
|
||||
self._has_valid_context = None
|
||||
# Instance info
|
||||
self._has_valid_context = context_info.is_valid
|
||||
self._is_mandatory = instance.is_mandatory
|
||||
self._instance_is_active = instance.is_active
|
||||
|
||||
self._checkbox_enabled = not instance.is_mandatory
|
||||
# Parent active state is fluent and can change
|
||||
self._parent_is_active = parent_is_active
|
||||
|
||||
self._set_valid_property(context_info.is_valid)
|
||||
# Widget logic info
|
||||
self._state = None
|
||||
self._toggle_is_enabled = True
|
||||
|
||||
self._update_style_state()
|
||||
self._update_checkbox_state()
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
widget = self.childAt(event.pos())
|
||||
|
|
@ -167,60 +187,108 @@ class InstanceListItemWidget(QtWidgets.QWidget):
|
|||
if widget is not self._active_checkbox:
|
||||
self.double_clicked.emit()
|
||||
|
||||
def _set_valid_property(self, valid):
|
||||
if self._has_valid_context == valid:
|
||||
return
|
||||
self._has_valid_context = valid
|
||||
state = ""
|
||||
if not valid:
|
||||
state = "invalid"
|
||||
self._instance_label_widget.setProperty("state", state)
|
||||
self._instance_label_widget.style().polish(self._instance_label_widget)
|
||||
|
||||
def is_active(self):
|
||||
def is_active(self) -> bool:
|
||||
"""Instance is activated."""
|
||||
return self._active_checkbox.isChecked()
|
||||
|
||||
def set_active(self, new_value):
|
||||
"""Change active state of instance and checkbox."""
|
||||
old_value = self.is_active()
|
||||
if new_value is None:
|
||||
new_value = not old_value
|
||||
|
||||
if new_value != old_value:
|
||||
self._active_checkbox.blockSignals(True)
|
||||
self._active_checkbox.setChecked(new_value)
|
||||
self._active_checkbox.blockSignals(False)
|
||||
|
||||
def is_checkbox_enabled(self) -> bool:
|
||||
"""Checkbox can be changed by user."""
|
||||
return self._checkbox_enabled
|
||||
return (
|
||||
self._parent_is_active
|
||||
and not self._is_mandatory
|
||||
)
|
||||
|
||||
def update_instance(self, instance, context_info):
|
||||
def set_active_toggle_enabled(self, enabled: bool) -> None:
|
||||
"""Toggle can be available for user."""
|
||||
self._toggle_is_enabled = enabled
|
||||
self._update_checkbox_state()
|
||||
|
||||
def set_active(self, new_value: Optional[bool]) -> None:
|
||||
"""Change active state of instance and checkbox by user interaction.
|
||||
|
||||
Args:
|
||||
new_value (Optional[bool]): New active state of instance. Toggle
|
||||
if is 'None'.
|
||||
|
||||
"""
|
||||
# Do not allow to change state if is mandatory or parent is not active
|
||||
if not self.is_checkbox_enabled():
|
||||
return
|
||||
|
||||
if new_value is None:
|
||||
new_value = not self._active_checkbox.isChecked()
|
||||
# Update instance active state
|
||||
self._instance_is_active = new_value
|
||||
self._set_checked(new_value)
|
||||
|
||||
def update_instance(
|
||||
self,
|
||||
instance: InstanceItem,
|
||||
context_info: InstanceContextInfo,
|
||||
parent_is_active: bool,
|
||||
) -> None:
|
||||
"""Update instance object."""
|
||||
# Check product name
|
||||
self._instance_id = instance.id
|
||||
label = instance.label
|
||||
if label != self._instance_label_widget.text():
|
||||
self._instance_label_widget.setText(html_escape(label))
|
||||
# Check active state
|
||||
self.set_active(instance.is_active)
|
||||
self._set_is_mandatory(instance.is_mandatory)
|
||||
# Check valid states
|
||||
self._set_valid_property(context_info.is_valid)
|
||||
|
||||
self._is_mandatory = instance.is_mandatory
|
||||
self._instance_is_active = instance.is_active
|
||||
self._has_valid_context = context_info.is_valid
|
||||
self._parent_is_active = parent_is_active
|
||||
|
||||
self._update_checkbox_state()
|
||||
self._update_style_state()
|
||||
|
||||
def set_parent_is_active(self, active: bool) -> None:
|
||||
if self._parent_is_active is active:
|
||||
return
|
||||
self._parent_is_active = active
|
||||
self._update_style_state()
|
||||
self._update_checkbox_state()
|
||||
|
||||
def _set_checked(self, checked: bool) -> None:
|
||||
"""Change checked state in UI without triggering checkstate change."""
|
||||
old_value = self._active_checkbox.isChecked()
|
||||
if checked is not old_value:
|
||||
self._active_checkbox.blockSignals(True)
|
||||
self._active_checkbox.setChecked(checked)
|
||||
self._active_checkbox.blockSignals(False)
|
||||
|
||||
def _update_style_state(self) -> None:
|
||||
state = ""
|
||||
if not self._parent_is_active:
|
||||
state = "disabled"
|
||||
elif not self._has_valid_context:
|
||||
state = "invalid"
|
||||
|
||||
if state == self._state:
|
||||
return
|
||||
self._state = state
|
||||
self._instance_label_widget.setProperty("state", state)
|
||||
self._instance_label_widget.style().polish(self._instance_label_widget)
|
||||
|
||||
def _update_checkbox_state(self) -> None:
|
||||
self._active_checkbox.setEnabled(
|
||||
self._toggle_is_enabled
|
||||
and not self._is_mandatory
|
||||
and self._parent_is_active
|
||||
)
|
||||
# Hide checkbox for mandatory instances
|
||||
self._active_checkbox.setVisible(not self._is_mandatory)
|
||||
|
||||
# Visually disable instance if parent is disabled
|
||||
checked = self._parent_is_active and self._instance_is_active
|
||||
if checked is not self._active_checkbox.isChecked():
|
||||
self._active_checkbox.setChecked(checked)
|
||||
|
||||
def _on_active_change(self):
|
||||
self.active_changed.emit(
|
||||
self._instance_id, self._active_checkbox.isChecked()
|
||||
)
|
||||
|
||||
def set_active_toggle_enabled(self, enabled):
|
||||
self._active_checkbox.setEnabled(enabled)
|
||||
|
||||
def _set_is_mandatory(self, is_mandatory: bool) -> None:
|
||||
self._checkbox_enabled = not is_mandatory
|
||||
self._active_checkbox.setVisible(not is_mandatory)
|
||||
|
||||
|
||||
class ListContextWidget(QtWidgets.QFrame):
|
||||
"""Context (or global attributes) widget."""
|
||||
|
|
@ -421,7 +489,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
|
||||
self._active_toggle_enabled = True
|
||||
|
||||
def _on_toggle_request(self, toggle):
|
||||
def _on_toggle_request(self, toggle: int) -> None:
|
||||
if not self._active_toggle_enabled:
|
||||
return
|
||||
|
||||
|
|
@ -432,20 +500,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
active = True
|
||||
else:
|
||||
active = False
|
||||
|
||||
group_names = set()
|
||||
for instance_id in selected_instance_ids:
|
||||
widget = self._widgets_by_id.get(instance_id)
|
||||
if widget is None:
|
||||
continue
|
||||
|
||||
widget.set_active(active)
|
||||
group_name = self._group_by_instance_id.get(instance_id)
|
||||
if group_name is not None:
|
||||
group_names.add(group_name)
|
||||
|
||||
for group_name in group_names:
|
||||
self._update_group_checkstate(group_name)
|
||||
self._toggle_active_state(selected_instance_ids, active)
|
||||
|
||||
def _update_group_checkstate(self, group_name):
|
||||
"""Update checkstate of one group."""
|
||||
|
|
@ -454,8 +509,10 @@ class InstanceListView(AbstractInstanceView):
|
|||
return
|
||||
|
||||
activity = None
|
||||
for instance_id, _group_name in self._group_by_instance_id.items():
|
||||
if _group_name != group_name:
|
||||
for (
|
||||
instance_id, instance_group_name
|
||||
) in self._group_by_instance_id.items():
|
||||
if instance_group_name != group_name:
|
||||
continue
|
||||
|
||||
instance_widget = self._widgets_by_id.get(instance_id)
|
||||
|
|
@ -509,13 +566,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
group_label = instance.group_label
|
||||
group_names.add(group_label)
|
||||
instances_by_group_name[group_label].append(instance)
|
||||
|
||||
missing_parent_ids = set(instances_by_parent_id) - instance_ids
|
||||
for instance_id in missing_parent_ids:
|
||||
for instance in instances_by_parent_id[instance_id]:
|
||||
group_label = instance.group_label
|
||||
group_names.add(group_label)
|
||||
instances_by_group_name[group_label].append(instance)
|
||||
self._group_by_instance_id[instance.id] = group_label
|
||||
|
||||
# Create new groups based on prepared `instances_by_group_name`
|
||||
if self._make_sure_groups_exists(group_names):
|
||||
|
|
@ -525,15 +576,42 @@ class InstanceListView(AbstractInstanceView):
|
|||
self._remove_groups_except(group_names)
|
||||
self._remove_instances_except(instance_items)
|
||||
|
||||
expand_groups = set()
|
||||
expand_to_items = []
|
||||
widgets_by_id = {}
|
||||
group_items = [
|
||||
(
|
||||
self._group_widgets[group_name],
|
||||
instances_by_group_name[group_name],
|
||||
group_item,
|
||||
)
|
||||
for group_name, group_item in self._group_items.items()
|
||||
]
|
||||
|
||||
# Handle orphaned instances
|
||||
missing_parent_ids = set(instances_by_parent_id) - instance_ids
|
||||
if not missing_parent_ids:
|
||||
# Make sure the item is not in view if there are no orhpaned items
|
||||
self._remove_missing_parent_item()
|
||||
else:
|
||||
# Add orphaned group item and append them to 'group_items'
|
||||
orphans_item = self._add_missing_parent_item()
|
||||
for instance_id in missing_parent_ids:
|
||||
group_items.append((
|
||||
None,
|
||||
instances_by_parent_id[instance_id],
|
||||
orphans_item,
|
||||
))
|
||||
|
||||
# Process changes in each group item
|
||||
# - create new instance, update existing and remove not existing
|
||||
for group_name, group_item in self._group_items.items():
|
||||
# Collect all new instances that are not existing under group
|
||||
# New items
|
||||
for group_widget, group_instances, group_item in group_items:
|
||||
# Group widget is not set if is orphaned
|
||||
# - This might need to be changed in future if widget could
|
||||
# be 'None'
|
||||
is_orpaned_item = group_widget is None
|
||||
|
||||
# Collect all new instances by parent id
|
||||
# - 'None' is used if parent is group item
|
||||
new_items = collections.defaultdict(list)
|
||||
# Tuples of model item and instance itself
|
||||
items_with_instance = []
|
||||
|
|
@ -542,7 +620,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
# - 1 when all instances are enabled
|
||||
# - -1 when it's mixed
|
||||
activity = None
|
||||
for instance in instances_by_group_name[group_name]:
|
||||
for instance in group_instances:
|
||||
_queue = collections.deque()
|
||||
_queue.append((instance, group_item, None))
|
||||
while _queue:
|
||||
|
|
@ -556,7 +634,9 @@ class InstanceListView(AbstractInstanceView):
|
|||
elif activity != instance.is_active:
|
||||
activity = -1
|
||||
|
||||
self._group_by_instance_id[instance_id] = group_name
|
||||
# Remove group name from groups mapping
|
||||
if parent_id is not None:
|
||||
self._group_by_instance_id.pop(instance_id, None)
|
||||
|
||||
# Create new item and store it as new
|
||||
item = self._items_by_id.get(instance_id)
|
||||
|
|
@ -572,7 +652,13 @@ class InstanceListView(AbstractInstanceView):
|
|||
|
||||
children = instances_by_parent_id.pop(instance_id, [])
|
||||
items_with_instance.append(
|
||||
(item, instance, bool(children))
|
||||
(
|
||||
item,
|
||||
instance,
|
||||
parent_id,
|
||||
is_orpaned_item,
|
||||
bool(children)
|
||||
)
|
||||
)
|
||||
|
||||
item.setData(instance.product_name, SORT_VALUE_ROLE)
|
||||
|
|
@ -582,15 +668,13 @@ class InstanceListView(AbstractInstanceView):
|
|||
_queue.append((child, item, instance_id))
|
||||
|
||||
# Set checkstate of group checkbox
|
||||
state = QtCore.Qt.PartiallyChecked
|
||||
if activity == 0:
|
||||
state = QtCore.Qt.Unchecked
|
||||
elif activity == 1:
|
||||
state = QtCore.Qt.Checked
|
||||
|
||||
if group_name is not None:
|
||||
widget = self._group_widgets[group_name]
|
||||
widget.set_checkstate(state)
|
||||
if group_widget is not None:
|
||||
state = QtCore.Qt.PartiallyChecked
|
||||
if activity == 0:
|
||||
state = QtCore.Qt.Unchecked
|
||||
elif activity == 1:
|
||||
state = QtCore.Qt.Checked
|
||||
group_widget.set_checkstate(state)
|
||||
|
||||
# Process new instance items and add them to model and create
|
||||
# their widgets
|
||||
|
|
@ -607,20 +691,38 @@ class InstanceListView(AbstractInstanceView):
|
|||
|
||||
parent_item.appendRows(items)
|
||||
|
||||
for item, instance, has_children in items_with_instance:
|
||||
for (
|
||||
item, instance, parent_id, is_orpaned_item, has_children
|
||||
) in items_with_instance:
|
||||
context_info = context_info_by_id[instance.id]
|
||||
# TODO expand all parents
|
||||
if not context_info.is_valid:
|
||||
expand_groups.add(group_name)
|
||||
expand_to_items.append(item)
|
||||
|
||||
parent_active = True
|
||||
if is_orpaned_item:
|
||||
parent_active = False
|
||||
|
||||
if parent_id:
|
||||
parent_widget = widgets_by_id.get(parent_id)
|
||||
parent_active = False
|
||||
if parent_widget is not None:
|
||||
parent_active = parent_widget.is_active()
|
||||
item_index = self._instance_model.indexFromItem(item)
|
||||
proxy_index = self._proxy_model.mapFromSource(item_index)
|
||||
widget = self._instance_view.indexWidget(proxy_index)
|
||||
if isinstance(widget, InstanceListItemWidget):
|
||||
widget.update_instance(instance, context_info)
|
||||
widget.update_instance(
|
||||
instance,
|
||||
context_info,
|
||||
parent_active,
|
||||
)
|
||||
else:
|
||||
widget = InstanceListItemWidget(
|
||||
instance, context_info, self._instance_view
|
||||
instance,
|
||||
context_info,
|
||||
parent_active,
|
||||
self._instance_view
|
||||
)
|
||||
widget.active_changed.connect(self._on_active_changed)
|
||||
widget.double_clicked.connect(self.double_clicked)
|
||||
|
|
@ -639,10 +741,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
self._widgets_by_id = widgets_by_id
|
||||
|
||||
# Expand items marked for expanding
|
||||
items_to_expand = [
|
||||
self._group_items[group_name]
|
||||
for group_name in expand_groups
|
||||
]
|
||||
items_to_expand = []
|
||||
_marked_ids = set()
|
||||
for item in expand_to_items:
|
||||
parent = item.parent()
|
||||
|
|
@ -669,7 +768,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
if sort_at_the_end:
|
||||
self._proxy_model.sort(0)
|
||||
|
||||
def _make_sure_context_item_exists(self):
|
||||
def _make_sure_context_item_exists(self) -> bool:
|
||||
if self._context_item is not None:
|
||||
return False
|
||||
|
||||
|
|
@ -692,7 +791,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
self._context_item = context_item
|
||||
return True
|
||||
|
||||
def _update_convertor_items_group(self):
|
||||
def _update_convertor_items_group(self) -> bool:
|
||||
created_new_items = False
|
||||
convertor_items_by_id = self._controller.get_convertor_items()
|
||||
group_item = self._convertor_group_item
|
||||
|
|
@ -758,7 +857,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
|
||||
return created_new_items
|
||||
|
||||
def _make_sure_groups_exists(self, group_names):
|
||||
def _make_sure_groups_exists(self, group_names: set[str]) -> bool:
|
||||
new_group_items = []
|
||||
for group_name in group_names:
|
||||
if group_name in self._group_items:
|
||||
|
|
@ -800,7 +899,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
|
||||
return True
|
||||
|
||||
def _remove_groups_except(self, group_names):
|
||||
def _remove_groups_except(self, group_names: set[str]) -> None:
|
||||
# Remove groups that are not available anymore
|
||||
root_item = self._instance_model.invisibleRootItem()
|
||||
for group_name in tuple(self._group_items.keys()):
|
||||
|
|
@ -840,14 +939,14 @@ class InstanceListView(AbstractInstanceView):
|
|||
|
||||
for instance_id in all_removed_ids:
|
||||
self._items_by_id.pop(instance_id)
|
||||
self._group_by_instance_id.pop(instance_id)
|
||||
self._parent_id_by_id.pop(instance_id)
|
||||
self._group_by_instance_id.pop(instance_id, None)
|
||||
widget = self._widgets_by_id.pop(instance_id, None)
|
||||
if widget is not None:
|
||||
widget.setVisible(False)
|
||||
widget.deleteLater()
|
||||
|
||||
def _add_missing_parent_item(self):
|
||||
def _add_missing_parent_item(self) -> QtGui.QStandardItem:
|
||||
label = "! Orphaned instances !"
|
||||
if self._missing_parent_item is None:
|
||||
item = QtGui.QStandardItem()
|
||||
|
|
@ -857,7 +956,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
item.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
self._missing_parent_item = item
|
||||
|
||||
if self._missing_parent_item.parent() is None:
|
||||
if self._missing_parent_item.row() < 0:
|
||||
root_item = self._instance_model.invisibleRootItem()
|
||||
root_item.appendRow(self._missing_parent_item)
|
||||
index = self._missing_parent_item.index()
|
||||
|
|
@ -867,7 +966,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
self._instance_view.setIndexWidget(proxy_index, widget)
|
||||
return self._missing_parent_item
|
||||
|
||||
def _remove_missing_parent_item(self):
|
||||
def _remove_missing_parent_item(self) -> None:
|
||||
if self._missing_parent_item is None:
|
||||
return
|
||||
|
||||
|
|
@ -890,34 +989,130 @@ class InstanceListView(AbstractInstanceView):
|
|||
"""Trigger update of all instances."""
|
||||
if instance_ids is not None:
|
||||
instance_ids = set(instance_ids)
|
||||
context_info_by_id = self._controller.get_instances_context_info()
|
||||
|
||||
context_info_by_id = self._controller.get_instances_context_info(
|
||||
instance_ids
|
||||
)
|
||||
instance_items_by_id = self._controller.get_instance_items_by_id(
|
||||
instance_ids
|
||||
)
|
||||
for instance_id, widget in self._widgets_by_id.items():
|
||||
if instance_ids is not None and instance_id not in instance_ids:
|
||||
instance_ids = set(instance_items_by_id)
|
||||
|
||||
group_items = list(self._group_items.values())
|
||||
if self._missing_parent_item is not None:
|
||||
group_items.append(self._missing_parent_item)
|
||||
|
||||
_queue = collections.deque()
|
||||
for group_item in group_items:
|
||||
if not group_item.hasChildren():
|
||||
continue
|
||||
widget.update_instance(
|
||||
instance_items_by_id[instance_id],
|
||||
context_info_by_id[instance_id],
|
||||
)
|
||||
|
||||
children = [
|
||||
group_item.child(row)
|
||||
for row in range(group_item.rowCount())
|
||||
]
|
||||
_queue.append((children, True))
|
||||
|
||||
while _queue:
|
||||
if not instance_ids:
|
||||
break
|
||||
|
||||
children, parent_active = _queue.popleft()
|
||||
for child in children:
|
||||
instance_id = child.data(INSTANCE_ID_ROLE)
|
||||
widget = self._widgets_by_id[instance_id]
|
||||
if instance_id in instance_ids:
|
||||
instance_ids.discard(instance_id)
|
||||
widget.update_instance(
|
||||
instance_items_by_id[instance_id],
|
||||
context_info_by_id[instance_id],
|
||||
parent_active,
|
||||
)
|
||||
if not instance_ids:
|
||||
break
|
||||
|
||||
if not child.hasChildren():
|
||||
continue
|
||||
|
||||
children = [
|
||||
child.child(row)
|
||||
for row in range(child.rowCount())
|
||||
]
|
||||
_queue.append((children, widget.is_active()))
|
||||
|
||||
def _on_active_changed(self, changed_instance_id, new_value):
|
||||
selected_instance_ids, _, _ = self.get_selected_items()
|
||||
if changed_instance_id not in selected_instance_ids:
|
||||
selected_instance_ids = {changed_instance_id}
|
||||
|
||||
self._toggle_active_state(
|
||||
set(selected_instance_ids),
|
||||
new_value,
|
||||
changed_instance_id
|
||||
)
|
||||
|
||||
def _toggle_active_state(
|
||||
self,
|
||||
instance_ids: set[str],
|
||||
new_value: Optional[bool],
|
||||
active_id: Optional[str] = None,
|
||||
) -> None:
|
||||
active_widget = None
|
||||
if active_id:
|
||||
active_widget = self._widgets_by_id[active_id]
|
||||
active_by_id = {}
|
||||
found = False
|
||||
for instance_id in selected_instance_ids:
|
||||
active_by_id[instance_id] = new_value
|
||||
if not found and instance_id == changed_instance_id:
|
||||
found = True
|
||||
if active_id and active_id not in instance_ids:
|
||||
if not active_widget.is_checkbox_enabled():
|
||||
return
|
||||
if new_value is None:
|
||||
new_value = not active_widget.is_active()
|
||||
active_by_id[active_id] = new_value
|
||||
active_widget.set_active(new_value)
|
||||
else:
|
||||
# First make sure that the item under mouse is changed if possible
|
||||
if active_widget and active_widget.is_checkbox_enabled():
|
||||
value = new_value
|
||||
if value is None:
|
||||
value = not active_widget.is_active()
|
||||
|
||||
if not found:
|
||||
active_by_id = {changed_instance_id: new_value}
|
||||
active_by_id[active_id] = value
|
||||
active_widget.set_active(new_value)
|
||||
instance_ids.discard(active_id)
|
||||
|
||||
# Change the states from top to bottom
|
||||
group_items = list(self._group_items.values())
|
||||
if self._missing_parent_item is not None:
|
||||
group_items.append(self._missing_parent_item)
|
||||
|
||||
_queue = collections.deque()
|
||||
for group_item in group_items:
|
||||
children = [
|
||||
group_item.child(row)
|
||||
for row in range(group_item.rowCount())
|
||||
]
|
||||
_queue.append((children, True))
|
||||
|
||||
while _queue:
|
||||
children, parent_active = _queue.popleft()
|
||||
for child in children:
|
||||
instance_id = child.data(INSTANCE_ID_ROLE)
|
||||
widget = self._widgets_by_id[instance_id]
|
||||
widget.set_parent_is_active(parent_active)
|
||||
if parent_active and instance_id in instance_ids:
|
||||
value = new_value
|
||||
if value is None:
|
||||
value = not widget.is_active()
|
||||
widget.set_active(value)
|
||||
active_by_id[instance_id] = value
|
||||
|
||||
children = [
|
||||
child.child(row)
|
||||
for row in range(child.rowCount())
|
||||
]
|
||||
_queue.append((children, widget.is_active()))
|
||||
|
||||
self._controller.set_instances_active_state(active_by_id)
|
||||
|
||||
self._change_active_instances(active_by_id, new_value)
|
||||
group_names = set()
|
||||
for instance_id in active_by_id:
|
||||
group_name = self._group_by_instance_id.get(instance_id)
|
||||
|
|
@ -927,15 +1122,6 @@ class InstanceListView(AbstractInstanceView):
|
|||
for group_name in group_names:
|
||||
self._update_group_checkstate(group_name)
|
||||
|
||||
def _change_active_instances(self, instance_ids, new_value):
|
||||
if not instance_ids:
|
||||
return
|
||||
|
||||
for instance_id in instance_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()
|
||||
|
||||
|
|
@ -952,64 +1138,39 @@ class InstanceListView(AbstractInstanceView):
|
|||
if state == QtCore.Qt.PartiallyChecked:
|
||||
return
|
||||
|
||||
if state == QtCore.Qt.Checked:
|
||||
active = True
|
||||
else:
|
||||
active = False
|
||||
|
||||
group_item = self._group_items.get(group_name)
|
||||
if not group_item:
|
||||
return
|
||||
|
||||
active_by_id = {}
|
||||
all_changed = True
|
||||
items_to_expand = [group_item]
|
||||
_queue = collections.deque()
|
||||
_queue.append(group_item)
|
||||
while _queue:
|
||||
item = _queue.popleft()
|
||||
for row in range(item.rowCount()):
|
||||
child = item.child(row)
|
||||
instance_id = child.data(INSTANCE_ID_ROLE)
|
||||
if child.hasChildren():
|
||||
items_to_expand.append(child)
|
||||
_queue.append(child)
|
||||
widget = self._widgets_by_id.get(instance_id)
|
||||
if widget is None:
|
||||
continue
|
||||
if widget.is_checkbox_enabled():
|
||||
active_by_id[instance_id] = active
|
||||
else:
|
||||
all_changed = False
|
||||
active = state == QtCore.Qt.Checked
|
||||
|
||||
self._controller.set_instances_active_state(active_by_id)
|
||||
instance_ids = set()
|
||||
for row in range(group_item.rowCount()):
|
||||
child = group_item.child(row)
|
||||
instance_id = child.data(INSTANCE_ID_ROLE)
|
||||
instance_ids.add(instance_id)
|
||||
|
||||
self._change_active_instances(active_by_id, active)
|
||||
self._toggle_active_state(instance_ids, active)
|
||||
|
||||
for item in items_to_expand:
|
||||
proxy_index = self._proxy_model.mapFromSource(item.index())
|
||||
if not self._instance_view.isExpanded(proxy_index):
|
||||
self._instance_view.expand(proxy_index)
|
||||
proxy_index = self._proxy_model.mapFromSource(group_item.index())
|
||||
if not self._instance_view.isExpanded(proxy_index):
|
||||
self._instance_view.expand(proxy_index)
|
||||
|
||||
if not all_changed:
|
||||
# If not all instances were changed, update group checkstate
|
||||
self._update_group_checkstate(group_name)
|
||||
|
||||
def has_items(self):
|
||||
def has_items(self) -> bool:
|
||||
if self._convertor_group_widget is not None:
|
||||
return True
|
||||
if self._group_items:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_selected_items(self):
|
||||
def get_selected_items(self) -> tuple[list[str], bool, list[str]]:
|
||||
"""Get selected instance ids and context selection.
|
||||
|
||||
Returns:
|
||||
tuple<list, bool>: Selected instance ids and boolean if context
|
||||
is selected.
|
||||
"""
|
||||
tuple[list[str], bool, list[str]]: Selected instance ids,
|
||||
boolean if context is selected and selected convertor ids.
|
||||
|
||||
"""
|
||||
instance_ids = []
|
||||
convertor_identifiers = []
|
||||
context_selected = False
|
||||
|
|
@ -1133,7 +1294,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
| QtCore.QItemSelectionModel.Rows
|
||||
)
|
||||
|
||||
def set_active_toggle_enabled(self, enabled):
|
||||
def set_active_toggle_enabled(self, enabled: bool) -> bool:
|
||||
if self._active_toggle_enabled is enabled:
|
||||
return
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue