PyQt5 QTextEdit auto completion
Asked Answered
D

2

7

Looking for a way to have an auto completion with a QTextEdit and QCompleter. I have read that it is possible but didn't find any example... I'm using python3.4 and PyQt5

I'm looking for a very basic example thanks for any help

Delete answered 10/3, 2015 at 5:23 Comment(0)
G
5

an example here...that i've worked on... although it is in python3.3 and pyqt4. I guess it should not make much of a difference..
you will have to change from PyQt4 to from PyQt5

shortcut keys are Ctrl+Space to show suggestions and Ctrl+E to autocomplete the first avialable suggestion

mMyTextEdit.py

from PyQt4 import QtGui,QtCore
from mMyDictionaryCompleter import MyDictionaryCompleter
#===============================================================================
# MyTextEdit  
#===============================================================================
class MyTextEdit(QtGui.QTextEdit):
#|-----------------------------------------------------------------------------|
# class Variables
#|-----------------------------------------------------------------------------| 
    #no classVariables
#     myFocusOutSignal=QtCore.pyqtSignal(str)
#|-----------------------------------------------------------------------------|
# Constructor  
#|-----------------------------------------------------------------------------|
    def __init__(self,*args):
        #*args to set parent
        QtGui.QLineEdit.__init__(self,*args)
        font=QtGui.QFont()
        font.setPointSize(12)
        self.setFont(font)
        self.completer = None

#|--------------------------End of __init__------------------------------------|
#|-----------------------------------------------------------------------------| 
# setCompleter
#|-----------------------------------------------------------------------------|
    def setCompleter(self, completer):
        if self.completer:
            self.disconnect(self.completer, 0, self, 0)
        if not completer:
            return

        completer.setWidget(self)
        completer.setCompletionMode(QtGui.QCompleter.PopupCompletion)
        completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.completer = completer
#        self.connect(self.completer,
#            QtCore.SIGNAL("activated(const QString&)"), self.insertCompletion)
        self.completer.insertText.connect(self.insertCompletion)
#|-----------------------End of setCompleter-------------------------------------|
#|-----------------------------------------------------------------------------| 
# insertCompletion
#|-----------------------------------------------------------------------------|
    def insertCompletion(self, completion):
        tc = self.textCursor()
        extra = (len(completion) -
            len(self.completer.completionPrefix()))
        tc.movePosition(QtGui.QTextCursor.Left)
        tc.movePosition(QtGui.QTextCursor.EndOfWord)
        tc.insertText(completion[-extra:])
        self.setTextCursor(tc)
#|-----------------------End of insertCompletion-------------------------------|
#|-----------------------------------------------------------------------------| 
# textUnderCursor
#|-----------------------------------------------------------------------------|
    def textUnderCursor(self):
        tc = self.textCursor()
        tc.select(QtGui.QTextCursor.WordUnderCursor)
        return tc.selectedText()
#|-----------------------End of textUnderCursor--------------------------------|
#|-----------------------------------------------------------------------------| 
# focusInEvent
#|-----------------------------------------------------------------------------|
    #---override
    def focusInEvent(self, event):
        if self.completer:
            self.completer.setWidget(self);
        QtGui.QTextEdit.focusInEvent(self, event)
#|-----------------------End of focusInEvent-------------------------------------|
#|-----------------------------------------------------------------------------| 
# keyPressEvent
#|-----------------------------------------------------------------------------|
    #---override
    def keyPressEvent(self, event):
        if self.completer and self.completer.popup() and self.completer.popup().isVisible():
            if event.key() in (
            QtCore.Qt.Key_Enter,
            QtCore.Qt.Key_Return,
            QtCore.Qt.Key_Escape,
            QtCore.Qt.Key_Tab,
            QtCore.Qt.Key_Backtab):
                event.ignore()
                return
        ## has ctrl-Space been pressed??
        isShortcut = (event.modifiers() == QtCore.Qt.ControlModifier and\
                      event.key() == QtCore.Qt.Key_Space)
        ## modifier to complete suggestion inline ctrl-e
        inline = (event.modifiers() == QtCore.Qt.ControlModifier and \
                  event.key() == QtCore.Qt.Key_E)
        ## if inline completion has been chosen
        if inline:
            # set completion mode as inline
            self.completer.setCompletionMode(QtGui.QCompleter.InlineCompletion)
            completionPrefix = self.textUnderCursor()
            if (completionPrefix != self.completer.completionPrefix()):
                self.completer.setCompletionPrefix(completionPrefix)
            self.completer.complete()
#            self.completer.setCurrentRow(0)
#            self.completer.activated.emit(self.completer.currentCompletion())
            # set the current suggestion in the text box
            self.completer.insertText.emit(self.completer.currentCompletion())
            # reset the completion mode
            self.completer.setCompletionMode(QtGui.QCompleter.PopupCompletion)
            return
        if (not self.completer or not isShortcut):
            pass
            QtGui.QTextEdit.keyPressEvent(self, event)
        # debug
#        print("After controlspace")
#        print("isShortcut is: {}".format(isShortcut))
        # debug over
        ## ctrl or shift key on it's own??
        ctrlOrShift = event.modifiers() in (QtCore.Qt.ControlModifier ,\
                QtCore.Qt.ShiftModifier)
        if ctrlOrShift and event.text()== '':
#             ctrl or shift key on it's own
            return
        # debug
#        print("After on its own")
#        print("isShortcut is: {}".format(isShortcut))
        # debug over
#         eow = QtCore.QString("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=") #end of word
#        eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=" #end of word
        eow = "~!@#$%^&*+{}|:\"<>?,./;'[]\\-=" #end of word

        hasModifier = ((event.modifiers() != QtCore.Qt.NoModifier) and\
                        not ctrlOrShift)

        completionPrefix = self.textUnderCursor()
#         print('event . text = {}'.format(event.text().right(1)))
#         if (not isShortcut and (hasModifier or event.text()=='' or\
#                                 len(completionPrefix) < 3 or \
#                                 eow.contains(event.text().right(1)))):
        if not isShortcut :
            if self.completer.popup():
                self.completer.popup().hide()
            return
#        print("complPref: {}".format(completionPrefix))
#        print("completer.complPref: {}".format(self.completer.completionPrefix()))
#        print("mode: {}".format(self.completer.completionMode()))
#        if (completionPrefix != self.completer.completionPrefix()):
        self.completer.setCompletionPrefix(completionPrefix)
        popup = self.completer.popup()
        popup.setCurrentIndex(
            self.completer.completionModel().index(0,0))
        cr = self.cursorRect()
        cr.setWidth(self.completer.popup().sizeHintForColumn(0)
            + self.completer.popup().verticalScrollBar().sizeHint().width())
        self.completer.complete(cr) ## popup it up!
#|-----------------------End of keyPressEvent----------------------------------|

if __name__ == "__main__":

    app = QtGui.QApplication([])
    completer = MyDictionaryCompleter()
    te = MyTextEdit()
    te.setCompleter(completer)
    te.show()
    app.exec_()

mMyDictionaryCompleter.py

#===============================================================================
# MyDictionaryCompleter
#===============================================================================
from PyQt4 import QtGui, QtCore
class MyDictionaryCompleter(QtGui.QCompleter):
#|-----------------------------------------------------------------------------|
# class Variables
#|-----------------------------------------------------------------------------| 
    insertText = QtCore.pyqtSignal(str)
    #no classVariables
#|-----------------------------------------------------------------------------|
# Constructor  
#|-----------------------------------------------------------------------------|
    def __init__(self, myKeywords=None,parent=None):


        myKeywords =['apple','aggresive','ball','bat','cat','cycle','dog','dumb',\
                     'elephant','engineer','food','file','good','great',\
                     'hippopotamus','hyper','india','ireland','just','just',\
                     'key','kid','lemon','lead','mute','magic',\
                     'news','newyork','orange','oval','parrot','patriot',\
                     'question','queue','right','rest','smile','simple',\
                     'tree','urban','very','wood','xylophone','yellow',\
                     'zebra']
        QtGui.QCompleter.__init__(self, myKeywords, parent)
        self.connect(self,
            QtCore.SIGNAL("activated(const QString&)"), self.changeCompletion)
