Skip to content

Instantly share code, notes, and snippets.

@minimalefforttech
Created December 5, 2024 09:46
Show Gist options
  • Save minimalefforttech/1f0a1854a373b6522dcf761315aa88da to your computer and use it in GitHub Desktop.
Save minimalefforttech/1f0a1854a373b6522dcf761315aa88da to your computer and use it in GitHub Desktop.
Short example showing smarter filtering in Qt
from difflib import SequenceMatcher
try:
from PySide6 import QtCore, QtGui, QtWidgets
except ImportError:
from PySide2 import QtCore, QtGui, QtWidgets
class FilterModel(QtCore.QSortFilterProxyModel):
""" A simple filter model based on sequencematcher quick_ratio.
ratio is 0-1 value for a match.
Note: This is not performant, it is an example, ideally you would look into precaching or difflib.get_close_matches() for many items
https://docs.python.org/3/library/difflib.html
"""
def __init__(self, ratio:float=0.6, parent=None):
super().__init__(parent)
self._filter_text = ""
self._ratio = ratio
self._show_all = False
self.sort(0, QtCore.Qt.AscendingOrder)
@QtCore.Slot(str)
def set_filter_text(self, text:str):
self._filter_text = str(text).lower()
self.invalidate()
@QtCore.Slot(float)
def set_ratio(self, ratio:float):
self._ratio = float(ratio)
self.invalidate()
@QtCore.Slot(bool)
def set_show_all(self, show_all:bool):
self._show_all = bool(show_all)
self.invalidate()
def filterAcceptsColumn(self, source_column:int, source_parent:QtCore.QModelIndex)->bool:
return True
def filterAcceptsRow(self, source_row:int, source_parent:QtCore.QModelIndex)->bool:
if self._ratio <= 0.0 or self._show_all or not self._filter_text:
# Nothing set, show everything
return True
# Case-insensitive
text = (self.sourceModel().index(source_row, 0, source_parent).data() or "").lower()
if not text:
return False
if self._filter_text in text:
return True
# First parameter is optional filtering of "junk" text like spaces, default is usually fine.
# ratio is more accurate but doesn't provide much in this context, real_quick_ratio is not accurate enough.
ratio = SequenceMatcher(None, self._filter_text, text).quick_ratio()
return ratio >= self._ratio
def lessThan(self, left:QtCore.QModelIndex, right:QtCore.QModelIndex)->bool:
if not self._filter_text or self._show_all:
return left.row() < right.row()
left_ratio = SequenceMatcher(None, self._filter_text, left.data().lower()).quick_ratio() if left.data() else 0.0
right_ratio = SequenceMatcher(None, self._filter_text, right.data().lower()).quick_ratio() if right.data() else 0.0
# Sort text ascending so ratio is flipped
return left_ratio > right_ratio
def data(self, index, role=QtCore.Qt.DisplayRole):
# Provides a color falloff to demonstrate the filtering
if not self._show_all or role != QtCore.Qt.ForegroundRole or self._ratio <= 0.0:
return super().data(index, role)
ratio = SequenceMatcher(None, self._filter_text, (index.data() or "").lower()).quick_ratio()
if ratio < self._ratio:
# Draw falloff color between 20 (no match) and 255 (full match)
t = ratio * (1.0/self._ratio)
luminance = (1 - t) * 20 + t * 255
return QtGui.QBrush(QtGui.QColor(luminance, luminance, luminance))
return super().data(index, role)
class Widget(QtWidgets.QWidget):
""" A Simple example widget that filters items based on some text input
"""
def __init__(self, items, parent=None):
super().__init__(parent)
self.setWindowTitle("Filter Example")
layout = QtWidgets.QVBoxLayout(self)
search_box = QtWidgets.QLineEdit()
search_box.setPlaceholderText("filter...")
layout.addWidget(search_box)
show_all_box = QtWidgets.QCheckBox("Show All")
layout.addWidget(show_all_box)
view = QtWidgets.QListView()
layout.addWidget(view)
model = QtCore.QStringListModel(items, parent=self)
filter_model = FilterModel(parent=self)
filter_model.setSourceModel(model)
view.setModel(filter_model)
search_box.textChanged.connect(filter_model.set_filter_text)
show_all_box.toggled.connect(filter_model.set_show_all)
items = ["January", "February", "March", "April", "May", "June", "July", "August", "October", "November", "December"]
widget = Widget(items)
widget.show()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment