Replace QAbstractItemModel by QStandardItemModel.

This is probably the most depressing commit ever.

For some reason I didn't find this while searching for models I could
use and rolled my own one. This uses A LOT less code, and is A LOT
faster...
This commit is contained in:
Florian Bruhin 2014-04-30 07:32:51 +02:00
parent bc02df0bde
commit 1eabbfbfcf
4 changed files with 21 additions and 297 deletions

2
TODO
View File

@ -2,13 +2,11 @@ Bugs
==== ====
All kind of FIXMEs All kind of FIXMEs
Changing completion models is awfully slow
Style Style
===== =====
Refactor completion widget mess (initializing / changing completions) Refactor completion widget mess (initializing / changing completions)
we probably could replace CompletionModel with QStandardModel...
_foo = QApplication.instance().obj.foo for global singletons? _foo = QApplication.instance().obj.foo for global singletons?
decorators for often-used signals from singletons? decorators for often-used signals from singletons?
- @on_config_changed('colors') - @on_config_changed('colors')

View File

@ -21,8 +21,8 @@ Module attributes:
ROLE_MARKS: The role index used for marks. ROLE_MARKS: The role index used for marks.
""" """
import logging from PyQt5.QtCore import Qt
from PyQt5.QtCore import Qt, QVariant, QAbstractItemModel, QModelIndex from PyQt5.QtGui import QStandardItemModel, QStandardItem
ROLE_MARKS = Qt.UserRole ROLE_MARKS = Qt.UserRole
@ -34,38 +34,17 @@ class NoCompletionsError(Exception):
pass pass
class BaseCompletionModel(QAbstractItemModel): class BaseCompletionModel(QStandardItemModel):
"""A simple tree model based on Python OrderdDict containing tuples. """A simple QStandardItemModel adopted for completions.
Used for showing completions later in the CompletionView. Used for showing completions later in the CompletionView. Supports setting
marks and adding new categories/items easily.
Attributes:
_id_map: A mapping from Python object IDs (from id()) to objects, to be
used as internalIndex for the model.
_root: The root item.
""" """
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self._id_map = {} self.setColumnCount(2)
self._root = CompletionItem([''] * 2)
self._id_map[id(self._root)] = self._root
def _node(self, index):
"""Return the interal data representation for index.
Args:
index: The QModelIndex to get data for.
Return:
The CompletionItem for index, or the root CompletionItem if the
index was invalid.
"""
if index.isValid():
return self._id_map[index.internalId()]
else:
return self._root
def _get_marks(self, needle, haystack): def _get_marks(self, needle, haystack):
"""Return the marks for needle in haystack. """Return the marks for needle in haystack.
@ -99,7 +78,7 @@ class BaseCompletionModel(QAbstractItemModel):
cat = self.index(i, 0) cat = self.index(i, 0)
for k in range(self.rowCount(cat)): for k in range(self.rowCount(cat)):
idx = self.index(k, 0, cat) idx = self.index(k, 0, cat)
old = self.data(idx).value() old = self.data(idx)
marks = self._get_marks(needle, old) marks = self._get_marks(needle, old)
self.setData(idx, marks, ROLE_MARKS) self.setData(idx, marks, ROLE_MARKS)
@ -110,11 +89,10 @@ class BaseCompletionModel(QAbstractItemModel):
name: The name of the category to add. name: The name of the category to add.
Return: Return:
The created CompletionItem. The created QStandardItem.
""" """
cat = CompletionItem([name], self._root) cat = QStandardItem(name)
self._id_map[id(cat)] = cat self.appendRow(cat)
self._root.children.append(cat)
return cat return cat
def new_item(self, cat, name, desc=''): def new_item(self, cat, name, desc=''):
@ -124,131 +102,12 @@ class BaseCompletionModel(QAbstractItemModel):
cat: The parent category. cat: The parent category.
name: The name of the item. name: The name of the item.
desc: The description of the item. desc: The description of the item.
Return:
The created CompletionItem.
""" """
item = CompletionItem((name, desc), parent=cat) nameitem = QStandardItem(name)
self._id_map[id(item)] = item descitem = QStandardItem(desc)
cat.children.append(item) idx = cat.rowCount()
return item cat.setChild(idx, 0, nameitem)
cat.setChild(idx, 1, descitem)
def removeRows(self, position=0, count=1, parent=QModelIndex()):
"""Remove rows from the model.
Override QAbstractItemModel::removeRows.
Args:
position: The start row to remove.
count: How many rows to remove.
parent: The parent QModelIndex.
"""
node = self._node(parent)
self.beginRemoveRows(parent, position, position + count - 1)
node.children.pop(position)
self.endRemoveRows()
def columnCount(self, _parent=QModelIndex()):
"""Return the column count in the model.
Override QAbstractItemModel::columnCount.
Args:
parent: The parent for which to return the column count. Currently
ignored.
Return:
Column count as an integer.
"""
return self._root.column_count()
def rowCount(self, parent=QModelIndex()):
"""Return the children count of an item.
Use the root frame if parent is invalid.
Override QAbstractItemModel::rowCount.
Args:
parent: The parent for which to return the row count.
Return:
Row count as an integer.
"""
if parent.column() > 0:
return 0
if not parent.isValid():
pitem = self._root
else:
pitem = self._id_map[parent.internalId()]
return len(pitem.children)
def data(self, index, role=Qt.DisplayRole):
"""Return the data for role/index as QVariant.
Override QAbstractItemModel::data.
Args:
index: The QModelIndex for which to get data for.
role: The role to use for the data.
Return:
The data as QVariant or an invalid QVariant on error.
"""
if not index.isValid():
return QVariant()
try:
item = self._id_map[index.internalId()]
except KeyError:
return QVariant()
try:
return QVariant(item.data(index.column(), role))
except (IndexError, ValueError):
return QVariant()
def headerData(self, section, orientation, role=Qt.DisplayRole):
"""Return the header data for role/index as QVariant.
Override QAbstractItemModel::headerData.
Args:
section: The section (as int) for which to get the header data.
orientation: Qt.Vertical or Qt.Horizontal
Return:
The data as QVariant or an invalid QVariant on error.
"""
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return QVariant(self._root.data(section))
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
"""Set the data for role/index to value.
Override QAbstractItemModel::setData.
Args:
index: The QModelIndex where to set the data.
value: The new data value.
role: The role to set the data for.
Return:
True on success, False on failure.
Emit:
dataChanged when the data was changed.
"""
if not index.isValid():
return False
item = self._id_map[index.internalId()]
try:
item.setdata(index.column(), value, role)
except (IndexError, ValueError):
return False
# We explicitely need to select this version, see:
# http://python.6.x6.nabble.com/Bug-Report-pyqt5-discard-silently-signal-with-missing-optional-parameters-dataChanged-roles-for-exam-tt5043737.html
self.dataChanged.emit(index, index, [])
return True
def flags(self, index): def flags(self, index):
"""Return the item flags for index. """Return the item flags for index.
@ -270,61 +129,6 @@ class BaseCompletionModel(QAbstractItemModel):
# category # category
return Qt.NoItemFlags return Qt.NoItemFlags
def index(self, row, column, parent=QModelIndex()):
"""Return the QModelIndex for row/column/parent.
Override QAbstractItemModel::index.
Args:
row: The row (int) to get an index for.
column: The column (int) to get an index for.
parent: The parent (QModelIndex) to get an index for.
Return:
A generated QModelIndex or an invalid QModelIndex on failure.
"""
if parent.model() is not None and parent.model() is not self:
logging.warn("index() called with wrong parent! - "
"row {}, column {}, parentmodel {}, self {}".format(
row, column, parent.model(), self))
return
if (0 <= row < self.rowCount(parent) and
0 <= column < self.columnCount(parent)):
pass
else:
return QModelIndex()
if not parent.isValid():
parent_item = self._root
else:
parent_item = self._id_map[parent.internalId()]
child_item = parent_item.children[row]
if child_item:
index = self.createIndex(row, column, id(child_item))
self._id_map.setdefault(index.internalId(), child_item)
return index
else:
return QModelIndex()
def parent(self, index):
"""Return the QModelIndex of the parent of the object behind index.
Override QAbstractItemModel::parent.
Args:
index: The QModelIndex to get the parent for.
Return:
The parent's QModelIndex or an invalid QModelIndex on failure.
"""
if not index.isValid():
return QModelIndex()
item = self._id_map[index.internalId()].parent
if item == self._root or item is None:
return QModelIndex()
return self.createIndex(item.row(), 0, id(item))
def sort(self, column, order=Qt.AscendingOrder): def sort(self, column, order=Qt.AscendingOrder):
"""Sort the data in column according to order. """Sort the data in column according to order.
@ -334,83 +138,3 @@ class BaseCompletionModel(QAbstractItemModel):
NotImplementedError, should be overwritten in a superclass. NotImplementedError, should be overwritten in a superclass.
""" """
raise NotImplementedError raise NotImplementedError
class CompletionItem():
"""An item (row) in a CompletionModel.
Attributes:
parent: The parent of this item.
children: The children of this item.
_data: The data of this item.
_marks: The marks of this item.
"""
def __init__(self, data, parent=None):
"""Constructor for CompletionItem.
Args:
data: The data for the model, as tuple (columns).
parent: An optional parent item.
"""
self.parent = parent
self.children = []
self._data = data
self._marks = []
def data(self, column, role=Qt.DisplayRole):
"""Get the data for role/column.
Args:
column: The column (int) to get data for.
role: The role to get data for.
Return:
The requested data.
Raise:
ValueError if the role is invalid.
"""
if role == Qt.DisplayRole:
return self._data[column]
elif role == ROLE_MARKS:
return self._marks
else:
raise ValueError("Invalid role {}".format(role))
def setdata(self, column, value, role=Qt.DisplayRole):
"""Set the data for column/role to value.
Args:
column: The column (int) to set the data for.
value: The value to set the data to.
role: The role to set the data for.
Raise:
ValueError if the role is invalid.
"""
if role == Qt.DisplayRole:
self._data[column] = value
elif role == ROLE_MARKS:
self._marks = value
else:
raise ValueError("Invalid role {}".format(role))
def column_count(self):
"""Get the column count in the item.
Return:
The column count.
"""
return len(self._data)
def row(self):
"""Get the row index (int) of the item.
Return:
The row index of the item, or 0 if we're a root item.
"""
if self.parent:
return self.parent.children.index(self)
return 0

