From 8e90cd158207bc77745b9d90f73f3d90d5370002 Mon Sep 17 00:00:00 2001 From: Ivan Titov Date: Mon, 12 Apr 2021 22:40:23 +0500 Subject: [PATCH] Operator Manager #22 --- .../operator_manager/backup_list/view.py | 6 - .../operator_manager/backup_list/window.py | 8 +- .../operator_manager/model/library.py | 38 +---- .../operator_manager/model/node_type.py | 138 +++++++++++++----- .../operator_manager/model/node_type_proxy.py | 50 +++++++ .../houdini_tdk/operator_manager/view.py | 1 + .../houdini_tdk/operator_manager/window.py | 27 +++- 7 files changed, 180 insertions(+), 88 deletions(-) create mode 100644 python2.7libs/houdini_tdk/operator_manager/model/node_type_proxy.py diff --git a/python2.7libs/houdini_tdk/operator_manager/backup_list/view.py b/python2.7libs/houdini_tdk/operator_manager/backup_list/view.py index 9385def..187bebb 100644 --- a/python2.7libs/houdini_tdk/operator_manager/backup_list/view.py +++ b/python2.7libs/houdini_tdk/operator_manager/backup_list/view.py @@ -49,12 +49,6 @@ def __init__(self): self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionMode(QAbstractItemView.SingleSelection) - def setSectionsResizeMode(self): - header = self.header() - header.setSectionResizeMode(0, QHeaderView.ResizeToContents) - header.setSectionResizeMode(1, QHeaderView.Stretch) - header.setSectionResizeMode(2, QHeaderView.ResizeToContents) - def hasSelection(self): """Returns True if selection model has selected items, False otherwise.""" return self.selectionModel().hasSelection() diff --git a/python2.7libs/houdini_tdk/operator_manager/backup_list/window.py b/python2.7libs/houdini_tdk/operator_manager/backup_list/window.py index 73b32c3..e1d5913 100644 --- a/python2.7libs/houdini_tdk/operator_manager/backup_list/window.py +++ b/python2.7libs/houdini_tdk/operator_manager/backup_list/window.py @@ -62,9 +62,15 @@ def updateWindowTitle(self): title += ': ' + self._library_path self.setWindowTitle(title) + def _setSectionsResizeMode(self): + header = self.view.header() + header.setSectionResizeMode(0, QHeaderView.ResizeToContents) + header.setSectionResizeMode(1, QHeaderView.Stretch) + header.setSectionResizeMode(2, QHeaderView.ResizeToContents) + def setLibrary(self, library_path): self.model.setLibrary(library_path) - self.view.setSectionsResizeMode() + self._setSectionsResizeMode() self._library_path = library_path self.updateWindowTitle() diff --git a/python2.7libs/houdini_tdk/operator_manager/model/library.py b/python2.7libs/houdini_tdk/operator_manager/model/library.py index 84d6dc8..83cdde8 100644 --- a/python2.7libs/houdini_tdk/operator_manager/model/library.py +++ b/python2.7libs/houdini_tdk/operator_manager/model/library.py @@ -17,6 +17,7 @@ """ import os +from operator import attrgetter try: from PyQt5.QtWidgets import * @@ -31,6 +32,8 @@ import hou +from .node_type_proxy import NodeTypeProxy + ICON_SIZE = 16 INSTALLED_ICON = hou.qt.Icon('TOP_status_cooked', ICON_SIZE, ICON_SIZE) NOT_INSTALLED_ICON = hou.qt.Icon('TOP_status_error', ICON_SIZE, ICON_SIZE) @@ -39,39 +42,6 @@ TextRole = Qt.UserRole + 1 -class NodeTypeProxy(object): - __slots__ = 'node_type', '_name', '_name_with_category', '_label', '_icon', '_library_file_path' - - def __init__(self, node_type): - self.node_type = node_type - self._name = node_type.name() - self._name_with_category = node_type.nameWithCategory() - self._label = node_type.description() - self._icon = node_type.icon() - if node_type.definition() is None: - self._library_file_path = 'Internal' - else: - self._library_file_path = node_type.definition().libraryFilePath() - - def name(self): - return self._name - - def nameWithCategory(self): - return self._name_with_category - - def description(self): - return self._label - - def icon(self): - return self._icon - - def libraryFilePath(self): - return self._library_file_path - - def __getattr__(self, attr_name): - return self.node_type.__getattribute__(attr_name) - - class OperatorManagerLibraryModel(QAbstractItemModel): def __init__(self, parent=None): super(OperatorManagerLibraryModel, self).__init__(parent) @@ -102,7 +72,7 @@ def updateData(self): else: multiple_def_libs.append(lib_path) self._types[lib_path] = tuple(sorted((NodeTypeProxy(d.nodeType()) for d in definitions), - key=lambda tp: tp.nameWithCategory())) + key=attrgetter('nameWithCategory'))) single_def_libs = tuple(sorted(single_def_libs.keys(), key=single_def_libs.get)) self._types['Internal'] = tuple(sorted(internal_types, diff --git a/python2.7libs/houdini_tdk/operator_manager/model/node_type.py b/python2.7libs/houdini_tdk/operator_manager/model/node_type.py index 8472627..45d408d 100644 --- a/python2.7libs/houdini_tdk/operator_manager/model/node_type.py +++ b/python2.7libs/houdini_tdk/operator_manager/model/node_type.py @@ -16,6 +16,8 @@ along with this program. If not, see . """ +from operator import attrgetter + try: from PyQt5.QtWidgets import * from PyQt5.QtGui import * @@ -29,56 +31,85 @@ import hou +from .node_type_proxy import NodeTypeProxy + ICON_SIZE = 16 EMPTY_ICON = hou.qt.Icon('MISC_empty', ICON_SIZE, ICON_SIZE) -TextRole = Qt.UserRole + 1 + +class TypeNameItem(object): + __slots__ = 'category_name', 'type_name', + + def __init__(self, category_name, type_name): + self.category_name = category_name + self.type_name = type_name + + def __eq__(self, other): + return other == self.type_name class OperatorManagerNodeTypeModel(QAbstractItemModel): def __init__(self, parent=None): super(OperatorManagerNodeTypeModel, self).__init__(parent) - self._names = () + self._category_names = () + self._type_names = {} self._types = {} def updateData(self): self.beginResetModel() - node_types = {} + types = {} - for category in hou.nodeTypeCategories().values(): + for category_name, category in hou.nodeTypeCategories().items(): + category_types = types[category_name] = {} for node_type in category.nodeTypes().values(): - if node_type.definition() is None: - continue - - _, _, name, _ = node_type.nameComponents() - if name not in node_types: - node_types[name] = [node_type] + type_name = node_type.nameComponents()[2] + if type_name not in category_types: + category_types[type_name] = [node_type] else: - node_types[name].append(node_type) + category_types[type_name].append(node_type) - self._names = tuple(sorted(node_types.keys())) - self._types = { - name: tuple(sorted(node_types[name], key=lambda t: t.name())) - for name in node_types.keys() - } + # Remove empty categories + for category_name in tuple(types.keys()): + if len(types[category_name]) == 0: + del types[category_name] + + self._category_names = tuple(sorted(types.keys())) + + self._type_names = {} + for category_name in self._category_names: + self._type_names[category_name] = tuple(TypeNameItem(category_name, type_name) + for type_name in sorted(types[category_name].keys())) + + types[category_name] = { + type_name: tuple(sorted(map(NodeTypeProxy, node_types), key=attrgetter('name'))) + for type_name, node_types in types[category_name].items() + } + + self._types = types self.endResetModel() def hasChildren(self, parent): if not parent.isValid(): - return bool(self._names) - elif isinstance(parent.internalPointer(), basestring): + return bool(self._category_names) + elif isinstance(parent.internalPointer(), (basestring, TypeNameItem)): return True - - return False + else: + return False def rowCount(self, parent): if not parent.isValid(): - return len(self._names) + return len(self._category_names) + elif isinstance(parent.internalPointer(), basestring): + category_name = parent.internalPointer() + return len(self._type_names[category_name]) + elif isinstance(parent.internalPointer(), TypeNameItem): + type_name_item = parent.internalPointer() + return len(self._types[type_name_item.category_name][type_name_item.type_name]) else: - return len(self._types[parent.internalPointer()]) + return 0 def columnCount(self, parent): return 4 @@ -87,22 +118,38 @@ def parent(self, index): if not index.isValid(): return QModelIndex() - item = index.internalPointer() - if isinstance(item, basestring): + item_data = index.internalPointer() + + if isinstance(item_data, basestring): return QModelIndex() + elif isinstance(item_data, TypeNameItem): + return self.createIndex(self._type_names[item_data.category_name].index(item_data.type_name), 0, + item_data.category_name) + elif isinstance(item_data, NodeTypeProxy): + node_type = item_data + category_name = node_type.category().name() + type_name = node_type.nameComponents()[2] + type_name_index = self._type_names[category_name].index(type_name) + type_name_item = self._type_names[category_name][type_name_index] + return self.createIndex(type_name_index, 0, type_name_item) else: - type_name = item.nameComponents()[2] - return self.createIndex(self._types[type_name].index(item), 0, type_name) + return QModelIndex() def index(self, row, column, parent): if not self.hasIndex(row, column, parent): return QModelIndex() if not parent.isValid(): - return self.createIndex(row, column, self._names[row]) - else: - type_name = parent.internalPointer() - return self.createIndex(row, column, self._types[type_name][row]) + return self.createIndex(row, column, self._category_names[row]) + elif isinstance(parent.internalPointer(), basestring): + category_name = parent.internalPointer() + type_name_item = self._type_names[category_name][row] + return self.createIndex(row, column, type_name_item) + elif isinstance(parent.internalPointer(), TypeNameItem): + type_name_item = parent.internalPointer() + return self.createIndex(row, column, + self._types[type_name_item.category_name][type_name_item.type_name][row]) + return QModelIndex() def flags(self, index): return Qt.ItemIsEnabled | Qt.ItemIsSelectable @@ -114,33 +161,44 @@ def data(self, index, role): item_data = index.internalPointer() if role == Qt.UserRole: + if isinstance(item_data, NodeTypeProxy): + item_data = item_data.node_type return item_data column = index.column() - if not isinstance(item_data, hou.NodeType): - type_name = item_data + if isinstance(item_data, basestring): + if item_data in self._category_names: + category_name = item_data + if column == 0: + if role == Qt.DisplayRole: + return category_name + elif isinstance(item_data, TypeNameItem): if column == 0: if role == Qt.DisplayRole: - return type_name - else: - node_type = item_data + return item_data.type_name + elif isinstance(item_data, NodeTypeProxy): + node_type_proxy = item_data if column == 0: if role == Qt.DisplayRole: - return node_type.description() + return node_type_proxy.description() elif role == Qt.DecorationRole: try: - return hou.qt.Icon(node_type.icon(), ICON_SIZE, ICON_SIZE) + return hou.qt.Icon(node_type_proxy.icon(), ICON_SIZE, ICON_SIZE) except hou.OperationFailed: return EMPTY_ICON elif column == 1: + namespace = node_type_proxy.nameComponents()[1] if role == Qt.DisplayRole: - namespace = node_type.nameComponents()[1] return namespace or '-' + elif role == Qt.TextAlignmentRole: + return Qt.AlignLeft | Qt.AlignVCenter if namespace else Qt.AlignCenter elif column == 2: + version = node_type_proxy.nameComponents()[3] if role == Qt.DisplayRole: - version = node_type.nameComponents()[3] return version or '-' + elif role == Qt.TextAlignmentRole: + return Qt.AlignLeft | Qt.AlignVCenter if version else Qt.AlignCenter elif column == 3: if role == Qt.DisplayRole: - return node_type.definition().libraryFilePath() + return node_type_proxy.libraryFilePath() diff --git a/python2.7libs/houdini_tdk/operator_manager/model/node_type_proxy.py b/python2.7libs/houdini_tdk/operator_manager/model/node_type_proxy.py new file mode 100644 index 0000000..5681175 --- /dev/null +++ b/python2.7libs/houdini_tdk/operator_manager/model/node_type_proxy.py @@ -0,0 +1,50 @@ +""" +Tool Development Kit for SideFX Houdini +Copyright (C) 2021 Ivan Titov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + + +class NodeTypeProxy(object): + __slots__ = 'node_type', '_name', '_name_with_category', '_label', '_icon', '_library_file_path' + + def __init__(self, node_type): + self.node_type = node_type + self._name = node_type.name() + self._name_with_category = node_type.nameWithCategory() + self._label = node_type.description() + self._icon = node_type.icon() + if node_type.definition() is None: + self._library_file_path = 'Internal' + else: + self._library_file_path = node_type.definition().libraryFilePath() + + def name(self): + return self._name + + def nameWithCategory(self): + return self._name_with_category + + def description(self): + return self._label + + def icon(self): + return self._icon + + def libraryFilePath(self): + return self._library_file_path + + def __getattr__(self, attr_name): + return self.node_type.__getattribute__(attr_name) diff --git a/python2.7libs/houdini_tdk/operator_manager/view.py b/python2.7libs/houdini_tdk/operator_manager/view.py index f4fdbd5..7399301 100644 --- a/python2.7libs/houdini_tdk/operator_manager/view.py +++ b/python2.7libs/houdini_tdk/operator_manager/view.py @@ -44,6 +44,7 @@ def __init__(self): self.setItemDelegate(OperatorManagerLibraryDelegate()) self.setAlternatingRowColors(True) + self.setStyleSheet('QTreeView::item {padding: 0 10px;}') self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionMode(QAbstractItemView.ExtendedSelection) diff --git a/python2.7libs/houdini_tdk/operator_manager/window.py b/python2.7libs/houdini_tdk/operator_manager/window.py index 69bf5fc..deddfea 100644 --- a/python2.7libs/houdini_tdk/operator_manager/window.py +++ b/python2.7libs/houdini_tdk/operator_manager/window.py @@ -36,7 +36,7 @@ from ..utils import openLocation, removePath from ..fuzzy_proxy_model import FuzzyProxyModel from .model import OperatorManagerLibraryModel, OperatorManagerNodeTypeModel, TextRole -from .model.library import NodeTypeProxy +from .model.node_type_proxy import NodeTypeProxy from .view import OperatorManagerView from .backup_list import BackupListWindow from .usage_list import UsageListWindow @@ -95,14 +95,14 @@ def __init__(self, parent=hou.qt.mainWindow()): self.library_model = OperatorManagerLibraryModel(self) self.node_type_model = OperatorManagerNodeTypeModel(self) - 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 is better) - self._filter_proxy_model.sort(0, Qt.DescendingOrder) - self._filter_proxy_model.setSourceModel(self.library_model) + # 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 is better) + # self._filter_proxy_model.sort(0, Qt.DescendingOrder) + # self._filter_proxy_model.setSourceModel(self.node_type_model) self.view = OperatorManagerView() - self.view.setModel(self._filter_proxy_model) + self.view.setModel(self.node_type_model) layout.addWidget(self.view) self._createActions() @@ -121,6 +121,19 @@ def _onFilterChange(self, pattern): def updateData(self): self.view.model().updateData() + def _setSectionsResizeMode(self, model_index): + header = self.view.header() + if model_index == 0: + pass + elif model_index == 1: + pass + else: + raise ValueError + + def setCurrentModel(self, model_index): + self.view.setModel((self.library_model, self.node_type_model)[model_index]) + self._setSectionsResizeMode(model_index) + def _onExpand(self): """Expands selected items.""" for index in self.view.selectedIndexes():