Merge pull request #2304 from pypeclub/feature/tools_cleanup

Tools: Cleanup of unused classes
This commit is contained in:
Jakub Trllo 2021-11-25 13:35:58 +01:00 committed by GitHub
commit b372eeef0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 89 additions and 996 deletions

View file

@ -3,9 +3,10 @@
import contextlib
import logging
from Qt import QtCore, QtGui
from openpype.tools.utils.widgets import AssetWidget
from avalon import style, io
from Qt import QtWidgets, QtCore, QtGui
from avalon import io
from openpype import style
from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget
from pxr import Sdf
@ -13,6 +14,60 @@ from pxr import Sdf
log = logging.getLogger(__name__)
class SelectAssetDialog(QtWidgets.QWidget):
"""Frameless assets dialog to select asset with double click.
Args:
parm: Parameter where selected asset name is set.
"""
def __init__(self, parm):
self.setWindowTitle("Pick Asset")
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Popup)
assets_widget = SingleSelectAssetsWidget(io, parent=self)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(assets_widget)
assets_widget.double_clicked.connect(self._set_parameter)
self._assets_widget = assets_widget
self._parm = parm
def _set_parameter(self):
name = self._assets_widget.get_selected_asset_name()
self._parm.set(name)
self.close()
def _on_show(self):
pos = QtGui.QCursor.pos()
# Select the current asset if there is any
select_id = None
name = self._parm.eval()
if name:
db_asset = io.find_one(
{"name": name, "type": "asset"},
{"_id": True}
)
if db_asset:
select_id = db_asset["_id"]
# Set stylesheet
self.setStyleSheet(style.load_stylesheet())
# Refresh assets (is threaded)
self._assets_widget.refresh()
# Select asset - must be done after refresh
if select_id is not None:
self._assets_widget.select_asset(select_id)
# Show cursor (top right of window) near cursor
self.resize(250, 400)
self.move(self.mapFromGlobal(pos) - QtCore.QPoint(self.width(), 0))
def showEvent(self, event):
super(SelectAssetDialog, self).showEvent(event)
self._on_show()
def pick_asset(node):
"""Show a user interface to select an Asset in the project
@ -21,43 +76,15 @@ def pick_asset(node):
"""
pos = QtGui.QCursor.pos()
parm = node.parm("asset_name")
if not parm:
log.error("Node has no 'asset' parameter: %s", node)
return
# Construct the AssetWidget as a frameless popup so it automatically
# Construct a frameless popup so it automatically
# closes when clicked outside of it.
global tool
tool = AssetWidget(io)
tool.setContentsMargins(5, 5, 5, 5)
tool.setWindowTitle("Pick Asset")
tool.setStyleSheet(style.load_stylesheet())
tool.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Popup)
tool.refresh()
# Select the current asset if there is any
name = parm.eval()
if name:
db_asset = io.find_one({"name": name, "type": "asset"})
if db_asset:
silo = db_asset.get("silo")
if silo:
tool.set_silo(silo)
tool.select_assets([name], expand=True)
# Show cursor (top right of window) near cursor
tool.resize(250, 400)
tool.move(tool.mapFromGlobal(pos) - QtCore.QPoint(tool.width(), 0))
def set_parameter_callback(index):
name = index.data(tool.model.DocumentRole)["name"]
parm.set(name)
tool.close()
tool.view.doubleClicked.connect(set_parameter_callback)
tool = SelectAssetDialog(parm)
tool.show()

View file

@ -1,16 +1,12 @@
import os
import sys
import json
from subprocess import Popen
try:
import ftrack_api_old as ftrack_api
except Exception:
import ftrack_api
import ftrack_api
from Qt import QtWidgets, QtCore
from openpype.api import get_current_project_settings
from openpype import lib as pypelib
from avalon.vendor.Qt import QtWidgets, QtCore
from openpype.tools.utils.lib import qt_app_context
from avalon import io, api, style, schema
from avalon.tools import lib as parentlib
from . import widget, model
module = sys.modules[__name__]
@ -630,7 +626,7 @@ def show(parent=None, debug=False, context=None):
if debug is True:
io.install()
with parentlib.application():
with qt_app_context():
window = Window(parent, context)
window.setStyleSheet(style.load_stylesheet())
window.show()

View file

