From d705ef56a08bbc804fbf38caabf139aab3e58fbb Mon Sep 17 00:00:00 2001 From: Ivan Titov Date: Sat, 27 Mar 2021 01:10:36 +0500 Subject: [PATCH] Operator Manager #22 --- .../houdini_tdk/operator_manager/delegate.py | 4 +- .../operator_manager/model/library.py | 10 +- .../houdini_tdk/operator_manager/view.py | 178 +++--------- .../houdini_tdk/operator_manager/window.py | 266 ++++++++++++++++++ 4 files changed, 319 insertions(+), 139 deletions(-) diff --git a/python2.7libs/houdini_tdk/operator_manager/delegate.py b/python2.7libs/houdini_tdk/operator_manager/delegate.py index bbce778..36adf6f 100644 --- a/python2.7libs/houdini_tdk/operator_manager/delegate.py +++ b/python2.7libs/houdini_tdk/operator_manager/delegate.py @@ -39,13 +39,13 @@ def paint(self, painter, option, index): painter.save() row_rect = QRectF(option.rect) - row_rect.setLeft(45) row_rect.setRight(option.widget.width()) icon_rect = QRectF(option.rect) icon_rect.setRight(45) text_rect = QRectF(row_rect) + text_rect.setLeft(45) text_rect.setRight(row_rect.right() - 30) selection_rect = QRectF(row_rect) @@ -56,7 +56,7 @@ def paint(self, painter, option, index): super(OperatorManagerLibraryDelegate, self).paint(painter, option, index) painter.restore() - if option.state & QStyle.State_Selected and index.column() == 0: + if option.state & QStyle.State_Selected: painter.fillRect(selection_rect, option.palette.highlight()) metrics = painter.fontMetrics() diff --git a/python2.7libs/houdini_tdk/operator_manager/model/library.py b/python2.7libs/houdini_tdk/operator_manager/model/library.py index 8bfa381..2cce3f7 100644 --- a/python2.7libs/houdini_tdk/operator_manager/model/library.py +++ b/python2.7libs/houdini_tdk/operator_manager/model/library.py @@ -98,7 +98,9 @@ def updateData(self): self.endResetModel() def hasChildren(self, parent): - if not parent.isValid() or not parent.parent().isValid(): + if not parent.isValid(): + return bool(self._libraries) + elif isinstance(parent.internalPointer(), basestring): return True return False @@ -117,11 +119,11 @@ def parent(self, index): return QModelIndex() item = index.internalPointer() - if not isinstance(item, basestring): + if isinstance(item, basestring): + return QModelIndex() + else: lib_path = item.libraryFilePath() return self.createIndex(self._definitions[lib_path].index(item), 0, lib_path) - else: - return QModelIndex() def index(self, row, column, parent): if not self.hasIndex(row, column, parent): diff --git a/python2.7libs/houdini_tdk/operator_manager/view.py b/python2.7libs/houdini_tdk/operator_manager/view.py index 3983a16..8e8f2a8 100644 --- a/python2.7libs/houdini_tdk/operator_manager/view.py +++ b/python2.7libs/houdini_tdk/operator_manager/view.py @@ -27,12 +27,8 @@ from PySide2.QtGui import * from PySide2.QtCore import * -import hou - from .delegate import OperatorManagerLibraryDelegate -ICON_SIZE = 16 - class OperatorManagerView(QTreeView): def __init__(self): @@ -47,132 +43,48 @@ def __init__(self): self.setIconSize(QSize(16, 16)) self.setItemDelegate(OperatorManagerLibraryDelegate()) - # self.setAlternatingRowColors(True) # Disabled due to a bug that clipping delegate's text - - self.__createActions() - self.__createContextMenus() - - def __createActions(self): - self._open_location_action = QAction(hou.qt.Icon('BUTTONS_folder', ICON_SIZE, ICON_SIZE), 'Open location') - - self._install_library_action = QAction('Install') - - self._uninstall_library_action = QAction('Uninstall') - - self._reinstall_library_action = QAction(hou.qt.Icon('MISC_loading', ICON_SIZE, ICON_SIZE), 'Reinstall') - - self._merge_with_library_action = QAction('Merge with Library') - - self._pack_action = QAction(hou.qt.Icon('DESKTOP_otl', ICON_SIZE, ICON_SIZE), 'Pack') - - self._unpack_action = QAction(hou.qt.Icon('DESKTOP_expanded_otl', ICON_SIZE, ICON_SIZE), 'Unpack') - - self._backup_list_action = QAction(hou.qt.Icon('BUTTONS_history', ICON_SIZE, ICON_SIZE), 'Backups...') - - self._open_type_properties_action = QAction(hou.qt.Icon('BUTTONS_gear_mini', ICON_SIZE, ICON_SIZE), - 'Open type properties...') - - self._change_instances_to_action = QAction('Change instances to...') - - self._run_hda_doctor_action = QAction(hou.qt.Icon('SOP_polydoctor', ICON_SIZE, ICON_SIZE), 'Run HDA Doctor...') - - self._find_usages_action = QAction(hou.qt.Icon('BUTTONS_search', ICON_SIZE, ICON_SIZE), 'Find usages...') - - self._find_dependencies_action = QAction(hou.qt.Icon('PANETYPES_network', ICON_SIZE, ICON_SIZE), - 'Find dependencies...') - - self._show_statistics_action = QAction(hou.qt.Icon('BUTTONS_info', ICON_SIZE, ICON_SIZE), 'Show statistics...') - - self._create_instance_action = QAction('Instance') - - self._create_new_hda_action = QAction(hou.qt.Icon('SOP_compile_begin', ICON_SIZE, ICON_SIZE), - 'New HDA...') - - self._create_new_version_action = QAction(hou.qt.Icon('BUTTONS_multi_insertbefore', ICON_SIZE, ICON_SIZE), - 'New version...') - - self._create_black_box_action = QAction(hou.qt.Icon('NETVIEW_hda_locked_badge', ICON_SIZE, ICON_SIZE), - 'Black box...') - - self._copy_action = QAction(hou.qt.Icon('BUTTONS_copy', ICON_SIZE, ICON_SIZE), 'Copy...') - - self._move_action = QAction(hou.qt.Icon('BUTTONS_move_to_right', ICON_SIZE, ICON_SIZE), 'Move...') - - self._rename_action = QAction(hou.qt.Icon('MISC_rename', ICON_SIZE, ICON_SIZE), 'Rename...') - - self._add_alias_action = QAction(hou.qt.Icon('BUTTONS_tag', ICON_SIZE, ICON_SIZE), 'Add alias...') - - self._delete_action = QAction(hou.qt.Icon('BUTTONS_delete', ICON_SIZE, ICON_SIZE), 'Delete') - - self._hide_action = QAction(hou.qt.Icon('BUTTONS_close', ICON_SIZE, ICON_SIZE), 'Hide') - - self._deprecate_action = QAction(hou.qt.Icon('BUTTONS_do_not', ICON_SIZE, ICON_SIZE), 'Deprecate') - - self._compare_action = QAction(hou.qt.Icon('BUTTONS_restore', ICON_SIZE, ICON_SIZE), 'Compare...') - - def __createContextMenus(self): - self._library_menu = QMenu(self) - - self._library_menu.addAction(self._open_location_action) - self._library_menu.addSeparator() - self._library_menu.addAction(self._install_library_action) - self._library_menu.addAction(self._uninstall_library_action) - self._library_menu.addAction(self._reinstall_library_action) - self._library_menu.addSeparator() - self._library_menu.addAction(self._merge_with_library_action) - self._library_menu.addAction(self._pack_action) - self._library_menu.addAction(self._unpack_action) - self._library_menu.addSeparator() - self._library_menu.addAction(self._backup_list_action) - - self._definition_menu = QMenu(self) - - self._definition_menu.addAction(self._open_type_properties_action) - self._definition_menu.addAction(self._change_instances_to_action) - - self._definition_inspect_menu = QMenu('Inspect', self) - self._definition_menu.addMenu(self._definition_inspect_menu) - - self._definition_inspect_menu.addAction(self._run_hda_doctor_action) - self._definition_inspect_menu.addAction(self._find_usages_action) - self._definition_inspect_menu.addAction(self._find_dependencies_action) - self._definition_inspect_menu.addAction(self._show_statistics_action) - - self._definition_create_menu = QMenu('Create', self) - self._definition_menu.addMenu(self._definition_create_menu) - - self._definition_create_menu.addAction(self._create_instance_action) - self._definition_create_menu.addAction(self._create_new_hda_action) - self._definition_create_menu.addAction(self._create_new_version_action) - self._definition_create_menu.addAction(self._create_black_box_action) - - self._definition_edit_menu = QMenu('Edit', self) - self._definition_menu.addMenu(self._definition_edit_menu) - - self._definition_edit_menu.addAction(self._copy_action) - self._definition_edit_menu.addSeparator() - self._definition_edit_menu.addAction(self._move_action) - self._definition_edit_menu.addAction(self._rename_action) - self._definition_edit_menu.addAction(self._add_alias_action) - self._definition_edit_menu.addSeparator() - self._definition_edit_menu.addAction(self._delete_action) - self._definition_edit_menu.addAction(self._hide_action) - self._definition_edit_menu.addAction(self._deprecate_action) - - self._definition_menu.addAction(self._compare_action) - - self.setContextMenuPolicy(Qt.CustomContextMenu) - self.customContextMenuRequested.connect(self.showContextMenu) - - def showContextMenu(self): - index = self.currentIndex() - - if not index.isValid(): - return - - if isinstance(index.data(Qt.UserRole), basestring): - self._library_menu.exec_(QCursor.pos()) - else: - icon = index.model().index(index.row(), 0, index.parent()).data(Qt.DecorationRole) - self._create_instance_action.setIcon(icon) - self._definition_menu.exec_(QCursor.pos()) + # self.setAlternatingRowColors(True) # Todo: Disabled due to a bug that clipping delegate's text + + self.setSelectionBehavior(QAbstractItemView.SelectRows) + self.setSelectionMode(QAbstractItemView.ExtendedSelection) + + def hasSelection(self): + """Returns True if selection model has selected items, False otherwise.""" + return self.selectionModel().hasSelection() + + def isSingleSelection(self): + """Returns True if selection contains only one row.""" + return self.hasSelection() and len(self.selectionModel().selectedRows(0)) == 1 + + def isMultipleSelection(self): + """Returns True if selection contains more than one row.""" + return self.hasSelection() and len(self.selectionModel().selectedRows(0)) > 1 + + def selectedIndex(self): + """ + Returns a single selected index. + Should be used only when isSingleSelection is True. + + Raised IndexError if no selection. + """ + return self.selectionModel().selectedIndexes()[0] + + def indexDepth(self, index): + """Returns level of nesting of the index. Root has level 0.""" + depth = 0 + while index.isValid(): + index = index.parent() + depth += 1 + return depth + + def deselectDifferingDepth(self, target_index): + """Remove items with differing level of nesting from the selection model.""" + selection_model = self.selectionModel() + target_depth = self.indexDepth(target_index) + for index in selection_model.selectedIndexes(): + depth = self.indexDepth(index) + if depth != target_depth: + selection_model.select(index, QItemSelectionModel.Deselect) + + def deselectDifferringParent(self, target_index): + raise NotImplementedError diff --git a/python2.7libs/houdini_tdk/operator_manager/window.py b/python2.7libs/houdini_tdk/operator_manager/window.py index 630a85c..a172a14 100644 --- a/python2.7libs/houdini_tdk/operator_manager/window.py +++ b/python2.7libs/houdini_tdk/operator_manager/window.py @@ -31,10 +31,13 @@ from ..widgets import FilterField from ..fuzzy_proxy_model import FuzzyProxyModel +from ..utils import openFileLocation from .model import OperatorManagerLibraryModel from .view import OperatorManagerView from .model.library import TextRole +ICON_SIZE = 16 + class OperatorManagerWindow(QWidget): def __init__(self, parent=hou.qt.mainWindow()): @@ -57,6 +60,7 @@ def __init__(self, parent=hou.qt.mainWindow()): self.filter_proxy_model = FuzzyProxyModel(self, TextRole, TextRole) self.filter_proxy_model.setDynamicSortFilter(True) + # Order is dictated by the weights of the fuzzy match function (bigger = better) self.filter_proxy_model.sort(0, Qt.DescendingOrder) self.filter_proxy_model.setSourceModel(self.model) @@ -64,9 +68,271 @@ def __init__(self, parent=hou.qt.mainWindow()): self.view.setModel(self.filter_proxy_model) layout.addWidget(self.view) + self.__createActions() + self.__createContextMenus() + def _onFilterChange(self, pattern): + """Delivers pattern from filter field to filter proxy model.""" # Pre-collapse all to speed up live filtering self.view.collapseAll() self.filter_proxy_model.setPattern(pattern) if pattern: self.view.expandAll() + + def _onOpenLocation(self): + """ + Opens location of the selected library or library of the selected node type. + Currently, supported single selection only. Multiple selection is ignored. + """ + if self.view.isSingleSelection(): + index = self.view.selectedIndex() + openFileLocation(index.data(Qt.UserRole)) + + def _onInstall(self): + raise NotImplementedError + + def _onUninstall(self): + """ + Uninstall all node types containing in the selected library from the current Houdini session. + Currently, supported single selection only. Multiple selection is ignored. + """ + if self.view.isSingleSelection(): + index = self.view.selectedIndex() + lib_file_path = index.data(Qt.UserRole) + hou.hda.uninstallFile(lib_file_path) + # Todo: Change item state to uninstalled + + def _onReload(self): + """ + Reload the contents of an HDA file, loading any updated digital asset definitions inside it. + Currently, supported single selection only. Multiple selection is ignored. + """ + if self.view.isSingleSelection(): + index = self.view.selectedIndex() + lib_file_path = index.data(Qt.UserRole) + hou.hda.reloadFile(lib_file_path) + + def _onMergeWithLibrary(self): + raise NotImplementedError + + def _onPack(self): + raise NotImplementedError + + def _onUnpack(self): + raise NotImplementedError + + def _onBackupList(self): + raise NotImplementedError + + def _onOpenTypeProperties(self): + """ + Opens Type Properties window for the selected definition node type. + Currently, supported single selection only. Multiple selection is ignored. + """ + if self.view.isSingleSelection(): + index = self.view.selectedIndex() + definition = index.data(Qt.UserRole) + hou.ui.openTypePropertiesDialog(definition.nodeType()) + + def _onChangeInstancesTo(self): + raise NotImplementedError + + def _onRunHDADoctor(self): + raise NotImplementedError + + def _onFindUsages(self): + raise NotImplementedError + + def _onFindDependencies(self): + raise NotImplementedError + + def _onShowStatistics(self): + raise NotImplementedError + + def _onCreateInstance(self): + raise NotImplementedError + + def _onCreateNewHDA(self): + raise NotImplementedError + + def _onCreateNewVersion(self): + raise NotImplementedError + + def _onCreateBlackBox(self): + raise NotImplementedError + + def _onCopy(self): + raise NotImplementedError + + def _onMove(self): + raise NotImplementedError + + def _onRename(self): + raise NotImplementedError + + def _onAddAlias(self): + raise NotImplementedError + + def _onDelete(self): + raise NotImplementedError + + def _onHide(self): + raise NotImplementedError + + def _onDeprecate(self): + raise NotImplementedError + + def _onCompare(self): + raise NotImplementedError + + def __createActions(self): + self._open_location_action = QAction(hou.qt.Icon('BUTTONS_folder', ICON_SIZE, ICON_SIZE), 'Open location') + self._open_location_action.triggered.connect(self._onOpenLocation) + + self._install_library_action = QAction('Install') + self._install_library_action.triggered.connect(self._onInstall) + + self._uninstall_library_action = QAction('Uninstall') + self._uninstall_library_action.triggered.connect(self._onUninstall) + + self._reload_library_action = QAction(hou.qt.Icon('MISC_loading', ICON_SIZE, ICON_SIZE), 'Reload') + self._reload_library_action.triggered.connect(self._onReload) + + self._merge_with_library_action = QAction('Merge with Library') + self._merge_with_library_action.triggered.connect(self._onMergeWithLibrary) + + self._pack_action = QAction(hou.qt.Icon('DESKTOP_otl', ICON_SIZE, ICON_SIZE), 'Pack') + self._pack_action.triggered.connect(self._onPack) + + self._unpack_action = QAction(hou.qt.Icon('DESKTOP_expanded_otl', ICON_SIZE, ICON_SIZE), 'Unpack') + self._unpack_action.triggered.connect(self._onUnpack) + + self._backup_list_action = QAction(hou.qt.Icon('BUTTONS_history', ICON_SIZE, ICON_SIZE), 'Backups...') + self._backup_list_action.triggered.connect(self._onBackupList) + + self._open_type_properties_action = QAction(hou.qt.Icon('BUTTONS_gear_mini', ICON_SIZE, ICON_SIZE), + 'Open type properties...') + self._open_type_properties_action.triggered.connect(self._onOpenTypeProperties) + + self._change_instances_to_action = QAction('Change instances to...') + self._change_instances_to_action.triggered.connect(self._onChangeInstancesTo) + + self._run_hda_doctor_action = QAction(hou.qt.Icon('SOP_polydoctor', ICON_SIZE, ICON_SIZE), 'Run HDA Doctor...') + self._run_hda_doctor_action.triggered.connect(self._onRunHDADoctor) + + self._find_usages_action = QAction(hou.qt.Icon('BUTTONS_search', ICON_SIZE, ICON_SIZE), 'Find usages...') + self._find_usages_action.triggered.connect(self._onFindUsages) + + self._find_dependencies_action = QAction(hou.qt.Icon('PANETYPES_network', ICON_SIZE, ICON_SIZE), + 'Find dependencies...') + self._find_dependencies_action.triggered.connect(self._onFindDependencies) + + self._show_statistics_action = QAction(hou.qt.Icon('BUTTONS_info', ICON_SIZE, ICON_SIZE), 'Show statistics...') + self._show_statistics_action.triggered.connect(self._onShowStatistics) + + self._create_instance_action = QAction('Instance') + self._create_instance_action.triggered.connect(self._onCreateInstance) + + self._create_new_hda_action = QAction(hou.qt.Icon('SOP_compile_begin', ICON_SIZE, ICON_SIZE), 'New HDA...') + self._create_new_hda_action.triggered.connect(self._onCreateNewHDA) + + self._create_new_version_action = QAction(hou.qt.Icon('BUTTONS_multi_insertbefore', ICON_SIZE, ICON_SIZE), + 'New version...') + self._create_new_version_action.triggered.connect(self._onCreateNewVersion) + + self._create_black_box_action = QAction(hou.qt.Icon('NETVIEW_hda_locked_badge', ICON_SIZE, ICON_SIZE), + 'Black box...') + self._create_black_box_action.triggered.connect(self._onCreateBlackBox) + + self._copy_action = QAction(hou.qt.Icon('BUTTONS_copy', ICON_SIZE, ICON_SIZE), 'Copy...') + self._copy_action.triggered.connect(self._onCopy) + + self._move_action = QAction(hou.qt.Icon('BUTTONS_move_to_right', ICON_SIZE, ICON_SIZE), 'Move...') + self._move_action.triggered.connect(self._onMove) + + self._rename_action = QAction(hou.qt.Icon('MISC_rename', ICON_SIZE, ICON_SIZE), 'Rename...') + self._rename_action.triggered.connect(self._onRename) + + self._add_alias_action = QAction(hou.qt.Icon('BUTTONS_tag', ICON_SIZE, ICON_SIZE), 'Add alias...') + self._add_alias_action.triggered.connect(self._onAddAlias) + + self._delete_action = QAction(hou.qt.Icon('BUTTONS_delete', ICON_SIZE, ICON_SIZE), 'Delete') + self._delete_action.triggered.connect(self._onDelete) + + self._hide_action = QAction(hou.qt.Icon('BUTTONS_close', ICON_SIZE, ICON_SIZE), 'Hide') + self._hide_action.triggered.connect(self._onHide) + + self._deprecate_action = QAction(hou.qt.Icon('BUTTONS_do_not', ICON_SIZE, ICON_SIZE), 'Deprecate') + self._deprecate_action.triggered.connect(self._onDeprecate) + + self._compare_action = QAction(hou.qt.Icon('BUTTONS_restore', ICON_SIZE, ICON_SIZE), 'Compare...') + self._compare_action.triggered.connect(self._onCompare) + + def __createContextMenus(self): + self._library_menu = QMenu(self) + + self._library_menu.addAction(self._open_location_action) + self._library_menu.addSeparator() + self._library_menu.addAction(self._install_library_action) + self._library_menu.addAction(self._uninstall_library_action) + self._library_menu.addAction(self._reload_library_action) + self._library_menu.addSeparator() + self._library_menu.addAction(self._merge_with_library_action) + self._library_menu.addAction(self._pack_action) + self._library_menu.addAction(self._unpack_action) + self._library_menu.addSeparator() + self._library_menu.addAction(self._backup_list_action) + + self._definition_menu = QMenu(self) + + self._definition_menu.addAction(self._open_type_properties_action) + self._definition_menu.addAction(self._change_instances_to_action) + + self._definition_inspect_menu = QMenu('Inspect', self) + self._definition_menu.addMenu(self._definition_inspect_menu) + + self._definition_inspect_menu.addAction(self._run_hda_doctor_action) + self._definition_inspect_menu.addAction(self._find_usages_action) + self._definition_inspect_menu.addAction(self._find_dependencies_action) + self._definition_inspect_menu.addAction(self._show_statistics_action) + + self._definition_create_menu = QMenu('Create', self) + self._definition_menu.addMenu(self._definition_create_menu) + + self._definition_create_menu.addAction(self._create_instance_action) + self._definition_create_menu.addAction(self._create_new_hda_action) + self._definition_create_menu.addAction(self._create_new_version_action) + self._definition_create_menu.addAction(self._create_black_box_action) + + self._definition_edit_menu = QMenu('Edit', self) + self._definition_menu.addMenu(self._definition_edit_menu) + + self._definition_edit_menu.addAction(self._copy_action) + self._definition_edit_menu.addSeparator() + self._definition_edit_menu.addAction(self._move_action) + self._definition_edit_menu.addAction(self._rename_action) + self._definition_edit_menu.addAction(self._add_alias_action) + self._definition_edit_menu.addSeparator() + self._definition_edit_menu.addAction(self._delete_action) + self._definition_edit_menu.addAction(self._hide_action) + self._definition_edit_menu.addAction(self._deprecate_action) + + self._definition_menu.addAction(self._compare_action) + + self.view.setContextMenuPolicy(Qt.CustomContextMenu) + self.view.customContextMenuRequested.connect(self.showContextMenu) + + def showContextMenu(self): + index = self.view.currentIndex() + + if not index.isValid(): + return + + self.view.deselectDifferingDepth(index) + + if isinstance(index.data(Qt.UserRole), basestring): + self._library_menu.exec_(QCursor.pos()) + else: + icon = index.model().index(index.row(), 0, index.parent()).data(Qt.DecorationRole) + self._create_instance_action.setIcon(icon) + self._definition_menu.exec_(QCursor.pos())