converted product types view to combobox

This commit is contained in:
Jakub Trllo 2025-02-18 14:37:08 +01:00
parent ac93b5a34b
commit 5317d2817d
3 changed files with 136 additions and 220 deletions

View file

@ -1,57 +1,66 @@
from qtpy import QtWidgets, QtGui, QtCore from qtpy import QtWidgets, QtGui, QtCore
from ayon_core.tools.utils import get_qt_icon from ._multicombobox import (
CustomPaintMultiselectComboBox,
BaseQtModel,
)
STATUS_ITEM_TYPE = 0
SELECT_ALL_TYPE = 1
DESELECT_ALL_TYPE = 2
SWAP_STATE_TYPE = 3
PRODUCT_TYPE_ROLE = QtCore.Qt.UserRole + 1 PRODUCT_TYPE_ROLE = QtCore.Qt.UserRole + 1
ITEM_TYPE_ROLE = QtCore.Qt.UserRole + 2
ITEM_SUBTYPE_ROLE = QtCore.Qt.UserRole + 3
class ProductTypesQtModel(QtGui.QStandardItemModel): class ProductTypesQtModel(BaseQtModel):
refreshed = QtCore.Signal()
filter_changed = QtCore.Signal()
def __init__(self, controller): def __init__(self, controller):
super(ProductTypesQtModel, self).__init__()
self._controller = controller
self._reset_filters_on_refresh = True self._reset_filters_on_refresh = True
self._refreshing = False self._refreshing = False
self._bulk_change = False self._bulk_change = False
self._last_project = None
self._items_by_name = {} self._items_by_name = {}
controller.register_event_callback( super().__init__(
"controller.reset.finished", item_type_role=ITEM_TYPE_ROLE,
self._on_controller_reset_finish, item_subtype_role=ITEM_SUBTYPE_ROLE,
empty_values_label="No product types...",
controller=controller,
) )
def is_refreshing(self): def is_refreshing(self):
return self._refreshing return self._refreshing
def get_filter_info(self):
"""Product types filtering info.
Returns:
dict[str, bool]: Filtering value by product type name. False value
means to hide product type.
"""
return {
name: item.checkState() == QtCore.Qt.Checked
for name, item in self._items_by_name.items()
}
def refresh(self, project_name): def refresh(self, project_name):
self._refreshing = True self._refreshing = True
super().refresh(project_name)
self._reset_filters_on_refresh = False
self._refreshing = False
def reset_product_types_filter_on_refresh(self):
self._reset_filters_on_refresh = True
def _get_standard_items(self) -> list[QtGui.QStandardItem]:
return list(self._items_by_name.values())
def _clear_standard_items(self):
self._items_by_name.clear()
def _prepare_new_value_items(self, project_name: str, _: bool) -> tuple[
list[QtGui.QStandardItem], list[QtGui.QStandardItem]
]:
product_type_items = self._controller.get_product_type_items( product_type_items = self._controller.get_product_type_items(
project_name) project_name)
self._last_project = project_name self._last_project = project_name
items_to_remove = set(self._items_by_name.keys()) names_to_remove = set(self._items_by_name.keys())
new_items = [] items = []
items_filter_required = {} items_filter_required = {}
for product_type_item in product_type_items: for product_type_item in product_type_items:
name = product_type_item.name name = product_type_item.name
items_to_remove.discard(name) names_to_remove.discard(name)
item = self._items_by_name.get(name) item = self._items_by_name.get(name)
# Apply filter to new items or if filters reset is requested # Apply filter to new items or if filters reset is requested
filter_required = self._reset_filters_on_refresh filter_required = self._reset_filters_on_refresh
@ -61,15 +70,13 @@ class ProductTypesQtModel(QtGui.QStandardItemModel):
item.setData(name, PRODUCT_TYPE_ROLE) item.setData(name, PRODUCT_TYPE_ROLE)
item.setEditable(False) item.setEditable(False)
item.setCheckable(True) item.setCheckable(True)
new_items.append(item)
self._items_by_name[name] = item self._items_by_name[name] = item
items.append(item)
if filter_required: if filter_required:
items_filter_required[name] = item items_filter_required[name] = item
icon = get_qt_icon(product_type_item.icon)
item.setData(icon, QtCore.Qt.DecorationRole)
if items_filter_required: if items_filter_required:
product_types_filter = self._controller.get_product_types_filter() product_types_filter = self._controller.get_product_types_filter()
for product_type, item in items_filter_required.items(): for product_type, item in items_filter_required.items():
@ -77,180 +84,77 @@ class ProductTypesQtModel(QtGui.QStandardItemModel):
int(product_type in product_types_filter.product_types) int(product_type in product_types_filter.product_types)
+ int(product_types_filter.is_allow_list) + int(product_types_filter.is_allow_list)
) )
state = ( item.setCheckState(
QtCore.Qt.Checked QtCore.Qt.Checked
if matching % 2 == 0 if matching % 2 == 0
else QtCore.Qt.Unchecked else QtCore.Qt.Unchecked
) )
item.setCheckState(state)
root_item = self.invisibleRootItem() items_to_remove = []
if new_items: for name in names_to_remove:
root_item.appendRows(new_items) items_to_remove.append(
self._items_by_name.pop(name)
)
for name in items_to_remove: # Uncheck all if all are checked (same result)
item = self._items_by_name.pop(name) if all(
root_item.removeRow(item.row()) item.checkState() == QtCore.Qt.Checked
for item in items
):
for item in items:
item.setCheckState(QtCore.Qt.Unchecked)
self._reset_filters_on_refresh = False return items, items_to_remove
self._refreshing = False
self.refreshed.emit()
def reset_product_types_filter_on_refresh(self):
self._reset_filters_on_refresh = True
def setData(self, index, value, role=None):
checkstate_changed = False
if role is None:
role = QtCore.Qt.EditRole
elif role == QtCore.Qt.CheckStateRole:
checkstate_changed = True
output = super(ProductTypesQtModel, self).setData(index, value, role)
if checkstate_changed and not self._bulk_change:
self.filter_changed.emit()
return output
def change_state_for_all(self, checked):
if self._items_by_name:
self.change_states(checked, self._items_by_name.keys())
def change_states(self, checked, product_types):
product_types = set(product_types)
if not product_types:
return
if checked is None:
state = None
elif checked:
state = QtCore.Qt.Checked
else:
state = QtCore.Qt.Unchecked
self._bulk_change = True
changed = False
for product_type in product_types:
item = self._items_by_name.get(product_type)
if item is None:
continue
new_state = state
item_checkstate = item.checkState()
if new_state is None:
if item_checkstate == QtCore.Qt.Checked:
new_state = QtCore.Qt.Unchecked
else:
new_state = QtCore.Qt.Checked
elif item_checkstate == new_state:
continue
changed = True
item.setCheckState(new_state)
self._bulk_change = False
if changed:
self.filter_changed.emit()
def _on_controller_reset_finish(self):
self.refresh(self._last_project)
class ProductTypesView(QtWidgets.QListView): class ProductTypesCombobox(CustomPaintMultiselectComboBox):
filter_changed = QtCore.Signal()
def __init__(self, controller, parent): def __init__(self, controller, parent):
super(ProductTypesView, self).__init__(parent) self._controller = controller
model = ProductTypesQtModel(controller)
self.setSelectionMode( super().__init__(
QtWidgets.QAbstractItemView.ExtendedSelection PRODUCT_TYPE_ROLE,
PRODUCT_TYPE_ROLE,
QtCore.Qt.ForegroundRole,
QtCore.Qt.DecorationRole,
item_type_role=ITEM_TYPE_ROLE,
model=model,
parent=parent
) )
self.setAlternatingRowColors(True) self.set_placeholder_text("Product types filter...")
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self._model = model
self._last_project_name = None
product_types_model = ProductTypesQtModel(controller) self._fully_disabled_filter = False
product_types_proxy_model = QtCore.QSortFilterProxyModel()
product_types_proxy_model.setSourceModel(product_types_model)
self.setModel(product_types_proxy_model)
product_types_model.refreshed.connect(self._on_refresh_finished)
product_types_model.filter_changed.connect(self._on_filter_change)
self.customContextMenuRequested.connect(self._on_context_menu)
controller.register_event_callback( controller.register_event_callback(
"selection.project.changed", "selection.project.changed",
self._on_project_change self._on_project_change
) )
controller.register_event_callback(
self._controller = controller "projects.refresh.finished",
self._refresh_product_types_filter = False self._on_projects_refresh
)
self._product_types_model = product_types_model self.setToolTip("Product types filter")
self._product_types_proxy_model = product_types_proxy_model self.value_changed.connect(
self._on_product_type_filter_change
def get_filter_info(self): )
return self._product_types_model.get_filter_info()
def reset_product_types_filter_on_refresh(self): def reset_product_types_filter_on_refresh(self):
self._product_types_model.reset_product_types_filter_on_refresh() self._model.reset_product_types_filter_on_refresh()
def _on_product_type_filter_change(self):
lines = ["Product types filter"]
for item in self.get_value_info():
status_name, enabled = item
lines.append(f"{'' if enabled else ''} {status_name}")
self.setToolTip("\n".join(lines))
def _on_project_change(self, event): def _on_project_change(self, event):
project_name = event["project_name"] project_name = event["project_name"]
self._product_types_model.refresh(project_name) self._last_project_name = project_name
self._model.refresh(project_name)
def _on_refresh_finished(self): def _on_projects_refresh(self):
# Apply product types filter on first show if self._last_project_name:
self.filter_changed.emit() self._model.refresh(self._last_project_name)
self._on_product_type_filter_change()
def _on_filter_change(self):
if not self._product_types_model.is_refreshing():
self.filter_changed.emit()
def _change_selection_state(self, checkstate):
selection_model = self.selectionModel()
product_types = {
index.data(PRODUCT_TYPE_ROLE)
for index in selection_model.selectedIndexes()
}
product_types.discard(None)
self._product_types_model.change_states(checkstate, product_types)
def _on_enable_all(self):
self._product_types_model.change_state_for_all(True)
def _on_disable_all(self):
self._product_types_model.change_state_for_all(False)
def _on_context_menu(self, pos):
menu = QtWidgets.QMenu(self)
# Add enable all action
action_check_all = QtWidgets.QAction(menu)
action_check_all.setText("Enable All")
action_check_all.triggered.connect(self._on_enable_all)
# Add disable all action
action_uncheck_all = QtWidgets.QAction(menu)
action_uncheck_all.setText("Disable All")
action_uncheck_all.triggered.connect(self._on_disable_all)
menu.addAction(action_check_all)
menu.addAction(action_uncheck_all)
# Get mouse position
global_pos = self.viewport().mapToGlobal(pos)
menu.exec_(global_pos)
def event(self, event):
if event.type() == QtCore.QEvent.KeyPress:
if event.key() == QtCore.Qt.Key_Space:
self._change_selection_state(None)
return True
if event.key() == QtCore.Qt.Key_Backspace:
self._change_selection_state(False)
return True
if event.key() == QtCore.Qt.Key_Return:
self._change_selection_state(True)
return True
return super(ProductTypesView, self).event(event)

