[PyQt] Questions about Trees
William
abecedarian314159 at yahoo.com
Thu Oct 15 09:32:57 BST 2009
Hi! I've been trying to fight my way though the model-view-delegate paradigm in pyqt and must admit that I'm a bit confused. Tables weren't that bad, but now I've reached trees....So, I have some questions. My first question is what are the reasons to use internalptr as compared to internalid.
One strategy that I've seen people adopt is to subclass QAbstractItemModel and to create a dictionary in it. They then use id() on the python side to create a dictionary where the keys are the ids of objects. As far as I can tell, internalid() also returns an address, so in data methods, etc. they can get at the actual item that they want to. When they use createindex, they store the id() of the object of interest for later retrieval, but it seems to hardly matter, because they use internalid() to get the address and then their dictionary to retrieve the object. Here is an example I found using this approach:
"""***************************************************************************
**
** Copyright (C) 2005-2005 Trolltech AS. All rights reserved.
**
** This file is part of the example classes of the Qt Toolkit.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file. Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://www.trolltech.com/products/qt/opensource.html
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://www.trolltech.com/products/qt/licensing.html or contact the
** sales department at sa... at trolltech.com.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
***************************************************************************"""
import sys
from PyQt4 import QtCore, QtGui
from PyQt4.examples.itemviews.simpletreemodel import simpletreemodel_rc
class TreeItem(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.itemData = data
self.childItems = []
def appendChild(self, item):
self.childItems.append(item)
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
def columnCount(self):
return len(self.itemData)
def data(self, column):
return self.itemData[column]
def parent(self):
return self.parentItem
def row(self):
if self.parentItem:
return self.parentItem.childItems.index(self)
return 0
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, data, parent=None):
QtCore.QAbstractItemModel.__init__(self, parent)
self.idMap = {}
rootData = []
rootData.append(QtCore.QVariant("Title"))
rootData.append(QtCore.QVariant("Summary"))
self.rootItem = TreeItem(rootData)
self.idMap[id(self.rootItem)] = self.rootItem
self.setupModelData(data.split("\n"), self.rootItem)
def columnCount(self, parent):
if parent.isValid():
return self.idMap[parent.internalId()].columnCount()
else:
return self.rootItem.columnCount()
def data(self, index, role):
if not index.isValid():
return QtCore.QVariant()
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
try:
item = self.idMap[index.internalId()]
return QtCore.QVariant(item.data(index.column()))
except KeyError:
return QtCore.QVariant()
def flags(self, index):
if not index.isValid():
return QtCore.Qt.ItemIsEnabled
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def headerData(self, section, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.rootItem.data(section)
return QtCore.QVariant()
def index(self, row, column, parent):
if row < 0 or column < 0 or row >= self.rowCount(parent) or column >= self.columnCount(parent):
return QtCore.QModelIndex()
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = self.idMap[parent.internalId()]
childItem = parentItem.child(row)
if childItem:
index = self.createIndex(row, column, id(childItem))
self.idMap.setdefault(index.internalId(), childItem)
return index
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
try:
childItem = self.idMap[index.internalId()]
parentItem = childItem.parent()
if parentItem == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(parentItem.row(), 0, id(parentItem))
except KeyError:
return QtCore.QModelIndex()
def rowCount(self, parent):
if parent.column() > 0:
return 0
try:
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = self.idMap[parent.internalId()]
return parentItem.childCount()
except:
return 0
def setupModelData(self, lines, parent):
parents = []
indentations = []
parents.append(parent)
indentations.append(0)
number = 0
while number < len(lines):
position = 0
while position < len(lines[number]):
if lines[number][position] != " ":
break
position += 1
lineData = lines[number][position:].trimmed()
if not lineData.isEmpty():
# Read the column data from the rest of the line.
columnStrings = lineData.split("\t", QtCore.QString.SkipEmptyParts)
columnData = []
for column in range(0, len(columnStrings)):
columnData.append(columnStrings[column])
if position > indentations[-1]:
# The last child of the current parent is now the new parent
# unless the current parent has no children.
if parents[-1].childCount() > 0:
parents.append(parents[-1].child(parents[-1].childCount() - 1))
indentations.append(position)
else:
while position < indentations[-1] and len(parents) > 0:
parents.pop()
indentations.pop()
# Append a new item to the current parent's list of children.
item = TreeItem(columnData, parents[-1])
self.idMap[id(item)] = item
parents[-1].appendChild(item)
number += 1
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
f = QtCore.QFile(":/default.txt")
f.open(QtCore.QIODevice.ReadOnly)
model = TreeModel(QtCore.QString(f.readAll()))
f.close()
view = QtGui.QTreeView()
view.setModel(model)
view.setWindowTitle("Simple Tree Model")
view.show()
sys.exit(app.exec_())
#######################################################################
#######################################################################
The other strategy I've seen is for people to simply use internalptr() to retrieve the item that they want. Here is an example I found of this approach:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from copy import deepcopy
from cPickle import dumps, load, loads
from cStringIO import StringIO
class PyMimeData(QMimeData):
""" The PyMimeData wraps a Python instance as MIME data.
"""
# The MIME type for instances.
MIME_TYPE = QString('application/x-ets-qt4-instance')
def __init__(self, data=None):
""" Initialise the instance.
"""
QMimeData.__init__(self)
# Keep a local reference to be returned if possible.
self._local_instance = data
if data is not None:
# We may not be able to pickle the data.
try:
pdata = dumps(data)
except:
return
# This format (as opposed to using a single sequence)allows the
# type to be extracted without unpickling the data itself.
self.setData(self.MIME_TYPE, dumps(data.__class__) + pdata)
@classmethod
def coerce(cls, md):
""" Coerce a QMimeData instance to a PyMimeData instance if
possible.
"""
# See if the data is already of the right type. If it is thenwe know
# we are in the same process.
if isinstance(md, cls):
return md
# See if the data type is supported.
if not md.hasFormat(cls.MIME_TYPE):
return None
nmd = cls()
nmd.setData(cls.MIME_TYPE, md.data())
return nmd
def instance(self):
""" Return the instance.
"""
if self._local_instance is not None:
return self._local_instance
io = StringIO(str(self.data(self.MIME_TYPE)))
try:
# Skip the type.
load(io)
# Recreate the instance.
return load(io)
except:
pass
return None
def instanceType(self):
""" Return the type of the instance.
"""
if self._local_instance is not None:
return self._local_instance.__class__
try:
return loads(str(self.data(self.MIME_TYPE)))
except:
pass
return None
class myNode(object):
def __init__(self, name, state, description, parent=None):
self.name = QString(name)
self.state = QString(state)
self.description = QString(description)
self.parent = parent
self.children = []
self.setParent(parent)
def setParent(self, parent):
if parent != None:
self.parent = parent
self.parent.appendChild(self)
else:
self.parent = None
def appendChild(self, child):
self.children.append(child)
def childAtRow(self, row):
return self.children[row]
def rowOfChild(self, child):
for i, item in enumerate(self.children):
if item == child:
return i
return -1
def removeChild(self, row):
value = self.children[row]
self.children.remove(value)
return True
def __len__(self):
return len(self.children)
class myModel(QAbstractItemModel):
def __init__(self, parent=None):
super(myModel, self).__init__(parent)
self.treeView = parent
self.headers = ['Item','State','Description']
self.columns = 3
# Create items
self.root = myNode('root', 'on', 'this is root', None)
itemA = myNode('itemA', 'on', 'this is item A',self.root)
#itemA.setCheckState(0, Qt.Unchecked) # 0 is the column number
itemA1 = myNode('itemA1', 'on', 'this is item A1', itemA)
itemB = myNode('itemB', 'on', 'this is item B', self.root)
itemB1 = myNode('itemB1', 'on', 'this is item B1', itemB)
itemC = myNode('itemC', 'on', 'this is item C',self.root)
itemC1 = myNode('itemC1', 'on', 'this is item C1', itemC)
def supportedDropActions(self):
return Qt.CopyAction | Qt.MoveAction
def flags(self, index):
defaultFlags = QAbstractItemModel.flags(self, index)
if index.isValid():
return Qt.ItemIsEditable | Qt.ItemIsDragEnabled | \
Qt.ItemIsDropEnabled | defaultFlags |Qt.ItemIsUserCheckable
else:
return Qt.ItemIsDropEnabled | defaultFlags | Qt.ItemIsUserCheckable
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return QVariant(self.headers[section])
return QVariant()
def mimeTypes(self):
types = QStringList()
types.append('application/x-ets-qt4-instance')
return types
def mimeData(self, index):
node = self.nodeFromIndex(index[0])
mimeData =PyMimeData(node)
return mimeData
def dropMimeData(self, mimedata, action, row, column, parentIndex):
if action == Qt.IgnoreAction:
return True
dragNode = mimedata.instance()
parentNode = self.nodeFromIndex(parentIndex)
# make an copy of the node being moved
newNode = deepcopy(dragNode)
newNode.setParent(parentNode)
self.insertRow(len(parentNode)-1, parentIndex)
self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"),parentIndex, parentIndex)
return True
def insertRow(self, row, parent):
return self.insertRows(row, 1, parent)
def insertRows(self, row, count, parent):
self.beginInsertRows(parent, row, (row + (count - 1)))
self.endInsertRows()
return True
def removeRow(self, row, parentIndex):
return self.removeRows(row, 1, parentIndex)
def removeRows(self, row, count, parentIndex):
self.beginRemoveRows(parentIndex, row, row)
node = self.nodeFromIndex(parentIndex)
node.removeChild(row)
self.endRemoveRows()
return True
def index(self, row, column, parent):
node = self.nodeFromIndex(parent)
return self.createIndex(row, column, node.childAtRow(row))
def data(self, index, role):
if role == Qt.DecorationRole:
return QVariant()
if role == Qt.TextAlignmentRole:
return QVariant(int(Qt.AlignTop | Qt.AlignLeft))
if role != Qt.DisplayRole:
return QVariant()
node = self.nodeFromIndex(index)
if index.column() == 0:
return QVariant(node.name)
elif index.column() == 1:
return QVariant(node.state)
elif index.column() == 2:
return QVariant(node.description)
else:
return QVariant()
def columnCount(self, parent):
return self.columns
def rowCount(self, parent):
node = self.nodeFromIndex(parent)
if node is None:
return 0
return len(node)
def parent(self, child):
if not child.isValid():
return QModelIndex()
node = self.nodeFromIndex(child)
if node is None:
return QModelIndex()
parent = node.parent
if parent is None:
return QModelIndex()
grandparent = parent.parent
if grandparent is None:
return QModelIndex()
row = grandparent.rowOfChild(parent)
assert row != - 1
return self.createIndex(row, 0, parent)
def nodeFromIndex(self, index):
return index.internalPointer() if index.isValid() else self.root
class myTreeView(QTreeView):
def __init__(self, parent=None):
super(myTreeView, self).__init__(parent)
self.myModel = myModel()
self.setModel(self.myModel)
#item=self.currentItem()
#item.setCheckState(0, Qt.Unchecked) # 0 is the column number
self.dragEnabled()
self.acceptDrops()
self.showDropIndicator()
self.setDragDropMode(QAbstractItemView.InternalMove)
self.connect(self.model(), SIGNAL("dataChanged(QModelIndex,QModelIndex)"), self.change)
self.expandAll()
def change(self, topLeftIndex, bottomRightIndex):
self.update(topLeftIndex)
self.expandAll()
self.expanded()
def expanded(self):
for column in range(self.model().columnCount(QModelIndex())):
self.resizeColumnToContents(column)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(600, 400)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout = QHBoxLayout(self.centralwidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.treeView = myTreeView(self.centralwidget)
self.treeView.setObjectName("treeView")
self.horizontalLayout.addWidget(self.treeView)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(MainWindow)
self.menubar.setGeometry(QRect(0, 0, 600, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QApplication.translate("MainWindow","MainWindow", None, QApplication.UnicodeUTF8))
if __name__ == "__main__":
app = QApplication(sys.argv)
MainWindow = QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Is there a reason to employ one approach as compared to the other?
__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around
http://mail.yahoo.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.riverbankcomputing.com/pipermail/pyqt/attachments/20091015/a2002b48/attachment-0001.html
More information about the PyQt
mailing list