From e8a5230fc71dc187197385b1fe5129eae969aff2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Mar 2024 17:49:52 +0100 Subject: [PATCH] removed unused assets_widget.py --- client/ayon_core/tools/utils/assets_widget.py | 694 ------------------ 1 file changed, 694 deletions(-) delete mode 100644 client/ayon_core/tools/utils/assets_widget.py diff --git a/client/ayon_core/tools/utils/assets_widget.py b/client/ayon_core/tools/utils/assets_widget.py deleted file mode 100644 index e7036ff8e8..0000000000 --- a/client/ayon_core/tools/utils/assets_widget.py +++ /dev/null @@ -1,694 +0,0 @@ -import time -import collections - -from qtpy import QtWidgets, QtCore, QtGui -import qtawesome -import ayon_api - -from ayon_core.client import get_assets -from ayon_core.style import ( - get_default_tools_icon_color, - get_default_entity_icon_color, -) -from ayon_core.tools.flickcharm import FlickCharm - -from .views import ( - TreeViewSpinner, - DeselectableTreeView -) -from .widgets import PlaceholderLineEdit -from .models import RecursiveSortFilterProxyModel -from .lib import ( - DynamicQThread, - get_qta_icon_by_name_and_color -) - -ASSET_ID_ROLE = QtCore.Qt.UserRole + 1 -ASSET_NAME_ROLE = QtCore.Qt.UserRole + 2 -ASSET_LABEL_ROLE = QtCore.Qt.UserRole + 3 -ASSET_UNDERLINE_COLORS_ROLE = QtCore.Qt.UserRole + 4 -ASSET_PATH_ROLE = QtCore.Qt.UserRole + 5 - - -def _get_default_asset_icon_name(has_children): - if has_children: - return "fa.folder" - return "fa.folder-o" - - -def _get_asset_icon_color_from_doc(asset_doc): - if asset_doc: - return asset_doc["data"].get("color") - return None - - -def _get_asset_icon_name_from_doc(asset_doc): - if asset_doc: - return asset_doc["data"].get("icon") - return None - - -def _get_asset_icon_color(asset_doc): - icon_color = _get_asset_icon_color_from_doc(asset_doc) - if icon_color: - return icon_color - return get_default_entity_icon_color() - - -def _get_asset_icon_name(asset_doc, has_children=True): - icon_name = _get_asset_icon_name_from_doc(asset_doc) - if icon_name: - return icon_name - return _get_default_asset_icon_name(has_children) - - -def get_asset_icon(asset_doc, has_children=False): - """Get asset icon. - - Deprecated: - This function will be removed in future releases. Use on your own - risk. - - Args: - asset_doc (dict): Asset document. - has_children (Optional[bool]): Asset has children assets. - - Returns: - QIcon: Asset icon. - - """ - icon_name = _get_asset_icon_name(asset_doc, has_children) - icon_color = _get_asset_icon_color(asset_doc) - - return get_qta_icon_by_name_and_color(icon_name, icon_color) - - -class _AssetsView(TreeViewSpinner, DeselectableTreeView): - """Asset items view. - - Adds abilities to deselect, show loading spinner and add flick charm - (scroll by mouse/touchpad click and move). - """ - - def __init__(self, parent=None): - super(_AssetsView, self).__init__(parent) - self.setIndentation(15) - self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - self.setHeaderHidden(True) - - self._flick_charm_activated = False - self._flick_charm = FlickCharm(parent=self) - self._before_flick_scroll_mode = None - - def activate_flick_charm(self): - if self._flick_charm_activated: - return - self._flick_charm_activated = True - self._before_flick_scroll_mode = self.verticalScrollMode() - self._flick_charm.activateOn(self) - self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) - - def deactivate_flick_charm(self): - if not self._flick_charm_activated: - return - self._flick_charm_activated = False - self._flick_charm.deactivateFrom(self) - if self._before_flick_scroll_mode is not None: - self.setVerticalScrollMode(self._before_flick_scroll_mode) - - def mousePressEvent(self, event): - index = self.indexAt(event.pos()) - if not index.isValid(): - modifiers = QtWidgets.QApplication.keyboardModifiers() - if modifiers == QtCore.Qt.ShiftModifier: - return - elif modifiers == QtCore.Qt.ControlModifier: - return - - super(_AssetsView, self).mousePressEvent(event) - - def set_loading_state(self, loading, empty): - """Change loading state. - - TODO: Separate into 2 individual methods. - - Args: - loading(bool): Is loading. - empty(bool): Is model empty. - """ - if self.is_loading != loading: - if loading: - self.spinner.repaintNeeded.connect( - self.viewport().update - ) - else: - self.spinner.repaintNeeded.disconnect() - self.viewport().update() - - self.is_loading = loading - self.is_empty = empty - - -class _AssetModel(QtGui.QStandardItemModel): - """A model listing assets in the active project. - - The assets are displayed in a treeview, they are visually parented by - a `visualParent` field in the database containing an `_id` to a parent - asset. - - Asset document may have defined label, icon or icon color. - - Loading of data for model happens in thread which means that refresh - is not sequential. When refresh is triggered it is required to listen for - 'refreshed' signal. - - Args: - parent (QObject): Parent Qt object. - """ - - _doc_fetched = QtCore.Signal() - refreshed = QtCore.Signal(bool) - - # Asset document projection - _asset_projection = { - "name": 1, - "parent": 1, - "data.visualParent": 1, - "data.label": 1, - "data.icon": 1, - "data.color": 1 - } - - def __init__(self, parent=None): - super(_AssetModel, self).__init__(parent=parent) - - self._refreshing = False - self._doc_fetching_thread = None - self._doc_fetching_stop = False - self._doc_payload = [] - - self._doc_fetched.connect(self._on_docs_fetched) - - self._item_ids_with_color = set() - self._items_by_asset_id = {} - - self._project_name = None - self._last_project_name = None - - @property - def refreshing(self): - return self._refreshing - - def get_index_by_asset_id(self, asset_id): - item = self._items_by_asset_id.get(asset_id) - if item is not None: - return item.index() - return QtCore.QModelIndex() - - def get_indexes_by_asset_ids(self, asset_ids): - return [ - self.get_index_by_asset_id(asset_id) - for asset_id in asset_ids - ] - - def get_index_by_asset_name(self, asset_name): - indexes = self.get_indexes_by_asset_names([asset_name]) - for index in indexes: - if index.isValid(): - return index - return indexes[0] - - def get_indexes_by_asset_names(self, asset_names): - asset_ids_by_name = { - asset_name: None - for asset_name in asset_names - } - - for asset_id, item in self._items_by_asset_id.items(): - asset_name = item.data(ASSET_NAME_ROLE) - if asset_name in asset_ids_by_name: - asset_ids_by_name[asset_name] = asset_id - - asset_ids = [ - asset_ids_by_name[asset_name] - for asset_name in asset_names - ] - - return self.get_indexes_by_asset_ids(asset_ids) - - def get_project_name(self): - return self._project_name - - def set_project_name(self, project_name, refresh): - if self._project_name == project_name: - return - self._project_name = project_name - if refresh: - self.refresh() - - def refresh(self, force=False): - """Refresh the data for the model. - - Args: - force (bool): Stop currently running refresh start new refresh. - """ - # Skip fetch if there is already other thread fetching documents - if self._refreshing: - if not force: - return - self.stop_refresh() - - project_name = self._project_name - clear_model = False - if project_name != self._last_project_name: - clear_model = True - self._last_project_name = project_name - - if clear_model: - self._clear_items() - - # Fetch documents from mongo - # Restart payload - self._refreshing = True - self._doc_payload = [] - self._doc_fetching_thread = DynamicQThread(self._threaded_fetch) - self._doc_fetching_thread.start() - - def stop_refresh(self): - self._stop_fetch_thread() - - def _clear_items(self): - root_item = self.invisibleRootItem() - root_item.removeRows(0, root_item.rowCount()) - self._items_by_asset_id = {} - self._item_ids_with_color = set() - - def _on_docs_fetched(self): - # Make sure refreshing did not change - # - since this line is refreshing sequential and - # triggering of new refresh will happen when this method is done - if not self._refreshing: - self._clear_items() - return - - self._fill_assets(self._doc_payload) - - self.refreshed.emit(bool(self._items_by_asset_id)) - - self._stop_fetch_thread() - - def _fill_assets(self, asset_docs): - # Collect asset documents as needed - asset_ids = set() - asset_docs_by_id = {} - asset_ids_by_parents = collections.defaultdict(set) - for asset_doc in asset_docs: - asset_id = asset_doc["_id"] - asset_data = asset_doc.get("data") or {} - parent_id = asset_data.get("visualParent") - asset_ids.add(asset_id) - asset_docs_by_id[asset_id] = asset_doc - asset_ids_by_parents[parent_id].add(asset_id) - - # Prepare removed asset ids - removed_asset_ids = ( - set(self._items_by_asset_id.keys()) - set(asset_docs_by_id.keys()) - ) - - # Prepare queue for adding new items - asset_items_queue = collections.deque() - - # Queue starts with root item and 'visualParent' None - root_item = self.invisibleRootItem() - asset_items_queue.append((None, root_item)) - - while asset_items_queue: - # Get item from queue - parent_id, parent_item = asset_items_queue.popleft() - # Skip if there are no children - children_ids = asset_ids_by_parents[parent_id] - - # Go through current children of parent item - # - find out items that were deleted and skip creation of already - # existing items - for row in reversed(range(parent_item.rowCount())): - child_item = parent_item.child(row, 0) - asset_id = child_item.data(ASSET_ID_ROLE) - # Remove item that is not available - if asset_id not in children_ids: - if asset_id in removed_asset_ids: - # Remove and destroy row - parent_item.removeRow(row) - else: - # Just take the row from parent without destroying - parent_item.takeRow(row) - continue - - # Remove asset id from `children_ids` set - # - is used as set for creation of "new items" - children_ids.remove(asset_id) - # Add existing children to queue - asset_items_queue.append((asset_id, child_item)) - - new_items = [] - for asset_id in children_ids: - # Look for item in cache (maybe parent changed) - item = self._items_by_asset_id.get(asset_id) - # Create new item if was not found - if item is None: - item = QtGui.QStandardItem() - item.setEditable(False) - item.setData(asset_id, ASSET_ID_ROLE) - self._items_by_asset_id[asset_id] = item - new_items.append(item) - # Add item to queue - asset_items_queue.append((asset_id, item)) - - if new_items: - parent_item.appendRows(new_items) - - # Remove cache of removed items - for asset_id in removed_asset_ids: - self._items_by_asset_id.pop(asset_id) - - # Refresh data - # - all items refresh all data except id - for asset_id, item in self._items_by_asset_id.items(): - asset_doc = asset_docs_by_id[asset_id] - - asset_name = asset_doc["name"] - if item.data(ASSET_NAME_ROLE) != asset_name: - item.setData(asset_name, ASSET_NAME_ROLE) - - asset_data = asset_doc.get("data") or {} - asset_label = asset_data.get("label") or asset_name - if item.data(ASSET_LABEL_ROLE) != asset_label: - item.setData(asset_label, QtCore.Qt.DisplayRole) - item.setData(asset_label, ASSET_LABEL_ROLE) - - has_children = item.rowCount() > 0 - icon = get_asset_icon(asset_doc, has_children) - item.setData(icon, QtCore.Qt.DecorationRole) - - def _threaded_fetch(self): - asset_docs = self._fetch_asset_docs() - if not self._refreshing: - return - - self._doc_payload = asset_docs - - # Emit doc fetched only if was not stopped - self._doc_fetched.emit() - - def _fetch_asset_docs(self): - project_name = self.get_project_name() - if not project_name: - return [] - - project_entity = ayon_api.get_project(project_name, fields=["name"]) - if not project_entity: - return [] - - # Get all assets sorted by name - return list( - get_assets(project_name, fields=self._asset_projection.keys()) - ) - - def _stop_fetch_thread(self): - self._refreshing = False - if self._doc_fetching_thread is not None: - while self._doc_fetching_thread.isRunning(): - time.sleep(0.01) - self._doc_fetching_thread = None - - -class _AssetsWidget(QtWidgets.QWidget): - """Base widget to display a tree of assets with filter. - - Assets have only one column and are sorted by name. - - Refreshing of assets happens in thread so calling 'refresh' method - is not sequential. To capture moment when refreshing is finished listen - to 'refreshed' signal. - - To capture selection changes listen to 'selection_changed' signal. It won't - send any information about new selection as it may be different based on - inheritance changes. - - Args: - parent (QWidget): Parent Qt widget. - """ - - # on model refresh - refresh_triggered = QtCore.Signal() - refreshed = QtCore.Signal() - # on view selection change - selection_changed = QtCore.Signal() - # It was double clicked on view - double_clicked = QtCore.Signal() - - def __init__(self, parent=None): - super(_AssetsWidget, self).__init__(parent=parent) - - # Tree View - model = self._create_source_model() - proxy = self._create_proxy_model(model) - - view = _AssetsView(self) - view.setModel(proxy) - - header_widget = QtWidgets.QWidget(self) - - current_asset_icon = qtawesome.icon( - "fa.arrow-down", color=get_default_tools_icon_color() - ) - current_asset_btn = QtWidgets.QPushButton(header_widget) - current_asset_btn.setIcon(current_asset_icon) - current_asset_btn.setToolTip("Go to Asset from current Session") - # Hide by default - current_asset_btn.setVisible(False) - - refresh_icon = qtawesome.icon( - "fa.refresh", color=get_default_tools_icon_color() - ) - refresh_btn = QtWidgets.QPushButton(header_widget) - refresh_btn.setIcon(refresh_icon) - refresh_btn.setToolTip("Refresh items") - - filter_input = PlaceholderLineEdit(header_widget) - filter_input.setPlaceholderText("Filter folders..") - - # Header - header_layout = QtWidgets.QHBoxLayout(header_widget) - header_layout.setContentsMargins(0, 0, 0, 0) - header_layout.addWidget(filter_input) - header_layout.addWidget(current_asset_btn) - header_layout.addWidget(refresh_btn) - - # Make header widgets expand vertically if there is a place - for widget in ( - current_asset_btn, - refresh_btn, - filter_input, - ): - size_policy = widget.sizePolicy() - size_policy.setVerticalPolicy( - QtWidgets.QSizePolicy.MinimumExpanding) - widget.setSizePolicy(size_policy) - - # Layout - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(header_widget, 0) - layout.addWidget(view, 1) - - # Signals/Slots - filter_input.textChanged.connect(self._on_filter_text_change) - - selection_model = view.selectionModel() - selection_model.selectionChanged.connect(self._on_selection_change) - refresh_btn.clicked.connect(self.refresh) - current_asset_btn.clicked.connect(self._on_current_asset_click) - view.doubleClicked.connect(self.double_clicked) - - self._header_widget = header_widget - self._filter_input = filter_input - self._refresh_btn = refresh_btn - self._current_asset_btn = current_asset_btn - self._model = model - self._proxy = proxy - self._view = view - - self._last_btns_height = None - - self._current_folder_path = None - - self.model_selection = {} - - @property - def header_widget(self): - return self._header_widget - - def get_project_name(self): - self._model.get_project_name() - - def set_project_name(self, project_name, refresh=True): - self._model.set_project_name(project_name, refresh) - - def set_current_asset_name(self, asset_name): - self._current_folder_path = asset_name - - def _create_source_model(self): - model = _AssetModel(parent=self) - model.refreshed.connect(self._on_model_refresh) - return model - - def _create_proxy_model(self, source_model): - proxy = RecursiveSortFilterProxyModel() - proxy.setSourceModel(source_model) - proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - proxy.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) - return proxy - - @property - def refreshing(self): - return self._model.refreshing - - def refresh(self): - self._refresh_model() - - def stop_refresh(self): - self._model.stop_refresh() - - def _get_current_folder_path(self): - return self._current_folder_path - - def _on_current_asset_click(self): - """Trigger change of asset to current context asset. - This separation gives ability to override this method and use it - in differnt way. - """ - - self.select_current_asset() - - def select_current_asset(self): - asset_name = self._get_current_folder_path() - if asset_name: - self.select_asset_by_name(asset_name) - - def set_refresh_btn_visibility(self, visible=None): - """Hide set refresh button. - Some tools may have their global refresh button or do not support - refresh at all. - """ - - if visible is None: - visible = not self._refresh_btn.isVisible() - self._refresh_btn.setVisible(visible) - - def set_current_asset_btn_visibility(self, visible=None): - """Hide set current asset button. - - Not all tools support using of current context asset. - """ - - if visible is None: - visible = not self._current_asset_btn.isVisible() - self._current_asset_btn.setVisible(visible) - - def select_asset(self, asset_id): - index = self._model.get_index_by_asset_id(asset_id) - new_index = self._proxy.mapFromSource(index) - self._select_indexes([new_index]) - - def select_asset_by_name(self, asset_name): - index = self._model.get_index_by_asset_name(asset_name) - new_index = self._proxy.mapFromSource(index) - self._select_indexes([new_index]) - - def activate_flick_charm(self): - self._view.activate_flick_charm() - - def deactivate_flick_charm(self): - self._view.deactivate_flick_charm() - - def _on_selection_change(self): - self.selection_changed.emit() - - def _on_filter_text_change(self, new_text): - self._proxy.setFilterFixedString(new_text) - - def _on_model_refresh(self, has_item): - """This method should be triggered on model refresh. - - Default implementation register this callback in '_create_source_model' - so if you're modifying model keep in mind that this method should be - called when refresh is done. - """ - - self._proxy.sort(0) - self._set_loading_state(loading=False, empty=not has_item) - self.refreshed.emit() - - def _refresh_model(self): - # Store selection - self._set_loading_state(loading=True, empty=True) - - # Trigger signal before refresh is called - self.refresh_triggered.emit() - # Refresh model - self._model.refresh() - - def _set_loading_state(self, loading, empty): - self._view.set_loading_state(loading, empty) - - def _clear_selection(self): - selection_model = self._view.selectionModel() - selection_model.clearSelection() - - def _select_indexes(self, indexes): - valid_indexes = [ - index - for index in indexes - if index.isValid() - ] - if not valid_indexes: - return - - selection_model = self._view.selectionModel() - selection_model.clearSelection() - - mode = ( - QtCore.QItemSelectionModel.Select - | QtCore.QItemSelectionModel.Rows - ) - for index in valid_indexes: - self._view.expand(self._proxy.parent(index)) - selection_model.select(index, mode) - self._view.setCurrentIndex(valid_indexes[0]) - - -class SingleSelectAssetsWidget(_AssetsWidget): - """Single selection asset widget. - - Contain single selection specific api methods. - - Deprecated: - This widget will be removed soon. Please do not use it in new code. - """ - - def get_selected_asset_id(self): - """Currently selected asset id.""" - selection_model = self._view.selectionModel() - indexes = selection_model.selectedRows() - for index in indexes: - return index.data(ASSET_ID_ROLE) - return None - - def get_selected_asset_name(self): - """Currently selected asset name.""" - selection_model = self._view.selectionModel() - indexes = selection_model.selectedRows() - for index in indexes: - return index.data(ASSET_NAME_ROLE) - return None