mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
Merge pull request #5547 from ynput/feature/multiselection-enum-def
Attribute Definitions: Multiselection enum def
This commit is contained in:
commit
0463124867
5 changed files with 145 additions and 57 deletions
|
|
@ -424,17 +424,25 @@ class TextDef(AbstractAttrDef):
|
|||
|
||||
|
||||
class EnumDef(AbstractAttrDef):
|
||||
"""Enumeration of single item from items.
|
||||
"""Enumeration of items.
|
||||
|
||||
Enumeration of single item from items. Or list of items if multiselection
|
||||
is enabled.
|
||||
|
||||
Args:
|
||||
items: Items definition that can be converted using
|
||||
'prepare_enum_items'.
|
||||
default: Default value. Must be one key(value) from passed items.
|
||||
items (Union[list[str], list[dict[str, Any]]): Items definition that
|
||||
can be converted using 'prepare_enum_items'.
|
||||
default (Optional[Any]): Default value. Must be one key(value) from
|
||||
passed items or list of values for multiselection.
|
||||
multiselection (Optional[bool]): If True, multiselection is allowed.
|
||||
Output is list of selected items.
|
||||
"""
|
||||
|
||||
type = "enum"
|
||||
|
||||
def __init__(self, key, items, default=None, **kwargs):
|
||||
def __init__(
|
||||
self, key, items, default=None, multiselection=False, **kwargs
|
||||
):
|
||||
if not items:
|
||||
raise ValueError((
|
||||
"Empty 'items' value. {} must have"
|
||||
|
|
@ -443,30 +451,44 @@ class EnumDef(AbstractAttrDef):
|
|||
|
||||
items = self.prepare_enum_items(items)
|
||||
item_values = [item["value"] for item in items]
|
||||
if default not in item_values:
|
||||
for value in item_values:
|
||||
default = value
|
||||
break
|
||||
item_values_set = set(item_values)
|
||||
if multiselection:
|
||||
if default is None:
|
||||
default = []
|
||||
default = list(item_values_set.intersection(default))
|
||||
|
||||
elif default not in item_values:
|
||||
default = next(iter(item_values), None)
|
||||
|
||||
super(EnumDef, self).__init__(key, default=default, **kwargs)
|
||||
|
||||
self.items = items
|
||||
self._item_values = set(item_values)
|
||||
self._item_values = item_values_set
|
||||
self.multiselection = multiselection
|
||||
|
||||
def __eq__(self, other):
|
||||
if not super(EnumDef, self).__eq__(other):
|
||||
return False
|
||||
|
||||
return self.items == other.items
|
||||
return (
|
||||
self.items == other.items
|
||||
and self.multiselection == other.multiselection
|
||||
)
|
||||
|
||||
def convert_value(self, value):
|
||||
if value in self._item_values:
|
||||
return value
|
||||
return self.default
|
||||
if not self.multiselection:
|
||||
if value in self._item_values:
|
||||
return value
|
||||
return self.default
|
||||
|
||||
if value is None:
|
||||
return copy.deepcopy(self.default)
|
||||
return list(self._item_values.intersection(value))
|
||||
|
||||
def serialize(self):
|
||||
data = super(EnumDef, self).serialize()
|
||||
data["items"] = copy.deepcopy(self.items)
|
||||
data["multiselection"] = self.multiselection
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from openpype.tools.utils import (
|
|||
CustomTextComboBox,
|
||||
FocusSpinBox,
|
||||
FocusDoubleSpinBox,
|
||||
MultiSelectionComboBox,
|
||||
)
|
||||
from openpype.widgets.nice_checkbox import NiceCheckbox
|
||||
|
||||
|
|
@ -412,10 +413,19 @@ class EnumAttrWidget(_BaseAttrDefWidget):
|
|||
self._multivalue = False
|
||||
super(EnumAttrWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def multiselection(self):
|
||||
return self.attr_def.multiselection
|
||||
|
||||
def _ui_init(self):
|
||||
input_widget = CustomTextComboBox(self)
|
||||
combo_delegate = QtWidgets.QStyledItemDelegate(input_widget)
|
||||
input_widget.setItemDelegate(combo_delegate)
|
||||
if self.multiselection:
|
||||
input_widget = MultiSelectionComboBox(self)
|
||||
|
||||
else:
|
||||
input_widget = CustomTextComboBox(self)
|
||||
combo_delegate = QtWidgets.QStyledItemDelegate(input_widget)
|
||||
input_widget.setItemDelegate(combo_delegate)
|
||||
self._combo_delegate = combo_delegate
|
||||
|
||||
if self.attr_def.tooltip:
|
||||
input_widget.setToolTip(self.attr_def.tooltip)
|
||||
|
|
@ -427,9 +437,11 @@ class EnumAttrWidget(_BaseAttrDefWidget):
|
|||
if idx >= 0:
|
||||
input_widget.setCurrentIndex(idx)
|
||||
|
||||
input_widget.currentIndexChanged.connect(self._on_value_change)
|
||||
if self.multiselection:
|
||||
input_widget.value_changed.connect(self._on_value_change)
|
||||
else:
|
||||
input_widget.currentIndexChanged.connect(self._on_value_change)
|
||||
|
||||
self._combo_delegate = combo_delegate
|
||||
self._input_widget = input_widget
|
||||
|
||||
self.main_layout.addWidget(input_widget, 0)
|
||||
|
|
@ -442,17 +454,40 @@ class EnumAttrWidget(_BaseAttrDefWidget):
|
|||
self.value_changed.emit(new_value, self.attr_def.id)
|
||||
|
||||
def current_value(self):
|
||||
if self.multiselection:
|
||||
return self._input_widget.value()
|
||||
idx = self._input_widget.currentIndex()
|
||||
return self._input_widget.itemData(idx)
|
||||
|
||||
def _multiselection_multivalue_prep(self, values):
|
||||
final = None
|
||||
multivalue = False
|
||||
for value in values:
|
||||
value = set(value)
|
||||
if final is None:
|
||||
final = value
|
||||
elif multivalue or final != value:
|
||||
final |= value
|
||||
multivalue = True
|
||||
return list(final), multivalue
|
||||
|
||||
def set_value(self, value, multivalue=False):
|
||||
if multivalue:
|
||||
set_value = set(value)
|
||||
if len(set_value) == 1:
|
||||
multivalue = False
|
||||
value = tuple(set_value)[0]
|
||||
if self.multiselection:
|
||||
value, multivalue = self._multiselection_multivalue_prep(
|
||||
value)
|
||||
else:
|
||||
set_value = set(value)
|
||||
if len(set_value) == 1:
|
||||
multivalue = False
|
||||
value = tuple(set_value)[0]
|
||||
|
||||
if not multivalue:
|
||||
if self.multiselection:
|
||||
self._input_widget.blockSignals(True)
|
||||
self._input_widget.set_value(value)
|
||||
self._input_widget.blockSignals(False)
|
||||
|
||||
elif not multivalue:
|
||||
idx = self._input_widget.findData(value)
|
||||
cur_idx = self._input_widget.currentIndex()
|
||||
if idx != cur_idx and idx >= 0:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from qtpy import QtWidgets, QtCore, QtGui
|
|||
|
||||
from openpype.widgets.sliders import NiceSlider
|
||||
from openpype.tools.settings import CHILD_OFFSET
|
||||
from openpype.tools.utils import MultiSelectionComboBox
|
||||
from openpype.settings.entities.exceptions import BaseInvalidValue
|
||||
|
||||
from .widgets import (
|
||||
|
|
@ -15,7 +16,6 @@ from .widgets import (
|
|||
SettingsNiceCheckbox,
|
||||
SettingsLineEdit
|
||||
)
|
||||
from .multiselection_combobox import MultiSelectionComboBox
|
||||
from .wrapper_widgets import (
|
||||
WrapperWidget,
|
||||
CollapsibleWrapper,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ from .models import (
|
|||
from .overlay_messages import (
|
||||
MessageOverlayObject,
|
||||
)
|
||||
from .multiselection_combobox import MultiSelectionComboBox
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
|
@ -78,4 +79,6 @@ __all__ = (
|
|||
"RecursiveSortFilterProxyModel",
|
||||
|
||||
"MessageOverlayObject",
|
||||
|
||||
"MultiSelectionComboBox",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
from openpype.tools.utils.lib import (
|
||||
|
||||
from .lib import (
|
||||
checkstate_int_to_enum,
|
||||
checkstate_enum_to_int,
|
||||
)
|
||||
from openpype.tools.utils.constants import (
|
||||
from .constants import (
|
||||
CHECKED_INT,
|
||||
UNCHECKED_INT,
|
||||
ITEM_IS_USER_TRISTATE,
|
||||
|
|
@ -60,12 +61,25 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
|
|||
self._block_mouse_release_timer = QtCore.QTimer(self, singleShot=True)
|
||||
self._initial_mouse_pos = None
|
||||
self._separator = separator
|
||||
self.placeholder_text = placeholder
|
||||
self.delegate = ComboItemDelegate(self)
|
||||
self.setItemDelegate(self.delegate)
|
||||
self._placeholder_text = placeholder
|
||||
delegate = ComboItemDelegate(self)
|
||||
self.setItemDelegate(delegate)
|
||||
|
||||
self.lines = {}
|
||||
self.item_height = None
|
||||
self._lines = {}
|
||||
self._item_height = None
|
||||
self._custom_text = None
|
||||
self._delegate = delegate
|
||||
|
||||
def get_placeholder_text(self):
|
||||
return self._placeholder_text
|
||||
|
||||
def set_placeholder_text(self, text):
|
||||
self._placeholder_text = text
|
||||
self._update_size_hint()
|
||||
|
||||
def set_custom_text(self, text):
|
||||
self._custom_text = text
|
||||
self._update_size_hint()
|
||||
|
||||
def focusInEvent(self, event):
|
||||
self.focused_in.emit()
|
||||
|
|
@ -158,7 +172,7 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
|
|||
if new_state is not None:
|
||||
model.setData(current_index, new_state, QtCore.Qt.CheckStateRole)
|
||||
self.view().update(current_index)
|
||||
self.update_size_hint()
|
||||
self._update_size_hint()
|
||||
self.value_changed.emit()
|
||||
return True
|
||||
|
||||
|
|
@ -182,25 +196,33 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
|
|||
self.initStyleOption(option)
|
||||
painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, option)
|
||||
|
||||
# draw the icon and text
|
||||
items = self.checked_items_text()
|
||||
if not items:
|
||||
option.currentText = self.placeholder_text
|
||||
# draw the icon and text
|
||||
draw_text = True
|
||||
combotext = None
|
||||
if self._custom_text is not None:
|
||||
combotext = self._custom_text
|
||||
elif not items:
|
||||
combotext = self._placeholder_text
|
||||
else:
|
||||
draw_text = False
|
||||
if draw_text:
|
||||
option.currentText = combotext
|
||||
option.palette.setCurrentColorGroup(QtGui.QPalette.Disabled)
|
||||
painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, option)
|
||||
return
|
||||
|
||||
font_metricts = self.fontMetrics()
|
||||
|
||||
if self.item_height is None:
|
||||
if self._item_height is None:
|
||||
self.updateGeometry()
|
||||
self.update()
|
||||
return
|
||||
|
||||
for line, items in self.lines.items():
|
||||
for line, items in self._lines.items():
|
||||
top_y = (
|
||||
option.rect.top()
|
||||
+ (line * self.item_height)
|
||||
+ (line * self._item_height)
|
||||
+ self.top_bottom_margins
|
||||
)
|
||||
left_x = option.rect.left() + self.left_offset
|
||||
|
|
@ -210,7 +232,7 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
|
|||
|
||||
label_rect.moveTop(top_y)
|
||||
label_rect.moveLeft(left_x)
|
||||
label_rect.setHeight(self.item_height)
|
||||
label_rect.setHeight(self._item_height)
|
||||
label_rect.setWidth(
|
||||
label_rect.width() + self.left_right_padding
|
||||
)
|
||||
|
|
@ -239,14 +261,18 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
|
|||
|
||||
def resizeEvent(self, *args, **kwargs):
|
||||
super(MultiSelectionComboBox, self).resizeEvent(*args, **kwargs)
|
||||
self.update_size_hint()
|
||||
self._update_size_hint()
|
||||
|
||||
def update_size_hint(self):
|
||||
self.lines = {}
|
||||
def _update_size_hint(self):
|
||||
if self._custom_text is not None:
|
||||
self.update()
|
||||
return
|
||||
self._lines = {}
|
||||
|
||||
items = self.checked_items_text()
|
||||
if not items:
|
||||
self.update()
|
||||
self.repaint()
|
||||
return
|
||||
|
||||
option = QtWidgets.QStyleOptionComboBox()
|
||||
|
|
@ -259,7 +285,7 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
|
|||
total_width = option.rect.width() - btn_rect.width()
|
||||
|
||||
line = 0
|
||||
self.lines = {line: []}
|
||||
self._lines = {line: []}
|
||||
|
||||
font_metricts = self.fontMetrics()
|
||||
default_left_x = 0 + self.left_offset
|
||||
|
|
@ -270,18 +296,18 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
|
|||
right_x = left_x + width
|
||||
if right_x > total_width:
|
||||
left_x = int(default_left_x)
|
||||
if self.lines.get(line):
|
||||
if self._lines.get(line):
|
||||
line += 1
|
||||
self.lines[line] = [item]
|
||||
self._lines[line] = [item]
|
||||
left_x += width
|
||||
else:
|
||||
self.lines[line] = [item]
|
||||
self._lines[line] = [item]
|
||||
line += 1
|
||||
else:
|
||||
if line in self.lines:
|
||||
self.lines[line].append(item)
|
||||
if line in self._lines:
|
||||
self._lines[line].append(item)
|
||||
else:
|
||||
self.lines[line] = [item]
|
||||
self._lines[line] = [item]
|
||||
left_x = left_x + width + self.item_spacing
|
||||
|
||||
self.update()
|
||||
|
|
@ -289,18 +315,20 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
|
|||
|
||||
def sizeHint(self):
|
||||
value = super(MultiSelectionComboBox, self).sizeHint()
|
||||
lines = len(self.lines)
|
||||
if lines == 0:
|
||||
lines = 1
|
||||
lines = 1
|
||||
if self._custom_text is None:
|
||||
lines = len(self._lines)
|
||||
if lines == 0:
|
||||
lines = 1
|
||||
|
||||
if self.item_height is None:
|
||||
self.item_height = (
|
||||
if self._item_height is None:
|
||||
self._item_height = (
|
||||
self.fontMetrics().height()
|
||||
+ (2 * self.top_bottom_padding)
|
||||
+ (2 * self.top_bottom_margins)
|
||||
)
|
||||
value.setHeight(
|
||||
(lines * self.item_height)
|
||||
(lines * self._item_height)
|
||||
+ (2 * self.top_bottom_margins)
|
||||
)
|
||||
return value
|
||||
|
|
@ -316,7 +344,7 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
|
|||
else:
|
||||
check_state = UNCHECKED_INT
|
||||
self.setItemData(idx, check_state, QtCore.Qt.CheckStateRole)
|
||||
self.update_size_hint()
|
||||
self._update_size_hint()
|
||||
|
||||
def value(self):
|
||||
items = list()
|
||||
Loading…
Add table
Add a link
Reference in a new issue