#|--------------------------End of Constructor---------------------------------| 
#|-----------------------------------------------------------------------------| 
# changeCompletion
#|-----------------------------------------------------------------------------|
    def changeCompletion(self, completion):
        if completion.find("(") != -1:
            completion = completion[:completion.find("(")]
        print(completion)
        self.insertText.emit(completion)
#|-----------------------End of changeCompletion-------------------------------|

EDIT

attached screenshots.
Character2nd Suggestion Selected2nd Suggestion Completed

Gatecrasher answered 11/3, 2015 at 8:24 Comment(8)
thank you for the reply, it's very helpful could you explain me how is the connection between the completer and the textEdit worksDelete
you might want to read this to know how it works.Gatecrasher
sorry but I still don't understand the purpose of the changeCompletion in the QCompleter class. I have now something that can display an popup of possible completion, my problems is that it's always insert the first item in the popup even though I select another one below. so where can I get the selected item in the popup?Delete
attached the screen-shots for the 2nd item in completion. It works just Fine. I'm not sure what you're referring to. And changeCompletion is the slot that finally inserts the selected word in MyTextEditGatecrasher
after some reading, QCompleter in PyQt5 does not have the connect methodDelete
@Gatecrasher How can I modify this to also include the definition in the suggestion?Misericord
@Misericord you can include that in myKeywords under MyDictionaryCompleterGatecrasher
@Gatecrasher When I click the suggestion, I only want the word to print, not the word with the definition. Won't putting everything in myKeywords also print the definition?Misericord
D
3

If anyone interested here is an "incomplete" solution. Here is what I did. I have change to PlainTextEdit because there was no significant advantages to use QTextEdit

Editor

from PyQt5.QtWidgets import QCompleter, QPlainTextEdit
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QTextCursor
import MyCompleter

class AwesomeTextEdit(QPlainTextEdit):
    def __init__(self, parent=None):
        super(AwesomeTextEdit, self).__init__(parent)

        self.completer = MyCompleterparent()
        self.completer.setWidget(self)
        self.completer.insertText.connect(self.insertCompletion)

    def insertCompletion(self, completion):
        tc = self.textCursor()
        extra = (len(completion) - len(self.completer.completionPrefix()))
        tc.movePosition(QTextCursor.Left)
        tc.movePosition(QTextCursor.EndOfWord)
        tc.insertText(completion[-extra:])
        self.setTextCursor(tc)
        self.completer.popup().hide()

    def focusInEvent(self, event):
        if self.completer:
            self.completer.setWidget(self)
        QPlainTextEdit.focusInEvent(self, event)

    def keyPressEvent(self, event):
    
        tc = self.textCursor()
        if event.key() == Qt.Key_Tab and self.completer.popup().isVisible():
            self.completer.insertText.emit(self.completer.getSelected())
            self.completer.setCompletionMode(QCompleter.PopupCompletion)
            return

        QPlainTextEdit.keyPressEvent(self, event)
        tc.select(QTextCursor.WordUnderCursor)
        cr = self.cursorRect()

        if len(tc.selectedText()) > 0:
            self.completer.setCompletionPrefix(tc.selectedText())
            popup = self.completer.popup()
            popup.setCurrentIndex(self.completer.completionModel().index(0,0))

            cr.setWidth(self.completer.popup().sizeHintForColumn(0) 
            + self.completer.popup().verticalScrollBar().sizeHint().width())
            self.completer.complete(cr)
        else:
            self.completer.popup().hide()

Completer

from PyQt5.QtWidgets import QCompleter
from PyQt5 import QtCore

class MyCompleter(QCompleter):
    insertText = QtCore.pyqtSignal(str)

    def __init__(self, parent=None):
        QCompleter.__init__(self, ["test","foo","bar"], parent)
        self.setCompletionMode(QCompleter.PopupCompletion)
        self.highlighted.connect(self.setHighlighted)

    def setHighlighted(self, text):
        self.lastSelected = text

    def getSelected(self):
        return self.lastSelected
Delete answered 26/3, 2015 at 0:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.