--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric7/EricWidgets/EricTreeWidget.py Sat May 22 19:58:24 2021 +0200 @@ -0,0 +1,283 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing specialized tree views. +""" + +import enum + +from PyQt6.QtCore import pyqtSignal, Qt +from PyQt6.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView + + +class EricTreeWidgetItemsState(enum.Enum): + """ + Class defining the items expansion state. + """ + COLLAPSED = 0 + EXPANDED = 1 + + +class EricTreeWidget(QTreeWidget): + """ + Class implementing an extended tree widget. + + @signal itemControlClicked(QTreeWidgetItem) emitted after a Ctrl-Click + on an item + @signal itemMiddleButtonClicked(QTreeWidgetItem) emitted after a click + of the middle button on an item + """ + itemControlClicked = pyqtSignal(QTreeWidgetItem) + itemMiddleButtonClicked = pyqtSignal(QTreeWidgetItem) + + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent widget (QWidget) + """ + super().__init__(parent) + + self.__refreshAllItemsNeeded = True + self.__allTreeItems = [] + self.__showMode = EricTreeWidgetItemsState.COLLAPSED + + self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) + + self.itemChanged.connect(self.__scheduleRefresh) + + def setDefaultItemShowMode(self, mode): + """ + Public method to set the default item show mode. + + @param mode default mode + @type EricTreeWidgetItemsState + """ + self.__showMode = mode + + def allItems(self): + """ + Public method to get a list of all items. + + @return list of all items (list of QTreeWidgetItem) + """ + if self.__refreshAllItemsNeeded: + self.__allTreeItems = [] + self.__iterateAllItems(None) + self.__refreshAllItemsNeeded = False + + return self.__allTreeItems + + def appendToParentItem(self, parent, item): + """ + Public method to append an item to a parent item. + + @param parent text of the parent item (string) or + the parent item (QTreeWidgetItem) + @param item item to be appended (QTreeWidgetItem) + @return flag indicating success (boolean) + @exception RuntimeError raised to indicate an illegal type for + the parent + """ + if not isinstance(parent, (QTreeWidgetItem, str)): + raise RuntimeError("illegal type for parent") + + if isinstance(parent, QTreeWidgetItem): + if parent is None or parent.treeWidget() != self: + return False + parentItem = parent + else: + lst = self.findItems(parent, Qt.MatchFlag.MatchExactly) + if not lst: + return False + parentItem = lst[0] + if parentItem is None: + return False + + self.__allTreeItems.append(item) + parentItem.addChild(item) + return True + + def prependToParentItem(self, parent, item): + """ + Public method to prepend an item to a parent item. + + @param parent text of the parent item (string) or + the parent item (QTreeWidgetItem) + @param item item to be prepended (QTreeWidgetItem) + @return flag indicating success (boolean) + @exception RuntimeError raised to indicate an illegal type for + the parent + """ + if not isinstance(parent, (QTreeWidgetItem, str)): + raise RuntimeError("illegal type for parent") + + if isinstance(parent, QTreeWidgetItem): + if parent is None or parent.treeWidget() != self: + return False + parentItem = parent + else: + lst = self.findItems(parent, Qt.MatchFlag.MatchExactly) + if not lst: + return False + parentItem = lst[0] + if parentItem is None: + return False + + self.__allTreeItems.append(item) + parentItem.insertChild(0, item) + return True + + def addTopLevelItem(self, item): + """ + Public method to add a top level item. + + @param item item to be added as a top level item (QTreeWidgetItem) + """ + self.__allTreeItems.append(item) + super().addTopLevelItem(item) + + def addTopLevelItems(self, items): + """ + Public method to add a list of top level items. + + @param items items to be added as top level items + (list of QTreeWidgetItem) + """ + self.__allTreeItems.extend(items) + super().addTopLevelItems(items) + + def insertTopLevelItem(self, index, item): + """ + Public method to insert a top level item. + + @param index index for the insertion (integer) + @param item item to be inserted as a top level item (QTreeWidgetItem) + """ + self.__allTreeItems.append(item) + super().insertTopLevelItem(index, item) + + def insertTopLevelItems(self, index, items): + """ + Public method to insert a list of top level items. + + @param index index for the insertion (integer) + @param items items to be inserted as top level items + (list of QTreeWidgetItem) + """ + self.__allTreeItems.extend(items) + super().insertTopLevelItems(index, items) + + def deleteItem(self, item): + """ + Public method to delete an item. + + @param item item to be deleted (QTreeWidgetItem) + """ + if item in self.__allTreeItems: + self.__allTreeItems.remove(item) + + self.__refreshAllItemsNeeded = True + + del item + + def deleteItems(self, items): + """ + Public method to delete a list of items. + + @param items items to be deleted (list of QTreeWidgetItem) + """ + for item in items: + self.deleteItem(item) + + def filterString(self, filterStr): + """ + Public slot to set a new filter. + + @param filterStr filter to be set (string) + """ + self.expandAll() + allItems = self.allItems() + + if filterStr: + lFilter = filterStr.lower() + for itm in allItems: + itm.setHidden(lFilter not in itm.text(0).lower()) + itm.setExpanded(True) + for index in range(self.topLevelItemCount()): + self.topLevelItem(index).setHidden(False) + + firstItm = self.topLevelItem(0) + belowItm = self.itemBelow(firstItm) + topLvlIndex = 0 + while firstItm: + if lFilter in firstItm.text(0).lower(): + firstItm.setHidden(False) + elif ( + not firstItm.parent() and + (not belowItm or not belowItm.parent()) + ): + firstItm.setHidden(True) + elif not belowItm: + break + + topLvlIndex += 1 + firstItm = self.topLevelItem(topLvlIndex) + belowItm = self.itemBelow(firstItm) + else: + for itm in allItems: + itm.setHidden(False) + for index in range(self.topLevelItemCount()): + self.topLevelItem(index).setHidden(False) + if self.__showMode == EricTreeWidgetItemsState.COLLAPSED: + self.collapseAll() + + def clear(self): + """ + Public slot to clear the tree. + """ + self.__allTreeItems = [] + super().clear() + + def __scheduleRefresh(self): + """ + Private slot to schedule a refresh of the tree. + """ + self.__refreshAllItemsNeeded = True + + def mousePressEvent(self, evt): + """ + Protected method handling mouse press events. + + @param evt mouse press event (QMouseEvent) + """ + if ( + evt.modifiers() == Qt.KeyboardModifier.ControlModifier and + evt.buttons() == Qt.MouseButton.LeftButton + ): + self.itemControlClicked.emit(self.itemAt(evt.position().toPoint())) + return + elif evt.buttons() == Qt.MouseButton.MiddleButton: + self.itemMiddleButtonClicked.emit(self.itemAt(evt.position().toPoint())) + return + else: + super().mousePressEvent(evt) + + def __iterateAllItems(self, parent): + """ + Private method to iterate over the child items of the parent. + + @param parent parent item to iterate (QTreeWidgetItem) + """ + count = parent.childCount() if parent else self.topLevelItemCount() + + for index in range(count): + itm = parent.child(index) if parent else self.topLevelItem(index) + + if itm.childCount() == 0: + self.__allTreeItems.append(itm) + + self.__iterateAllItems(itm)