[PyQt] QComboBox + custom model, view, completer fails.(with example)

Igor Prischepoff igor at tyumbit.ru
Tue Feb 19 12:40:34 GMT 2008


Hello,all!
I'm trying to build my own completion in QComboBox.
My overall setup with this thing is not working.Apparently I'm doing
something wrong here.
But what?

What I'm trying to do:
let's suppose we have this completion list (for simplicity it's small and
with one column only,
real data have much more columns).
So items goes like this:
- item1
- item11
- item111
- item1111
if I type '111' in QComboBox I want a popup with 'item111','item1111'
because '111' is only in those items.
I'm defined my own TreeView,SourceModel, SourceFilterModel(like proxy for
filtering of SourceModel) and bind them
to standard QComboBox.
Than I did a little trick with passing focus to lineEdit and back to Popup.
Well, my whole schema is not working.
Python recursively calls 'somethingIsTyped' method in which I'm trying to
force popup to be filtered and
shown with new data and I got stack overflow catched by python interpreter.

Please see attached example with comments.
Can anybody enlighten me on what I'm doing wrong?

System: Python 2.5, pyqt 4.3.3, qt 4.3.3, winxp.

---

igor at tyumbit.ru
-------------- next part --------------
import sys

from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import *


app = QtGui.QApplication(sys.argv)
form_class, base_class = uic.loadUiType("mainform.ui")

class SourceModel(QtCore.QAbstractItemModel):
    """ sample model
    """
    def __init__(self, parent=None):
        QtCore.QAbstractItemModel.__init__(self, parent)
        self.colLabels = ["column1","column2"]
        self.items=[
                    ['item1'     , 'item2'     ]  , 
                    ['item11'    , 'item22'    ]  , 
                    ['item111'   , 'item222'   ]  , 
                    ['item1111'  , 'item2222'  ]  , 
                    ['item11111' , 'item22222' ]  , 
                ]
    def index(self,row,column,parent):
        return self.createIndex(row,column,str(row)+str(column))
    def parent(self, index):
        if not index.isValid():
            return QtCore.QModelIndex()
        return QtCore.QModelIndex()
    def rowCount(self, parent):
        return len(self.items)
    def columnCount(self, parent):
        return len(self.colLabels)
    def data(self, index, role):
        if not index.isValid():
            return QtCore.QVariant()
        elif role != QtCore.Qt.DisplayRole and role != QtCore.Qt.EditRole:
            return QtCore.QVariant()
        row = index.row()
        col = index.column()
        return QtCore.QVariant(str(self.items[row][col]))
    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 QtCore.QVariant(self.colLabels[section])
        return QtCore.QVariant()

class SourceFilterModel(QtGui.QSortFilterProxyModel):
    """ used to filter and sort SourceModel. Acts like a proxy
    """
    def __init__(self,parent):
        QtGui.QSortFilterProxyModel.__init__(self,parent)
        self.gui = parent
    def filterAcceptsRow(self,sourceRow,sourceParent):
        index = self.sourceModel().index(sourceRow,1,sourceParent)
        # get text from uderlying source model
        item_text = str(self.sourceModel().data(index,QtCore.Qt.DisplayRole).toString())
        # get what user is typed in comboBox
        typed_text = str(self.gui.autoCombo.lineEdit().text())
        # if typed text in item_text - then this item is Ok to show in
        # resulting filtering model
        if item_text.find(typed_text)  == -1:
            return False
        else:
            return True

class myTreeView(QtGui.QTreeView):
    """ display our model in 2 column plain list with headers
    """
    def __init__(self, parent=None):
        QtGui.QTreeView.__init__(self, parent)
        # store MainForm instance for referencing autoCombo later.
        self.parent = parent
    def keyPressEvent(self, e):
        if e.key() in [QtCore.Qt.Key_Escape,QtCore.Qt.Key_Tab,QtCore.Qt.Key_Down,QtCore.Qt.Key_Up]:
            # escape, tab, Up, Down key events is passed to TreeView so we can
            # navigate popup list.
            QtGui.QTreeView.keyPressEvent(self,e)
        else:
            # typed chars is passed to autoCombo's lineEdit 
            self.parent.autoCombo.keyPressEvent(e)

class MainForm(QtGui.QDialog, form_class):
    def __init__(self, *args):
        QtGui.QWidget.__init__(self, *args)
        self.setupUi(self)

        self.treeView = QtGui.QTreeView(self)
        self.treeView.setSelectionMode(QtGui.QAbstractItemView.MultiSelection)
        self.treeView.setSortingEnabled(True)
        self.treeView.setAnimated(True)
        self.treeView.setWordWrap(True)
        self.treeView.setItemsExpandable(False)
        self.treeView.setRootIsDecorated(False)


        self.sourcemodel = SourceModel()
        self.proxyModel = SourceFilterModel(self)
        self.proxyModel.setSourceModel(self.sourcemodel)

        self.autoCombo.setModel(self.proxyModel)
        self.autoCombo.setView(self.treeView)

        #
        # setup a completer in order to turn off inline completion for
        # comboBox
        #
        self.autoCombo.completer().setCompletionMode(QtGui.QCompleter.PopupCompletion)
        self.autoCombo.clearEditText()

        self.connect(self.autoCombo, SIGNAL("editTextChanged ( QString )"), self.somethingIsTyped)

    def somethingIsTyped(self,text):
        # !!! WARNING: Memory error:Stack overflow !!! catched by python.
        #
        # that's where trouble lies... this function is going to be called
        # recursively until stack overflow. Why?
        print "editTextChanged ->",text
        # hide old popup
        self.autoCombo.hidePopup()
        print "now going to invalidate filter" 
        # this is going to call filterAcceptsRow of SourceFilterModel
        # indirectly.We can compare what user is typed in lineEdit and what is
        # in our model and filter model's items
        self.proxyModel.invalidateFilter()
        # force popup to display new filtered content
        self.autoCombo.showPopup()
        # set focus back to autoCombo's lineEdit
        self.autoCombo.lineEdit().setFocus()
form = MainForm()
form.show()
app.exec_()
-------------- next part --------------
A non-text attachment was scrubbed...
Name: mainform.ui
Type: application/octet-stream
Size: 1076 bytes
Desc: not available
Url : http://www.riverbankcomputing.com/pipermail/pyqt/attachments/20080219/80f83b1e/mainform.obj


More information about the PyQt mailing list