mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
first commit of added docstrings and comments
This commit is contained in:
parent
72e966db39
commit
56153c1d6c
3 changed files with 161 additions and 4 deletions
|
|
@ -2,12 +2,21 @@ import re
|
|||
from Qt import QtCore
|
||||
|
||||
|
||||
# Item identifier (unique ID - uuid4 is used)
|
||||
IDENTIFIER_ROLE = QtCore.Qt.UserRole + 1
|
||||
# Item has duplicated name (Asset and Task items)
|
||||
DUPLICATED_ROLE = QtCore.Qt.UserRole + 2
|
||||
# It is possible to move and rename items
|
||||
# - that is disabled if e.g. Asset has published content
|
||||
HIERARCHY_CHANGE_ABLE_ROLE = QtCore.Qt.UserRole + 3
|
||||
# Item is marked for deletion
|
||||
# - item will be deleted after hitting save
|
||||
REMOVED_ROLE = QtCore.Qt.UserRole + 4
|
||||
# Item type in string
|
||||
ITEM_TYPE_ROLE = QtCore.Qt.UserRole + 5
|
||||
# Item has opened editor (per column)
|
||||
EDITOR_OPENED_ROLE = QtCore.Qt.UserRole + 6
|
||||
|
||||
# Allowed symbols for any name
|
||||
NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_"
|
||||
NAME_REGEX = re.compile("^[" + NAME_ALLOWED_SYMBOLS + "]*$")
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ from .multiselection_combobox import MultiSelectionComboBox
|
|||
|
||||
|
||||
class ResizeEditorDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""Implementation of private method from QStyledItemDelegate.
|
||||
|
||||
Force editor to resize into item size.
|
||||
"""
|
||||
@staticmethod
|
||||
def _q_smart_min_size(editor):
|
||||
min_size_hint = editor.minimumSizeHint()
|
||||
|
|
@ -67,6 +71,16 @@ class ResizeEditorDelegate(QtWidgets.QStyledItemDelegate):
|
|||
|
||||
|
||||
class NumberDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""Delegate for number attributes.
|
||||
|
||||
Editor correspond passed arguments.
|
||||
|
||||
Args:
|
||||
minimum(int, float): Minimum possible value.
|
||||
maximum(int, float): Maximum possible value.
|
||||
decimals(int): How many decimal points can be used. Float will be used
|
||||
as value if is higher than 0.
|
||||
"""
|
||||
def __init__(self, minimum, maximum, decimals, *args, **kwargs):
|
||||
super(NumberDelegate, self).__init__(*args, **kwargs)
|
||||
self.minimum = minimum
|
||||
|
|
@ -80,10 +94,13 @@ class NumberDelegate(QtWidgets.QStyledItemDelegate):
|
|||
editor = QtWidgets.QSpinBox(parent)
|
||||
|
||||
editor.setObjectName("NumberEditor")
|
||||
# Set min/max
|
||||
editor.setMinimum(self.minimum)
|
||||
editor.setMaximum(self.maximum)
|
||||
# Hide spinbox buttons
|
||||
editor.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
|
||||
# Try to set value from item
|
||||
value = index.data(QtCore.Qt.EditRole)
|
||||
if value is not None:
|
||||
try:
|
||||
|
|
@ -98,6 +115,8 @@ class NumberDelegate(QtWidgets.QStyledItemDelegate):
|
|||
|
||||
|
||||
class NameDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""Specific delegate for "name" key."""
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
editor = NameTextEdit(parent)
|
||||
editor.setObjectName("NameEditor")
|
||||
|
|
@ -108,11 +127,26 @@ class NameDelegate(QtWidgets.QStyledItemDelegate):
|
|||
|
||||
|
||||
class TypeDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""Specific delegate for "type" key.
|
||||
|
||||
It is expected that will be used only for TaskItem which has modifiable
|
||||
type. Type values are defined with cached project document.
|
||||
|
||||
Args:
|
||||
project_doc_cache(ProjectDocCache): Project cache shared across all
|
||||
delegates (kind of a struct pointer).
|
||||
"""
|
||||
|
||||
def __init__(self, project_doc_cache, *args, **kwargs):
|
||||
self._project_doc_cache = project_doc_cache
|
||||
super(TypeDelegate, self).__init__(*args, **kwargs)
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
"""Editor is using filtrable combobox.
|
||||
|
||||
Editor should not be possible to create new items or set values that
|
||||
are not in this method.
|
||||
"""
|
||||
editor = FilterComboBox(parent)
|
||||
editor.setObjectName("TypeEditor")
|
||||
editor.style().polish(editor)
|
||||
|
|
@ -136,6 +170,18 @@ class TypeDelegate(QtWidgets.QStyledItemDelegate):
|
|||
|
||||
|
||||
class ToolsDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""Specific delegate for "tools_env" key.
|
||||
|
||||
Exected that editor will be used only on AssetItem which is only item that
|
||||
can have `tools_env` (except project).
|
||||
|
||||
Delegate requires tools cache which is shared across all ToolsDelegate
|
||||
objects.
|
||||
|
||||
Args:
|
||||
tools_cache (ToolsCache): Possible values of tools.
|
||||
"""
|
||||
|
||||
def __init__(self, tools_cache, *args, **kwargs):
|
||||
self._tools_cache = tools_cache
|
||||
super(ToolsDelegate, self).__init__(*args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,11 @@ from Qt import QtCore, QtGui
|
|||
|
||||
|
||||
class ProjectModel(QtGui.QStandardItemModel):
|
||||
project_changed = QtCore.Signal()
|
||||
"""Load possible projects to modify from MongoDB.
|
||||
|
||||
Mongo collection must contain project document with "type" "project" and
|
||||
matching "name" value with name of collection.
|
||||
"""
|
||||
|
||||
def __init__(self, dbcon, *args, **kwargs):
|
||||
self.dbcon = dbcon
|
||||
|
|
@ -31,6 +35,7 @@ class ProjectModel(QtGui.QStandardItemModel):
|
|||
super(ProjectModel, self).__init__(*args, **kwargs)
|
||||
|
||||
def refresh(self):
|
||||
"""Reload projects."""
|
||||
self.dbcon.Session["AVALON_PROJECT"] = None
|
||||
|
||||
project_items = []
|
||||
|
|
@ -63,6 +68,12 @@ class ProjectModel(QtGui.QStandardItemModel):
|
|||
|
||||
|
||||
class HierarchySelectionModel(QtCore.QItemSelectionModel):
|
||||
"""Selection model with defined allowed multiselection columns.
|
||||
|
||||
This model allows to select multiple rows and enter one of their
|
||||
editors to edit value of all selected rows.
|
||||
"""
|
||||
|
||||
def __init__(self, multiselection_columns, *args, **kwargs):
|
||||
super(HierarchySelectionModel, self).__init__(*args, **kwargs)
|
||||
self.multiselection_columns = multiselection_columns
|
||||
|
|
@ -78,6 +89,21 @@ class HierarchySelectionModel(QtCore.QItemSelectionModel):
|
|||
|
||||
|
||||
class HierarchyModel(QtCore.QAbstractItemModel):
|
||||
"""Main model for hierarchy modification and value changes.
|
||||
|
||||
Main part of ProjectManager.
|
||||
|
||||
Model should be able to load existing entities, create new, handle their
|
||||
validations like name duplication and validate if is possible to save it's
|
||||
data.
|
||||
|
||||
Args:
|
||||
dbcon (AvalonMongoDB): Connection to MongoDB with set AVALON_PROJECT in
|
||||
it's Session to current project.
|
||||
"""
|
||||
|
||||
# Definition of all possible columns with their labels in default order
|
||||
# - order is important as column names are used as keys for column indexes
|
||||
_columns_def = [
|
||||
("name", "Name"),
|
||||
("type", "Type"),
|
||||
|
|
@ -93,6 +119,8 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
("pixelAspect", "Pixel aspect"),
|
||||
("tools_env", "Tools")
|
||||
]
|
||||
# Columns allowing multiselection in edit mode
|
||||
# - gives ability to set all of keys below on multiple items at once
|
||||
multiselection_columns = {
|
||||
"frameStart",
|
||||
"frameEnd",
|
||||
|
|
@ -141,13 +169,19 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
return self._items_by_id
|
||||
|
||||
def _reset_root_item(self):
|
||||
"""Removes all previous content related to model."""
|
||||
self._root_item = RootItem(self)
|
||||
|
||||
def refresh_project(self):
|
||||
"""Reload project data and discard unsaved changes."""
|
||||
self.set_project(self._current_project, True)
|
||||
|
||||
@property
|
||||
def project_item(self):
|
||||
"""Access to current project item.
|
||||
|
||||
Model can have 0-1 ProjectItems at once.
|
||||
"""
|
||||
output = None
|
||||
for row in range(self._root_item.rowCount()):
|
||||
item = self._root_item.child(row)
|
||||
|
|
@ -157,25 +191,41 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
return output
|
||||
|
||||
def set_project(self, project_name, force=False):
|
||||
"""Change project and discard unsaved changes.
|
||||
|
||||
Args:
|
||||
project_name(str): New project name. Or None if just clearing
|
||||
content.
|
||||
force(bool): Force to change project even if project name is same
|
||||
as current project.
|
||||
"""
|
||||
if self._current_project == project_name and not force:
|
||||
return
|
||||
|
||||
# Clear all current content
|
||||
self.clear()
|
||||
|
||||
self._current_project = project_name
|
||||
|
||||
# Skip if project is None
|
||||
if not project_name:
|
||||
return
|
||||
|
||||
# Find project'd document
|
||||
project_doc = self.dbcon.database[project_name].find_one(
|
||||
{"type": "project"},
|
||||
ProjectItem.query_projection
|
||||
)
|
||||
# Skip if project document does not exist
|
||||
# - this shouldn't happen using only UI elements
|
||||
if not project_doc:
|
||||
return
|
||||
|
||||
# Create project item
|
||||
project_item = ProjectItem(project_doc)
|
||||
self.add_item(project_item)
|
||||
|
||||
# Query all assets of the project
|
||||
asset_docs = self.dbcon.database[project_name].find(
|
||||
{"type": "asset"},
|
||||
AssetItem.query_projection
|
||||
|
|
@ -185,7 +235,8 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
for asset_doc in asset_docs
|
||||
}
|
||||
|
||||
# Prepare booleans if asset item can be modified (name or hierarchy)
|
||||
# Check if asset have published content and prepare booleans
|
||||
# if asset item can be modified (name and hierarchy change)
|
||||
# - the same must be applied to all it's parents
|
||||
asset_ids = list(asset_docs_by_id.keys())
|
||||
result = []
|
||||
|
|
@ -214,6 +265,7 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
count = item["count"]
|
||||
asset_modifiable[asset_id] = count < 1
|
||||
|
||||
# Store assets by their visual parent to be able create their hierarchy
|
||||
asset_docs_by_parent_id = collections.defaultdict(list)
|
||||
for asset_doc in asset_docs_by_id.values():
|
||||
parent_id = asset_doc["data"].get("visualParent")
|
||||
|
|
@ -282,9 +334,11 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
|
||||
self.add_items(task_items, asset_item)
|
||||
|
||||
# Emit that project was successfully changed
|
||||
self.project_changed.emit()
|
||||
|
||||
def rowCount(self, parent=None):
|
||||
"""Number of rows for passed parent."""
|
||||
if parent is None or not parent.isValid():
|
||||
parent_item = self._root_item
|
||||
else:
|
||||
|
|
@ -292,9 +346,15 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
return parent_item.rowCount()
|
||||
|
||||
def columnCount(self, *args, **kwargs):
|
||||
"""Number of columns is static for this model."""
|
||||
return self.columns_len
|
||||
|
||||
def data(self, index, role):
|
||||
"""Access data for passed index and it's role.
|
||||
|
||||
Model is using principles implemented in BaseItem so converts passed
|
||||
index column into key and ask item to return value for passed role.
|
||||
"""
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
|
|
@ -305,18 +365,24 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
return item.data(role, key)
|
||||
|
||||
def setData(self, index, value, role=QtCore.Qt.EditRole):
|
||||
"""Store data to passed index under role.
|
||||
|
||||
Pass values to corresponding item and behave by it's result.
|
||||
"""
|
||||
if not index.isValid():
|
||||
return False
|
||||
|
||||
item = index.internalPointer()
|
||||
column = index.column()
|
||||
key = self.columns[column]
|
||||
# Capture asset name changes for duplcated asset names validation.
|
||||
if (
|
||||
key == "name"
|
||||
and role in (QtCore.Qt.EditRole, QtCore.Qt.DisplayRole)
|
||||
):
|
||||
self._rename_asset(item, value)
|
||||
|
||||
# Pass values to item and by result emi dataChanged signal or not
|
||||
result = item.setData(value, role, key)
|
||||
if result:
|
||||
self.dataChanged.emit(index, index, [role])
|
||||
|
|
@ -324,6 +390,7 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
return result
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
"""Header labels."""
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if section < self.columnCount():
|
||||
return self.column_labels[section]
|
||||
|
|
@ -333,6 +400,7 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
)
|
||||
|
||||
def flags(self, index):
|
||||
"""Index flags are defined by corresponding item."""
|
||||
item = index.internalPointer()
|
||||
if item is None:
|
||||
return QtCore.Qt.NoItemFlags
|
||||
|
|
@ -341,6 +409,11 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
return item.flags(key)
|
||||
|
||||
def parent(self, index=None):
|
||||
"""Parent for passed index as QModelIndex.
|
||||
|
||||
Args:
|
||||
index(QModelIndex): Parent index. Root item is used if not passed.
|
||||
"""
|
||||
if not index.isValid():
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
|
|
@ -354,7 +427,13 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
return self.createIndex(parent_item.row(), 0, parent_item)
|
||||
|
||||
def index(self, row, column, parent=None):
|
||||
"""Return index for row/column under parent"""
|
||||
"""Return index for row/column under parent.
|
||||
|
||||
Args:
|
||||
row(int): Row number.
|
||||
column(int): Column number.
|
||||
parent(QModelIndex): Parent index. Root item is used if not passed.
|
||||
"""
|
||||
parent_item = None
|
||||
if parent is not None and parent.isValid():
|
||||
parent_item = parent.internalPointer()
|
||||
|
|
@ -362,11 +441,31 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
return self.index_from_item(row, column, parent_item)
|
||||
|
||||
def index_for_item(self, item, column=0):
|
||||
"""Index for passet item.
|
||||
|
||||
This is for cases that index operations are required on specific item.
|
||||
|
||||
Args:
|
||||
item(BaseItem): Item from model that will be converted to
|
||||
corresponding QModelIndex.
|
||||
column(int): Which column will be part of returned index. By
|
||||
default is used column 0.
|
||||
"""
|
||||
return self.index_from_item(
|
||||
item.row(), column, item.parent()
|
||||
)
|
||||
|
||||
def index_from_item(self, row, column, parent=None):
|
||||
"""Index for passed row, column and parent item.
|
||||
|
||||
Same implementation as `index` method but "parent" is one of
|
||||
BaseItem objects instead of QModelIndex.
|
||||
|
||||
Args:
|
||||
row(int): Row number.
|
||||
column(int): Column number.
|
||||
parent(BaseItem): Parent item. Root item is used if not passed.
|
||||
"""
|
||||
if parent is None:
|
||||
parent = self._root_item
|
||||
|
||||
|
|
@ -377,15 +476,18 @@ class HierarchyModel(QtCore.QAbstractItemModel):
|
|||
return QtCore.QModelIndex()
|
||||
|
||||
def add_new_asset(self, source_index):
|
||||
"""Method to create new asset item in hierarchy."""
|
||||
item_id = source_index.data(IDENTIFIER_ROLE)
|
||||
item = self.items_by_id[item_id]
|
||||
|
||||
if isinstance(item, (RootItem, ProjectItem)):
|
||||
name = "ep"
|
||||
new_row = None
|
||||
else:
|
||||
elif isinstance(item, AssetItem):
|
||||
name = None
|
||||
new_row = item.rowCount()
|
||||
else:
|
||||
return
|
||||
|
||||
asset_data = {}
|
||||
if name:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue