diff --git a/client/ayon_core/tools/loader/ui/_multicombobox.py b/client/ayon_core/tools/loader/ui/_multicombobox.py index 66a6963775..c026952418 100644 --- a/client/ayon_core/tools/loader/ui/_multicombobox.py +++ b/client/ayon_core/tools/loader/ui/_multicombobox.py @@ -1,3 +1,5 @@ +from typing import List, Tuple, Optional, Iterable, Any + from qtpy import QtWidgets, QtCore, QtGui from ayon_core.tools.utils.lib import ( @@ -10,7 +12,7 @@ from ayon_core.tools.utils.constants import ( ITEM_IS_USER_TRISTATE, ) -CUSTOM_ITEM_TYPE = 0 +VALUE_ITEM_TYPE = 0 STANDARD_ITEM_TYPE = 1 SEPARATOR_ITEM_TYPE = 2 @@ -22,11 +24,11 @@ class CustomPaintDelegate(QtWidgets.QStyledItemDelegate): def __init__( self, - text_role, - short_text_role, - text_color_role, - icon_role, - item_type_role=None, + text_role: int, + short_text_role: int, + text_color_role: int, + icon_role: int, + item_type_role: Optional[int] = None, parent=None ): super().__init__(parent) @@ -42,7 +44,7 @@ class CustomPaintDelegate(QtWidgets.QStyledItemDelegate): item_type = index.data(self._item_type_role) if item_type is None: - item_type = CUSTOM_ITEM_TYPE + item_type = VALUE_ITEM_TYPE if item_type == STANDARD_ITEM_TYPE: super().paint(painter, option, index) @@ -290,15 +292,49 @@ class CustomPaintMultiselectComboBox(QtWidgets.QComboBox): self._placeholder_text = placeholder self._custom_text = None + self._all_unchecked_as_checked = True - def get_placeholder_text(self): + def all_unchecked_as_checked(self) -> bool: + return self._all_unchecked_as_checked + + def set_all_unchecked_as_checked(self, value: bool): + """Set if all unchecked items should be treated as checked. + + Args: + value (bool): If True, all unchecked items will be treated + as checked. + + """ + self._all_unchecked_as_checked = value + + def get_placeholder_text(self) -> Optional[str]: return self._placeholder_text - def set_placeholder_text(self, text): + def set_placeholder_text(self, text: Optional[str]): + """Set the placeholder text. + + Text shown when nothing is selected. + + Args: + text (str | None): The placeholder text. + + """ + if text == self._placeholder_text: + return self._placeholder_text = text self.repaint() - def set_custom_text(self, text): + def set_custom_text(self, text: Optional[str]): + """Set the placeholder text. + + Text always shown in combobox field. + + Args: + text (str | None): The text. Use 'None' to reset to default. + + """ + if text == self._custom_text: + return self._custom_text = text self.repaint() @@ -481,64 +517,78 @@ class CustomPaintMultiselectComboBox(QtWidgets.QComboBox): def setItemCheckState(self, index, state): self.setItemData(index, state, QtCore.Qt.CheckStateRole) - def set_value(self, values, role=None): + def set_value(self, values: Optional[Iterable[Any]], role: Optional[int] = None): if role is None: role = self._value_role for idx in range(self.count()): value = self.itemData(idx, role=role) - if value in values: - check_state = CHECKED_INT - else: + check_state = CHECKED_INT + if values is None or value not in values: check_state = UNCHECKED_INT self.setItemData(idx, check_state, QtCore.Qt.CheckStateRole) self.repaint() + def get_value_info( + self, + role: Optional[int] = None, + propagate_all_unchecked_as_checked: bool = None + ) -> List[Tuple[Any, bool]]: + """Get the values and their checked state. + + Args: + role (int | None): The role to get the values from. + If None, the default value role is used. + propagate_all_unchecked_as_checked (bool | None): If True, + all unchecked items will be treated as checked. + If None, the current value of + 'propagate_all_unchecked_as_checked' is used. + + Returns: + List[Tuple[Any, bool]]: The values and their checked state. + + """ + if role is None: + role = self._value_role + + if propagate_all_unchecked_as_checked is None: + propagate_all_unchecked_as_checked = ( + self._all_unchecked_as_checked + ) + + items = [] + all_unchecked = True + for idx in range(self.count()): + item_type = self.itemData(idx, role=self._item_type_role) + if item_type is not None and item_type != VALUE_ITEM_TYPE: + continue + + state = checkstate_int_to_enum( + self.itemData(idx, role=QtCore.Qt.CheckStateRole) + ) + checked = state == QtCore.Qt.Checked + if checked: + all_unchecked = False + items.append( + (self.itemData(idx, role=role), checked) + ) + + if propagate_all_unchecked_as_checked and all_unchecked: + items = [ + (value, True) + for value, checked in items + ] + return items + def get_value(self, role=None): if role is None: role = self._value_role - items = [] - for idx in range(self.count()): - item_type = self.itemData(idx, role=self._item_type_role) - if item_type is not None and item_type != CUSTOM_ITEM_TYPE: - continue - state = checkstate_int_to_enum( - self.itemData(idx, role=QtCore.Qt.CheckStateRole) - ) - if state == QtCore.Qt.Checked: - items.append(self.itemData(idx, role=role)) - return items - - def get_all_value_info(self, role=None): - if role is None: - role = self._value_role - items = [] - for idx in range(self.count()): - item_type = self.itemData(idx, role=self._item_type_role) - if item_type is not None and item_type != CUSTOM_ITEM_TYPE: - continue - - state = checkstate_int_to_enum( - self.itemData(idx, role=QtCore.Qt.CheckStateRole) - ) - items.append( - ( - self.itemData(idx, role=role), - state == QtCore.Qt.Checked - ) - ) - return items - - def _get_checked_idx(self): - indexes = [] - for idx in range(self.count()): - state = checkstate_int_to_enum( - self.itemData(idx, role=QtCore.Qt.CheckStateRole) - ) - if state == QtCore.Qt.Checked: - indexes.append(idx) - return indexes + return [ + value + for value, checked in self.get_value_info(role) + if checked + ] def wheelEvent(self, event): event.ignore() @@ -555,6 +605,20 @@ class CustomPaintMultiselectComboBox(QtWidgets.QComboBox): return super().keyPressEvent(event) + def _get_checked_idx(self) -> List[int]: + checked_indexes = [] + for idx in range(self.count()): + item_type = self.itemData(idx, role=self._item_type_role) + if item_type is not None and item_type != VALUE_ITEM_TYPE: + continue + + state = checkstate_int_to_enum( + self.itemData(idx, role=QtCore.Qt.CheckStateRole) + ) + if state == QtCore.Qt.Checked: + checked_indexes.append(idx) + return checked_indexes + def _mouse_released_event_handle( self, event, current_index, index_flags, state ): @@ -573,7 +637,6 @@ class CustomPaintMultiselectComboBox(QtWidgets.QComboBox): return UNCHECKED_INT return CHECKED_INT - def _key_press_event_handler( self, event, current_index, index_flags, state ): diff --git a/client/ayon_core/tools/loader/ui/statuses_combo.py b/client/ayon_core/tools/loader/ui/statuses_combo.py index 881de94629..5587853940 100644 --- a/client/ayon_core/tools/loader/ui/statuses_combo.py +++ b/client/ayon_core/tools/loader/ui/statuses_combo.py @@ -42,13 +42,16 @@ class StatusesQtModel(QtGui.QStandardItemModel): self.refresh(None) + def get_placeholder_text(self): + return self._placeholder + def refresh(self, project_name): # New project was selected # status filter is reset to show all statuses - check_all = False + uncheck_all = False if project_name != self._last_project: self._last_project = project_name - check_all = True + uncheck_all = True if project_name is None: self._add_select_project_item() @@ -72,14 +75,14 @@ class StatusesQtModel(QtGui.QStandardItemModel): if name in self._items_by_name: is_new = False item = self._items_by_name[name] - if check_all: - item.setCheckState(QtCore.Qt.Checked) + if uncheck_all: + item.setCheckState(QtCore.Qt.Unchecked) items_to_remove.discard(name) else: is_new = True item = QtGui.QStandardItem() item.setData(ITEM_SUBTYPE_ROLE, STATUS_ITEM_TYPE) - item.setCheckState(QtCore.Qt.Checked) + item.setCheckState(QtCore.Qt.Unchecked) item.setFlags( QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable @@ -148,8 +151,8 @@ class StatusesQtModel(QtGui.QStandardItemModel): if self._empty_statuses_item is not None: return - empty_statuses_item = QtGui.QStandardItem("No statuses..") - select_project_item = QtGui.QStandardItem("Select project..") + empty_statuses_item = QtGui.QStandardItem("No statuses...") + select_project_item = QtGui.QStandardItem("Select project...") select_all_item = QtGui.QStandardItem("Select all") deselect_all_item = QtGui.QStandardItem("Deselect all") @@ -291,9 +294,10 @@ class StatusesCombobox(CustomPaintMultiselectComboBox): model=model, parent=parent ) - self.set_placeholder_text("Statuses filter..") + self.set_placeholder_text("Statuses...") self._model = model self._last_project_name = None + self._fully_disabled_filter = False controller.register_event_callback( "selection.project.changed", @@ -310,7 +314,7 @@ class StatusesCombobox(CustomPaintMultiselectComboBox): def _on_status_filter_change(self): lines = ["Statuses filter"] - for item in self.get_all_value_info(): + for item in self.get_value_info(): status_name, enabled = item lines.append(f"{'✔' if enabled else '☐'} {status_name}") @@ -320,7 +324,6 @@ class StatusesCombobox(CustomPaintMultiselectComboBox): project_name = event["project_name"] self._last_project_name = project_name self._model.refresh(project_name) - self._on_status_filter_change() def _on_projects_refresh(self): if self._last_project_name: