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.parentItem: Optional["SidebarItem"] = None
|
||||||
self.tooltip: Optional[str] = None
|
self.tooltip: Optional[str] = None
|
||||||
self.row_in_parent: Optional[int] = None
|
self.row_in_parent: Optional[int] = None
|
||||||
|
self._search_matches_self = False
|
||||||
|
self._search_matches_child = False
|
||||||
|
|
||||||
def addChild(self, cb: "SidebarItem") -> None:
|
def addChild(self, cb: "SidebarItem") -> None:
|
||||||
self.children.append(cb)
|
self.children.append(cb)
|
||||||
@ -104,6 +106,30 @@ class SidebarItem:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return None
|
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):
|
class SidebarModel(QAbstractItemModel):
|
||||||
def __init__(self, root: SidebarItem) -> None:
|
def __init__(self, root: SidebarItem) -> None:
|
||||||
@ -120,6 +146,9 @@ class SidebarModel(QAbstractItemModel):
|
|||||||
def item_for_index(self, idx: QModelIndex) -> SidebarItem:
|
def item_for_index(self, idx: QModelIndex) -> SidebarItem:
|
||||||
return idx.internalPointer()
|
return idx.internalPointer()
|
||||||
|
|
||||||
|
def search(self, text: str) -> None:
|
||||||
|
self.root.search(text.lower())
|
||||||
|
|
||||||
# Qt API
|
# Qt API
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
@ -204,39 +233,20 @@ class SidebarModel(QAbstractItemModel):
|
|||||||
|
|
||||||
|
|
||||||
def expand_where_necessary(
|
def expand_where_necessary(
|
||||||
model: SidebarModel, tree: QTreeView, parent: Optional[QModelIndex] = None
|
model: SidebarModel,
|
||||||
|
tree: QTreeView,
|
||||||
|
parent: Optional[QModelIndex] = None,
|
||||||
|
searching: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
parent = parent or QModelIndex()
|
parent = parent or QModelIndex()
|
||||||
for row in range(model.rowCount(parent)):
|
for row in range(model.rowCount(parent)):
|
||||||
idx = model.index(row, 0, parent)
|
idx = model.index(row, 0, parent)
|
||||||
if not idx.isValid():
|
if not idx.isValid():
|
||||||
continue
|
continue
|
||||||
expand_where_necessary(model, tree, idx)
|
expand_where_necessary(model, tree, idx, searching)
|
||||||
item = model.item_for_index(idx)
|
if item := model.item_for_index(idx):
|
||||||
if item and item.expanded:
|
if item.is_expanded(searching):
|
||||||
tree.setExpanded(idx, True)
|
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)
|
|
||||||
|
|
||||||
|
|
||||||
class SidebarSearchBar(QLineEdit):
|
class SidebarSearchBar(QLineEdit):
|
||||||
@ -312,7 +322,7 @@ class SidebarTreeView(QTreeView):
|
|||||||
bgcolor = QPalette().window().color().name()
|
bgcolor = QPalette().window().color().name()
|
||||||
self.setStyleSheet("QTreeView { background: '%s'; }" % bgcolor)
|
self.setStyleSheet("QTreeView { background: '%s'; }" % bgcolor)
|
||||||
|
|
||||||
def model(self) -> Union[FilterModel, SidebarModel]:
|
def model(self) -> SidebarModel:
|
||||||
return super().model()
|
return super().model()
|
||||||
|
|
||||||
def refresh(self) -> None:
|
def refresh(self) -> None:
|
||||||
@ -340,47 +350,22 @@ class SidebarTreeView(QTreeView):
|
|||||||
self.current_search = None
|
self.current_search = None
|
||||||
self.refresh()
|
self.refresh()
|
||||||
return
|
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
|
self.current_search = text
|
||||||
# Without collapsing first, can be very slow. Surely there's
|
# start from a collapsed state, as it's faster
|
||||||
# a better way than this?
|
|
||||||
self.collapseAll()
|
self.collapseAll()
|
||||||
filter_model.setFilterFixedString(text)
|
self.model().search(text)
|
||||||
self.expandMatches(self.rootIndex())
|
expand_where_necessary(self.model(), self, searching=True)
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def drawRow(
|
def drawRow(
|
||||||
self, painter: QPainter, options: QStyleOptionViewItem, idx: QModelIndex
|
self, painter: QPainter, options: QStyleOptionViewItem, idx: QModelIndex
|
||||||
) -> None:
|
) -> None:
|
||||||
if self.current_search is None:
|
if self.current_search and (item := self.model().item_for_index(idx)):
|
||||||
return super().drawRow(painter, options, idx)
|
if item.is_highlighted():
|
||||||
if not (item := self.model().item_for_index(idx)):
|
brush = QBrush(theme_manager.qcolor("suspended-bg"))
|
||||||
return super().drawRow(painter, options, idx)
|
painter.save()
|
||||||
if self.current_search.lower() in item.name.lower():
|
painter.fillRect(options.rect, brush)
|
||||||
brush = QBrush(theme_manager.qcolor("suspended-bg"))
|
painter.restore()
|
||||||
painter.save()
|
|
||||||
painter.fillRect(options.rect, brush)
|
|
||||||
painter.restore()
|
|
||||||
return super().drawRow(painter, options, idx)
|
return super().drawRow(painter, options, idx)
|
||||||
|
|
||||||
def dropEvent(self, event: QDropEvent) -> None:
|
def dropEvent(self, event: QDropEvent) -> None:
|
||||||
|
Loading…
Reference in New Issue
Block a user