2

I want to overload QInputDialog because I want to replace its QCombobox with my own QCombobox derivative. I checked the source-code of QInputDialog and tried to overload its ensureCombobox(). However, when I try something like:

class AutoCompleteInputDialog(QtGui.QInputDialog):

    def __init__(self, *args, **kwargs):
        self.ensureComboBox()
        super(AutoCompleteInputDialog, self).__init__(*args, **kwargs)


    def ensureComboBox(self):
        print "ensureComboBox"
        self.comboBox = AutoCompleteComboBox(self)
        self.comboBox.hide()

        self.comboBox.editTextChanged.connect(self.textChanged)
        self.comboBox.currentIndexChanged.connect(self.textChanged)

AutoCompleteInputDialog.getItems(None, "test title", "test label", ["albatross 12", "tiger 12", "item 2", "item 3"])

ensureCombobox is never called.

I tried also to define a static method that creates a QInputDialog and sets its combobox. But it does not work either.

    @staticmethod
    def getItem(*args, **kwargs):
        dialog = QtGui.QInputDialog()
        dialog.comboBox = AutoCompleteComboBox(dialog)
        return dialog.getItem(*args, **kwargs)

For completeness, the code of AutoCompleteCombobox

class AutoCompleteComboBox(QtGui.QComboBox):
    def __init__(self, *args, **kwargs):
        super(AutoCompleteComboBox, self).__init__(*args, **kwargs)

        self.setEditable(True)
        self.setInsertPolicy(self.NoInsert)

        # self.comp = QtGui.QCompleter([""], self)
        self.comp = CustomQCompleter([""], self)
        self.comp.setCompletionMode(QtGui.QCompleter.PopupCompletion)
        self.comp.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.setCompleter(self.comp)#
        self.setModel(["hallo babe", "world", "we", "are babe"])

    def setModel(self, strList):
        # self.comp.model().setStringList(strList)
        self.clear()
        self.insertItems(0, strList)
        self.comp.setModel(self.model())

    def focusInEvent(self, event):
        self.clearEditText()
        super(AutoCompleteComboBox, self).focusInEvent(event)

class CustomQCompleter(QtGui.QCompleter):
    """
    copied from: http://stackoverflow.com/a/7767999/2156909
    """
    def __init__(self, *args):#parent=None):
        super(CustomQCompleter, self).__init__(*args)
        self.local_completion_prefix = ""
        self.source_model = None

    def setModel(self, model):
        self.source_model = model
        super(CustomQCompleter, self).setModel(self.source_model)

    def updateModel(self):
        local_completion_prefix = self.local_completion_prefix
        class InnerProxyModel(QtGui.QSortFilterProxyModel):
            def filterAcceptsRow(self, sourceRow, sourceParent):
                index0 = self.sourceModel().index(sourceRow, 0, sourceParent)
                return local_completion_prefix.lower() in self.sourceModel().data(index0).lower()
        proxy_model = InnerProxyModel()
        proxy_model.setSourceModel(self.source_model)
        super(CustomQCompleter, self).setModel(proxy_model)

    def splitPath(self, path):
        self.local_completion_prefix = path
        self.updateModel()
        return ""
mdurant
  • 27,272
  • 5
  • 45
  • 74
P.R.
  • 3,785
  • 1
  • 27
  • 47
  • 2
    you are calling `self.ensureComboBox()` before `super().__init__()`, meaning your `self.comboBox` is likely getting overwritten. – user2085282 Sep 15 '14 at 19:11
  • ah sorry. I forgot to mention, that I tried calling `self.ensureCombobox` before and after `super()` – P.R. Sep 15 '14 at 19:53

3 Answers3

4

In order to reimplement a C++ method in PySide or PyQt, at least three conditions must be met:

  1. The method must be part of Qt's public API
  2. The method must be defined as virtual
  3. PySide/PyQt must provide a python wrapper for the method

Note that if the second condition is not true, a reimplementation of the method in python would never be called internally by Qt, and so would be mostly useless. The Qt documentation should always clearly indicate whether a method is virtual or not, e.g.:

    virtual QSize sizeHint() const

So, since none of the above conditions are met for ensureCombobox, it should be clear that reimplementing it is not going to be effective (as you've already discovered).

But anyway, for something as simple as an input dialog, is it really worth the bother of trying to create a subclass? It's only four widgets in a dialog...

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Thanks for your explanation. I asked the question on this "easy" case because I wanted to know how experts would proceed generally, as I expected an elegant alternative to such a "reimplementation" would exist. – P.R. Sep 16 '14 at 09:48
  • 1
    @P.R. There are lots of Qt classes that provide APIs for replacing certain elements with your own implentation (QComboBox being one of them). But there also simple convenience classes like QInputDialog which generally have much less flexible APIs. The elegant alternative to these classes is to simply write your version that does exactly what you want. This will usually be much quicker and easier than trying to work around the limitations in the built-in classes. – ekhumoro Sep 16 '14 at 13:39
2

OK, so the problem is that when you call QInputDialog, it sets up the sub-widgets in C++-land (or so it appears to me), so you cannot directly substitute in your favourite flavour. Therefore you need to go down the more typical route as shown:

class AutoCompleteInputDialog(QtGui.QDialog):

    def __init__(self, *args, **kwargs):
        super(AutoCompleteInputDialog, self).__init__(*args, **kwargs)
        self.comboBox = AutoCompleteComboBox(self)
        self.va = QtGui.QVBoxLayout(self)
        self.va.addWidget(self.comboBox)
        self.box = QtGui.QWidget(self)
        self.ha = QtGui.QHBoxLayout(self)
        self.va.addWidget(self.box)
        self.box.setLayout(self.ha)
        self.OK = QtGui.QPushButton("OK",self)
        self.OK.setDefault(True)
        self.cancel = QtGui.QPushButton("Cancel",self)
        self.ha.addWidget(self.OK)
        self.ha.addWidget(self.cancel)
        self.OK.clicked.connect(self.accept)
        self.cancel.clicked.connect(self.reject)

The resultant value is stored in acid.comboBox.currentText().

mdurant
  • 27,272
  • 5
  • 45
  • 74
  • Ok thanks, I'll try it tomorrow. But that means at that level I would have to reimplement the complete `QInputDialog` to change that single element. In this case it is not much trouble, but in a case where the standard class is more complex using methodology from OOP is not possible?! Is this a PySide problem, or would I face the same problems in C++ as well? – P.R. Sep 15 '14 at 21:24
0

AutoCompleteInputDialog.getItems, a class method, is actually identical to QtGui.QInputDialog.getItems in this case, by inheritance. The overloading only works on instance method, via passing the implicit self. So to make this work, you either need to reimplement getItems, or use the class directly without the convenience function:

acid = AutoCompleteInputDialog(parent=None)
#set up options, buttons
answer = acid.exec_()
mdurant
  • 27,272
  • 5
  • 45
  • 74
  • I like `acid` ;) Generally, you are right that in this way my `ensureComboBox` is called. Unfortunately, I still get to see the standard `QComboBox` when the dialog shows up.. – P.R. Sep 15 '14 at 20:10
  • A problem might be that these things (like the `QComboBox` member as well as the `ensureComboBox` function) are handled by `InputDialogPrivate` which is used with the qpointer in the Qt code http://code.woboq.org/kde/qt4/src/gui/dialogs/qinputdialog.cpp.html#QInputDialogPrivate – P.R. Sep 15 '14 at 20:13