Skip to content

Commit

Permalink
Replace QAbstractItemModel by QStandardItemModel.
Browse files Browse the repository at this point in the history
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...
  • Loading branch information
The-Compiler committed Apr 30, 2014
1 parent bc02df0 commit 1eabbfb
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 297 deletions.
2 changes: 0 additions & 2 deletions TODO
Expand Up @@ -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')
Expand Down
308 changes: 16 additions & 292 deletions qutebrowser/models/basecompletion.py
Expand Up @@ -21,8 +21,8 @@
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

Expand All @@ -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.
Expand Down Expand Up @@ -99,7 +78,7 @@ def mark_all_items(self, needle):
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)

Expand All @@ -110,11 +89,10 @@ def new_category(self, name):
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=''):
Expand All @@ -124,131 +102,12 @@ def new_item(self, cat, name, desc=''):
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.
Expand All @@ -270,61 +129,6 @@ def flags(self, index):
# 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.
Expand All @@ -334,83 +138,3 @@ def sort(self, column, order=Qt.AscendingOrder):
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
6 changes: 3 additions & 3 deletions qutebrowser/models/completionfilter.py
Expand Up @@ -106,7 +106,7 @@ def filterAcceptsRow(self, row, parent):
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
Expand All @@ -125,8 +125,8 @@ def lessThan(self, lindex, rindex):
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)
Expand Down

0 comments on commit 1eabbfb

Please sign in to comment.