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
Changing completion models is awfully slow
Style
=====
Refactor completion widget mess (initializing / changing completions)
we probably could replace CompletionModel with QStandardModel...
_foo = QApplication.instance().obj.foo for global singletons?
decorators for often-used signals from singletons?
- @on_config_changed('colors')

View File

@ -21,8 +21,8 @@ Module attributes:
ROLE_MARKS: The role index used for marks.
"""
import logging
from PyQt5.QtCore import Qt, QVariant, QAbstractItemModel, QModelIndex
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QStandardItemModel, QStandardItem
ROLE_MARKS = Qt.UserRole
@ -34,38 +34,17 @@ class NoCompletionsError(Exception):
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.
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.
Used for showing completions later in the CompletionView. Supports setting
marks and adding new categories/items easily.
"""
def __init__(self, parent=None):
super().__init__(parent)
self._id_map = {}
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
self.setColumnCount(2)
def _get_marks(self, needle, haystack):
"""Return the marks for needle in haystack.
@ -99,7 +78,7 @@ class BaseCompletionModel(QAbstractItemModel):
cat = self.index(i, 0)
for k in range(self.rowCount(cat)):
idx = self.index(k, 0, cat)
old = self.data(idx).value()
old = self.data(idx)
marks = self._get_marks(needle, old)
self.setData(idx, marks, ROLE_MARKS)
@ -110,11 +89,10 @@ class BaseCompletionModel(QAbstractItemModel):
name: The name of the category to add.
Return:
The created CompletionItem.
The created QStandardItem.
"""
cat = CompletionItem([name], self._root)
self._id_map[id(cat)] = cat
self._root.children.append(cat)
cat = QStandardItem(name)
self.appendRow(cat)
return cat
def new_item(self, cat, name, desc=''):
@ -124,131 +102,12 @@ class BaseCompletionModel(QAbstractItemModel):
cat: The parent category.
name: The name of the item.
desc: The description of the item.
Return:
The created CompletionItem.
"""
item = CompletionItem((name, desc), parent=cat)
self._id_map[id(item)] = item
cat.children.append(item)
return item
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
nameitem = QStandardItem(name)
descitem = QStandardItem(desc)
idx = cat.rowCount()
cat.setChild(idx, 0, nameitem)
cat.setChild(idx, 1, descitem)
def flags(self, index):
"""Return the item flags for index.
@ -270,61 +129,6 @@ class BaseCompletionModel(QAbstractItemModel):
# category
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):
"""Sort the data in column according to order.
@ -334,83 +138,3 @@ class BaseCompletionModel(QAbstractItemModel):
NotImplementedError, should be overwritten in a superclass.
"""
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():
return True
idx = self.srcmodel.index(row, 0, parent)
data = self.srcmodel.data(idx).value()
data = self.srcmodel.data(idx)
# TODO more sophisticated filtering
if not self.pattern:
return True
@ -125,8 +125,8 @@ class CompletionFilterModel(QSortFilterProxyModel):
Return:
True if left < right, else False
"""
left = self.srcmodel.data(lindex).value()
right = self.srcmodel.data(rindex).value()
left = self.srcmodel.data(lindex)
right = self.srcmodel.data(rindex)
leftstart = left.startswith(self.pattern)
rightstart = right.startswith(self.pattern)

View File

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