View file

@ -1,4 +1,6 @@
from __future__ import annotations
import collections import collections
from typing import Optional
from qtpy import QtWidgets, QtCore from qtpy import QtWidgets, QtCore
@ -36,7 +38,7 @@ class ProductsProxyModel(RecursiveSortFilterProxyModel):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self._product_type_filters = {} self._product_type_filters = None
self._statuses_filter = None self._statuses_filter = None
self._ascending_sort = True self._ascending_sort = True
@ -46,6 +48,8 @@ class ProductsProxyModel(RecursiveSortFilterProxyModel):
return set(self._statuses_filter) return set(self._statuses_filter)
def set_product_type_filters(self, product_type_filters): def set_product_type_filters(self, product_type_filters):
if self._product_type_filters == product_type_filters:
return
self._product_type_filters = product_type_filters self._product_type_filters = product_type_filters
self.invalidateFilter() self.invalidateFilter()
@ -59,28 +63,32 @@ class ProductsProxyModel(RecursiveSortFilterProxyModel):
source_model = self.sourceModel() source_model = self.sourceModel()
index = source_model.index(source_row, 0, source_parent) index = source_model.index(source_row, 0, source_parent)
product_types_s = source_model.data(index, PRODUCT_TYPE_ROLE) if not self._accept_row_by_role_value(
product_types = [] index, self._product_type_filters, PRODUCT_TYPE_ROLE
if product_types_s: ):
product_types = product_types_s.split("|")
for product_type in product_types:
if not self._product_type_filters.get(product_type, True):
return False
if not self._accept_row_by_statuses(index):
return False return False
if not self._accept_row_by_role_value(
index, self._statuses_filter, STATUS_NAME_FILTER_ROLE
):
return False
return super().filterAcceptsRow(source_row, source_parent) return super().filterAcceptsRow(source_row, source_parent)
def _accept_row_by_statuses(self, index): def _accept_row_by_role_value(
if self._statuses_filter is None: self,
index: QtCore.QModelIndex,
filter_value: Optional[set[str]],
role: int
):
if filter_value is None:
return True return True
if not self._statuses_filter: if not filter_value:
return False return False
status_s = index.data(STATUS_NAME_FILTER_ROLE) status_s = index.data(role)
for status in status_s.split("|"): for status in status_s.split("|"):
if status in self._statuses_filter: if status in filter_value:
return True return True
return False return False
@ -120,7 +128,7 @@ class ProductsWidget(QtWidgets.QWidget):
90, # Product type 90, # Product type
130, # Folder label 130, # Folder label
60, # Version 60, # Version
100, # Status 100, # Status
125, # Time 125, # Time
75, # Author 75, # Author
75, # Frames 75, # Frames

