ditch QSortFilterProxyModel in favour of our own code
Simpler and approximately twice as fast in a large collection: old approach search for a: 371ms search for an: 260ms new approach: search for a: 171ms search for an: 149ms Still todo: add enum defs for the other root categories, update the _section_root() calls, and update is_expanded() to use the new extra types
This commit is contained in:
parent
c96248d67f
commit
3a8fce69dd
@ -93,6 +93,8 @@ class SidebarItem:
|
||||
self.parentItem: Optional["SidebarItem"] = None
|
||||
self.tooltip: Optional[str] = None
|
||||
self.row_in_parent: Optional[int] = None
|
||||
self._search_matches_self = False
|
||||
self._search_matches_child = False
|
||||
|
||||
def addChild(self, cb: "SidebarItem") -> None:
|
||||
self.children.append(cb)
|
||||
@ -104,6 +106,30 @@ class SidebarItem:
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def is_expanded(self, searching: bool) -> bool:
|
||||
if not searching:
|
||||
return self.expanded
|
||||
else:
|
||||
if self._search_matches_child:
|
||||
return True
|
||||
# if search matches top level, expand children one level
|
||||
# FIXME: add types for other roots
|
||||
return self._search_matches_self and self.item_type in (
|
||||
SidebarItemType.SAVED_SEARCH_ROOT,
|
||||
SidebarItemType.DECK_ROOT,
|
||||
)
|
||||
|
||||
def is_highlighted(self) -> bool:
|
||||
return self._search_matches_self
|
||||
|
||||
def search(self, lowered_text: str) -> bool:
|
||||
"True if we or child matched."
|
||||
self._search_matches_self = lowered_text in self.name.lower()
|
||||
self._search_matches_child = any(
|
||||
[child.search(lowered_text) for child in self.children]
|
||||
)
|
||||
return self._search_matches_self or self._search_matches_child
|
||||
|
||||
|
||||
class SidebarModel(QAbstractItemModel):
|
||||
def __init__(self, root: SidebarItem) -> None:
|
||||
@ -120,6 +146,9 @@ class SidebarModel(QAbstractItemModel):
|
||||
def item_for_index(self, idx: QModelIndex) -> SidebarItem:
|
||||
return idx.internalPointer()
|
||||
|
||||
def search(self, text: str) -> None:
|
||||
self.root.search(text.lower())
|
||||
|
||||
# Qt API
|
||||
######################################################################
|
||||
|
||||
@ -204,39 +233,20 @@ class SidebarModel(QAbstractItemModel):
|
||||
|
||||
|
||||
def expand_where_necessary(
|
||||
model: SidebarModel, tree: QTreeView, parent: Optional[QModelIndex] = None
|
||||
model: SidebarModel,
|
||||
tree: QTreeView,
|
||||
parent: Optional[QModelIndex] = None,
|
||||
searching: bool = False,
|
||||
) -> None:
|
||||
parent = parent or QModelIndex()
|
||||
for row in range(model.rowCount(parent)):
|
||||
idx = model.index(row, 0, parent)
|
||||
if not idx.isValid():
|
||||
continue
|
||||
expand_where_necessary(model, tree, idx)
|
||||
item = model.item_for_index(idx)
|
||||
if item and item.expanded:
|
||||
tree.setExpanded(idx, True)
|
||||
|
||||
|
||||
class FilterModel(QSortFilterProxyModel):
|
||||
def item_for_index(self, idx: QModelIndex) -> Optional[SidebarItem]:
|
||||
if not idx.isValid():
|
||||
return None
|
||||
return self.mapToSource(idx).internalPointer()
|
||||
|
||||
def _anyParentMatches(self, item: SidebarItem) -> bool:
|
||||
if not item.parentItem:
|
||||
return False
|
||||
if self.parent().current_search.lower() in item.parentItem.name.lower():
|
||||
return True
|
||||
return self._anyParentMatches(item.parentItem)
|
||||
|
||||
def filterAcceptsRow(self, row: int, parent: QModelIndex) -> bool:
|
||||
current_search = self.parent().current_search
|
||||
if not current_search:
|
||||
return False
|
||||
current_search = current_search.lower()
|
||||
item = self.sourceModel().index(row, 0, parent).internalPointer()
|
||||
return current_search in item.name.lower() or self._anyParentMatches(item)
|
||||
expand_where_necessary(model, tree, idx, searching)
|
||||
if item := model.item_for_index(idx):
|
||||
if item.is_expanded(searching):
|
||||
tree.setExpanded(idx, True)
|
||||
|
||||
|
||||
class SidebarSearchBar(QLineEdit):
|
||||
@ -312,7 +322,7 @@ class SidebarTreeView(QTreeView):
|
||||
bgcolor = QPalette().window().color().name()
|
||||
self.setStyleSheet("QTreeView { background: '%s'; }" % bgcolor)
|
||||
|
||||
def model(self) -> Union[FilterModel, SidebarModel]:
|
||||
def model(self) -> SidebarModel:
|
||||
return super().model()
|
||||
|
||||
def refresh(self) -> None:
|
||||
@ -340,47 +350,22 @@ class SidebarTreeView(QTreeView):
|
||||
self.current_search = None
|
||||
self.refresh()
|
||||
return
|
||||
if not isinstance(self.model(), FilterModel):
|
||||
filter_model = FilterModel(self)
|
||||
filter_model.setSourceModel(self.model())
|
||||
filter_model.setFilterCaseSensitivity(False) # type: ignore
|
||||
filter_model.setRecursiveFilteringEnabled(True)
|
||||
self.setModel(filter_model)
|
||||
else:
|
||||
filter_model = self.model()
|
||||
|
||||
self.current_search = text
|
||||
# Without collapsing first, can be very slow. Surely there's
|
||||
# a better way than this?
|
||||
# start from a collapsed state, as it's faster
|
||||
self.collapseAll()
|
||||
filter_model.setFilterFixedString(text)
|
||||
self.expandMatches(self.rootIndex())
|
||||
|
||||
def expandMatches(self, parent: QModelIndex) -> bool:
|
||||
"Expand match trees one level."
|
||||
expand = False
|
||||
for i in range(self.model().rowCount(parent)):
|
||||
idx = self.model().index(i, 0, parent)
|
||||
item = self.model().item_for_index(idx)
|
||||
expandChild = self.expandMatches(idx) or (
|
||||
bool(item) and self.current_search.lower() in item.name.lower()
|
||||
)
|
||||
expand |= expandChild
|
||||
self.setExpanded(idx, expandChild)
|
||||
return expand
|
||||
self.model().search(text)
|
||||
expand_where_necessary(self.model(), self, searching=True)
|
||||
|
||||
def drawRow(
|
||||
self, painter: QPainter, options: QStyleOptionViewItem, idx: QModelIndex
|
||||
) -> None:
|
||||
if self.current_search is None:
|
||||
return super().drawRow(painter, options, idx)
|
||||
if not (item := self.model().item_for_index(idx)):
|
||||
return super().drawRow(painter, options, idx)
|
||||
if self.current_search.lower() in item.name.lower():
|
||||
brush = QBrush(theme_manager.qcolor("suspended-bg"))
|
||||
painter.save()
|
||||
painter.fillRect(options.rect, brush)
|
||||
painter.restore()
|
||||
if self.current_search and (item := self.model().item_for_index(idx)):
|
||||
if item.is_highlighted():
|
||||
brush = QBrush(theme_manager.qcolor("suspended-bg"))
|
||||
painter.save()
|
||||
painter.fillRect(options.rect, brush)
|
||||
painter.restore()
|
||||
return super().drawRow(painter, options, idx)
|
||||
|
||||
def dropEvent(self, event: QDropEvent) -> None:
|
||||
|
Loading…
Reference in New Issue
Block a user