added few new attribute definitions and their widgets

This commit is contained in:
iLLiCiTiT 2022-01-18 17:16:32 +01:00
parent f28b4242c8
commit 396bdfde8a
9 changed files with 919 additions and 7 deletions

View file

@ -1,18 +1,30 @@
from .attribute_definitions import (
AbtractAttrDef,
UIDef,
UISeparatorDef,
UILabelDef,
UnknownDef,
NumberDef,
TextDef,
EnumDef,
BoolDef
BoolDef,
FileDef,
)
__all__ = (
"AbtractAttrDef",
"UIDef",
"UISeparatorDef",
"UILabelDef",
"UnknownDef",
"NumberDef",
"TextDef",
"EnumDef",
"BoolDef"
"BoolDef",
"FileDef",
)

View file

@ -38,13 +38,19 @@ class AbtractAttrDef:
key(str): Under which key will be attribute value stored.
label(str): Attribute label.
tooltip(str): Attribute tooltip.
is_label_horizontal(bool): UI specific argument. Specify if label is
next to value input or ahead.
"""
is_value_def = True
def __init__(self, key, default, label=None, tooltip=None):
def __init__(
self, key, default, label=None, tooltip=None, is_label_horizontal=None
):
self.key = key
self.label = label
self.tooltip = tooltip
self.default = default
self.is_label_horizontal = is_label_horizontal
self._id = uuid.uuid4()
self.__init__class__ = AbtractAttrDef
@ -68,8 +74,39 @@ class AbtractAttrDef:
pass
# -----------------------------------------
# UI attribute definitoins won't hold value
# -----------------------------------------
class UIDef(AbtractAttrDef):
is_value_def = False
def __init__(self, key=None, default=None, *args, **kwargs):
super(UIDef, self).__init__(key, default, *args, **kwargs)
def convert_value(self, value):
return value
class UISeparatorDef(UIDef):
pass
class UILabelDef(UIDef):
def __init__(self, label):
super(UILabelDef, self).__init__(label=label)
# ---------------------------------------
# Attribute defintioins should hold value
# ---------------------------------------
class UnknownDef(AbtractAttrDef):
"""Definition is not known because definition is not available."""
"""Definition is not known because definition is not available.
This attribute can be used to keep existing data unchanged but does not
have known definition of type.
"""
def __init__(self, key, default=None, **kwargs):
kwargs["default"] = default
super(UnknownDef, self).__init__(key, **kwargs)
@ -261,3 +298,87 @@ class BoolDef(AbtractAttrDef):
if isinstance(value, bool):
return value
return self.default
class FileDef(AbtractAttrDef):
"""File definition.
It is possible to define filters of allowed file extensions and if supports
folders.
Args:
multipath(bool): Allow multiple path.
folders(bool): Allow folder paths.
extensions(list<str>): Allow files with extensions. Empty list will
allow all extensions and None will disable files completely.
default(str, list<str>): Defautl value.
"""
def __init__(
self, key, multipath=False, folders=None, extensions=None,
default=None, **kwargs
):
if folders is None and extensions is None:
folders = True
extensions = []
if default is None:
if multipath:
default = []
else:
default = ""
else:
if multipath:
if not isinstance(default, (tuple, list, set)):
raise TypeError((
"'default' argument must be 'list', 'tuple' or 'set'"
", not '{}'"
).format(type(default)))
else:
if not isinstance(default, six.string_types):
raise TypeError((
"'default' argument must be 'str' not '{}'"
).format(type(default)))
default = default.strip()
# Change horizontal label
is_label_horizontal = kwargs.get("is_label_horizontal")
if is_label_horizontal is None and multipath:
kwargs["is_label_horizontal"] = False
self.multipath = multipath
self.folders = folders
self.extensions = extensions
super(FileDef, self).__init__(key, default=default, **kwargs)
def __eq__(self, other):
if not super(FileDef, self).__eq__(other):
return False
return (
self.multipath == other.multipath
and self.folders == other.folders
and self.extensions == other.extensions
)
def convert_value(self, value):
if isinstance(value, six.string_types):
if self.multipath:
value = [value.strip()]
else:
value = value.strip()
return value
if isinstance(value, (tuple, list, set)):
_value = []
for item in value:
if isinstance(item, six.string_types):
_value.append(item.strip())
if self.multipath:
return _value
if not _value:
return self.default
return _value[0].strip()
return str(value).strip()

View file

@ -0,0 +1,45 @@
import os
from Qt import QtGui
def get_icon_path(icon_name=None, filename=None):
"""Path to image in './images' folder."""
if icon_name is None and filename is None:
return None
if filename is None:
filename = "{}.png".format(icon_name)
path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"images",
filename
)
if os.path.exists(path):
return path
return None
def get_image(icon_name=None, filename=None):
"""Load image from './images' as QImage."""
path = get_icon_path(icon_name, filename)
if path:
return QtGui.QImage(path)
return None
def get_pixmap(icon_name=None, filename=None):
"""Load image from './images' as QPixmap."""
path = get_icon_path(icon_name, filename)
if path:
return QtGui.QPixmap(path)
return None
def get_icon(icon_name=None, filename=None):
"""Load image from './images' as QICon."""
pix = get_pixmap(icon_name, filename)
if pix:
return QtGui.QIcon(pix)
return None

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,645 @@
import os
import collections
import uuid
import clique
from Qt import QtWidgets, QtCore, QtGui
from openpype.tools.utils import paint_image_with_color
# TODO change imports
from openpype.tools.resources import (
get_pixmap,
get_image,
)
from openpype.tools.utils import (
IconButton,
PixmapLabel
)
ITEM_ID_ROLE = QtCore.Qt.UserRole + 1
ITEM_LABEL_ROLE = QtCore.Qt.UserRole + 2
ITEM_ICON_ROLE = QtCore.Qt.UserRole + 3
FILENAMES_ROLE = QtCore.Qt.UserRole + 4
DIRPATH_ROLE = QtCore.Qt.UserRole + 5
IS_DIR_ROLE = QtCore.Qt.UserRole + 6
EXT_ROLE = QtCore.Qt.UserRole + 7
class DropEmpty(QtWidgets.QWidget):
_drop_enabled_text = "Drag & Drop\n(drop files here)"
def __init__(self, parent):
super(DropEmpty, self).__init__(parent)
label_widget = QtWidgets.QLabel(self._drop_enabled_text, self)
label_widget.setAlignment(QtCore.Qt.AlignCenter)
label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addSpacing(10)
layout.addWidget(
label_widget,
alignment=QtCore.Qt.AlignCenter
)
layout.addSpacing(10)
self._label_widget = label_widget
def paintEvent(self, event):
super(DropEmpty, self).paintEvent(event)
painter = QtGui.QPainter(self)
pen = QtGui.QPen()
pen.setWidth(1)
pen.setBrush(QtCore.Qt.darkGray)
pen.setStyle(QtCore.Qt.DashLine)
painter.setPen(pen)
content_margins = self.layout().contentsMargins()
left_m = content_margins.left()
top_m = content_margins.top()
rect = QtCore.QRect(
left_m,
top_m,
(
self.rect().width()
- (left_m + content_margins.right() + pen.width())
),
(
self.rect().height()
- (top_m + content_margins.bottom() + pen.width())
)
)
painter.drawRect(rect)
class FilesModel(QtGui.QStandardItemModel):
sequence_exts = [
".ani", ".anim", ".apng", ".art", ".bmp", ".bpg", ".bsave", ".cal",
".cin", ".cpc", ".cpt", ".dds", ".dpx", ".ecw", ".exr", ".fits",
".flic", ".flif", ".fpx", ".gif", ".hdri", ".hevc", ".icer",
".icns", ".ico", ".cur", ".ics", ".ilbm", ".jbig", ".jbig2",
".jng", ".jpeg", ".jpeg-ls", ".2000", ".jpg", ".xr",
".jpeg-hdr", ".kra", ".mng", ".miff", ".nrrd",
".ora", ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf",
".pictor", ".png", ".psb", ".psp", ".qtvr", ".ras",
".rgbe", ".logluv", ".tiff", ".sgi", ".tga", ".tiff", ".tiff/ep",
".tiff/it", ".ufo", ".ufp", ".wbmp", ".webp", ".xbm", ".xcf",
".xpm", ".xwd"
]
def __init__(self):
super(FilesModel, self).__init__()
self._filenames_by_dirpath = collections.defaultdict(set)
self._items_by_dirpath = collections.defaultdict(list)
def add_filepaths(self, filepaths):
if not filepaths:
return
new_dirpaths = set()
for filepath in filepaths:
filename = os.path.basename(filepath)
dirpath = os.path.dirname(filepath)
filenames = self._filenames_by_dirpath[dirpath]
if filename not in filenames:
new_dirpaths.add(dirpath)
filenames.add(filename)
self._refresh_items(new_dirpaths)
def remove_item_by_ids(self, item_ids):
if not item_ids:
return
remaining_ids = set(item_ids)
result = collections.defaultdict(list)
for dirpath, items in self._items_by_dirpath.items():
if not remaining_ids:
break
for item in items:
if not remaining_ids:
break
item_id = item.data(ITEM_ID_ROLE)
if item_id in remaining_ids:
remaining_ids.remove(item_id)
result[dirpath].append(item)
if not result:
return
dirpaths = set(result.keys())
for dirpath, items in result.items():
filenames_cache = self._filenames_by_dirpath[dirpath]
for item in items:
filenames = item.data(FILENAMES_ROLE)
self._items_by_dirpath[dirpath].remove(item)
self.removeRows(item.row(), 1)
for filename in filenames:
if filename in filenames_cache:
filenames_cache.remove(filename)
self._refresh_items(dirpaths)
def _refresh_items(self, dirpaths=None):
if dirpaths is None:
dirpaths = set(self._items_by_dirpath.keys())
new_items = []
for dirpath in dirpaths:
items_to_remove = list(self._items_by_dirpath[dirpath])
cols, remainders = clique.assemble(
self._filenames_by_dirpath[dirpath]
)
filtered_cols = []
for collection in cols:
filenames = set(collection)
valid_col = True
for filename in filenames:
ext = os.path.splitext(filename)[-1]
valid_col = ext in self.sequence_exts
break
if valid_col:
filtered_cols.append(collection)
else:
for filename in filenames:
remainders.append(filename)
for filename in remainders:
found = False
for item in items_to_remove:
item_filenames = item.data(FILENAMES_ROLE)
if filename in item_filenames and len(item_filenames) == 1:
found = True
items_to_remove.remove(item)
break
if found:
continue
fullpath = os.path.join(dirpath, filename)
if os.path.isdir(fullpath):
icon_pixmap = get_pixmap(filename="folder.png")
else:
icon_pixmap = get_pixmap(filename="file.png")
label = filename
filenames = [filename]
item = self._create_item(
label, filenames, dirpath, icon_pixmap
)
new_items.append(item)
self._items_by_dirpath[dirpath].append(item)
for collection in filtered_cols:
filenames = set(collection)
found = False
for item in items_to_remove:
item_filenames = item.data(FILENAMES_ROLE)
if item_filenames == filenames:
found = True
items_to_remove.remove(item)
break
if found:
continue
col_range = collection.format("{ranges}")
label = "{}<{}>{}".format(
collection.head, col_range, collection.tail
)
icon_pixmap = get_pixmap(filename="files.png")
item = self._create_item(
label, filenames, dirpath, icon_pixmap
)
new_items.append(item)
self._items_by_dirpath[dirpath].append(item)
for item in items_to_remove:
self._items_by_dirpath[dirpath].remove(item)
self.removeRows(item.row(), 1)
if new_items:
self.invisibleRootItem().appendRows(new_items)
def _create_item(self, label, filenames, dirpath, icon_pixmap=None):
first_filename = None
for filename in filenames:
first_filename = filename
break
ext = os.path.splitext(first_filename)[-1]
is_dir = False
if len(filenames) == 1:
filepath = os.path.join(dirpath, first_filename)
is_dir = os.path.isdir(filepath)
item = QtGui.QStandardItem()
item.setData(str(uuid.uuid4()), ITEM_ID_ROLE)
item.setData(label, ITEM_LABEL_ROLE)
item.setData(filenames, FILENAMES_ROLE)
item.setData(dirpath, DIRPATH_ROLE)
item.setData(icon_pixmap, ITEM_ICON_ROLE)
item.setData(ext, EXT_ROLE)
item.setData(is_dir, IS_DIR_ROLE)
return item
class FilesProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, *args, **kwargs):
super(FilesProxyModel, self).__init__(*args, **kwargs)
self._allow_folders = False
self._allowed_extensions = None
def set_allow_folders(self, allow=None):
if allow is None:
allow = not self._allow_folders
if allow == self._allow_folders:
return
self._allow_folders = allow
self.invalidateFilter()
def set_allowed_extensions(self, extensions=None):
if extensions is not None:
extensions = set(extensions)
if self._allowed_extensions != extensions:
self._allowed_extensions = extensions
self.invalidateFilter()
def filterAcceptsRow(self, row, parent_index):
model = self.sourceModel()
index = model.index(row, self.filterKeyColumn(), parent_index)
# First check if item is folder and if folders are enabled
if index.data(IS_DIR_ROLE):
if not self._allow_folders:
return False
return True
# Check if there are any allowed extensions
if self._allowed_extensions is None:
return False
if index.data(EXT_ROLE) not in self._allowed_extensions:
return False
return True
def lessThan(self, left, right):
left_comparison = left.data(DIRPATH_ROLE)
right_comparison = right.data(DIRPATH_ROLE)
if left_comparison == right_comparison:
left_comparison = left.data(ITEM_LABEL_ROLE)
right_comparison = right.data(ITEM_LABEL_ROLE)
if sorted((left_comparison, right_comparison))[0] == left_comparison:
return True
return False
class ItemWidget(QtWidgets.QWidget):
remove_requested = QtCore.Signal(str)
def __init__(self, item_id, label, pixmap_icon, parent=None):
self._item_id = item_id
super(ItemWidget, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
icon_widget = PixmapLabel(pixmap_icon, self)
label_widget = QtWidgets.QLabel(label, self)
pixmap = paint_image_with_color(
get_image(filename="delete.png"), QtCore.Qt.white
)
remove_btn = IconButton(self)
remove_btn.setIcon(QtGui.QIcon(pixmap))
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(icon_widget, 0)
layout.addWidget(label_widget, 1)
layout.addWidget(remove_btn, 0)
remove_btn.clicked.connect(self._on_remove_clicked)
self._icon_widget = icon_widget
self._label_widget = label_widget
self._remove_btn = remove_btn
def _on_remove_clicked(self):
self.remove_requested.emit(self._item_id)
class FilesView(QtWidgets.QListView):
"""View showing instances and their groups."""
def __init__(self, *args, **kwargs):
super(FilesView, self).__init__(*args, **kwargs)
self.setEditTriggers(QtWidgets.QListView.NoEditTriggers)
self.setSelectionMode(
QtWidgets.QAbstractItemView.ExtendedSelection
)
def get_selected_item_ids(self):
"""Ids of selected instances."""
selected_item_ids = set()
for index in self.selectionModel().selectedIndexes():
instance_id = index.data(ITEM_ID_ROLE)
if instance_id is not None:
selected_item_ids.add(instance_id)
return selected_item_ids
def event(self, event):
if not event.type() == QtCore.QEvent.KeyPress:
pass
elif event.key() == QtCore.Qt.Key_Space:
self.toggle_requested.emit(-1)
return True
elif event.key() == QtCore.Qt.Key_Backspace:
self.toggle_requested.emit(0)
return True
elif event.key() == QtCore.Qt.Key_Return:
self.toggle_requested.emit(1)
return True
return super(FilesView, self).event(event)
class MultiFilesWidget(QtWidgets.QFrame):
value_changed = QtCore.Signal()
def __init__(self, parent):
super(MultiFilesWidget, self).__init__(parent)
self.setAcceptDrops(True)
empty_widget = DropEmpty(self)
files_model = FilesModel()
files_proxy_model = FilesProxyModel()
files_proxy_model.setSourceModel(files_model)
files_view = FilesView(self)
files_view.setModel(files_proxy_model)
files_view.setVisible(False)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(empty_widget, 1)
layout.addWidget(files_view, 1)
files_proxy_model.rowsInserted.connect(self._on_rows_inserted)
files_proxy_model.rowsRemoved.connect(self._on_rows_removed)
self._in_set_value = False
self._empty_widget = empty_widget
self._files_model = files_model
self._files_proxy_model = files_proxy_model
self._files_view = files_view
self._widgets_by_id = {}
def set_value(self, value, multivalue):
self._in_set_value = True
widget_ids = set(self._widgets_by_id.keys())
self._remove_item_by_ids(widget_ids)
# TODO how to display multivalue?
all_same = True
if multivalue:
new_value = set()
item_row = None
for _value in value:
_value_set = set(_value)
new_value |= _value_set
if item_row is None:
item_row = _value_set
elif item_row != _value_set:
all_same = False
value = new_value
if value:
self._add_filepaths(value)
self._in_set_value = False
def current_value(self):
model = self._files_proxy_model
filepaths = set()
for row in range(model.rowCount()):
index = model.index(row, 0)
dirpath = index.data(DIRPATH_ROLE)
filenames = index.data(FILENAMES_ROLE)
for filename in filenames:
filepaths.add(os.path.join(dirpath, filename))
return filepaths
def set_filters(self, folders_allowed, exts_filter):
self._files_proxy_model.set_allow_folders(folders_allowed)
self._files_proxy_model.set_allowed_extensions(exts_filter)
def _on_rows_inserted(self, parent_index, start_row, end_row):
for row in range(start_row, end_row + 1):
index = self._files_proxy_model.index(row, 0, parent_index)
item_id = index.data(ITEM_ID_ROLE)
if item_id in self._widgets_by_id:
continue
label = index.data(ITEM_LABEL_ROLE)
pixmap_icon = index.data(ITEM_ICON_ROLE)
widget = ItemWidget(item_id, label, pixmap_icon)
self._files_view.setIndexWidget(index, widget)
self._files_proxy_model.setData(
index, widget.sizeHint(), QtCore.Qt.SizeHintRole
)
widget.remove_requested.connect(self._on_remove_request)
self._widgets_by_id[item_id] = widget
self._files_proxy_model.sort(0)
if not self._in_set_value:
self.value_changed.emit()
def _on_rows_removed(self, parent_index, start_row, end_row):
available_item_ids = set()
for row in range(self._files_proxy_model.rowCount()):
index = self._files_proxy_model.index(row, 0)
item_id = index.data(ITEM_ID_ROLE)
available_item_ids.add(index.data(ITEM_ID_ROLE))
widget_ids = set(self._widgets_by_id.keys())
for item_id in available_item_ids:
if item_id in widget_ids:
widget_ids.remove(item_id)
for item_id in widget_ids:
widget = self._widgets_by_id.pop(item_id)
widget.setVisible(False)
widget.deleteLater()
if not self._in_set_value:
self.value_changed.emit()
def _on_remove_request(self, item_id):
found_index = None
for row in range(self._files_model.rowCount()):
index = self._files_model.index(row, 0)
_item_id = index.data(ITEM_ID_ROLE)
if item_id == _item_id:
found_index = index
break
if found_index is None:
return
items_to_delete = self._files_view.get_selected_item_ids()
if item_id not in items_to_delete:
items_to_delete = [item_id]
self._remove_item_by_ids(items_to_delete)
def sizeHint(self):
# Get size hints of widget and visible widgets
result = super(MultiFilesWidget, self).sizeHint()
if not self._files_view.isVisible():
not_visible_hint = self._files_view.sizeHint()
else:
not_visible_hint = self._empty_widget.sizeHint()
# Get margins of this widget
margins = self.layout().contentsMargins()
# Change size hint based on result of maximum size hint of widgets
result.setWidth(max(
result.width(),
not_visible_hint.width() + margins.left() + margins.right()
))
result.setHeight(max(
result.height(),
not_visible_hint.height() + margins.top() + margins.bottom()
))
return result
def dragEnterEvent(self, event):
mime_data = event.mimeData()
if mime_data.hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
def dragLeaveEvent(self, event):
event.accept()
def dropEvent(self, event):
mime_data = event.mimeData()
if mime_data.hasUrls():
filepaths = []
for url in mime_data.urls():
filepath = url.toLocalFile()
if os.path.exists(filepath):
filepaths.append(filepath)
if filepaths:
self._add_filepaths(filepaths)
event.accept()
def _add_filepaths(self, filepaths):
self._files_model.add_filepaths(filepaths)
self._update_visibility()
def _remove_item_by_ids(self, item_ids):
self._files_model.remove_item_by_ids(item_ids)
self._update_visibility()
def _update_visibility(self):
files_exists = self._files_model.rowCount() > 0
self._files_view.setVisible(files_exists)
self._empty_widget.setVisible(not files_exists)
class SingleFileWidget(QtWidgets.QWidget):
value_changed = QtCore.Signal()
def __init__(self, parent):
super(SingleFileWidget, self).__init__(parent)
self.setAcceptDrops(True)
filepath_input = QtWidgets.QLineEdit(self)
browse_btn = QtWidgets.QPushButton("Browse", self)
browse_btn.setVisible(False)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(filepath_input, 1)
layout.addWidget(browse_btn, 0)
browse_btn.clicked.connect(self._on_browse_clicked)
filepath_input.textChanged.connect(self._on_text_change)
self._in_set_value = False
self._filepath_input = filepath_input
self._folders_allowed = False
self._exts_filter = []
def set_value(self, value, multivalue):
self._in_set_value = True
if multivalue:
set_value = set(value)
if len(set_value) == 1:
value = tuple(set_value)[0]
else:
value = "< Multiselection >"
self._filepath_input.setText(value)
self._in_set_value = False
def current_value(self):
return self._filepath_input.text()
def set_filters(self, folders_allowed, exts_filter):
self._folders_allowed = folders_allowed
self._exts_filter = exts_filter
def _on_text_change(self, text):
if not self._in_set_value:
self.value_changed.emit()
def _on_browse_clicked(self):
# TODO implement file dialog logic in '_on_browse_clicked'
print("_on_browse_clicked")
def dragEnterEvent(self, event):
mime_data = event.mimeData()
if not mime_data.hasUrls():
return
filepaths = []
for url in mime_data.urls():
filepath = url.toLocalFile()
if os.path.exists(filepath):
filepaths.append(filepath)
# TODO add folder, extensions check
if len(filepaths) == 1:
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
def dragLeaveEvent(self, event):
event.accept()
def dropEvent(self, event):
mime_data = event.mimeData()
if mime_data.hasUrls():
filepaths = []
for url in mime_data.urls():
filepath = url.toLocalFile()
if os.path.exists(filepath):
filepaths.append(filepath)
# TODO filter check
if len(filepaths) == 1:
self.set_value(filepaths[0], False)
event.accept()

View file

@ -1,14 +1,20 @@
import os
import uuid
from Qt import QtWidgets, QtCore
from openpype.pipeline.lib import (
AbtractAttrDef,
UnknownDef,
NumberDef,
TextDef,
EnumDef,
BoolDef
BoolDef,
FileDef,
UISeparatorDef,
UILabelDef
)
from openpype.widgets.nice_checkbox import NiceCheckbox
from Qt import QtWidgets, QtCore
def create_widget_for_attr_def(attr_def, parent=None):
@ -32,12 +38,22 @@ def create_widget_for_attr_def(attr_def, parent=None):
if isinstance(attr_def, UnknownDef):
return UnknownAttrWidget(attr_def, parent)
if isinstance(attr_def, FileDef):
return FileAttrWidget(attr_def, parent)
if isinstance(attr_def, UISeparatorDef):
return SeparatorAttrWidget(attr_def, parent)
if isinstance(attr_def, UILabelDef):
return LabelAttrWidget(attr_def, parent)
raise ValueError("Unknown attribute definition \"{}\"".format(
str(type(attr_def))
))
class _BaseAttrDefWidget(QtWidgets.QWidget):
# Type 'object' may not work with older PySide versions
value_changed = QtCore.Signal(object, uuid.UUID)
def __init__(self, attr_def, parent):
@ -68,12 +84,36 @@ class _BaseAttrDefWidget(QtWidgets.QWidget):
def set_value(self, value, multivalue=False):
raise NotImplementedError(
"Method 'current_value' is not implemented. {}".format(
"Method 'set_value' is not implemented. {}".format(
self.__class__.__name__
)
)
class SeparatorAttrWidget(_BaseAttrDefWidget):
def _ui_init(self):
input_widget = QtWidgets.QWidget(self)
input_widget.setObjectName("Separator")
input_widget.setMinimumHeight(2)
input_widget.setMaximumHeight(2)
self._input_widget = input_widget
self.main_layout.addWidget(input_widget, 0)
class LabelAttrWidget(_BaseAttrDefWidget):
def _ui_init(self):
input_widget = QtWidgets.QLabel(self)
label = self.attr_def.label
if label:
input_widget.setText(str(label))
self._input_widget = input_widget
self.main_layout.addWidget(input_widget, 0)
class NumberAttrWidget(_BaseAttrDefWidget):
def _ui_init(self):
decimals = self.attr_def.decimals
@ -83,6 +123,9 @@ class NumberAttrWidget(_BaseAttrDefWidget):
else:
input_widget = QtWidgets.QSpinBox(self)
if self.attr_def.tooltip:
input_widget.setToolTip(self.attr_def.tooltip)
input_widget.setMinimum(self.attr_def.minimum)
input_widget.setMaximum(self.attr_def.maximum)
input_widget.setValue(self.attr_def.default)
@ -136,6 +179,9 @@ class TextAttrWidget(_BaseAttrDefWidget):
):
input_widget.setPlaceholderText(self.attr_def.placeholder)
if self.attr_def.tooltip:
input_widget.setToolTip(self.attr_def.tooltip)
if self.attr_def.default:
if self.multiline:
input_widget.setPlainText(self.attr_def.default)
@ -184,6 +230,9 @@ class BoolAttrWidget(_BaseAttrDefWidget):
input_widget = NiceCheckbox(parent=self)
input_widget.setChecked(self.attr_def.default)
if self.attr_def.tooltip:
input_widget.setToolTip(self.attr_def.tooltip)
input_widget.stateChanged.connect(self._on_value_change)
self._input_widget = input_widget
@ -220,6 +269,9 @@ class EnumAttrWidget(_BaseAttrDefWidget):
combo_delegate = QtWidgets.QStyledItemDelegate(input_widget)
input_widget.setItemDelegate(combo_delegate)
if self.attr_def.tooltip:
input_widget.setToolTip(self.attr_def.tooltip)
items = self.attr_def.items
for key, label in items.items():
input_widget.addItem(label, key)
@ -281,3 +333,40 @@ class UnknownAttrWidget(_BaseAttrDefWidget):
if str_value != self._value:
self._value = str_value
self._input_widget.setText(str_value)
class FileAttrWidget(_BaseAttrDefWidget):
def _ui_init(self):
self.multipath = self.attr_def.multipath
if self.multipath:
from .files_widget import MultiFilesWidget
input_widget = MultiFilesWidget(self)
else:
from .files_widget import SingleFileWidget
input_widget = SingleFileWidget(self)
if self.attr_def.tooltip:
input_widget.setToolTip(self.attr_def.tooltip)
input_widget.set_filters(
self.attr_def.folders, self.attr_def.extensions
)
input_widget.value_changed.connect(self._on_value_change)
self._input_widget = input_widget
self.main_layout.addWidget(input_widget, 0)
def _on_value_change(self):
new_value = self.current_value()
self.value_changed.emit(new_value, self.attr_def.id)
def current_value(self):
return self._input_widget.current_value()
def set_value(self, value, multivalue=False):
self._input_widget.set_value(value, multivalue)