View file

@ -15,7 +15,7 @@ from ayon_core.tools.loader.control import LoaderController
from .folders_widget import LoaderFoldersWidget from .folders_widget import LoaderFoldersWidget
from .products_widget import ProductsWidget from .products_widget import ProductsWidget
from .product_types_widget import ProductTypesView from .product_types_widget import ProductTypesCombobox
from .product_group_dialog import ProductGroupDialog from .product_group_dialog import ProductGroupDialog
from .info_widget import InfoWidget from .info_widget import InfoWidget
from .repres_widget import RepresentationsWidget from .repres_widget import RepresentationsWidget
@ -164,8 +164,6 @@ class LoaderWindow(QtWidgets.QWidget):
folders_widget = LoaderFoldersWidget(controller, context_widget) folders_widget = LoaderFoldersWidget(controller, context_widget)
product_types_widget = ProductTypesView(controller, context_splitter)
context_layout = QtWidgets.QVBoxLayout(context_widget) context_layout = QtWidgets.QVBoxLayout(context_widget)
context_layout.setContentsMargins(0, 0, 0, 0) context_layout.setContentsMargins(0, 0, 0, 0)
context_layout.addWidget(context_top_widget, 0) context_layout.addWidget(context_top_widget, 0)
@ -173,7 +171,6 @@ class LoaderWindow(QtWidgets.QWidget):
context_layout.addWidget(folders_widget, 1) context_layout.addWidget(folders_widget, 1)
context_splitter.addWidget(context_widget) context_splitter.addWidget(context_widget)
context_splitter.addWidget(product_types_widget)
context_splitter.setStretchFactor(0, 65) context_splitter.setStretchFactor(0, 65)
context_splitter.setStretchFactor(1, 35) context_splitter.setStretchFactor(1, 35)
@ -185,6 +182,10 @@ class LoaderWindow(QtWidgets.QWidget):
products_filter_input = PlaceholderLineEdit(products_inputs_widget) products_filter_input = PlaceholderLineEdit(products_inputs_widget)
products_filter_input.setPlaceholderText("Product name filter...") products_filter_input.setPlaceholderText("Product name filter...")
product_types_filter_combo = ProductTypesCombobox(
controller, products_inputs_widget
)
product_status_filter_combo = StatusesCombobox(controller, self) product_status_filter_combo = StatusesCombobox(controller, self)
product_group_checkbox = QtWidgets.QCheckBox( product_group_checkbox = QtWidgets.QCheckBox(
@ -196,6 +197,7 @@ class LoaderWindow(QtWidgets.QWidget):
products_inputs_layout = QtWidgets.QHBoxLayout(products_inputs_widget) products_inputs_layout = QtWidgets.QHBoxLayout(products_inputs_widget)
products_inputs_layout.setContentsMargins(0, 0, 0, 0) products_inputs_layout.setContentsMargins(0, 0, 0, 0)
products_inputs_layout.addWidget(products_filter_input, 1) products_inputs_layout.addWidget(products_filter_input, 1)
products_inputs_layout.addWidget(product_types_filter_combo, 1)
products_inputs_layout.addWidget(product_status_filter_combo, 1) products_inputs_layout.addWidget(product_status_filter_combo, 1)
products_inputs_layout.addWidget(product_group_checkbox, 0) products_inputs_layout.addWidget(product_group_checkbox, 0)
@ -244,12 +246,12 @@ class LoaderWindow(QtWidgets.QWidget):
folders_filter_input.textChanged.connect( folders_filter_input.textChanged.connect(
self._on_folder_filter_change self._on_folder_filter_change
) )
product_types_widget.filter_changed.connect(
self._on_product_type_filter_change
)
products_filter_input.textChanged.connect( products_filter_input.textChanged.connect(
self._on_product_filter_change self._on_product_filter_change
) )
product_types_filter_combo.value_changed.connect(
self._on_product_type_filter_change
)
product_status_filter_combo.value_changed.connect( product_status_filter_combo.value_changed.connect(
self._on_status_filter_change self._on_status_filter_change
) )
@ -304,9 +306,8 @@ class LoaderWindow(QtWidgets.QWidget):
self._folders_filter_input = folders_filter_input self._folders_filter_input = folders_filter_input
self._folders_widget = folders_widget self._folders_widget = folders_widget
self._product_types_widget = product_types_widget
self._products_filter_input = products_filter_input self._products_filter_input = products_filter_input
self._product_types_filter_combo = product_types_filter_combo
self._product_status_filter_combo = product_status_filter_combo self._product_status_filter_combo = product_status_filter_combo
self._product_group_checkbox = product_group_checkbox self._product_group_checkbox = product_group_checkbox
self._products_widget = products_widget self._products_widget = products_widget
@ -335,7 +336,7 @@ class LoaderWindow(QtWidgets.QWidget):
self._controller.reset() self._controller.reset()
def showEvent(self, event): def showEvent(self, event):
super(LoaderWindow, self).showEvent(event) super().showEvent(event)
if self._first_show: if self._first_show:
self._on_first_show() self._on_first_show()
@ -343,9 +344,13 @@ class LoaderWindow(QtWidgets.QWidget):
self._show_timer.start() self._show_timer.start()
def closeEvent(self, event): def closeEvent(self, event):
super(LoaderWindow, self).closeEvent(event) super().closeEvent(event)
self._product_types_widget.reset_product_types_filter_on_refresh() (
self
._product_types_filter_combo
.reset_product_types_filter_on_refresh()
)
self._reset_on_show = True self._reset_on_show = True
@ -363,7 +368,7 @@ class LoaderWindow(QtWidgets.QWidget):
event.setAccepted(True) event.setAccepted(True)
return return
super(LoaderWindow, self).keyPressEvent(event) super().keyPressEvent(event)
def _on_first_show(self): def _on_first_show(self):
self._first_show = False self._first_show = False
@ -428,9 +433,8 @@ class LoaderWindow(QtWidgets.QWidget):
self._products_widget.set_statuses_filter(status_names) self._products_widget.set_statuses_filter(status_names)
def _on_product_type_filter_change(self): def _on_product_type_filter_change(self):
self._products_widget.set_product_type_filter( product_types = self._product_types_filter_combo.get_value()
self._product_types_widget.get_filter_info() self._products_widget.set_product_type_filter(product_types)
)
def _on_merged_products_selection_change(self): def _on_merged_products_selection_change(self):
items = self._products_widget.get_selected_merged_products() items = self._products_widget.get_selected_merged_products()