From 528fa8db596260011997385eb9865af4c7be1e19 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Apr 2021 10:03:44 +0200 Subject: [PATCH 001/219] initial commit --- openpype/tools/project_manager/__init__.py | 6 + openpype/tools/project_manager/__main__.py | 30 + .../project_manager/__init__.py | 30 + .../project_manager/constants.py | 5 + .../project_manager/delegates.py | 23 + .../project_manager/project_manager/model.py | 562 ++++++++++++++++++ .../project_manager/project_manager/view.py | 180 ++++++ .../project_manager/project_manager/window.py | 56 ++ 8 files changed, 892 insertions(+) create mode 100644 openpype/tools/project_manager/__init__.py create mode 100644 openpype/tools/project_manager/__main__.py create mode 100644 openpype/tools/project_manager/project_manager/__init__.py create mode 100644 openpype/tools/project_manager/project_manager/constants.py create mode 100644 openpype/tools/project_manager/project_manager/delegates.py create mode 100644 openpype/tools/project_manager/project_manager/model.py create mode 100644 openpype/tools/project_manager/project_manager/view.py create mode 100644 openpype/tools/project_manager/project_manager/window.py diff --git a/openpype/tools/project_manager/__init__.py b/openpype/tools/project_manager/__init__.py new file mode 100644 index 00000000000..7d8f8bf4325 --- /dev/null +++ b/openpype/tools/project_manager/__init__.py @@ -0,0 +1,6 @@ +from .project_manager import Window + + +__all__ = ( + "Window", +) diff --git a/openpype/tools/project_manager/__main__.py b/openpype/tools/project_manager/__main__.py new file mode 100644 index 00000000000..3f29eb5a214 --- /dev/null +++ b/openpype/tools/project_manager/__main__.py @@ -0,0 +1,30 @@ +import os +import sys +paths = [ + r"C:\Users\iLLiCiT\PycharmProjects\pype3\.venv\Lib\site-packages", + r"C:\Users\iLLiCiT\PycharmProjects\pype3", + r"C:\Users\iLLiCiT\PycharmProjects\pype3\repos\avalon-core" +] +for path in paths: + sys.path.append(path) + +os.environ["OPENPYPE_DATABASE_NAME"] = "openpype" +os.environ["OPENPYPE_MONGO"] = "mongodb://localhost:2707" +os.environ["AVALON_TIMEOUT"] = "1000" + +from project_manager import Window + +from Qt import QtWidgets + + +def main(): + app = QtWidgets.QApplication([]) + + window = Window() + window.show() + + sys.exit(app.exec_()) + + +if __name__ == "__main__": + main() diff --git a/openpype/tools/project_manager/project_manager/__init__.py b/openpype/tools/project_manager/project_manager/__init__.py new file mode 100644 index 00000000000..1a538baa208 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/__init__.py @@ -0,0 +1,30 @@ +from .constants import ( + IDENTIFIER_ROLE +) +from .view import HierarchyView +from .model import ( + HierarchyModel, + HierarchySelectionModel, + BaseItem, + RootItem, + ProjectItem, + AssetItem, + TaskItem +) +from .window import Window + +__all__ = ( + "IDENTIFIER_ROLE", + + "HierarchyView", + + "HierarchyModel", + "HierarchySelectionModel", + "BaseItem", + "RootItem", + "ProjectItem", + "AssetItem", + "TaskItem", + + "Window" +) diff --git a/openpype/tools/project_manager/project_manager/constants.py b/openpype/tools/project_manager/project_manager/constants.py new file mode 100644 index 00000000000..6c84ee66358 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/constants.py @@ -0,0 +1,5 @@ +from Qt import QtWidgets, QtCore + + +IDENTIFIER_ROLE = QtCore.Qt.UserRole + 1 +COLUMNS_ROLE = QtCore.Qt.UserRole + 2 diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py new file mode 100644 index 00000000000..605d0cbbab8 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -0,0 +1,23 @@ +from Qt import QtWidgets, QtCore + + +class NumberDelegate(QtWidgets.QStyledItemDelegate): + def createEditor(self, parent, option, index): + editor = QtWidgets.QSpinBox(parent) + value = index.data(QtCore.Qt.DisplayRole) + if value is not None: + editor.setValue(value) + return editor + + # def updateEditorGeometry(self, editor, options, index): + # print(editor) + # return super().updateEditorGeometry(editor, options, index) + + +class StringDelegate(QtWidgets.QStyledItemDelegate): + def createEditor(self, parent, option, index): + editor = QtWidgets.QLineEdit(parent) + value = index.data(QtCore.Qt.DisplayRole) + if value is not None: + editor.setText(str(value)) + return editor diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py new file mode 100644 index 00000000000..d8341df8c14 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/model.py @@ -0,0 +1,562 @@ +import collections +from queue import Queue +from uuid import uuid4 + +from .constants import ( + IDENTIFIER_ROLE, + COLUMNS_ROLE +) + +from avalon.api import AvalonMongoDB + +from Qt import QtWidgets, QtCore + + +class HierarchySelectionModel(QtCore.QItemSelectionModel): + def setCurrentIndex(self, index, command): + # v = "" + # if command == QtCore.QItemSelectionModel.NoUpdate: + # v += "NoUpdate" + # if command & QtCore.QItemSelectionModel.Clear: + # v += "Clear" + # if command & QtCore.QItemSelectionModel.Select: + # v += "Select" + # if command & QtCore.QItemSelectionModel.Deselect: + # v += "Deselect" + # if command & QtCore.QItemSelectionModel.Toggle: + # v += "Toggle" + # if command & QtCore.QItemSelectionModel.Current: + # v += "Current" + # if command & QtCore.QItemSelectionModel.Rows: + # v += "Rows" + # if command & QtCore.QItemSelectionModel.Columns: + # v += "Columns" + if index.column() > 0: + if ( + command & QtCore.QItemSelectionModel.Clear + and command & QtCore.QItemSelectionModel.Select + ): + command = QtCore.QItemSelectionModel.NoUpdate + super(HierarchySelectionModel, self).setCurrentIndex(index, command) + + +class HierarchyModel(QtCore.QAbstractItemModel): + columns = [ + "name", + "type", + "frameStart", + "frameEnd", + "fps", + "resolutionWidth", + "resolutionHeight" + ] + + def __init__(self, parent=None): + super(HierarchyModel, self).__init__(parent) + self._root_item = None + self._items_by_id = {} + self.dbcon = AvalonMongoDB() + + self._hierarchy_mode = True + self._reset_root_item() + + def change_edit_mode(self, hiearchy_mode): + self._hierarchy_mode = hiearchy_mode + + @property + def items_by_id(self): + return self._items_by_id + + def _reset_root_item(self): + self._root_item = RootItem(self) + + def set_project(self, project_doc): + self.clear() + + item = ProjectItem(project_doc) + self.add_item(item) + + def rowCount(self, parent=None): + if parent is None or not parent.isValid(): + parent_item = self._root_item + else: + parent_item = parent.internalPointer() + return parent_item.childCount() + + def columnCount(self, *args, **kwargs): + return len(self.columns) + + def data(self, index, role): + if not index.isValid(): + return None + + column = index.column() + key = self.columns[column] + + item = index.internalPointer() + return item.data(key, role) + + def setData(self, index, value, role=QtCore.Qt.EditRole): + if not index.isValid(): + return False + + item = index.internalPointer() + column = index.column() + key = self.columns[column] + result = item.setData(key, value, role) + if result: + self.dataChanged.emit(index, index, [role]) + + return result + + def headerData(self, section, orientation, role): + if role == QtCore.Qt.DisplayRole: + if section < len(self.columns): + return self.columns[section] + + super(HierarchyModel, self).headerData(section, orientation, role) + + def flags(self, index): + item = index.internalPointer() + column = index.column() + key = self.columns[column] + return item.flags(key) + + def parent(self, index): + item = index.internalPointer() + parent_item = item.parent() + + # If it has no parents we return invalid + if not parent_item or parent_item is self._root_item: + return QtCore.QModelIndex() + + return self.createIndex(parent_item.row(), 0, parent_item) + + def index(self, row, column, parent=None): + """Return index for row/column under parent""" + parent_item = None + if parent is not None and parent.isValid(): + parent_item = parent.internalPointer() + + return self.index_from_item(row, column, parent_item) + + def index_for_item(self, item, column=0): + return self.index_from_item( + item.row(), column, item.parent() + ) + + def index_from_item(self, row, column, parent=None): + if parent is None: + parent = self._root_item + + child_item = parent.child(row) + if child_item: + return self.createIndex(row, column, child_item) + + return QtCore.QModelIndex() + + def add_new_asset(self, source_index): + item_id = source_index.data(IDENTIFIER_ROLE) + item = self.items_by_id[item_id] + + if isinstance(item, (RootItem, ProjectItem)): + name = "eq" + parent = item + else: + name = source_index.data(QtCore.Qt.DisplayRole) + parent = item.parent() + + data = {"name": name} + new_child = AssetItem(data) + return self.add_item(new_child, parent) + + def add_new_task(self, parent): + pass + + def add_new_item(self, parent): + data = {"name": "Test {}".format(parent.childCount())} + new_child = AssetItem(data) + + return self.add_item(new_child, parent) + + def add_item(self, item, parent=None): + if parent is None: + parent = self._root_item + + idx = parent.childCount() + + parent_index = self.index_from_item(parent.row(), 0, parent.parent()) + self.beginInsertRows(parent_index, idx, idx) + + if item.parent() is not parent: + item.set_parent(parent) + + parent.add_child(item) + + if item.id not in self._items_by_id: + self._items_by_id[item.id] = item + + self.endInsertRows() + + self.rowsInserted.emit(parent_index, idx, idx) + + return self.index_from_item(idx, 0, parent) + + def remove_index(self, index): + if not index.isValid(): + return + + item_id = index.data(IDENTIFIER_ROLE) + item = self._items_by_id[item_id] + if isinstance(item, (RootItem, ProjectItem)): + return + + parent = item.parent() + all_descendants = collections.defaultdict(dict) + all_descendants[parent.id][item.id] = item + + row = item.row() + self.beginRemoveRows(self.index_for_item(parent), row, row) + + todo_queue = Queue() + todo_queue.put(item) + while not todo_queue.empty(): + _item = todo_queue.get() + for row in range(_item.childCount()): + child_item = _item.child(row) + all_descendants[_item.id][child_item.id] = child_item + todo_queue.put(child_item) + + while all_descendants: + for parent_id in tuple(all_descendants.keys()): + children = all_descendants[parent_id] + if not children: + all_descendants.pop(parent_id) + continue + + for child_id in tuple(children.keys()): + child_item = children[child_id] + if child_id in all_descendants: + continue + + children.pop(child_id) + child_item.set_parent(None) + self._items_by_id.pop(child_id) + + self.endRemoveRows() + + def move_vertical(self, index, direction): + if not index.isValid(): + return + + item_id = index.data(IDENTIFIER_ROLE) + if item_id is None: + return + + item = self._items_by_id[item_id] + if isinstance(item, (RootItem, ProjectItem)): + return + + if abs(direction) != 1: + return + + # Move under parent of parent + src_row = item.row() + src_parent = item.parent() + src_parent_index = self.index_from_item( + src_parent.row(), 0, src_parent.parent() + ) + + dst_row = None + dst_parent = None + dst_parent_index = None + + if direction == -1: + if isinstance(src_parent, (RootItem, ProjectItem)): + return + dst_parent = src_parent.parent() + dst_row = src_parent.row() + 1 + + # Move under parent before or after if before is None + elif direction == 1: + if src_parent.childCount() == 1: + return + + if item.row() == 0: + parent_row = item.row() + 1 + else: + parent_row = item.row() - 1 + dst_parent = src_parent.child(parent_row) + dst_row = dst_parent.childCount() + + if src_parent is dst_parent: + return + + if dst_parent_index is None: + dst_parent_index = self.index_from_item( + dst_parent.row(), 0, dst_parent.parent() + ) + + self.beginMoveRows( + src_parent_index, + src_row, + src_row, + dst_parent_index, + dst_row + ) + src_parent.remove_child(item) + dst_parent.add_child(item) + item.set_parent(dst_parent) + dst_parent.move_to(item, dst_row) + + self.endMoveRows() + + def move_horizontal(self, index, direction): + if not index.isValid(): + return + + item_id = index.data(IDENTIFIER_ROLE) + item = self._items_by_id[item_id] + if isinstance(item, (RootItem, ProjectItem)): + return + + if abs(direction) != 1: + return + + src_parent = item.parent() + src_parent_index = self.index_from_item( + src_parent.row(), 0, src_parent.parent() + ) + source_row = item.row() + + dst_parent = None + dst_parent_index = None + destination_row = None + _destination_row = None + # Down + if direction == 1: + if source_row < src_parent.childCount() - 1: + dst_parent_index = src_parent_index + dst_parent = src_parent + destination_row = source_row + 1 + # This row is not row number after moving but before moving + _destination_row = destination_row + 1 + else: + destination_row = 0 + parent_parent = src_parent.parent() + if not parent_parent: + return + + new_parent = parent_parent.child(src_parent.row() + 1) + if not new_parent: + return + dst_parent = new_parent + + # Up + elif direction == -1: + if source_row > 0: + dst_parent_index = src_parent_index + dst_parent = src_parent + destination_row = source_row - 1 + else: + parent_parent = src_parent.parent() + if not parent_parent: + return + + previous_parent = parent_parent.child(src_parent.row() - 1) + if not previous_parent: + return + dst_parent = previous_parent + destination_row = previous_parent.childCount() + + if dst_parent_index is None: + dst_parent_index = self.index_from_item( + dst_parent.row(), 0, dst_parent.parent() + ) + + if _destination_row is None: + _destination_row = destination_row + + self.beginMoveRows( + src_parent_index, + source_row, + source_row, + dst_parent_index, + _destination_row + ) + + if src_parent is dst_parent: + dst_parent.move_to(item, destination_row) + + else: + src_parent.remove_child(item) + dst_parent.add_child(item) + item.set_parent(dst_parent) + dst_parent.move_to(item, destination_row) + + self.endMoveRows() + + def child_removed(self, child): + self._items_by_id.pop(child.id, None) + + def column_name(self, column): + """Return column key by index""" + if column < len(self.columns): + return self.columns[column] + return None + + def clear(self): + self.beginResetModel() + self._reset_root_item() + self.endResetModel() + + +class BaseItem: + columns = ["name"] + + def __init__(self, data=None): + self._id = uuid4() + self._children = list() + self._parent = None + + self._data = { + key: None + for key in self.columns + } + self._source_data = data + if data: + for key, value in data.items(): + if key in self.columns: + self._data[key] = value + + def model(self): + return self._parent.model + + def move_to(self, item, row): + idx = self._children.index(item) + if idx == row: + return + + self._children.pop(idx) + self._children.insert(row, item) + + def data(self, key, role): + if role == IDENTIFIER_ROLE: + return self._id + + if role == COLUMNS_ROLE: + return self.columns + + if key not in self.columns: + return None + + if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole): + value = self._data[key] + if value is None: + value = self.parent().data(key, role) + return value + + return None + + def setData(self, key, value, role): + if key not in self.columns: + return False + + if role == QtCore.Qt.EditRole: + self._data[key] = value + + # must return true if successful + return True + + return False + + @property + def id(self): + return self._id + + def childCount(self): + return len(self._children) + + def child(self, row): + if -1 < row < self.childCount(): + return self._children[row] + return None + + def children(self): + return self._children + + def child_row(self, child): + if child not in self._children: + return -1 + return self._children.index(child) + + def parent(self): + return self._parent + + def set_parent(self, parent): + if parent is self._parent: + return + + if self._parent: + self._parent.remove_child(self) + self._parent = parent + + def row(self): + if self._parent is not None: + return self._parent.child_row(self) + return -1 + + def add_child(self, item): + if item not in self._children: + self._children.append(item) + + def remove_child(self, item): + if item in self._children: + self._children.remove(item) + + def flags(self, key): + flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + if key in self.columns: + flags |= QtCore.Qt.ItemIsEditable + return flags + + +class RootItem(BaseItem): + def __init__(self, model): + super(RootItem, self).__init__() + self._model = model + + def model(self): + return self._model + + def flags(self, *args, **kwargs): + return QtCore.Qt.NoItemFlags + + +class ProjectItem(BaseItem): + def __init__(self, data=None): + super(ProjectItem, self).__init__(data) + self._data["name"] = "project" + + def flags(self, *args, **kwargs): + return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + + +class AssetItem(BaseItem): + columns = [ + "name", + "type", + "frameStart", + "frameEnd", + "fps", + "resolutionWidth", + "resolutionHeight" + ] + + def __init__(self, data=None): + super(AssetItem, self).__init__(data) + + +class TaskItem(BaseItem): + def __init__(self, data=None): + super(TaskItem, self).__init__(data) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py new file mode 100644 index 00000000000..d49b5e3b52c --- /dev/null +++ b/openpype/tools/project_manager/project_manager/view.py @@ -0,0 +1,180 @@ +from Qt import QtWidgets, QtCore + +from .constants import ( + IDENTIFIER_ROLE, + COLUMNS_ROLE +) +from .delegates import NumberDelegate, StringDelegate + + +class HierarchyView(QtWidgets.QTreeView): + """A tree view that deselects on clicking on an empty area in the view""" + column_delegate_defs = { + "name": StringDelegate, + "frameStart": NumberDelegate, + "frameEnd": NumberDelegate, + "fps": NumberDelegate, + "resolutionWidth": NumberDelegate, + "resolutionHeight": NumberDelegate + } + persistent_columns = [ + "frameStart", "frameEnd", "fps", "resolutionWidth", "resolutionHeight" + ] + + def __init__(self, source_model, *args, **kwargs): + super(HierarchyView, self).__init__(*args, **kwargs) + self._source_model = source_model + + main_delegate = QtWidgets.QStyledItemDelegate() + self.setItemDelegate(main_delegate) + self.setAlternatingRowColors(True) + self.setSelectionMode(HierarchyView.ContiguousSelection) + + column_delegates = {} + column_key_to_index = {} + for key, delegate_klass in self.column_delegate_defs.items(): + delegate = delegate_klass() + column = self._source_model.columns.index(key) + self.setItemDelegateForColumn(column, delegate) + column_delegates[key] = delegate + column_key_to_index[key] = column + + self._delegate = main_delegate + self._column_delegates = column_delegates + self._column_key_to_index = column_key_to_index + + def commitData(self, editor): + super(HierarchyView, self).commitData(editor) + current_index = self.currentIndex() + column = current_index.column() + row = current_index.row() + skipped_index = None + if column > 0: + indexes = [] + for index in self.selectedIndexes(): + if index.column() == column: + if index.row() == row: + skipped_index = index + else: + indexes.append(index) + + if skipped_index is not None: + value = current_index.data(QtCore.Qt.EditRole) + for index in indexes: + index.model().setData(index, value, QtCore.Qt.EditRole) + + # Update children data + self.updateEditorData() + + def _deselect_editor(self, editor): + if editor: + if isinstance(editor, QtWidgets.QSpinBox): + line_edit = editor.findChild(QtWidgets.QLineEdit) + line_edit.deselect() + + elif isinstance(editor, QtWidgets.QLineEdit): + editor.deselect() + + def edit(self, index, *args, **kwargs): + result = super(HierarchyView, self).edit(index, *args, **kwargs) + self._deselect_editor(self.indexWidget(index)) + return result + + def openPersistentEditor(self, index): + super(HierarchyView, self).openPersistentEditor(index) + self._deselect_editor(self.indexWidget(index)) + + def rowsInserted(self, parent_index, start, end): + super(HierarchyView, self).rowsInserted(parent_index, start, end) + + for row in range(start, end + 1): + index = self._source_model.index(row, 0, parent_index) + columns = index.data(COLUMNS_ROLE) or [] + for key, column in self._column_key_to_index.items(): + if key not in self.persistent_columns: + continue + col_index = self._source_model.index(row, column, parent_index) + self.openPersistentEditor(col_index) + + # Expand parent on insert + if not self.isExpanded(parent_index): + self.expand(parent_index) + + def mousePressEvent(self, event): + index = self.indexAt(event.pos()) + if not index.isValid(): + # clear the selection + self.clearSelection() + # clear the current index + self.setCurrentIndex(QtCore.QModelIndex()) + + super(HierarchyView, self).mousePressEvent(event) + + def keyPressEvent(self, event): + call_super = False + if event.key() == QtCore.Qt.Key_Delete: + self._delete_item() + + elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): + if event.modifiers() == QtCore.Qt.ShiftModifier: + self._on_shift_enter_pressed() + else: + if self.state() == HierarchyView.NoState: + self._on_enter_pressed() + + elif event.modifiers() == QtCore.Qt.ControlModifier: + if event.key() == QtCore.Qt.Key_Left: + self._on_left_ctrl_pressed() + elif event.key() == QtCore.Qt.Key_Right: + self._on_right_ctrl_pressed() + elif event.key() == QtCore.Qt.Key_Up: + self._on_up_ctrl_pressed() + elif event.key() == QtCore.Qt.Key_Down: + self._on_down_ctrl_pressed() + else: + call_super = True + + if call_super: + super(HierarchyView, self).keyPressEvent(event) + else: + event.accept() + + def _delete_item(self): + index = self.currentIndex() + self._source_model.remove_index(index) + + def _on_shift_enter_pressed(self): + index = self.currentIndex() + if not index.isValid(): + return + + # Stop editing + self.setState(HierarchyView.NoState) + QtWidgets.QApplication.processEvents() + + new_index = self._source_model.add_new_asset(index) + + # Change current index + self.setCurrentIndex(new_index) + # Start editing + self.edit(new_index) + + def _on_up_ctrl_pressed(self): + self._source_model.move_horizontal(self.currentIndex(), -1) + + def _on_down_ctrl_pressed(self): + self._source_model.move_horizontal(self.currentIndex(), 1) + + def _on_left_ctrl_pressed(self): + self._source_model.move_vertical(self.currentIndex(), -1) + + def _on_right_ctrl_pressed(self): + self._source_model.move_vertical(self.currentIndex(), 1) + + def _on_enter_pressed(self): + index = self.currentIndex() + if ( + index.isValid() + and index.flags() & QtCore.Qt.ItemIsEditable + ): + self.edit(index) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py new file mode 100644 index 00000000000..db5527b4ac2 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/window.py @@ -0,0 +1,56 @@ +from Qt import QtWidgets, QtCore + +from . import ( + HierarchyModel, + HierarchySelectionModel, + HierarchyView +) + + +class Window(QtWidgets.QWidget): + def __init__(self, parent=None): + super(Window, self).__init__(parent) + + model = HierarchyModel() + view = HierarchyView(model, self) + view.setModel(model) + _selection_model = HierarchySelectionModel() + _selection_model.setModel(view.model()) + view.setSelectionModel(_selection_model) + + header = view.header() + header.setStretchLastSection(False) + header.setSectionResizeMode( + header.logicalIndex(0), QtWidgets.QHeaderView.Stretch + ) + checkbox = QtWidgets.QCheckBox(self) + # btn = QtWidgets.QPushButton("Refresh") + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.addWidget(view) + main_layout.addWidget(checkbox) + # main_layout.addWidget(btn) + # btn.clicked.connect(self._on_refresh) + + checkbox.toggled.connect(self._on_checkbox) + + self.view = view + self.model = model + # self.btn = btn + self.checkbox = checkbox + + self.change_edit_mode() + + self.resize(1200, 600) + model.set_project({"name": "test"}) + + def change_edit_mode(self, value=None): + if value is None: + value = self.checkbox.isChecked() + self.model.change_edit_mode(value) + + def _on_checkbox(self, value): + self.change_edit_mode(value) + + def _on_refresh(self): + self.model.clear() From f6f779e127033cb147d9605a108ca10b012ec307 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Apr 2021 10:55:24 +0200 Subject: [PATCH 002/219] removed develop code --- openpype/tools/project_manager/__main__.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/openpype/tools/project_manager/__main__.py b/openpype/tools/project_manager/__main__.py index 3f29eb5a214..0855a0fc71f 100644 --- a/openpype/tools/project_manager/__main__.py +++ b/openpype/tools/project_manager/__main__.py @@ -1,16 +1,4 @@ -import os import sys -paths = [ - r"C:\Users\iLLiCiT\PycharmProjects\pype3\.venv\Lib\site-packages", - r"C:\Users\iLLiCiT\PycharmProjects\pype3", - r"C:\Users\iLLiCiT\PycharmProjects\pype3\repos\avalon-core" -] -for path in paths: - sys.path.append(path) - -os.environ["OPENPYPE_DATABASE_NAME"] = "openpype" -os.environ["OPENPYPE_MONGO"] = "mongodb://localhost:2707" -os.environ["AVALON_TIMEOUT"] = "1000" from project_manager import Window From 3a139256acc1f18c5fe08f86519742afd7196ccc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Apr 2021 19:02:53 +0200 Subject: [PATCH 003/219] task item has basic implementation --- .../project_manager/project_manager/model.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index d8341df8c14..aa3380b87fb 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -170,8 +170,19 @@ def add_new_asset(self, source_index): new_child = AssetItem(data) return self.add_item(new_child, parent) - def add_new_task(self, parent): - pass + def add_new_task(self, source_index): + item_id = source_index.data(IDENTIFIER_ROLE) + item = self.items_by_id[item_id] + + if not isinstance(item, AssetItem): + return None + + name = "task" + parent = item.parent() + + data = {"name": name} + new_child = TaskItem(data) + return self.add_item(new_child, parent) def add_new_item(self, parent): data = {"name": "Test {}".format(parent.childCount())} From 0ba22be07d380ebee17e681557eaa94078974369 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Apr 2021 19:03:06 +0200 Subject: [PATCH 004/219] remove not needed debug --- .../project_manager/project_manager/model.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index aa3380b87fb..63b833910ea 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -14,23 +14,6 @@ class HierarchySelectionModel(QtCore.QItemSelectionModel): def setCurrentIndex(self, index, command): - # v = "" - # if command == QtCore.QItemSelectionModel.NoUpdate: - # v += "NoUpdate" - # if command & QtCore.QItemSelectionModel.Clear: - # v += "Clear" - # if command & QtCore.QItemSelectionModel.Select: - # v += "Select" - # if command & QtCore.QItemSelectionModel.Deselect: - # v += "Deselect" - # if command & QtCore.QItemSelectionModel.Toggle: - # v += "Toggle" - # if command & QtCore.QItemSelectionModel.Current: - # v += "Current" - # if command & QtCore.QItemSelectionModel.Rows: - # v += "Rows" - # if command & QtCore.QItemSelectionModel.Columns: - # v += "Columns" if index.column() > 0: if ( command & QtCore.QItemSelectionModel.Clear From 81958d7461ef45373c35722cde132ff59f82b92c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Apr 2021 19:03:30 +0200 Subject: [PATCH 005/219] added ability to create task with ctrl+shift+enter --- .../project_manager/project_manager/view.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index d49b5e3b52c..62a2f225061 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -116,7 +116,10 @@ def keyPressEvent(self, event): self._delete_item() elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): - if event.modifiers() == QtCore.Qt.ShiftModifier: + mdfs = event.modifiers() + if mdfs == (QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier): + self._on_ctrl_shift_enter_pressed() + elif mdfs == QtCore.Qt.ShiftModifier: self._on_shift_enter_pressed() else: if self.state() == HierarchyView.NoState: @@ -143,6 +146,24 @@ def _delete_item(self): index = self.currentIndex() self._source_model.remove_index(index) + def _on_ctrl_shift_enter_pressed(self): + index = self.currentIndex() + if not index.isValid(): + return + + new_index = self._source_model.add_new_task(index) + if new_index is None: + return + + # Stop editing + self.setState(HierarchyView.NoState) + QtWidgets.QApplication.processEvents() + + # Change current index + self.setCurrentIndex(new_index) + # Start editing + self.edit(new_index) + def _on_shift_enter_pressed(self): index = self.currentIndex() if not index.isValid(): From dcb51bfaf3a3ea891b78092b79c7258bc8b4c2fd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 22 Apr 2021 19:04:02 +0200 Subject: [PATCH 006/219] columns definitions per line --- openpype/tools/project_manager/project_manager/view.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 62a2f225061..e35663c24f6 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -18,7 +18,11 @@ class HierarchyView(QtWidgets.QTreeView): "resolutionHeight": NumberDelegate } persistent_columns = [ - "frameStart", "frameEnd", "fps", "resolutionWidth", "resolutionHeight" + "frameStart", + "frameEnd", + "fps", + "resolutionWidth", + "resolutionHeight" ] def __init__(self, source_model, *args, **kwargs): From 57ba2a547d528204da3dc9d6acf826acb0d60e22 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 23 Apr 2021 09:55:24 +0200 Subject: [PATCH 007/219] replaced chidCount with rowCount --- .../project_manager/project_manager/model.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 63b833910ea..bdb0da64924 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -9,7 +9,7 @@ from avalon.api import AvalonMongoDB -from Qt import QtWidgets, QtCore +from Qt import QtCore class HierarchySelectionModel(QtCore.QItemSelectionModel): @@ -64,7 +64,7 @@ def rowCount(self, parent=None): parent_item = self._root_item else: parent_item = parent.internalPointer() - return parent_item.childCount() + return parent_item.rowCount() def columnCount(self, *args, **kwargs): return len(self.columns) @@ -168,7 +168,7 @@ def add_new_task(self, source_index): return self.add_item(new_child, parent) def add_new_item(self, parent): - data = {"name": "Test {}".format(parent.childCount())} + data = {"name": "Test {}".format(parent.rowCount())} new_child = AssetItem(data) return self.add_item(new_child, parent) @@ -177,7 +177,7 @@ def add_item(self, item, parent=None): if parent is None: parent = self._root_item - idx = parent.childCount() + idx = parent.rowCount() parent_index = self.index_from_item(parent.row(), 0, parent.parent()) self.beginInsertRows(parent_index, idx, idx) @@ -216,7 +216,7 @@ def remove_index(self, index): todo_queue.put(item) while not todo_queue.empty(): _item = todo_queue.get() - for row in range(_item.childCount()): + for row in range(_item.rowCount()): child_item = _item.child(row) all_descendants[_item.id][child_item.id] = child_item todo_queue.put(child_item) @@ -273,7 +273,7 @@ def move_vertical(self, index, direction): # Move under parent before or after if before is None elif direction == 1: - if src_parent.childCount() == 1: + if src_parent.rowCount() == 1: return if item.row() == 0: @@ -281,7 +281,7 @@ def move_vertical(self, index, direction): else: parent_row = item.row() - 1 dst_parent = src_parent.child(parent_row) - dst_row = dst_parent.childCount() + dst_row = dst_parent.rowCount() if src_parent is dst_parent: return @@ -329,7 +329,7 @@ def move_horizontal(self, index, direction): _destination_row = None # Down if direction == 1: - if source_row < src_parent.childCount() - 1: + if source_row < src_parent.rowCount() - 1: dst_parent_index = src_parent_index dst_parent = src_parent destination_row = source_row + 1 @@ -361,7 +361,7 @@ def move_horizontal(self, index, direction): if not previous_parent: return dst_parent = previous_parent - destination_row = previous_parent.childCount() + destination_row = previous_parent.rowCount() if dst_parent_index is None: dst_parent_index = self.index_from_item( @@ -468,11 +468,11 @@ def setData(self, key, value, role): def id(self): return self._id - def childCount(self): + def rowCount(self): return len(self._children) def child(self, row): - if -1 < row < self.childCount(): + if -1 < row < self.rowCount(): return self._children[row] return None From e4282babb510a71225b25468d27cbe9b21f02047 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 23 Apr 2021 13:23:05 +0200 Subject: [PATCH 008/219] cleanup unused parts --- openpype/tools/project_manager/project_manager/constants.py | 2 +- openpype/tools/project_manager/project_manager/view.py | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/constants.py b/openpype/tools/project_manager/project_manager/constants.py index 6c84ee66358..21db80e5f7a 100644 --- a/openpype/tools/project_manager/project_manager/constants.py +++ b/openpype/tools/project_manager/project_manager/constants.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore +from Qt import QtCore IDENTIFIER_ROLE = QtCore.Qt.UserRole + 1 diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index e35663c24f6..606edbda99a 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -1,9 +1,5 @@ from Qt import QtWidgets, QtCore -from .constants import ( - IDENTIFIER_ROLE, - COLUMNS_ROLE -) from .delegates import NumberDelegate, StringDelegate @@ -92,8 +88,6 @@ def rowsInserted(self, parent_index, start, end): super(HierarchyView, self).rowsInserted(parent_index, start, end) for row in range(start, end + 1): - index = self._source_model.index(row, 0, parent_index) - columns = index.data(COLUMNS_ROLE) or [] for key, column in self._column_key_to_index.items(): if key not in self.persistent_columns: continue From 5e48a87531650562829e1f369405120f1bd78c36 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 23 Apr 2021 13:23:30 +0200 Subject: [PATCH 009/219] added basic icons --- .../project_manager/project_manager/model.py | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index bdb0da64924..721a1c88e46 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -8,6 +8,7 @@ ) from avalon.api import AvalonMongoDB +from avalon.vendor import qtawesome from Qt import QtCore @@ -407,6 +408,7 @@ def clear(self): class BaseItem: columns = ["name"] + _name_icon = None def __init__(self, data=None): self._id = uuid4() @@ -423,6 +425,10 @@ def __init__(self, data=None): if key in self.columns: self._data[key] = value + @classmethod + def name_icon(cls): + return cls._name_icon + def model(self): return self._parent.model @@ -450,6 +456,8 @@ def data(self, key, role): value = self.parent().data(key, role) return value + if role == QtCore.Qt.DecorationRole and key == "name": + return self.name_icon() return None def setData(self, key, value, role): @@ -500,9 +508,23 @@ def row(self): return self._parent.child_row(self) return -1 - def add_child(self, item): - if item not in self._children: + def add_child(self, item, row=None): + if item in self._children: + return + + row_count = self.rowCount() + if row is None or row == row_count: self._children.append(item) + return + + if row > row_count or row < 0: + raise ValueError( + "Invalid row number {} expected range 0 - {}".format( + row, row_count + ) + ) + + self._children.insert(row, item) def remove_child(self, item): if item in self._children: @@ -547,10 +569,16 @@ class AssetItem(BaseItem): "resolutionHeight" ] - def __init__(self, data=None): - super(AssetItem, self).__init__(data) + @classmethod + def name_icon(cls): + if cls._name_icon is None: + cls._name_icon = qtawesome.icon("fa.folder", color="#333333") + return cls._name_icon class TaskItem(BaseItem): - def __init__(self, data=None): - super(TaskItem, self).__init__(data) + @classmethod + def name_icon(cls): + if cls._name_icon is None: + cls._name_icon = qtawesome.icon("fa.file-o", color="#333333") + return cls._name_icon From b224216cf55d76c34e0dd566835ad7a7cf38c958 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 23 Apr 2021 13:23:43 +0200 Subject: [PATCH 010/219] it is possible to add asset directly under current selection --- .../project_manager/project_manager/model.py | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 721a1c88e46..7b54f2682fe 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -143,28 +143,33 @@ def add_new_asset(self, source_index): item_id = source_index.data(IDENTIFIER_ROLE) item = self.items_by_id[item_id] + new_row = None if isinstance(item, (RootItem, ProjectItem)): name = "eq" parent = item else: name = source_index.data(QtCore.Qt.DisplayRole) parent = item.parent() + new_row = item.row() + 1 data = {"name": name} new_child = AssetItem(data) - return self.add_item(new_child, parent) - def add_new_task(self, source_index): - item_id = source_index.data(IDENTIFIER_ROLE) + return self.add_item(new_child, parent, new_row) + + def add_new_task(self, parent_index): + item_id = parent_index.data(IDENTIFIER_ROLE) item = self.items_by_id[item_id] - if not isinstance(item, AssetItem): - return None + if isinstance(item, TaskItem): + parent = item.parent() + else: + parent = item - name = "task" - parent = item.parent() + if not isinstance(parent, AssetItem): + return None - data = {"name": name} + data = {"name": "task"} new_child = TaskItem(data) return self.add_item(new_child, parent) @@ -174,28 +179,29 @@ def add_new_item(self, parent): return self.add_item(new_child, parent) - def add_item(self, item, parent=None): + def add_item(self, item, parent=None, row=None): if parent is None: parent = self._root_item - idx = parent.rowCount() + if row is None: + row = parent.rowCount() parent_index = self.index_from_item(parent.row(), 0, parent.parent()) - self.beginInsertRows(parent_index, idx, idx) + self.beginInsertRows(parent_index, row, row) if item.parent() is not parent: item.set_parent(parent) - parent.add_child(item) + parent.add_child(item, row) if item.id not in self._items_by_id: self._items_by_id[item.id] = item self.endInsertRows() - self.rowsInserted.emit(parent_index, idx, idx) + self.rowsInserted.emit(parent_index, row, row) - return self.index_from_item(idx, 0, parent) + return self.index_from_item(row, 0, parent) def remove_index(self, index): if not index.isValid(): From e88f7c3e333d4eb809516df1ef4b71cfb69d4e49 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 23 Apr 2021 13:26:19 +0200 Subject: [PATCH 011/219] avoid moving task under project --- openpype/tools/project_manager/project_manager/model.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 7b54f2682fe..8c233564ff6 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -293,6 +293,12 @@ def move_vertical(self, index, direction): if src_parent is dst_parent: return + if ( + isinstance(item, TaskItem) + and not isinstance(dst_parent, AssetItem) + ): + return + if dst_parent_index is None: dst_parent_index = self.index_from_item( dst_parent.row(), 0, dst_parent.parent() From f32cd5ced5fa11b94ea677f35ae6fe7ca3d21e82 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 23 Apr 2021 13:41:38 +0200 Subject: [PATCH 012/219] added some kind of check of editability --- openpype/tools/project_manager/project_manager/model.py | 4 ++++ openpype/tools/project_manager/project_manager/view.py | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 8c233564ff6..867adbaef35 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -589,6 +589,10 @@ def name_icon(cls): class TaskItem(BaseItem): + columns = [ + "name", + "type" + ] @classmethod def name_icon(cls): if cls._name_icon is None: diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 606edbda99a..7843254f87f 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -92,7 +92,11 @@ def rowsInserted(self, parent_index, start, end): if key not in self.persistent_columns: continue col_index = self._source_model.index(row, column, parent_index) - self.openPersistentEditor(col_index) + if bool( + self._source_model.flags(col_index) + & QtCore.Qt.ItemIsEditable + ): + self.openPersistentEditor(col_index) # Expand parent on insert if not self.isExpanded(parent_index): From c45af6caebd6ad7463f97d9db3a56b1a2458dd64 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 23 Apr 2021 17:23:43 +0200 Subject: [PATCH 013/219] init commit asset name duplicity validation --- .../project_manager/constants.py | 2 +- .../project_manager/project_manager/model.py | 59 +++++++++++++++---- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/constants.py b/openpype/tools/project_manager/project_manager/constants.py index 21db80e5f7a..61d1944979d 100644 --- a/openpype/tools/project_manager/project_manager/constants.py +++ b/openpype/tools/project_manager/project_manager/constants.py @@ -2,4 +2,4 @@ IDENTIFIER_ROLE = QtCore.Qt.UserRole + 1 -COLUMNS_ROLE = QtCore.Qt.UserRole + 2 +DUPLICATED_ROLE = QtCore.Qt.UserRole + 2 diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 867adbaef35..47fc6081129 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -4,7 +4,7 @@ from .constants import ( IDENTIFIER_ROLE, - COLUMNS_ROLE + DUPLICATED_ROLE ) from avalon.api import AvalonMongoDB @@ -39,6 +39,7 @@ def __init__(self, parent=None): super(HierarchyModel, self).__init__(parent) self._root_item = None self._items_by_id = {} + self._asset_items_by_name = collections.defaultdict(list) self.dbcon = AvalonMongoDB() self._hierarchy_mode = True @@ -87,6 +88,12 @@ def setData(self, index, value, role=QtCore.Qt.EditRole): item = index.internalPointer() column = index.column() key = self.columns[column] + if ( + key == "name" + and role in (QtCore.Qt.EditRole, QtCore.Qt.DisplayRole) + ): + self._rename_asset(item, value) + result = item.setData(key, value, role) if result: self.dataChanged.emit(index, index, [role]) @@ -154,8 +161,13 @@ def add_new_asset(self, source_index): data = {"name": name} new_child = AssetItem(data) + self._asset_items_by_name[name].append(new_child) - return self.add_item(new_child, parent, new_row) + result = self.add_item(new_child, parent, new_row) + + self._validate_asset_duplicity(name) + + return result def add_new_task(self, parent_index): item_id = parent_index.data(IDENTIFIER_ROLE) @@ -173,12 +185,6 @@ def add_new_task(self, parent_index): new_child = TaskItem(data) return self.add_item(new_child, parent) - def add_new_item(self, parent): - data = {"name": "Test {}".format(parent.rowCount())} - new_child = AssetItem(data) - - return self.add_item(new_child, parent) - def add_item(self, item, parent=None, row=None): if parent is None: parent = self._root_item @@ -240,12 +246,46 @@ def remove_index(self, index): if child_id in all_descendants: continue + if isinstance(child_item, AssetItem): + self._rename_asset(child_item, None) children.pop(child_id) child_item.set_parent(None) self._items_by_id.pop(child_id) self.endRemoveRows() + def _rename_asset(self, asset_item, new_name): + if not isinstance(asset_item, AssetItem): + return + + prev_name = asset_item.data("name", QtCore.Qt.DisplayRole) + print(prev_name) + self._asset_items_by_name[prev_name].remove(asset_item) + + self._validate_asset_duplicity(prev_name) + + if new_name is None: + return + self._asset_items_by_name[new_name].append(asset_item) + + self._validate_asset_duplicity(new_name) + + def _validate_asset_duplicity(self, name): + if name not in self._asset_items_by_name: + return + + items = self._asset_items_by_name[name] + if not items: + self._asset_items_by_name.pop(name) + + elif len(items) == 1: + index = self.index_for_item(items[0]) + self.setData(index, False, DUPLICATED_ROLE) + else: + for item in items: + index = self.index_for_item(item) + self.setData(index, True, DUPLICATED_ROLE) + def move_vertical(self, index, direction): if not index.isValid(): return @@ -456,9 +496,6 @@ def data(self, key, role): if role == IDENTIFIER_ROLE: return self._id - if role == COLUMNS_ROLE: - return self.columns - if key not in self.columns: return None From 7ea8a6222e78ba202da3bcdb366cd58463d8b9b9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 26 Apr 2021 09:10:36 +0200 Subject: [PATCH 014/219] asset names has duplicated information --- .../project_manager/project_manager/model.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 47fc6081129..5e497b755d5 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -10,7 +10,7 @@ from avalon.api import AvalonMongoDB from avalon.vendor import qtawesome -from Qt import QtCore +from Qt import QtCore, QtGui class HierarchySelectionModel(QtCore.QItemSelectionModel): @@ -259,7 +259,6 @@ def _rename_asset(self, asset_item, new_name): return prev_name = asset_item.data("name", QtCore.Qt.DisplayRole) - print(prev_name) self._asset_items_by_name[prev_name].remove(asset_item) self._validate_asset_duplicity(prev_name) @@ -461,6 +460,7 @@ def clear(self): class BaseItem: columns = ["name"] _name_icon = None + _is_duplicated = False def __init__(self, data=None): self._id = uuid4() @@ -496,6 +496,19 @@ def data(self, key, role): if role == IDENTIFIER_ROLE: return self._id + if role == DUPLICATED_ROLE: + return self._is_duplicated + + if role == QtCore.Qt.ForegroundRole: + if self._is_duplicated: + return QtGui.QColor(255, 0, 0) + + if role == QtCore.Qt.ToolTipRole: + if self._is_duplicated: + return "Asset with name \"{}\" already exists.".format( + self._data["name"] + ) + if key not in self.columns: return None @@ -510,6 +523,13 @@ def data(self, key, role): return None def setData(self, key, value, role): + if role == DUPLICATED_ROLE: + if value == self._is_duplicated: + return False + + self._is_duplicated = value + return True + if key not in self.columns: return False From eb6ece64222561bf9a0eaab2e5b56d268b6e0380 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 27 Apr 2021 11:54:17 +0200 Subject: [PATCH 015/219] avoid adding children to TaskItem --- .../project_manager/project_manager/model.py | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 5e497b755d5..88ae8f841b2 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -319,14 +319,30 @@ def move_vertical(self, index, direction): # Move under parent before or after if before is None elif direction == 1: - if src_parent.rowCount() == 1: + src_row_count = src_parent.rowCount() + if src_row_count == 1: return - if item.row() == 0: - parent_row = item.row() + 1 - else: - parent_row = item.row() - 1 - dst_parent = src_parent.child(parent_row) + item_row = item.row() + dst_parent = None + for row in reversed(range(item_row)): + if row == item_row: + continue + _item = src_parent.child(row) + if not isinstance(_item, TaskItem): + dst_parent = _item + break + + if dst_parent is None: + for row in range(item_row + 1, src_row_count + 2): + _item = src_parent.child(row) + if not isinstance(_item, TaskItem): + dst_parent = _item + break + + if dst_parent is None: + return + dst_row = dst_parent.rowCount() if src_parent is dst_parent: @@ -655,3 +671,6 @@ def name_icon(cls): if cls._name_icon is None: cls._name_icon = qtawesome.icon("fa.file-o", color="#333333") return cls._name_icon + + def add_child(self, item, row=None): + raise AssertionError("BUG: Can't add children to Task") From 7c78a9a584457252d7a201cda24c0a09d315121d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 27 Apr 2021 11:59:36 +0200 Subject: [PATCH 016/219] always expand parent of moved item --- .../project_manager/project_manager/view.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 7843254f87f..78536d556df 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -183,16 +183,29 @@ def _on_shift_enter_pressed(self): self.edit(new_index) def _on_up_ctrl_pressed(self): - self._source_model.move_horizontal(self.currentIndex(), -1) + index = self.currentIndex() + self._source_model.move_horizontal(index, -1) + parent_index = index.parent() + if not self.isExpanded(parent_index): + self.expand(parent_index) def _on_down_ctrl_pressed(self): - self._source_model.move_horizontal(self.currentIndex(), 1) + index = self.currentIndex() + self._source_model.move_horizontal(index, 1) + parent_index = index.parent() + if not self.isExpanded(parent_index): + self.expand(parent_index) def _on_left_ctrl_pressed(self): self._source_model.move_vertical(self.currentIndex(), -1) def _on_right_ctrl_pressed(self): - self._source_model.move_vertical(self.currentIndex(), 1) + index = self.currentIndex() + self._source_model.move_vertical(index, 1) + + parent_index = index.parent() + if not self.isExpanded(parent_index): + self.expand(parent_index) def _on_enter_pressed(self): index = self.currentIndex() From 31145de994857f23e7b8b4e5db1fa0cd20218ba8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 27 Apr 2021 12:36:05 +0200 Subject: [PATCH 017/219] added index_moved signal to model --- openpype/tools/project_manager/project_manager/model.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 88ae8f841b2..d426c15a03b 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -34,6 +34,7 @@ class HierarchyModel(QtCore.QAbstractItemModel): "resolutionWidth", "resolutionHeight" ] + index_moved = QtCore.Signal(QtCore.QModelIndex) def __init__(self, parent=None): super(HierarchyModel, self).__init__(parent) @@ -205,8 +206,6 @@ def add_item(self, item, parent=None, row=None): self.endInsertRows() - self.rowsInserted.emit(parent_index, row, row) - return self.index_from_item(row, 0, parent) def remove_index(self, index): @@ -373,6 +372,8 @@ def move_vertical(self, index, direction): self.endMoveRows() + self.index_moved.emit(index) + def move_horizontal(self, index, direction): if not index.isValid(): return @@ -458,6 +459,8 @@ def move_horizontal(self, index, direction): self.endMoveRows() + self.index_moved.emit(index) + def child_removed(self, child): self._items_by_id.pop(child.id, None) From cdaa06706de32e47e705aa1cd43b96839bb6cd9e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 27 Apr 2021 12:36:25 +0200 Subject: [PATCH 018/219] added row moved callback --- .../project_manager/project_manager/view.py | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 78536d556df..1f064ee17d6 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -39,10 +39,17 @@ def __init__(self, source_model, *args, **kwargs): column_delegates[key] = delegate column_key_to_index[key] = column + source_model.index_moved.connect(self._on_rows_moved) + self._delegate = main_delegate self._column_delegates = column_delegates self._column_key_to_index = column_key_to_index + def _on_rows_moved(self, index): + parent_index = index.parent() + if not self.isExpanded(parent_index): + self.expand(parent_index) + def commitData(self, editor): super(HierarchyView, self).commitData(editor) current_index = self.currentIndex() @@ -183,29 +190,16 @@ def _on_shift_enter_pressed(self): self.edit(new_index) def _on_up_ctrl_pressed(self): - index = self.currentIndex() - self._source_model.move_horizontal(index, -1) - parent_index = index.parent() - if not self.isExpanded(parent_index): - self.expand(parent_index) + self._source_model.move_horizontal(self.currentIndex(), -1) def _on_down_ctrl_pressed(self): - index = self.currentIndex() - self._source_model.move_horizontal(index, 1) - parent_index = index.parent() - if not self.isExpanded(parent_index): - self.expand(parent_index) + self._source_model.move_horizontal(self.currentIndex(), 1) def _on_left_ctrl_pressed(self): self._source_model.move_vertical(self.currentIndex(), -1) def _on_right_ctrl_pressed(self): - index = self.currentIndex() - self._source_model.move_vertical(index, 1) - - parent_index = index.parent() - if not self.isExpanded(parent_index): - self.expand(parent_index) + self._source_model.move_vertical(self.currentIndex(), 1) def _on_enter_pressed(self): index = self.currentIndex() From a36048ca1340881b384b118df8ae0308aac10019 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 27 Apr 2021 12:57:17 +0200 Subject: [PATCH 019/219] move up/dowm works for multiselection --- .../project_manager/project_manager/model.py | 42 ++++++++++++++++++- .../project_manager/project_manager/view.py | 6 ++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index d426c15a03b..fc6442830a5 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -374,7 +374,7 @@ def move_vertical(self, index, direction): self.index_moved.emit(index) - def move_horizontal(self, index, direction): + def _move_horizontal_single(self, index, direction): if not index.isValid(): return @@ -461,6 +461,46 @@ def move_horizontal(self, index, direction): self.index_moved.emit(index) + def move_horizontal(self, indexes, direction): + if not indexes: + return + + if isinstance(indexes, QtCore.QModelIndex): + indexes = [indexes] + + if len(indexes) == 1: + self._move_horizontal_single(indexes[0], direction) + return + + items_by_id = {} + for index in indexes: + item_id = index.data(IDENTIFIER_ROLE) + items_by_id[item_id] = self._items_by_id[item_id] + + skip_ids = set(items_by_id.keys()) + for item_id, item in tuple(items_by_id.items()): + parent = item.parent() + parent_ids = set() + skip_item = False + while parent is not None: + if parent.id in skip_ids: + skip_item = True + skip_ids |= parent_ids + break + parent_ids.add(parent.id) + parent = parent.parent() + + if skip_item: + items_by_id.pop(item_id) + + items = tuple(items_by_id.values()) + if direction == 1: + items = reversed(items) + + for item in items: + index = self.index_for_item(item) + self._move_horizontal_single(index, direction) + def child_removed(self, child): self._items_by_id.pop(child.id, None) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 1f064ee17d6..59a380d6b68 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -190,10 +190,12 @@ def _on_shift_enter_pressed(self): self.edit(new_index) def _on_up_ctrl_pressed(self): - self._source_model.move_horizontal(self.currentIndex(), -1) + indexes = self.selectedIndexes() + self._source_model.move_horizontal(indexes, -1) def _on_down_ctrl_pressed(self): - self._source_model.move_horizontal(self.currentIndex(), 1) + indexes = self.selectedIndexes() + self._source_model.move_horizontal(indexes, 1) def _on_left_ctrl_pressed(self): self._source_model.move_vertical(self.currentIndex(), -1) From 33c86aef37e281c4dd9c27b001926941c8540f6b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 09:58:24 +0200 Subject: [PATCH 020/219] use extended selection --- openpype/tools/project_manager/project_manager/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 59a380d6b68..59eb9684234 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -28,7 +28,7 @@ def __init__(self, source_model, *args, **kwargs): main_delegate = QtWidgets.QStyledItemDelegate() self.setItemDelegate(main_delegate) self.setAlternatingRowColors(True) - self.setSelectionMode(HierarchyView.ContiguousSelection) + self.setSelectionMode(HierarchyView.ExtendedSelection) column_delegates = {} column_key_to_index = {} From 77fa75ec17544064d05a0fbc9adc3e45f39c7d67 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 10:16:38 +0200 Subject: [PATCH 021/219] move vertically multiple items --- .../project_manager/project_manager/model.py | 69 ++++++++++++++++++- .../project_manager/project_manager/view.py | 6 +- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index fc6442830a5..04399c637e6 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -284,7 +284,7 @@ def _validate_asset_duplicity(self, name): index = self.index_for_item(item) self.setData(index, True, DUPLICATED_ROLE) - def move_vertical(self, index, direction): + def _move_vertical_single(self, index, direction): if not index.isValid(): return @@ -374,6 +374,73 @@ def move_vertical(self, index, direction): self.index_moved.emit(index) + def move_vertical(self, indexes, direction): + if not indexes: + return + + if isinstance(indexes, QtCore.QModelIndex): + indexes = [indexes] + + if len(indexes) == 1: + self._move_vertical_single(indexes[0], direction) + return + + items_by_id = {} + for index in indexes: + item_id = index.data(IDENTIFIER_ROLE) + item = self._items_by_id[item_id] + if isinstance(item, (RootItem, ProjectItem)): + continue + + if ( + direction == -1 + and isinstance(item.parent(), (RootItem, ProjectItem)) + ): + continue + + items_by_id[item_id] = item + + if not items_by_id: + return + + parents_by_id = {} + items_ids_by_parent_id = collections.defaultdict(set) + skip_ids = set(items_by_id.keys()) + for item_id, item in tuple(items_by_id.items()): + item_parent = item.parent() + + parent_ids = set() + skip_item = False + parent = item_parent + while parent is not None: + if parent.id in skip_ids: + skip_item = True + skip_ids |= parent_ids + break + parent_ids.add(parent.id) + parent = parent.parent() + + if skip_item: + items_by_id.pop(item_id) + else: + parents_by_id[item_parent.id] = item_parent + items_ids_by_parent_id[item_parent.id].add(item_id) + + if direction == 1: + for parent_id, parent in parents_by_id.items(): + items_ids = items_ids_by_parent_id[parent_id] + if len(items_ids) == parent.rowCount(): + for item_id in items_ids: + items_by_id.pop(item_id) + + items = tuple(items_by_id.values()) + if direction == -1: + items = reversed(items) + + for item in items: + index = self.index_for_item(item) + self._move_vertical_single(index, direction) + def _move_horizontal_single(self, index, direction): if not index.isValid(): return diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 59eb9684234..b17736d7b0b 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -198,10 +198,12 @@ def _on_down_ctrl_pressed(self): self._source_model.move_horizontal(indexes, 1) def _on_left_ctrl_pressed(self): - self._source_model.move_vertical(self.currentIndex(), -1) + indexes = self.selectedIndexes() + self._source_model.move_vertical(indexes, -1) def _on_right_ctrl_pressed(self): - self._source_model.move_vertical(self.currentIndex(), 1) + indexes = self.selectedIndexes() + self._source_model.move_vertical(indexes, 1) def _on_enter_pressed(self): index = self.currentIndex() From 77488bbd974a40cf39218ad342127e5c41f361f4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 10:21:07 +0200 Subject: [PATCH 022/219] items have editable columns definition --- .../project_manager/project_manager/model.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 04399c637e6..6a899b3519f 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -585,6 +585,9 @@ def clear(self): class BaseItem: columns = ["name"] + # Use `set` for faster result + editable_columns = {"name"} + _name_icon = None _is_duplicated = False @@ -727,7 +730,7 @@ def remove_child(self, item): def flags(self, key): flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable - if key in self.columns: + if key in self.editable_columns: flags |= QtCore.Qt.ItemIsEditable return flags @@ -763,6 +766,14 @@ class AssetItem(BaseItem): "resolutionWidth", "resolutionHeight" ] + editable_columns = { + "name", + "frameStart", + "frameEnd", + "fps", + "resolutionWidth", + "resolutionHeight" + } @classmethod def name_icon(cls): @@ -776,6 +787,11 @@ class TaskItem(BaseItem): "name", "type" ] + editable_columns = { + "name", + "type" + } + @classmethod def name_icon(cls): if cls._name_icon is None: From 21620f715fa094e6923a134156ede71c27b76063 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 10:23:49 +0200 Subject: [PATCH 023/219] small tweaks of columns and editable columns --- .../project_manager/project_manager/model.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 6a899b3519f..e53f3fe4055 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -160,7 +160,10 @@ def add_new_asset(self, source_index): parent = item.parent() new_row = item.row() + 1 - data = {"name": name} + data = { + "name": name, + "type": "asset" + } new_child = AssetItem(data) self._asset_items_by_name[name].append(new_child) @@ -584,9 +587,9 @@ def clear(self): class BaseItem: - columns = ["name"] + columns = [] # Use `set` for faster result - editable_columns = {"name"} + editable_columns = set() _name_icon = None _is_duplicated = False @@ -748,9 +751,15 @@ def flags(self, *args, **kwargs): class ProjectItem(BaseItem): + columns = [ + "name", + "type" + ] + def __init__(self, data=None): super(ProjectItem, self).__init__(data) self._data["name"] = "project" + self._data["type"] = "project" def flags(self, *args, **kwargs): return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable From f79d497313b134d3308428f80c0f5551de4dea90 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 10:24:01 +0200 Subject: [PATCH 024/219] red color only for name column --- openpype/tools/project_manager/project_manager/model.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index e53f3fe4055..14950e09c63 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -631,10 +631,6 @@ def data(self, key, role): if role == DUPLICATED_ROLE: return self._is_duplicated - if role == QtCore.Qt.ForegroundRole: - if self._is_duplicated: - return QtGui.QColor(255, 0, 0) - if role == QtCore.Qt.ToolTipRole: if self._is_duplicated: return "Asset with name \"{}\" already exists.".format( @@ -644,6 +640,11 @@ def data(self, key, role): if key not in self.columns: return None + if role == QtCore.Qt.ForegroundRole: + if self._is_duplicated and key == "name": + return QtGui.QColor(255, 0, 0) + return None + if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole): value = self._data[key] if value is None: From 8ebf7f9fb32553cfd955f6c2f7692cafc3c26910 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 11:07:15 +0200 Subject: [PATCH 025/219] added project model --- .../project_manager/project_manager/model.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 14950e09c63..08621075c0d 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -13,6 +13,43 @@ from Qt import QtCore, QtGui +class ProjectModel(QtGui.QStandardItemModel): + project_changed = QtCore.Signal() + + def __init__(self, dbcon, *args, **kwargs): + self.dbcon = dbcon + + self._project_names = set() + + super(ProjectModel, self).__init__(*args, **kwargs) + + def refresh(self): + self.dbcon.Session["AVALON_PROJECT"] = None + + project_items = [] + database = self.dbcon.database + project_names = set() + for project_name in database.collection_names(): + # Each collection will have exactly one project document + project_doc = database[project_name].find_one( + {"type": "project"}, + {"name": 1} + ) + if not project_doc: + continue + + project_name = project_doc.get("name") + if project_name: + project_names.add(project_name) + project_items.append(QtGui.QStandardItem(project_name)) + + self.clear() + + self._project_names = project_names + + self.invisibleRootItem().appendRows(project_items) + + class HierarchySelectionModel(QtCore.QItemSelectionModel): def setCurrentIndex(self, index, command): if index.column() > 0: From f13d8c7cd9fe0032fdb0533fc526540de1f83853 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 11:09:15 +0200 Subject: [PATCH 026/219] dbconection is passed from window --- openpype/tools/project_manager/project_manager/model.py | 6 ++---- openpype/tools/project_manager/project_manager/window.py | 6 +++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 08621075c0d..a0f75b51218 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -7,9 +7,7 @@ DUPLICATED_ROLE ) -from avalon.api import AvalonMongoDB from avalon.vendor import qtawesome - from Qt import QtCore, QtGui @@ -73,12 +71,12 @@ class HierarchyModel(QtCore.QAbstractItemModel): ] index_moved = QtCore.Signal(QtCore.QModelIndex) - def __init__(self, parent=None): + def __init__(self, dbcon, parent=None): super(HierarchyModel, self).__init__(parent) self._root_item = None self._items_by_id = {} self._asset_items_by_name = collections.defaultdict(list) - self.dbcon = AvalonMongoDB() + self.dbcon = dbcon self._hierarchy_mode = True self._reset_root_item() diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index db5527b4ac2..257850c0143 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -6,12 +6,16 @@ HierarchyView ) +from avalon.api import AvalonMongoDB + class Window(QtWidgets.QWidget): def __init__(self, parent=None): super(Window, self).__init__(parent) - model = HierarchyModel() + dbcon = AvalonMongoDB() + + model = HierarchyModel(dbcon) view = HierarchyView(model, self) view.setModel(model) _selection_model = HierarchySelectionModel() From 00b1f35526da3784f691cc75a09b41239321636b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 11:11:08 +0200 Subject: [PATCH 027/219] renmamed variables in window --- .../project_manager/project_manager/window.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 257850c0143..77cff6759c9 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -15,14 +15,15 @@ def __init__(self, parent=None): dbcon = AvalonMongoDB() - model = HierarchyModel(dbcon) - view = HierarchyView(model, self) - view.setModel(model) + hierarchy_model = HierarchyModel(dbcon) + + hierarchy_view = HierarchyView(hierarchy_model, self) + hierarchy_view.setModel(hierarchy_model) _selection_model = HierarchySelectionModel() - _selection_model.setModel(view.model()) - view.setSelectionModel(_selection_model) + _selection_model.setModel(hierarchy_view.model()) + hierarchy_view.setSelectionModel(_selection_model) - header = view.header() + header = hierarchy_view.header() header.setStretchLastSection(False) header.setSectionResizeMode( header.logicalIndex(0), QtWidgets.QHeaderView.Stretch @@ -31,27 +32,26 @@ def __init__(self, parent=None): # btn = QtWidgets.QPushButton("Refresh") main_layout = QtWidgets.QVBoxLayout(self) - main_layout.addWidget(view) + main_layout.addWidget(hierarchy_view) main_layout.addWidget(checkbox) # main_layout.addWidget(btn) # btn.clicked.connect(self._on_refresh) checkbox.toggled.connect(self._on_checkbox) - self.view = view - self.model = model # self.btn = btn + self.hierarchy_view = hierarchy_view + self.hierarchy_model = hierarchy_model self.checkbox = checkbox self.change_edit_mode() self.resize(1200, 600) - model.set_project({"name": "test"}) def change_edit_mode(self, value=None): if value is None: value = self.checkbox.isChecked() - self.model.change_edit_mode(value) + self.hierarchy_model.change_edit_mode(value) def _on_checkbox(self, value): self.change_edit_mode(value) From 64e4d3be4af122aaa1822d0b32298d4b943dbdef Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 11:11:40 +0200 Subject: [PATCH 028/219] added project combobox to window --- .../tools/project_manager/project_manager/__init__.py | 4 ++++ openpype/tools/project_manager/project_manager/window.py | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/__init__.py b/openpype/tools/project_manager/project_manager/__init__.py index 1a538baa208..a652e950c49 100644 --- a/openpype/tools/project_manager/project_manager/__init__.py +++ b/openpype/tools/project_manager/project_manager/__init__.py @@ -3,6 +3,8 @@ ) from .view import HierarchyView from .model import ( + ProjectModel, + HierarchyModel, HierarchySelectionModel, BaseItem, @@ -18,6 +20,8 @@ "HierarchyView", + "ProjectModel", + "HierarchyModel", "HierarchySelectionModel", "BaseItem", diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 77cff6759c9..61db70a6d7d 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -1,6 +1,8 @@ from Qt import QtWidgets, QtCore from . import ( + ProjectModel, + HierarchyModel, HierarchySelectionModel, HierarchyView @@ -15,6 +17,12 @@ def __init__(self, parent=None): dbcon = AvalonMongoDB() + project_model = ProjectModel(dbcon) + + project_combobox = QtWidgets.QComboBox() + project_combobox.setModel(project_model) + project_combobox.setRootModelIndex(QtCore.QModelIndex()) + hierarchy_model = HierarchyModel(dbcon) hierarchy_view = HierarchyView(hierarchy_model, self) @@ -32,6 +40,7 @@ def __init__(self, parent=None): # btn = QtWidgets.QPushButton("Refresh") main_layout = QtWidgets.QVBoxLayout(self) + main_layout.addWidget(project_combobox) main_layout.addWidget(hierarchy_view) main_layout.addWidget(checkbox) # main_layout.addWidget(btn) From 9250939bdd36b3ac531c6c695debeb7871f4d7d5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 11:12:46 +0200 Subject: [PATCH 029/219] project item has more columns and has convertor from project document --- .../project_manager/project_manager/model.py | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index a0f75b51218..5f1f1d5d0a0 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -789,13 +789,33 @@ def flags(self, *args, **kwargs): class ProjectItem(BaseItem): columns = [ "name", - "type" + "type", + "frameStart", + "frameEnd", + "fps", + "resolutionWidth", + "resolutionHeight" ] - def __init__(self, data=None): + def __init__(self, project_doc): + + data = self.data_from_doc(project_doc) super(ProjectItem, self).__init__(data) - self._data["name"] = "project" - self._data["type"] = "project" + + @classmethod + def data_from_doc(cls, project_doc): + data = { + "name": project_doc["name"], + "type": project_doc["type"] + } + doc_data = project_doc.get("data") or {} + for key in cls.columns: + if key in data: + continue + + data[key] = doc_data.get(key) + + return data def flags(self, *args, **kwargs): return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable From eb8380aeb25ee6db25a3198c0a232193bf113e23 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 11:13:21 +0200 Subject: [PATCH 030/219] model expect project name in set_project method --- .../project_manager/project_manager/model.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 5f1f1d5d0a0..9c307063c09 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -73,6 +73,7 @@ class HierarchyModel(QtCore.QAbstractItemModel): def __init__(self, dbcon, parent=None): super(HierarchyModel, self).__init__(parent) + self._current_project = None self._root_item = None self._items_by_id = {} self._asset_items_by_name = collections.defaultdict(list) @@ -91,11 +92,22 @@ def items_by_id(self): def _reset_root_item(self): self._root_item = RootItem(self) - def set_project(self, project_doc): + def set_project(self, project_name): + if self._current_project == project_name: + return + self.clear() - item = ProjectItem(project_doc) - self.add_item(item) + self._current_project = project_name + if not project_name: + return + + project_doc = self.dbcon.database[project_name].find_one({ + "type": "project" + }) + if project_doc: + item = ProjectItem(project_doc) + self.add_item(item) def rowCount(self, parent=None): if parent is None or not parent.isValid(): From 135d811654874e0becf5c369392a75f35bd66626 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 11:13:35 +0200 Subject: [PATCH 031/219] window can load and change projects --- .../project_manager/project_manager/window.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 61db70a6d7d..fa47bfd1bbe 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -57,6 +57,31 @@ def __init__(self, parent=None): self.resize(1200, 600) + self.refresh_projects() + + def refresh_projects(self): + current_project = None + if self.project_combobox.count() > 0: + current_project = self.project_combobox.currentText() + + self.project_model.refresh() + + if self.project_combobox.count() == 0: + return self._set_project() + + if current_project: + row = self.project_combobox.findText(current_project) + if row >= 0: + self._set_project(current_project) + index = self.project_combobox.model().index(row, 0) + self.project_combobox.setCurrentIndex(index) + return + + self._set_project(self.project_combobox.currentText()) + + def _set_project(self, project_name=None): + self.hierarchy_model.set_project(project_name) + def change_edit_mode(self, value=None): if value is None: value = self.checkbox.isChecked() From f8ef837a0cbdfe8552c98172c449896e3df2b6c7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 11:19:55 +0200 Subject: [PATCH 032/219] added simple refresh button for projects --- .../tools/project_manager/project_manager/window.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index fa47bfd1bbe..0dee861f591 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -17,12 +17,23 @@ def __init__(self, parent=None): dbcon = AvalonMongoDB() + # TOP Project selection + project_widget = QtWidgets.QWidget(self) + project_model = ProjectModel(dbcon) - project_combobox = QtWidgets.QComboBox() + project_combobox = QtWidgets.QComboBox(project_widget) project_combobox.setModel(project_model) project_combobox.setRootModelIndex(QtCore.QModelIndex()) + refresh_projects_btn = QtWidgets.QPushButton("Refresh", project_widget) + + project_layout = QtWidgets.QHBoxLayout(project_widget) + project_layout.setContentsMargins(0, 0, 0, 0) + project_layout.addWidget(refresh_projects_btn, 0) + project_layout.addWidget(project_combobox, 0) + project_layout.addStretch(1) + hierarchy_model = HierarchyModel(dbcon) hierarchy_view = HierarchyView(hierarchy_model, self) From 533adaf44db4c22f1a9c71ac29ee914772758778 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 11:20:49 +0200 Subject: [PATCH 033/219] removed unused parts --- openpype/tools/project_manager/project_manager/window.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 0dee861f591..2f7d4d472ed 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -48,20 +48,17 @@ def __init__(self, parent=None): header.logicalIndex(0), QtWidgets.QHeaderView.Stretch ) checkbox = QtWidgets.QCheckBox(self) - # btn = QtWidgets.QPushButton("Refresh") main_layout = QtWidgets.QVBoxLayout(self) main_layout.addWidget(project_combobox) main_layout.addWidget(hierarchy_view) main_layout.addWidget(checkbox) - # main_layout.addWidget(btn) - # btn.clicked.connect(self._on_refresh) checkbox.toggled.connect(self._on_checkbox) - # self.btn = btn self.hierarchy_view = hierarchy_view self.hierarchy_model = hierarchy_model + self.checkbox = checkbox self.change_edit_mode() @@ -100,6 +97,3 @@ def change_edit_mode(self, value=None): def _on_checkbox(self, value): self.change_edit_mode(value) - - def _on_refresh(self): - self.model.clear() From 27eb318274c3f274eb198c12a27e93bf9654704e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 11:21:09 +0200 Subject: [PATCH 034/219] project is changed on combobox change --- .../project_manager/project_manager/window.py | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 2f7d4d472ed..3f9a54442b0 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -50,11 +50,16 @@ def __init__(self, parent=None): checkbox = QtWidgets.QCheckBox(self) main_layout = QtWidgets.QVBoxLayout(self) - main_layout.addWidget(project_combobox) + main_layout.addWidget(project_widget) main_layout.addWidget(hierarchy_view) main_layout.addWidget(checkbox) + refresh_projects_btn.clicked.connect(self._on_project_refresh) checkbox.toggled.connect(self._on_checkbox) + project_combobox.currentIndexChanged.connect(self._on_project_change) + + self.project_model = project_model + self.project_combobox = project_combobox self.hierarchy_view = hierarchy_view self.hierarchy_model = hierarchy_model @@ -67,6 +72,14 @@ def __init__(self, parent=None): self.refresh_projects() + def _change_edit_mode(self, value=None): + if value is None: + value = self.checkbox.isChecked() + self.hierarchy_model.change_edit_mode(value) + + def _set_project(self, project_name=None): + self.hierarchy_model.set_project(project_name) + def refresh_projects(self): current_project = None if self.project_combobox.count() > 0: @@ -87,13 +100,11 @@ def refresh_projects(self): self._set_project(self.project_combobox.currentText()) - def _set_project(self, project_name=None): - self.hierarchy_model.set_project(project_name) + def _on_project_change(self): + self._set_project(self.project_combobox.currentText()) - def change_edit_mode(self, value=None): - if value is None: - value = self.checkbox.isChecked() - self.hierarchy_model.change_edit_mode(value) + def _on_project_refresh(self): + self.refresh_projects() def _on_checkbox(self, value): - self.change_edit_mode(value) + self._change_edit_mode(value) From b7efe9b9f61a21082113918fd23bd4bb5b6e6993 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 11:24:13 +0200 Subject: [PATCH 035/219] removed checkbox without logic --- .../project_manager/project_manager/model.py | 4 ---- .../project_manager/project_manager/window.py | 15 --------------- 2 files changed, 19 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 9c307063c09..f7973563ad0 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -79,12 +79,8 @@ def __init__(self, dbcon, parent=None): self._asset_items_by_name = collections.defaultdict(list) self.dbcon = dbcon - self._hierarchy_mode = True self._reset_root_item() - def change_edit_mode(self, hiearchy_mode): - self._hierarchy_mode = hiearchy_mode - @property def items_by_id(self): return self._items_by_id diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 3f9a54442b0..7f9b4e73315 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -47,15 +47,12 @@ def __init__(self, parent=None): header.setSectionResizeMode( header.logicalIndex(0), QtWidgets.QHeaderView.Stretch ) - checkbox = QtWidgets.QCheckBox(self) main_layout = QtWidgets.QVBoxLayout(self) main_layout.addWidget(project_widget) main_layout.addWidget(hierarchy_view) - main_layout.addWidget(checkbox) refresh_projects_btn.clicked.connect(self._on_project_refresh) - checkbox.toggled.connect(self._on_checkbox) project_combobox.currentIndexChanged.connect(self._on_project_change) self.project_model = project_model @@ -64,19 +61,10 @@ def __init__(self, parent=None): self.hierarchy_view = hierarchy_view self.hierarchy_model = hierarchy_model - self.checkbox = checkbox - - self.change_edit_mode() - self.resize(1200, 600) self.refresh_projects() - def _change_edit_mode(self, value=None): - if value is None: - value = self.checkbox.isChecked() - self.hierarchy_model.change_edit_mode(value) - def _set_project(self, project_name=None): self.hierarchy_model.set_project(project_name) @@ -105,6 +93,3 @@ def _on_project_change(self): def _on_project_refresh(self): self.refresh_projects() - - def _on_checkbox(self, value): - self._change_edit_mode(value) From 108666cd3ffd4dbc4c9c08d1043b76be10dd17f0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 12:05:28 +0200 Subject: [PATCH 036/219] added hardcoded min/max for number delegate --- openpype/tools/project_manager/project_manager/delegates.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index 605d0cbbab8..11022421996 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -4,6 +4,8 @@ class NumberDelegate(QtWidgets.QStyledItemDelegate): def createEditor(self, parent, option, index): editor = QtWidgets.QSpinBox(parent) + editor.setMaximum(999999) + editor.setMinimum(0) value = index.data(QtCore.Qt.DisplayRole) if value is not None: editor.setValue(value) From 6e578ee63c99a2f7afb8360e2e30613567312194 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 12:06:12 +0200 Subject: [PATCH 037/219] items have defined query projections --- .../project_manager/project_manager/model.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index f7973563ad0..dea14d021d1 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -804,9 +804,18 @@ class ProjectItem(BaseItem): "resolutionWidth", "resolutionHeight" ] + query_projection = { + "_id": 1, + "name": 1, + "type": 1, + "data.frameStart": 1, + "data.frameEnd": 1, + "data.fps": 1, + "data.resolutionWidth": 1, + "data.resolutionHeight": 1 + } def __init__(self, project_doc): - data = self.data_from_doc(project_doc) super(ProjectItem, self).__init__(data) @@ -847,6 +856,17 @@ class AssetItem(BaseItem): "resolutionWidth", "resolutionHeight" } + query_projection = { + "_id": 1, + "name": 1, + "type": 1, + "data.frameStart": 1, + "data.frameEnd": 1, + "data.fps": 1, + "data.resolutionWidth": 1, + "data.resolutionHeight": 1, + "data.visualParent": 1 + } @classmethod def name_icon(cls): From 7d3132f058f89c7f3c631bee523a4a89c5e5b0db Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 12:06:24 +0200 Subject: [PATCH 038/219] add existing asset docs to hierarchy --- .../project_manager/project_manager/model.py | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index dea14d021d1..8cafc9019a6 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -98,12 +98,37 @@ def set_project(self, project_name): if not project_name: return - project_doc = self.dbcon.database[project_name].find_one({ - "type": "project" - }) - if project_doc: - item = ProjectItem(project_doc) - self.add_item(item) + project_doc = self.dbcon.database[project_name].find_one( + {"type": "project"}, + ProjectItem.query_projection + ) + if not project_doc: + return + + project_item = ProjectItem(project_doc) + self.add_item(project_item) + + asset_docs = self.dbcon.database[project_name].find( + {"type": "asset"}, + AssetItem.query_projection + ) + asset_docs_by_parent_id = collections.defaultdict(list) + for asset_doc in asset_docs: + parent_id = asset_doc["data"]["visualParent"] + asset_docs_by_parent_id[parent_id].append(asset_doc) + + appending_queue = Queue() + appending_queue.put((None, project_item)) + + while not appending_queue.empty(): + parent_id, parent_item = appending_queue.get() + if parent_id not in asset_docs_by_parent_id: + continue + + for asset_doc in asset_docs_by_parent_id[parent_id]: + new_item = AssetItem(asset_doc) + self.add_item(new_item, parent_item) + appending_queue.put((asset_doc["_id"], new_item)) def rowCount(self, parent=None): if parent is None or not parent.isValid(): From df179af335a3a6dd44ce1c64fa3bc25b536c928f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 12:08:04 +0200 Subject: [PATCH 039/219] AssetItem has convertor from document to data --- .../project_manager/project_manager/model.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 8cafc9019a6..bf00c3f865f 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -893,6 +893,25 @@ class AssetItem(BaseItem): "data.visualParent": 1 } + def __init__(self, asset_doc): + data = self.data_from_doc(asset_doc) + super(AssetItem, self).__init__(data) + + @classmethod + def data_from_doc(cls, asset_doc): + data = { + "name": asset_doc["name"], + "type": asset_doc["type"] + } + doc_data = asset_doc.get("data") or {} + for key in cls.columns: + if key in data: + continue + + data[key] = doc_data.get(key) + + return data + @classmethod def name_icon(cls): if cls._name_icon is None: From d08fbcc02f0f3395137e483e23078f7273f4902d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 12:15:32 +0200 Subject: [PATCH 040/219] added ability to add multiple items under same parent --- .../project_manager/project_manager/model.py | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index bf00c3f865f..55dd23c76ab 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -125,11 +125,14 @@ def set_project(self, project_name): if parent_id not in asset_docs_by_parent_id: continue + new_items = [] for asset_doc in asset_docs_by_parent_id[parent_id]: new_item = AssetItem(asset_doc) - self.add_item(new_item, parent_item) + new_items.append(new_item) appending_queue.put((asset_doc["_id"], new_item)) + self.add_items(new_items, parent_item) + def rowCount(self, parent=None): if parent is None or not parent.isValid(): parent_item = self._root_item @@ -257,27 +260,39 @@ def add_new_task(self, parent_index): new_child = TaskItem(data) return self.add_item(new_child, parent) - def add_item(self, item, parent=None, row=None): + def add_items(self, items, parent=None, start_row=None): if parent is None: parent = self._root_item - if row is None: - row = parent.rowCount() + if start_row is None: + start_row = parent.rowCount() + + end_row = start_row + len(items) - 1 parent_index = self.index_from_item(parent.row(), 0, parent.parent()) - self.beginInsertRows(parent_index, row, row) + self.beginInsertRows(parent_index, start_row, end_row) - if item.parent() is not parent: - item.set_parent(parent) + for idx, item in enumerate(items): + row = start_row + idx + if item.parent() is not parent: + item.set_parent(parent) - parent.add_child(item, row) + parent.add_child(item, row) - if item.id not in self._items_by_id: - self._items_by_id[item.id] = item + if item.id not in self._items_by_id: + self._items_by_id[item.id] = item self.endInsertRows() - return self.index_from_item(row, 0, parent) + indexes = [] + for row in range(start_row, end_row + 1): + indexes.append( + self.index_from_item(row, 0, parent) + ) + return indexes + + def add_item(self, item, parent=None, row=None): + return self.add_items([item], parent, row)[0] def remove_index(self, index): if not index.isValid(): From b8e500cb6809f831dc50f33fdff9cd7ad5efa823 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 12:22:05 +0200 Subject: [PATCH 041/219] fix project combobox refresh --- openpype/tools/project_manager/project_manager/window.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 7f9b4e73315..56c496346dc 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -81,10 +81,7 @@ def refresh_projects(self): if current_project: row = self.project_combobox.findText(current_project) if row >= 0: - self._set_project(current_project) - index = self.project_combobox.model().index(row, 0) - self.project_combobox.setCurrentIndex(index) - return + self.project_combobox.setCurrentIndex(row) self._set_project(self.project_combobox.currentText()) From ab54913a435dd7a0319e94a3cf4a36eedf5e8e35 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 12:24:09 +0200 Subject: [PATCH 042/219] modified asset query projection --- openpype/tools/project_manager/project_manager/model.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 55dd23c76ab..f25b187b6c1 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -898,14 +898,16 @@ class AssetItem(BaseItem): } query_projection = { "_id": 1, + "data.tasks": 1, + "data.visualParent": 1, + "name": 1, "type": 1, "data.frameStart": 1, "data.frameEnd": 1, "data.fps": 1, "data.resolutionWidth": 1, - "data.resolutionHeight": 1, - "data.visualParent": 1 + "data.resolutionHeight": 1 } def __init__(self, asset_doc): From 558ca50ed18c69127729b66d95f4c61fee17da30 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 12:27:20 +0200 Subject: [PATCH 043/219] load also task items --- .../project_manager/project_manager/model.py | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index f25b187b6c1..947dd5087f1 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1,4 +1,5 @@ import collections +import copy from queue import Queue from uuid import uuid4 @@ -112,14 +113,20 @@ def set_project(self, project_name): {"type": "asset"}, AssetItem.query_projection ) + asset_docs_by_id = { + asset_doc["_id"]: asset_doc + for asset_doc in asset_docs + } asset_docs_by_parent_id = collections.defaultdict(list) - for asset_doc in asset_docs: + for asset_doc in asset_docs_by_id.values(): parent_id = asset_doc["data"]["visualParent"] asset_docs_by_parent_id[parent_id].append(asset_doc) appending_queue = Queue() appending_queue.put((None, project_item)) + asset_items_by_id = {} + while not appending_queue.empty(): parent_id, parent_item = appending_queue.get() if parent_id not in asset_docs_by_parent_id: @@ -127,12 +134,33 @@ def set_project(self, project_name): new_items = [] for asset_doc in asset_docs_by_parent_id[parent_id]: + # Create new Item new_item = AssetItem(asset_doc) + # Store item to be added under parent in bulk new_items.append(new_item) - appending_queue.put((asset_doc["_id"], new_item)) + + # Store item by id for task processing + asset_id = asset_doc["_id"] + asset_items_by_id[asset_id] = new_item + # Add item to appending queue + appending_queue.put((asset_id, new_item)) self.add_items(new_items, parent_item) + for asset_id, asset_item in asset_items_by_id.items(): + asset_doc = asset_docs_by_id[asset_id] + asset_tasks = asset_doc["data"]["tasks"] + if not asset_tasks: + continue + + task_items = [] + for task_name, task_data in asset_tasks.items(): + _task_data = copy.deepcopy(task_data) + _task_data["name"] = task_name + task_item = TaskItem(_task_data) + task_items.append(task_item) + self.add_items(task_items, asset_item) + def rowCount(self, parent=None): if parent is None or not parent.isValid(): parent_item = self._root_item From e771d4b887d5657c282e70eb99e1e7b667023c29 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 12:40:41 +0200 Subject: [PATCH 044/219] different way how to define delegates with more abilities --- .../project_manager/delegates.py | 17 +++++++-- .../project_manager/project_manager/view.py | 36 ++++++++++++++----- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index 11022421996..a26fb17b29e 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -2,10 +2,21 @@ class NumberDelegate(QtWidgets.QStyledItemDelegate): + def __init__(self, minimum, maximum, decimals, *args, **kwargs): + super(NumberDelegate, self).__init__(*args, **kwargs) + self.minimum = minimum + self.maximum = maximum + self.decimals = decimals + def createEditor(self, parent, option, index): - editor = QtWidgets.QSpinBox(parent) - editor.setMaximum(999999) - editor.setMinimum(0) + print(option.rect) + if self.decimals > 0: + editor = QtWidgets.QDoubleSpinBox(parent) + else: + editor = QtWidgets.QSpinBox(parent) + editor.setMinimum(self.minimum) + editor.setMaximum(self.maximum) + value = index.data(QtCore.Qt.DisplayRole) if value is not None: editor.setValue(value) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index b17736d7b0b..90e0e5289e9 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -3,15 +3,27 @@ from .delegates import NumberDelegate, StringDelegate +class StringDef: + def __init__(self, regex=None): + self.regex = regex + + +class NumberDef: + def __init__(self, minimum=None, maximum=None, decimals=None): + self.minimum = 0 if minimum is None else minimum + self.maximum = 999999 if maximum is None else maximum + self.decimals = 0 if decimals is None else decimals + + class HierarchyView(QtWidgets.QTreeView): """A tree view that deselects on clicking on an empty area in the view""" column_delegate_defs = { - "name": StringDelegate, - "frameStart": NumberDelegate, - "frameEnd": NumberDelegate, - "fps": NumberDelegate, - "resolutionWidth": NumberDelegate, - "resolutionHeight": NumberDelegate + "name": StringDef(), + "frameStart": NumberDef(1), + "frameEnd": NumberDef(1), + "fps": NumberDef(1, decimals=2), + "resolutionWidth": NumberDef(0), + "resolutionHeight": NumberDef(0) } persistent_columns = [ "frameStart", @@ -32,8 +44,16 @@ def __init__(self, source_model, *args, **kwargs): column_delegates = {} column_key_to_index = {} - for key, delegate_klass in self.column_delegate_defs.items(): - delegate = delegate_klass() + for key, item_type in self.column_delegate_defs.items(): + if isinstance(item_type, StringDef): + delegate = StringDelegate() + elif isinstance(item_type, NumberDef): + delegate = NumberDelegate( + item_type.minimum, + item_type.maximum, + item_type.decimals + ) + column = self._source_model.columns.index(key) self.setItemDelegateForColumn(column, delegate) column_delegates[key] = delegate From f5bbe2a3d4265e20e5e4bd249b1c73aca6b9bd42 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 12:41:24 +0200 Subject: [PATCH 045/219] remove debug print --- openpype/tools/project_manager/project_manager/delegates.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index a26fb17b29e..51bf6515ad6 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -9,7 +9,6 @@ def __init__(self, minimum, maximum, decimals, *args, **kwargs): self.decimals = decimals def createEditor(self, parent, option, index): - print(option.rect) if self.decimals > 0: editor = QtWidgets.QDoubleSpinBox(parent) else: From 550945972ad41102887bfc220803837f7801b546 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 30 Apr 2021 13:40:29 +0200 Subject: [PATCH 046/219] added more columns --- .../project_manager/project_manager/model.py | 92 ++++++++++++++----- .../project_manager/project_manager/view.py | 19 +++- 2 files changed, 85 insertions(+), 26 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 947dd5087f1..a6446b22bbf 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -61,15 +61,30 @@ def setCurrentIndex(self, index, command): class HierarchyModel(QtCore.QAbstractItemModel): + _columns_def = [ + ("name", "Name"), + ("type", "Type"), + ("fps", "FPS"), + ("frameStart", "Frame start"), + ("frameEnd", "Frame end"), + ("handleStart", "Handle start"), + ("handleEnd", "Handle end"), + ("resolutionWidth", "Width"), + ("resolutionHeight", "Height"), + ("clipIn", "Clip in"), + ("clipOut", "Clip out"), + ("pixelAspect", "Pixel aspect"), + ("tools_env", "Tools") + ] columns = [ - "name", - "type", - "frameStart", - "frameEnd", - "fps", - "resolutionWidth", - "resolutionHeight" + item[0] + for item in _columns_def ] + columns_len = len(columns) + column_labels = { + idx: item[1] + for idx, item in enumerate(_columns_def) + } index_moved = QtCore.Signal(QtCore.QModelIndex) def __init__(self, dbcon, parent=None): @@ -169,7 +184,7 @@ def rowCount(self, parent=None): return parent_item.rowCount() def columnCount(self, *args, **kwargs): - return len(self.columns) + return self.columns_len def data(self, index, role): if not index.isValid(): @@ -202,8 +217,8 @@ def setData(self, index, value, role=QtCore.Qt.EditRole): def headerData(self, section, orientation, role): if role == QtCore.Qt.DisplayRole: - if section < len(self.columns): - return self.columns[section] + if section < self.columnCount(): + return self.column_labels[section] super(HierarchyModel, self).headerData(section, orientation, role) @@ -252,7 +267,7 @@ def add_new_asset(self, source_index): new_row = None if isinstance(item, (RootItem, ProjectItem)): - name = "eq" + name = "ep" parent = item else: name = source_index.data(QtCore.Qt.DisplayRole) @@ -863,24 +878,37 @@ def flags(self, *args, **kwargs): class ProjectItem(BaseItem): - columns = [ + columns = { "name", "type", "frameStart", "frameEnd", "fps", "resolutionWidth", - "resolutionHeight" - ] + "resolutionHeight", + "handleStart", + "handleEnd", + "clipIn", + "clipOut", + "pixelAspect", + "tools_env", + } query_projection = { "_id": 1, "name": 1, "type": 1, + "data.frameStart": 1, "data.frameEnd": 1, "data.fps": 1, "data.resolutionWidth": 1, - "data.resolutionHeight": 1 + "data.resolutionHeight": 1, + "data.handleStart": 1, + "data.handleEnd": 1, + "data.clipIn": 1, + "data.clipOut": 1, + "data.pixelAspect": 1, + "data.tools_env": 1 } def __init__(self, project_doc): @@ -907,22 +935,34 @@ def flags(self, *args, **kwargs): class AssetItem(BaseItem): - columns = [ + columns = { "name", "type", + "fps", "frameStart", "frameEnd", - "fps", "resolutionWidth", - "resolutionHeight" - ] + "resolutionHeight", + "handleStart", + "handleEnd", + "clipIn", + "clipOut", + "pixelAspect", + "tools_env" + } editable_columns = { "name", "frameStart", "frameEnd", "fps", "resolutionWidth", - "resolutionHeight" + "resolutionHeight", + "handleStart", + "handleEnd", + "clipIn", + "clipOut", + "pixelAspect", + "tools_env" } query_projection = { "_id": 1, @@ -935,7 +975,13 @@ class AssetItem(BaseItem): "data.frameEnd": 1, "data.fps": 1, "data.resolutionWidth": 1, - "data.resolutionHeight": 1 + "data.resolutionHeight": 1, + "data.handleStart": 1, + "data.handleEnd": 1, + "data.clipIn": 1, + "data.clipOut": 1, + "data.pixelAspect": 1, + "data.tools_env": 1 } def __init__(self, asset_doc): @@ -965,10 +1011,10 @@ def name_icon(cls): class TaskItem(BaseItem): - columns = [ + columns = { "name", "type" - ] + } editable_columns = { "name", "type" diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 90e0e5289e9..7131687612e 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -23,14 +23,25 @@ class HierarchyView(QtWidgets.QTreeView): "frameEnd": NumberDef(1), "fps": NumberDef(1, decimals=2), "resolutionWidth": NumberDef(0), - "resolutionHeight": NumberDef(0) + "resolutionHeight": NumberDef(0), + "handleStart": NumberDef(0), + "handleEnd": NumberDef(0), + "clipIn": NumberDef(1), + "clipOut": NumberDef(1), + "pixelAspect": NumberDef(0, decimals=2), + # "tools_env": NumberDef(0) } persistent_columns = [ "frameStart", "frameEnd", "fps", "resolutionWidth", - "resolutionHeight" + "resolutionHeight", + "handleStart", + "handleEnd", + "clipIn", + "clipOut", + "pixelAspect" ] def __init__(self, source_model, *args, **kwargs): @@ -95,7 +106,9 @@ def commitData(self, editor): def _deselect_editor(self, editor): if editor: - if isinstance(editor, QtWidgets.QSpinBox): + if isinstance( + editor, (QtWidgets.QSpinBox, QtWidgets.QDoubleSpinBox) + ): line_edit = editor.findChild(QtWidgets.QLineEdit) line_edit.deselect() From 16f7bd9ec1c8438fdfeddc51c7c2c4e1348ec74a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 14:07:34 +0200 Subject: [PATCH 047/219] adde ProjectHelper which helps to keep track with project document --- .../project_manager/project_manager/view.py | 25 ++++++++++++++++++- .../project_manager/project_manager/window.py | 4 +-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 7131687612e..7f8abd82c90 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -15,6 +15,22 @@ def __init__(self, minimum=None, maximum=None, decimals=None): self.decimals = 0 if decimals is None else decimals +class ProjectHelper: + def __init__(self, dbcon): + self.dbcon = dbcon + self.project_doc = None + + def set_project(self, project_name): + self.project_doc = None + + if not project_name: + return + + self.project_doc = self.dbcon.database[project_name].find_one( + {"type": "project"} + ) + + class HierarchyView(QtWidgets.QTreeView): """A tree view that deselects on clicking on an empty area in the view""" column_delegate_defs = { @@ -44,10 +60,12 @@ class HierarchyView(QtWidgets.QTreeView): "pixelAspect" ] - def __init__(self, source_model, *args, **kwargs): + def __init__(self, dbcon, source_model, *args, **kwargs): super(HierarchyView, self).__init__(*args, **kwargs) self._source_model = source_model + project_helper = ProjectHelper(dbcon) + main_delegate = QtWidgets.QStyledItemDelegate() self.setItemDelegate(main_delegate) self.setAlternatingRowColors(True) @@ -72,10 +90,15 @@ def __init__(self, source_model, *args, **kwargs): source_model.index_moved.connect(self._on_rows_moved) + self._project_helper = project_helper self._delegate = main_delegate self._column_delegates = column_delegates self._column_key_to_index = column_key_to_index + def set_project(self, project_name): + self._source_model.set_project(project_name) + self._project_helper.set_project(project_name) + def _on_rows_moved(self, index): parent_index = index.parent() if not self.isExpanded(parent_index): diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 56c496346dc..790df4557e1 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -36,7 +36,7 @@ def __init__(self, parent=None): hierarchy_model = HierarchyModel(dbcon) - hierarchy_view = HierarchyView(hierarchy_model, self) + hierarchy_view = HierarchyView(dbcon, hierarchy_model, self) hierarchy_view.setModel(hierarchy_model) _selection_model = HierarchySelectionModel() _selection_model.setModel(hierarchy_view.model()) @@ -66,7 +66,7 @@ def __init__(self, parent=None): self.refresh_projects() def _set_project(self, project_name=None): - self.hierarchy_model.set_project(project_name) + self.hierarchy_view.set_project(project_name) def refresh_projects(self): current_project = None From 00b7b4514ef10acf74bf35ae35a5bd538aa5f79b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 14:07:48 +0200 Subject: [PATCH 048/219] adde type delegate for task types --- .../project_manager/delegates.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index 51bf6515ad6..e660cd05f41 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -33,3 +33,20 @@ def createEditor(self, parent, option, index): if value is not None: editor.setText(str(value)) return editor + + +class TypeDelegate(QtWidgets.QStyledItemDelegate): + def __init__(self, project_helper, *args, **kwargs): + self.project_helper = project_helper + super(TypeDelegate, self).__init__(*args, **kwargs) + + def createEditor(self, parent, option, index): + editor = QtWidgets.QComboBox(parent) + task_type_defs = self.project_helper.project_doc["config"]["tasks"] + items = list(task_type_defs.keys()) + + value = index.data(QtCore.Qt.DisplayRole) + + editor.addItems(items) + + return editor From 3028e61c0010d4012b539da9f15d8c3ee0b8b119 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 14:08:02 +0200 Subject: [PATCH 049/219] use task type delegate creating combobox --- .../tools/project_manager/project_manager/view.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 7f8abd82c90..88836aa9699 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -1,6 +1,10 @@ from Qt import QtWidgets, QtCore -from .delegates import NumberDelegate, StringDelegate +from .delegates import ( + NumberDelegate, + StringDelegate, + TypeDelegate +) class StringDef: @@ -15,6 +19,10 @@ def __init__(self, minimum=None, maximum=None, decimals=None): self.decimals = 0 if decimals is None else decimals +class TypeDef: + pass + + class ProjectHelper: def __init__(self, dbcon): self.dbcon = dbcon @@ -35,6 +43,7 @@ class HierarchyView(QtWidgets.QTreeView): """A tree view that deselects on clicking on an empty area in the view""" column_delegate_defs = { "name": StringDef(), + "type": TypeDef(), "frameStart": NumberDef(1), "frameEnd": NumberDef(1), "fps": NumberDef(1, decimals=2), @@ -82,6 +91,8 @@ def __init__(self, dbcon, source_model, *args, **kwargs): item_type.maximum, item_type.decimals ) + elif isinstance(item_type, TypeDef): + delegate = TypeDelegate(project_helper) column = self._source_model.columns.index(key) self.setItemDelegateForColumn(column, delegate) From 72c2d66c029c61fe767099296bc2027505d24c96 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 14:56:48 +0200 Subject: [PATCH 050/219] fixed TypeDelegate --- .../project_manager/project_manager/delegates.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index e660cd05f41..22b8427dd14 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -36,17 +36,17 @@ def createEditor(self, parent, option, index): class TypeDelegate(QtWidgets.QStyledItemDelegate): - def __init__(self, project_helper, *args, **kwargs): - self.project_helper = project_helper + 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 = QtWidgets.QComboBox(parent) - task_type_defs = self.project_helper.project_doc["config"]["tasks"] - items = list(task_type_defs.keys()) + if not self._project_doc_cache.project_doc: + return editor - value = index.data(QtCore.Qt.DisplayRole) + task_type_defs = self._project_doc_cache.project_doc["config"]["tasks"] + editor.addItems(list(task_type_defs.keys())) - editor.addItems(items) return editor From 3e2507598f5e2780314c78c945e95488c563e450 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 14:57:06 +0200 Subject: [PATCH 051/219] added multiselection combobox --- .../multiselection_combobox.py | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 openpype/tools/project_manager/project_manager/multiselection_combobox.py diff --git a/openpype/tools/project_manager/project_manager/multiselection_combobox.py b/openpype/tools/project_manager/project_manager/multiselection_combobox.py new file mode 100644 index 00000000000..b26976d3c6c --- /dev/null +++ b/openpype/tools/project_manager/project_manager/multiselection_combobox.py @@ -0,0 +1,215 @@ +from Qt import QtCore, QtGui, QtWidgets + + +class ComboItemDelegate(QtWidgets.QStyledItemDelegate): + """ + Helper styled delegate (mostly based on existing private Qt's + delegate used by the QtWidgets.QComboBox). Used to style the popup like a + list view (e.g windows style). + """ + + def paint(self, painter, option, index): + option = QtWidgets.QStyleOptionViewItem(option) + option.showDecorationSelected = True + + # option.state &= ( + # ~QtWidgets.QStyle.State_HasFocus + # & ~QtWidgets.QStyle.State_MouseOver + # ) + super(ComboItemDelegate, self).paint(painter, option, index) + + +class MultiSelectionComboBox(QtWidgets.QComboBox): + value_changed = QtCore.Signal() + ignored_keys = { + QtCore.Qt.Key_Up, + QtCore.Qt.Key_Down, + QtCore.Qt.Key_PageDown, + QtCore.Qt.Key_PageUp, + QtCore.Qt.Key_Home, + QtCore.Qt.Key_End + } + + def __init__(self, parent=None, **kwargs): + super(MultiSelectionComboBox, self).__init__(parent=parent, **kwargs) + self.setObjectName("MultiSelectionComboBox") + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + self._popup_is_shown = False + self._block_mouse_release_timer = QtCore.QTimer(self, singleShot=True) + self._initial_mouse_pos = None + self._delegate = ComboItemDelegate(self) + self.setItemDelegate(self._delegate) + + def mousePressEvent(self, event): + """Reimplemented.""" + self._popup_is_shown = False + super(MultiSelectionComboBox, self).mousePressEvent(event) + if self._popup_is_shown: + self._initial_mouse_pos = self.mapToGlobal(event.pos()) + self._block_mouse_release_timer.start( + QtWidgets.QApplication.doubleClickInterval() + ) + + def showPopup(self): + """Reimplemented.""" + super(MultiSelectionComboBox, self).showPopup() + view = self.view() + view.installEventFilter(self) + view.viewport().installEventFilter(self) + self._popup_is_shown = True + + def hidePopup(self): + """Reimplemented.""" + self.view().removeEventFilter(self) + self.view().viewport().removeEventFilter(self) + self._popup_is_shown = False + self._initial_mouse_pos = None + super(MultiSelectionComboBox, self).hidePopup() + self.view().clearFocus() + + def _event_popup_shown(self, obj, event): + if not self._popup_is_shown: + return + + current_index = self.view().currentIndex() + model = self.model() + + if event.type() == QtCore.QEvent.MouseMove: + if ( + self.view().isVisible() + and self._initial_mouse_pos is not None + and self._block_mouse_release_timer.isActive() + ): + diff = obj.mapToGlobal(event.pos()) - self._initial_mouse_pos + if diff.manhattanLength() > 9: + self._block_mouse_release_timer.stop() + return + + index_flags = current_index.flags() + state = current_index.data(QtCore.Qt.CheckStateRole) + new_state = None + + if event.type() == QtCore.QEvent.MouseButtonRelease: + if ( + self._block_mouse_release_timer.isActive() + or not current_index.isValid() + or not self.view().isVisible() + or not self.view().rect().contains(event.pos()) + or not index_flags & QtCore.Qt.ItemIsSelectable + or not index_flags & QtCore.Qt.ItemIsEnabled + or not index_flags & QtCore.Qt.ItemIsUserCheckable + ): + return + + if state == QtCore.Qt.Unchecked: + new_state = QtCore.Qt.Checked + else: + new_state = QtCore.Qt.Unchecked + + elif event.type() == QtCore.QEvent.KeyPress: + # TODO: handle QtCore.Qt.Key_Enter, Key_Return? + if event.key() == QtCore.Qt.Key_Space: + # toogle the current items check state + if ( + index_flags & QtCore.Qt.ItemIsUserCheckable + and index_flags & QtCore.Qt.ItemIsTristate + ): + new_state = QtCore.Qt.CheckState((int(state) + 1) % 3) + + elif index_flags & QtCore.Qt.ItemIsUserCheckable: + if state != QtCore.Qt.Checked: + new_state = QtCore.Qt.Checked + else: + new_state = QtCore.Qt.Unchecked + + if new_state is not None: + model.setData(current_index, new_state, QtCore.Qt.CheckStateRole) + self.view().update(current_index) + self.value_changed.emit() + return True + + def eventFilter(self, obj, event): + """Reimplemented.""" + result = self._event_popup_shown(obj, event) + if result is not None: + return result + + return super(MultiSelectionComboBox, self).eventFilter(obj, event) + + def addItem(self, *args, **kwargs): + idx = self.count() + super(MultiSelectionComboBox, self).addItem(*args, **kwargs) + self.model().item(idx).setCheckable(True) + + def paintEvent(self, event): + """Reimplemented.""" + painter = QtWidgets.QStylePainter(self) + option = QtWidgets.QStyleOptionComboBox() + self.initStyleOption(option) + painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, option) + + # draw the icon and text + items = self.checked_items_text() + if not items: + return + + text_rect = self.style().subControlRect( + QtWidgets.QStyle.CC_ComboBox, + option, + QtWidgets.QStyle.SC_ComboBoxEditField + ) + text = ", ".join(items) + new_text = self.fontMetrics().elidedText( + text, QtCore.Qt.ElideRight, text_rect.width() + ) + painter.drawText( + text_rect, + QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, + new_text + ) + + def setItemCheckState(self, index, state): + self.setItemData(index, state, QtCore.Qt.CheckStateRole) + + def set_value(self, values): + for idx in range(self.count()): + value = self.itemData(idx, role=QtCore.Qt.UserRole) + if value in values: + check_state = QtCore.Qt.Checked + else: + check_state = QtCore.Qt.Unchecked + self.setItemData(idx, check_state, QtCore.Qt.CheckStateRole) + + def value(self): + items = list() + for idx in range(self.count()): + state = self.itemData(idx, role=QtCore.Qt.CheckStateRole) + if state == QtCore.Qt.Checked: + items.append( + self.itemData(idx, role=QtCore.Qt.UserRole) + ) + return items + + def checked_items_text(self): + items = list() + for idx in range(self.count()): + state = self.itemData(idx, role=QtCore.Qt.CheckStateRole) + if state == QtCore.Qt.Checked: + items.append(self.itemText(idx)) + return items + + def wheelEvent(self, event): + event.ignore() + + def keyPressEvent(self, event): + if ( + event.key() == QtCore.Qt.Key_Down + and event.modifiers() & QtCore.Qt.AltModifier + ): + return self.showPopup() + + if event.key() in self.ignored_keys: + return event.ignore() + + return super(MultiSelectionComboBox, self).keyPressEvent(event) From 948212c658475ab3d9a3d61e48cc85aac3c83a63 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 14:57:34 +0200 Subject: [PATCH 052/219] implemented ToolsDelegate --- .../project_manager/delegates.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index 22b8427dd14..67e462bf4a4 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -1,5 +1,7 @@ from Qt import QtWidgets, QtCore +from .multiselection_combobox import MultiSelectionComboBox + class NumberDelegate(QtWidgets.QStyledItemDelegate): def __init__(self, minimum, maximum, decimals, *args, **kwargs): @@ -48,5 +50,20 @@ def createEditor(self, parent, option, index): task_type_defs = self._project_doc_cache.project_doc["config"]["tasks"] editor.addItems(list(task_type_defs.keys())) + return editor + + +class ToolsDelegate(QtWidgets.QStyledItemDelegate): + def __init__(self, tools_cache, *args, **kwargs): + self._tools_cache = tools_cache + super(ToolsDelegate, self).__init__(*args, **kwargs) + + def createEditor(self, parent, option, index): + editor = MultiSelectionComboBox(parent) + if not self._tools_cache.tools_data: + return editor + + for key, label in self._tools_cache.tools_data: + editor.addItem(label, key) return editor From e802fd240c5a72c7c4dd391afa6d2c65df84bd8a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 14:58:36 +0200 Subject: [PATCH 053/219] changed variable name --- .../tools/project_manager/project_manager/view.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 88836aa9699..2543d061ea7 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -23,7 +23,7 @@ class TypeDef: pass -class ProjectHelper: +class ProjectDocCache: def __init__(self, dbcon): self.dbcon = dbcon self.project_doc = None @@ -73,7 +73,7 @@ def __init__(self, dbcon, source_model, *args, **kwargs): super(HierarchyView, self).__init__(*args, **kwargs) self._source_model = source_model - project_helper = ProjectHelper(dbcon) + project_doc_cache = ProjectDocCache(dbcon) main_delegate = QtWidgets.QStyledItemDelegate() self.setItemDelegate(main_delegate) @@ -101,14 +101,17 @@ def __init__(self, dbcon, source_model, *args, **kwargs): source_model.index_moved.connect(self._on_rows_moved) - self._project_helper = project_helper + self._project_doc_cache = project_doc_cache self._delegate = main_delegate self._column_delegates = column_delegates self._column_key_to_index = column_key_to_index def set_project(self, project_name): + # Trigger helpers first + self._project_doc_cache.set_project(project_name) + + # Trigger update of model after all data for delegates are filled self._source_model.set_project(project_name) - self._project_helper.set_project(project_name) def _on_rows_moved(self, index): parent_index = index.parent() From 09a92474f8fd1cc54c3d24cfc4b3f24f8710b302 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 14:58:56 +0200 Subject: [PATCH 054/219] added tools delegate --- .../project_manager/project_manager/view.py | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 2543d061ea7..82275a4557f 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -3,9 +3,12 @@ from .delegates import ( NumberDelegate, StringDelegate, - TypeDelegate + TypeDelegate, + ToolsDelegate ) +from openpype.lib import ApplicationManager + class StringDef: def __init__(self, regex=None): @@ -23,6 +26,10 @@ class TypeDef: pass +class ToolsDef: + pass + + class ProjectDocCache: def __init__(self, dbcon): self.dbcon = dbcon @@ -39,6 +46,20 @@ def set_project(self, project_name): ) +class ToolsCache: + def __init__(self): + self.tools_data = [] + + def refresh(self): + app_manager = ApplicationManager() + tools_data = [] + for tool_name, tool in app_manager.tools.items(): + tools_data.append( + (tool_name, tool.label) + ) + self.tools_data = tools_data + + class HierarchyView(QtWidgets.QTreeView): """A tree view that deselects on clicking on an empty area in the view""" column_delegate_defs = { @@ -54,7 +75,7 @@ class HierarchyView(QtWidgets.QTreeView): "clipIn": NumberDef(1), "clipOut": NumberDef(1), "pixelAspect": NumberDef(0, decimals=2), - # "tools_env": NumberDef(0) + "tools_env": ToolsDef() } persistent_columns = [ "frameStart", @@ -66,7 +87,8 @@ class HierarchyView(QtWidgets.QTreeView): "handleEnd", "clipIn", "clipOut", - "pixelAspect" + "pixelAspect", + "tools_env" ] def __init__(self, dbcon, source_model, *args, **kwargs): @@ -74,6 +96,7 @@ def __init__(self, dbcon, source_model, *args, **kwargs): self._source_model = source_model project_doc_cache = ProjectDocCache(dbcon) + tools_cache = ToolsCache() main_delegate = QtWidgets.QStyledItemDelegate() self.setItemDelegate(main_delegate) @@ -85,14 +108,19 @@ def __init__(self, dbcon, source_model, *args, **kwargs): for key, item_type in self.column_delegate_defs.items(): if isinstance(item_type, StringDef): delegate = StringDelegate() + elif isinstance(item_type, NumberDef): delegate = NumberDelegate( item_type.minimum, item_type.maximum, item_type.decimals ) + elif isinstance(item_type, TypeDef): - delegate = TypeDelegate(project_helper) + delegate = TypeDelegate(project_doc_cache) + + elif isinstance(item_type, ToolsDef): + delegate = ToolsDelegate(tools_cache) column = self._source_model.columns.index(key) self.setItemDelegateForColumn(column, delegate) @@ -102,6 +130,8 @@ def __init__(self, dbcon, source_model, *args, **kwargs): source_model.index_moved.connect(self._on_rows_moved) self._project_doc_cache = project_doc_cache + self._tools_cache = tools_cache + self._delegate = main_delegate self._column_delegates = column_delegates self._column_key_to_index = column_key_to_index @@ -109,6 +139,7 @@ def __init__(self, dbcon, source_model, *args, **kwargs): def set_project(self, project_name): # Trigger helpers first self._project_doc_cache.set_project(project_name) + self._tools_cache.refresh() # Trigger update of model after all data for delegates are filled self._source_model.set_project(project_name) From 4d737e47d00fd4e4a1ef68944d2b57384ad18d5d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 15:19:13 +0200 Subject: [PATCH 055/219] separated global data getter --- .../project_manager/project_manager/model.py | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index a6446b22bbf..7907966951f 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -720,6 +720,8 @@ class BaseItem: _name_icon = None _is_duplicated = False + _None = object() + def __init__(self, data=None): self._id = uuid4() self._children = list() @@ -750,18 +752,19 @@ def move_to(self, item, row): self._children.pop(idx) self._children.insert(row, item) - def data(self, key, role): + def _global_data(self, role): if role == IDENTIFIER_ROLE: return self._id if role == DUPLICATED_ROLE: return self._is_duplicated - if role == QtCore.Qt.ToolTipRole: - if self._is_duplicated: - return "Asset with name \"{}\" already exists.".format( - self._data["name"] - ) + return self._None + + def data(self, key, role): + value = self._global_data(role) + if value is not self._None: + return value if key not in self.columns: return None @@ -1009,6 +1012,13 @@ def name_icon(cls): cls._name_icon = qtawesome.icon("fa.folder", color="#333333") return cls._name_icon + def _global_data(self, role): + if role == QtCore.Qt.ToolTipRole and self._is_duplicated: + return "Asset with name \"{}\" already exists.".format( + self._data["name"] + ) + return super(AssetItem, self)._global_data(role) + class TaskItem(BaseItem): columns = { @@ -1028,3 +1038,10 @@ def name_icon(cls): def add_child(self, item, row=None): raise AssertionError("BUG: Can't add children to Task") + + def _global_data(self, role): + if role == QtCore.Qt.ToolTipRole and self._is_duplicated: + return "Duplicated Task name \"{}\".".format( + self._data["name"] + ) + return super(TaskItem, self)._global_data(role) From 1d0058dc6145deef13e2ba552093911dee1ef4b9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 16:09:14 +0200 Subject: [PATCH 056/219] fix asset name duplications --- openpype/tools/project_manager/project_manager/model.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 7907966951f..e6344fc5dcf 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -279,7 +279,6 @@ def add_new_asset(self, source_index): "type": "asset" } new_child = AssetItem(data) - self._asset_items_by_name[name].append(new_child) result = self.add_item(new_child, parent, new_row) @@ -322,6 +321,10 @@ def add_items(self, items, parent=None, start_row=None): parent.add_child(item, row) + if isinstance(item, AssetItem): + name = item.data("name", QtCore.Qt.DisplayRole) + self._asset_items_by_name[name].append(item) + if item.id not in self._items_by_id: self._items_by_id[item.id] = item @@ -387,6 +390,9 @@ def _rename_asset(self, asset_item, new_name): return prev_name = asset_item.data("name", QtCore.Qt.DisplayRole) + if prev_name == new_name: + return + self._asset_items_by_name[prev_name].remove(asset_item) self._validate_asset_duplicity(prev_name) From b5bd9e57131271380e63243f852a1d3d51ce9494 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 16:09:32 +0200 Subject: [PATCH 057/219] tools_env data can be changed --- .../project_manager/project_manager/delegates.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index 67e462bf4a4..3d8451bde6f 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -18,7 +18,7 @@ def createEditor(self, parent, option, index): editor.setMinimum(self.minimum) editor.setMaximum(self.maximum) - value = index.data(QtCore.Qt.DisplayRole) + value = index.data(QtCore.Qt.EditRole) if value is not None: editor.setValue(value) return editor @@ -31,7 +31,7 @@ def createEditor(self, parent, option, index): class StringDelegate(QtWidgets.QStyledItemDelegate): def createEditor(self, parent, option, index): editor = QtWidgets.QLineEdit(parent) - value = index.data(QtCore.Qt.DisplayRole) + value = index.data(QtCore.Qt.EditRole) if value is not None: editor.setText(str(value)) return editor @@ -67,3 +67,10 @@ def createEditor(self, parent, option, index): editor.addItem(label, key) return editor + + def setEditorData(self, editor, index): + value = index.data(QtCore.Qt.EditRole) + editor.set_value(value) + + def setModelData(self, editor, model, index): + model.setData(index, editor.value(), QtCore.Qt.EditRole) From 9813597feb4ecf76e4405ea9e5ff8edba07eda84 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 20:45:00 +0200 Subject: [PATCH 058/219] added base of save mechanism --- .../tools/project_manager/project_manager/model.py | 3 +++ .../project_manager/project_manager/window.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index e6344fc5dcf..db016b38c90 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -717,6 +717,9 @@ def clear(self): self._reset_root_item() self.endResetModel() + def save(self): + print("Saving (They said)") + class BaseItem: columns = [] diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 790df4557e1..0be69c29c83 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -38,6 +38,7 @@ def __init__(self, parent=None): hierarchy_view = HierarchyView(dbcon, hierarchy_model, self) hierarchy_view.setModel(hierarchy_model) + _selection_model = HierarchySelectionModel() _selection_model.setModel(hierarchy_view.model()) hierarchy_view.setSelectionModel(_selection_model) @@ -47,13 +48,23 @@ def __init__(self, parent=None): header.setSectionResizeMode( header.logicalIndex(0), QtWidgets.QHeaderView.Stretch ) + buttons_widget = QtWidgets.QWidget(self) + + save_btn = QtWidgets.QPushButton("Save", buttons_widget) + + buttons_layout = QtWidgets.QHBoxLayout(buttons_widget) + buttons_layout.setContentsMargins(0, 0, 0, 0) + buttons_layout.addStretch(1) + buttons_layout.addWidget(save_btn) main_layout = QtWidgets.QVBoxLayout(self) main_layout.addWidget(project_widget) main_layout.addWidget(hierarchy_view) + main_layout.addWidget(buttons_widget) refresh_projects_btn.clicked.connect(self._on_project_refresh) project_combobox.currentIndexChanged.connect(self._on_project_change) + save_btn.clicked.connect(self._on_save_click) self.project_model = project_model self.project_combobox = project_combobox @@ -90,3 +101,6 @@ def _on_project_change(self): def _on_project_refresh(self): self.refresh_projects() + + def _on_save_click(self): + self.hierarchy_model.save() From ce95ee27de1262d527e67c645af9f954932112c4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 20:49:01 +0200 Subject: [PATCH 059/219] implemented few necessary properties --- .../project_manager/project_manager/model.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index db016b38c90..d7abca47f0e 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -924,9 +924,22 @@ class ProjectItem(BaseItem): } def __init__(self, project_doc): + self._mongo_id = project_doc["_id"] + data = self.data_from_doc(project_doc) super(ProjectItem, self).__init__(data) + @property + def project_id(self): + return self._mongo_id + + @property + def asset_id(self): + return None + + @property + def name(self): + return self._data["name"] @classmethod def data_from_doc(cls, project_doc): data = { @@ -997,9 +1010,25 @@ class AssetItem(BaseItem): } def __init__(self, asset_doc): + self.mongo_id = asset_doc.get("_id") + self._project_id = None + data = self.data_from_doc(asset_doc) super(AssetItem, self).__init__(data) + @property + def project_id(self): + if self._project_id is None: + self._project_id = self.parent().project_id + return self._project_id + + @property + def asset_id(self): + return self.mongo_id + + @property + def name(self): + return self._data["name"] @classmethod def data_from_doc(cls, asset_doc): data = { From c2e6c974b979f6c5d3992286a78a1a1044c975d2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 20:51:13 +0200 Subject: [PATCH 060/219] task can be converted to data --- openpype/tools/project_manager/project_manager/model.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index d7abca47f0e..b346dbba078 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1083,3 +1083,10 @@ def _global_data(self, role): self._data["name"] ) return super(TaskItem, self)._global_data(role) + + def to_doc_data(self): + data = copy.deepcopy(self._data) + name = data.pop("name") + return { + name: data + } From df75e05e7d4dd835dbca2072d756f02afe4c7328 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 20:59:41 +0200 Subject: [PATCH 061/219] added conversion of asset item to doc data --- .../project_manager/project_manager/model.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index b346dbba078..5ed09bc6772 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1029,6 +1029,35 @@ def asset_id(self): @property def name(self): return self._data["name"] + + def to_doc(self): + tasks = {} + for item in self.children(): + if isinstance(item, TaskItem): + tasks.update(item.to_doc_data()) + + doc_data = { + "parents": self.parent().asset_parents(), + "visualParent": self.parent().asset_id, + "tasks": tasks + } + schema_name = ( + self._origin_asset_doc.get("schema") or "openpype:asset-3.0" + ) + + doc = { + "name": self._data["name"], + "type": self._data["type"], + "schema": schema_name, + "data": doc_data, + "parent": self.project_id + } + for key, value in self._data.items(): + if key in doc: + continue + doc_data[key] = value + + return doc @classmethod def data_from_doc(cls, asset_doc): data = { From 7d56929ffff8655a673dc35bd6332b13e6b616a2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 21:00:05 +0200 Subject: [PATCH 062/219] it is possible to get update changes for mongo from asset item --- .../project_manager/project_manager/model.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 5ed09bc6772..9d26a1c96ef 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1058,6 +1058,42 @@ def to_doc(self): doc_data[key] = value return doc + + def update_data(self): + if not self.mongo_id: + return {} + + document = self.to_doc() + + changes = {} + + for key, value in document.items(): + if key in ("data", "_id"): + continue + + if ( + key in self._origin_asset_doc + and self._origin_asset_doc[key] == value + ): + continue + + changes[key] = value + + if "data" not in self._origin_asset_doc: + changes["data"] = document["data"] + else: + origin_data = self._origin_asset_doc["data"] + + for key, value in document["data"].items(): + if key in origin_data and origin_data[key] == value: + continue + _key = "data.{}".format(key) + changes[_key] = value + + if changes: + return {"$set": changes} + return {} + @classmethod def data_from_doc(cls, asset_doc): data = { From feb64e776e4fff1cdf6ccc879dc94763b2a17b13 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 21:03:28 +0200 Subject: [PATCH 063/219] added child_parents method --- .../tools/project_manager/project_manager/model.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 9d26a1c96ef..cc52e67c47a 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -940,6 +940,10 @@ def asset_id(self): @property def name(self): return self._data["name"] + + def child_parents(self): + return [] + @classmethod def data_from_doc(cls, project_doc): data = { @@ -1030,6 +1034,11 @@ def asset_id(self): def name(self): return self._data["name"] + def child_parents(self): + parents = self.parent().child_parents() + parents.append(self.name) + return parents + def to_doc(self): tasks = {} for item in self.children(): @@ -1037,7 +1046,7 @@ def to_doc(self): tasks.update(item.to_doc_data()) doc_data = { - "parents": self.parent().asset_parents(), + "parents": self.parent().child_parents(), "visualParent": self.parent().asset_id, "tasks": tasks } From e60aa9e80de3d03e69ead12e3c9f9a63b56f5f45 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 21:08:01 +0200 Subject: [PATCH 064/219] modified how AssetItem is created --- .../project_manager/project_manager/model.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index cc52e67c47a..9690f52ed53 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -274,11 +274,7 @@ def add_new_asset(self, source_index): parent = item.parent() new_row = item.row() + 1 - data = { - "name": name, - "type": "asset" - } - new_child = AssetItem(data) + new_child = AssetItem() result = self.add_item(new_child, parent, new_row) @@ -1013,10 +1009,14 @@ class AssetItem(BaseItem): "data.tools_env": 1 } - def __init__(self, asset_doc): + def __init__(self, asset_doc=None): + if not asset_doc: + asset_doc = {} self.mongo_id = asset_doc.get("_id") self._project_id = None + self._origin_asset_doc = copy.deepcopy(asset_doc) + data = self.data_from_doc(asset_doc) super(AssetItem, self).__init__(data) @@ -1106,9 +1106,14 @@ def update_data(self): @classmethod def data_from_doc(cls, asset_doc): data = { - "name": asset_doc["name"], - "type": asset_doc["type"] + "name": None, + "type": "asset" } + if asset_doc: + for key in data.keys(): + if key in asset_doc: + data[key] = asset_doc[key] + doc_data = asset_doc.get("data") or {} for key in cls.columns: if key in data: From dc76e91f41c3b87c96abc950e059d6864777d349 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 21:08:32 +0200 Subject: [PATCH 065/219] add id to doc data --- openpype/tools/project_manager/project_manager/model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 9690f52ed53..9eb7a7656c1 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1061,6 +1061,9 @@ def to_doc(self): "data": doc_data, "parent": self.project_id } + if self.mongo_id: + doc["_id"] = self.mongo_id + for key, value in self._data.items(): if key in doc: continue From bc532fa6cb330907d1ee578c2efb306e157090e6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 21:10:15 +0200 Subject: [PATCH 066/219] AssetItem requires at least empty dictionary --- .../tools/project_manager/project_manager/model.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 9eb7a7656c1..dd9bfdc5617 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -266,15 +266,19 @@ def add_new_asset(self, source_index): item = self.items_by_id[item_id] new_row = None + name = None + asset_data = {} if isinstance(item, (RootItem, ProjectItem)): name = "ep" parent = item else: - name = source_index.data(QtCore.Qt.DisplayRole) parent = item.parent() new_row = item.row() + 1 - new_child = AssetItem() + if name: + asset_data["name"] = name + + new_child = AssetItem(asset_data) result = self.add_item(new_child, parent, new_row) @@ -1009,7 +1013,7 @@ class AssetItem(BaseItem): "data.tools_env": 1 } - def __init__(self, asset_doc=None): + def __init__(self, asset_doc): if not asset_doc: asset_doc = {} self.mongo_id = asset_doc.get("_id") From 1c1c11625aef236fe6250b3c1123159fecb43e15 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 21:11:09 +0200 Subject: [PATCH 067/219] added schema to query projection --- openpype/tools/project_manager/project_manager/model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index dd9bfdc5617..12ad49086b6 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -997,6 +997,7 @@ class AssetItem(BaseItem): "_id": 1, "data.tasks": 1, "data.visualParent": 1, + "schema": 1, "name": 1, "type": 1, From e124d084aa2795ac9e13c4feba2654b127d4c7c6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 May 2021 21:11:20 +0200 Subject: [PATCH 068/219] implemented save method --- .../project_manager/project_manager/model.py | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 12ad49086b6..83a8b2d7f8f 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -7,7 +7,7 @@ IDENTIFIER_ROLE, DUPLICATED_ROLE ) - +from pymongo import UpdateOne from avalon.vendor import qtawesome from Qt import QtCore, QtGui @@ -718,7 +718,51 @@ def clear(self): self.endResetModel() def save(self): - print("Saving (They said)") + project_item = None + for _project_item in self._root_item.children(): + project_item = _project_item + + if not project_item: + return + + project_name = project_item.name + project_col = self.dbcon.database[project_name] + + to_process = Queue() + to_process.put(project_item) + + update_list = [] + while not to_process.empty(): + parent = to_process.get() + insert_list = [] + for item in parent.children(): + if not isinstance(item, AssetItem): + continue + + to_process.put(item) + + if item.asset_id is None: + insert_list.append(item) + continue + + update_data = item.update_data() + if update_data: + update_list.append(UpdateOne( + {"_id": item.asset_id}, + update_data + )) + + if insert_list: + new_docs = [] + for item in insert_list: + new_docs.append(item.to_doc()) + + result = project_col.insert_many(new_docs) + for idx, mongo_id in enumerate(result.inserted_ids): + insert_list[idx].mongo_id = mongo_id + + if update_list: + project_col.bulk_write(update_list) class BaseItem: From 5eb439b3b32653b8f24bdb667e100b8cdf5285e4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 May 2021 14:05:11 +0200 Subject: [PATCH 069/219] changed imports and main definition location --- openpype/tools/project_manager/__init__.py | 6 ++- openpype/tools/project_manager/__main__.py | 15 +------ .../project_manager/__init__.py | 40 +++++++++++++------ 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/openpype/tools/project_manager/__init__.py b/openpype/tools/project_manager/__init__.py index 7d8f8bf4325..880fc253cf6 100644 --- a/openpype/tools/project_manager/__init__.py +++ b/openpype/tools/project_manager/__init__.py @@ -1,6 +1,10 @@ -from .project_manager import Window +from .project_manager import ( + Window, + main +) __all__ = ( "Window", + "main" ) diff --git a/openpype/tools/project_manager/__main__.py b/openpype/tools/project_manager/__main__.py index 0855a0fc71f..2e57af5f114 100644 --- a/openpype/tools/project_manager/__main__.py +++ b/openpype/tools/project_manager/__main__.py @@ -1,17 +1,4 @@ -import sys - -from project_manager import Window - -from Qt import QtWidgets - - -def main(): - app = QtWidgets.QApplication([]) - - window = Window() - window.show() - - sys.exit(app.exec_()) +from project_manager import main if __name__ == "__main__": diff --git a/openpype/tools/project_manager/project_manager/__init__.py b/openpype/tools/project_manager/project_manager/__init__.py index a652e950c49..dccc46f771d 100644 --- a/openpype/tools/project_manager/project_manager/__init__.py +++ b/openpype/tools/project_manager/project_manager/__init__.py @@ -1,3 +1,23 @@ +__all__ = ( + "IDENTIFIER_ROLE", + + "HierarchyView", + + "ProjectModel", + + "HierarchyModel", + "HierarchySelectionModel", + "BaseItem", + "RootItem", + "ProjectItem", + "AssetItem", + "TaskItem", + + "Window", + "main" +) + + from .constants import ( IDENTIFIER_ROLE ) @@ -15,20 +35,14 @@ ) from .window import Window -__all__ = ( - "IDENTIFIER_ROLE", - "HierarchyView", +def main(): + import sys + from Qt import QtWidgets - "ProjectModel", + app = QtWidgets.QApplication([]) - "HierarchyModel", - "HierarchySelectionModel", - "BaseItem", - "RootItem", - "ProjectItem", - "AssetItem", - "TaskItem", + window = Window() + window.show() - "Window" -) + sys.exit(app.exec_()) From 70ed7abeb5d4201965570bbc88ee53e68587c4d2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 May 2021 15:13:50 +0200 Subject: [PATCH 070/219] added projectmanager to openpype cli commands --- openpype/cli.py | 5 +++++ openpype/pype_commands.py | 6 ++++++ tools/run_project_manager.ps1 | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 tools/run_project_manager.ps1 diff --git a/openpype/cli.py b/openpype/cli.py index 9c498257210..df38c74a219 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -224,6 +224,11 @@ def launch(app, project, asset, task, PypeCommands().run_application(app, project, asset, task, tools, arguments) +@main.command(context_settings={"ignore_unknown_options": True}) +def projectmanager(): + PypeCommands().launch_project_manager() + + @main.command( context_settings=dict( ignore_unknown_options=True, diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 981cca82dc8..326ca8349a7 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -110,6 +110,12 @@ def extractenvironments(output_json_path, project, asset, task, app): with open(output_json_path, "w") as file_stream: json.dump(env, file_stream, indent=4) + @staticmethod + def launch_project_manager(): + from openpype.tools import project_manager + + project_manager.main() + def texture_copy(self, project, asset, path): pass diff --git a/tools/run_project_manager.ps1 b/tools/run_project_manager.ps1 new file mode 100644 index 00000000000..78dce19df1d --- /dev/null +++ b/tools/run_project_manager.ps1 @@ -0,0 +1,18 @@ +<# +.SYNOPSIS + Helper script OpenPype Tray. + +.DESCRIPTION + + +.EXAMPLE + +PS> .\run_tray.ps1 + +#> +$current_dir = Get-Location +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$openpype_root = (Get-Item $script_dir).parent.FullName +Set-Location -Path $openpype_root +& poetry run python "$($openpype_root)\start.py" projectmanager +Set-Location -Path $current_dir From f561e1a864f04d5d1389403fd0a0668d61c3b8b2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 May 2021 15:14:05 +0200 Subject: [PATCH 071/219] added validation of number values in number delegate --- .../tools/project_manager/project_manager/delegates.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index 3d8451bde6f..88b366a7d0a 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -20,7 +20,14 @@ def createEditor(self, parent, option, index): value = index.data(QtCore.Qt.EditRole) if value is not None: - editor.setValue(value) + try: + if isinstance(value, str): + value = float(value) + editor.setValue(value) + + except Exception: + print("Couldn't set invalid value \"{}\"".format(str(value))) + return editor # def updateEditorGeometry(self, editor, options, index): From 0a273b23049c7bef207c4b539c9ef4da1e7a3a41 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 18:03:39 +0200 Subject: [PATCH 072/219] added _add_task to hierarchy view --- .../tools/project_manager/project_manager/view.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 82275a4557f..30a9ec6eb15 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -257,11 +257,16 @@ def _delete_item(self): self._source_model.remove_index(index) def _on_ctrl_shift_enter_pressed(self): - index = self.currentIndex() - if not index.isValid(): + self._add_task() + + def _add_task(self, parent_index=None): + if parent_index is None: + parent_index = self.currentIndex() + + if not parent_index.isValid(): return - new_index = self._source_model.add_new_task(index) + new_index = self._source_model.add_new_task(parent_index) if new_index is None: return From 96d8079a54431a83c9c4063fc7f0c7055debb01f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 18:03:57 +0200 Subject: [PATCH 073/219] type is edited as first thing on task creation --- openpype/tools/project_manager/project_manager/view.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 30a9ec6eb15..66051fe0302 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -274,10 +274,14 @@ def _add_task(self, parent_index=None): self.setState(HierarchyView.NoState) QtWidgets.QApplication.processEvents() + # TODO change hardcoded column index to coded + task_type_index = self._source_model.index( + new_index.row(), 1, new_index.parent() + ) # Change current index - self.setCurrentIndex(new_index) + self.setCurrentIndex(task_type_index) # Start editing - self.edit(new_index) + self.edit(task_type_index) def _on_shift_enter_pressed(self): index = self.currentIndex() From 1e79141b24d946fc9e0e42efde0275b0eb903777 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 18:09:51 +0200 Subject: [PATCH 074/219] added type to persisten columns --- openpype/tools/project_manager/project_manager/view.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 66051fe0302..b2ca27e77cc 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -78,6 +78,7 @@ class HierarchyView(QtWidgets.QTreeView): "tools_env": ToolsDef() } persistent_columns = [ + "type", "frameStart", "frameEnd", "fps", From acb7ea347a37f9a4510c0ad3160bb22497672121 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 18:10:29 +0200 Subject: [PATCH 075/219] TaskItem does not have default values prefilled --- .../project_manager/project_manager/model.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 83a8b2d7f8f..b70426640b3 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -298,8 +298,7 @@ def add_new_task(self, parent_index): if not isinstance(parent, AssetItem): return None - data = {"name": "task"} - new_child = TaskItem(data) + new_child = TaskItem() return self.add_item(new_child, parent) def add_items(self, items, parent=None, start_row=None): @@ -1199,6 +1198,11 @@ class TaskItem(BaseItem): "type" } + def __init__(self, data=None): + if data is None: + data = {} + super(TaskItem, self).__init__(data) + @classmethod def name_icon(cls): if cls._name_icon is None: @@ -1217,7 +1221,16 @@ def _global_data(self, role): def to_doc_data(self): data = copy.deepcopy(self._data) - name = data.pop("name") + data.pop("name") + name = self.data("name", QtCore.Qt.DisplayRole) return { name: data } + + def data(self, key, role): + if ( + role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole) + and key == "name" + ): + return self._data[key] or self._data["type"] or "< Select Type >" + return super(TaskItem, self).data(key, role) From 048f856f6317496b23aec92c1da4de91e72ac068 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 18:12:13 +0200 Subject: [PATCH 076/219] do not set keys that are not in editable keys --- openpype/tools/project_manager/project_manager/model.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index b70426640b3..3f400fc8eea 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -844,12 +844,11 @@ def setData(self, key, value, role): self._is_duplicated = value return True - if key not in self.columns: - return False - if role == QtCore.Qt.EditRole: - self._data[key] = value + if key not in self.editable_columns: + return False + self._data[key] = value # must return true if successful return True From 35e7cb7b46e18c41ac0543e1c3f749906fd1aedd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 18:14:02 +0200 Subject: [PATCH 077/219] defined new role defying if asset item is modifiable --- openpype/tools/project_manager/project_manager/constants.py | 1 + openpype/tools/project_manager/project_manager/model.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/constants.py b/openpype/tools/project_manager/project_manager/constants.py index 61d1944979d..49ae2f7883c 100644 --- a/openpype/tools/project_manager/project_manager/constants.py +++ b/openpype/tools/project_manager/project_manager/constants.py @@ -3,3 +3,4 @@ IDENTIFIER_ROLE = QtCore.Qt.UserRole + 1 DUPLICATED_ROLE = QtCore.Qt.UserRole + 2 +HIERARCHY_CHANGE_ABLE_ROLE = QtCore.Qt.UserRole + 3 diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 3f400fc8eea..c397906e713 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -5,7 +5,8 @@ from .constants import ( IDENTIFIER_ROLE, - DUPLICATED_ROLE + DUPLICATED_ROLE, + HIERARCHY_CHANGE_ABLE_ROLE ) from pymongo import UpdateOne from avalon.vendor import qtawesome From c9202e2c029044b0df60d4d7a238c5164ecb47a2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 18:14:48 +0200 Subject: [PATCH 078/219] asset item can handle new role defying if is editable --- .../project_manager/project_manager/model.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index c397906e713..6744d511131 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1062,6 +1062,7 @@ def __init__(self, asset_doc): asset_doc = {} self.mongo_id = asset_doc.get("_id") self._project_id = None + self._hierarchy_changes_enabled = True self._origin_asset_doc = copy.deepcopy(asset_doc) @@ -1181,12 +1182,38 @@ def name_icon(cls): return cls._name_icon def _global_data(self, role): + if role == HIERARCHY_CHANGE_ABLE_ROLE: + return self._hierarchy_changes_enabled + if role == QtCore.Qt.ToolTipRole and self._is_duplicated: return "Asset with name \"{}\" already exists.".format( self._data["name"] ) return super(AssetItem, self)._global_data(role) + def setData(self, key, value, role): + if role == HIERARCHY_CHANGE_ABLE_ROLE: + if self._hierarchy_changes_enabled == value: + return False + self._hierarchy_changes_enabled = value + return True + + if ( + role == QtCore.Qt.EditRole + and key == "name" + and not self._hierarchy_changes_enabled + ): + return False + return super(AssetItem, self).setData(key, value, role) + + def flags(self, key): + if key == "name": + flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + if self._hierarchy_changes_enabled: + flags |= QtCore.Qt.ItemIsEditable + return flags + return super(AssetItem, self).flags(key) + class TaskItem(BaseItem): columns = { From 9a0203bd9e45ca6820030bb805a49186231c7e9d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 18:15:15 +0200 Subject: [PATCH 079/219] store information about loaded asset documents form database if can be modified --- .../project_manager/project_manager/model.py | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 6744d511131..90a45b9f486 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -133,6 +133,36 @@ def set_project(self, project_name): asset_doc["_id"]: asset_doc for asset_doc in asset_docs } + + # Prepare booleans if asset item can be modified (name or hierarchy) + # - the same must be applied to all it's parents + asset_ids = list(asset_docs_by_id.keys()) + result = [] + if asset_ids: + result = self.dbcon.database[project_name].aggregate([ + { + "$match": { + "type": "subset", + "parent": {"$in": asset_ids} + } + }, + { + "$group": { + "_id": "$parent", + "count": {"$sum": 1} + } + } + ]) + + asset_modifiable = { + asset_id: True + for asset_id in asset_docs_by_id.keys() + } + for item in result: + asset_id = item["_id"] + count = item["count"] + asset_modifiable[asset_id] = count < 1 + asset_docs_by_parent_id = collections.defaultdict(list) for asset_doc in asset_docs_by_id.values(): parent_id = asset_doc["data"]["visualParent"] @@ -142,7 +172,7 @@ def set_project(self, project_name): appending_queue.put((None, project_item)) asset_items_by_id = {} - + non_modifiable_items = set() while not appending_queue.empty(): parent_id, parent_item = appending_queue.get() if parent_id not in asset_docs_by_parent_id: @@ -157,12 +187,35 @@ def set_project(self, project_name): # Store item by id for task processing asset_id = asset_doc["_id"] + if not asset_modifiable[asset_id]: + non_modifiable_items.add(new_item.id) + asset_items_by_id[asset_id] = new_item # Add item to appending queue appending_queue.put((asset_id, new_item)) self.add_items(new_items, parent_item) + # Handle Asset's that are not modifiable + # - pass the information to all it's parents + non_modifiable_queue = Queue() + for item_id in non_modifiable_items: + non_modifiable_queue.put(item_id) + + while not non_modifiable_queue.empty(): + item_id = non_modifiable_queue.get() + item = self._items_by_id[item_id] + item.setData(None, False, HIERARCHY_CHANGE_ABLE_ROLE) + + parent = item.parent() + if ( + isinstance(parent, AssetItem) + and parent.id not in non_modifiable_items + ): + non_modifiable_items.add(parent.id) + non_modifiable_queue.put(parent.id) + + # Add task items for asset_id, asset_item in asset_items_by_id.items(): asset_doc = asset_docs_by_id[asset_id] asset_tasks = asset_doc["data"]["tasks"] From 6b940ae6bff1a1036deb4efef9a28ed1a8a6d313 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:24:23 +0200 Subject: [PATCH 080/219] added few checks --- openpype/tools/project_manager/project_manager/view.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index b2ca27e77cc..dee694a59c6 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -253,8 +253,9 @@ def keyPressEvent(self, event): else: event.accept() - def _delete_item(self): - index = self.currentIndex() + def _delete_item(self, index=None): + if index is None: + index = self.currentIndex() self._source_model.remove_index(index) def _on_ctrl_shift_enter_pressed(self): @@ -294,6 +295,8 @@ def _on_shift_enter_pressed(self): QtWidgets.QApplication.processEvents() new_index = self._source_model.add_new_asset(index) + if new_index is None: + return # Change current index self.setCurrentIndex(new_index) From 41f21bc0669394b22c2d1105ec7f2e546e8707f1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:24:33 +0200 Subject: [PATCH 081/219] added REMOVED_ROLE --- openpype/tools/project_manager/project_manager/constants.py | 1 + openpype/tools/project_manager/project_manager/model.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/constants.py b/openpype/tools/project_manager/project_manager/constants.py index 49ae2f7883c..7f9a859ac1f 100644 --- a/openpype/tools/project_manager/project_manager/constants.py +++ b/openpype/tools/project_manager/project_manager/constants.py @@ -4,3 +4,4 @@ IDENTIFIER_ROLE = QtCore.Qt.UserRole + 1 DUPLICATED_ROLE = QtCore.Qt.UserRole + 2 HIERARCHY_CHANGE_ABLE_ROLE = QtCore.Qt.UserRole + 3 +REMOVED_ROLE = QtCore.Qt.UserRole + 4 diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 90a45b9f486..c06be9e2198 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -6,7 +6,8 @@ from .constants import ( IDENTIFIER_ROLE, DUPLICATED_ROLE, - HIERARCHY_CHANGE_ABLE_ROLE + HIERARCHY_CHANGE_ABLE_ROLE, + REMOVED_ROLE ) from pymongo import UpdateOne from avalon.vendor import qtawesome From b8775392f190c4958a06ca6eec7a7da80f7d9fb5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:26:13 +0200 Subject: [PATCH 082/219] minor changes and fixes --- .../tools/project_manager/project_manager/model.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index c06be9e2198..750cb52661a 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -91,6 +91,7 @@ class HierarchyModel(QtCore.QAbstractItemModel): def __init__(self, dbcon, parent=None): super(HierarchyModel, self).__init__(parent) + # TODO Reset them on project change self._current_project = None self._root_item = None self._items_by_id = {} @@ -275,7 +276,9 @@ def headerData(self, section, orientation, role): if section < self.columnCount(): return self.column_labels[section] - super(HierarchyModel, self).headerData(section, orientation, role) + return super(HierarchyModel, self).headerData( + section, orientation, role + ) def flags(self, index): item = index.internalPointer() @@ -283,7 +286,10 @@ def flags(self, index): key = self.columns[column] return item.flags(key) - def parent(self, index): + def parent(self, index=None): + if not index.isValid(): + return QtCore.QModelIndex() + item = index.internalPointer() parent_item = item.parent() From 72508c738f151a251f5b9816c8d1dd439d045675 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:26:43 +0200 Subject: [PATCH 083/219] added validations on add item which may not be successfull --- openpype/tools/project_manager/project_manager/model.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 750cb52661a..05e7b1831bc 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -342,8 +342,8 @@ def add_new_asset(self, source_index): new_child = AssetItem(asset_data) result = self.add_item(new_child, parent, new_row) - - self._validate_asset_duplicity(name) + if result is not None: + self._validate_asset_duplicity(name) return result @@ -398,7 +398,10 @@ def add_items(self, items, parent=None, start_row=None): return indexes def add_item(self, item, parent=None, row=None): - return self.add_items([item], parent, row)[0] + result = self.add_items([item], parent, row) + if result: + return result[0] + return None def remove_index(self, index): if not index.isValid(): From 0a764aca055c22cfb176d4b3881e1cd1b7585427 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:27:22 +0200 Subject: [PATCH 084/219] items have is_new properties --- .../tools/project_manager/project_manager/model.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 05e7b1831bc..ae47fc3168a 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -922,6 +922,10 @@ def setData(self, key, value, role): def id(self): return self._id + @property + def is_new(self): + return False + def rowCount(self): return len(self._children) @@ -1142,6 +1146,10 @@ def project_id(self): def asset_id(self): return self.mongo_id + @property + def is_new(self): + return self.asset_id is None + @property def name(self): return self._data["name"] @@ -1293,6 +1301,10 @@ def __init__(self, data=None): data = {} super(TaskItem, self).__init__(data) + @property + def is_new(self): + return self._origin_data is None + @classmethod def name_icon(cls): if cls._name_icon is None: From 146a99bd8658849bc8e0bdb81655ce15b701fda0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:29:05 +0200 Subject: [PATCH 085/219] items can be tagged as removed --- .../project_manager/project_manager/model.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index ae47fc3168a..c060a490379 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1130,6 +1130,7 @@ def __init__(self, asset_doc): self.mongo_id = asset_doc.get("_id") self._project_id = None self._hierarchy_changes_enabled = True + self._removed = False self._origin_asset_doc = copy.deepcopy(asset_doc) @@ -1256,6 +1257,9 @@ def _global_data(self, role): if role == HIERARCHY_CHANGE_ABLE_ROLE: return self._hierarchy_changes_enabled + if role == REMOVED_ROLE: + return self._removed + if role == QtCore.Qt.ToolTipRole and self._is_duplicated: return "Asset with name \"{}\" already exists.".format( self._data["name"] @@ -1263,6 +1267,10 @@ def _global_data(self, role): return super(AssetItem, self)._global_data(role) def setData(self, key, value, role): + if role == REMOVED_ROLE: + self._removed = value + return True + if role == HIERARCHY_CHANGE_ABLE_ROLE: if self._hierarchy_changes_enabled == value: return False @@ -1297,6 +1305,7 @@ class TaskItem(BaseItem): } def __init__(self, data=None): + self._removed = False if data is None: data = {} super(TaskItem, self).__init__(data) @@ -1315,6 +1324,9 @@ def add_child(self, item, row=None): raise AssertionError("BUG: Can't add children to Task") def _global_data(self, role): + if role == REMOVED_ROLE: + return self._removed + if role == QtCore.Qt.ToolTipRole and self._is_duplicated: return "Duplicated Task name \"{}\".".format( self._data["name"] @@ -1336,3 +1348,9 @@ def data(self, key, role): ): return self._data[key] or self._data["type"] or "< Select Type >" return super(TaskItem, self).data(key, role) + + def setData(self, key, value, role): + if role == REMOVED_ROLE: + self._removed = value + return True + return super(TaskItem, self).setData(key, value, role) From 656c0f87c910ea77d2e7671d563540d57488d500 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:29:25 +0200 Subject: [PATCH 086/219] set red background on removed items --- openpype/tools/project_manager/project_manager/model.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index c060a490379..d8dfe5287e3 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1266,6 +1266,12 @@ def _global_data(self, role): ) return super(AssetItem, self)._global_data(role) + def data(self, key, role): + if self._removed and role == QtCore.Qt.BackgroundRole: + return QtGui.QColor(255, 0, 0, 127) + + return super(AssetItem, self).data(key, role) + def setData(self, key, value, role): if role == REMOVED_ROLE: self._removed = value @@ -1342,6 +1348,9 @@ def to_doc_data(self): } def data(self, key, role): + if self._removed and role == QtCore.Qt.BackgroundRole: + return QtGui.QColor(255, 0, 0, 127) + if ( role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole) and key == "name" From 15c5361733b780dd420ed17900bcff2c907ec0aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:30:04 +0200 Subject: [PATCH 087/219] use is_new attribute --- openpype/tools/project_manager/project_manager/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index d8dfe5287e3..aa429d9fbe5 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -804,7 +804,7 @@ def save(self): to_process.put(item) - if item.asset_id is None: + if item.is_new: insert_list.append(item) continue From 7d225a2ab461b5b5bbd7cfce911bdc96df255384 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:33:31 +0200 Subject: [PATCH 088/219] added default value for removed role --- openpype/tools/project_manager/project_manager/model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index aa429d9fbe5..0c999974f69 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -875,6 +875,9 @@ def _global_data(self, role): if role == DUPLICATED_ROLE: return self._is_duplicated + if role == REMOVED_ROLE: + return False + return self._None def data(self, key, role): From 84887088f908d05e94c78a0fe1d11e9740ecd2b9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:33:48 +0200 Subject: [PATCH 089/219] don't allow adding items under removed items --- openpype/tools/project_manager/project_manager/model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 0c999974f69..ea3fd854961 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -366,6 +366,9 @@ def add_items(self, items, parent=None, start_row=None): if parent is None: parent = self._root_item + if parent.data(None, REMOVED_ROLE): + return [] + if start_row is None: start_row = parent.rowCount() From c84e37c4bb7c009bd22c9876a5dc9791ba153d31 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:34:05 +0200 Subject: [PATCH 090/219] store origin data of task item --- openpype/tools/project_manager/project_manager/model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index ea3fd854961..20cce4fd24d 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1317,6 +1317,7 @@ class TaskItem(BaseItem): } def __init__(self, data=None): + self._origin_data = copy.deepcopy(data) self._removed = False if data is None: data = {} From 335c1073dffd6374434abfeacfaef73fab705d84 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:34:27 +0200 Subject: [PATCH 091/219] modified delete index method --- .../project_manager/project_manager/model.py | 94 +++++++++++++++---- 1 file changed, 75 insertions(+), 19 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 20cce4fd24d..cdc46b43839 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -410,27 +410,38 @@ def remove_index(self, index): if not index.isValid(): return - item_id = index.data(IDENTIFIER_ROLE) - item = self._items_by_id[item_id] + item = index.internalPointer() + if isinstance(item, (RootItem, ProjectItem)): return parent = item.parent() + all_descendants = collections.defaultdict(dict) all_descendants[parent.id][item.id] = item - row = item.row() - self.beginRemoveRows(self.index_for_item(parent), row, row) + def _fill_children(_all_descendants, cur_item, parent_item=None): + if parent_item is not None: + _all_descendants[parent_item.id][cur_item.id] = cur_item - todo_queue = Queue() - todo_queue.put(item) - while not todo_queue.empty(): - _item = todo_queue.get() - for row in range(_item.rowCount()): - child_item = _item.child(row) - all_descendants[_item.id][child_item.id] = child_item - todo_queue.put(child_item) + if isinstance(cur_item, TaskItem): + task_removed = True + cur_item.setData(None, task_removed, REMOVED_ROLE) + return task_removed + remove_item = cur_item.data(None, HIERARCHY_CHANGE_ABLE_ROLE) + for row in range(cur_item.rowCount()): + child_item = cur_item.child(row) + if not _fill_children(_all_descendants, child_item, cur_item): + remove_item = False + + if remove_item: + cur_item.setData(None, True, REMOVED_ROLE) + return remove_item + + _fill_children(all_descendants, item) + + modified_children = [] while all_descendants: for parent_id in tuple(all_descendants.keys()): children = all_descendants[parent_id] @@ -438,18 +449,63 @@ def remove_index(self, index): all_descendants.pop(parent_id) continue + parent_children = {} + all_without_children = True for child_id in tuple(children.keys()): - child_item = children[child_id] if child_id in all_descendants: + all_without_children = False + break + parent_children[child_id] = children[child_id] + + if not all_without_children: + continue + + parent_item = self._items_by_id[parent_id] + row_ranges = [] + start_row = end_row = None + chilren_by_row = {} + for row in range(parent_item.rowCount()): + child_item = parent_item.child(row) + child_id = child_item.id + if child_id not in children: + continue + + chilren_by_row[row] = child_item + children.pop(child_item.id) + + remove_item = child_item.data(None, REMOVED_ROLE) + if not remove_item or not child_item.is_new: + modified_children.append(child_item) + if end_row is not None: + row_ranges.append((start_row, end_row)) + start_row = end_row = None continue - if isinstance(child_item, AssetItem): - self._rename_asset(child_item, None) - children.pop(child_id) - child_item.set_parent(None) - self._items_by_id.pop(child_id) + end_row = row + if start_row is None: + start_row = row + + if end_row is not None: + row_ranges.append((start_row, end_row)) + + parent_index = None + for start, end in row_ranges: + if parent_index is None: + parent_index = self.index_for_item(parent_item) + + self.beginRemoveRows(parent_index, start, end) + + for idx in range(start, end + 1): + child_item = chilren_by_row[idx] + child_item.set_parent(None) + self._items_by_id.pop(child_item.id) + + self.endRemoveRows() - self.endRemoveRows() + for item in modified_children: + s_index = self.index_for_item(item) + e_index = self.index_for_item(item, column=self.columns_len - 1) + self.dataChanged.emit(s_index, e_index, [QtCore.Qt.BackgroundRole]) def _rename_asset(self, asset_item, new_name): if not isinstance(asset_item, AssetItem): From a000c51dd4a882b3a6e44b7c277c98d78a688693 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:36:44 +0200 Subject: [PATCH 092/219] force name validation on remove --- openpype/tools/project_manager/project_manager/model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index cdc46b43839..986bc7f38ad 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -497,6 +497,9 @@ def _fill_children(_all_descendants, cur_item, parent_item=None): for idx in range(start, end + 1): child_item = chilren_by_row[idx] + # Force name validation + if isinstance(child_item, AssetItem): + self._rename_asset(child_item, None) child_item.set_parent(None) self._items_by_id.pop(child_item.id) From b27116102bd609eab54607f0ab96f3d2381b4830 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 May 2021 22:38:28 +0200 Subject: [PATCH 093/219] added bg color for new items --- .../project_manager/project_manager/model.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 986bc7f38ad..baea086d874 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1332,8 +1332,11 @@ def _global_data(self, role): return super(AssetItem, self)._global_data(role) def data(self, key, role): - if self._removed and role == QtCore.Qt.BackgroundRole: - return QtGui.QColor(255, 0, 0, 127) + if role == QtCore.Qt.BackgroundRole: + if self._removed: + return QtGui.QColor(255, 0, 0, 127) + elif self.is_new: + return QtGui.QColor(0, 255, 0, 127) return super(AssetItem, self).data(key, role) @@ -1414,8 +1417,12 @@ def to_doc_data(self): } def data(self, key, role): - if self._removed and role == QtCore.Qt.BackgroundRole: - return QtGui.QColor(255, 0, 0, 127) + if role == QtCore.Qt.BackgroundRole: + if self._removed: + return QtGui.QColor(255, 0, 0, 127) + + elif self.is_new: + return QtGui.QColor(0, 255, 0, 127) if ( role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole) From d76fae0096aeada27b2eb46dec21b1cf1376780e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 09:29:33 +0200 Subject: [PATCH 094/219] added filtrable combobox widget --- .../project_manager/widgets.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 openpype/tools/project_manager/project_manager/widgets.py diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py new file mode 100644 index 00000000000..03f43fa4898 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -0,0 +1,46 @@ +from Qt import QtWidgets, QtCore + + +class FilterComboBox(QtWidgets.QComboBox): + def __init__(self, parent=None): + super(FilterComboBox, self).__init__(parent) + + self.setFocusPolicy(QtCore.Qt.StrongFocus) + self.setEditable(True) + + filter_proxy_model = QtCore.QSortFilterProxyModel(self) + filter_proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) + filter_proxy_model.setSourceModel(self.model()) + + completer = QtWidgets.QCompleter(filter_proxy_model, self) + completer.setCompletionMode( + QtWidgets.QCompleter.UnfilteredPopupCompletion + ) + self.setCompleter(completer) + + self.lineEdit().textEdited.connect( + filter_proxy_model.setFilterFixedString + ) + completer.activated.connect(self.on_completer_activated) + + self._completer = completer + self._filter_proxy_model = filter_proxy_model + + def focusInEvent(self, event): + super(FilterComboBox, self).focusInEvent(event) + self.lineEdit().selectAll() + + def on_completer_activated(self, text): + if text: + index = self.findText(text) + self.setCurrentIndex(index) + + def setModel(self, model): + super(FilterComboBox, self).setModel(model) + self._filter_proxy_model.setSourceModel(model) + self._completer.setModel(self._filter_proxy_model) + + def setModelColumn(self, column): + self._completer.setCompletionColumn(column) + self._filter_proxy_model.setFilterKeyColumn(column) + super(FilterComboBox, self).setModelColumn(column) From 342fd59eb28a34e0c75a4d87fd6c1da00c8f4964 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 09:30:29 +0200 Subject: [PATCH 095/219] use filter combobox for type editor --- .../tools/project_manager/project_manager/delegates.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index 88b366a7d0a..c00cd379568 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -1,5 +1,6 @@ from Qt import QtWidgets, QtCore +from .widgets import FilterComboBox from .multiselection_combobox import MultiSelectionComboBox @@ -50,7 +51,7 @@ def __init__(self, project_doc_cache, *args, **kwargs): super(TypeDelegate, self).__init__(*args, **kwargs) def createEditor(self, parent, option, index): - editor = QtWidgets.QComboBox(parent) + editor = FilterComboBox(parent) if not self._project_doc_cache.project_doc: return editor @@ -59,6 +60,12 @@ def createEditor(self, parent, option, index): return editor + def setEditorData(self, editor, index): + value = index.data(QtCore.Qt.EditRole) + index = editor.findText(value) + if index >= 0: + editor.setCurrentIndex(index) + class ToolsDelegate(QtWidgets.QStyledItemDelegate): def __init__(self, tools_cache, *args, **kwargs): From 6b8002f4a0d354b01d6c6459c9e9ff3bb973a38a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 09:45:12 +0200 Subject: [PATCH 096/219] Text input may have set text regex --- .../project_manager/project_manager/delegates.py | 14 ++++++++++++-- .../tools/project_manager/project_manager/view.py | 2 +- .../project_manager/project_manager/widgets.py | 11 +++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index c00cd379568..bfd6cfaabb7 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -1,6 +1,9 @@ from Qt import QtWidgets, QtCore -from .widgets import FilterComboBox +from .widgets import ( + RegexTextEdit, + FilterComboBox +) from .multiselection_combobox import MultiSelectionComboBox @@ -37,8 +40,15 @@ def createEditor(self, parent, option, index): class StringDelegate(QtWidgets.QStyledItemDelegate): + def __init__(self, regex, *args, **kwargs): + super(StringDelegate, self).__init__(*args, **kwargs) + self._regex = regex + def createEditor(self, parent, option, index): - editor = QtWidgets.QLineEdit(parent) + if self._regex: + editor = RegexTextEdit(self._regex, parent) + else: + editor = QtWidgets.QLineEdit(parent) value = index.data(QtCore.Qt.EditRole) if value is not None: editor.setText(str(value)) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index dee694a59c6..be7513de25f 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -108,7 +108,7 @@ def __init__(self, dbcon, source_model, *args, **kwargs): column_key_to_index = {} for key, item_type in self.column_delegate_defs.items(): if isinstance(item_type, StringDef): - delegate = StringDelegate() + delegate = StringDelegate(item_type.regex) elif isinstance(item_type, NumberDef): delegate = NumberDelegate( diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index 03f43fa4898..9ef5dfaf85d 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -1,6 +1,17 @@ from Qt import QtWidgets, QtCore +class RegexTextEdit(QtWidgets.QLineEdit): + def __init__(self, regex, *args, **kwargs): + super(RegexTextEdit, self).__init__(*args, **kwargs) + self._regex = regex + + self.textChanged.connect(self._on_text_change) + + def _on_text_change(self, text): + print(text) + + class FilterComboBox(QtWidgets.QComboBox): def __init__(self, parent=None): super(FilterComboBox, self).__init__(parent) From 586f8faa1047b176009c2c4b2f29b012f2a73de3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 10:08:14 +0200 Subject: [PATCH 097/219] changed string delegate to name delegate --- .../project_manager/project_manager/constants.py | 4 ++++ .../project_manager/project_manager/delegates.py | 13 +++---------- .../tools/project_manager/project_manager/view.py | 13 ++++++------- .../project_manager/project_manager/widgets.py | 12 ++++++++---- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/constants.py b/openpype/tools/project_manager/project_manager/constants.py index 7f9a859ac1f..76d3d3cda1a 100644 --- a/openpype/tools/project_manager/project_manager/constants.py +++ b/openpype/tools/project_manager/project_manager/constants.py @@ -1,3 +1,4 @@ +import re from Qt import QtCore @@ -5,3 +6,6 @@ DUPLICATED_ROLE = QtCore.Qt.UserRole + 2 HIERARCHY_CHANGE_ABLE_ROLE = QtCore.Qt.UserRole + 3 REMOVED_ROLE = QtCore.Qt.UserRole + 4 + +NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_" +NAME_REGEX = re.compile("^[" + NAME_ALLOWED_SYMBOLS + "]*$") diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index bfd6cfaabb7..18e3e2d81bf 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -1,7 +1,7 @@ from Qt import QtWidgets, QtCore from .widgets import ( - RegexTextEdit, + NameTextEdit, FilterComboBox ) from .multiselection_combobox import MultiSelectionComboBox @@ -39,16 +39,9 @@ def createEditor(self, parent, option, index): # return super().updateEditorGeometry(editor, options, index) -class StringDelegate(QtWidgets.QStyledItemDelegate): - def __init__(self, regex, *args, **kwargs): - super(StringDelegate, self).__init__(*args, **kwargs) - self._regex = regex - +class NameDelegate(QtWidgets.QStyledItemDelegate): def createEditor(self, parent, option, index): - if self._regex: - editor = RegexTextEdit(self._regex, parent) - else: - editor = QtWidgets.QLineEdit(parent) + editor = NameTextEdit(parent) value = index.data(QtCore.Qt.EditRole) if value is not None: editor.setText(str(value)) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index be7513de25f..b782d27d471 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -2,7 +2,7 @@ from .delegates import ( NumberDelegate, - StringDelegate, + NameDelegate, TypeDelegate, ToolsDelegate ) @@ -10,9 +10,8 @@ from openpype.lib import ApplicationManager -class StringDef: - def __init__(self, regex=None): - self.regex = regex +class NameDef: + pass class NumberDef: @@ -63,7 +62,7 @@ def refresh(self): class HierarchyView(QtWidgets.QTreeView): """A tree view that deselects on clicking on an empty area in the view""" column_delegate_defs = { - "name": StringDef(), + "name": NameDef(), "type": TypeDef(), "frameStart": NumberDef(1), "frameEnd": NumberDef(1), @@ -107,8 +106,8 @@ def __init__(self, dbcon, source_model, *args, **kwargs): column_delegates = {} column_key_to_index = {} for key, item_type in self.column_delegate_defs.items(): - if isinstance(item_type, StringDef): - delegate = StringDelegate(item_type.regex) + if isinstance(item_type, NameDef): + delegate = NameDelegate() elif isinstance(item_type, NumberDef): delegate = NumberDelegate( diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index 9ef5dfaf85d..878033de27a 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -1,10 +1,14 @@ +import re +from .constants import ( + NAME_ALLOWED_SYMBOLS, + NAME_REGEX +) from Qt import QtWidgets, QtCore -class RegexTextEdit(QtWidgets.QLineEdit): - def __init__(self, regex, *args, **kwargs): - super(RegexTextEdit, self).__init__(*args, **kwargs) - self._regex = regex +class NameTextEdit(QtWidgets.QLineEdit): + def __init__(self, *args, **kwargs): + super(NameTextEdit, self).__init__(*args, **kwargs) self.textChanged.connect(self._on_text_change) From 6daec25886aa7fe6585ff538eb6fb3e1e4915af2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 10:08:29 +0200 Subject: [PATCH 098/219] skip not allowed symbols and keep position --- .../project_manager/project_manager/widgets.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index 878033de27a..1da4da3c243 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -13,7 +13,19 @@ def __init__(self, *args, **kwargs): self.textChanged.connect(self._on_text_change) def _on_text_change(self, text): - print(text) + if NAME_REGEX.match(text): + return + + idx = self.cursorPosition() + before_text = text[0:idx] + after_text = text[idx:len(text)] + sub_regex = "[^{}]+".format(NAME_ALLOWED_SYMBOLS) + new_before_text = re.sub(sub_regex, "", before_text) + new_after_text = re.sub(sub_regex, "", after_text) + idx -= (len(before_text) - len(new_before_text)) + + self.setText(new_before_text + new_after_text) + self.setCursorPosition(idx) class FilterComboBox(QtWidgets.QComboBox): From fb09eb8ef75ffa454a09cb0a5f600069a51ac46b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 10:35:09 +0200 Subject: [PATCH 099/219] AssetItem cares about children task duplications --- .../project_manager/project_manager/model.py | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index baea086d874..2ed9f04e4fd 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1197,6 +1197,9 @@ def __init__(self, asset_doc): self._hierarchy_changes_enabled = True self._removed = False + self._task_items_by_name = collections.defaultdict(list) + self._task_name_by_item_id = {} + self._origin_asset_doc = copy.deepcopy(asset_doc) data = self.data_from_doc(asset_doc) @@ -1367,6 +1370,76 @@ def flags(self, key): return flags return super(AssetItem, self).flags(key) + def _add_task(self, item): + name = item.data("name", QtCore.Qt.DisplayRole).lower() + item_id = item.data(None, IDENTIFIER_ROLE) + + self._task_name_by_item_id[item_id] = name + self._task_items_by_name[name].append(item) + if len(self._task_items_by_name[name]) > 1: + for _item in self._task_items_by_name[name]: + _item.setData(None, True, DUPLICATED_ROLE) + + def _remove_task(self, item): + item_id = item.data(None, IDENTIFIER_ROLE) + name = self._task_name_by_item_id[item_id] + + self._task_name_by_item_id.pop(item_id) + self._task_items_by_name[name].append(item) + if not self._task_items_by_name[name]: + self._task_items_by_name.pop(name) + + elif len(self._task_items_by_name[name]) == 1: + for _item in self._task_items_by_name[name]: + _item.setData(None, False, DUPLICATED_ROLE) + + def _rename_task(self, item): + new_name = item.data("name", QtCore.Qt.DisplayRole).lower() + item_id = item.data(None, IDENTIFIER_ROLE) + prev_name = self._task_name_by_item_id[item_id] + if new_name == prev_name: + return + + # Remove from previous name mapping + self._task_items_by_name[prev_name].remove(item) + if not self._task_items_by_name[prev_name]: + self._task_items_by_name.pop(prev_name) + + elif len(self._task_items_by_name[prev_name]) == 1: + for _item in self._task_items_by_name[prev_name]: + _item.setData(None, False, DUPLICATED_ROLE) + + # Add to new name mapping + self._task_items_by_name[new_name].append(item) + if len(self._task_items_by_name[new_name]) > 1: + for _item in self._task_items_by_name[new_name]: + _item.setData(None, True, DUPLICATED_ROLE) + else: + item.setData(None, False, DUPLICATED_ROLE) + + self._task_name_by_item_id[item_id] = new_name + + def on_task_name_change(self, task_item): + self._rename_task(task_item) + + def add_child(self, item, row=None): + if item in self._children: + return + + super(AssetItem, self).add_child(item, row) + + if isinstance(item, TaskItem): + self._add_task(item) + + def remove_child(self, item): + if item not in self._children: + return + + if isinstance(item, TaskItem): + self._remove_task(item) + + super(AssetItem).remove_child(item) + class TaskItem(BaseItem): columns = { @@ -1435,4 +1508,10 @@ def setData(self, key, value, role): if role == REMOVED_ROLE: self._removed = value return True - return super(TaskItem, self).setData(key, value, role) + + result = super(TaskItem, self).setData(key, value, role) + + if key == "name": + self.parent().on_task_name_change(self) + + return result From 94dedde1dc865ca711d1f9a95902ce04be2c4bb8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 10:41:06 +0200 Subject: [PATCH 100/219] check for type changes if name is not set --- openpype/tools/project_manager/project_manager/model.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 2ed9f04e4fd..e26d213297d 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1511,7 +1511,10 @@ def setData(self, key, value, role): result = super(TaskItem, self).setData(key, value, role) - if key == "name": + if ( + key == "name" + or (key == "type" and self._data["name"] is None) + ): self.parent().on_task_name_change(self) return result From 964f7b2b290e6876559921726235f4b2dc685624 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 10:41:21 +0200 Subject: [PATCH 101/219] store duplicated task names --- openpype/tools/project_manager/project_manager/model.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index e26d213297d..8f7e86dbb06 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1194,12 +1194,17 @@ def __init__(self, asset_doc): asset_doc = {} self.mongo_id = asset_doc.get("_id") self._project_id = None + + # Item data self._hierarchy_changes_enabled = True self._removed = False + # Task children duplication variables self._task_items_by_name = collections.defaultdict(list) self._task_name_by_item_id = {} + self._duplicated_task_names = set() + # Copy of original document self._origin_asset_doc = copy.deepcopy(asset_doc) data = self.data_from_doc(asset_doc) @@ -1377,6 +1382,7 @@ def _add_task(self, item): self._task_name_by_item_id[item_id] = name self._task_items_by_name[name].append(item) if len(self._task_items_by_name[name]) > 1: + self._duplicated_task_names.add(name) for _item in self._task_items_by_name[name]: _item.setData(None, True, DUPLICATED_ROLE) @@ -1390,6 +1396,7 @@ def _remove_task(self, item): self._task_items_by_name.pop(name) elif len(self._task_items_by_name[name]) == 1: + self._duplicated_task_names.remove(name) for _item in self._task_items_by_name[name]: _item.setData(None, False, DUPLICATED_ROLE) @@ -1406,12 +1413,14 @@ def _rename_task(self, item): self._task_items_by_name.pop(prev_name) elif len(self._task_items_by_name[prev_name]) == 1: + self._duplicated_task_names.remove(prev_name) for _item in self._task_items_by_name[prev_name]: _item.setData(None, False, DUPLICATED_ROLE) # Add to new name mapping self._task_items_by_name[new_name].append(item) if len(self._task_items_by_name[new_name]) > 1: + self._duplicated_task_names.add(new_name) for _item in self._task_items_by_name[new_name]: _item.setData(None, True, DUPLICATED_ROLE) else: From ad50d5b38d09b73db6edda66ceb8112a67d85486 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 10:46:58 +0200 Subject: [PATCH 102/219] added base of item validations --- .../tools/project_manager/project_manager/model.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 8f7e86dbb06..e6df0ea5ab2 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -843,6 +843,15 @@ def clear(self): self.endResetModel() def save(self): + all_valid = True + for item in self._items_by_id.values(): + if not item.is_valid: + all_valid = False + break + + if not all_valid: + return + project_item = None for _project_item in self._root_item.children(): project_item = _project_item @@ -919,6 +928,10 @@ def __init__(self, data=None): def name_icon(cls): return cls._name_icon + @property + def is_valid(self): + return not self._is_duplicated + def model(self): return self._parent.model From 5fa0bb524aa2766961b8705b71a63b887a7dc8b5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 10:47:12 +0200 Subject: [PATCH 103/219] use data method to get asset values --- openpype/tools/project_manager/project_manager/model.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index e6df0ea5ab2..bf1f0eda499 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1262,8 +1262,8 @@ def to_doc(self): ) doc = { - "name": self._data["name"], - "type": self._data["type"], + "name": self.data("name", QtCore.Qt.DisplayRole), + "type": self.data("type", QtCore.Qt.DisplayRole), "schema": schema_name, "data": doc_data, "parent": self.project_id @@ -1271,10 +1271,11 @@ def to_doc(self): if self.mongo_id: doc["_id"] = self.mongo_id - for key, value in self._data.items(): + for key in self._data.keys(): if key in doc: continue - doc_data[key] = value + # Use `data` method to get inherited values + doc_data[key] = self.data(key, QtCore.Qt.DisplayRole) return doc From c26c141343685ec8815d01df03247fa19664e637 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 10:49:31 +0200 Subject: [PATCH 104/219] fix super call --- openpype/tools/project_manager/project_manager/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index bf1f0eda499..86c7f79aaba 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1461,7 +1461,7 @@ def remove_child(self, item): if isinstance(item, TaskItem): self._remove_task(item) - super(AssetItem).remove_child(item) + super(AssetItem, self).remove_child(item) class TaskItem(BaseItem): From 53027f6916392ca851bf730397d99443cdf8ec14 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 11:01:08 +0200 Subject: [PATCH 105/219] store origin data of asset and task --- openpype/tools/project_manager/project_manager/model.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 86c7f79aaba..9c8021f9740 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1221,6 +1221,9 @@ def __init__(self, asset_doc): self._origin_asset_doc = copy.deepcopy(asset_doc) data = self.data_from_doc(asset_doc) + + self._origin_data = copy.deepcopy(data) + super(AssetItem, self).__init__(data) @property @@ -1475,10 +1478,11 @@ class TaskItem(BaseItem): } def __init__(self, data=None): - self._origin_data = copy.deepcopy(data) self._removed = False if data is None: data = {} + self._origin_data = copy.deepcopy(data) + super(TaskItem, self).__init__(data) @property From 2bc795197e07f53a15078435e888aa859695030b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 11:25:49 +0200 Subject: [PATCH 106/219] implemented delegate that can resize editor to required sizes --- .../project_manager/delegates.py | 63 +++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index 18e3e2d81bf..4292224b093 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -7,6 +7,65 @@ from .multiselection_combobox import MultiSelectionComboBox +class ResizeEditorDelegate(QtWidgets.QStyledItemDelegate): + @staticmethod + def _q_smart_min_size(editor): + min_size_hint = editor.minimumSizeHint() + size_policy = editor.sizePolicy() + width = 0 + height = 0 + if size_policy.horizontalPolicy() != QtWidgets.QSizePolicy.Ignored: + if ( + size_policy.horizontalPolicy() + & QtWidgets.QSizePolicy.ShrinkFlag + ): + width = min_size_hint.width() + else: + width = max( + editor.sizeHint().width(), + min_size_hint.width() + ) + + if size_policy.verticalPolicy() != QtWidgets.QSizePolicy.Ignored: + if size_policy.verticalPolicy() & QtWidgets.QSizePolicy.ShrinkFlag: + height = min_size_hint.height() + else: + height = max( + editor.sizeHint().height(), + min_size_hint.height() + ) + + output = QtCore.QSize(width, height).boundedTo(editor.maximumSize()) + min_size = editor.minimumSize() + if min_size.width() > 0: + output.setWidth(min_size.width()) + if min_size.height() > 0: + output.setHeight(min_size.height()) + + return output.expandedTo(QtCore.QSize(0, 0)) + + def updateEditorGeometry(self, editor, option, index): + self.initStyleOption(option, index) + + option.showDecorationSelected = editor.style().styleHint( + QtWidgets.QStyle.SH_ItemView_ShowDecorationSelected, None, editor + ) + + widget = option.widget + + style = widget.style() if widget else QtWidgets.QApplication.style() + geo = style.subElementRect( + QtWidgets.QStyle.SE_ItemViewItemText, option, widget + ) + delta = self._q_smart_min_size(editor).width() - geo.width() + if delta > 0: + if editor.layoutDirection() == QtCore.Qt.RightToLeft: + geo.adjust(-delta, 0, 0, 0) + else: + geo.adjust(0, 0, delta, 0) + editor.setGeometry(geo) + + class NumberDelegate(QtWidgets.QStyledItemDelegate): def __init__(self, minimum, maximum, decimals, *args, **kwargs): super(NumberDelegate, self).__init__(*args, **kwargs) @@ -34,10 +93,6 @@ def createEditor(self, parent, option, index): return editor - # def updateEditorGeometry(self, editor, options, index): - # print(editor) - # return super().updateEditorGeometry(editor, options, index) - class NameDelegate(QtWidgets.QStyledItemDelegate): def createEditor(self, parent, option, index): From 1c9c6164a62d6bd9ec9c2b6558ca1ac63a49ba3e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 11:38:44 +0200 Subject: [PATCH 107/219] fix TaskItem new item detection --- openpype/tools/project_manager/project_manager/model.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 9c8021f9740..cf5f3cf9f19 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1479,15 +1479,16 @@ class TaskItem(BaseItem): def __init__(self, data=None): self._removed = False + self._is_new = data is None if data is None: data = {} - self._origin_data = copy.deepcopy(data) + self._origin_data = copy.deepcopy(data) super(TaskItem, self).__init__(data) @property def is_new(self): - return self._origin_data is None + return self._is_new @classmethod def name_icon(cls): From 675aac19322b345094a2864f18c568c96d7b9707 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 12:15:52 +0200 Subject: [PATCH 108/219] model has defined multiselection columns --- .../project_manager/project_manager/model.py | 26 ++++++++++++++++++- .../project_manager/project_manager/view.py | 4 +-- .../project_manager/project_manager/window.py | 4 ++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index cf5f3cf9f19..f34b9eb39ca 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -52,8 +52,12 @@ def refresh(self): class HierarchySelectionModel(QtCore.QItemSelectionModel): + def __init__(self, multiselection_columns, *args, **kwargs): + super(HierarchySelectionModel, self).__init__(*args, **kwargs) + self.multiselection_columns = multiselection_columns + def setCurrentIndex(self, index, command): - if index.column() > 0: + if index.column() in self.multiselection_columns: if ( command & QtCore.QItemSelectionModel.Clear and command & QtCore.QItemSelectionModel.Select @@ -78,6 +82,19 @@ class HierarchyModel(QtCore.QAbstractItemModel): ("pixelAspect", "Pixel aspect"), ("tools_env", "Tools") ] + multiselection_columns = { + "frameStart", + "frameEnd", + "fps", + "resolutionWidth", + "resolutionHeight", + "handleStart", + "handleEnd", + "clipIn", + "clipOut", + "pixelAspect", + "tools_env" + } columns = [ item[0] for item in _columns_def @@ -87,10 +104,17 @@ class HierarchyModel(QtCore.QAbstractItemModel): idx: item[1] for idx, item in enumerate(_columns_def) } + index_moved = QtCore.Signal(QtCore.QModelIndex) def __init__(self, dbcon, parent=None): super(HierarchyModel, self).__init__(parent) + + self.multiselection_column_indexes = { + self.columns.index(key) + for key in self.multiselection_columns + } + # TODO Reset them on project change self._current_project = None self._root_item = None diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index b782d27d471..04c0e6b08b3 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -76,7 +76,7 @@ class HierarchyView(QtWidgets.QTreeView): "pixelAspect": NumberDef(0, decimals=2), "tools_env": ToolsDef() } - persistent_columns = [ + persistent_columns = { "type", "frameStart", "frameEnd", @@ -89,7 +89,7 @@ class HierarchyView(QtWidgets.QTreeView): "clipOut", "pixelAspect", "tools_env" - ] + } def __init__(self, dbcon, source_model, *args, **kwargs): super(HierarchyView, self).__init__(*args, **kwargs) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 0be69c29c83..f2ad399ab5f 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -39,7 +39,9 @@ def __init__(self, parent=None): hierarchy_view = HierarchyView(dbcon, hierarchy_model, self) hierarchy_view.setModel(hierarchy_model) - _selection_model = HierarchySelectionModel() + _selection_model = HierarchySelectionModel( + hierarchy_model.multiselection_column_indexes + ) _selection_model.setModel(hierarchy_view.model()) hierarchy_view.setSelectionModel(_selection_model) From db10512a305f66b61e97c87b6047b920601d56b5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 12:16:14 +0200 Subject: [PATCH 109/219] clear selection on adding new item --- .../tools/project_manager/project_manager/view.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 04c0e6b08b3..4d3557eb9e5 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -280,7 +280,11 @@ def _add_task(self, parent_index=None): new_index.row(), 1, new_index.parent() ) # Change current index - self.setCurrentIndex(task_type_index) + self.selectionModel().setCurrentIndex( + task_type_index, + QtCore.QItemSelectionModel.Clear + | QtCore.QItemSelectionModel.Select + ) # Start editing self.edit(task_type_index) @@ -298,7 +302,11 @@ def _on_shift_enter_pressed(self): return # Change current index - self.setCurrentIndex(new_index) + self.selectionModel().setCurrentIndex( + new_index, + QtCore.QItemSelectionModel.Clear + | QtCore.QItemSelectionModel.Select + ) # Start editing self.edit(new_index) From 8b30c7a5edcc170c469704b7fb9f3118acaca1d7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 May 2021 12:16:23 +0200 Subject: [PATCH 110/219] added `_add_asset` method --- openpype/tools/project_manager/project_manager/view.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 4d3557eb9e5..1db8a12c230 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -289,7 +289,12 @@ def _add_task(self, parent_index=None): self.edit(task_type_index) def _on_shift_enter_pressed(self): - index = self.currentIndex() + self._add_asset() + + def _add_asset(self, index=None): + if index is None: + index = self.currentIndex() + if not index.isValid(): return From 54e7a479c3096f73010332b1cc2f57874686382e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 13:06:02 +0200 Subject: [PATCH 111/219] dont remove tasks if it's parent was not removed and task is not selected --- openpype/tools/project_manager/project_manager/model.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index f34b9eb39ca..03e28717afd 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -449,8 +449,12 @@ def _fill_children(_all_descendants, cur_item, parent_item=None): _all_descendants[parent_item.id][cur_item.id] = cur_item if isinstance(cur_item, TaskItem): + was_removed = cur_item.data(None, REMOVED_ROLE) task_removed = True - cur_item.setData(None, task_removed, REMOVED_ROLE) + if not was_removed and parent_item is not None: + task_removed = parent_item.data(None, REMOVED_ROLE) + if not was_removed: + cur_item.setData(None, task_removed, REMOVED_ROLE) return task_removed remove_item = cur_item.data(None, HIERARCHY_CHANGE_ABLE_ROLE) From e31a2687c88801dbd1db85d384700e0357df3494 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 13:08:01 +0200 Subject: [PATCH 112/219] added method remove multiple indexes at once --- .../project_manager/project_manager/model.py | 108 +----------------- 1 file changed, 3 insertions(+), 105 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 03e28717afd..c0a96058e3d 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -431,112 +431,10 @@ def add_item(self, item, parent=None, row=None): return None def remove_index(self, index): - if not index.isValid(): - return - - item = index.internalPointer() - - if isinstance(item, (RootItem, ProjectItem)): - return + return self.remove_indexes([index]) - parent = item.parent() - - all_descendants = collections.defaultdict(dict) - all_descendants[parent.id][item.id] = item - - def _fill_children(_all_descendants, cur_item, parent_item=None): - if parent_item is not None: - _all_descendants[parent_item.id][cur_item.id] = cur_item - - if isinstance(cur_item, TaskItem): - was_removed = cur_item.data(None, REMOVED_ROLE) - task_removed = True - if not was_removed and parent_item is not None: - task_removed = parent_item.data(None, REMOVED_ROLE) - if not was_removed: - cur_item.setData(None, task_removed, REMOVED_ROLE) - return task_removed - - remove_item = cur_item.data(None, HIERARCHY_CHANGE_ABLE_ROLE) - for row in range(cur_item.rowCount()): - child_item = cur_item.child(row) - if not _fill_children(_all_descendants, child_item, cur_item): - remove_item = False - - if remove_item: - cur_item.setData(None, True, REMOVED_ROLE) - return remove_item - - _fill_children(all_descendants, item) - - modified_children = [] - while all_descendants: - for parent_id in tuple(all_descendants.keys()): - children = all_descendants[parent_id] - if not children: - all_descendants.pop(parent_id) - continue - - parent_children = {} - all_without_children = True - for child_id in tuple(children.keys()): - if child_id in all_descendants: - all_without_children = False - break - parent_children[child_id] = children[child_id] - - if not all_without_children: - continue - - parent_item = self._items_by_id[parent_id] - row_ranges = [] - start_row = end_row = None - chilren_by_row = {} - for row in range(parent_item.rowCount()): - child_item = parent_item.child(row) - child_id = child_item.id - if child_id not in children: - continue - - chilren_by_row[row] = child_item - children.pop(child_item.id) - - remove_item = child_item.data(None, REMOVED_ROLE) - if not remove_item or not child_item.is_new: - modified_children.append(child_item) - if end_row is not None: - row_ranges.append((start_row, end_row)) - start_row = end_row = None - continue - - end_row = row - if start_row is None: - start_row = row - - if end_row is not None: - row_ranges.append((start_row, end_row)) - - parent_index = None - for start, end in row_ranges: - if parent_index is None: - parent_index = self.index_for_item(parent_item) - - self.beginRemoveRows(parent_index, start, end) - - for idx in range(start, end + 1): - child_item = chilren_by_row[idx] - # Force name validation - if isinstance(child_item, AssetItem): - self._rename_asset(child_item, None) - child_item.set_parent(None) - self._items_by_id.pop(child_item.id) - - self.endRemoveRows() - - for item in modified_children: - s_index = self.index_for_item(item) - e_index = self.index_for_item(item, column=self.columns_len - 1) - self.dataChanged.emit(s_index, e_index, [QtCore.Qt.BackgroundRole]) + def remove_indexes(self, indexes): + items_by_id = {} def _rename_asset(self, asset_item, new_name): if not isinstance(asset_item, AssetItem): From 44cc3d40113d2110d0f274e9b2f1da61fe7db018 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 13:08:32 +0200 Subject: [PATCH 113/219] implemeted method that cares about removing single item --- .../project_manager/project_manager/model.py | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index c0a96058e3d..cb51323975a 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -436,6 +436,110 @@ def remove_index(self, index): def remove_indexes(self, indexes): items_by_id = {} + def _remove_item(self, item): + is_removed = item.data(None, REMOVED_ROLE) + if is_removed: + return + + parent = item.parent() + + all_descendants = collections.defaultdict(dict) + all_descendants[parent.id][item.id] = item + + def _fill_children(_all_descendants, cur_item, parent_item=None): + if parent_item is not None: + _all_descendants[parent_item.id][cur_item.id] = cur_item + + if isinstance(cur_item, TaskItem): + was_removed = cur_item.data(None, REMOVED_ROLE) + task_removed = True + if not was_removed and parent_item is not None: + task_removed = parent_item.data(None, REMOVED_ROLE) + if not was_removed: + cur_item.setData(None, task_removed, REMOVED_ROLE) + return task_removed + + remove_item = cur_item.data(None, HIERARCHY_CHANGE_ABLE_ROLE) + for row in range(cur_item.rowCount()): + child_item = cur_item.child(row) + if not _fill_children(_all_descendants, child_item, cur_item): + remove_item = False + + if remove_item: + cur_item.setData(None, True, REMOVED_ROLE) + return remove_item + + _fill_children(all_descendants, item) + + modified_children = [] + while all_descendants: + for parent_id in tuple(all_descendants.keys()): + children = all_descendants[parent_id] + if not children: + all_descendants.pop(parent_id) + continue + + parent_children = {} + all_without_children = True + for child_id in tuple(children.keys()): + if child_id in all_descendants: + all_without_children = False + break + parent_children[child_id] = children[child_id] + + if not all_without_children: + continue + + parent_item = self._items_by_id[parent_id] + row_ranges = [] + start_row = end_row = None + chilren_by_row = {} + for row in range(parent_item.rowCount()): + child_item = parent_item.child(row) + child_id = child_item.id + if child_id not in children: + continue + + chilren_by_row[row] = child_item + children.pop(child_item.id) + + remove_item = child_item.data(None, REMOVED_ROLE) + if not remove_item or not child_item.is_new: + modified_children.append(child_item) + if end_row is not None: + row_ranges.append((start_row, end_row)) + start_row = end_row = None + continue + + end_row = row + if start_row is None: + start_row = row + + if end_row is not None: + row_ranges.append((start_row, end_row)) + + parent_index = None + for start, end in row_ranges: + if parent_index is None: + parent_index = self.index_for_item(parent_item) + + self.beginRemoveRows(parent_index, start, end) + + for idx in range(start, end + 1): + child_item = chilren_by_row[idx] + # Force name validation + if isinstance(child_item, AssetItem): + self._rename_asset(child_item, None) + child_item.set_parent(None) + self._items_by_id.pop(child_item.id) + + self.endRemoveRows() + + for item in modified_children: + s_index = self.index_for_item(item) + e_index = self.index_for_item(item, column=self.columns_len - 1) + self.dataChanged.emit(s_index, e_index, [QtCore.Qt.BackgroundRole]) + def _rename_asset(self, asset_item, new_name): if not isinstance(asset_item, AssetItem): return From 188cf1e57a37d70d228f433d8a183132f35da271 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 13:10:45 +0200 Subject: [PATCH 114/219] remove indexes implemented remove_indexes content --- .../project_manager/project_manager/model.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index cb51323975a..0b3bf2fa9ec 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -435,6 +435,25 @@ def remove_index(self, index): def remove_indexes(self, indexes): items_by_id = {} + processed_ids = set() + for index in indexes: + if not index.isValid(): + continue + + item_id = index.data(IDENTIFIER_ROLE) + # There may be indexes for multiple columns + if item_id not in processed_ids: + processed_ids.add(item_id) + + item = self._items_by_id[item_id] + if isinstance(item, (TaskItem, AssetItem)): + items_by_id[item_id] = item + + if not items_by_id: + return + + for item in items_by_id.values(): + self._remove_item(item) def _remove_item(self, item): is_removed = item.data(None, REMOVED_ROLE) From c1a8d59ad125c7eff5517d934569a68e9785383e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 13:17:10 +0200 Subject: [PATCH 115/219] view send all selected indexes to model on remove --- openpype/tools/project_manager/project_manager/view.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 1db8a12c230..680601587b0 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -252,10 +252,10 @@ def keyPressEvent(self, event): else: event.accept() - def _delete_item(self, index=None): - if index is None: - index = self.currentIndex() - self._source_model.remove_index(index) + def _delete_item(self, indexes=None): + if indexes is None: + indexes = self.selectedIndexes() + self._source_model.remove_indexes(indexes) def _on_ctrl_shift_enter_pressed(self): self._add_task() From 15a15336b6659daf1d78035692a97b0eea2849d9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 14:56:27 +0200 Subject: [PATCH 116/219] added ability to unset delete flags by item ids --- .../project_manager/project_manager/model.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 0b3bf2fa9ec..c672cdc053c 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -430,6 +430,33 @@ def add_item(self, item, parent=None, row=None): return result[0] return None + def remove_delete_flag(self, item_ids): + remove_tag_items_by_id = {} + for item_id in item_ids: + item = self.items_by_id[item_id] + if not isinstance(item, (AssetItem, TaskItem)): + continue + + if item.data(None, REMOVED_ROLE): + remove_tag_items_by_id[item_id] = item + + for item in remove_tag_items_by_id.values(): + parent = item.parent() + while True: + if not isinstance(parent, (AssetItem, TaskItem)): + break + + if parent.id in remove_tag_items_by_id: + continue + + if parent.data(None, REMOVED_ROLE): + remove_tag_items_by_id[parent.id] = parent + + parent = parent.parent() + + for item in remove_tag_items_by_id.values(): + item.setData(None, False, REMOVED_ROLE) + def remove_index(self, index): return self.remove_indexes([index]) From 439876d64b76c80cd7b2c5901c196dc7653e1e5f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 14:57:04 +0200 Subject: [PATCH 117/219] hierarchy view has ability to show context menu --- .../project_manager/project_manager/view.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 680601587b0..598d55270a4 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -102,6 +102,7 @@ def __init__(self, dbcon, source_model, *args, **kwargs): self.setItemDelegate(main_delegate) self.setAlternatingRowColors(True) self.setSelectionMode(HierarchyView.ExtendedSelection) + self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) column_delegates = {} column_key_to_index = {} @@ -128,6 +129,7 @@ def __init__(self, dbcon, source_model, *args, **kwargs): column_key_to_index[key] = column source_model.index_moved.connect(self._on_rows_moved) + self.customContextMenuRequested.connect(self._on_context_menu) self._project_doc_cache = project_doc_cache self._tools_cache = tools_cache @@ -338,3 +340,21 @@ def _on_enter_pressed(self): and index.flags() & QtCore.Qt.ItemIsEditable ): self.edit(index) + + def _on_context_menu(self, point): + index = self.indexAt(point) + if index.column() != 0: + return + + actions = [] + + context_menu = QtWidgets.QMenu(self) + if not actions: + return + + for action in actions: + context_menu.addAction(action) + + global_point = self.viewport().mapToGlobal(point) + context_menu.exec_(global_point) + From 196c2bc175cc1cf723fbadf36b80268654fc32e0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 14:57:16 +0200 Subject: [PATCH 118/219] added action to unsed remove tag --- .../project_manager/project_manager/view.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 598d55270a4..a91f02a590c 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -8,6 +8,10 @@ ) from openpype.lib import ApplicationManager +from .constants import ( + REMOVED_ROLE, + IDENTIFIER_ROLE +) class NameDef: @@ -349,6 +353,28 @@ def _on_context_menu(self, point): actions = [] context_menu = QtWidgets.QMenu(self) + + indexes = self.selectedIndexes() + + items_by_id = {} + for index in indexes: + item_id = index.data(IDENTIFIER_ROLE) + if item_id in items_by_id: + continue + items_by_id[item_id] = self._source_model.items_by_id[item_id] + + removed_item_ids = [] + for item_id, item in items_by_id.items(): + if item.data(None, REMOVED_ROLE): + removed_item_ids.append(item_id) + + if removed_item_ids: + action = QtWidgets.QAction("Keep items", context_menu) + action.triggered.connect( + lambda: self._remove_delete_flag(removed_item_ids) + ) + actions.append(action) + if not actions: return @@ -358,3 +384,5 @@ def _on_context_menu(self, point): global_point = self.viewport().mapToGlobal(point) context_menu.exec_(global_point) + def _remove_delete_flag(self, item_ids): + self._source_model.remove_delete_flag(item_ids) From 40ac5afb28bb0275cd0de89a0fc71f5842d9387c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 15:08:58 +0200 Subject: [PATCH 119/219] fix task removement validation order --- openpype/tools/project_manager/project_manager/model.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index c672cdc053c..dd514369ea5 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -506,13 +506,21 @@ def _fill_children(_all_descendants, cur_item, parent_item=None): return task_removed remove_item = cur_item.data(None, HIERARCHY_CHANGE_ABLE_ROLE) + task_children = [] for row in range(cur_item.rowCount()): child_item = cur_item.child(row) + if isinstance(child_item, TaskItem): + task_children.append(child_item) + continue + if not _fill_children(_all_descendants, child_item, cur_item): remove_item = False if remove_item: cur_item.setData(None, True, REMOVED_ROLE) + + for task_item in task_children: + _fill_children(_all_descendants, task_item, cur_item) return remove_item _fill_children(all_descendants, item) From 85f0117bec9540b6ff85d74d706c4ea8253780ac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 15:20:55 +0200 Subject: [PATCH 120/219] fix remove_delete_flag method --- .../project_manager/project_manager/model.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index dd514369ea5..ed84ab631bd 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -430,7 +430,7 @@ def add_item(self, item, parent=None, row=None): return result[0] return None - def remove_delete_flag(self, item_ids): + def remove_delete_flag(self, item_ids, with_children=True): remove_tag_items_by_id = {} for item_id in item_ids: item = self.items_by_id[item_id] @@ -440,7 +440,7 @@ def remove_delete_flag(self, item_ids): if item.data(None, REMOVED_ROLE): remove_tag_items_by_id[item_id] = item - for item in remove_tag_items_by_id.values(): + for item in tuple(remove_tag_items_by_id.values()): parent = item.parent() while True: if not isinstance(parent, (AssetItem, TaskItem)): @@ -454,6 +454,18 @@ def remove_delete_flag(self, item_ids): parent = parent.parent() + if not with_children: + continue + + def _children_recursion(_item, store_obj): + if isinstance(_item, AssetItem): + for row in range(_item.rowCount()): + _child_item = _item.child(row) + if _child_item.id not in store_obj: + store_obj[_child_item.id] = _child_item + _children_recursion(_child_item, store_obj) + _children_recursion(item, remove_tag_items_by_id) + for item in remove_tag_items_by_id.values(): item.setData(None, False, REMOVED_ROLE) From 0739ee5a3a98f85ee761ba81541687f3738fbc32 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 15:29:25 +0200 Subject: [PATCH 121/219] save can remove tasks and change type of asset to archived_asset --- .../project_manager/project_manager/model.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index ed84ab631bd..e9f517b7f16 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -961,7 +961,7 @@ def save(self): to_process = Queue() to_process.put(project_item) - update_list = [] + bulk_writes = [] while not to_process.empty(): parent = to_process.get() insert_list = [] @@ -973,15 +973,21 @@ def save(self): if item.is_new: insert_list.append(item) - continue - update_data = item.update_data() - if update_data: - update_list.append(UpdateOne( + elif item.data(None, REMOVED_ROLE): + bulk_writes.append(UpdateOne( {"_id": item.asset_id}, - update_data + {"$set": {"type": "archived_asset"}} )) + else: + update_data = item.update_data() + if update_data: + bulk_writes.append(UpdateOne( + {"_id": item.asset_id}, + update_data + )) + if insert_list: new_docs = [] for item in insert_list: @@ -991,8 +997,8 @@ def save(self): for idx, mongo_id in enumerate(result.inserted_ids): insert_list[idx].mongo_id = mongo_id - if update_list: - project_col.bulk_write(update_list) + if bulk_writes: + project_col.bulk_write(bulk_writes) class BaseItem: @@ -1606,6 +1612,8 @@ def _global_data(self, role): return super(TaskItem, self)._global_data(role) def to_doc_data(self): + if self._removed: + return {} data = copy.deepcopy(self._data) data.pop("name") name = self.data("name", QtCore.Qt.DisplayRole) From 1d4dd107d30c73355caacd7a72666a1a02e2240f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 15:30:22 +0200 Subject: [PATCH 122/219] refresh project after save --- openpype/tools/project_manager/project_manager/model.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index e9f517b7f16..1cca1d1544c 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -131,6 +131,11 @@ def items_by_id(self): def _reset_root_item(self): self._root_item = RootItem(self) + def refresh_project(self): + project_name = self._current_project + self._current_project = None + self.set_project(project_name) + def set_project(self, project_name): if self._current_project == project_name: return @@ -1000,6 +1005,8 @@ def save(self): if bulk_writes: project_col.bulk_write(bulk_writes) + self.refresh_project() + class BaseItem: columns = [] From 30bc0c8fabc28e1b94de4015e20dc42b528c5c3e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 15:46:28 +0200 Subject: [PATCH 123/219] items have item_type --- .../project_manager/constants.py | 1 + .../project_manager/project_manager/model.py | 44 +++++++++++++------ 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/constants.py b/openpype/tools/project_manager/project_manager/constants.py index 76d3d3cda1a..703cc0d0038 100644 --- a/openpype/tools/project_manager/project_manager/constants.py +++ b/openpype/tools/project_manager/project_manager/constants.py @@ -6,6 +6,7 @@ DUPLICATED_ROLE = QtCore.Qt.UserRole + 2 HIERARCHY_CHANGE_ABLE_ROLE = QtCore.Qt.UserRole + 3 REMOVED_ROLE = QtCore.Qt.UserRole + 4 +ITEM_TYPE_ROLE = QtCore.Qt.UserRole + 5 NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_" NAME_REGEX = re.compile("^[" + NAME_ALLOWED_SYMBOLS + "]*$") diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 1cca1d1544c..145dacd7403 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -5,6 +5,7 @@ from .constants import ( IDENTIFIER_ROLE, + ITEM_TYPE_ROLE, DUPLICATED_ROLE, HIERARCHY_CHANGE_ABLE_ROLE, REMOVED_ROLE @@ -1015,6 +1016,7 @@ class BaseItem: _name_icon = None _is_duplicated = False + item_type = "base" _None = object() @@ -1027,6 +1029,7 @@ def __init__(self, data=None): key: None for key in self.columns } + self._global_data = {} self._source_data = data if data: for key, value in data.items(): @@ -1052,7 +1055,10 @@ def move_to(self, item, row): self._children.pop(idx) self._children.insert(row, item) - def _global_data(self, role): + def _get_global_data(self, role): + if role == ITEM_TYPE_ROLE: + return self.item_type + if role == IDENTIFIER_ROLE: return self._id @@ -1062,10 +1068,14 @@ def _global_data(self, role): if role == REMOVED_ROLE: return False - return self._None + return self._global_data.get(role, self._None) + + def _set_global_data(self, value, role): + self._global_data[role] = value + return True def data(self, key, role): - value = self._global_data(role) + value = self._get_global_data(role) if value is not self._None: return value @@ -1096,14 +1106,12 @@ def setData(self, key, value, role): return True if role == QtCore.Qt.EditRole: - if key not in self.editable_columns: - return False - - self._data[key] = value - # must return true if successful - return True + if key in self.editable_columns: + self._data[key] = value + # must return true if successful + return True - return False + return self._set_global_data(value, role) @property def id(self): @@ -1175,6 +1183,8 @@ def flags(self, key): class RootItem(BaseItem): + item_type = "root" + def __init__(self, model): super(RootItem, self).__init__() self._model = model @@ -1187,6 +1197,8 @@ def flags(self, *args, **kwargs): class ProjectItem(BaseItem): + item_type = "project" + columns = { "name", "type", @@ -1261,6 +1273,8 @@ def flags(self, *args, **kwargs): class AssetItem(BaseItem): + item_type = "asset" + columns = { "name", "type", @@ -1452,7 +1466,7 @@ def name_icon(cls): cls._name_icon = qtawesome.icon("fa.folder", color="#333333") return cls._name_icon - def _global_data(self, role): + def _get_global_data(self, role): if role == HIERARCHY_CHANGE_ABLE_ROLE: return self._hierarchy_changes_enabled @@ -1463,7 +1477,7 @@ def _global_data(self, role): return "Asset with name \"{}\" already exists.".format( self._data["name"] ) - return super(AssetItem, self)._global_data(role) + return super(AssetItem, self)._get_global_data(role) def data(self, key, role): if role == QtCore.Qt.BackgroundRole: @@ -1577,6 +1591,8 @@ def remove_child(self, item): class TaskItem(BaseItem): + item_type = "task" + columns = { "name", "type" @@ -1608,7 +1624,7 @@ def name_icon(cls): def add_child(self, item, row=None): raise AssertionError("BUG: Can't add children to Task") - def _global_data(self, role): + def _get_global_data(self, role): if role == REMOVED_ROLE: return self._removed @@ -1616,7 +1632,7 @@ def _global_data(self, role): return "Duplicated Task name \"{}\".".format( self._data["name"] ) - return super(TaskItem, self)._global_data(role) + return super(TaskItem, self)._get_global_data(role) def to_doc_data(self): if self._removed: From 6f8a57daaf26e170075cf12a27bc7ed4428eb386 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 15:48:21 +0200 Subject: [PATCH 124/219] context menu has add task and add asset actions --- .../project_manager/project_manager/view.py | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index a91f02a590c..c8ce7219ca2 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -10,7 +10,8 @@ from openpype.lib import ApplicationManager from .constants import ( REMOVED_ROLE, - IDENTIFIER_ROLE + IDENTIFIER_ROLE, + ITEM_TYPE_ROLE ) @@ -347,7 +348,8 @@ def _on_enter_pressed(self): def _on_context_menu(self, point): index = self.indexAt(point) - if index.column() != 0: + column = index.column() + if column != 0: return actions = [] @@ -358,11 +360,31 @@ def _on_context_menu(self, point): items_by_id = {} for index in indexes: - item_id = index.data(IDENTIFIER_ROLE) - if item_id in items_by_id: + if index.column() != column: continue + + item_id = index.data(IDENTIFIER_ROLE) items_by_id[item_id] = self._source_model.items_by_id[item_id] + item_ids = tuple(items_by_id.keys()) + if len(item_ids) == 1: + item = items_by_id[item_ids[0]] + item_type = item.data(None, ITEM_TYPE_ROLE) + if item_type in ("asset", "project"): + add_asset_action = QtWidgets.QAction("Add Asset", context_menu) + add_asset_action.triggered.connect( + lambda: self._add_asset() + ) + actions.append(add_asset_action) + + if item_type in ("asset", "task"): + add_task_action = QtWidgets.QAction("Add Task", context_menu) + add_task_action.triggered.connect( + lambda: self._add_task() + ) + actions.append(add_task_action) + + # Remove delete tag on items removed_item_ids = [] for item_id, item in items_by_id.items(): if item.data(None, REMOVED_ROLE): From 4319136dd8bb3ae9a4fdc2d421a3a8d6a3aa6f46 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 15:51:18 +0200 Subject: [PATCH 125/219] assets and task are loaded sorted by name --- openpype/tools/project_manager/project_manager/model.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 145dacd7403..e0af188ec6e 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -211,7 +211,8 @@ def set_project(self, project_name): continue new_items = [] - for asset_doc in asset_docs_by_parent_id[parent_id]: + asset_docs = asset_docs_by_parent_id[parent_id] + for asset_doc in sorted(asset_docs, key=lambda item: item["name"]): # Create new Item new_item = AssetItem(asset_doc) # Store item to be added under parent in bulk @@ -255,8 +256,8 @@ def set_project(self, project_name): continue task_items = [] - for task_name, task_data in asset_tasks.items(): - _task_data = copy.deepcopy(task_data) + for task_name in sorted(asset_tasks.keys()): + _task_data = copy.deepcopy(asset_tasks[task_name]) _task_data["name"] = task_name task_item = TaskItem(_task_data) task_items.append(task_item) From 6e16479d22d2f2baaf858787388eb295b71833d5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 15:58:15 +0200 Subject: [PATCH 126/219] auto refill selected value of filter combobox on focus out --- .../tools/project_manager/project_manager/widgets.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index 1da4da3c243..566e17ea86b 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -57,6 +57,15 @@ def focusInEvent(self, event): super(FilterComboBox, self).focusInEvent(event) self.lineEdit().selectAll() + def focusOutEvent(self, event): + idx = self.currentIndex() + if idx > -1: + index = self.model().index(idx, 0) + text = index.data(QtCore.Qt.DisplayRole) + if text != self.lineEdit().text(): + self.lineEdit().setText(text) + super(FilterComboBox, self).focusOutEvent(event) + def on_completer_activated(self, text): if text: index = self.findText(text) From 9b65ef84eb7b6f4925928c56f4b7eb44273ac154 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 16:00:07 +0200 Subject: [PATCH 127/219] shuffle methods order --- .../tools/project_manager/project_manager/view.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index c8ce7219ca2..a2a9fc1666d 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -295,9 +295,6 @@ def _add_task(self, parent_index=None): # Start editing self.edit(task_type_index) - def _on_shift_enter_pressed(self): - self._add_asset() - def _add_asset(self, index=None): if index is None: index = self.currentIndex() @@ -322,6 +319,9 @@ def _add_asset(self, index=None): # Start editing self.edit(new_index) + def _on_shift_enter_pressed(self): + self._add_asset() + def _on_up_ctrl_pressed(self): indexes = self.selectedIndexes() self._source_model.move_horizontal(indexes, -1) @@ -346,6 +346,9 @@ def _on_enter_pressed(self): ): self.edit(index) + def _remove_delete_flag(self, item_ids): + self._source_model.remove_delete_flag(item_ids) + def _on_context_menu(self, point): index = self.indexAt(point) column = index.column() @@ -405,6 +408,3 @@ def _on_context_menu(self, point): global_point = self.viewport().mapToGlobal(point) context_menu.exec_(global_point) - - def _remove_delete_flag(self, item_ids): - self._source_model.remove_delete_flag(item_ids) From 434db48bc8e27a1c6e306cdb1accab5884cd96d8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 16:52:41 +0200 Subject: [PATCH 128/219] fix horizontal/vertical naming --- .../project_manager/project_manager/model.py | 16 ++++++++-------- .../project_manager/project_manager/view.py | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index e0af188ec6e..435b6e25386 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -647,7 +647,7 @@ def _validate_asset_duplicity(self, name): index = self.index_for_item(item) self.setData(index, True, DUPLICATED_ROLE) - def _move_vertical_single(self, index, direction): + def _move_horizontal_single(self, index, direction): if not index.isValid(): return @@ -737,7 +737,7 @@ def _move_vertical_single(self, index, direction): self.index_moved.emit(index) - def move_vertical(self, indexes, direction): + def move_horizontal(self, indexes, direction): if not indexes: return @@ -745,7 +745,7 @@ def move_vertical(self, indexes, direction): indexes = [indexes] if len(indexes) == 1: - self._move_vertical_single(indexes[0], direction) + self._move_horizontal_single(indexes[0], direction) return items_by_id = {} @@ -802,9 +802,9 @@ def move_vertical(self, indexes, direction): for item in items: index = self.index_for_item(item) - self._move_vertical_single(index, direction) + self._move_horizontal_single(index, direction) - def _move_horizontal_single(self, index, direction): + def _move_vertical_single(self, index, direction): if not index.isValid(): return @@ -891,7 +891,7 @@ def _move_horizontal_single(self, index, direction): self.index_moved.emit(index) - def move_horizontal(self, indexes, direction): + def move_vertical(self, indexes, direction): if not indexes: return @@ -899,7 +899,7 @@ def move_horizontal(self, indexes, direction): indexes = [indexes] if len(indexes) == 1: - self._move_horizontal_single(indexes[0], direction) + self._move_vertical_single(indexes[0], direction) return items_by_id = {} @@ -929,7 +929,7 @@ def move_horizontal(self, indexes, direction): for item in items: index = self.index_for_item(item) - self._move_horizontal_single(index, direction) + self._move_vertical_single(index, direction) def child_removed(self, child): self._items_by_id.pop(child.id, None) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index a2a9fc1666d..4a1f4998a8e 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -324,19 +324,19 @@ def _on_shift_enter_pressed(self): def _on_up_ctrl_pressed(self): indexes = self.selectedIndexes() - self._source_model.move_horizontal(indexes, -1) + self._source_model.move_vertical(indexes, -1) def _on_down_ctrl_pressed(self): indexes = self.selectedIndexes() - self._source_model.move_horizontal(indexes, 1) + self._source_model.move_vertical(indexes, 1) def _on_left_ctrl_pressed(self): indexes = self.selectedIndexes() - self._source_model.move_vertical(indexes, -1) + self._source_model.move_horizontal(indexes, -1) def _on_right_ctrl_pressed(self): indexes = self.selectedIndexes() - self._source_model.move_vertical(indexes, 1) + self._source_model.move_horizontal(indexes, 1) def _on_enter_pressed(self): index = self.currentIndex() From d01cd88a1be997c8a52a1ea029aa74f443b93cc1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 17:00:48 +0200 Subject: [PATCH 129/219] change `key` argument order for `data` and `setData` methods --- .../project_manager/project_manager/model.py | 84 +++++++++---------- .../project_manager/project_manager/view.py | 4 +- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 435b6e25386..b44e8786aad 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -238,7 +238,7 @@ def set_project(self, project_name): while not non_modifiable_queue.empty(): item_id = non_modifiable_queue.get() item = self._items_by_id[item_id] - item.setData(None, False, HIERARCHY_CHANGE_ABLE_ROLE) + item.setData(False, HIERARCHY_CHANGE_ABLE_ROLE) parent = item.parent() if ( @@ -281,7 +281,7 @@ def data(self, index, role): key = self.columns[column] item = index.internalPointer() - return item.data(key, role) + return item.data(role, key) def setData(self, index, value, role=QtCore.Qt.EditRole): if not index.isValid(): @@ -296,7 +296,7 @@ def setData(self, index, value, role=QtCore.Qt.EditRole): ): self._rename_asset(item, value) - result = item.setData(key, value, role) + result = item.setData(value, role, key) if result: self.dataChanged.emit(index, index, [role]) @@ -397,7 +397,7 @@ def add_items(self, items, parent=None, start_row=None): if parent is None: parent = self._root_item - if parent.data(None, REMOVED_ROLE): + if parent.data(REMOVED_ROLE): return [] if start_row is None: @@ -416,7 +416,7 @@ def add_items(self, items, parent=None, start_row=None): parent.add_child(item, row) if isinstance(item, AssetItem): - name = item.data("name", QtCore.Qt.DisplayRole) + name = item.data(QtCore.Qt.DisplayRole, "name") self._asset_items_by_name[name].append(item) if item.id not in self._items_by_id: @@ -444,7 +444,7 @@ def remove_delete_flag(self, item_ids, with_children=True): if not isinstance(item, (AssetItem, TaskItem)): continue - if item.data(None, REMOVED_ROLE): + if item.data(REMOVED_ROLE): remove_tag_items_by_id[item_id] = item for item in tuple(remove_tag_items_by_id.values()): @@ -456,7 +456,7 @@ def remove_delete_flag(self, item_ids, with_children=True): if parent.id in remove_tag_items_by_id: continue - if parent.data(None, REMOVED_ROLE): + if parent.data(REMOVED_ROLE): remove_tag_items_by_id[parent.id] = parent parent = parent.parent() @@ -474,7 +474,7 @@ def _children_recursion(_item, store_obj): _children_recursion(item, remove_tag_items_by_id) for item in remove_tag_items_by_id.values(): - item.setData(None, False, REMOVED_ROLE) + item.setData(False, REMOVED_ROLE) def remove_index(self, index): return self.remove_indexes([index]) @@ -502,7 +502,7 @@ def remove_indexes(self, indexes): self._remove_item(item) def _remove_item(self, item): - is_removed = item.data(None, REMOVED_ROLE) + is_removed = item.data(REMOVED_ROLE) if is_removed: return @@ -516,15 +516,15 @@ def _fill_children(_all_descendants, cur_item, parent_item=None): _all_descendants[parent_item.id][cur_item.id] = cur_item if isinstance(cur_item, TaskItem): - was_removed = cur_item.data(None, REMOVED_ROLE) + was_removed = cur_item.data(REMOVED_ROLE) task_removed = True if not was_removed and parent_item is not None: - task_removed = parent_item.data(None, REMOVED_ROLE) + task_removed = parent_item.data(REMOVED_ROLE) if not was_removed: - cur_item.setData(None, task_removed, REMOVED_ROLE) + cur_item.setData(task_removed, REMOVED_ROLE) return task_removed - remove_item = cur_item.data(None, HIERARCHY_CHANGE_ABLE_ROLE) + remove_item = cur_item.data(HIERARCHY_CHANGE_ABLE_ROLE) task_children = [] for row in range(cur_item.rowCount()): child_item = cur_item.child(row) @@ -536,7 +536,7 @@ def _fill_children(_all_descendants, cur_item, parent_item=None): remove_item = False if remove_item: - cur_item.setData(None, True, REMOVED_ROLE) + cur_item.setData(True, REMOVED_ROLE) for task_item in task_children: _fill_children(_all_descendants, task_item, cur_item) @@ -576,7 +576,7 @@ def _fill_children(_all_descendants, cur_item, parent_item=None): chilren_by_row[row] = child_item children.pop(child_item.id) - remove_item = child_item.data(None, REMOVED_ROLE) + remove_item = child_item.data(REMOVED_ROLE) if not remove_item or not child_item.is_new: modified_children.append(child_item) if end_row is not None: @@ -617,7 +617,7 @@ def _rename_asset(self, asset_item, new_name): if not isinstance(asset_item, AssetItem): return - prev_name = asset_item.data("name", QtCore.Qt.DisplayRole) + prev_name = asset_item.data(QtCore.Qt.DisplayRole, "name") if prev_name == new_name: return @@ -981,7 +981,7 @@ def save(self): if item.is_new: insert_list.append(item) - elif item.data(None, REMOVED_ROLE): + elif item.data(REMOVED_ROLE): bulk_writes.append(UpdateOne( {"_id": item.asset_id}, {"$set": {"type": "archived_asset"}} @@ -1075,7 +1075,7 @@ def _set_global_data(self, value, role): self._global_data[role] = value return True - def data(self, key, role): + def data(self, role, key=None): value = self._get_global_data(role) if value is not self._None: return value @@ -1091,14 +1091,14 @@ def data(self, key, role): if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole): value = self._data[key] if value is None: - value = self.parent().data(key, role) + value = self.parent().data(role, key) return value if role == QtCore.Qt.DecorationRole and key == "name": return self.name_icon() return None - def setData(self, key, value, role): + def setData(self, value, role, key=None): if role == DUPLICATED_ROLE: if value == self._is_duplicated: return False @@ -1389,8 +1389,8 @@ def to_doc(self): ) doc = { - "name": self.data("name", QtCore.Qt.DisplayRole), - "type": self.data("type", QtCore.Qt.DisplayRole), + "name": self.data(QtCore.Qt.DisplayRole, "name"), + "type": self.data(QtCore.Qt.DisplayRole, "type"), "schema": schema_name, "data": doc_data, "parent": self.project_id @@ -1402,7 +1402,7 @@ def to_doc(self): if key in doc: continue # Use `data` method to get inherited values - doc_data[key] = self.data(key, QtCore.Qt.DisplayRole) + doc_data[key] = self.data(QtCore.Qt.DisplayRole, key) return doc @@ -1480,16 +1480,16 @@ def _get_global_data(self, role): ) return super(AssetItem, self)._get_global_data(role) - def data(self, key, role): + def data(self, role, key=None): if role == QtCore.Qt.BackgroundRole: if self._removed: return QtGui.QColor(255, 0, 0, 127) elif self.is_new: return QtGui.QColor(0, 255, 0, 127) - return super(AssetItem, self).data(key, role) + return super(AssetItem, self).data(role, key) - def setData(self, key, value, role): + def setData(self, value, role, key=None): if role == REMOVED_ROLE: self._removed = value return True @@ -1506,7 +1506,7 @@ def setData(self, key, value, role): and not self._hierarchy_changes_enabled ): return False - return super(AssetItem, self).setData(key, value, role) + return super(AssetItem, self).setData(value, role, key) def flags(self, key): if key == "name": @@ -1517,18 +1517,18 @@ def flags(self, key): return super(AssetItem, self).flags(key) def _add_task(self, item): - name = item.data("name", QtCore.Qt.DisplayRole).lower() - item_id = item.data(None, IDENTIFIER_ROLE) + name = item.data(QtCore.Qt.DisplayRole, "name").lower() + item_id = item.data(IDENTIFIER_ROLE) self._task_name_by_item_id[item_id] = name self._task_items_by_name[name].append(item) if len(self._task_items_by_name[name]) > 1: self._duplicated_task_names.add(name) for _item in self._task_items_by_name[name]: - _item.setData(None, True, DUPLICATED_ROLE) + _item.setData(True, DUPLICATED_ROLE) def _remove_task(self, item): - item_id = item.data(None, IDENTIFIER_ROLE) + item_id = item.data(IDENTIFIER_ROLE) name = self._task_name_by_item_id[item_id] self._task_name_by_item_id.pop(item_id) @@ -1539,11 +1539,11 @@ def _remove_task(self, item): elif len(self._task_items_by_name[name]) == 1: self._duplicated_task_names.remove(name) for _item in self._task_items_by_name[name]: - _item.setData(None, False, DUPLICATED_ROLE) + _item.setData(False, DUPLICATED_ROLE) def _rename_task(self, item): - new_name = item.data("name", QtCore.Qt.DisplayRole).lower() - item_id = item.data(None, IDENTIFIER_ROLE) + new_name = item.data(QtCore.Qt.DisplayRole, "name").lower() + item_id = item.data(IDENTIFIER_ROLE) prev_name = self._task_name_by_item_id[item_id] if new_name == prev_name: return @@ -1556,16 +1556,16 @@ def _rename_task(self, item): elif len(self._task_items_by_name[prev_name]) == 1: self._duplicated_task_names.remove(prev_name) for _item in self._task_items_by_name[prev_name]: - _item.setData(None, False, DUPLICATED_ROLE) + _item.setData(False, DUPLICATED_ROLE) # Add to new name mapping self._task_items_by_name[new_name].append(item) if len(self._task_items_by_name[new_name]) > 1: self._duplicated_task_names.add(new_name) for _item in self._task_items_by_name[new_name]: - _item.setData(None, True, DUPLICATED_ROLE) + _item.setData(True, DUPLICATED_ROLE) else: - item.setData(None, False, DUPLICATED_ROLE) + item.setData(False, DUPLICATED_ROLE) self._task_name_by_item_id[item_id] = new_name @@ -1640,12 +1640,12 @@ def to_doc_data(self): return {} data = copy.deepcopy(self._data) data.pop("name") - name = self.data("name", QtCore.Qt.DisplayRole) + name = self.data(QtCore.Qt.DisplayRole, "name") return { name: data } - def data(self, key, role): + def data(self, role, key=None): if role == QtCore.Qt.BackgroundRole: if self._removed: return QtGui.QColor(255, 0, 0, 127) @@ -1658,14 +1658,14 @@ def data(self, key, role): and key == "name" ): return self._data[key] or self._data["type"] or "< Select Type >" - return super(TaskItem, self).data(key, role) + return super(TaskItem, self).data(role, key) - def setData(self, key, value, role): + def setData(self, value, role, key=None): if role == REMOVED_ROLE: self._removed = value return True - result = super(TaskItem, self).setData(key, value, role) + result = super(TaskItem, self).setData(value, role, key) if ( key == "name" diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 4a1f4998a8e..32eff232096 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -372,7 +372,7 @@ def _on_context_menu(self, point): item_ids = tuple(items_by_id.keys()) if len(item_ids) == 1: item = items_by_id[item_ids[0]] - item_type = item.data(None, ITEM_TYPE_ROLE) + item_type = item.data(ITEM_TYPE_ROLE) if item_type in ("asset", "project"): add_asset_action = QtWidgets.QAction("Add Asset", context_menu) add_asset_action.triggered.connect( @@ -390,7 +390,7 @@ def _on_context_menu(self, point): # Remove delete tag on items removed_item_ids = [] for item_id, item in items_by_id.items(): - if item.data(None, REMOVED_ROLE): + if item.data(REMOVED_ROLE): removed_item_ids.append(item_id) if removed_item_ids: From 0c779b7ed2e068cfc91ae073a4162ac7212fa157 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 17:28:48 +0200 Subject: [PATCH 130/219] not possible to move with deleted or not hierachy changeable items --- .../project_manager/project_manager/model.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index b44e8786aad..0ca958f83fa 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -659,6 +659,15 @@ def _move_horizontal_single(self, index, direction): if isinstance(item, (RootItem, ProjectItem)): return + if item.data(REMOVED_ROLE): + return + + if ( + isinstance(item, AssetItem) + and not item.data(HIERARCHY_CHANGE_ABLE_ROLE) + ): + return + if abs(direction) != 1: return @@ -813,6 +822,15 @@ def _move_vertical_single(self, index, direction): if isinstance(item, (RootItem, ProjectItem)): return + if item.data(REMOVED_ROLE): + return + + if ( + isinstance(item, AssetItem) + and not item.data(HIERARCHY_CHANGE_ABLE_ROLE) + ): + return + if abs(direction) != 1: return From 31077bdc5a63cfd8ba6b303dbb91163baa4fddda Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 May 2021 17:29:29 +0200 Subject: [PATCH 131/219] added restrictions to vertical movement into removed items --- .../project_manager/project_manager/model.py | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 0ca958f83fa..b375bba22f9 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -697,22 +697,31 @@ def _move_horizontal_single(self, index, direction): item_row = item.row() dst_parent = None for row in reversed(range(item_row)): - if row == item_row: - continue _item = src_parent.child(row) - if not isinstance(_item, TaskItem): + if not isinstance(_item, AssetItem): + continue + + if _item.data(REMOVED_ROLE): + continue + + dst_parent = _item + break + + _next_row = item_row + 1 + if dst_parent is None and _next_row < src_row_count: + for row in range(_next_row, src_row_count): + _item = src_parent.child(row) + if not isinstance(_item, AssetItem): + continue + + if _item.data(REMOVED_ROLE): + continue + dst_parent = _item break if dst_parent is None: - for row in range(item_row + 1, src_row_count + 2): - _item = src_parent.child(row) - if not isinstance(_item, TaskItem): - dst_parent = _item - break - - if dst_parent is None: - return + return dst_row = dst_parent.rowCount() From dab7a53fb48c97fc42894935c723ff23fbc7e286 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 11:42:41 +0200 Subject: [PATCH 132/219] vertical movement can go to next parents across the board --- .../project_manager/project_manager/model.py | 89 ++++++++++++++++--- 1 file changed, 77 insertions(+), 12 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index b375bba22f9..7626ef41994 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -844,11 +844,22 @@ def _move_vertical_single(self, index, direction): return src_parent = item.parent() + if not isinstance(src_parent, AssetItem): + return + src_parent_index = self.index_from_item( src_parent.row(), 0, src_parent.parent() ) source_row = item.row() + parent_items = [] + parent = src_parent + while True: + parent = parent.parent() + parent_items.insert(0, parent) + if isinstance(parent, ProjectItem): + break + dst_parent = None dst_parent_index = None destination_row = None @@ -874,20 +885,71 @@ def _move_vertical_single(self, index, direction): # Up elif direction == -1: - if source_row > 0: - dst_parent_index = src_parent_index - dst_parent = src_parent - destination_row = source_row - 1 - else: - parent_parent = src_parent.parent() - if not parent_parent: + current_idxs = [] + for parent_item in parent_items: + if not isinstance(parent_item, ProjectItem): + current_idxs.append(parent_item.row()) + current_idxs.append(src_parent.row()) + + max_idxs = [0 for _ in current_idxs] + indexes_len = len(current_idxs) + + while True: + if current_idxs == max_idxs: return - previous_parent = parent_parent.child(src_parent.row() - 1) - if not previous_parent: + def _update_parents( + _current_idx, _parent_items, _current_idxs, top=True + ): + if _current_idx < 0: + return False + + if _current_idxs[_current_idx] == 0: + if not _update_parents( + _current_idx - 1, _parent_items, _current_idxs, False + ): + return False + + parent = _parent_items[_current_idx] + row_count = 0 + if parent is not None: + row_count = parent.rowCount() + _current_idxs[_current_idx] = row_count + return True + if top: + return True + + _current_idxs[_current_idx] -= 1 + parent_item = _parent_items[_current_idx] + new_item = parent_item.child(_current_idxs[_current_idx]) + _parent_items[_current_idx + 1] = new_item + + return True + + updated = _update_parents( + indexes_len - 1, parent_items, current_idxs + ) + if not updated: return - dst_parent = previous_parent - destination_row = previous_parent.rowCount() + + parent_item = parent_items[-1] + row_count = current_idxs[-1] + current_idxs[-1] = 0 + for row in reversed(range(row_count)): + child_item = parent_item.child(row) + if ( + child_item is src_parent + or child_item.data(REMOVED_ROLE) + or not isinstance(child_item, AssetItem) + ): + continue + + dst_parent = child_item + destination_row = dst_parent.rowCount() + break + + if dst_parent is not None: + break if dst_parent_index is None: dst_parent_index = self.index_from_item( @@ -916,7 +978,10 @@ def _move_vertical_single(self, index, direction): self.endMoveRows() - self.index_moved.emit(index) + new_index = self.index( + _destination_row, index.column(), dst_parent_index + ) + self.index_moved.emit(new_index) def move_vertical(self, indexes, direction): if not indexes: From 1bdae76a96a40b5199cd220cf5389c1d98945f97 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 11:50:42 +0200 Subject: [PATCH 133/219] simplified parent resolving function --- .../project_manager/project_manager/model.py | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 7626ef41994..4e0139a8d10 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -898,37 +898,31 @@ def _move_vertical_single(self, index, direction): if current_idxs == max_idxs: return - def _update_parents( - _current_idx, _parent_items, _current_idxs, top=True - ): + def _update_parents(_current_idx, top=True): if _current_idx < 0: return False - if _current_idxs[_current_idx] == 0: - if not _update_parents( - _current_idx - 1, _parent_items, _current_idxs, False - ): + if current_idxs[_current_idx] == 0: + if not _update_parents(_current_idx - 1, False): return False - parent = _parent_items[_current_idx] + parent = parent_items[_current_idx] row_count = 0 if parent is not None: row_count = parent.rowCount() - _current_idxs[_current_idx] = row_count + current_idxs[_current_idx] = row_count return True if top: return True - _current_idxs[_current_idx] -= 1 - parent_item = _parent_items[_current_idx] - new_item = parent_item.child(_current_idxs[_current_idx]) - _parent_items[_current_idx + 1] = new_item + current_idxs[_current_idx] -= 1 + parent_item = parent_items[_current_idx] + new_item = parent_item.child(current_idxs[_current_idx]) + parent_items[_current_idx + 1] = new_item return True - updated = _update_parents( - indexes_len - 1, parent_items, current_idxs - ) + updated = _update_parents(indexes_len - 1) if not updated: return From 1e59f2f91903fa5a61c86848f1ba2c59d940c47a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 12:10:07 +0200 Subject: [PATCH 134/219] it is possible to jump with items in both ways --- .../project_manager/project_manager/model.py | 89 +++++++++++++------ 1 file changed, 64 insertions(+), 25 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 4e0139a8d10..86f889ae95d 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -861,27 +861,67 @@ def _move_vertical_single(self, index, direction): break dst_parent = None - dst_parent_index = None - destination_row = None - _destination_row = None # Down if direction == 1: - if source_row < src_parent.rowCount() - 1: - dst_parent_index = src_parent_index - dst_parent = src_parent - destination_row = source_row + 1 - # This row is not row number after moving but before moving - _destination_row = destination_row + 1 - else: - destination_row = 0 - parent_parent = src_parent.parent() - if not parent_parent: - return + current_idxs = [] + current_max_idxs = [] + for parent_item in parent_items: + current_max_idxs.append(parent_item.rowCount()) + if not isinstance(parent_item, ProjectItem): + current_idxs.append(parent_item.row()) + current_idxs.append(src_parent.row()) + indexes_len = len(current_idxs) + + while True: + def _update_parents(idx, top=True): + if idx < 0: + return False + + if current_max_idxs[idx] == current_idxs[idx]: + if not _update_parents(idx - 1, False): + return False + + parent = parent_items[idx] + row_count = 0 + if parent is not None: + row_count = parent.rowCount() + current_max_idxs[idx] = row_count + current_idxs[idx] = 0 + return True + + if top: + return True + + current_idxs[idx] += 1 + parent_item = parent_items[idx] + new_item = parent_item.child(current_idxs[idx]) + parent_items[idx + 1] = new_item - new_parent = parent_parent.child(src_parent.row() + 1) - if not new_parent: + return True + + updated = _update_parents(indexes_len - 1) + if not updated: return - dst_parent = new_parent + + start = current_idxs[-1] + end = current_max_idxs[-1] + current_idxs[-1] = current_max_idxs[-1] + parent = parent_items[-1] + for row in range(start, end): + child_item = parent.child(row) + if ( + child_item is src_parent + or child_item.data(REMOVED_ROLE) + or not isinstance(child_item, AssetItem) + ): + continue + + dst_parent = child_item + destination_row = 0 + break + + if dst_parent is not None: + break # Up elif direction == -1: @@ -945,20 +985,19 @@ def _update_parents(_current_idx, top=True): if dst_parent is not None: break - if dst_parent_index is None: - dst_parent_index = self.index_from_item( - dst_parent.row(), 0, dst_parent.parent() - ) + if dst_parent is None: + return - if _destination_row is None: - _destination_row = destination_row + dst_parent_index = self.index_from_item( + dst_parent.row(), 0, dst_parent.parent() + ) self.beginMoveRows( src_parent_index, source_row, source_row, dst_parent_index, - _destination_row + destination_row ) if src_parent is dst_parent: @@ -973,7 +1012,7 @@ def _update_parents(_current_idx, top=True): self.endMoveRows() new_index = self.index( - _destination_row, index.column(), dst_parent_index + destination_row, index.column(), dst_parent_index ) self.index_moved.emit(new_index) From d3f66d28d6b352347d809c801c86b8e1a9315067 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 12:10:24 +0200 Subject: [PATCH 135/219] easier horizontal movement --- .../tools/project_manager/project_manager/model.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 86f889ae95d..98952c8b73c 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -680,7 +680,6 @@ def _move_horizontal_single(self, index, direction): dst_row = None dst_parent = None - dst_parent_index = None if direction == -1: if isinstance(src_parent, (RootItem, ProjectItem)): @@ -734,10 +733,9 @@ def _move_horizontal_single(self, index, direction): ): return - if dst_parent_index is None: - dst_parent_index = self.index_from_item( - dst_parent.row(), 0, dst_parent.parent() - ) + dst_parent_index = self.index_from_item( + dst_parent.row(), 0, dst_parent.parent() + ) self.beginMoveRows( src_parent_index, @@ -753,7 +751,8 @@ def _move_horizontal_single(self, index, direction): self.endMoveRows() - self.index_moved.emit(index) + new_index = self.index(dst_row, index.column(), dst_parent_index) + self.index_moved.emit(new_index) def move_horizontal(self, indexes, direction): if not indexes: From e999b1a8c4c2e854f6f0a382aef5e25f69c93542 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 12:10:37 +0200 Subject: [PATCH 136/219] fix task remove and name validations --- openpype/tools/project_manager/project_manager/model.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 98952c8b73c..8c70c4e9fe1 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1650,13 +1650,14 @@ def _add_task(self, item): self._duplicated_task_names.add(name) for _item in self._task_items_by_name[name]: _item.setData(True, DUPLICATED_ROLE) + elif item.data(DUPLICATED_ROLE): + item.setData(False, DUPLICATED_ROLE) def _remove_task(self, item): item_id = item.data(IDENTIFIER_ROLE) - name = self._task_name_by_item_id[item_id] - self._task_name_by_item_id.pop(item_id) - self._task_items_by_name[name].append(item) + name = self._task_name_by_item_id.pop(item_id) + self._task_items_by_name[name].remove(item) if not self._task_items_by_name[name]: self._task_items_by_name.pop(name) From 8a1028912bfdcbc02c13e2890b71ddaca8880618 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 12:43:29 +0200 Subject: [PATCH 137/219] return no flags if item is None --- openpype/tools/project_manager/project_manager/model.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 8c70c4e9fe1..20162847f29 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -313,6 +313,8 @@ def headerData(self, section, orientation, role): def flags(self, index): item = index.internalPointer() + if item is None: + return QtCore.Qt.NoItemFlags column = index.column() key = self.columns[column] return item.flags(key) From 9268f5b8e05565411dfd06d2acd1f8f0572e8462 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 12:56:02 +0200 Subject: [PATCH 138/219] jump from type to name index on type commit --- openpype/tools/project_manager/project_manager/view.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 32eff232096..a1763d3067a 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -162,7 +162,15 @@ def commitData(self, editor): column = current_index.column() row = current_index.row() skipped_index = None - if column > 0: + # Change column from "type" to "name" + if column == 1: + new_index = self._source_model.index( + current_index.row(), + 0, + current_index.parent() + ) + self.setCurrentIndex(new_index) + elif column > 0: indexes = [] for index in self.selectedIndexes(): if index.column() == column: From 9042e6689437f187ae4e0755196e4531856ae417 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 12:56:36 +0200 Subject: [PATCH 139/219] change method name to match plural --- openpype/tools/project_manager/project_manager/view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index a1763d3067a..c1f416a484e 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -238,7 +238,7 @@ def mousePressEvent(self, event): def keyPressEvent(self, event): call_super = False if event.key() == QtCore.Qt.Key_Delete: - self._delete_item() + self._delete_items() elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): mdfs = event.modifiers() @@ -267,7 +267,7 @@ def keyPressEvent(self, event): else: event.accept() - def _delete_item(self, indexes=None): + def _delete_items(self, indexes=None): if indexes is None: indexes = self.selectedIndexes() self._source_model.remove_indexes(indexes) From f6019b2bb14ecdeccc95793d3bfddd35136981e5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 13:53:10 +0200 Subject: [PATCH 140/219] added collapse and expand actions --- .../project_manager/project_manager/view.py | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index c1f416a484e..042fae6074c 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -1,4 +1,6 @@ -from Qt import QtWidgets, QtCore +from queue import Queue + +from Qt import QtWidgets, QtCore, QtGui from .delegates import ( NumberDelegate, @@ -357,6 +359,52 @@ def _on_enter_pressed(self): def _remove_delete_flag(self, item_ids): self._source_model.remove_delete_flag(item_ids) + def _expand_items(self, indexes): + item_ids = set() + process_queue = Queue() + for index in indexes: + if index.column() == 0: + process_queue.put(index) + + while not process_queue.empty(): + index = process_queue.get() + item_id = index.data(IDENTIFIER_ROLE) + if item_id in item_ids: + continue + item_ids.add(item_id) + + item = self._source_model._items_by_id[item_id] + if not self.isExpanded(index): + self.expand(index) + + for row in range(item.rowCount()): + process_queue.put(self._source_model.index( + row, 0, index + )) + + def _collapse_items(self, indexes): + item_ids = set() + process_queue = Queue() + for index in indexes: + if index.column() == 0: + process_queue.put(index) + + while not process_queue.empty(): + index = process_queue.get() + item_id = index.data(IDENTIFIER_ROLE) + if item_id in item_ids: + continue + item_ids.add(item_id) + + item = self._source_model._items_by_id[item_id] + if self.isExpanded(index): + self.collapse(index) + + for row in range(item.rowCount()): + process_queue.put(self._source_model.index( + row, 0, index + )) + def _on_context_menu(self, point): index = self.indexAt(point) column = index.column() @@ -408,6 +456,27 @@ def _on_context_menu(self, point): ) actions.append(action) + # Collapse/Expand action + show_collapse_expand_action = False + for item_id in item_ids: + item = items_by_id[item_ids[0]] + item_type = item.data(ITEM_TYPE_ROLE) + if item_type != "task": + show_collapse_expand_action = True + break + + if show_collapse_expand_action: + expand_action = QtWidgets.QAction("Expand all", context_menu) + collapse_action = QtWidgets.QAction("Collapse all", context_menu) + expand_action.triggered.connect( + lambda: self._expand_items(indexes) + ) + collapse_action.triggered.connect( + lambda: self._collapse_items(indexes) + ) + actions.append(expand_action) + actions.append(collapse_action) + if not actions: return From 64c6414367c14fe7b39cceff56e8547b590a478c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 14:17:40 +0200 Subject: [PATCH 141/219] implemented copy/paste of tasks --- .../project_manager/project_manager/model.py | 83 ++++++++++++++++++- .../project_manager/project_manager/view.py | 18 ++++ 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 20162847f29..4cb8ec6b90a 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1,5 +1,6 @@ import collections import copy +import json from queue import Queue from uuid import uuid4 @@ -1135,6 +1136,75 @@ def save(self): self.refresh_project() + def copy_mime_data(self, indexes): + items = [] + processed_ids = set() + for index in indexes: + if not index.isValid(): + continue + item_id = index.data(IDENTIFIER_ROLE) + if item_id in processed_ids: + continue + processed_ids.add(item_id) + item = self._items_by_id[item_id] + items.append(item) + + parent_item = None + for item in items: + if not isinstance(item, TaskItem): + raise ValueError("Can copy only tasks") + + if parent_item is None: + parent_item = item.parent() + elif item.parent() is not parent_item: + raise ValueError("Can copy only tasks from same parent") + + data = [] + for task_item in items: + data.append(task_item.to_json_data()) + + encoded_data = QtCore.QByteArray() + stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.WriteOnly) + stream.writeQString(json.dumps(data)) + mimedata = QtCore.QMimeData() + mimedata.setData("application/copy_task", encoded_data) + return mimedata + + def paste_mime_data(self, index, mime_data): + if not index.isValid(): + return + + item_id = index.data(IDENTIFIER_ROLE) + item = self._items_by_id[item_id] + if not isinstance(item, (AssetItem, TaskItem)): + return + + raw_data = mime_data.data("application/copy_task") + encoded_data = QtCore.QByteArray.fromRawData(raw_data) + stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.ReadOnly) + text = stream.readQString() + try: + data = json.loads(text) + except Exception: + data = [] + + if not data: + return + + if isinstance(item, TaskItem): + parent = item.parent() + else: + parent = item + + for task_item_data in data: + task_data = {} + for name, data in task_item_data.items(): + task_data = data + task_data["name"] = name + + task_item = TaskItem(task_data, True) + self.add_item(task_item, parent) + class BaseItem: columns = [] @@ -1730,9 +1800,11 @@ class TaskItem(BaseItem): "type" } - def __init__(self, data=None): + def __init__(self, data=None, is_new=None): self._removed = False - self._is_new = data is None + if is_new is None: + is_new = data is None + self._is_new = is_new if data is None: data = {} @@ -1801,3 +1873,10 @@ def setData(self, value, role, key=None): self.parent().on_task_name_change(self) return result + + def to_json_data(self): + """Convert json data without parent reference. + + Method used for mime data on copy/paste + """ + return self.to_doc_data() diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 042fae6074c..236a62425bd 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -242,6 +242,12 @@ def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key_Delete: self._delete_items() + elif event.matches(QtGui.QKeySequence.Copy): + self._copy_items() + + elif event.matches(QtGui.QKeySequence.Paste): + self._paste_items() + elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): mdfs = event.modifiers() if mdfs == (QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier): @@ -269,6 +275,18 @@ def keyPressEvent(self, event): else: event.accept() + def _copy_items(self, indexes=None): + if indexes is None: + indexes = self.selectedIndexes() + mime_data = self._source_model.copy_mime_data(indexes) + + QtWidgets.QApplication.clipboard().setMimeData(mime_data) + + def _paste_items(self): + index = self.currentIndex() + mime_data = QtWidgets.QApplication.clipboard().mimeData() + self._source_model.paste_mime_data(index, mime_data) + def _delete_items(self, indexes=None): if indexes is None: indexes = self.selectedIndexes() From 6f68b9599121260d15dffdb39bc4fb5cb9a75ad4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 14:25:21 +0200 Subject: [PATCH 142/219] added basic message showing --- .../project_manager/project_manager/view.py | 23 ++++++++++++++----- .../project_manager/project_manager/window.py | 8 +++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 236a62425bd..37c3423de53 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -98,9 +98,12 @@ class HierarchyView(QtWidgets.QTreeView): "tools_env" } - def __init__(self, dbcon, source_model, *args, **kwargs): - super(HierarchyView, self).__init__(*args, **kwargs) + def __init__(self, dbcon, source_model, parent): + super(HierarchyView, self).__init__(parent) + # Direct access to model self._source_model = source_model + # Access to parent because of `show_message` method + self._parent = parent project_doc_cache = ProjectDocCache(dbcon) tools_cache = ToolsCache() @@ -276,11 +279,15 @@ def keyPressEvent(self, event): event.accept() def _copy_items(self, indexes=None): - if indexes is None: - indexes = self.selectedIndexes() - mime_data = self._source_model.copy_mime_data(indexes) + try: + if indexes is None: + indexes = self.selectedIndexes() + mime_data = self._source_model.copy_mime_data(indexes) - QtWidgets.QApplication.clipboard().setMimeData(mime_data) + QtWidgets.QApplication.clipboard().setMimeData(mime_data) + self._show_message("Tasks copied") + except ValueError as exc: + self._show_message(str(exc)) def _paste_items(self): index = self.currentIndex() @@ -423,6 +430,10 @@ def _collapse_items(self, indexes): row, 0, index )) + def _show_message(self, message): + """Show message to user.""" + self._parent.show_message(message) + def _on_context_menu(self, point): index = self.indexAt(point) column = index.column() diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index f2ad399ab5f..d98cc0f8010 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -52,10 +52,12 @@ def __init__(self, parent=None): ) buttons_widget = QtWidgets.QWidget(self) + message_label = QtWidgets.QLabel(buttons_widget) save_btn = QtWidgets.QPushButton("Save", buttons_widget) buttons_layout = QtWidgets.QHBoxLayout(buttons_widget) buttons_layout.setContentsMargins(0, 0, 0, 0) + buttons_layout.addWidget(message_label) buttons_layout.addStretch(1) buttons_layout.addWidget(save_btn) @@ -74,6 +76,8 @@ def __init__(self, parent=None): self.hierarchy_view = hierarchy_view self.hierarchy_model = hierarchy_model + self.message_label = message_label + self.resize(1200, 600) self.refresh_projects() @@ -106,3 +110,7 @@ def _on_project_refresh(self): def _on_save_click(self): self.hierarchy_model.save() + + def show_message(self, message): + # TODO add nicer message pop + self.message_label.setText(message) From 2d4a2188f21759ef2d2ecf216f7017acf4b3d4e1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 14:42:57 +0200 Subject: [PATCH 143/219] fix variable usage --- openpype/tools/project_manager/project_manager/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 37c3423de53..cd6ee005dc5 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -488,7 +488,7 @@ def _on_context_menu(self, point): # Collapse/Expand action show_collapse_expand_action = False for item_id in item_ids: - item = items_by_id[item_ids[0]] + item = items_by_id[item_id] item_type = item.data(ITEM_TYPE_ROLE) if item_type != "task": show_collapse_expand_action = True From 275bf78be28dc381051461c1ddfe077e3288c88e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 14:43:14 +0200 Subject: [PATCH 144/219] faster remove deletion tag --- .../project_manager/project_manager/model.py | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 4cb8ec6b90a..530fee3528a 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -441,43 +441,46 @@ def add_item(self, item, parent=None, row=None): return None def remove_delete_flag(self, item_ids, with_children=True): - remove_tag_items_by_id = {} + items_by_id = {} for item_id in item_ids: - item = self.items_by_id[item_id] - if not isinstance(item, (AssetItem, TaskItem)): + if item_id in items_by_id: continue - if item.data(REMOVED_ROLE): - remove_tag_items_by_id[item_id] = item + item = self.items_by_id[item_id] + if isinstance(item, (AssetItem, TaskItem)): + items_by_id[item_id] = item - for item in tuple(remove_tag_items_by_id.values()): + for item in tuple(items_by_id.values()): parent = item.parent() while True: if not isinstance(parent, (AssetItem, TaskItem)): break - if parent.id in remove_tag_items_by_id: - continue - - if parent.data(REMOVED_ROLE): - remove_tag_items_by_id[parent.id] = parent + if parent.id not in items_by_id: + items_by_id[parent.id] = parent parent = parent.parent() if not with_children: continue - def _children_recursion(_item, store_obj): - if isinstance(_item, AssetItem): - for row in range(_item.rowCount()): - _child_item = _item.child(row) - if _child_item.id not in store_obj: - store_obj[_child_item.id] = _child_item - _children_recursion(_child_item, store_obj) - _children_recursion(item, remove_tag_items_by_id) - - for item in remove_tag_items_by_id.values(): - item.setData(False, REMOVED_ROLE) + def _children_recursion(_item): + if not isinstance(_item, AssetItem): + return + + for row in range(_item.rowCount()): + _child_item = _item.child(row) + if _child_item.id in items_by_id: + continue + + items_by_id[_child_item.id] = _child_item + _children_recursion(_child_item) + + _children_recursion(item) + + for item in items_by_id.values(): + if item.data(REMOVED_ROLE): + item.setData(False, REMOVED_ROLE) def remove_index(self, index): return self.remove_indexes([index]) From bc3c392ba59ef2d2bfa95fb055c03fd414807c2b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 15:36:53 +0200 Subject: [PATCH 145/219] renamed remove_index to delete_index --- openpype/tools/project_manager/project_manager/model.py | 6 +++--- openpype/tools/project_manager/project_manager/view.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 530fee3528a..9525d808f84 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -482,10 +482,10 @@ def _children_recursion(_item): if item.data(REMOVED_ROLE): item.setData(False, REMOVED_ROLE) - def remove_index(self, index): - return self.remove_indexes([index]) + def delete_indexe(self, index): + return self.delete_indexes([index]) - def remove_indexes(self, indexes): + def delete_indexes(self, indexes): items_by_id = {} processed_ids = set() for index in indexes: diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index cd6ee005dc5..7a5cdb72dab 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -297,7 +297,7 @@ def _paste_items(self): def _delete_items(self, indexes=None): if indexes is None: indexes = self.selectedIndexes() - self._source_model.remove_indexes(indexes) + self._source_model.delete_indexes(indexes) def _on_ctrl_shift_enter_pressed(self): self._add_task() From 890f612c2eb620dfbb0aad152721747b84245f9a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 16:14:46 +0200 Subject: [PATCH 146/219] fixed typo --- openpype/tools/project_manager/project_manager/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 9525d808f84..00cd22c720d 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -482,7 +482,7 @@ def _children_recursion(_item): if item.data(REMOVED_ROLE): item.setData(False, REMOVED_ROLE) - def delete_indexe(self, index): + def delete_index(self, index): return self.delete_indexes([index]) def delete_indexes(self, indexes): From 6b2548bc00394109680450d5796269ac70b343b1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 16:15:17 +0200 Subject: [PATCH 147/219] implemented new item AddAssetItem --- .../project_manager/project_manager/model.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 00cd22c720d..575aba8394a 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1472,6 +1472,65 @@ def flags(self, *args, **kwargs): return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable +class AddAssetItem(BaseItem): + item_type = "add_asset" + columns = {"name"} + editable_columns = {"name"} + + def __init__(self, parent): + super(AddAssetItem, self).__init__() + self._parent = parent + + @classmethod + def name_icon(cls): + if cls._name_icon is None: + cls._name_icon = qtawesome.icon( + "fa.plus-circle", + color="#333333" + ) + return cls._name_icon + + def data(self, role, key=None): + if role == REMOVED_ROLE: + return True + + if role == HIERARCHY_CHANGE_ABLE_ROLE: + return True + + if key == "name": + if role == QtCore.Qt.DisplayRole: + return "Add Asset" + elif role == QtCore.Qt.EditRole: + return "" + return super(AddAssetItem, self).data(role, key) + + def setData(self, value, role, key=None): + if key == "name": + if not value: + return False + index = self.model().index_for_item(self) + new_index = self.model().add_new_asset(index) + self.model().setData(new_index, value, QtCore.Qt.EditRole) + return True + return super(AddAssetItem, self).setData(value, role, key) + + def flags(self, key): + if key != "name": + return QtCore.Qt.NoItemFlags + + return ( + QtCore.Qt.ItemIsEnabled + | QtCore.Qt.ItemIsSelectable + | QtCore.Qt.ItemIsEditable + ) + + def add_child(self, item, row=None): + raise AssertionError("BUG: Can't add children to AddAssetItem") + + def remove_child(self, item): + raise AssertionError("BUG: Can't remove children from AddAssetItem") + + class AssetItem(BaseItem): item_type = "asset" From c1a7a18cea14bf50d3fd35793b7a7ec72652f3d7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 16:17:24 +0200 Subject: [PATCH 148/219] each AssetItem and ProjectItem have AddAssetItem --- openpype/tools/project_manager/project_manager/model.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 575aba8394a..89f20311ad1 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1217,6 +1217,7 @@ class BaseItem: _name_icon = None _is_duplicated = False item_type = "base" + add_asset_item_visible = False _None = object() @@ -1435,6 +1436,8 @@ class ProjectItem(BaseItem): def __init__(self, project_doc): self._mongo_id = project_doc["_id"] + self.add_asset_item = AddAssetItem(self) + data = self.data_from_doc(project_doc) super(ProjectItem, self).__init__(data) @@ -1590,6 +1593,9 @@ def __init__(self, asset_doc): self.mongo_id = asset_doc.get("_id") self._project_id = None + self.add_asset_item_visible = False + self.add_asset_item = AddAssetItem(self) + # Item data self._hierarchy_changes_enabled = True self._removed = False From c4854c02ddbbcaef3e9de9b1bc51e1296f99e503 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 16:18:25 +0200 Subject: [PATCH 149/219] make sure AddAsseItem is visible --- .../tools/project_manager/project_manager/model.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 89f20311ad1..24e766671fc 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -208,11 +208,9 @@ def set_project(self, project_name): non_modifiable_items = set() while not appending_queue.empty(): parent_id, parent_item = appending_queue.get() - if parent_id not in asset_docs_by_parent_id: - continue + asset_docs = asset_docs_by_parent_id.get(parent_id) or [] new_items = [] - asset_docs = asset_docs_by_parent_id[parent_id] for asset_doc in sorted(asset_docs, key=lambda item: item["name"]): # Create new Item new_item = AssetItem(asset_doc) @@ -228,7 +226,11 @@ def set_project(self, project_name): # Add item to appending queue appending_queue.put((asset_id, new_item)) - self.add_items(new_items, parent_item) + if isinstance(parent_item, (ProjectItem, AssetItem)): + new_items.append(parent_item.add_asset_item) + + if new_items: + self.add_items(new_items, parent_item) # Handle Asset's that are not modifiable # - pass the information to all it's parents @@ -262,6 +264,7 @@ def set_project(self, project_name): _task_data["name"] = task_name task_item = TaskItem(_task_data) task_items.append(task_item) + self.add_items(task_items, asset_item) def rowCount(self, parent=None): @@ -379,6 +382,9 @@ def add_new_asset(self, source_index): if result is not None: self._validate_asset_duplicity(name) + if not new_child.add_asset_item_visible: + self.add_item(new_child.add_asset_item, new_child) + return result def add_new_task(self, parent_index): From 2e703231668acf17889e7bf572b82bb403d0891d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 16:19:13 +0200 Subject: [PATCH 150/219] fix model method on items --- openpype/tools/project_manager/project_manager/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 24e766671fc..1117e505ad5 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1252,7 +1252,7 @@ def is_valid(self): return not self._is_duplicated def model(self): - return self._parent.model + return self._parent.model() def move_to(self, item, row): idx = self._children.index(item) From 9e40246514b2d8dbfa204a45959c1c107ea33617 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 16:19:42 +0200 Subject: [PATCH 151/219] AddAssetItem are skipped on remove --- openpype/tools/project_manager/project_manager/model.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 1117e505ad5..fea76367a0d 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -504,7 +504,7 @@ def delete_indexes(self, indexes): processed_ids.add(item_id) item = self._items_by_id[item_id] - if isinstance(item, (TaskItem, AssetItem)): + if isinstance(item, (TaskItem, AssetItem, AddAssetItem)): items_by_id[item_id] = item if not items_by_id: @@ -514,6 +514,9 @@ def delete_indexes(self, indexes): self._remove_item(item) def _remove_item(self, item): + if isinstance(item, AddAssetItem): + return + is_removed = item.data(REMOVED_ROLE) if is_removed: return @@ -544,6 +547,9 @@ def _fill_children(_all_descendants, cur_item, parent_item=None): task_children.append(child_item) continue + elif isinstance(child_item, AddAssetItem): + continue + if not _fill_children(_all_descendants, child_item, cur_item): remove_item = False From 8493274f5c26a2bca3638e080baf0f4b627f5977 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 16:20:22 +0200 Subject: [PATCH 152/219] AssetItem and ProjectItem know if it's add asset item is visible --- .../project_manager/project_manager/model.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index fea76367a0d..67a3157cb2e 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1486,6 +1486,18 @@ def data_from_doc(cls, project_doc): def flags(self, *args, **kwargs): return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + def add_child(self, item, row=None): + if not self.add_asset_item_visible and item is self.add_asset_item: + self.add_asset_item_visible = True + + super(ProjectItem, self).add_child(item, row) + + def remove_child(self, item): + if self.add_asset_item_visible and item is self.add_asset_item: + self.add_asset_item_visible = False + + super(ProjectItem, self).remove_child(item) + class AddAssetItem(BaseItem): item_type = "add_asset" @@ -1855,14 +1867,20 @@ def add_child(self, item, row=None): super(AssetItem, self).add_child(item, row) - if isinstance(item, TaskItem): + if not self.add_asset_item_visible and item is self.add_asset_item: + self.add_asset_item_visible = True + + elif isinstance(item, TaskItem): self._add_task(item) def remove_child(self, item): if item not in self._children: return - if isinstance(item, TaskItem): + if self.add_asset_item_visible and item is self.add_asset_item: + self.add_asset_item_visible = False + + elif isinstance(item, TaskItem): self._remove_task(item) super(AssetItem, self).remove_child(item) From 1dd406722f963d1e44041f8aa53ae4ef32c57389 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 16:20:54 +0200 Subject: [PATCH 153/219] implemented delete_add_asset_item to remove AddAssetItem --- .../tools/project_manager/project_manager/model.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 67a3157cb2e..9aa553b8a10 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -488,6 +488,20 @@ def _children_recursion(_item): if item.data(REMOVED_ROLE): item.setData(False, REMOVED_ROLE) + def delete_add_asset_item(self, parent): + item = parent.add_asset_item + children = parent.children() + if item not in children: + return + parent_index = self.index_for_item(parent) + row = children.index(item) + + self.beginRemoveRows(parent_index, row, row) + + parent.remove_child(item) + + self.endRemoveRows() + def delete_index(self, index): return self.delete_indexes([index]) From a66a9245beb7ee6e08d0ca5bb93cd46afa9e79ba Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 16:21:26 +0200 Subject: [PATCH 154/219] use one row less if AddAssetItem is visible on item --- openpype/tools/project_manager/project_manager/model.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 9aa553b8a10..cc0cd1c3bdf 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -412,9 +412,13 @@ def add_items(self, items, parent=None, start_row=None): if start_row is None: start_row = parent.rowCount() + if parent.add_asset_item_visible and start_row == parent.rowCount(): + start_row -= 1 + end_row = start_row + len(items) - 1 parent_index = self.index_from_item(parent.row(), 0, parent.parent()) + self.beginInsertRows(parent_index, start_row, end_row) for idx, item in enumerate(items): @@ -755,6 +759,8 @@ def _move_horizontal_single(self, index, direction): return dst_row = dst_parent.rowCount() + if dst_parent.add_asset_item_visible: + dst_row -= 1 if src_parent is dst_parent: return @@ -1011,6 +1017,8 @@ def _update_parents(_current_idx, top=True): dst_parent = child_item destination_row = dst_parent.rowCount() + if dst_parent.add_asset_item_visible: + destination_row -= 1 break if dst_parent is not None: From 2e5bec225aa7f796d817f5e99df15edc5f452fd1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 16:22:02 +0200 Subject: [PATCH 155/219] AssetItem handle for adding removing AddAssetItem on change of removed role --- openpype/tools/project_manager/project_manager/model.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index cc0cd1c3bdf..79ef5e22e65 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1802,6 +1802,11 @@ def data(self, role, key=None): def setData(self, value, role, key=None): if role == REMOVED_ROLE: self._removed = value + if not value and not self.add_asset_item_visible: + self.model().add_item(self.add_asset_item, self) + elif value and self.add_asset_item_visible: + self.model().delete_add_asset_item(self) + return True if role == HIERARCHY_CHANGE_ABLE_ROLE: From 2a4b4ebdd24a5a38e476039bde290aebe686c667 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 16:26:21 +0200 Subject: [PATCH 156/219] HierarchicalModel has project_item property --- .../tools/project_manager/project_manager/model.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 79ef5e22e65..abacac0d321 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -138,6 +138,16 @@ def refresh_project(self): self._current_project = None self.set_project(project_name) + @property + def project_item(self): + output = None + for row in range(self._root_item.rowCount()): + item = self._root_item.child(row) + if isinstance(item, ProjectItem): + output = item + break + return output + def set_project(self, project_name): if self._current_project == project_name: return From 300c7a2c308f95ef0969e763004fb4575ab2c3a7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 16:26:45 +0200 Subject: [PATCH 157/219] HierarchicalView collapse all except project on project load --- openpype/tools/project_manager/project_manager/view.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 7a5cdb72dab..b2f21aaffd2 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -156,6 +156,13 @@ def set_project(self, project_name): # Trigger update of model after all data for delegates are filled self._source_model.set_project(project_name) + self.collapseAll() + + project_item = self._source_model.project_item + if project_item: + index = self._source_model.index_for_item(project_item) + self.expand(index) + def _on_rows_moved(self, index): parent_index = index.parent() if not self.isExpanded(parent_index): From f053323d2e1368d87bca9b9c12787f8c418c3441 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 16:38:26 +0200 Subject: [PATCH 158/219] keep name resized to content --- .../project_manager/project_manager/window.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index d98cc0f8010..25cb7c9975f 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -47,9 +47,17 @@ def __init__(self, parent=None): header = hierarchy_view.header() header.setStretchLastSection(False) - header.setSectionResizeMode( - header.logicalIndex(0), QtWidgets.QHeaderView.Stretch - ) + for idx in range(header.count()): + logical_index = header.logicalIndex(idx) + if idx == 0: + header.setSectionResizeMode( + logical_index, QtWidgets.QHeaderView.Stretch + ) + else: + header.setSectionResizeMode( + logical_index, QtWidgets.QHeaderView.ResizeToContents + ) + buttons_widget = QtWidgets.QWidget(self) message_label = QtWidgets.QLabel(buttons_widget) From 8db22fbb9870397f48aec54ae499d62dbf1e05b0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 17:56:05 +0200 Subject: [PATCH 159/219] added initial styles --- .../project_manager/style/__init__.py | 13 +++++++++++++ .../project_manager/project_manager/style/style.css | 5 +++++ .../tools/project_manager/project_manager/window.py | 2 ++ 3 files changed, 20 insertions(+) create mode 100644 openpype/tools/project_manager/project_manager/style/__init__.py create mode 100644 openpype/tools/project_manager/project_manager/style/style.css diff --git a/openpype/tools/project_manager/project_manager/style/__init__.py b/openpype/tools/project_manager/project_manager/style/__init__.py new file mode 100644 index 00000000000..5a57642ee18 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/style/__init__.py @@ -0,0 +1,13 @@ +import os +from openpype import resources + + +def load_stylesheet(): + style_path = os.path.join(os.path.dirname(__file__), "style.css") + with open(style_path, "r") as style_file: + stylesheet = style_file.read() + return stylesheet + + +def app_icon_path(): + return resources.pype_icon_filepath() diff --git a/openpype/tools/project_manager/project_manager/style/style.css b/openpype/tools/project_manager/project_manager/style/style.css new file mode 100644 index 00000000000..6730342b30b --- /dev/null +++ b/openpype/tools/project_manager/project_manager/style/style.css @@ -0,0 +1,5 @@ +QTreeView::item { + padding-top: 3px; + padding-bottom: 3px; + padding-right: 3px; +} diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 25cb7c9975f..bfc76b2bf7a 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -7,6 +7,7 @@ HierarchySelectionModel, HierarchyView ) +from .style import load_stylesheet from avalon.api import AvalonMongoDB @@ -87,6 +88,7 @@ def __init__(self, parent=None): self.message_label = message_label self.resize(1200, 600) + self.setStyleSheet(load_stylesheet()) self.refresh_projects() From 5390658044e58334570e53a4499a0c10337e9ff3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 18:13:57 +0200 Subject: [PATCH 160/219] removed AddAssetItem --- .../project_manager/project_manager/model.py | 124 +----------------- 1 file changed, 3 insertions(+), 121 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index abacac0d321..621521b68b8 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -236,9 +236,6 @@ def set_project(self, project_name): # Add item to appending queue appending_queue.put((asset_id, new_item)) - if isinstance(parent_item, (ProjectItem, AssetItem)): - new_items.append(parent_item.add_asset_item) - if new_items: self.add_items(new_items, parent_item) @@ -392,9 +389,6 @@ def add_new_asset(self, source_index): if result is not None: self._validate_asset_duplicity(name) - if not new_child.add_asset_item_visible: - self.add_item(new_child.add_asset_item, new_child) - return result def add_new_task(self, parent_index): @@ -422,9 +416,6 @@ def add_items(self, items, parent=None, start_row=None): if start_row is None: start_row = parent.rowCount() - if parent.add_asset_item_visible and start_row == parent.rowCount(): - start_row -= 1 - end_row = start_row + len(items) - 1 parent_index = self.index_from_item(parent.row(), 0, parent.parent()) @@ -502,20 +493,6 @@ def _children_recursion(_item): if item.data(REMOVED_ROLE): item.setData(False, REMOVED_ROLE) - def delete_add_asset_item(self, parent): - item = parent.add_asset_item - children = parent.children() - if item not in children: - return - parent_index = self.index_for_item(parent) - row = children.index(item) - - self.beginRemoveRows(parent_index, row, row) - - parent.remove_child(item) - - self.endRemoveRows() - def delete_index(self, index): return self.delete_indexes([index]) @@ -532,7 +509,7 @@ def delete_indexes(self, indexes): processed_ids.add(item_id) item = self._items_by_id[item_id] - if isinstance(item, (TaskItem, AssetItem, AddAssetItem)): + if isinstance(item, (TaskItem, AssetItem)): items_by_id[item_id] = item if not items_by_id: @@ -542,9 +519,6 @@ def delete_indexes(self, indexes): self._remove_item(item) def _remove_item(self, item): - if isinstance(item, AddAssetItem): - return - is_removed = item.data(REMOVED_ROLE) if is_removed: return @@ -575,9 +549,6 @@ def _fill_children(_all_descendants, cur_item, parent_item=None): task_children.append(child_item) continue - elif isinstance(child_item, AddAssetItem): - continue - if not _fill_children(_all_descendants, child_item, cur_item): remove_item = False @@ -769,8 +740,6 @@ def _move_horizontal_single(self, index, direction): return dst_row = dst_parent.rowCount() - if dst_parent.add_asset_item_visible: - dst_row -= 1 if src_parent is dst_parent: return @@ -1027,8 +996,6 @@ def _update_parents(_current_idx, top=True): dst_parent = child_item destination_row = dst_parent.rowCount() - if dst_parent.add_asset_item_visible: - destination_row -= 1 break if dst_parent is not None: @@ -1261,7 +1228,6 @@ class BaseItem: _name_icon = None _is_duplicated = False item_type = "base" - add_asset_item_visible = False _None = object() @@ -1480,8 +1446,6 @@ class ProjectItem(BaseItem): def __init__(self, project_doc): self._mongo_id = project_doc["_id"] - self.add_asset_item = AddAssetItem(self) - data = self.data_from_doc(project_doc) super(ProjectItem, self).__init__(data) @@ -1518,76 +1482,8 @@ def data_from_doc(cls, project_doc): def flags(self, *args, **kwargs): return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable - def add_child(self, item, row=None): - if not self.add_asset_item_visible and item is self.add_asset_item: - self.add_asset_item_visible = True - - super(ProjectItem, self).add_child(item, row) - - def remove_child(self, item): - if self.add_asset_item_visible and item is self.add_asset_item: - self.add_asset_item_visible = False - - super(ProjectItem, self).remove_child(item) -class AddAssetItem(BaseItem): - item_type = "add_asset" - columns = {"name"} - editable_columns = {"name"} - - def __init__(self, parent): - super(AddAssetItem, self).__init__() - self._parent = parent - - @classmethod - def name_icon(cls): - if cls._name_icon is None: - cls._name_icon = qtawesome.icon( - "fa.plus-circle", - color="#333333" - ) - return cls._name_icon - - def data(self, role, key=None): - if role == REMOVED_ROLE: - return True - - if role == HIERARCHY_CHANGE_ABLE_ROLE: - return True - - if key == "name": - if role == QtCore.Qt.DisplayRole: - return "Add Asset" - elif role == QtCore.Qt.EditRole: - return "" - return super(AddAssetItem, self).data(role, key) - - def setData(self, value, role, key=None): - if key == "name": - if not value: - return False - index = self.model().index_for_item(self) - new_index = self.model().add_new_asset(index) - self.model().setData(new_index, value, QtCore.Qt.EditRole) - return True - return super(AddAssetItem, self).setData(value, role, key) - - def flags(self, key): - if key != "name": - return QtCore.Qt.NoItemFlags - - return ( - QtCore.Qt.ItemIsEnabled - | QtCore.Qt.ItemIsSelectable - | QtCore.Qt.ItemIsEditable - ) - - def add_child(self, item, row=None): - raise AssertionError("BUG: Can't add children to AddAssetItem") - - def remove_child(self, item): - raise AssertionError("BUG: Can't remove children from AddAssetItem") class AssetItem(BaseItem): @@ -1649,9 +1545,6 @@ def __init__(self, asset_doc): self.mongo_id = asset_doc.get("_id") self._project_id = None - self.add_asset_item_visible = False - self.add_asset_item = AddAssetItem(self) - # Item data self._hierarchy_changes_enabled = True self._removed = False @@ -1812,11 +1705,6 @@ def data(self, role, key=None): def setData(self, value, role, key=None): if role == REMOVED_ROLE: self._removed = value - if not value and not self.add_asset_item_visible: - self.model().add_item(self.add_asset_item, self) - elif value and self.add_asset_item_visible: - self.model().delete_add_asset_item(self) - return True if role == HIERARCHY_CHANGE_ABLE_ROLE: @@ -1904,20 +1792,14 @@ def add_child(self, item, row=None): super(AssetItem, self).add_child(item, row) - if not self.add_asset_item_visible and item is self.add_asset_item: - self.add_asset_item_visible = True - - elif isinstance(item, TaskItem): + if isinstance(item, TaskItem): self._add_task(item) def remove_child(self, item): if item not in self._children: return - if self.add_asset_item_visible and item is self.add_asset_item: - self.add_asset_item_visible = False - - elif isinstance(item, TaskItem): + if isinstance(item, TaskItem): self._remove_task(item) super(AssetItem, self).remove_child(item) From 4f2e82aeccd5efd40a59efaa555455d5aec8cd73 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 18:28:14 +0200 Subject: [PATCH 161/219] asset is created under passed asset --- .../tools/project_manager/project_manager/model.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 621521b68b8..79c0bc952fd 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -370,22 +370,20 @@ def add_new_asset(self, source_index): item_id = source_index.data(IDENTIFIER_ROLE) item = self.items_by_id[item_id] - new_row = None - name = None - asset_data = {} if isinstance(item, (RootItem, ProjectItem)): name = "ep" - parent = item + new_row = None else: - parent = item.parent() + name = None new_row = item.row() + 1 + asset_data = {} if name: asset_data["name"] = name new_child = AssetItem(asset_data) - result = self.add_item(new_child, parent, new_row) + result = self.add_item(new_child, item, new_row) if result is not None: self._validate_asset_duplicity(name) From 12a7ae0ace910e9e55fb2259b5dbb4d9e763d289 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 18:28:32 +0200 Subject: [PATCH 162/219] validate new name instead of passed name --- openpype/tools/project_manager/project_manager/model.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 79c0bc952fd..c365bb65f8b 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -385,7 +385,9 @@ def add_new_asset(self, source_index): result = self.add_item(new_child, item, new_row) if result is not None: - self._validate_asset_duplicity(name) + # WARNING Expecting result is index for column 0 ("name") + new_name = result.data(QtCore.Qt.DisplayRole) + self._validate_asset_duplicity(new_name) return result From 2ce685d3e391f92d4619e8dce67659b668b1e3bf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 18:29:11 +0200 Subject: [PATCH 163/219] asset name duplications are handled by item id --- .../project_manager/project_manager/model.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index c365bb65f8b..84b0c17e8c8 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -121,7 +121,7 @@ def __init__(self, dbcon, parent=None): self._current_project = None self._root_item = None self._items_by_id = {} - self._asset_items_by_name = collections.defaultdict(list) + self._asset_items_by_name = collections.defaultdict(set) self.dbcon = dbcon self._reset_root_item() @@ -431,7 +431,7 @@ def add_items(self, items, parent=None, start_row=None): if isinstance(item, AssetItem): name = item.data(QtCore.Qt.DisplayRole, "name") - self._asset_items_by_name[name].append(item) + self._asset_items_by_name[name].add(item.id) if item.id not in self._items_by_id: self._items_by_id[item.id] = item @@ -638,13 +638,13 @@ def _rename_asset(self, asset_item, new_name): if prev_name == new_name: return - self._asset_items_by_name[prev_name].remove(asset_item) + self._asset_items_by_name[prev_name].remove(asset_item.id) self._validate_asset_duplicity(prev_name) if new_name is None: return - self._asset_items_by_name[new_name].append(asset_item) + self._asset_items_by_name[new_name].add(asset_item.id) self._validate_asset_duplicity(new_name) @@ -652,15 +652,19 @@ def _validate_asset_duplicity(self, name): if name not in self._asset_items_by_name: return - items = self._asset_items_by_name[name] - if not items: + item_ids = self._asset_items_by_name[name] + if not item_ids: self._asset_items_by_name.pop(name) - elif len(items) == 1: - index = self.index_for_item(items[0]) + elif len(item_ids) == 1: + for item_id in item_ids: + item = self._items_by_id[item_id] + index = self.index_for_item(item) self.setData(index, False, DUPLICATED_ROLE) + else: - for item in items: + for item_id in item_ids: + item = self._items_by_id[item_id] index = self.index_for_item(item) self.setData(index, True, DUPLICATED_ROLE) From cc0cbbfade168cfab07e37b347ab334dbc325c89 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 18:29:47 +0200 Subject: [PATCH 164/219] small changes --- .../project_manager/project_manager/delegates.py | 5 +++++ .../project_manager/project_manager/model.py | 3 --- .../project_manager/project_manager/view.py | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index 4292224b093..c2b2d07b585 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -78,6 +78,8 @@ def createEditor(self, parent, option, index): editor = QtWidgets.QDoubleSpinBox(parent) else: editor = QtWidgets.QSpinBox(parent) + + editor.setObjectName("NumberEditor") editor.setMinimum(self.minimum) editor.setMaximum(self.maximum) @@ -97,6 +99,7 @@ def createEditor(self, parent, option, index): class NameDelegate(QtWidgets.QStyledItemDelegate): def createEditor(self, parent, option, index): editor = NameTextEdit(parent) + editor.setObjectName("NameEditor") value = index.data(QtCore.Qt.EditRole) if value is not None: editor.setText(str(value)) @@ -110,6 +113,7 @@ def __init__(self, project_doc_cache, *args, **kwargs): def createEditor(self, parent, option, index): editor = FilterComboBox(parent) + editor.setObjectName("TypeEditor") if not self._project_doc_cache.project_doc: return editor @@ -132,6 +136,7 @@ def __init__(self, tools_cache, *args, **kwargs): def createEditor(self, parent, option, index): editor = MultiSelectionComboBox(parent) + editor.setObjectName("ToolEditor") if not self._tools_cache.tools_data: return editor diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 84b0c17e8c8..171aecef7d5 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1487,9 +1487,6 @@ def flags(self, *args, **kwargs): return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable - - - class AssetItem(BaseItem): item_type = "asset" diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index b2f21aaffd2..8c7902cbd72 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -389,9 +389,16 @@ def _on_enter_pressed(self): self.edit(index) def _remove_delete_flag(self, item_ids): + """Remove deletion flag on items marked for deletion.""" self._source_model.remove_delete_flag(item_ids) def _expand_items(self, indexes): + """Expand multiple items with all it's children. + + Args: + indexes (list): List of QModelIndex that should be expanded. + """ + item_ids = set() process_queue = Queue() for index in indexes: @@ -415,6 +422,11 @@ def _expand_items(self, indexes): )) def _collapse_items(self, indexes): + """Collapse multiple items with all it's children. + + Args: + indexes (list): List of QModelIndex that should be collapsed. + """ item_ids = set() process_queue = Queue() for index in indexes: @@ -442,6 +454,10 @@ def _show_message(self, message): self._parent.show_message(message) def _on_context_menu(self, point): + """Context menu on right click. + + Currently is menu shown only on "name" column. + """ index = self.indexAt(point) column = index.column() if column != 0: From 03cb11d091a2e76925df30ee9f9d95ff77b97c6e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 18:34:52 +0200 Subject: [PATCH 165/219] hide spinbox buttons --- openpype/tools/project_manager/project_manager/delegates.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index c2b2d07b585..4c6f7f70eb8 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -82,6 +82,7 @@ def createEditor(self, parent, option, index): editor.setObjectName("NumberEditor") editor.setMinimum(self.minimum) editor.setMaximum(self.maximum) + editor.setButtonSymbols(QtWidgets.QSpinBox.NoButtons) value = index.data(QtCore.Qt.EditRole) if value is not None: From 3e58532a4252632677194630ffeda91584aeb76d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 20:01:28 +0200 Subject: [PATCH 166/219] added none project to project model --- openpype/tools/project_manager/project_manager/model.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 171aecef7d5..9d0edef886f 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -30,6 +30,11 @@ def refresh(self): self.dbcon.Session["AVALON_PROJECT"] = None project_items = [] + + none_project = QtGui.QStandardItem("< Select Project >") + none_project.setData(None) + project_items.append(none_project) + database = self.dbcon.database project_names = set() for project_name in database.collection_names(): From 68aa6b2aac0c31b213015016c8a10ea8cdb61f74 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 20:02:05 +0200 Subject: [PATCH 167/219] added cache of resources --- .../project_manager/style/__init__.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/style/__init__.py b/openpype/tools/project_manager/project_manager/style/__init__.py index 5a57642ee18..bf3a7398230 100644 --- a/openpype/tools/project_manager/project_manager/style/__init__.py +++ b/openpype/tools/project_manager/project_manager/style/__init__.py @@ -1,5 +1,73 @@ import os from openpype import resources +from avalon.vendor import qtawesome + + +class ResourceCache: + colors = { + "standard": "#333333", + "warning": "#ff0000", + "new": "#00ff00" + } + icons = None + + @classmethod + def get_icon(cls, *keys): + output = cls.get_icons() + for key in keys: + output = output[key] + return output + + @classmethod + def get_icons(cls): + if cls.icons is None: + cls.icons = { + "asset": { + "existing": qtawesome.icon( + "fa.folder", + color=cls.colors["standard"] + ), + "new": qtawesome.icon( + "fa.folder", + color=cls.colors["new"] + ), + "duplicated": qtawesome.icon( + "fa.folder", + color=cls.colors["warning"] + ), + "removed": qtawesome.icon( + "fa.trash", + color=cls.colors["warning"] + ) + }, + "task": { + "existing": qtawesome.icon( + "fa.check-circle-o", + color=cls.colors["standard"] + ), + "new": qtawesome.icon( + "fa.check-circle", + color=cls.colors["new"] + ), + "duplicated": qtawesome.icon( + "fa.check-circle", + color=cls.colors["warning"] + ), + "removed": qtawesome.icon( + "fa.trash", + color=cls.colors["warning"] + ) + }, + "refresh": qtawesome.icon( + "fa.refresh", + color=cls.colors["standard"] + ) + } + return cls.icons + + @classmethod + def get_color(cls, color_name): + return cls.colors[color_name] def load_stylesheet(): From 93c5f27e3afa4a7b2e8dbe229d9806806cfb47f8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 20:02:26 +0200 Subject: [PATCH 168/219] refresh button has icon --- .../tools/project_manager/project_manager/style/style.css | 4 ++++ openpype/tools/project_manager/project_manager/window.py | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/style/style.css b/openpype/tools/project_manager/project_manager/style/style.css index 6730342b30b..c62f0fcd814 100644 --- a/openpype/tools/project_manager/project_manager/style/style.css +++ b/openpype/tools/project_manager/project_manager/style/style.css @@ -3,3 +3,7 @@ QTreeView::item { padding-bottom: 3px; padding-right: 3px; } + +#RefreshBtn { + padding: 2px; +} diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index bfc76b2bf7a..cae605af4d6 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -7,7 +7,7 @@ HierarchySelectionModel, HierarchyView ) -from .style import load_stylesheet +from .style import load_stylesheet, ResourceCache from avalon.api import AvalonMongoDB @@ -27,7 +27,10 @@ def __init__(self, parent=None): project_combobox.setModel(project_model) project_combobox.setRootModelIndex(QtCore.QModelIndex()) - refresh_projects_btn = QtWidgets.QPushButton("Refresh", project_widget) + refresh_projects_btn = QtWidgets.QPushButton(project_widget) + refresh_projects_btn.setIcon(ResourceCache.get_icon("refresh")) + refresh_projects_btn.setToolTip("Refresh projects") + refresh_projects_btn.setObjectName("RefreshBtn") project_layout = QtWidgets.QHBoxLayout(project_widget) project_layout.setContentsMargins(0, 0, 0, 0) From a993504fa893c3788ae056d6cadb71495c92cabf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 20:02:37 +0200 Subject: [PATCH 169/219] changed order of refresh button --- openpype/tools/project_manager/project_manager/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index cae605af4d6..ae1ab70c708 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -34,8 +34,8 @@ def __init__(self, parent=None): project_layout = QtWidgets.QHBoxLayout(project_widget) project_layout.setContentsMargins(0, 0, 0, 0) - project_layout.addWidget(refresh_projects_btn, 0) project_layout.addWidget(project_combobox, 0) + project_layout.addWidget(refresh_projects_btn, 0) project_layout.addStretch(1) hierarchy_model = HierarchyModel(dbcon) From fe52e249d96ef6eb8744ab1e9fce833d00d277c0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 20:04:08 +0200 Subject: [PATCH 170/219] added delete action to context menu --- .../project_manager/project_manager/view.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 8c7902cbd72..85db6161a65 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -13,7 +13,8 @@ from .constants import ( REMOVED_ROLE, IDENTIFIER_ROLE, - ITEM_TYPE_ROLE + ITEM_TYPE_ROLE, + HIERARCHY_CHANGE_ABLE_ROLE ) @@ -497,9 +498,23 @@ def _on_context_menu(self, point): # Remove delete tag on items removed_item_ids = [] + show_delete_items = False for item_id, item in items_by_id.items(): if item.data(REMOVED_ROLE): removed_item_ids.append(item_id) + elif ( + not show_delete_items + and item.data(ITEM_TYPE_ROLE) != "project" + and item.data(HIERARCHY_CHANGE_ABLE_ROLE) + ): + show_delete_items = True + + if show_delete_items: + action = QtWidgets.QAction("Delete items", context_menu) + action.triggered.connect( + lambda: self._delete_items() + ) + actions.append(action) if removed_item_ids: action = QtWidgets.QAction("Keep items", context_menu) From 924dbe408c887fd00b0a63c6a0556a0e0467bdf5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 20:04:49 +0200 Subject: [PATCH 171/219] use icons defined in style --- .../project_manager/project_manager/model.py | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 9d0edef886f..d724b91dab3 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -11,6 +11,7 @@ HIERARCHY_CHANGE_ABLE_ROLE, REMOVED_ROLE ) +from .style import ResourceCache from pymongo import UpdateOne from avalon.vendor import qtawesome from Qt import QtCore, QtGui @@ -1234,7 +1235,7 @@ class BaseItem: # Use `set` for faster result editable_columns = set() - _name_icon = None + _name_icons = None _is_duplicated = False item_type = "base" @@ -1256,9 +1257,8 @@ def __init__(self, data=None): if key in self.columns: self._data[key] = value - @classmethod - def name_icon(cls): - return cls._name_icon + def name_icon(self): + return None @property def is_valid(self): @@ -1680,11 +1680,19 @@ def data_from_doc(cls, asset_doc): return data - @classmethod - def name_icon(cls): - if cls._name_icon is None: - cls._name_icon = qtawesome.icon("fa.folder", color="#333333") - return cls._name_icon + def name_icon(self): + if self.__class__._name_icons is None: + self.__class__._name_icons = ResourceCache.get_icons()["asset"] + + if self._removed: + icon_type = "removed" + elif self._is_duplicated: + icon_type = "duplicated" + elif self.is_new: + icon_type = "new" + else: + icon_type = "existing" + return self.__class__._name_icons[icon_type] def _get_global_data(self, role): if role == HIERARCHY_CHANGE_ABLE_ROLE: @@ -1838,11 +1846,19 @@ def __init__(self, data=None, is_new=None): def is_new(self): return self._is_new - @classmethod - def name_icon(cls): - if cls._name_icon is None: - cls._name_icon = qtawesome.icon("fa.file-o", color="#333333") - return cls._name_icon + def name_icon(self): + if self.__class__._name_icons is None: + self.__class__._name_icons = ResourceCache.get_icons()["task"] + + if self._removed: + icon_type = "removed" + elif self._is_duplicated: + icon_type = "duplicated" + elif self.is_new: + icon_type = "new" + else: + icon_type = "existing" + return self.__class__._name_icons[icon_type] def add_child(self, item, row=None): raise AssertionError("BUG: Can't add children to Task") From b388441342c44d5dc9268bac9cfbfa56734db0a7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 20:05:03 +0200 Subject: [PATCH 172/219] don't change background of items --- openpype/tools/project_manager/project_manager/model.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index d724b91dab3..44496288243 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1707,15 +1707,6 @@ def _get_global_data(self, role): ) return super(AssetItem, self)._get_global_data(role) - def data(self, role, key=None): - if role == QtCore.Qt.BackgroundRole: - if self._removed: - return QtGui.QColor(255, 0, 0, 127) - elif self.is_new: - return QtGui.QColor(0, 255, 0, 127) - - return super(AssetItem, self).data(role, key) - def setData(self, value, role, key=None): if role == REMOVED_ROLE: self._removed = value From 4f68e953d54d62c7747cbb0568d239af6dc0f25b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 20:38:46 +0200 Subject: [PATCH 173/219] a little bit faster expanding --- openpype/tools/project_manager/project_manager/view.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 85db6161a65..b7b984fccb2 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -400,12 +400,12 @@ def _expand_items(self, indexes): indexes (list): List of QModelIndex that should be expanded. """ - item_ids = set() process_queue = Queue() for index in indexes: if index.column() == 0: process_queue.put(index) + item_ids = set() while not process_queue.empty(): index = process_queue.get() item_id = index.data(IDENTIFIER_ROLE) @@ -413,11 +413,9 @@ def _expand_items(self, indexes): continue item_ids.add(item_id) - item = self._source_model._items_by_id[item_id] - if not self.isExpanded(index): - self.expand(index) + self.expand(index) - for row in range(item.rowCount()): + for row in range(self._source_model.rowCount(index)): process_queue.put(self._source_model.index( row, 0, index )) From b0f0f94f7708282f916cc3f8cfb246c2352d5cf9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 May 2021 21:25:30 +0200 Subject: [PATCH 174/219] expand/collapse event faster --- .../tools/project_manager/project_manager/view.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index b7b984fccb2..96e5be9a40b 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -1,3 +1,4 @@ +import collections from queue import Queue from Qt import QtWidgets, QtCore, QtGui @@ -399,13 +400,14 @@ def _expand_items(self, indexes): Args: indexes (list): List of QModelIndex that should be expanded. """ - process_queue = Queue() for index in indexes: if index.column() == 0: process_queue.put(index) item_ids = set() + # Use deque as expanding not visible items as first is faster + indexes_deque = collections.deque() while not process_queue.empty(): index = process_queue.get() item_id = index.data(IDENTIFIER_ROLE) @@ -413,13 +415,16 @@ def _expand_items(self, indexes): continue item_ids.add(item_id) - self.expand(index) + indexes_deque.append(index) for row in range(self._source_model.rowCount(index)): process_queue.put(self._source_model.index( row, 0, index )) + while indexes_deque: + self.expand(indexes_deque.pop()) + def _collapse_items(self, indexes): """Collapse multiple items with all it's children. @@ -439,11 +444,9 @@ def _collapse_items(self, indexes): continue item_ids.add(item_id) - item = self._source_model._items_by_id[item_id] - if self.isExpanded(index): - self.collapse(index) + self.collapse(index) - for row in range(item.rowCount()): + for row in range(self._source_model.rowCount(index)): process_queue.put(self._source_model.index( row, 0, index )) From 4439805536f8e42712f734dff62694ef8b2cf0d7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 10:38:30 +0200 Subject: [PATCH 175/219] fix adding new asset --- openpype/tools/project_manager/project_manager/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 44496288243..612411e60bf 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -381,7 +381,7 @@ def add_new_asset(self, source_index): new_row = None else: name = None - new_row = item.row() + 1 + new_row = item.rowCount() asset_data = {} if name: From 0f40eb699d0f7c83c6a6c79f17f671c45bf01838 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 10:38:51 +0200 Subject: [PATCH 176/219] all assets can be marked for deletion --- openpype/tools/project_manager/project_manager/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 612411e60bf..9f9c0814585 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -547,7 +547,7 @@ def _fill_children(_all_descendants, cur_item, parent_item=None): cur_item.setData(task_removed, REMOVED_ROLE) return task_removed - remove_item = cur_item.data(HIERARCHY_CHANGE_ABLE_ROLE) + remove_item = True task_children = [] for row in range(cur_item.rowCount()): child_item = cur_item.child(row) From 5a9b45f484292d50831f32362fcdd5b5e1c80041 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 10:39:35 +0200 Subject: [PATCH 177/219] assets without published content are removed from mongo --- .../project_manager/project_manager/model.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 9f9c0814585..4227771854d 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -12,7 +12,7 @@ REMOVED_ROLE ) from .style import ResourceCache -from pymongo import UpdateOne +from pymongo import UpdateOne, DeleteOne from avalon.vendor import qtawesome from Qt import QtCore, QtGui @@ -1133,10 +1133,15 @@ def save(self): insert_list.append(item) elif item.data(REMOVED_ROLE): - bulk_writes.append(UpdateOne( - {"_id": item.asset_id}, - {"$set": {"type": "archived_asset"}} - )) + if item.data(HIERARCHY_CHANGE_ABLE_ROLE): + bulk_writes.append(DeleteOne( + {"_id": item.asset_id} + )) + else: + bulk_writes.append(UpdateOne( + {"_id": item.asset_id}, + {"$set": {"type": "archived_asset"}} + )) else: update_data = item.update_data() From 80dbf6a1ce820ce7a32de67b428d7cebf6979bee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 10:58:43 +0200 Subject: [PATCH 178/219] add exclamation mark to duplicated --- .../tools/project_manager/project_manager/style/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/style/__init__.py b/openpype/tools/project_manager/project_manager/style/__init__.py index bf3a7398230..e7bb116843b 100644 --- a/openpype/tools/project_manager/project_manager/style/__init__.py +++ b/openpype/tools/project_manager/project_manager/style/__init__.py @@ -32,7 +32,7 @@ def get_icons(cls): color=cls.colors["new"] ), "duplicated": qtawesome.icon( - "fa.folder", + "fa.exclamation-triangle", color=cls.colors["warning"] ), "removed": qtawesome.icon( @@ -50,7 +50,7 @@ def get_icons(cls): color=cls.colors["new"] ), "duplicated": qtawesome.icon( - "fa.check-circle", + "fa.exclamation-circle", color=cls.colors["warning"] ), "removed": qtawesome.icon( From f85ecfaf060e8a925876fe98f7fe3f5336f92816 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 11:03:07 +0200 Subject: [PATCH 179/219] added one more level of asset/task creation --- .../project_manager/project_manager/view.py | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 96e5be9a40b..6bbae10f027 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -309,62 +309,68 @@ def _delete_items(self, indexes=None): self._source_model.delete_indexes(indexes) def _on_ctrl_shift_enter_pressed(self): - self._add_task() + self._add_task_and_edit() - def _add_task(self, parent_index=None): + def add_asset(self, parent_index=None): if parent_index is None: parent_index = self.currentIndex() if not parent_index.isValid(): return - new_index = self._source_model.add_new_task(parent_index) - if new_index is None: - return - # Stop editing self.setState(HierarchyView.NoState) QtWidgets.QApplication.processEvents() - # TODO change hardcoded column index to coded - task_type_index = self._source_model.index( - new_index.row(), 1, new_index.parent() - ) + return self._source_model.add_new_asset(parent_index) + + def add_task(self, parent_index=None): + if parent_index is None: + parent_index = self.currentIndex() + + if not parent_index.isValid(): + return + + return self._source_model.add_new_task(parent_index) + + def _add_asset_and_edit(self): + new_index = self.add_asset() + if new_index is None: + return + # Change current index self.selectionModel().setCurrentIndex( - task_type_index, + new_index, QtCore.QItemSelectionModel.Clear | QtCore.QItemSelectionModel.Select ) # Start editing - self.edit(task_type_index) - - def _add_asset(self, index=None): - if index is None: - index = self.currentIndex() + self.edit(new_index) - if not index.isValid(): + def _add_task_and_edit(self): + new_index = self.add_task() + if new_index is None: return # Stop editing self.setState(HierarchyView.NoState) QtWidgets.QApplication.processEvents() - new_index = self._source_model.add_new_asset(index) - if new_index is None: - return - + # TODO change hardcoded column index to coded + task_type_index = self._source_model.index( + new_index.row(), 1, new_index.parent() + ) # Change current index self.selectionModel().setCurrentIndex( - new_index, + task_type_index, QtCore.QItemSelectionModel.Clear | QtCore.QItemSelectionModel.Select ) # Start editing - self.edit(new_index) + self.edit(task_type_index) def _on_shift_enter_pressed(self): - self._add_asset() + self._add_asset_and_edit() def _on_up_ctrl_pressed(self): indexes = self.selectedIndexes() @@ -486,14 +492,14 @@ def _on_context_menu(self, point): if item_type in ("asset", "project"): add_asset_action = QtWidgets.QAction("Add Asset", context_menu) add_asset_action.triggered.connect( - lambda: self._add_asset() + self._add_asset_and_edit ) actions.append(add_asset_action) if item_type in ("asset", "task"): add_task_action = QtWidgets.QAction("Add Task", context_menu) add_task_action.triggered.connect( - lambda: self._add_task() + self._add_task_and_edit ) actions.append(add_task_action) From d0c3a3df758a0d8383dd3ee1c0e55f905877641e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 11:03:24 +0200 Subject: [PATCH 180/219] added buttons to add asset and task --- .../project_manager/project_manager/window.py | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index ae1ab70c708..df762bacce0 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -18,11 +18,13 @@ def __init__(self, parent=None): dbcon = AvalonMongoDB() - # TOP Project selection - project_widget = QtWidgets.QWidget(self) + # Top part of window + top_part_widget = QtWidgets.QWidget(self) - project_model = ProjectModel(dbcon) + # Project selection + project_widget = QtWidgets.QWidget(top_part_widget) + project_model = ProjectModel(dbcon) project_combobox = QtWidgets.QComboBox(project_widget) project_combobox.setModel(project_model) project_combobox.setRootModelIndex(QtCore.QModelIndex()) @@ -38,6 +40,30 @@ def __init__(self, parent=None): project_layout.addWidget(refresh_projects_btn, 0) project_layout.addStretch(1) + # Helper buttons + helper_btns_widget = QtWidgets.QWidget(top_part_widget) + + helper_label = QtWidgets.QLabel("Add:", helper_btns_widget) + add_asset_btn = QtWidgets.QPushButton(helper_btns_widget) + add_asset_btn.setIcon(ResourceCache.get_icon("asset", "existing")) + add_asset_btn.setText("Asset") + add_task_btn = QtWidgets.QPushButton("Task", helper_btns_widget) + add_task_btn.setIcon(ResourceCache.get_icon("task", "existing")) + add_task_btn.setText("Task") + + helper_btns_layout = QtWidgets.QHBoxLayout(helper_btns_widget) + helper_btns_layout.setContentsMargins(0, 0, 0, 0) + helper_btns_layout.addWidget(helper_label) + helper_btns_layout.addWidget(add_asset_btn) + helper_btns_layout.addWidget(add_task_btn) + helper_btns_layout.addStretch(1) + + # Add widgets to top widget layout + top_part_layout = QtWidgets.QVBoxLayout(top_part_widget) + top_part_layout.setContentsMargins(0, 0, 0, 0) + top_part_layout.addWidget(project_widget) + top_part_layout.addWidget(helper_btns_widget) + hierarchy_model = HierarchyModel(dbcon) hierarchy_view = HierarchyView(dbcon, hierarchy_model, self) @@ -74,13 +100,15 @@ def __init__(self, parent=None): buttons_layout.addWidget(save_btn) main_layout = QtWidgets.QVBoxLayout(self) - main_layout.addWidget(project_widget) + main_layout.addWidget(top_part_widget) main_layout.addWidget(hierarchy_view) main_layout.addWidget(buttons_widget) refresh_projects_btn.clicked.connect(self._on_project_refresh) project_combobox.currentIndexChanged.connect(self._on_project_change) save_btn.clicked.connect(self._on_save_click) + add_asset_btn.clicked.connect(self._on_add_asset) + add_task_btn.clicked.connect(self._on_add_task) self.project_model = project_model self.project_combobox = project_combobox @@ -124,6 +152,12 @@ def _on_project_refresh(self): def _on_save_click(self): self.hierarchy_model.save() + def _on_add_asset(self): + self.hierarchy_view.add_asset() + + def _on_add_task(self): + self.hierarchy_view.add_task() + def show_message(self, message): # TODO add nicer message pop self.message_label.setText(message) From b382d5eea8a4a55ceb1d4d5c0237dce1f9d790a3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 15:56:29 +0200 Subject: [PATCH 181/219] TaskItem and AssetItem have custom is_valid property --- .../tools/project_manager/project_manager/model.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 4227771854d..1f11874ba79 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1588,6 +1588,12 @@ def asset_id(self): def is_new(self): return self.asset_id is None + @property + def is_valid(self): + if self._is_duplicated or not self._data["name"]: + return False + return True + @property def name(self): return self._data["name"] @@ -1842,6 +1848,14 @@ def __init__(self, data=None, is_new=None): def is_new(self): return self._is_new + @property + def is_valid(self): + if self._is_duplicated or not self._data["type"]: + return False + if not self.data(QtCore.Qt.EditRole, "name"): + return False + return True + def name_icon(self): if self.__class__._name_icons is None: self.__class__._name_icons = ResourceCache.get_icons()["task"] From c2d5afb18bb6e50151599eb89bc8fbec7d2c5cda Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 16:04:44 +0200 Subject: [PATCH 182/219] use is_valid property in code --- openpype/tools/project_manager/project_manager/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 1f11874ba79..82e4b7f3e0d 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1308,7 +1308,7 @@ def data(self, role, key=None): return None if role == QtCore.Qt.ForegroundRole: - if self._is_duplicated and key == "name": + if key == "name" and not self.is_valid: return QtGui.QColor(255, 0, 0) return None @@ -1697,7 +1697,7 @@ def name_icon(self): if self._removed: icon_type = "removed" - elif self._is_duplicated: + elif not self.is_valid: icon_type = "duplicated" elif self.is_new: icon_type = "new" @@ -1862,7 +1862,7 @@ def name_icon(self): if self._removed: icon_type = "removed" - elif self._is_duplicated: + elif not self.is_valid: icon_type = "duplicated" elif self.is_new: icon_type = "new" From 3e51be7e3b62c403f719d7f9bb9b0fa08be7b6f3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 16:05:34 +0200 Subject: [PATCH 183/219] change invalid tooltips based on issue --- .../project_manager/project_manager/model.py | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 82e4b7f3e0d..d33360c4133 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1712,10 +1712,15 @@ def _get_global_data(self, role): if role == REMOVED_ROLE: return self._removed - if role == QtCore.Qt.ToolTipRole and self._is_duplicated: - return "Asset with name \"{}\" already exists.".format( - self._data["name"] - ) + if role == QtCore.Qt.ToolTipRole: + name = self.data(QtCore.Qt.EditRole, "name") + if not name: + return "Name is not set" + + elif self._is_duplicated: + return "Duplicated asset name \"{}\"".format(name) + return None + return super(AssetItem, self)._get_global_data(role) def setData(self, value, role, key=None): @@ -1877,10 +1882,18 @@ def _get_global_data(self, role): if role == REMOVED_ROLE: return self._removed - if role == QtCore.Qt.ToolTipRole and self._is_duplicated: - return "Duplicated Task name \"{}\".".format( - self._data["name"] - ) + if role == QtCore.Qt.ToolTipRole: + if not self._data["type"]: + return "Type is not set" + + name = self.data(QtCore.Qt.EditRole, "name") + if not name: + return "Name is not set" + + elif self._is_duplicated: + return "Duplicated task name \"{}".format(name) + return None + return super(TaskItem, self)._get_global_data(role) def to_doc_data(self): From cda522d6541823c019ab0579ff3a90357f00ba66 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 16:06:11 +0200 Subject: [PATCH 184/219] task may have different edit and display role value --- .../project_manager/project_manager/model.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index d33360c4133..0471e863d74 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1907,18 +1907,19 @@ def to_doc_data(self): } def data(self, role, key=None): - if role == QtCore.Qt.BackgroundRole: - if self._removed: - return QtGui.QColor(255, 0, 0, 127) - - elif self.is_new: - return QtGui.QColor(0, 255, 0, 127) + if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole): + if key == "type": + return self._data["type"] + + if key == "name": + if not self._data["type"]: + if role == QtCore.Qt.DisplayRole: + return "< Select Type >" + if role == QtCore.Qt.EditRole: + return "" + else: + return self._data[key] or self._data["type"] - if ( - role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole) - and key == "name" - ): - return self._data[key] or self._data["type"] or "< Select Type >" return super(TaskItem, self).data(role, key) def setData(self, value, role, key=None): From b86ab862a956f1014dd0e9cb42566ff205a7c373 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 16:06:32 +0200 Subject: [PATCH 185/219] task will store empty name as None --- .../project_manager/project_manager/model.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 0471e863d74..b33c4431f0c 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1927,13 +1927,21 @@ def setData(self, value, role, key=None): self._removed = value return True - result = super(TaskItem, self).setData(value, role, key) - if ( - key == "name" - or (key == "type" and self._data["name"] is None) + role == QtCore.Qt.EditRole + and key == "name" + and not value ): - self.parent().on_task_name_change(self) + value = None + + result = super(TaskItem, self).setData(value, role, key) + + if role == QtCore.Qt.EditRole: + if ( + key == "name" + or (key == "type" and not self._data["name"]) + ): + self.parent().on_task_name_change(self) return result From c2aef84f2e853ec797577c11e86f6dfeec6b16f9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 16:13:25 +0200 Subject: [PATCH 186/219] use EditRole to get value of name and type --- .../tools/project_manager/project_manager/model.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index b33c4431f0c..5ce45d2b991 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -640,7 +640,7 @@ def _rename_asset(self, asset_item, new_name): if not isinstance(asset_item, AssetItem): return - prev_name = asset_item.data(QtCore.Qt.DisplayRole, "name") + prev_name = asset_item.data(QtCore.Qt.EditRole, "name") if prev_name == new_name: return @@ -1619,8 +1619,8 @@ def to_doc(self): ) doc = { - "name": self.data(QtCore.Qt.DisplayRole, "name"), - "type": self.data(QtCore.Qt.DisplayRole, "type"), + "name": self.data(QtCore.Qt.EditRole, "name"), + "type": self.data(QtCore.Qt.EditRole, "type"), "schema": schema_name, "data": doc_data, "parent": self.project_id @@ -1632,7 +1632,7 @@ def to_doc(self): if key in doc: continue # Use `data` method to get inherited values - doc_data[key] = self.data(QtCore.Qt.DisplayRole, key) + doc_data[key] = self.data(QtCore.Qt.EditRole, key) return doc @@ -1751,7 +1751,7 @@ def flags(self, key): return super(AssetItem, self).flags(key) def _add_task(self, item): - name = item.data(QtCore.Qt.DisplayRole, "name").lower() + name = item.data(QtCore.Qt.EditRole, "name").lower() item_id = item.data(IDENTIFIER_ROLE) self._task_name_by_item_id[item_id] = name @@ -1901,7 +1901,7 @@ def to_doc_data(self): return {} data = copy.deepcopy(self._data) data.pop("name") - name = self.data(QtCore.Qt.DisplayRole, "name") + name = self.data(QtCore.Qt.EditRole, "name") return { name: data } From cf9cf6278a36afd86e19dff36582e969c8184c7b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 17:22:21 +0200 Subject: [PATCH 187/219] asset and task items know if should return display role --- .../project_manager/constants.py | 1 + .../project_manager/project_manager/model.py | 42 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/constants.py b/openpype/tools/project_manager/project_manager/constants.py index 703cc0d0038..6fb4b991ed0 100644 --- a/openpype/tools/project_manager/project_manager/constants.py +++ b/openpype/tools/project_manager/project_manager/constants.py @@ -7,6 +7,7 @@ HIERARCHY_CHANGE_ABLE_ROLE = QtCore.Qt.UserRole + 3 REMOVED_ROLE = QtCore.Qt.UserRole + 4 ITEM_TYPE_ROLE = QtCore.Qt.UserRole + 5 +EDITOR_OPENED_ROLE = QtCore.Qt.UserRole + 6 NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_" NAME_REGEX = re.compile("^[" + NAME_ALLOWED_SYMBOLS + "]*$") diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 5ce45d2b991..2c4e1838a6f 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -9,7 +9,8 @@ ITEM_TYPE_ROLE, DUPLICATED_ROLE, HIERARCHY_CHANGE_ABLE_ROLE, - REMOVED_ROLE + REMOVED_ROLE, + EDITOR_OPENED_ROLE ) from .style import ResourceCache from pymongo import UpdateOne, DeleteOne @@ -1555,6 +1556,10 @@ def __init__(self, asset_doc): asset_doc = {} self.mongo_id = asset_doc.get("_id") self._project_id = None + self._edited_columns = { + column_name: False + for column_name in self.editable_columns + } # Item data self._hierarchy_changes_enabled = True @@ -1723,7 +1728,24 @@ def _get_global_data(self, role): return super(AssetItem, self)._get_global_data(role) + def data(self, role, key=None): + if role == EDITOR_OPENED_ROLE: + if key not in self._edited_columns: + return False + return self._edited_columns[key] + + if role == QtCore.Qt.DisplayRole and self._edited_columns.get(key): + return "" + + return super(AssetItem, self).data(role, key) + def setData(self, value, role, key=None): + if role == EDITOR_OPENED_ROLE: + if key not in self._edited_columns: + return False + self._edited_columns[key] = value + return True + if role == REMOVED_ROLE: self._removed = value return True @@ -1846,6 +1868,10 @@ def __init__(self, data=None, is_new=None): if data is None: data = {} + self._edited_columns = { + column_name: False + for column_name in self.editable_columns + } self._origin_data = copy.deepcopy(data) super(TaskItem, self).__init__(data) @@ -1907,6 +1933,14 @@ def to_doc_data(self): } def data(self, role, key=None): + if role == EDITOR_OPENED_ROLE: + if key not in self._edited_columns: + return False + return self._edited_columns[key] + + if role == QtCore.Qt.DisplayRole and self._edited_columns.get(key): + return "" + if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole): if key == "type": return self._data["type"] @@ -1923,6 +1957,12 @@ def data(self, role, key=None): return super(TaskItem, self).data(role, key) def setData(self, value, role, key=None): + if role == EDITOR_OPENED_ROLE: + if key not in self._edited_columns: + return False + self._edited_columns[key] = value + return True + if role == REMOVED_ROLE: self._removed = value return True From 0699d8d843c548319d8de81166e6db22f95f07ee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 17:23:09 +0200 Subject: [PATCH 188/219] view gives model items info if should return display role --- .../project_manager/project_manager/view.py | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 6bbae10f027..01316875412 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -15,7 +15,8 @@ REMOVED_ROLE, IDENTIFIER_ROLE, ITEM_TYPE_ROLE, - HIERARCHY_CHANGE_ABLE_ROLE + HIERARCHY_CHANGE_ABLE_ROLE, + EDITOR_OPENED_ROLE ) @@ -104,6 +105,8 @@ def __init__(self, dbcon, source_model, parent): super(HierarchyView, self).__init__(parent) # Direct access to model self._source_model = source_model + self._editors_mapping = {} + self._persisten_editors = set() # Access to parent because of `show_message` method self._parent = parent @@ -214,12 +217,41 @@ def _deselect_editor(self, editor): def edit(self, index, *args, **kwargs): result = super(HierarchyView, self).edit(index, *args, **kwargs) - self._deselect_editor(self.indexWidget(index)) + if result: + # Mark index to not return text for DisplayRole + editor = self.indexWidget(index) + if ( + editor not in self._persisten_editors + and editor not in self._editors_mapping + ): + self._editors_mapping[editor] = index + self._source_model.setData(index, True, EDITOR_OPENED_ROLE) + # Deselect content of editor + # QUESTION not sure if we want do this all the time + self._deselect_editor(editor) return result + def closeEditor(self, editor, hint): + if ( + editor not in self._persisten_editors + and editor in self._editors_mapping + ): + index = self._editors_mapping.pop(editor) + self._source_model.setData(index, False, EDITOR_OPENED_ROLE) + super(HierarchyView, self).closeEditor(editor, hint) + def openPersistentEditor(self, index): + self._source_model.setData(index, True, EDITOR_OPENED_ROLE) super(HierarchyView, self).openPersistentEditor(index) - self._deselect_editor(self.indexWidget(index)) + editor = self.indexWidget(index) + self._persisten_editors.add(editor) + self._deselect_editor(editor) + + def closePersistentEditor(self, index): + self._source_model.setData(index, False, EDITOR_OPENED_ROLE) + editor = self.indexWidget(index) + self._persisten_editors.remove(editor) + super(HierarchyView, self).closePersistentEditor(index) def rowsInserted(self, parent_index, start, end): super(HierarchyView, self).rowsInserted(parent_index, start, end) From b318387c3cb81e9b8d64cacaa7b2a6e0f9a8f91e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 17:23:47 +0200 Subject: [PATCH 189/219] added some styles to qtreeview --- .../project_manager/project_manager/delegates.py | 1 + .../project_manager/project_manager/style/style.css | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index 4c6f7f70eb8..f53e0442f1f 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -115,6 +115,7 @@ def __init__(self, project_doc_cache, *args, **kwargs): def createEditor(self, parent, option, index): editor = FilterComboBox(parent) editor.setObjectName("TypeEditor") + editor.style().polish(editor) if not self._project_doc_cache.project_doc: return editor diff --git a/openpype/tools/project_manager/project_manager/style/style.css b/openpype/tools/project_manager/project_manager/style/style.css index c62f0fcd814..31196b7cc6a 100644 --- a/openpype/tools/project_manager/project_manager/style/style.css +++ b/openpype/tools/project_manager/project_manager/style/style.css @@ -4,6 +4,18 @@ QTreeView::item { padding-right: 3px; } + +QTreeView::item:selected, QTreeView::item:selected:!active { + background: rgba(0, 122, 204, 127); + color: black; +} + #RefreshBtn { padding: 2px; } + +#TypeEditor, #ToolEditor, #NameEditor, #NumberEditor { + background: transparent; + border: 1px solid #005c99; + border-radius: 0.3em; +} From 382febe03fb2081fd16fdf1ca82f48dd1c564be1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 17:28:13 +0200 Subject: [PATCH 190/219] renamed icon keys --- openpype/tools/project_manager/project_manager/model.py | 8 ++++---- .../project_manager/project_manager/style/__init__.py | 8 ++++---- openpype/tools/project_manager/project_manager/window.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 2c4e1838a6f..b371242388e 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1703,11 +1703,11 @@ def name_icon(self): if self._removed: icon_type = "removed" elif not self.is_valid: - icon_type = "duplicated" + icon_type = "invalid" elif self.is_new: icon_type = "new" else: - icon_type = "existing" + icon_type = "default" return self.__class__._name_icons[icon_type] def _get_global_data(self, role): @@ -1894,11 +1894,11 @@ def name_icon(self): if self._removed: icon_type = "removed" elif not self.is_valid: - icon_type = "duplicated" + icon_type = "invalid" elif self.is_new: icon_type = "new" else: - icon_type = "existing" + icon_type = "default" return self.__class__._name_icons[icon_type] def add_child(self, item, row=None): diff --git a/openpype/tools/project_manager/project_manager/style/__init__.py b/openpype/tools/project_manager/project_manager/style/__init__.py index e7bb116843b..6b9d708f767 100644 --- a/openpype/tools/project_manager/project_manager/style/__init__.py +++ b/openpype/tools/project_manager/project_manager/style/__init__.py @@ -23,7 +23,7 @@ def get_icons(cls): if cls.icons is None: cls.icons = { "asset": { - "existing": qtawesome.icon( + "default": qtawesome.icon( "fa.folder", color=cls.colors["standard"] ), @@ -31,7 +31,7 @@ def get_icons(cls): "fa.folder", color=cls.colors["new"] ), - "duplicated": qtawesome.icon( + "invalid": qtawesome.icon( "fa.exclamation-triangle", color=cls.colors["warning"] ), @@ -41,7 +41,7 @@ def get_icons(cls): ) }, "task": { - "existing": qtawesome.icon( + "default": qtawesome.icon( "fa.check-circle-o", color=cls.colors["standard"] ), @@ -49,7 +49,7 @@ def get_icons(cls): "fa.check-circle", color=cls.colors["new"] ), - "duplicated": qtawesome.icon( + "invalid": qtawesome.icon( "fa.exclamation-circle", color=cls.colors["warning"] ), diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index df762bacce0..c4243c3cc39 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -45,10 +45,10 @@ def __init__(self, parent=None): helper_label = QtWidgets.QLabel("Add:", helper_btns_widget) add_asset_btn = QtWidgets.QPushButton(helper_btns_widget) - add_asset_btn.setIcon(ResourceCache.get_icon("asset", "existing")) + add_asset_btn.setIcon(ResourceCache.get_icon("asset", "default")) add_asset_btn.setText("Asset") add_task_btn = QtWidgets.QPushButton("Task", helper_btns_widget) - add_task_btn.setIcon(ResourceCache.get_icon("task", "existing")) + add_task_btn.setIcon(ResourceCache.get_icon("task", "default")) add_task_btn.setText("Task") helper_btns_layout = QtWidgets.QHBoxLayout(helper_btns_widget) From 354ea1d5818f71a95b1feb94c96e2d2cc74f17b5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 17:30:13 +0200 Subject: [PATCH 191/219] unified color source --- openpype/tools/project_manager/project_manager/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index b371242388e..9de4301e8db 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -1310,7 +1310,7 @@ def data(self, role, key=None): if role == QtCore.Qt.ForegroundRole: if key == "name" and not self.is_valid: - return QtGui.QColor(255, 0, 0) + return ResourceCache.colors["warning"] return None if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole): From d5201b718cce9b7fe2363d9c0438c41e9802ae89 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 17:32:27 +0200 Subject: [PATCH 192/219] modified color a little bit --- .../tools/project_manager/project_manager/style/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/style/__init__.py b/openpype/tools/project_manager/project_manager/style/__init__.py index 6b9d708f767..f3b0e0f9c0f 100644 --- a/openpype/tools/project_manager/project_manager/style/__init__.py +++ b/openpype/tools/project_manager/project_manager/style/__init__.py @@ -6,8 +6,8 @@ class ResourceCache: colors = { "standard": "#333333", - "warning": "#ff0000", - "new": "#00ff00" + "new": "#2d9a4c", + "warning": "#c83232" } icons = None From 1c1b077fe437fbe1b43ca0193a80b107cda77257 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 17:42:01 +0200 Subject: [PATCH 193/219] properly handle duplicated asset names on remove and un-remove --- openpype/tools/project_manager/project_manager/model.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 9de4301e8db..1d48e78a198 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -499,6 +499,10 @@ def _children_recursion(_item): for item in items_by_id.values(): if item.data(REMOVED_ROLE): item.setData(False, REMOVED_ROLE) + if isinstance(item, AssetItem): + name = item.data(QtCore.Qt.EditRole, "name") + self._asset_items_by_name[name].add(item.id) + self._validate_asset_duplicity(name) def delete_index(self, index): return self.delete_indexes([index]) @@ -606,6 +610,8 @@ def _fill_children(_all_descendants, cur_item, parent_item=None): if end_row is not None: row_ranges.append((start_row, end_row)) start_row = end_row = None + if isinstance(child_item, AssetItem): + self._rename_asset(child_item, None) continue end_row = row From 57dc929bd2e8128cdc4ba312487498662d8964a9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 18:09:03 +0200 Subject: [PATCH 194/219] moved header definition to HierarchicalView --- .../project_manager/project_manager/model.py | 3 +++ .../project_manager/project_manager/view.py | 18 ++++++++++++++++++ .../project_manager/project_manager/window.py | 13 ------------- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 1d48e78a198..7617275b916 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -115,6 +115,7 @@ class HierarchyModel(QtCore.QAbstractItemModel): } index_moved = QtCore.Signal(QtCore.QModelIndex) + project_changed = QtCore.Signal() def __init__(self, dbcon, parent=None): super(HierarchyModel, self).__init__(parent) @@ -281,6 +282,8 @@ def set_project(self, project_name): self.add_items(task_items, asset_item) + self.project_changed.emit() + def rowCount(self, parent=None): if parent is None or not parent.isValid(): parent_item = self._root_item diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 01316875412..02d5b40fd6b 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -145,6 +145,7 @@ def __init__(self, dbcon, source_model, parent): source_model.index_moved.connect(self._on_rows_moved) self.customContextMenuRequested.connect(self._on_context_menu) + self._source_model.project_changed.connect(self._on_project_reset) self._project_doc_cache = project_doc_cache self._tools_cache = tools_cache @@ -153,6 +154,20 @@ def __init__(self, dbcon, source_model, parent): self._column_delegates = column_delegates self._column_key_to_index = column_key_to_index + def header_init(self): + header = self.header() + header.setStretchLastSection(False) + for idx in range(header.count()): + logical_index = header.logicalIndex(idx) + if idx == 0: + header.setSectionResizeMode( + logical_index, QtWidgets.QHeaderView.Stretch + ) + else: + header.setSectionResizeMode( + logical_index, QtWidgets.QHeaderView.ResizeToContents + ) + def set_project(self, project_name): # Trigger helpers first self._project_doc_cache.set_project(project_name) @@ -161,6 +176,9 @@ def set_project(self, project_name): # Trigger update of model after all data for delegates are filled self._source_model.set_project(project_name) + def _on_project_reset(self): + self.header_init() + self.collapseAll() project_item = self._source_model.project_item diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index c4243c3cc39..49c48912cf5 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -75,19 +75,6 @@ def __init__(self, parent=None): _selection_model.setModel(hierarchy_view.model()) hierarchy_view.setSelectionModel(_selection_model) - header = hierarchy_view.header() - header.setStretchLastSection(False) - for idx in range(header.count()): - logical_index = header.logicalIndex(idx) - if idx == 0: - header.setSectionResizeMode( - logical_index, QtWidgets.QHeaderView.Stretch - ) - else: - header.setSectionResizeMode( - logical_index, QtWidgets.QHeaderView.ResizeToContents - ) - buttons_widget = QtWidgets.QWidget(self) message_label = QtWidgets.QLabel(buttons_widget) From 3ddefade0dd1d6fe3d1dc3b28a7452c5a9d0cd52 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 18:09:35 +0200 Subject: [PATCH 195/219] it is possible to force project change even if current project name is same --- openpype/tools/project_manager/project_manager/model.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 7617275b916..a597550b36b 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -142,9 +142,7 @@ def _reset_root_item(self): self._root_item = RootItem(self) def refresh_project(self): - project_name = self._current_project - self._current_project = None - self.set_project(project_name) + self.set_project(self._current_project, True) @property def project_item(self): @@ -156,8 +154,8 @@ def project_item(self): break return output - def set_project(self, project_name): - if self._current_project == project_name: + def set_project(self, project_name, force=False): + if self._current_project == project_name and not force: return self.clear() From b3aa86bb54eb7d54e7be264633516f6b4a69becc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 18:10:03 +0200 Subject: [PATCH 196/219] view has defined some default widths of columns --- .../project_manager/project_manager/view.py | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 02d5b40fd6b..e96176debd3 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -86,6 +86,27 @@ class HierarchyView(QtWidgets.QTreeView): "pixelAspect": NumberDef(0, decimals=2), "tools_env": ToolsDef() } + + columns_sizes = { + "default": { + "stretch": QtWidgets.QHeaderView.ResizeToContents + }, + "name": { + "stretch": QtWidgets.QHeaderView.Stretch + }, + "type": { + "stretch": QtWidgets.QHeaderView.Interactive, + "width": 100 + }, + "tools_env": { + "stretch": QtWidgets.QHeaderView.Interactive, + "width": 140 + }, + "pixelAspect": { + "stretch": QtWidgets.QHeaderView.Interactive, + "width": 80 + } + } persistent_columns = { "type", "frameStart", @@ -157,16 +178,21 @@ def __init__(self, dbcon, source_model, parent): def header_init(self): header = self.header() header.setStretchLastSection(False) + + default_behavior = self.columns_sizes["default"] + widths_by_idx = {} for idx in range(header.count()): + key = self._source_model.columns[idx] + behavior = self.columns_sizes.get(key, default_behavior) logical_index = header.logicalIndex(idx) - if idx == 0: - header.setSectionResizeMode( - logical_index, QtWidgets.QHeaderView.Stretch - ) - else: - header.setSectionResizeMode( - logical_index, QtWidgets.QHeaderView.ResizeToContents - ) + stretch = behavior["stretch"] + header.setSectionResizeMode(logical_index, stretch) + width = behavior.get("width") + if width is not None: + widths_by_idx[idx] = width + + for idx, width in widths_by_idx.items(): + self.setColumnWidth(idx, width) def set_project(self, project_name): # Trigger helpers first From d2bb8e2836c91a9218bae7fa876812375153704f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 18:27:55 +0200 Subject: [PATCH 197/219] fixed removing of asset items --- openpype/tools/project_manager/project_manager/model.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index a597550b36b..874e1b3a9c5 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -566,6 +566,8 @@ def _fill_children(_all_descendants, cur_item, parent_item=None): if remove_item: cur_item.setData(True, REMOVED_ROLE) + if isinstance(cur_item, AssetItem): + self._rename_asset(cur_item, None) for task_item in task_children: _fill_children(_all_descendants, task_item, cur_item) @@ -611,8 +613,6 @@ def _fill_children(_all_descendants, cur_item, parent_item=None): if end_row is not None: row_ranges.append((start_row, end_row)) start_row = end_row = None - if isinstance(child_item, AssetItem): - self._rename_asset(child_item, None) continue end_row = row @@ -652,7 +652,8 @@ def _rename_asset(self, asset_item, new_name): if prev_name == new_name: return - self._asset_items_by_name[prev_name].remove(asset_item.id) + if asset_item.id in self._asset_items_by_name[prev_name]: + self._asset_items_by_name[prev_name].remove(asset_item.id) self._validate_asset_duplicity(prev_name) From ab35d00183bacc40e097c577b82e7c29af8fea90 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 18:32:56 +0200 Subject: [PATCH 198/219] added name and icon to window --- openpype/tools/project_manager/project_manager/window.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 49c48912cf5..52046fb94bf 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore +from Qt import QtWidgets, QtCore, QtGui from . import ( ProjectModel, @@ -9,6 +9,7 @@ ) from .style import load_stylesheet, ResourceCache +from openpype import resources from avalon.api import AvalonMongoDB @@ -16,7 +17,8 @@ class Window(QtWidgets.QWidget): def __init__(self, parent=None): super(Window, self).__init__(parent) - dbcon = AvalonMongoDB() + self.setWindowTitle("OpenPype Project Manager") + self.setWindowIcon(QtGui.QIcon(resources.pype_icon_filepath())) # Top part of window top_part_widget = QtWidgets.QWidget(self) @@ -24,6 +26,8 @@ def __init__(self, parent=None): # Project selection project_widget = QtWidgets.QWidget(top_part_widget) + dbcon = AvalonMongoDB() + project_model = ProjectModel(dbcon) project_combobox = QtWidgets.QComboBox(project_widget) project_combobox.setModel(project_model) From 1477dadd341b0e23fd3cfaefab9a2169945f5f8e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 18:34:13 +0200 Subject: [PATCH 199/219] renamed class from Window to ProjectManagerWindow --- openpype/tools/project_manager/__init__.py | 4 ++-- openpype/tools/project_manager/project_manager/__init__.py | 6 +++--- openpype/tools/project_manager/project_manager/window.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/tools/project_manager/__init__.py b/openpype/tools/project_manager/__init__.py index 880fc253cf6..62fa8af8aa6 100644 --- a/openpype/tools/project_manager/__init__.py +++ b/openpype/tools/project_manager/__init__.py @@ -1,10 +1,10 @@ from .project_manager import ( - Window, + ProjectManagerWindow, main ) __all__ = ( - "Window", + "ProjectManagerWindow", "main" ) diff --git a/openpype/tools/project_manager/project_manager/__init__.py b/openpype/tools/project_manager/project_manager/__init__.py index dccc46f771d..34beec331d3 100644 --- a/openpype/tools/project_manager/project_manager/__init__.py +++ b/openpype/tools/project_manager/project_manager/__init__.py @@ -13,7 +13,7 @@ "AssetItem", "TaskItem", - "Window", + "ProjectManagerWindow", "main" ) @@ -33,7 +33,7 @@ AssetItem, TaskItem ) -from .window import Window +from .window import ProjectManagerWindow def main(): @@ -42,7 +42,7 @@ def main(): app = QtWidgets.QApplication([]) - window = Window() + window = ProjectManagerWindow() window.show() sys.exit(app.exec_()) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 52046fb94bf..81f49d3c26a 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -13,9 +13,9 @@ from avalon.api import AvalonMongoDB -class Window(QtWidgets.QWidget): +class ProjectManagerWindow(QtWidgets.QWidget): def __init__(self, parent=None): - super(Window, self).__init__(parent) + super(ProjectManagerWindow, self).__init__(parent) self.setWindowTitle("OpenPype Project Manager") self.setWindowIcon(QtGui.QIcon(resources.pype_icon_filepath())) From ced76863671af5bff75cc51c6e90c6e418e1b332 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 May 2021 19:45:48 +0200 Subject: [PATCH 200/219] create add asset/task buttons in one step --- .../project_manager/project_manager/window.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 81f49d3c26a..61000464eab 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -48,12 +48,16 @@ def __init__(self, parent=None): helper_btns_widget = QtWidgets.QWidget(top_part_widget) helper_label = QtWidgets.QLabel("Add:", helper_btns_widget) - add_asset_btn = QtWidgets.QPushButton(helper_btns_widget) - add_asset_btn.setIcon(ResourceCache.get_icon("asset", "default")) - add_asset_btn.setText("Asset") - add_task_btn = QtWidgets.QPushButton("Task", helper_btns_widget) - add_task_btn.setIcon(ResourceCache.get_icon("task", "default")) - add_task_btn.setText("Task") + add_asset_btn = QtWidgets.QPushButton( + ResourceCache.get_icon("asset", "default"), + "Asset", + helper_btns_widget + ) + add_task_btn = QtWidgets.QPushButton( + ResourceCache.get_icon("task", "default"), + "Task", + helper_btns_widget + ) helper_btns_layout = QtWidgets.QHBoxLayout(helper_btns_widget) helper_btns_layout.setContentsMargins(0, 0, 0, 0) From d17a0307ab4599d82436dbbcceec79f64c81c108 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 19:47:43 +0200 Subject: [PATCH 201/219] defined CURRENT_DOC_SCHEMAS constant in openpype.lib --- openpype/lib/__init__.py | 2 ++ openpype/lib/avalon_context.py | 7 +++++++ .../ftrack/event_handlers_server/event_sync_to_avalon.py | 6 ++---- openpype/modules/ftrack/lib/avalon_sync.py | 8 ++++---- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 457ceb1d567..6e63b3d9a6d 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -58,6 +58,7 @@ ) from .avalon_context import ( + CURRENT_DOC_SCHEMAS, is_latest, any_outdated, get_asset, @@ -162,6 +163,7 @@ "recursive_bases_from_class", "classes_from_module", + "CURRENT_DOC_SCHEMAS", "is_latest", "any_outdated", "get_asset", diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 2d8726352af..2d608e82793 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -17,6 +17,13 @@ log = logging.getLogger("AvalonContext") +CURRENT_DOC_SCHEMAS = { + "project": "openpype:project-3.0", + "asset": "openpype:asset-3.0", + "config": "openpype:config-2.0" +} + + def with_avalon(func): @functools.wraps(func) def wrap_avalon(*args, **kwargs): diff --git a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py index 3bb01798e44..410e51e2a4c 100644 --- a/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py @@ -26,9 +26,7 @@ BaseEvent ) -from openpype.modules.ftrack.lib.avalon_sync import ( - EntitySchemas -) +from openpype.lib import CURRENT_DOC_SCHEMAS class SyncToAvalonEvent(BaseEvent): @@ -1128,7 +1126,7 @@ def create_entity_in_avalon(self, ftrack_ent, parent_avalon): "_id": mongo_id, "name": name, "type": "asset", - "schema": EntitySchemas["asset"], + "schema": CURRENT_DOC_SCHEMAS["asset"], "parent": proj["_id"], "data": { "ftrackId": ftrack_ent["id"], diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index f58e858a5a4..a3b926464ed 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -34,7 +34,7 @@ # Current schemas for avalon types -EntitySchemas = { +CURRENT_DOC_SCHEMAS = { "project": "openpype:project-3.0", "asset": "openpype:asset-3.0", "config": "openpype:config-2.0" @@ -1862,7 +1862,7 @@ def create_avalon_entity(self, ftrack_id): item["_id"] = new_id item["parent"] = self.avalon_project_id - item["schema"] = EntitySchemas["asset"] + item["schema"] = CURRENT_DOC_SCHEMAS["asset"] item["data"]["visualParent"] = avalon_parent new_id_str = str(new_id) @@ -2003,8 +2003,8 @@ def create_avalon_project(self): project_item["_id"] = new_id project_item["parent"] = None - project_item["schema"] = EntitySchemas["project"] - project_item["config"]["schema"] = EntitySchemas["config"] + project_item["schema"] = CURRENT_DOC_SCHEMAS["project"] + project_item["config"]["schema"] = CURRENT_DOC_SCHEMAS["config"] self.ftrack_avalon_mapper[self.ft_project_id] = new_id self.avalon_ftrack_mapper[new_id] = self.ft_project_id From 85f812bfe5b74844685f3dd5fee62bb211f8ee52 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 20:15:31 +0200 Subject: [PATCH 202/219] define project regex in code --- openpype/lib/__init__.py | 6 ++++++ openpype/lib/avalon_context.py | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 6e63b3d9a6d..9fcc536a3aa 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -59,6 +59,9 @@ from .avalon_context import ( CURRENT_DOC_SCHEMAS, + PROJECT_NAME_ALLOWED_SYMBOLS, + PROJECT_NAME_REGEX, + create_project, is_latest, any_outdated, get_asset, @@ -164,6 +167,9 @@ "classes_from_module", "CURRENT_DOC_SCHEMAS", + "PROJECT_NAME_ALLOWED_SYMBOLS", + "PROJECT_NAME_REGEX", + "create_project", "is_latest", "any_outdated", "get_asset", diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 2d608e82793..f4a58c74fdb 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -22,6 +22,10 @@ "asset": "openpype:asset-3.0", "config": "openpype:config-2.0" } +PROJECT_NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_" +PROJECT_NAME_REGEX = re.compile( + "^[{}]+$".format(PROJECT_NAME_ALLOWED_SYMBOLS) +) def with_avalon(func): From 29040d449f42fb68e6596cb95ea8b01c46f0eacd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 20:23:11 +0200 Subject: [PATCH 203/219] implemented function to create new project --- openpype/lib/avalon_context.py | 82 ++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index f4a58c74fdb..c53b028a44c 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -28,6 +28,88 @@ ) +def create_project( + project_name, project_code, library_project=False, dbcon=None +): + """Create project using OpenPype settings. + + This project creation function is not validating project document on + creation. It is because project document is created blindly with only + minimum required information about project which is it's name, code, type + and schema. + + Entered project name must be unique and project must not exist yet. + + Args: + project_name(str): New project name. Should be unique. + project_code(str): Project's code should be unique too. + library_project(bool): Project is library project. + dbcon(AvalonMongoDB): Object of connection to MongoDB. + + Raises: + ValueError: When project name already exists in MongoDB. + + Returns: + dict: Created project document. + """ + + from openpype.settings import ProjectSettings, SaveWarningExc + from avalon.api import AvalonMongoDB + from avalon.schema import validate + + if dbcon is None: + dbcon = AvalonMongoDB() + + if not PROJECT_NAME_REGEX.match(project_name): + raise ValueError(( + "Project name \"{}\" contain invalid characters" + ).format(project_name)) + + database = dbcon.database + project_doc = database[project_name].find_one( + {"type": "project"}, + {"name": 1} + ) + if project_doc: + raise ValueError("Project with name \"{}\" already exists".format( + project_name + )) + + project_doc = { + "type": "project", + "name": project_name, + "data": { + "code": project_code, + "library_project": library_project + }, + "schema": CURRENT_DOC_SCHEMAS["project"] + } + # Insert document with basic data + database[project_name].insert_one(project_doc) + # Load ProjectSettings for the project and save it to store all attributes + # and Anatomy + try: + project_settings_entity = ProjectSettings(project_name) + project_settings_entity.save() + except SaveWarningExc as exc: + print(str(exc)) + except Exception: + database[project_name].delete_one({"type": "project"}) + raise + + project_doc = database[project_name].find_one({"type": "project"}) + + try: + # Validate created project document + validate(project_doc) + except Exception as exc: + # Remove project if is not valid + database[project_name].delete_one({"type": "project"}) + raise + + return project_doc + + def with_avalon(func): @functools.wraps(func) def wrap_avalon(*args, **kwargs): From 7d6741f18517e5ad8c17050ede6b10f41a6264cf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 20:23:29 +0200 Subject: [PATCH 204/219] use CURRENT_DOC_SCHEMAS in hierarchy model --- openpype/tools/project_manager/project_manager/model.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 874e1b3a9c5..8f6fe040068 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -13,6 +13,8 @@ EDITOR_OPENED_ROLE ) from .style import ResourceCache + +from openpype.lib import CURRENT_DOC_SCHEMAS from pymongo import UpdateOne, DeleteOne from avalon.vendor import qtawesome from Qt import QtCore, QtGui @@ -1628,7 +1630,8 @@ def to_doc(self): "tasks": tasks } schema_name = ( - self._origin_asset_doc.get("schema") or "openpype:asset-3.0" + self._origin_asset_doc.get("schema") + or CURRENT_DOC_SCHEMAS["asset"] ) doc = { From cc559527e249cb39fc246012cf2ea94f607f1605 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 20:23:47 +0200 Subject: [PATCH 205/219] implemented CreateProjectDialog to be able create projects --- .../project_manager/widgets.py | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index 566e17ea86b..c650a4a8b70 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -1,8 +1,16 @@ import re + from .constants import ( NAME_ALLOWED_SYMBOLS, NAME_REGEX ) +from openpype.lib import ( + create_project, + PROJECT_NAME_ALLOWED_SYMBOLS, + PROJECT_NAME_REGEX +) +from avalon.api import AvalonMongoDB + from Qt import QtWidgets, QtCore @@ -80,3 +88,177 @@ def setModelColumn(self, column): self._completer.setCompletionColumn(column) self._filter_proxy_model.setFilterKeyColumn(column) super(FilterComboBox, self).setModelColumn(column) + + +class CreateProjectDialog(QtWidgets.QDialog): + def __init__(self, parent=None, dbcon=None): + super(CreateProjectDialog, self).__init__(parent) + + self.setWindowTitle("Create Project") + + self.allowed_regex = "[^{}]+".format(PROJECT_NAME_ALLOWED_SYMBOLS) + + if dbcon is None: + dbcon = AvalonMongoDB() + + self.dbcon = dbcon + self._ignore_code_change = False + self._project_name_is_valid = False + self._project_code_is_valid = False + self._project_code_value = None + + project_names, project_codes = self._get_existing_projects() + + inputs_widget = QtWidgets.QWidget(self) + project_name_input = QtWidgets.QLineEdit(inputs_widget) + project_code_input = QtWidgets.QLineEdit(inputs_widget) + library_project_input = QtWidgets.QCheckBox(inputs_widget) + + inputs_layout = QtWidgets.QFormLayout(inputs_widget) + inputs_layout.setContentsMargins(0, 0, 0, 0) + inputs_layout.addRow("Project name:", project_name_input) + inputs_layout.addRow("Project code:", project_code_input) + inputs_layout.addRow("Library project:", library_project_input) + + project_name_label = QtWidgets.QLabel(self) + project_code_label = QtWidgets.QLabel(self) + + btns_widget = QtWidgets.QWidget(self) + ok_btn = QtWidgets.QPushButton("Ok", btns_widget) + ok_btn.setEnabled(False) + cancel_btn = QtWidgets.QPushButton("Cancel", btns_widget) + btns_layout = QtWidgets.QHBoxLayout(btns_widget) + btns_layout.setContentsMargins(0, 0, 0, 0) + btns_layout.addStretch(1) + btns_layout.addWidget(ok_btn) + btns_layout.addWidget(cancel_btn) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.addWidget(inputs_widget, 0) + main_layout.addWidget(project_name_label, 1) + main_layout.addWidget(project_code_label, 1) + main_layout.addStretch(1) + main_layout.addWidget(btns_widget, 0) + + project_name_input.textChanged.connect(self._on_project_name_change) + project_code_input.textChanged.connect(self._on_project_code_change) + ok_btn.clicked.connect(self._on_ok_clicked) + cancel_btn.clicked.connect(self._on_cancel_clicked) + + self.invalid_project_names = project_names + self.invalid_project_codes = project_codes + + self.project_name_label = project_name_label + self.project_code_label = project_code_label + + self.project_name_input = project_name_input + self.project_code_input = project_code_input + self.library_project_input = library_project_input + + self.ok_btn = ok_btn + + @property + def project_name(self): + return self.project_name_input.text() + + def _on_project_name_change(self, value): + if self._project_code_value is None: + self._ignore_code_change = True + self.project_code_input.setText(value.lower()) + self._ignore_code_change = False + + self._update_valid_project_name(value) + + def _on_project_code_change(self, value): + if not value: + value = None + + self._update_valid_project_code(value) + + if not self._ignore_code_change: + self._project_code_value = value + + def _update_valid_project_name(self, value): + message = "" + is_valid = True + if not value: + message = "Project name is empty" + is_valid = False + + elif value in self.invalid_project_names: + message = "Project name \"{}\" already exist".format(value) + is_valid = False + + elif not PROJECT_NAME_REGEX.match(value): + message = ( + "Project name \"{}\" contain not supported symbols" + ).format(value) + is_valid = False + + self._project_name_is_valid = is_valid + self.project_name_label.setText(message) + self._enable_button() + + def _update_valid_project_code(self, value): + message = "" + is_valid = True + if not value: + message = "Project code is empty" + is_valid = False + + elif value in self.invalid_project_names: + message = "Project code \"{}\" already exist".format(value) + is_valid = False + + elif not PROJECT_NAME_REGEX.match(value): + message = ( + "Project code \"{}\" contain not supported symbols" + ).format(value) + is_valid = False + + self._project_code_is_valid = is_valid + self.project_code_label.setText(message) + self._enable_button() + + def _enable_button(self): + self.ok_btn.setEnabled( + self._project_name_is_valid and self._project_code_is_valid + ) + + def _on_cancel_clicked(self): + self.done(0) + + def _on_ok_clicked(self): + if not self._project_name_is_valid or not self._project_code_is_valid: + return + + project_name = self.project_name_input.text() + project_code = self.project_code_input.text() + library_project = self.library_project_input.isChecked() + create_project(project_name, project_code, library_project, self.dbcon) + + self.done(1) + + def _get_existing_projects(self): + project_names = set() + project_codes = set() + for project_name in self.dbcon.database.collection_names(): + # Each collection will have exactly one project document + project_doc = self.dbcon.database[project_name].find_one( + {"type": "project"}, + {"name": 1, "data.code": 1} + ) + if not project_doc: + continue + + project_name = project_doc.get("name") + if not project_name: + continue + + project_names.add(project_name) + project_code = project_doc.get("data", {}).get("code") + if not project_code: + project_code = project_name + + project_codes.add(project_code) + return project_names, project_codes From 8ec20d1b7e67911b94602216ff470fe41331569e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 20:25:08 +0200 Subject: [PATCH 206/219] use CreateProjectDialog in project manager --- .../project_manager/project_manager/window.py | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 61000464eab..ad377c44031 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -5,7 +5,9 @@ HierarchyModel, HierarchySelectionModel, - HierarchyView + HierarchyView, + + CreateProjectDialog ) from .style import load_stylesheet, ResourceCache @@ -38,10 +40,15 @@ def __init__(self, parent=None): refresh_projects_btn.setToolTip("Refresh projects") refresh_projects_btn.setObjectName("RefreshBtn") + create_project_btn = QtWidgets.QPushButton( + "Create project...", project_widget + ) + project_layout = QtWidgets.QHBoxLayout(project_widget) project_layout.setContentsMargins(0, 0, 0, 0) project_layout.addWidget(project_combobox, 0) project_layout.addWidget(refresh_projects_btn, 0) + project_layout.addWidget(create_project_btn, 0) project_layout.addStretch(1) # Helper buttons @@ -100,6 +107,7 @@ def __init__(self, parent=None): main_layout.addWidget(buttons_widget) refresh_projects_btn.clicked.connect(self._on_project_refresh) + create_project_btn.clicked.connect(self._on_project_create) project_combobox.currentIndexChanged.connect(self._on_project_change) save_btn.clicked.connect(self._on_save_click) add_asset_btn.clicked.connect(self._on_add_asset) @@ -121,18 +129,18 @@ def __init__(self, parent=None): def _set_project(self, project_name=None): self.hierarchy_view.set_project(project_name) - def refresh_projects(self): - current_project = None - if self.project_combobox.count() > 0: - current_project = self.project_combobox.currentText() + def refresh_projects(self, project_name=None): + if project_name is None: + if self.project_combobox.count() > 0: + project_name = self.project_combobox.currentText() self.project_model.refresh() if self.project_combobox.count() == 0: return self._set_project() - if current_project: - row = self.project_combobox.findText(current_project) + if project_name: + row = self.project_combobox.findText(project_name) if row >= 0: self.project_combobox.setCurrentIndex(row) @@ -156,3 +164,15 @@ def _on_add_task(self): def show_message(self, message): # TODO add nicer message pop self.message_label.setText(message) + + def _on_project_create(self): + dialog = CreateProjectDialog(self) + dialog.exec_() + print(dialog.result()) + if dialog.result() != 1: + return + + project_name = dialog.project_name + print("Created project \"{}\"".format(project_name)) + self.show_message("Created project \"{}\"".format(project_name)) + self.refresh_projects(project_name) From 6fe08049d6730c240aeefc7dda30970b3072e281 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 20:26:36 +0200 Subject: [PATCH 207/219] make visualParent not required on asset doc --- openpype/tools/project_manager/project_manager/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 8f6fe040068..0259a75f210 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -216,7 +216,7 @@ def set_project(self, project_name, force=False): asset_docs_by_parent_id = collections.defaultdict(list) for asset_doc in asset_docs_by_id.values(): - parent_id = asset_doc["data"]["visualParent"] + parent_id = asset_doc["data"].get("visualParent") asset_docs_by_parent_id[parent_id].append(asset_doc) appending_queue = Queue() From 1850687160d26f85e821c502a712ac05b7813158 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 20:27:19 +0200 Subject: [PATCH 208/219] added CreateProjectDialog to init of project manager module --- openpype/tools/project_manager/project_manager/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/__init__.py b/openpype/tools/project_manager/project_manager/__init__.py index 34beec331d3..49ade4a989b 100644 --- a/openpype/tools/project_manager/project_manager/__init__.py +++ b/openpype/tools/project_manager/project_manager/__init__.py @@ -4,6 +4,7 @@ "HierarchyView", "ProjectModel", + "CreateProjectDialog", "HierarchyModel", "HierarchySelectionModel", @@ -21,6 +22,7 @@ from .constants import ( IDENTIFIER_ROLE ) +from .widgets import CreateProjectDialog from .view import HierarchyView from .model import ( ProjectModel, From 376c3dffed965025063d3dca59cdf386e502eb38 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 20:35:42 +0200 Subject: [PATCH 209/219] add filling of style data to stylesheets --- .../project_manager/style/__init__.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/style/__init__.py b/openpype/tools/project_manager/project_manager/style/__init__.py index f3b0e0f9c0f..a268d657f15 100644 --- a/openpype/tools/project_manager/project_manager/style/__init__.py +++ b/openpype/tools/project_manager/project_manager/style/__init__.py @@ -69,11 +69,24 @@ def get_icons(cls): def get_color(cls, color_name): return cls.colors[color_name] + @classmethod + def style_fill_data(cls): + output = {} + for color_name, color_value in cls.colors.items(): + key = "color:{}".format(color_name) + output[key] = color_value + return output + def load_stylesheet(): - style_path = os.path.join(os.path.dirname(__file__), "style.css") + current_dir = os.path.dirname(os.path.abspath(__file__)) + style_path = os.path.join(current_dir, "style.css") with open(style_path, "r") as style_file: stylesheet = style_file.read() + + for key, value in ResourceCache.style_fill_data().items(): + replacement_key = "{" + key + "}" + stylesheet = stylesheet.replace(replacement_key, value) return stylesheet From 77108c554245ac3d1b311f511fcac42b880eb43c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 20:44:53 +0200 Subject: [PATCH 210/219] added base of resources --- .../project_manager/style/__init__.py | 1 + .../style/images/combobox_arrow.png | Bin 0 -> 166 bytes .../style/images/combobox_arrow_disabled.png | Bin 0 -> 165 bytes .../project_manager/style/pyqt5_resources.py | 94 ++++++++++++++++++ .../style/pyside2_resources.py | 75 ++++++++++++++ .../project_manager/style/rc_resources.py | 12 +++ .../project_manager/style/resources.qrc | 6 ++ 7 files changed, 188 insertions(+) create mode 100644 openpype/tools/project_manager/project_manager/style/images/combobox_arrow.png create mode 100644 openpype/tools/project_manager/project_manager/style/images/combobox_arrow_disabled.png create mode 100644 openpype/tools/project_manager/project_manager/style/pyqt5_resources.py create mode 100644 openpype/tools/project_manager/project_manager/style/pyside2_resources.py create mode 100644 openpype/tools/project_manager/project_manager/style/rc_resources.py create mode 100644 openpype/tools/project_manager/project_manager/style/resources.qrc diff --git a/openpype/tools/project_manager/project_manager/style/__init__.py b/openpype/tools/project_manager/project_manager/style/__init__.py index a268d657f15..6fd7d304cb3 100644 --- a/openpype/tools/project_manager/project_manager/style/__init__.py +++ b/openpype/tools/project_manager/project_manager/style/__init__.py @@ -79,6 +79,7 @@ def style_fill_data(cls): def load_stylesheet(): + from . import rc_resources current_dir = os.path.dirname(os.path.abspath(__file__)) style_path = os.path.join(current_dir, "style.css") with open(style_path, "r") as style_file: diff --git a/openpype/tools/project_manager/project_manager/style/images/combobox_arrow.png b/openpype/tools/project_manager/project_manager/style/images/combobox_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..5805d9842bb3c8bdf9ae741ebabc690a4929585a GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRR!2%?ApR4f$QjEnx?oJHr&dIz4a+s35-CY>| zxA&jf59DzcctjR6FmMZlFeAgPITAoY_7YEDSN1y`;vAyZcdU741BJ9aT^vI=t|uoP zVCdoDDYjGKUczBuWT3#kjKjddz-flSK@mm~;ef4+85xf4WSw z1e^Sc1@brxJR*x37`TN&n2}-D90{Nxdx@v7EBhS|ac(A-+!@lDKp{;}7sn8e>&XcR z7pulY;Wn^IBG(*7A%~?b^VC!N=hHC=sm-IdEdjT~uc)I$ztaD0e F0ssj2CNKa1 literal 0 HcmV?d00001 diff --git a/openpype/tools/project_manager/project_manager/style/pyqt5_resources.py b/openpype/tools/project_manager/project_manager/style/pyqt5_resources.py new file mode 100644 index 00000000000..34b65512103 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/style/pyqt5_resources.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2) +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore + +qt_resource_data = b"\ +\x00\x00\x00\xa5\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\x9c\x53\x34\xfc\x5d\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x0b\x02\x04\x6d\ +\x98\x1b\x69\x00\x00\x00\x29\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ +\x00\x8c\x0c\x0c\xff\xcf\xa3\x08\x18\x32\x32\x30\x20\x0b\x32\x1a\ +\x32\x30\x30\x42\x98\x10\x41\x46\x43\x14\x13\x50\xb5\xa3\x01\x00\ +\xd6\x10\x07\xd2\x2f\x48\xdf\x4a\x00\x00\x00\x00\x49\x45\x4e\x44\ +\xae\x42\x60\x82\ +\x00\x00\x00\xa6\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\ +\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x15\x3b\xdc\ +\x3b\x0c\x9b\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\ +\x00\x8c\x0c\x0c\x73\x3e\x20\x0b\xa4\x08\x30\x32\x30\x20\x0b\xa6\ +\x08\x30\x30\x30\x42\x98\x10\xc1\x14\x01\x14\x13\x50\xb5\xa3\x01\ +\x00\xc6\xb9\x07\x90\x5d\x66\x1f\x83\x00\x00\x00\x00\x49\x45\x4e\ +\x44\xae\x42\x60\x82\ +" + +qt_resource_name = b"\ +\x00\x08\ +\x06\xc5\x8e\xa5\ +\x00\x6f\ +\x00\x70\x00\x65\x00\x6e\x00\x70\x00\x79\x00\x70\x00\x65\ +\x00\x06\ +\x07\x03\x7d\xc3\ +\x00\x69\ +\x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\ +\x00\x12\ +\x01\x2e\x03\x27\ +\x00\x63\ +\x00\x6f\x00\x6d\x00\x62\x00\x6f\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\ +\x00\x67\ +\x00\x1b\ +\x03\x5a\x32\x27\ +\x00\x63\ +\x00\x6f\x00\x6d\x00\x62\x00\x6f\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x64\x00\x69\ +\x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ +" + +qt_resource_struct_v1 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x16\x00\x02\x00\x00\x00\x02\x00\x00\x00\x03\ +\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa9\ +" + +qt_resource_struct_v2 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x16\x00\x02\x00\x00\x00\x02\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x76\x41\x9d\xa2\x35\ +\x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa9\ +\x00\x00\x01\x76\x41\x9d\xa2\x35\ +" + +qt_version = [int(v) for v in QtCore.qVersion().split('.')] +if qt_version < [5, 8, 0]: + rcc_version = 1 + qt_resource_struct = qt_resource_struct_v1 +else: + rcc_version = 2 + qt_resource_struct = qt_resource_struct_v2 + +def qInitResources(): + QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) diff --git a/openpype/tools/project_manager/project_manager/style/pyside2_resources.py b/openpype/tools/project_manager/project_manager/style/pyside2_resources.py new file mode 100644 index 00000000000..ecd5e7fa525 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/style/pyside2_resources.py @@ -0,0 +1,75 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 5.15.2 +# WARNING! All changes made in this file will be lost! + +from PySide2 import QtCore + +qt_resource_data = b"\ +\x00\x00\x00\xa5\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ +\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02bKGD\x00\x9cS4\xfc]\x00\x00\x00\x09p\ +HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x0b\x02\x04m\ +\x98\x1bi\x00\x00\x00)IDAT\x08\xd7c`\xc0\ +\x00\x8c\x0c\x0c\xff\xcf\xa3\x08\x18220 \x0b2\x1a\ +200B\x98\x10AFC\x14\x13P\xb5\xa3\x01\x00\ +\xd6\x10\x07\xd2/H\xdfJ\x00\x00\x00\x00IEND\ +\xaeB`\x82\ +\x00\x00\x00\xa6\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\ +\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\ +HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\ +\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x08\x15;\xdc\ +;\x0c\x9b\x00\x00\x00*IDAT\x08\xd7c`\xc0\ +\x00\x8c\x0c\x0cs> \x0b\xa4\x08020 \x0b\xa6\ +\x08000B\x98\x10\xc1\x14\x01\x14\x13P\xb5\xa3\x01\ +\x00\xc6\xb9\x07\x90]f\x1f\x83\x00\x00\x00\x00IEN\ +D\xaeB`\x82\ +" + +qt_resource_name = b"\ +\x00\x08\ +\x06\xc5\x8e\xa5\ +\x00o\ +\x00p\x00e\x00n\x00p\x00y\x00p\x00e\ +\x00\x06\ +\x07\x03}\xc3\ +\x00i\ +\x00m\x00a\x00g\x00e\x00s\ +\x00\x12\ +\x01.\x03'\ +\x00c\ +\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\ +\x00g\ +\x00\x1b\ +\x03Z2'\ +\x00c\ +\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00d\x00i\ +\x00s\x00a\x00b\x00l\x00e\x00d\x00.\x00p\x00n\x00g\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x16\x00\x02\x00\x00\x00\x02\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00(\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01vA\x9d\xa25\ +\x00\x00\x00R\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa9\ +\x00\x00\x01vA\x9d\xa25\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) diff --git a/openpype/tools/project_manager/project_manager/style/rc_resources.py b/openpype/tools/project_manager/project_manager/style/rc_resources.py new file mode 100644 index 00000000000..19dcda95647 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/style/rc_resources.py @@ -0,0 +1,12 @@ +import Qt + + +resources = None +if Qt.__binding__ == "PySide2": + from . import pyside2_resources as resources +elif Qt.__binding__ == "PyQt5": + from . import pyqt5_resources as resources + + +if resources is not None: + resources.qInitResources() diff --git a/openpype/tools/project_manager/project_manager/style/resources.qrc b/openpype/tools/project_manager/project_manager/style/resources.qrc new file mode 100644 index 00000000000..9281c694797 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/style/resources.qrc @@ -0,0 +1,6 @@ + + + images/combobox_arrow.png + images/combobox_arrow_disabled.png + + From 807eefee6dbdb54044c9a33b96c05ebbcbc3da44 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 20:47:12 +0200 Subject: [PATCH 211/219] hide project name messages without text --- openpype/tools/project_manager/project_manager/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index c650a4a8b70..263b15daeb3 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -197,6 +197,7 @@ def _update_valid_project_name(self, value): self._project_name_is_valid = is_valid self.project_name_label.setText(message) + self.project_name_label.setVisible(bool(message)) self._enable_button() def _update_valid_project_code(self, value): From 0b159479facbb4e5b7cc19ca14223b610e0637b6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 20:48:42 +0200 Subject: [PATCH 212/219] removed debug prints --- openpype/tools/project_manager/project_manager/window.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index ad377c44031..a800214517c 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -168,11 +168,9 @@ def show_message(self, message): def _on_project_create(self): dialog = CreateProjectDialog(self) dialog.exec_() - print(dialog.result()) if dialog.result() != 1: return project_name = dialog.project_name - print("Created project \"{}\"".format(project_name)) self.show_message("Created project \"{}\"".format(project_name)) self.refresh_projects(project_name) From 5134ae743b7d27ef6b8c2a50388cbe8ef63fb2ea Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 20:53:58 +0200 Subject: [PATCH 213/219] hound fixes --- openpype/lib/avalon_context.py | 2 +- .../project_manager/style/__init__.py | 5 +++- .../project_manager/style/pyqt5_resources.py | 7 ++++++ .../style/pyside2_resources.py | 5 ++++ .../project_manager/style/qrc_resources.py | 25 +++++++++++++++++++ .../project_manager/style/rc_resources.py | 12 --------- 6 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 openpype/tools/project_manager/project_manager/style/qrc_resources.py delete mode 100644 openpype/tools/project_manager/project_manager/style/rc_resources.py diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index c53b028a44c..2a7c58c4eec 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -102,7 +102,7 @@ def create_project( try: # Validate created project document validate(project_doc) - except Exception as exc: + except Exception: # Remove project if is not valid database[project_name].delete_one({"type": "project"}) raise diff --git a/openpype/tools/project_manager/project_manager/style/__init__.py b/openpype/tools/project_manager/project_manager/style/__init__.py index 6fd7d304cb3..b686967dddb 100644 --- a/openpype/tools/project_manager/project_manager/style/__init__.py +++ b/openpype/tools/project_manager/project_manager/style/__init__.py @@ -79,7 +79,10 @@ def style_fill_data(cls): def load_stylesheet(): - from . import rc_resources + from . import qrc_resources + + qrc_resources.qInitResources() + current_dir = os.path.dirname(os.path.abspath(__file__)) style_path = os.path.join(current_dir, "style.css") with open(style_path, "r") as style_file: diff --git a/openpype/tools/project_manager/project_manager/style/pyqt5_resources.py b/openpype/tools/project_manager/project_manager/style/pyqt5_resources.py index 34b65512103..bb26221a46a 100644 --- a/openpype/tools/project_manager/project_manager/style/pyqt5_resources.py +++ b/openpype/tools/project_manager/project_manager/style/pyqt5_resources.py @@ -8,6 +8,7 @@ from PyQt5 import QtCore + qt_resource_data = b"\ \x00\x00\x00\xa5\ \x89\ @@ -37,6 +38,7 @@ \x44\xae\x42\x60\x82\ " + qt_resource_name = b"\ \x00\x08\ \x06\xc5\x8e\xa5\ @@ -58,6 +60,7 @@ \x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ " + qt_resource_struct_v1 = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ @@ -66,6 +69,7 @@ \x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa9\ " + qt_resource_struct_v2 = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\ @@ -79,6 +83,7 @@ \x00\x00\x01\x76\x41\x9d\xa2\x35\ " + qt_version = [int(v) for v in QtCore.qVersion().split('.')] if qt_version < [5, 8, 0]: rcc_version = 1 @@ -87,8 +92,10 @@ rcc_version = 2 qt_resource_struct = qt_resource_struct_v2 + def qInitResources(): QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + def qCleanupResources(): QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) diff --git a/openpype/tools/project_manager/project_manager/style/pyside2_resources.py b/openpype/tools/project_manager/project_manager/style/pyside2_resources.py index ecd5e7fa525..a8a368601df 100644 --- a/openpype/tools/project_manager/project_manager/style/pyside2_resources.py +++ b/openpype/tools/project_manager/project_manager/style/pyside2_resources.py @@ -5,6 +5,7 @@ from PySide2 import QtCore + qt_resource_data = b"\ \x00\x00\x00\xa5\ \x89\ @@ -34,6 +35,7 @@ D\xaeB`\x82\ " + qt_resource_name = b"\ \x00\x08\ \x06\xc5\x8e\xa5\ @@ -55,6 +57,7 @@ \x00s\x00a\x00b\x00l\x00e\x00d\x00.\x00p\x00n\x00g\ " + qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\ @@ -68,8 +71,10 @@ \x00\x00\x01vA\x9d\xa25\ " + def qInitResources(): QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + def qCleanupResources(): QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) diff --git a/openpype/tools/project_manager/project_manager/style/qrc_resources.py b/openpype/tools/project_manager/project_manager/style/qrc_resources.py new file mode 100644 index 00000000000..be859cae464 --- /dev/null +++ b/openpype/tools/project_manager/project_manager/style/qrc_resources.py @@ -0,0 +1,25 @@ +import Qt + + +initialized = False +resources = None +if Qt.__binding__ == "PySide2": + from . import pyside2_resources as resources +elif Qt.__binding__ == "PyQt5": + from . import pyqt5_resources as resources + + +def qInitResources(): + global resources + global initialized + if resources is not None and not initialized: + initialized = True + resources.qInitResources() + + +def qCleanupResources(): + global resources + global initialized + if resources is not None: + initialized = False + resources.qCleanupResources() diff --git a/openpype/tools/project_manager/project_manager/style/rc_resources.py b/openpype/tools/project_manager/project_manager/style/rc_resources.py deleted file mode 100644 index 19dcda95647..00000000000 --- a/openpype/tools/project_manager/project_manager/style/rc_resources.py +++ /dev/null @@ -1,12 +0,0 @@ -import Qt - - -resources = None -if Qt.__binding__ == "PySide2": - from . import pyside2_resources as resources -elif Qt.__binding__ == "PyQt5": - from . import pyqt5_resources as resources - - -if resources is not None: - resources.qInitResources() From 3600686ab06d83ef723d9bee4b82c07d93d3a349 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 20:59:41 +0200 Subject: [PATCH 214/219] a little bit more cleanup --- .../project_manager/style/pyqt5_resources.py | 8 ++++++-- .../project_manager/style/pyside2_resources.py | 8 ++++++-- .../project_manager/style/qrc_resources.py | 7 +++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/style/pyqt5_resources.py b/openpype/tools/project_manager/project_manager/style/pyqt5_resources.py index bb26221a46a..836934019d4 100644 --- a/openpype/tools/project_manager/project_manager/style/pyqt5_resources.py +++ b/openpype/tools/project_manager/project_manager/style/pyqt5_resources.py @@ -94,8 +94,12 @@ def qInitResources(): - QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData( + rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data + ) def qCleanupResources(): - QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData( + rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data + ) diff --git a/openpype/tools/project_manager/project_manager/style/pyside2_resources.py b/openpype/tools/project_manager/project_manager/style/pyside2_resources.py index a8a368601df..b73d5e334a5 100644 --- a/openpype/tools/project_manager/project_manager/style/pyside2_resources.py +++ b/openpype/tools/project_manager/project_manager/style/pyside2_resources.py @@ -73,8 +73,12 @@ def qInitResources(): - QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData( + 0x03, qt_resource_struct, qt_resource_name, qt_resource_data + ) def qCleanupResources(): - QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData( + 0x03, qt_resource_struct, qt_resource_name, qt_resource_data + ) diff --git a/openpype/tools/project_manager/project_manager/style/qrc_resources.py b/openpype/tools/project_manager/project_manager/style/qrc_resources.py index be859cae464..a9e219c9ad4 100644 --- a/openpype/tools/project_manager/project_manager/style/qrc_resources.py +++ b/openpype/tools/project_manager/project_manager/style/qrc_resources.py @@ -23,3 +23,10 @@ def qCleanupResources(): if resources is not None: initialized = False resources.qCleanupResources() + + +__all__ = ( + "resources", + "qInitResources", + "qCleanupResources" +) From 42c950a033bdfa22e8b193ce6814cef3f9ed8e7b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 22:26:02 +0200 Subject: [PATCH 215/219] fix new asset name validation --- openpype/tools/project_manager/project_manager/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 0259a75f210..66693b292fa 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -396,7 +396,7 @@ def add_new_asset(self, source_index): result = self.add_item(new_child, item, new_row) if result is not None: # WARNING Expecting result is index for column 0 ("name") - new_name = result.data(QtCore.Qt.DisplayRole) + new_name = result.data(QtCore.Qt.EditRole) self._validate_asset_duplicity(new_name) return result From 229b2a17959f37e1dc258953690b1d2e6b1888e5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 May 2021 22:30:11 +0200 Subject: [PATCH 216/219] even more fixes of name getting --- openpype/tools/project_manager/project_manager/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 66693b292fa..6e20dd368fe 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -440,7 +440,7 @@ def add_items(self, items, parent=None, start_row=None): parent.add_child(item, row) if isinstance(item, AssetItem): - name = item.data(QtCore.Qt.DisplayRole, "name") + name = item.data(QtCore.Qt.EditRole, "name") self._asset_items_by_name[name].add(item.id) if item.id not in self._items_by_id: @@ -1810,7 +1810,7 @@ def _remove_task(self, item): _item.setData(False, DUPLICATED_ROLE) def _rename_task(self, item): - new_name = item.data(QtCore.Qt.DisplayRole, "name").lower() + new_name = item.data(QtCore.Qt.EditRole, "name").lower() item_id = item.data(IDENTIFIER_ROLE) prev_name = self._task_name_by_item_id[item_id] if new_name == prev_name: From 0795df31c4c905bbff08d64644f15f7d2b56799a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 18 May 2021 09:38:12 +0200 Subject: [PATCH 217/219] shift + enter create asset as sibling --- .../tools/project_manager/project_manager/view.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index e96176debd3..70af11e68d9 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -409,8 +409,8 @@ def add_task(self, parent_index=None): return self._source_model.add_new_task(parent_index) - def _add_asset_and_edit(self): - new_index = self.add_asset() + def _add_asset_and_edit(self, parent_index=None): + new_index = self.add_asset(parent_index) if new_index is None: return @@ -446,7 +446,13 @@ def _add_task_and_edit(self): self.edit(task_type_index) def _on_shift_enter_pressed(self): - self._add_asset_and_edit() + parent_index = self.currentIndex() + if not parent_index.isValid(): + return + + if parent_index.data(ITEM_TYPE_ROLE) == "asset": + parent_index = parent_index.parent() + self._add_asset_and_edit(parent_index) def _on_up_ctrl_pressed(self): indexes = self.selectedIndexes() From 84b42581ff1fe671fd983c839400db6d66f9f31d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 18 May 2021 10:36:36 +0200 Subject: [PATCH 218/219] added value cleanup to filter combobox --- .../project_manager/delegates.py | 4 +++ .../project_manager/widgets.py | 27 +++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index f53e0442f1f..51edff028f7 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -130,6 +130,10 @@ def setEditorData(self, editor, index): if index >= 0: editor.setCurrentIndex(index) + def setModelData(self, editor, model, index): + editor.value_cleanup() + super(TypeDelegate, self).setModelData(editor, model, index) + class ToolsDelegate(QtWidgets.QStyledItemDelegate): def __init__(self, tools_cache, *args, **kwargs): diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index 263b15daeb3..503ecbb6bcc 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -65,20 +65,31 @@ def focusInEvent(self, event): super(FilterComboBox, self).focusInEvent(event) self.lineEdit().selectAll() - def focusOutEvent(self, event): - idx = self.currentIndex() - if idx > -1: - index = self.model().index(idx, 0) - text = index.data(QtCore.Qt.DisplayRole) - if text != self.lineEdit().text(): - self.lineEdit().setText(text) - super(FilterComboBox, self).focusOutEvent(event) + def value_cleanup(self): + text = self.lineEdit().text() + idx = self.findText(text) + if idx < 0: + count = self._completer.completionModel().rowCount() + if count > 0: + index = self._completer.completionModel().index(0, 0) + text = index.data(QtCore.Qt.DisplayRole) + idx = self.findText(text) + + if idx < 0: + idx = 0 + self.setCurrentIndex(idx) def on_completer_activated(self, text): if text: index = self.findText(text) self.setCurrentIndex(index) + def keyPressEvent(self, event): + if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): + self.value_cleanup() + + super(FilterComboBox, self).keyPressEvent(event) + def setModel(self, model): super(FilterComboBox, self).setModel(model) self._filter_proxy_model.setSourceModel(model) From c5525ab6936dee33915f88d5f97aec5bd09e806c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 18 May 2021 11:22:19 +0200 Subject: [PATCH 219/219] set last value if current is invalid --- openpype/tools/project_manager/project_manager/widgets.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index 503ecbb6bcc..9c57febcf68 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -40,6 +40,8 @@ class FilterComboBox(QtWidgets.QComboBox): def __init__(self, parent=None): super(FilterComboBox, self).__init__(parent) + self._last_value = None + self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setEditable(True) @@ -63,6 +65,7 @@ def __init__(self, parent=None): def focusInEvent(self, event): super(FilterComboBox, self).focusInEvent(event) + self._last_value = self.lineEdit().text() self.lineEdit().selectAll() def value_cleanup(self): @@ -74,6 +77,8 @@ def value_cleanup(self): index = self._completer.completionModel().index(0, 0) text = index.data(QtCore.Qt.DisplayRole) idx = self.findText(text) + elif self._last_value is not None: + idx = self.findText(self._last_value) if idx < 0: idx = 0