Completion problem with QLineEdit and QItemDelegate
Solved
General and Desktop
-
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_())