@ -31,7 +31,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
message_timeout = 5000
def __init__(
self, parent=None, icon=None, show_projects=False, show_libraries=True
self, parent=None, show_projects=False, show_libraries=True
):
super(LibraryLoaderWindow, self).__init__(parent)
@ -517,10 +517,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
return super(LibraryLoaderWindow, self).closeEvent(event)
def show(
debug=False, parent=None, icon=None,
show_projects=False, show_libraries=True
):
def show(debug=False, parent=None, show_projects=False, show_libraries=True):
"""Display Loader GUI
Arguments:
@ -555,9 +552,9 @@ def show(
import traceback
sys.excepthook = lambda typ, val, tb: traceback.print_last()
with tools_lib.application():
with tools_lib.qt_app_context():
window = LibraryLoaderWindow(
parent, icon, show_projects, show_libraries
parent, show_projects, show_libraries
)
window.show()

View file

@ -631,7 +631,7 @@ def show(debug=False, parent=None, use_context=False):
api.Session["AVALON_PROJECT"] = any_project["name"]
module.project = any_project["name"]
with lib.application():
with lib.qt_app_context():
window = LoaderWindow(parent)
window.show()

View file

@ -16,7 +16,10 @@ from openpype.tools.utils.delegates import (
VersionDelegate,
PrettyTimeDelegate
)
from openpype.tools.utils.widgets import OptionalMenu
from openpype.tools.utils.widgets import (
OptionalMenu,
PlaceholderLineEdit
)
from openpype.tools.utils.views import (
TreeViewSpinner,
DeselectableTreeView
@ -175,7 +178,7 @@ class SubsetWidget(QtWidgets.QWidget):
family_proxy = FamiliesFilterProxyModel()
family_proxy.setSourceModel(proxy)
subset_filter = QtWidgets.QLineEdit(self)
subset_filter = PlaceholderLineEdit(self)
subset_filter.setPlaceholderText("Filter subsets..")
group_checkbox = QtWidgets.QCheckBox("Enable Grouping", self)
@ -810,8 +813,9 @@ class ThumbnailWidget(QtWidgets.QLabel):
{"_id": doc_id},
{"data.thumbnail_id"}
)
thumbnail_id = doc.get("data", {}).get("thumbnail_id")
thumbnail_id = None
if doc:
thumbnail_id = doc.get("data", {}).get("thumbnail_id")
if thumbnail_id == self.current_thumb_id:
if self.current_thumbnail is None:
self.set_pixmap()

View file

@ -564,6 +564,8 @@ class AssetsWidget(QtWidgets.QWidget):
refreshed = QtCore.Signal()
# on view selection change
selection_changed = QtCore.Signal()
# It was double clicked on view
double_clicked = QtCore.Signal()
def __init__(self, dbcon, parent=None):
super(AssetsWidget, self).__init__(parent=parent)
@ -618,6 +620,7 @@ class AssetsWidget(QtWidgets.QWidget):
refresh_btn.clicked.connect(self.refresh)
current_asset_btn.clicked.connect(self.set_current_session_asset)
model.refreshed.connect(self._on_model_refresh)
view.doubleClicked.connect(self.double_clicked)
self._current_asset_btn = current_asset_btn
self._model = model

View file

@ -5,10 +5,6 @@ DEFAULT_PROJECT_LABEL = "< Default >"
PROJECT_NAME_ROLE = QtCore.Qt.UserRole + 101
PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 102
TASK_NAME_ROLE = QtCore.Qt.UserRole + 301
TASK_TYPE_ROLE = QtCore.Qt.UserRole + 302
TASK_ORDER_ROLE = QtCore.Qt.UserRole + 303
LOCAL_PROVIDER_ROLE = QtCore.Qt.UserRole + 500 # provider of active site
REMOTE_PROVIDER_ROLE = QtCore.Qt.UserRole + 501 # provider of remote site
LOCAL_PROGRESS_ROLE = QtCore.Qt.UserRole + 502 # percentage downld on active

View file

@ -8,10 +8,7 @@ from Qt import QtWidgets, QtGui, QtCore
from avalon.lib import HeroVersionType
from openpype.style import get_objected_colors
from .models import (
AssetModel,
TreeModel
)
from .models import TreeModel
from . import lib
if Qt.__binding__ == "PySide":
@ -22,173 +19,6 @@ elif Qt.__binding__ == "PyQt4":
log = logging.getLogger(__name__)
class AssetDelegate(QtWidgets.QItemDelegate):
bar_height = 3
def __init__(self, *args, **kwargs):
super(AssetDelegate, self).__init__(*args, **kwargs)
asset_view_colors = get_objected_colors()["loader"]["asset-view"]
self._selected_color = (
asset_view_colors["selected"].get_qcolor()
)
self._hover_color = (
asset_view_colors["hover"].get_qcolor()
)
self._selected_hover_color = (
asset_view_colors["selected-hover"].get_qcolor()
)
def sizeHint(self, option, index):
result = super(AssetDelegate, self).sizeHint(option, index)
height = result.height()
result.setHeight(height + self.bar_height)
return result
def paint(self, painter, option, index):
# Qt4 compat
if Qt.__binding__ in ("PySide", "PyQt4"):
option = QStyleOptionViewItemV4(option)
painter.save()
item_rect = QtCore.QRect(option.rect)
item_rect.setHeight(option.rect.height() - self.bar_height)
subset_colors = index.data(AssetModel.subsetColorsRole)
subset_colors_width = 0
if subset_colors:
subset_colors_width = option.rect.width() / len(subset_colors)
subset_rects = []
counter = 0
for subset_c in subset_colors:
new_color = None
new_rect = None
if subset_c:
new_color = QtGui.QColor(*subset_c)
new_rect = QtCore.QRect(
option.rect.left() + (counter * subset_colors_width),
option.rect.top() + (
option.rect.height() - self.bar_height
),
subset_colors_width,
self.bar_height
)
subset_rects.append((new_color, new_rect))
counter += 1
# Background
if option.state & QtWidgets.QStyle.State_Selected:
if len(subset_colors) == 0:
item_rect.setTop(item_rect.top() + (self.bar_height / 2))
if option.state & QtWidgets.QStyle.State_MouseOver:
bg_color = self._selected_hover_color
else:
bg_color = self._selected_color
else:
item_rect.setTop(item_rect.top() + (self.bar_height / 2))
if option.state & QtWidgets.QStyle.State_MouseOver:
bg_color = self._hover_color
else:
bg_color = QtGui.QColor()
bg_color.setAlpha(0)
# When not needed to do a rounded corners (easier and without
# painter restore):
# painter.fillRect(
# item_rect,
# QtGui.QBrush(bg_color)
# )
pen = painter.pen()
pen.setStyle(QtCore.Qt.NoPen)
pen.setWidth(0)
painter.setPen(pen)
painter.setBrush(QtGui.QBrush(bg_color))
painter.drawRoundedRect(option.rect, 3, 3)
if option.state & QtWidgets.QStyle.State_Selected:
for color, subset_rect in subset_rects:
if not color or not subset_rect:
continue
painter.fillRect(subset_rect, QtGui.QBrush(color))
painter.restore()
painter.save()
# Icon
icon_index = index.model().index(
index.row(), index.column(), index.parent()
)
# - Default icon_rect if not icon
icon_rect = QtCore.QRect(
item_rect.left(),
item_rect.top(),
# To make sure it's same size all the time
option.rect.height() - self.bar_height,
option.rect.height() - self.bar_height
)
icon = index.model().data(icon_index, QtCore.Qt.DecorationRole)
if icon:
mode = QtGui.QIcon.Normal
if not (option.state & QtWidgets.QStyle.State_Enabled):
mode = QtGui.QIcon.Disabled
elif option.state & QtWidgets.QStyle.State_Selected:
mode = QtGui.QIcon.Selected
if isinstance(icon, QtGui.QPixmap):
icon = QtGui.QIcon(icon)
option.decorationSize = icon.size() / icon.devicePixelRatio()
elif isinstance(icon, QtGui.QColor):
pixmap = QtGui.QPixmap(option.decorationSize)
pixmap.fill(icon)
icon = QtGui.QIcon(pixmap)
elif isinstance(icon, QtGui.QImage):
icon = QtGui.QIcon(QtGui.QPixmap.fromImage(icon))
option.decorationSize = icon.size() / icon.devicePixelRatio()
elif isinstance(icon, QtGui.QIcon):
state = QtGui.QIcon.Off
if option.state & QtWidgets.QStyle.State_Open:
state = QtGui.QIcon.On
actualSize = option.icon.actualSize(
option.decorationSize, mode, state
)
option.decorationSize = QtCore.QSize(
min(option.decorationSize.width(), actualSize.width()),
min(option.decorationSize.height(), actualSize.height())
)
state = QtGui.QIcon.Off
if option.state & QtWidgets.QStyle.State_Open:
state = QtGui.QIcon.On
icon.paint(
painter, icon_rect,
QtCore.Qt.AlignLeft, mode, state
)
# Text
text_rect = QtCore.QRect(
icon_rect.left() + icon_rect.width() + 2,
item_rect.top(),
item_rect.width(),
item_rect.height()
)
painter.drawText(
text_rect, QtCore.Qt.AlignVCenter,
index.data(QtCore.Qt.DisplayRole)
)
painter.restore()
class VersionDelegate(QtWidgets.QStyledItemDelegate):
"""A delegate that display version integer formatted as version string."""

View file

@ -1,7 +1,5 @@
import re
import time
import logging
import collections
import Qt
from Qt import QtCore, QtGui
@ -11,10 +9,7 @@ from . import lib
from .constants import (
PROJECT_IS_ACTIVE_ROLE,
PROJECT_NAME_ROLE,
DEFAULT_PROJECT_LABEL,
TASK_ORDER_ROLE,
TASK_TYPE_ROLE,
TASK_NAME_ROLE
DEFAULT_PROJECT_LABEL
)
log = logging.getLogger(__name__)
@ -203,283 +198,6 @@ class Item(dict):
self._children.append(child)
class AssetModel(TreeModel):
"""A model listing assets in the silo in the active project.
The assets are displayed in a treeview, they are visually parented by
a `visualParent` field in the database containing an `_id` to a parent
asset.
"""
Columns = ["label"]
Name = 0
Deprecated = 2
ObjectId = 3
DocumentRole = QtCore.Qt.UserRole + 2
ObjectIdRole = QtCore.Qt.UserRole + 3
subsetColorsRole = QtCore.Qt.UserRole + 4
doc_fetched = QtCore.Signal(bool)
refreshed = QtCore.Signal(bool)
# Asset document projection
asset_projection = {
"type": 1,
"schema": 1,
"name": 1,
"silo": 1,
"data.visualParent": 1,
"data.label": 1,
"data.tags": 1,
"data.icon": 1,
"data.color": 1,
"data.deprecated": 1
}
def __init__(self, dbcon=None, parent=None, asset_projection=None):
super(AssetModel, self).__init__(parent=parent)
if dbcon is None:
dbcon = io
self.dbcon = dbcon
self.asset_colors = {}
# Projections for Mongo queries
# - let ability to modify them if used in tools that require more than
# defaults
if asset_projection:
self.asset_projection = asset_projection
self.asset_projection = asset_projection
self._doc_fetching_thread = None
self._doc_fetching_stop = False
self._doc_payload = {}
self.doc_fetched.connect(self.on_doc_fetched)
self.refresh()
def _add_hierarchy(self, assets, parent=None, silos=None):
"""Add the assets that are related to the parent as children items.
This method does *not* query the database. These instead are queried
in a single batch upfront as an optimization to reduce database
queries. Resulting in up to 10x speed increase.
Args:
assets (dict): All assets in the currently active silo stored
by key/value
Returns:
None
"""
# Reset colors
self.asset_colors = {}
if silos:
# WARNING: Silo item "_id" is set to silo value
# mainly because GUI issue with perserve selection and expanded row
# and because of easier hierarchy parenting (in "assets")
for silo in silos:
item = Item({
"_id": silo,
"name": silo,
"label": silo,
"type": "silo"
})
self.add_child(item, parent=parent)
self._add_hierarchy(assets, parent=item)
parent_id = parent["_id"] if parent else None
current_assets = assets.get(parent_id, list())
for asset in current_assets:
# get label from data, otherwise use name
data = asset.get("data", {})
label = data.get("label", asset["name"])
tags = data.get("tags", [])
# store for the asset for optimization
deprecated = "deprecated" in tags
item = Item({
"_id": asset["_id"],
"name": asset["name"],
"label": label,
"type": asset["type"],
"tags": ", ".join(tags),
"deprecated": deprecated,
"_document": asset
})
self.add_child(item, parent=parent)
# Add asset's children recursively if it has children
if asset["_id"] in assets:
self._add_hierarchy(assets, parent=item)
self.asset_colors[asset["_id"]] = []
def on_doc_fetched(self, was_stopped):
if was_stopped:
self.stop_fetch_thread()
return
self.beginResetModel()
assets_by_parent = self._doc_payload.get("assets_by_parent")
silos = self._doc_payload.get("silos")
if assets_by_parent is not None:
# Build the hierarchical tree items recursively
self._add_hierarchy(
assets_by_parent,
parent=None,
silos=silos
)
self.endResetModel()
has_content = bool(assets_by_parent) or bool(silos)
self.refreshed.emit(has_content)
self.stop_fetch_thread()
def fetch(self):
self._doc_payload = self._fetch() or {}
# Emit doc fetched only if was not stopped
self.doc_fetched.emit(self._doc_fetching_stop)
def _fetch(self):
if not self.dbcon.Session.get("AVALON_PROJECT"):
return
project_doc = self.dbcon.find_one(
{"type": "project"},
{"_id": True}
)
if not project_doc:
return
# Get all assets sorted by name
db_assets = self.dbcon.find(
{"type": "asset"},
self.asset_projection
).sort("name", 1)
# Group the assets by their visual parent's id
assets_by_parent = collections.defaultdict(list)
for asset in db_assets:
if self._doc_fetching_stop:
return
parent_id = asset.get("data", {}).get("visualParent")
assets_by_parent[parent_id].append(asset)
return {
"assets_by_parent": assets_by_parent,
"silos": None
}
def stop_fetch_thread(self):
if self._doc_fetching_thread is not None:
self._doc_fetching_stop = True
while self._doc_fetching_thread.isRunning():
time.sleep(0.001)
self._doc_fetching_thread = None
def refresh(self, force=False):
"""Refresh the data for the model."""
# Skip fetch if there is already other thread fetching documents
if self._doc_fetching_thread is not None:
if not force:
return
self.stop_fetch_thread()
# Clear model items
self.clear()
# Fetch documents from mongo
# Restart payload
self._doc_payload = {}
self._doc_fetching_stop = False
self._doc_fetching_thread = lib.create_qthread(self.fetch)
self._doc_fetching_thread.start()
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def setData(self, index, value, role=QtCore.Qt.EditRole):
if not index.isValid():
return False
if role == self.subsetColorsRole:
asset_id = index.data(self.ObjectIdRole)
self.asset_colors[asset_id] = value
if Qt.__binding__ in ("PyQt4", "PySide"):
self.dataChanged.emit(index, index)
else:
self.dataChanged.emit(index, index, [role])
return True
return super(AssetModel, self).setData(index, value, role)
def data(self, index, role):
if not index.isValid():
return
item = index.internalPointer()
if role == QtCore.Qt.DecorationRole:
column = index.column()
if column == self.Name:
# Allow a custom icon and custom icon color to be defined
data = item.get("_document", {}).get("data", {})
icon = data.get("icon", None)
if icon is None and item.get("type") == "silo":
icon = "database"
color = data.get("color", style.colors.default)
if icon is None:
# Use default icons if no custom one is specified.
# If it has children show a full folder, otherwise
# show an open folder
has_children = self.rowCount(index) > 0
icon = "folder" if has_children else "folder-o"
# Make the color darker when the asset is deprecated
if item.get("deprecated", False):
color = QtGui.QColor(color).darker(250)
try:
key = "fa.{0}".format(icon) # font-awesome key
icon = qtawesome.icon(key, color=color)
return icon
except Exception as exception:
# Log an error message instead of erroring out completely
# when the icon couldn't be created (e.g. invalid name)
log.error(exception)
return
if role == QtCore.Qt.ForegroundRole: # font color
if "deprecated" in item.get("tags", []):
return QtGui.QColor(style.colors.light).darker(250)
if role == self.ObjectIdRole:
return item.get("_id", None)
if role == self.DocumentRole:
return item.get("_document", None)
if role == self.subsetColorsRole:
asset_id = item.get("_id", None)
return self.asset_colors.get(asset_id) or []
return super(AssetModel, self).data(index, role)
class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel):
"""Filters to the regex if any of the children matches allow parent"""
def filterAcceptsRow(self, row, parent):
@ -654,163 +372,3 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel):
def set_filter_enabled(self, value):
self._filter_enabled = value
self.invalidateFilter()
class TasksModel(QtGui.QStandardItemModel):
"""A model listing the tasks combined for a list of assets"""
def __init__(self, dbcon, parent=None):
super(TasksModel, self).__init__(parent=parent)
self.dbcon = dbcon
self._default_icon = qtawesome.icon(
"fa.male",
color=style.colors.default
)
self._no_tasks_icon = qtawesome.icon(
"fa.exclamation-circle",
color=style.colors.mid
)
self._cached_icons = {}
self._project_task_types = {}
self._last_asset_id = None
self.refresh()
def refresh(self):
if self.dbcon.Session.get("AVALON_PROJECT"):
self._refresh_task_types()
self.set_asset_id(self._last_asset_id)
else:
self.clear()
def _refresh_task_types(self):
# Get the project configured icons from database
project = self.dbcon.find_one(
{"type": "project"},
{"config.tasks"}
)
tasks = project["config"].get("tasks") or {}
self._project_task_types = tasks
def _try_get_awesome_icon(self, icon_name):
icon = None
if icon_name:
try:
icon = qtawesome.icon(
"fa.{}".format(icon_name),
color=style.colors.default
)
except Exception:
pass
return icon
def headerData(self, section, orientation, role):
# Show nice labels in the header
if (
role == QtCore.Qt.DisplayRole
and orientation == QtCore.Qt.Horizontal
):
if section == 0:
return "Tasks"
return super(TasksModel, self).headerData(section, orientation, role)
def _get_icon(self, task_icon, task_type_icon):
if task_icon in self._cached_icons:
return self._cached_icons[task_icon]
icon = self._try_get_awesome_icon(task_icon)
if icon is not None:
self._cached_icons[task_icon] = icon
return icon
if task_type_icon in self._cached_icons:
icon = self._cached_icons[task_type_icon]
self._cached_icons[task_icon] = icon
return icon
icon = self._try_get_awesome_icon(task_type_icon)
if icon is None:
icon = self._default_icon
self._cached_icons[task_icon] = icon
self._cached_icons[task_type_icon] = icon
return icon
def set_asset_id(self, asset_id):
asset_doc = None
if asset_id:
asset_doc = self.dbcon.find_one(
{"_id": asset_id},
{"data.tasks": True}
)
self.set_asset(asset_doc)
def set_asset(self, asset_doc):
"""Set assets to track by their database id
Arguments:
asset_doc (dict): Asset document from MongoDB.
"""
self.clear()
if not asset_doc:
self._last_asset_id = None
return
self._last_asset_id = asset_doc["_id"]
asset_tasks = asset_doc.get("data", {}).get("tasks") or {}
items = []
for task_name, task_info in asset_tasks.items():
task_icon = task_info.get("icon")
task_type = task_info.get("type")
task_order = task_info.get("order")
task_type_info = self._project_task_types.get(task_type) or {}
task_type_icon = task_type_info.get("icon")
icon = self._get_icon(task_icon, task_type_icon)
label = "{} ({})".format(task_name, task_type or "type N/A")
item = QtGui.QStandardItem(label)
item.setData(task_name, TASK_NAME_ROLE)
item.setData(task_type, TASK_TYPE_ROLE)
item.setData(task_order, TASK_ORDER_ROLE)
item.setData(icon, QtCore.Qt.DecorationRole)
item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
items.append(item)
if not items:
item = QtGui.QStandardItem("No task")
item.setData(self._no_tasks_icon, QtCore.Qt.DecorationRole)
item.setFlags(QtCore.Qt.NoItemFlags)
items.append(item)
self.invisibleRootItem().appendRows(items)
class TasksProxyModel(QtCore.QSortFilterProxyModel):
def lessThan(self, x_index, y_index):
x_order = x_index.data(TASK_ORDER_ROLE)
y_order = y_index.data(TASK_ORDER_ROLE)
if x_order is not None and y_order is not None:
if x_order < y_order:
return True
if x_order > y_order:
return False
elif x_order is None and y_order is not None:
return True
elif y_order is None and x_order is not None:
return False
x_name = x_index.data(QtCore.Qt.DisplayRole)
y_name = y_index.data(QtCore.Qt.DisplayRole)
if x_name == y_name:
return True
if x_name == tuple(sorted((x_name, y_name)))[0]:
return True
return False

View file

@ -4,11 +4,11 @@ from avalon import style
from avalon.vendor import qtawesome
from .views import DeselectableTreeView
from .constants import (
TASK_ORDER_ROLE,
TASK_TYPE_ROLE,
TASK_NAME_ROLE
)
TASK_NAME_ROLE = QtCore.Qt.UserRole + 1
TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2
TASK_ORDER_ROLE = QtCore.Qt.UserRole + 3
class TasksModel(QtGui.QStandardItemModel):

View file

@ -61,26 +61,3 @@ class TreeViewSpinner(QtWidgets.QTreeView):
self.paint_empty(event)
else:
super(TreeViewSpinner, self).paintEvent(event)
class AssetsView(TreeViewSpinner, DeselectableTreeView):
"""Item view.
This implements a context menu.
"""
def __init__(self, parent=None):
super(AssetsView, self).__init__(parent)
self.setIndentation(15)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.setHeaderHidden(True)
def mousePressEvent(self, event):
index = self.indexAt(event.pos())
if not index.isValid():
modifiers = QtWidgets.QApplication.keyboardModifiers()
if modifiers == QtCore.Qt.ShiftModifier:
return
elif modifiers == QtCore.Qt.ControlModifier:
return
super(AssetsView, self).mousePressEvent(event)

View file

@ -1,18 +1,10 @@
import logging
import time
from . import lib
from Qt import QtWidgets, QtCore, QtGui
from avalon.vendor import qtawesome, qargparse
from avalon import style
from openpype.style import get_objected_colors
from .models import AssetModel, RecursiveSortFilterProxyModel
from .views import AssetsView
from .delegates import AssetDelegate
log = logging.getLogger(__name__)
@ -38,293 +30,6 @@ class PlaceholderLineEdit(QtWidgets.QLineEdit):
self.setPalette(filter_palette)
class AssetWidget(QtWidgets.QWidget):
"""A Widget to display a tree of assets with filter
To list the assets of the active project:
>>> # widget = AssetWidget()
>>> # widget.refresh()
>>> # widget.show()
"""
refresh_triggered = QtCore.Signal() # on model refresh
refreshed = QtCore.Signal()
selection_changed = QtCore.Signal() # on view selection change
current_changed = QtCore.Signal() # on view current index change
def __init__(self, dbcon, multiselection=False, parent=None):
super(AssetWidget, self).__init__(parent=parent)
self.dbcon = dbcon
# Tree View
model = AssetModel(dbcon=self.dbcon, parent=self)
proxy = RecursiveSortFilterProxyModel()
proxy.setSourceModel(model)
proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
view = AssetsView(self)
view.setModel(proxy)
if multiselection:
asset_delegate = AssetDelegate()
view.setSelectionMode(view.ExtendedSelection)
view.setItemDelegate(asset_delegate)
icon = qtawesome.icon("fa.arrow-down", color=style.colors.light)
set_current_asset_btn = QtWidgets.QPushButton(icon, "")
set_current_asset_btn.setToolTip("Go to Asset from current Session")
# Hide by default
set_current_asset_btn.setVisible(False)
icon = qtawesome.icon("fa.refresh", color=style.colors.light)
refresh = QtWidgets.QPushButton(icon, "", parent=self)
refresh.setToolTip("Refresh items")
filter_input = QtWidgets.QLineEdit(self)
filter_input.setPlaceholderText("Filter assets..")
# Header
header_layout = QtWidgets.QHBoxLayout()
header_layout.addWidget(filter_input)
header_layout.addWidget(set_current_asset_btn)
header_layout.addWidget(refresh)
# Layout
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(4)
layout.addLayout(header_layout)
layout.addWidget(view)
# Signals/Slots
filter_input.textChanged.connect(proxy.setFilterFixedString)
selection = view.selectionModel()
selection.selectionChanged.connect(self.selection_changed)
selection.currentChanged.connect(self.current_changed)
refresh.clicked.connect(self.refresh)
set_current_asset_btn.clicked.connect(self.set_current_session_asset)
self.set_current_asset_btn = set_current_asset_btn
self.model = model
self.proxy = proxy
self.view = view
self.model_selection = {}
def set_current_asset_btn_visibility(self, visible=None):
"""Hide set current asset button.
Not all tools support using of current context asset.
"""
if visible is None:
visible = not self.set_current_asset_btn.isVisible()
self.set_current_asset_btn.setVisible(visible)
def _refresh_model(self):
# Store selection
self._store_model_selection()
time_start = time.time()
self.set_loading_state(
loading=True,
empty=True
)
def on_refreshed(has_item):
self.set_loading_state(loading=False, empty=not has_item)
self._restore_model_selection()
self.model.refreshed.disconnect()
self.refreshed.emit()
print("Duration: %.3fs" % (time.time() - time_start))
# Connect to signal
self.model.refreshed.connect(on_refreshed)
# Trigger signal before refresh is called
self.refresh_triggered.emit()
# Refresh model
self.model.refresh()
def refresh(self):
self._refresh_model()
def get_active_asset(self):
"""Return the asset item of the current selection."""
current = self.view.currentIndex()
return current.data(self.model.ItemRole)
def get_active_asset_document(self):
"""Return the asset document of the current selection."""
current = self.view.currentIndex()
return current.data(self.model.DocumentRole)
def get_active_index(self):
return self.view.currentIndex()
def get_selected_assets(self):
"""Return the documents of selected assets."""
selection = self.view.selectionModel()
rows = selection.selectedRows()
assets = [row.data(self.model.DocumentRole) for row in rows]
# NOTE: skip None object assumed they are silo (backwards comp.)
return [asset for asset in assets if asset]
def select_assets(self, assets, expand=True, key="name"):
"""Select assets by item key.
Args:
assets (list): List of asset values that can be found under
specified `key`
expand (bool): Whether to also expand to the asset in the view
key (string): Key that specifies where to look for `assets` values
Returns:
None
Default `key` is "name" in that case `assets` should contain single
asset name or list of asset names. (It is good idea to use "_id" key
instead of name in that case `assets` must contain `ObjectId` object/s)
It is expected that each value in `assets` will be found only once.
If the filters according to the `key` and `assets` correspond to
the more asset, only the first found will be selected.
"""
if not isinstance(assets, (tuple, list)):
assets = [assets]
# convert to list - tuple cant be modified
assets = set(assets)
# Clear selection
selection_model = self.view.selectionModel()
selection_model.clearSelection()
# Select
mode = selection_model.Select | selection_model.Rows
for index in lib.iter_model_rows(
self.proxy, column=0, include_root=False
):
# stop iteration if there are no assets to process
if not assets:
break
value = index.data(self.model.ItemRole).get(key)
if value not in assets:
continue
# Remove processed asset
assets.discard(value)
selection_model.select(index, mode)
if expand:
# Expand parent index
self.view.expand(self.proxy.parent(index))
# Set the currently active index
self.view.setCurrentIndex(index)
def set_loading_state(self, loading, empty):
if self.view.is_loading != loading:
if loading:
self.view.spinner.repaintNeeded.connect(
self.view.viewport().update
)
else:
self.view.spinner.repaintNeeded.disconnect()
self.view.is_loading = loading
self.view.is_empty = empty
def _store_model_selection(self):
index = self.view.currentIndex()
current = None
if index and index.isValid():
current = index.data(self.model.ObjectIdRole)
expanded = set()
model = self.view.model()
for index in lib.iter_model_rows(
model, column=0, include_root=False
):
if self.view.isExpanded(index):
value = index.data(self.model.ObjectIdRole)
expanded.add(value)
selection_model = self.view.selectionModel()
selected = None
selected_rows = selection_model.selectedRows()
if selected_rows:
selected = set(
row.data(self.model.ObjectIdRole)
for row in selected_rows
)
self.model_selection = {
"expanded": expanded,
"selected": selected,
"current": current
}
def _restore_model_selection(self):
model = self.view.model()
not_set = object()
expanded = self.model_selection.pop("expanded", not_set)
selected = self.model_selection.pop("selected", not_set)
current = self.model_selection.pop("current", not_set)
if (
expanded is not_set
or selected is not_set
or current is not_set
):
return
if expanded:
for index in lib.iter_model_rows(
model, column=0, include_root=False
):
is_expanded = index.data(self.model.ObjectIdRole) in expanded
self.view.setExpanded(index, is_expanded)
if not selected and not current:
self.set_current_session_asset()
return
current_index = None
selected_indexes = []
# Go through all indices, select the ones with similar data
for index in lib.iter_model_rows(
model, column=0, include_root=False
):
object_id = index.data(self.model.ObjectIdRole)
if object_id in selected:
selected_indexes.append(index)
if not current_index and object_id == current:
current_index = index
if current_index:
self.view.setCurrentIndex(current_index)
if not selected_indexes:
return
selection_model = self.view.selectionModel()
flags = selection_model.Select | selection_model.Rows
for index in selected_indexes:
# Ensure item is visible
self.view.scrollTo(index)
selection_model.select(index, flags)
def set_current_session_asset(self):
asset_name = self.dbcon.Session.get("AVALON_ASSET")
if asset_name:
self.select_assets([asset_name])
class OptionalMenu(QtWidgets.QMenu):
"""A subclass of `QtWidgets.QMenu` to work with `OptionalAction`