[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