Completion problem with QLineEdit and QItemDelegate



  • Hello,

    I'm trying to implement the QCompleter example we can find in the widgets/tools/customcompleter sources.
    What I want is when I'm typing some text, if there is some matches, the first completion line available is selected so if I hit the return key (and only if I hit it), the completion is done.

    But in my code here, the line is never selected. I think my problem is located in the keyPressEvent. It is a minimal example, written in pyqt5.

    Any help is very appreciated :)

    import sys
    
    from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant
    from PyQt5.QtWidgets import (QApplication, QCompleter, QItemDelegate,
                                 QLineEdit, QMainWindow, QTableView)
    
    
    class MyLineEdit(QLineEdit):
    
        def __init__(self, parent=None, completer=None):
            super().__init__(parent)
            if completer:
                self.setCompleter(completer)
    
        def setCompleter(self, completer):
            if completer:
                completer.setWidget(self)
                completer.setCompletionMode(QCompleter.PopupCompletion)
                completer.setCaseSensitivity(Qt.CaseInsensitive)
                completer.setModelSorting(
                    QCompleter.CaseSensitivelySortedModel)
                completer.setMaxVisibleItems(15)
                completer.activated.connect(self.insertCompletion)
    
            super().setCompleter(completer)
    
        def insertCompletion(self, completion):
            completer = self.completer()
            if completer and completer.widget() == self:
                completer.widget().setText(completion)
    
        def keyPressEvent(self, event):
    
            completer = self.completer()
    
            if event.key() in (Qt.Key_Return, Qt.Key_Enter,
                               Qt.Key_Tab, Qt.Key_Backtab):
                self.returnPressed.emit()
                if completer and completer.popup().isHidden():
                    return
    
            super().keyPressEvent(event)
            input_text = self.text()
    
            if completer:
    
                if not event.text():
                    completer.popup().hide()
                    return
    
                if input_text and input_text != completer.completionPrefix():
                    completer.setCompletionPrefix(input_text)
                    completer.popup().setCurrentIndex(
                        completer.completionModel().index(0, 0))
    
    
    class MyDelegate(QItemDelegate):
    
        def __init__(self, parent):
            super().__init__(parent)
    
        def createEditor(self, parent, option, index):
            strings = ('tata', 'tete', 'titi', 'toto', 'tutu')
            completer = QCompleter(strings)
            editor = MyLineEdit(parent)
            editor.setCompleter(completer)
            editor.editingFinished.connect(self.commitAndCloseEditor)
            editor.returnPressed.connect(self.commitAndCloseEditor)
            return editor
    
        def commitAndCloseEditor(self):
            editor = self.sender()
            self.commitData.emit(editor)
            self.closeEditor.emit(editor)
    
        def setEditorData(self, editor, index):
            if editor:
                editor.setText(index.model().data[0])
    
        def setModelData(self, editor, model, index):
            if editor:
                model.setData(index, editor.text(), Qt.EditRole)
    
    
    class Model(QAbstractTableModel):
    
        def __init__(self):
            super().__init__()
            self.data = ['hello']
    
        def rowCount(self, parent=None):
            return 1
    
        def columnCount(self, parent=None):
            return 1
    
        def data(self, index, role):
            if not index.isValid():
                return QVariant()
    
            if role in (Qt.DisplayRole, Qt.EditRole):
                return self.data[0]
    
            return QVariant()
    
        def setData(self, index, value, role):
            if role == Qt.EditRole:
                self.data[0] = value
    
                top_left = self.index(0, 0)
                bottom_right = self.index(
                    self.rowCount() + 1, self.columnCount())
                self.dataChanged.emit(top_left, bottom_right,
                                      [Qt.DisplayRole])
                return True
    
            return False
    
        def flags(self, index):
            return Qt.ItemIsEditable | super().flags(index)
    
    
    class MainWindow(QMainWindow):
    
        def __init__(self):
            super().__init__()
            self.model = Model()
            self.table = QTableView()
            self.table.setModel(self.model)
            delegate = MyDelegate(self.table)
            self.table.setItemDelegateForColumn(0, delegate)
    
        def initUI(self):
            self.show()
    
            self.setCentralWidget(self.table)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
    
        mw = MainWindow()
        mw.initUI()
    
        sys.exit(app.exec_())
    
    

    Best regards



  • Finally found a solution. I used a QTextEdit, it does not work with QLineEdit, I don't know why. I still have a small problem, I can't close the completion popup when I hit the return key, so I must hit return key, twice. Not a big problem.

    import sys
    
    from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, pyqtSignal
    from PyQt5.QtGui import QTextCursor, QTextOption
    from PyQt5.QtWidgets import (QAbstractItemDelegate, QApplication, QCompleter,
                                 QItemDelegate, QMainWindow, QTableView, QTextEdit)
    
    
    class MyLineEdit(QTextEdit):
    
        returnPressed = pyqtSignal()
    
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setAcceptRichText(False)
            self.setWordWrapMode(QTextOption.NoWrap)
            self.setUndoRedoEnabled(False)
            self.setTabChangesFocus(True)
            self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            self.completer = None
            self.textChanged.connect(self.textHasChanged)
    
        def setCompleter(self, completer):
            if completer:
                completer.setWidget(self)
                completer.setCompletionMode(QCompleter.PopupCompletion)
                completer.setCaseSensitivity(Qt.CaseInsensitive)
                completer.setModelSorting(
                    QCompleter.CaseSensitivelySortedModel)
                completer.setMaxVisibleItems(15)
                completer.activated.connect(self.insertCompletion)
                self.completer = completer
    
        def insertCompletion(self, completion):
            print('>>> insertCompletion')
            if self.completer and self.completer.widget() == self:
                self.completer.widget().setPlainText(completion)
                self.completer.widget().moveCursor(QTextCursor.EndOfLine)
                self.completer.widget().ensureCursorVisible()
    
        def focusInEvent(self, event):
            print('>>> focusInEvent')
            if self.completer:
                self.completer.setWidget(self)
            super().focusInEvent(event)
    
        def keyPressEvent(self, event):
            print('>>> keyPressEvent')
    
            if self.completer and self.completer.popup().isVisible():
    
                if event.key() in (Qt.Key_Return, Qt.Key_Enter,
                                   Qt.Key_Tab, Qt.Key_Backtab, Qt.Key_Escape):
                    event.ignore()
                    return
            else:
                if event.key() in(Qt.Key_Return, Qt.Key_Enter):
                    self.returnPressed.emit()
                    return
    
            super().keyPressEvent(event)
    
            if not self.toPlainText():
                self.completer.popup().hide()
                return
    
            self.completer.setCompletionPrefix(self.toPlainText())
            self.completer.popup().setCurrentIndex(
                self.completer.completionModel().index(0, 0))
            self.completer.complete()
    
        def textHasChanged(self):
            # remove new lines and strip left blank characters
            self.blockSignals(True)
            cursor = self.textCursor()
            self.setPlainText(' '.join(self.toPlainText().splitlines()).lstrip())
            self.setTextCursor(cursor)
            self.ensureCursorVisible()
            self.blockSignals(False)
    
    
    class MyDelegate(QItemDelegate):
    
        def __init__(self, parent):
            super().__init__(parent)
    
        def createEditor(self, parent, option, index):
            strings = ('tata', 'tada', 'tadam', 'tete', 'titi', 'toto', 'tutu')
            completer = QCompleter(strings)
            editor = MyLineEdit(parent)
            editor.setCompleter(completer)
            editor.returnPressed.connect(self.commitAndCloseEditor)
            return editor
    
        def commitAndCloseEditor(self):
            print('>>> commitAndCloseEditor')
            editor = self.sender()
            self.commitData.emit(editor)
            self.closeEditor.emit(editor)
    
        def setEditorData(self, editor, index):
            if editor:
                editor.setText(index.model().data[0])
                editor.selectAll()
    
        def setModelData(self, editor, model, index):
            if editor:
                model.setData(index, editor.toPlainText(), Qt.EditRole)
    
    
    class Model(QAbstractTableModel):
    
        def __init__(self):
            super().__init__()
            self.data = ['hello']
    
        def rowCount(self, parent=None):
            return 1
    
        def columnCount(self, parent=None):
            return 1
    
        def data(self, index, role):
            if not index.isValid():
                return QVariant()
    
            if role in (Qt.DisplayRole, Qt.EditRole):
                return self.data[0]
    
            return QVariant()
    
        def setData(self, index, value, role):
            if role == Qt.EditRole:
                self.data[0] = value
    
                top_left = self.index(0, 0)
                bottom_right = self.index(
                    self.rowCount() + 1, self.columnCount())
                self.dataChanged.emit(top_left, bottom_right,
                                      [Qt.DisplayRole])
                return True
    
            return False
    
        def flags(self, index):
            return Qt.ItemIsEditable | super().flags(index)
    
    
    class MainWindow(QMainWindow):
    
        def __init__(self):
            super().__init__()
            self.model = Model()
            self.table = QTableView()
            self.table.setModel(self.model)
            delegate = MyDelegate(self.table)
            self.table.setItemDelegateForColumn(0, delegate)
    
        def initUI(self):
            self.show()
    
            self.setCentralWidget(self.table)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
    
        mw = MainWindow()
        mw.initUI()
    
        sys.exit(app.exec_())
    

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.