mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
added widgets for component inserting
This commit is contained in:
parent
09cc2e9e59
commit
a09c90aeb3
6 changed files with 539 additions and 0 deletions
|
|
@ -21,3 +21,10 @@ from .widget_asset_view import AssetView
|
|||
from .widget_asset import AssetWidget
|
||||
from .widget_family_desc import FamilyDescriptionWidget
|
||||
from .widget_family import FamilyWidget
|
||||
from .widget_drop_data import DropDataWidget
|
||||
|
||||
from .widget_component import ComponentWidget
|
||||
from .widget_tree_components import TreeComponents
|
||||
from .widget_component_item import ComponentItem
|
||||
|
||||
from .widget_drop_files import DropDataFrame
|
||||
|
|
|
|||
189
pype/tools/standalonepublish/widgets/widget_component.py
Normal file
189
pype/tools/standalonepublish/widgets/widget_component.py
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
from . import QtCore, QtGui, QtWidgets
|
||||
from . import SvgButton
|
||||
from . import get_resource
|
||||
|
||||
|
||||
class ComponentWidget(QtWidgets.QFrame):
|
||||
C_NORMAL = '#777777'
|
||||
C_HOVER = '#ffffff'
|
||||
C_ACTIVE = '#4BB543'
|
||||
C_ACTIVE_HOVER = '#4BF543'
|
||||
signal_remove = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
self.resize(290, 70)
|
||||
self.setMinimumSize(QtCore.QSize(0, 70))
|
||||
self.parent_item = parent
|
||||
# Font
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("DejaVu Sans Condensed")
|
||||
font.setPointSize(9)
|
||||
font.setBold(True)
|
||||
font.setWeight(50)
|
||||
font.setKerning(True)
|
||||
|
||||
# Main widgets
|
||||
frame = QtWidgets.QFrame(self)
|
||||
frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
|
||||
frame.setFrameShadow(QtWidgets.QFrame.Raised)
|
||||
|
||||
layout_main = QtWidgets.QHBoxLayout(frame)
|
||||
layout_main.setSpacing(2)
|
||||
layout_main.setContentsMargins(2, 2, 2, 2)
|
||||
|
||||
# Image + Info
|
||||
frame_image_info = QtWidgets.QFrame(frame)
|
||||
|
||||
# Layout image info
|
||||
layout = QtWidgets.QVBoxLayout(frame_image_info)
|
||||
layout.setSpacing(2)
|
||||
layout.setContentsMargins(2, 2, 2, 2)
|
||||
|
||||
image = QtWidgets.QLabel(frame)
|
||||
image.setMinimumSize(QtCore.QSize(22, 22))
|
||||
image.setMaximumSize(QtCore.QSize(22, 22))
|
||||
image.setText("")
|
||||
image.setScaledContents(True)
|
||||
pixmap = QtGui.QPixmap(get_resource('image_sequence.png'))
|
||||
image.setPixmap(pixmap)
|
||||
|
||||
self.info = SvgButton(
|
||||
get_resource('information.svg'), 22, 22,
|
||||
[self.C_NORMAL, self.C_HOVER],
|
||||
frame_image_info, False
|
||||
)
|
||||
|
||||
expanding_sizePolicy = QtWidgets.QSizePolicy(
|
||||
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
|
||||
)
|
||||
expanding_sizePolicy.setHorizontalStretch(0)
|
||||
expanding_sizePolicy.setVerticalStretch(0)
|
||||
|
||||
layout.addWidget(image, alignment=QtCore.Qt.AlignCenter)
|
||||
layout.addWidget(self.info, alignment=QtCore.Qt.AlignCenter)
|
||||
|
||||
layout_main.addWidget(frame_image_info)
|
||||
|
||||
# Name + representation
|
||||
self.name = QtWidgets.QLabel(frame)
|
||||
self.frames = QtWidgets.QLabel(frame)
|
||||
self.ext = QtWidgets.QLabel(frame)
|
||||
|
||||
self.name.setFont(font)
|
||||
self.frames.setFont(font)
|
||||
self.ext.setFont(font)
|
||||
|
||||
self.frames.setStyleSheet('padding-left:3px;')
|
||||
|
||||
expanding_sizePolicy.setHeightForWidth(self.name.sizePolicy().hasHeightForWidth())
|
||||
|
||||
frame_name_repre = QtWidgets.QFrame(frame)
|
||||
|
||||
self.frames.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
|
||||
self.ext.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
|
||||
self.name.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(frame_name_repre)
|
||||
layout.setSpacing(0)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(self.name, alignment=QtCore.Qt.AlignLeft)
|
||||
layout.addWidget(self.frames, alignment=QtCore.Qt.AlignLeft)
|
||||
layout.addWidget(self.ext, alignment=QtCore.Qt.AlignRight)
|
||||
|
||||
frame_name_repre.setSizePolicy(
|
||||
QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding
|
||||
)
|
||||
|
||||
# Frames + icons
|
||||
frame_repre_icons = QtWidgets.QFrame(frame)
|
||||
|
||||
label_repre = QtWidgets.QLabel()
|
||||
label_repre.setText('Representation:')
|
||||
|
||||
self.input_repre = QtWidgets.QLineEdit()
|
||||
self.input_repre.setMaximumWidth(50)
|
||||
|
||||
frame_icons = QtWidgets.QFrame(frame_repre_icons)
|
||||
|
||||
self.preview = SvgButton(
|
||||
get_resource('preview.svg'), 64, 18,
|
||||
[self.C_NORMAL, self.C_HOVER, self.C_ACTIVE, self.C_ACTIVE_HOVER],
|
||||
frame_icons
|
||||
)
|
||||
|
||||
self.thumbnail = SvgButton(
|
||||
get_resource('thumbnail.svg'), 84, 18,
|
||||
[self.C_NORMAL, self.C_HOVER, self.C_ACTIVE, self.C_ACTIVE_HOVER],
|
||||
frame_icons
|
||||
)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(frame_icons)
|
||||
layout.setSpacing(6)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(self.thumbnail)
|
||||
layout.addWidget(self.preview)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(frame_repre_icons)
|
||||
layout.setSpacing(0)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
layout.addWidget(label_repre, alignment=QtCore.Qt.AlignLeft)
|
||||
layout.addWidget(self.input_repre, alignment=QtCore.Qt.AlignLeft)
|
||||
layout.addWidget(frame_icons, alignment=QtCore.Qt.AlignRight)
|
||||
|
||||
frame_middle = QtWidgets.QFrame(frame)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(frame_middle)
|
||||
layout.setSpacing(0)
|
||||
layout.setContentsMargins(4, 0, 4, 0)
|
||||
layout.addWidget(frame_name_repre)
|
||||
layout.addWidget(frame_repre_icons)
|
||||
|
||||
layout.setStretchFactor(frame_name_repre, 1)
|
||||
layout.setStretchFactor(frame_repre_icons, 1)
|
||||
|
||||
layout_main.addWidget(frame_middle)
|
||||
|
||||
self.remove = SvgButton(
|
||||
get_resource('trash.svg'), 22, 22,
|
||||
[self.C_NORMAL, self.C_HOVER],
|
||||
frame, False
|
||||
)
|
||||
|
||||
layout_main.addWidget(self.remove)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setSpacing(0)
|
||||
layout.setContentsMargins(2, 2, 2, 2)
|
||||
layout.addWidget(frame)
|
||||
|
||||
self.preview.setToolTip('Mark component as Preview')
|
||||
self.thumbnail.setToolTip('Component will be selected as thumbnail')
|
||||
|
||||
# self.frame.setStyleSheet("border: 1px solid black;")
|
||||
|
||||
def set_context(self, data):
|
||||
|
||||
self.remove.clicked.connect(self._remove)
|
||||
name = data['name']
|
||||
representation = data['representation']
|
||||
ext = data['ext']
|
||||
file_info = data['file_info']
|
||||
thumb = data['thumb']
|
||||
prev = data['prev']
|
||||
info = data['info']
|
||||
|
||||
self.name.setText(name)
|
||||
self.input_repre.setText(representation)
|
||||
self.ext.setText('( {} )'.format(ext))
|
||||
if file_info is None:
|
||||
self.file_info.setVisible(False)
|
||||
else:
|
||||
self.file_info.setText('[{}]'.format(file_info))
|
||||
|
||||
# self.thumbnail.setVisible(thumb)
|
||||
# self.preview.setVisible(prev)
|
||||
|
||||
def _remove(self):
|
||||
self.signal_remove.emit(self.parent_item)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
from . import QtWidgets
|
||||
from . import ComponentWidget
|
||||
|
||||
|
||||
class ComponentItem(QtWidgets.QTreeWidgetItem):
|
||||
def __init__(self, parent, data):
|
||||
super().__init__(parent)
|
||||
self.in_data = data
|
||||
self._widget = ComponentWidget(self)
|
||||
self._widget.set_context(data)
|
||||
|
||||
self.treeWidget().setItemWidget(self, 0, self._widget)
|
||||
|
||||
def double_clicked(*args):
|
||||
pass
|
||||
41
pype/tools/standalonepublish/widgets/widget_drop_data.py
Normal file
41
pype/tools/standalonepublish/widgets/widget_drop_data.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import os
|
||||
import logging
|
||||
import clique
|
||||
from . import QtWidgets, QtCore, QtGui
|
||||
|
||||
|
||||
class DropDataWidget(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
'''Initialise DataDropZone widget.'''
|
||||
super().__init__(parent)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
|
||||
bottomCenterAlignment = QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter
|
||||
topCenterAlignment = QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter
|
||||
|
||||
self._label = QtWidgets.QLabel('Drop files here')
|
||||
layout.addWidget(
|
||||
self._label,
|
||||
alignment=bottomCenterAlignment
|
||||
)
|
||||
|
||||
self._browseButton = QtWidgets.QPushButton('Browse')
|
||||
self._browseButton.setToolTip('Browse for file(s).')
|
||||
layout.addWidget(
|
||||
self._browseButton, alignment=topCenterAlignment
|
||||
)
|
||||
|
||||
def paintEvent(self, event):
|
||||
super().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)
|
||||
painter.drawRect(
|
||||
10, 10,
|
||||
self.rect().width()-15, self.rect().height()-15
|
||||
)
|
||||
273
pype/tools/standalonepublish/widgets/widget_drop_files.py
Normal file
273
pype/tools/standalonepublish/widgets/widget_drop_files.py
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
import os
|
||||
import clique
|
||||
from . import QtWidgets, QtCore
|
||||
from . import ComponentItem, TreeComponents, DropDataWidget
|
||||
|
||||
|
||||
class DropDataFrame(QtWidgets.QFrame):
|
||||
# signal_dropped = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
|
||||
self.items = []
|
||||
|
||||
self.setAcceptDrops(True)
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
|
||||
self.tree_widget = TreeComponents(self)
|
||||
layout.addWidget(self.tree_widget)
|
||||
|
||||
self.drop_widget = DropDataWidget(self)
|
||||
layout.addWidget(self.drop_widget)
|
||||
|
||||
self._refresh_view()
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
event.setDropAction(QtCore.Qt.CopyAction)
|
||||
event.accept()
|
||||
|
||||
def dragLeaveEvent(self, event):
|
||||
event.accept()
|
||||
|
||||
def dropEvent(self, event):
|
||||
paths = self._processMimeData(event.mimeData())
|
||||
if paths:
|
||||
self._add_components(paths)
|
||||
event.accept()
|
||||
|
||||
def _processMimeData(self, mimeData):
|
||||
paths = []
|
||||
|
||||
if not mimeData.hasUrls():
|
||||
print('Dropped invalid file/folder')
|
||||
return paths
|
||||
|
||||
for path in mimeData.urls():
|
||||
local_path = path.toLocalFile()
|
||||
if os.path.isfile(local_path) or os.path.isdir(local_path):
|
||||
paths.append(local_path)
|
||||
else:
|
||||
print('Invalid input: "{}"'.format(local_path))
|
||||
|
||||
return paths
|
||||
|
||||
def _add_components(self, paths):
|
||||
components = self._process_paths(paths)
|
||||
if not components:
|
||||
return
|
||||
for component in components:
|
||||
self._add_item(component)
|
||||
|
||||
def _add_item(self, data):
|
||||
# Assign to self so garbage collector wont remove the component
|
||||
# during initialization
|
||||
self.new_component = ComponentItem(self.tree_widget, data)
|
||||
self.new_component._widget.signal_remove.connect(self._remove_item)
|
||||
self.tree_widget.addTopLevelItem(self.new_component)
|
||||
self.items.append(self.new_component)
|
||||
self.new_component = None
|
||||
|
||||
self._refresh_view()
|
||||
|
||||
def _remove_item(self, item):
|
||||
root = self.tree_widget.invisibleRootItem()
|
||||
(item.parent() or root).removeChild(item)
|
||||
self.items.remove(item)
|
||||
self._refresh_view()
|
||||
|
||||
def _refresh_view(self):
|
||||
_bool = len(self.items) == 0
|
||||
|
||||
self.tree_widget.setVisible(not _bool)
|
||||
self.drop_widget.setVisible(_bool)
|
||||
|
||||
def _process_paths(self, in_paths):
|
||||
paths = self._get_all_paths(in_paths)
|
||||
collections, remainders = clique.assemble(paths)
|
||||
for collection in collections:
|
||||
self._process_collection(collection)
|
||||
for remainder in remainders:
|
||||
self._process_remainder(remainder)
|
||||
|
||||
def _get_all_paths(self, paths):
|
||||
output_paths = []
|
||||
for path in paths:
|
||||
path = os.path.normpath(path)
|
||||
if os.path.isfile(path):
|
||||
output_paths.append(path)
|
||||
elif os.path.isdir(path):
|
||||
s_paths = []
|
||||
for s_item in os.listdir(path):
|
||||
s_path = os.path.sep.join([path, s_item])
|
||||
s_paths.append(s_path)
|
||||
output_paths.extend(self._get_all_paths(s_paths))
|
||||
else:
|
||||
print('Invalid path: "{}"'.format(path))
|
||||
return output_paths
|
||||
|
||||
def _process_collection(self, collection):
|
||||
file_base = os.path.basename(collection.head)
|
||||
folder_path = os.path.dirname(collection.head)
|
||||
if file_base[-1] in ['.']:
|
||||
file_base = file_base[:-1]
|
||||
file_ext = collection.tail
|
||||
repr_name = file_ext.replace('.', '')
|
||||
range = self._get_ranges(collection.indexes)
|
||||
thumb = False
|
||||
if file_ext in ['.jpeg']:
|
||||
thumb = True
|
||||
|
||||
prev = False
|
||||
if file_ext in ['.jpeg']:
|
||||
prev = True
|
||||
|
||||
files = []
|
||||
for file in os.listdir(folder_path):
|
||||
if file.startswith(file_base) and file.endswith(file_ext):
|
||||
files.append(os.path.sep.join([folder_path, file]))
|
||||
info = {}
|
||||
|
||||
data = {
|
||||
'files': files,
|
||||
'name': file_base,
|
||||
'ext': file_ext,
|
||||
'file_info': range,
|
||||
'representation': repr_name,
|
||||
'folder_path': folder_path,
|
||||
'icon': 'sequence',
|
||||
'thumb': thumb,
|
||||
'prev': prev,
|
||||
'is_sequence': True,
|
||||
'info': info
|
||||
}
|
||||
self._process_data(data)
|
||||
|
||||
def _get_ranges(self, indexes):
|
||||
if len(indexes) == 1:
|
||||
return str(indexes[0])
|
||||
ranges = []
|
||||
first = None
|
||||
last = None
|
||||
for index in indexes:
|
||||
if first is None:
|
||||
first = index
|
||||
last = index
|
||||
elif (last+1) == index:
|
||||
last = index
|
||||
else:
|
||||
if first == last:
|
||||
range = str(first)
|
||||
else:
|
||||
range = '{}-{}'.format(first, last)
|
||||
ranges.append(range)
|
||||
first = index
|
||||
last = index
|
||||
if first == last:
|
||||
range = str(first)
|
||||
else:
|
||||
range = '{}-{}'.format(first, last)
|
||||
ranges.append(range)
|
||||
return ', '.join(ranges)
|
||||
|
||||
def _process_remainder(self, remainder):
|
||||
filename = os.path.basename(remainder)
|
||||
folder_path = os.path.dirname(remainder)
|
||||
file_base, file_ext = os.path.splitext(filename)
|
||||
repr_name = file_ext.replace('.', '')
|
||||
file_info = None
|
||||
thumb = False
|
||||
if file_ext in ['.jpeg']:
|
||||
thumb = True
|
||||
|
||||
prev = False
|
||||
if file_ext in ['.jpeg']:
|
||||
prev = True
|
||||
|
||||
files = []
|
||||
files.append(remainder)
|
||||
|
||||
info = {}
|
||||
|
||||
data = {
|
||||
'files': files,
|
||||
'name': file_base,
|
||||
'ext': file_ext,
|
||||
'file_info': file_info,
|
||||
'representation': repr_name,
|
||||
'folder_path': folder_path,
|
||||
'icon': 'sequence',
|
||||
'thumb': thumb,
|
||||
'prev': prev,
|
||||
'is_sequence': False,
|
||||
'info': info
|
||||
}
|
||||
|
||||
self._process_data(data)
|
||||
|
||||
def _process_data(self, data):
|
||||
found = False
|
||||
for item in self.items:
|
||||
if data['ext'] != item.in_data['ext']:
|
||||
continue
|
||||
if data['folder_path'] != item.in_data['folder_path']:
|
||||
continue
|
||||
|
||||
new_is_seq = data['is_sequence']
|
||||
ex_is_seq = item.in_data['is_sequence']
|
||||
|
||||
# If both are single files
|
||||
if not new_is_seq and not ex_is_seq:
|
||||
if data['name'] != item.in_data['name']:
|
||||
continue
|
||||
found = True
|
||||
break
|
||||
# If new is sequence and ex is single file
|
||||
elif new_is_seq and not ex_is_seq:
|
||||
if data['name'] not in item.in_data['name']:
|
||||
continue
|
||||
ex_file = item.in_data['files'][0]
|
||||
found = True
|
||||
# If file is one of inserted sequence
|
||||
if ex_file in data['files']:
|
||||
self._remove_item(item)
|
||||
self._add_item(data)
|
||||
break
|
||||
# if file is missing in inserted sequence
|
||||
paths = data['files']
|
||||
paths.append(ex_file)
|
||||
collections, remainders = clique.assemble(paths)
|
||||
self._process_collection(collections[0])
|
||||
break
|
||||
# If new is single file existing is sequence
|
||||
elif not new_is_seq and ex_is_seq:
|
||||
if item.in_data['name'] not in data['name']:
|
||||
continue
|
||||
new_file = data['files'][0]
|
||||
found = True
|
||||
if new_file in item.in_data['files']:
|
||||
break
|
||||
paths = item.in_data['files']
|
||||
paths.append(new_file)
|
||||
collections, remainders = clique.assemble(paths)
|
||||
self._remove_item(item)
|
||||
self._process_collection(collections[0])
|
||||
|
||||
break
|
||||
# If both are sequence
|
||||
else:
|
||||
if data['name'] != item.in_data['name']:
|
||||
continue
|
||||
found = True
|
||||
ex_files = item.in_data['files']
|
||||
for file in data['files']:
|
||||
if file not in ex_files:
|
||||
ex_files.append(file)
|
||||
paths = list(set(ex_files))
|
||||
collections, remainders = clique.assemble(paths)
|
||||
self._remove_item(item)
|
||||
self._process_collection(collections[0])
|
||||
break
|
||||
|
||||
if found is False:
|
||||
self._add_item(data)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
from . import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class TreeComponents(QtWidgets.QTreeWidget):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self.invisibleRootItem().setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
self.setIndentation(28)
|
||||
self.headerItem().setText(0, 'Components')
|
||||
|
||||
self.setRootIsDecorated(False)
|
||||
|
||||
self.itemDoubleClicked.connect(lambda i, c: i.double_clicked(c))
|
||||
Loading…
Add table
Add a link
Reference in a new issue