removed attribute defs from widgets

This commit is contained in:
Jakub Trllo 2022-09-07 13:48:01 +02:00
parent c78b7d1d0c
commit 72e166066e
3 changed files with 0 additions and 1468 deletions

View file

@ -1,10 +0,0 @@
from .widgets import (
create_widget_for_attr_def,
AttributeDefinitionsWidget,
)
__all__ = (
"create_widget_for_attr_def",
"AttributeDefinitionsWidget",
)

View file

@ -1,968 +0,0 @@
import os
import collections
import uuid
import json
from Qt import QtWidgets, QtCore, QtGui
from openpype.lib import FileDefItem
from openpype.tools.utils import (
paint_image_with_color,
ClickableLabel,
)
# TODO change imports
from openpype.tools.resources import 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
IS_SEQUENCE_ROLE = QtCore.Qt.UserRole + 7
EXT_ROLE = QtCore.Qt.UserRole + 8
def convert_bytes_to_json(bytes_value):
if isinstance(bytes_value, QtCore.QByteArray):
# Raw data are already QByteArray and we don't have to load them
encoded_data = bytes_value
else:
encoded_data = QtCore.QByteArray.fromRawData(bytes_value)
stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.ReadOnly)
text = stream.readQString()
try:
return json.loads(text)
except Exception:
return None
def convert_data_to_bytes(data):
bytes_value = QtCore.QByteArray()
stream = QtCore.QDataStream(bytes_value, QtCore.QIODevice.WriteOnly)
stream.writeQString(json.dumps(data))
return bytes_value
class SupportLabel(QtWidgets.QLabel):
pass
class DropEmpty(QtWidgets.QWidget):
_empty_extensions = "Any file"
def __init__(self, single_item, allow_sequences, extensions_label, parent):
super(DropEmpty, self).__init__(parent)
drop_label_widget = QtWidgets.QLabel("Drag & Drop files here", self)
items_label_widget = SupportLabel(self)
items_label_widget.setWordWrap(True)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addSpacing(20)
layout.addWidget(
drop_label_widget, 0, alignment=QtCore.Qt.AlignCenter
)
layout.addSpacing(30)
layout.addStretch(1)
layout.addWidget(
items_label_widget, 0, alignment=QtCore.Qt.AlignCenter
)
layout.addSpacing(10)
for widget in (
drop_label_widget,
items_label_widget,
):
widget.setAlignment(QtCore.Qt.AlignCenter)
widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
update_size_timer = QtCore.QTimer()
update_size_timer.setInterval(10)
update_size_timer.setSingleShot(True)
update_size_timer.timeout.connect(self._on_update_size_timer)
self._update_size_timer = update_size_timer
if extensions_label and not extensions_label.startswith(" "):
extensions_label = " " + extensions_label
self._single_item = single_item
self._extensions_label = extensions_label
self._allow_sequences = allow_sequences
self._allowed_extensions = set()
self._allow_folders = None
self._drop_label_widget = drop_label_widget
self._items_label_widget = items_label_widget
self.set_allow_folders(False)
def set_extensions(self, extensions):
if extensions:
extensions = {
ext.replace(".", "")
for ext in extensions
}
if extensions == self._allowed_extensions:
return
self._allowed_extensions = extensions
self._update_items_label()
def set_allow_folders(self, allowed):
if self._allow_folders == allowed:
return
self._allow_folders = allowed
self._update_items_label()
def _update_items_label(self):
allowed_items = []
if self._allow_folders:
allowed_items.append("folder")
if self._allowed_extensions:
allowed_items.append("file")
if self._allow_sequences:
allowed_items.append("sequence")
if not self._single_item:
allowed_items = [item + "s" for item in allowed_items]
if not allowed_items:
self._items_label_widget.setText(
"It is not allowed to add anything here!"
)
return
items_label = "Multiple "
if self._single_item:
items_label = "Single "
if len(allowed_items) == 1:
extensions_label = allowed_items[0]
elif len(allowed_items) == 2:
extensions_label = " or ".join(allowed_items)
else:
last_item = allowed_items.pop(-1)
new_last_item = " or ".join(last_item, allowed_items.pop(-1))
allowed_items.append(new_last_item)
extensions_label = ", ".join(allowed_items)
allowed_items_label = extensions_label
items_label += allowed_items_label
label_tooltip = None
if self._allowed_extensions:
items_label += " of\n{}".format(
", ".join(sorted(self._allowed_extensions))
)
if self._extensions_label:
label_tooltip = items_label
items_label = self._extensions_label
if self._items_label_widget.text() == items_label:
return
self._items_label_widget.setToolTip(label_tooltip)
self._items_label_widget.setText(items_label)
self._update_size_timer.start()
def resizeEvent(self, event):
super(DropEmpty, self).resizeEvent(event)
self._update_size_timer.start()
def _on_update_size_timer(self):
"""Recalculate height of label with extensions.
Dynamic QLabel with word wrap does not handle properly it's sizeHint
calculations on show. This way it is recalculated. It is good practice
to trigger this method with small offset using '_update_size_timer'.
"""
width = self._items_label_widget.width()
height = self._items_label_widget.heightForWidth(width)
self._items_label_widget.setMinimumHeight(height)
self._items_label_widget.updateGeometry()
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):
def __init__(self, single_item, allow_sequences):
super(FilesModel, self).__init__()
self._id = str(uuid.uuid4())
self._single_item = single_item
self._multivalue = False
self._allow_sequences = allow_sequences
self._items_by_id = {}
self._file_items_by_id = {}
self._filenames_by_dirpath = collections.defaultdict(set)
self._items_by_dirpath = collections.defaultdict(list)
@property
def id(self):
return self._id
def set_multivalue(self, multivalue):
"""Disable filtering."""
if self._multivalue == multivalue:
return
self._multivalue = multivalue
def add_filepaths(self, items):
if not items:
return
file_items = FileDefItem.from_value(items, self._allow_sequences)
if not file_items:
return
if not self._multivalue and self._single_item:
file_items = [file_items[0]]
current_ids = list(self._file_items_by_id.keys())
if current_ids:
self.remove_item_by_ids(current_ids)
new_model_items = []
for file_item in file_items:
item_id, model_item = self._create_item(file_item)
new_model_items.append(model_item)
self._file_items_by_id[item_id] = file_item
self._items_by_id[item_id] = model_item
if new_model_items:
roow_item = self.invisibleRootItem()
roow_item.appendRows(new_model_items)
def remove_item_by_ids(self, item_ids):
if not item_ids:
return
items = []
for item_id in set(item_ids):
if item_id not in self._items_by_id:
continue
item = self._items_by_id.pop(item_id)
self._file_items_by_id.pop(item_id)
items.append(item)
if items:
for item in items:
self.removeRows(item.row(), 1)
def get_file_item_by_id(self, item_id):
return self._file_items_by_id.get(item_id)
def _create_item(self, file_item):
if file_item.is_dir:
icon_pixmap = paint_image_with_color(
get_image(filename="folder.png"), QtCore.Qt.white
)
else:
icon_pixmap = paint_image_with_color(
get_image(filename="file.png"), QtCore.Qt.white
)
item = QtGui.QStandardItem()
item_id = str(uuid.uuid4())
item.setData(item_id, ITEM_ID_ROLE)
item.setData(file_item.label or "< empty >", ITEM_LABEL_ROLE)
item.setData(file_item.filenames, FILENAMES_ROLE)
item.setData(file_item.directory, DIRPATH_ROLE)
item.setData(icon_pixmap, ITEM_ICON_ROLE)
item.setData(file_item.ext, EXT_ROLE)
item.setData(file_item.is_dir, IS_DIR_ROLE)
item.setData(file_item.is_sequence, IS_SEQUENCE_ROLE)
return item_id, item
def mimeData(self, indexes):
item_ids = [
index.data(ITEM_ID_ROLE)
for index in indexes
]
item_ids_data = convert_data_to_bytes(item_ids)
mime_data = super(FilesModel, self).mimeData(indexes)
mime_data.setData("files_widget/internal_move", item_ids_data)
file_items = []
for item_id in item_ids:
file_item = self.get_file_item_by_id(item_id)
if file_item:
file_items.append(file_item.to_dict())
full_item_data = convert_data_to_bytes({
"items": file_items,
"id": self._id
})
mime_data.setData("files_widget/full_data", full_item_data)
return mime_data
def dropMimeData(self, mime_data, action, row, col, index):
item_ids = convert_bytes_to_json(
mime_data.data("files_widget/internal_move")
)
if item_ids is None:
return False
# Find matching item after which will be items moved
# - store item before moved items are removed
root = self.invisibleRootItem()
if row >= 0:
src_item = self.item(row)
else:
src_item_id = index.data(ITEM_ID_ROLE)
src_item = self._items_by_id.get(src_item_id)
# Take out items that should be moved
items = []
for item_id in item_ids:
item = self._items_by_id.get(item_id)
if item:
self.takeRow(item.row())
items.append(item)
# Skip if there are not items that can be moved
if not items:
return False
# Calculate row where items should be inserted
if src_item:
src_row = src_item.row()
else:
src_row = root.rowCount()
root.insertRow(src_row, items)
return True
class FilesProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, *args, **kwargs):
super(FilesProxyModel, self).__init__(*args, **kwargs)
self._allow_folders = False
self._allowed_extensions = None
self._multivalue = False
def set_multivalue(self, multivalue):
"""Disable filtering."""
if self._multivalue == multivalue:
return
self._multivalue = multivalue
self.invalidateFilter()
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()
for ext in set(extensions):
if not ext.startswith("."):
ext = ".{}".format(ext)
_extensions.add(ext.lower())
extensions = _extensions
if self._allowed_extensions != extensions:
self._allowed_extensions = extensions
self.invalidateFilter()
def are_valid_files(self, filepaths):
for filepath in filepaths:
if os.path.isfile(filepath):
_, ext = os.path.splitext(filepath)
if ext in self._allowed_extensions:
return True
elif self._allow_folders:
return True
return False
def filter_valid_files(self, filepaths):
filtered_paths = []
for filepath in filepaths:
if os.path.isfile(filepath):
_, ext = os.path.splitext(filepath)
if ext in self._allowed_extensions:
filtered_paths.append(filepath)
elif self._allow_folders:
filtered_paths.append(filepath)
return filtered_paths
def filterAcceptsRow(self, row, parent_index):
# Skip filtering if multivalue is set
if self._multivalue:
return True
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):
context_menu_requested = QtCore.Signal(QtCore.QPoint)
def __init__(
self, item_id, label, pixmap_icon, is_sequence, multivalue, 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)
label_size_hint = label_widget.sizeHint()
height = label_size_hint.height()
actions_menu_pix = paint_image_with_color(
get_image(filename="menu.png"), QtCore.Qt.white
)
split_btn = ClickableLabel(self)
split_btn.setFixedSize(height, height)
split_btn.setPixmap(actions_menu_pix)
if multivalue:
split_btn.setVisible(False)
else:
split_btn.setVisible(is_sequence)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(5, 5, 5, 5)
layout.addWidget(icon_widget, 0)
layout.addWidget(label_widget, 1)
layout.addWidget(split_btn, 0)
split_btn.clicked.connect(self._on_actions_clicked)
self._icon_widget = icon_widget
self._label_widget = label_widget
self._split_btn = split_btn
self._actions_menu_pix = actions_menu_pix
self._last_scaled_pix_height = None
def _update_btn_size(self):
label_size_hint = self._label_widget.sizeHint()
height = label_size_hint.height()
if height == self._last_scaled_pix_height:
return
self._last_scaled_pix_height = height
self._split_btn.setFixedSize(height, height)
pix = self._actions_menu_pix.scaled(
height, height,
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation
)
self._split_btn.setPixmap(pix)
def showEvent(self, event):
super(ItemWidget, self).showEvent(event)
self._update_btn_size()
def resizeEvent(self, event):
super(ItemWidget, self).resizeEvent(event)
self._update_btn_size()
def _on_actions_clicked(self):
pos = self._split_btn.rect().bottomLeft()
point = self._split_btn.mapToGlobal(pos)
self.context_menu_requested.emit(point)
class InViewButton(IconButton):
pass
class FilesView(QtWidgets.QListView):
"""View showing instances and their groups."""
remove_requested = QtCore.Signal()
context_menu_requested = QtCore.Signal(QtCore.QPoint)
def __init__(self, *args, **kwargs):
super(FilesView, self).__init__(*args, **kwargs)
self.setEditTriggers(QtWidgets.QListView.NoEditTriggers)
self.setSelectionMode(
QtWidgets.QAbstractItemView.ExtendedSelection
)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.setAcceptDrops(True)
self.setDragEnabled(True)
self.setDragDropMode(self.InternalMove)
remove_btn = InViewButton(self)
pix_enabled = paint_image_with_color(
get_image(filename="delete.png"), QtCore.Qt.white
)
pix_disabled = paint_image_with_color(
get_image(filename="delete.png"), QtCore.Qt.gray
)
icon = QtGui.QIcon(pix_enabled)
icon.addPixmap(pix_disabled, icon.Disabled, icon.Off)
remove_btn.setIcon(icon)
remove_btn.setEnabled(False)
remove_btn.clicked.connect(self._on_remove_clicked)
self.customContextMenuRequested.connect(self._on_context_menu_request)
self._remove_btn = remove_btn
def setSelectionModel(self, *args, **kwargs):
"""Catch selection model set to register signal callback.
Selection model is not available during initialization.
"""
super(FilesView, self).setSelectionModel(*args, **kwargs)
selection_model = self.selectionModel()
selection_model.selectionChanged.connect(self._on_selection_change)
def set_multivalue(self, multivalue):
"""Disable remove button on multivalue."""
self._remove_btn.setVisible(not multivalue)
def has_selected_item_ids(self):
"""Is any index selected."""
for index in self.selectionModel().selectedIndexes():
instance_id = index.data(ITEM_ID_ROLE)
if instance_id is not None:
return True
return False
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 has_selected_sequence(self):
for index in self.selectionModel().selectedIndexes():
if index.data(IS_SEQUENCE_ROLE):
return True
return False
def event(self, event):
if event.type() == QtCore.QEvent.KeyPress:
if (
event.key() == QtCore.Qt.Key_Delete
and self.has_selected_item_ids()
):
self.remove_requested.emit()
return True
return super(FilesView, self).event(event)
def _on_context_menu_request(self, pos):
index = self.indexAt(pos)
if index.isValid():
point = self.viewport().mapToGlobal(pos)
self.context_menu_requested.emit(point)
def _on_selection_change(self):
self._remove_btn.setEnabled(self.has_selected_item_ids())
def _on_remove_clicked(self):
self.remove_requested.emit()
def _update_remove_btn(self):
"""Position remove button to bottom right."""
viewport = self.viewport()
height = viewport.height()
pos_x = viewport.width() - self._remove_btn.width() - 5
pos_y = height - self._remove_btn.height() - 5
self._remove_btn.move(max(0, pos_x), max(0, pos_y))
def resizeEvent(self, event):
super(FilesView, self).resizeEvent(event)
self._update_remove_btn()
def showEvent(self, event):
super(FilesView, self).showEvent(event)
self._update_remove_btn()
class FilesWidget(QtWidgets.QFrame):
value_changed = QtCore.Signal()
def __init__(self, single_item, allow_sequences, extensions_label, parent):
super(FilesWidget, self).__init__(parent)
self.setAcceptDrops(True)
empty_widget = DropEmpty(
single_item, allow_sequences, extensions_label, self
)
files_model = FilesModel(single_item, allow_sequences)
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)
files_view.remove_requested.connect(self._on_remove_requested)
files_view.context_menu_requested.connect(
self._on_context_menu_requested
)
self._in_set_value = False
self._single_item = single_item
self._multivalue = 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_multivalue(self, multivalue):
if self._multivalue == multivalue:
return
self._multivalue = multivalue
self._files_view.set_multivalue(multivalue)
self._files_model.set_multivalue(multivalue)
self._files_proxy_model.set_multivalue(multivalue)
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)
self._set_multivalue(multivalue)
self._add_filepaths(value)
self._in_set_value = False
def current_value(self):
model = self._files_proxy_model
item_ids = set()
for row in range(model.rowCount()):
index = model.index(row, 0)
item_ids.add(index.data(ITEM_ID_ROLE))
file_items = []
for item_id in item_ids:
file_item = self._files_model.get_file_item_by_id(item_id)
if file_item is not None:
file_items.append(file_item.to_dict())
if not self._single_item:
return file_items
if file_items:
return file_items[0]
empty_item = FileDefItem.create_empty_item()
return empty_item.to_dict()
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)
self._empty_widget.set_extensions(exts_filter)
self._empty_widget.set_allow_folders(folders_allowed)
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)
is_sequence = index.data(IS_SEQUENCE_ROLE)
widget = ItemWidget(
item_id,
label,
pixmap_icon,
is_sequence,
self._multivalue
)
widget.context_menu_requested.connect(
self._on_context_menu_requested
)
self._files_view.setIndexWidget(index, widget)
self._files_proxy_model.setData(
index, widget.sizeHint(), QtCore.Qt.SizeHintRole
)
self._widgets_by_id[item_id] = widget
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_split_request(self):
if self._multivalue:
return
item_ids = self._files_view.get_selected_item_ids()
if not item_ids:
return
for item_id in item_ids:
file_item = self._files_model.get_file_item_by_id(item_id)
if not file_item:
return
new_items = file_item.split_sequence()
self._add_filepaths(new_items)
self._remove_item_by_ids(item_ids)
def _on_remove_requested(self):
if self._multivalue:
return
items_to_delete = self._files_view.get_selected_item_ids()
if items_to_delete:
self._remove_item_by_ids(items_to_delete)
def _on_context_menu_requested(self, pos):
if self._multivalue:
return
menu = QtWidgets.QMenu(self._files_view)
if self._files_view.has_selected_sequence():
split_action = QtWidgets.QAction("Split sequence", menu)
split_action.triggered.connect(self._on_split_request)
menu.addAction(split_action)
remove_action = QtWidgets.QAction("Remove", menu)
remove_action.triggered.connect(self._on_remove_requested)
menu.addAction(remove_action)
menu.popup(pos)
def sizeHint(self):
# Get size hints of widget and visible widgets
result = super(FilesWidget, 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):
if self._multivalue:
return
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 self._files_proxy_model.are_valid_files(filepaths):
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
full_data_value = mime_data.data("files_widget/full_data")
if self._handle_full_data_drag(full_data_value):
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
def dragLeaveEvent(self, event):
event.accept()
def dropEvent(self, event):
if self._multivalue:
return
mime_data = event.mimeData()
if mime_data.hasUrls():
event.accept()
# event.setDropAction(QtCore.Qt.CopyAction)
filepaths = []
for url in mime_data.urls():
filepath = url.toLocalFile()
if os.path.exists(filepath):
filepaths.append(filepath)
# Filter filepaths before passing it to model
filepaths = self._files_proxy_model.filter_valid_files(filepaths)
if filepaths:
self._add_filepaths(filepaths)
if self._handle_full_data_drop(
mime_data.data("files_widget/full_data")
):
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
super(FilesWidget, self).dropEvent(event)
def _handle_full_data_drag(self, value):
if value is None:
return False
full_data = convert_bytes_to_json(value)
if full_data is None:
return False
if full_data["id"] == self._files_model.id:
return False
return True
def _handle_full_data_drop(self, value):
if value is None:
return False
full_data = convert_bytes_to_json(value)
if full_data is None:
return False
if full_data["id"] == self._files_model.id:
return False
for item in full_data["items"]:
filepaths = [
os.path.join(item["directory"], filename)
for filename in item["filenames"]
]
filepaths = self._files_proxy_model.filter_valid_files(filepaths)
if filepaths:
self._add_filepaths(filepaths)
if self._copy_modifiers_enabled():
return False
return True
def _copy_modifiers_enabled(self):
if (
QtWidgets.QApplication.keyboardModifiers()
& QtCore.Qt.ControlModifier
):
return True
return False
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_proxy_model.rowCount() > 0
self._files_view.setVisible(files_exists)
self._empty_widget.setVisible(not files_exists)

View file

@ -1,490 +0,0 @@
import uuid
import copy
from Qt import QtWidgets, QtCore
from openpype.lib.attribute_definitions import (
AbtractAttrDef,
UnknownDef,
NumberDef,
TextDef,
EnumDef,
BoolDef,
FileDef,
UIDef,
UISeparatorDef,
UILabelDef
)
from openpype.tools.utils import CustomTextComboBox
from openpype.widgets.nice_checkbox import NiceCheckbox
from .files_widget import FilesWidget
def create_widget_for_attr_def(attr_def, parent=None):
if not isinstance(attr_def, AbtractAttrDef):
raise TypeError("Unexpected type \"{}\" expected \"{}\"".format(
str(type(attr_def)), AbtractAttrDef
))
if isinstance(attr_def, NumberDef):
return NumberAttrWidget(attr_def, parent)
if isinstance(attr_def, TextDef):
return TextAttrWidget(attr_def, parent)
if isinstance(attr_def, EnumDef):
return EnumAttrWidget(attr_def, parent)
if isinstance(attr_def, BoolDef):
return BoolAttrWidget(attr_def, parent)
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 AttributeDefinitionsWidget(QtWidgets.QWidget):
"""Create widgets for attribute definitions in grid layout.
Widget creates input widgets for passed attribute definitions.
Widget can't handle multiselection values.
"""
def __init__(self, attr_defs=None, parent=None):
super(AttributeDefinitionsWidget, self).__init__(parent)
self._widgets = []
self._current_keys = set()
self.set_attr_defs(attr_defs)
def clear_attr_defs(self):
"""Remove all existing widgets and reset layout if needed."""
self._widgets = []
self._current_keys = set()
layout = self.layout()
if layout is not None:
if layout.count() == 0:
return
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget:
widget.setVisible(False)
widget.deleteLater()
layout.deleteLater()
new_layout = QtWidgets.QGridLayout()
new_layout.setColumnStretch(0, 0)
new_layout.setColumnStretch(1, 1)
self.setLayout(new_layout)
def set_attr_defs(self, attr_defs):
"""Replace current attribute definitions with passed."""
self.clear_attr_defs()
if attr_defs:
self.add_attr_defs(attr_defs)
def add_attr_defs(self, attr_defs):
"""Add attribute definitions to current."""
layout = self.layout()
row = 0
for attr_def in attr_defs:
if attr_def.key in self._current_keys:
raise KeyError("Duplicated key \"{}\"".format(attr_def.key))
self._current_keys.add(attr_def.key)
widget = create_widget_for_attr_def(attr_def, self)
expand_cols = 2
if attr_def.is_value_def and attr_def.is_label_horizontal:
expand_cols = 1
col_num = 2 - expand_cols
if attr_def.label:
label_widget = QtWidgets.QLabel(attr_def.label, self)
layout.addWidget(
label_widget, row, 0, 1, expand_cols
)
if not attr_def.is_label_horizontal:
row += 1
layout.addWidget(
widget, row, col_num, 1, expand_cols
)
self._widgets.append(widget)
row += 1
def set_value(self, value):
new_value = copy.deepcopy(value)
unused_keys = set(new_value.keys())
for widget in self._widgets:
attr_def = widget.attr_def
if attr_def.key not in new_value:
continue
unused_keys.remove(attr_def.key)
widget_value = new_value[attr_def.key]
if widget_value is None:
widget_value = copy.deepcopy(attr_def.default)
widget.set_value(widget_value)
def current_value(self):
output = {}
for widget in self._widgets:
attr_def = widget.attr_def
if not isinstance(attr_def, UIDef):
output[attr_def.key] = widget.current_value()
return output
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):
super(_BaseAttrDefWidget, self).__init__(parent)
self.attr_def = attr_def
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
self.main_layout = main_layout
self._ui_init()
def _ui_init(self):
raise NotImplementedError(
"Method '_ui_init' is not implemented. {}".format(
self.__class__.__name__
)
)
def current_value(self):
raise NotImplementedError(
"Method 'current_value' is not implemented. {}".format(
self.__class__.__name__
)
)
def set_value(self, value, multivalue=False):
raise NotImplementedError(
"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
if decimals > 0:
input_widget = QtWidgets.QDoubleSpinBox(self)
input_widget.setDecimals(decimals)
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)
input_widget.setButtonSymbols(
QtWidgets.QAbstractSpinBox.ButtonSymbols.NoButtons
)
input_widget.valueChanged.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.value_changed.emit(new_value, self.attr_def.id)
def current_value(self):
return self._input_widget.value()
def set_value(self, value, multivalue=False):
if multivalue:
set_value = set(value)
if None in set_value:
set_value.remove(None)
set_value.add(self.attr_def.default)
if len(set_value) > 1:
self._input_widget.setSpecialValueText("Multiselection")
return
value = tuple(set_value)[0]
if self.current_value != value:
self._input_widget.setValue(value)
class TextAttrWidget(_BaseAttrDefWidget):
def _ui_init(self):
# TODO Solve how to handle regex
# self.attr_def.regex
self.multiline = self.attr_def.multiline
if self.multiline:
input_widget = QtWidgets.QPlainTextEdit(self)
else:
input_widget = QtWidgets.QLineEdit(self)
if (
self.attr_def.placeholder
and hasattr(input_widget, "setPlaceholderText")
):
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)
else:
input_widget.setText(self.attr_def.default)
input_widget.textChanged.connect(self._on_value_change)
self._input_widget = input_widget
self.main_layout.addWidget(input_widget, 0)
def _on_value_change(self):
if self.multiline:
new_value = self._input_widget.toPlainText()
else:
new_value = self._input_widget.text()
self.value_changed.emit(new_value, self.attr_def.id)
def current_value(self):
if self.multiline:
return self._input_widget.toPlainText()
return self._input_widget.text()
def set_value(self, value, multivalue=False):
if multivalue:
set_value = set(value)
if None in set_value:
set_value.remove(None)
set_value.add(self.attr_def.default)
if len(set_value) == 1:
value = tuple(set_value)[0]
else:
value = "< Multiselection >"
if value != self.current_value():
if self.multiline:
self._input_widget.setPlainText(value)
else:
self._input_widget.setText(value)
class BoolAttrWidget(_BaseAttrDefWidget):
def _ui_init(self):
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
self.main_layout.addWidget(input_widget, 0)
self.main_layout.addStretch(1)
def _on_value_change(self):
new_value = self._input_widget.isChecked()
self.value_changed.emit(new_value, self.attr_def.id)
def current_value(self):
return self._input_widget.isChecked()
def set_value(self, value, multivalue=False):
if multivalue:
set_value = set(value)
if None in set_value:
set_value.remove(None)
set_value.add(self.attr_def.default)
if len(set_value) > 1:
self._input_widget.setCheckState(QtCore.Qt.PartiallyChecked)
return
value = tuple(set_value)[0]
if value != self.current_value():
self._input_widget.setChecked(value)
class EnumAttrWidget(_BaseAttrDefWidget):
def __init__(self, *args, **kwargs):
self._multivalue = False
super(EnumAttrWidget, self).__init__(*args, **kwargs)
def _ui_init(self):
input_widget = CustomTextComboBox(self)
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)
idx = input_widget.findData(self.attr_def.default)
if idx >= 0:
input_widget.setCurrentIndex(idx)
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)
def _on_value_change(self):
new_value = self.current_value()
if self._multivalue:
self._multivalue = False
self._input_widget.set_custom_text(None)
self.value_changed.emit(new_value, self.attr_def.id)
def current_value(self):
idx = self._input_widget.currentIndex()
return self._input_widget.itemData(idx)
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 not multivalue:
idx = self._input_widget.findData(value)
cur_idx = self._input_widget.currentIndex()
if idx != cur_idx and idx >= 0:
self._input_widget.setCurrentIndex(idx)
custom_text = None
if multivalue:
custom_text = "< Multiselection >"
self._input_widget.set_custom_text(custom_text)
self._multivalue = multivalue
class UnknownAttrWidget(_BaseAttrDefWidget):
def _ui_init(self):
input_widget = QtWidgets.QLabel(self)
self._value = self.attr_def.default
input_widget.setText(str(self._value))
self._input_widget = input_widget
self.main_layout.addWidget(input_widget, 0)
def current_value(self):
raise ValueError(
"{} can't hold real value.".format(self.__class__.__name__)
)
def set_value(self, value, multivalue=False):
if multivalue:
set_value = set(value)
if len(set_value) == 1:
value = tuple(set_value)[0]
else:
value = "< Multiselection >"
str_value = str(value)
if str_value != self._value:
self._value = str_value
self._input_widget.setText(str_value)
class FileAttrWidget(_BaseAttrDefWidget):
def _ui_init(self):
input_widget = FilesWidget(
self.attr_def.single_item,
self.attr_def.allow_sequences,
self.attr_def.extensions_label,
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)