View File

@ -106,7 +106,7 @@ class CompletionFilterModel(QSortFilterProxyModel):
if parent == QModelIndex(): if parent == QModelIndex():
return True return True
idx = self.srcmodel.index(row, 0, parent) idx = self.srcmodel.index(row, 0, parent)
data = self.srcmodel.data(idx).value() data = self.srcmodel.data(idx)
# TODO more sophisticated filtering # TODO more sophisticated filtering
if not self.pattern: if not self.pattern:
return True return True
@ -125,8 +125,8 @@ class CompletionFilterModel(QSortFilterProxyModel):
Return: Return:
True if left < right, else False True if left < right, else False
""" """
left = self.srcmodel.data(lindex).value() left = self.srcmodel.data(lindex)
right = self.srcmodel.data(rindex).value() right = self.srcmodel.data(rindex)
leftstart = left.startswith(self.pattern) leftstart = left.startswith(self.pattern)
rightstart = right.startswith(self.pattern) rightstart = right.startswith(self.pattern)

View File

@ -448,6 +448,8 @@ class _CompletionItemDelegate(QStyledItemDelegate):
if index.column() == 0: if index.column() == 0:
marks = index.data(ROLE_MARKS) marks = index.data(ROLE_MARKS)
if marks is None:
return
for mark in marks: for mark in marks:
cur = QTextCursor(self._doc) cur = QTextCursor(self._doc)
cur.setPosition(mark[0]) cur.setPosition(mark[0])