Beispiel #1
0
class SQLiteCompleterText(QTextEdit):
    def __init__(self, parent=None):
        super(SQLiteCompleterText, self).__init__(parent)
        self.cp = QCompleter(words)
        self.cp.setWrapAround(False)
        self.cp.setWidget(self)
        self.cp.setCompletionMode(QCompleter.PopupCompletion)
        self.cp.activated.connect(self.insertCompletion)

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

    def textUnderCursor(self):
        tc = self.textCursor()
        tc.select(QTextCursor.WordUnderCursor)
        return tc.selectedText()

    def focusInEvent(self, e):
        self.cp.setWidget(self)
        super(SQLiteCompleterText, self).focusInEvent(e)

    def keyPressEvent(self, e):
        if self.cp.popup().isVisible():
            if e.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab):
                e.ignore()
                return

        is_shortcut = ((e.modifiers() & Qt.ControlModifier) != 0 and e.key() == Qt.Key_E & Qt.ControlModifier)
        if not is_shortcut:
            super(SQLiteCompleterText, self).keyPressEvent(e)

        ctrl_or_shift = e.modifiers() & (Qt.ControlModifier | Qt.ShiftModifier)
        if ctrl_or_shift and len(e.text()) == 0:
            return

        eo = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="
        has_modifier = (e.modifiers() != Qt.NoModifier) and not ctrl_or_shift
        cp = self.textUnderCursor()

        if not is_shortcut and (cp in words or has_modifier or len(e.text()) == 0 or len(cp) < 1 or e.text()[-1] in eo):
            self.cp.popup().hide()
            return

        if cp != self.cp.completionPrefix():
            self.cp.setCompletionPrefix(cp)
            self.cp.popup().setCurrentIndex(self.cp.completionModel().index(0, 0))

        cr = self.cursorRect()
        cr.setWidth(self.cp.popup().sizeHintForColumn(0) + self.cp.popup().verticalScrollBar().sizeHint().width())
        self.cp.complete(cr)
Beispiel #2
0
class CompletingTextEdit(QTextEdit):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.completerSequence = QKeySequence("Ctrl+Space")

        self._completer = QCompleter()
        self._completer.setWidget(self)
        self._completer.activated.connect(self.insertCompletion)

    def insertCompletion(self, completion):
        if self._completer.widget() is not self:
            return

        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)

    def textUnderCursor(self):
        tc = self.textCursor()
        tc.select(QTextCursor.WordUnderCursor)

        return tc.selectedText()

    def loadFromFile(self, fileName):
        f = QFile(fileName)
        if not f.open(QFile.ReadOnly):
            model = QStringListModel()
            self._completer.setModel(model)

        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))

        words = []
        while not f.atEnd():
            line = f.readLine().trimmed()
            if line.length() != 0:
                try:
                    line = str(line, encoding='utf-8')
                except TypeError:
                    line = str(line)

                words.append(line)

        QApplication.restoreOverrideCursor()

        model = QStringListModel(words)
        self._completer.setModel(model)

    def loadFromList(self, words):
        model = QStringListModel(words)
        self._completer.setModel(model)

    def keyPressEvent(self, e):
        if self._completer.popup().isVisible():
            # The following keys are forwarded by the completer to the widget.
            if e.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape,
                           Qt.Key_Tab, Qt.Key_Backtab):
                e.ignore()
                # Let the completer do default behavior.
                return

        newSeq = QKeySequence(e.modifiers() | e.key())
        isShortcut = newSeq == self.completerSequence

        if not isShortcut:
            # Do not process the shortcut when we have a completer.
            super().keyPressEvent(e)
            return

        ctrlOrShift = e.modifiers() & (Qt.ControlModifier | Qt.ShiftModifier)
        if ctrlOrShift and not e.text():
            return

        eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="
        hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift
        completionPrefix = self.textUnderCursor()

        if not isShortcut and (hasModifier or not e.text()
                               or len(completionPrefix) < 3
                               or e.text()[-1] in eow):
            self._completer.popup().hide()
            return

        if completionPrefix != self._completer.completionPrefix():
            self._completer.setCompletionPrefix(completionPrefix)
            self._completer.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)
Beispiel #3
0
class TextStatusEditComplete(TextStatusEdit):
    """ Adds Completion functions to the base class

    This class extends 'TextStatusEdit' by:

    1.  providing a QCompleter to validate lines for the
        'fixupText' and 'lineChanged' signals
    2.  providing a popup for suggested completions as
        the user is typing
    3.  auto-completing the line when the user selects
        a suggestion.

    The task of auto completion and providing suggestions
    is provided directly by this class.

    The task validating and cleaning up text is provided by
    the PluginFinder.
    """
    def __init__(self, parent: QWidget = None):
        super().__init__(parent)
        self._dataModel = None
        self._monitorDbChanges = False
        self._enableAutoCompletion = False
        self._completedAndSelected = False
        self._completer = QCompleter(self)

        self._completer.setWidget(self)
        self._completer.setWrapAround(False)
        self._completer.setCompletionMode(QCompleter.PopupCompletion)
        self._completer.setCaseSensitivity(Qt.CaseInsensitive)
        self._completer.setFilterMode(Qt.MatchStartsWith)
        self._completer.setModelSorting(
            QCompleter.CaseInsensitivelySortedModel)
        self._completer.activated.connect(self.replaceLine)

        self._pluginFinder = PluginFinder(self._completer, self)
        self.fixupText.connect(self._pluginFinder.fixupText)
        self.lineChanged.connect(self._pluginFinder.setRowForLine)

        QShortcut(Qt.CTRL + Qt.Key_E, self, self.toggleAutoCompletion)
        QShortcut(Qt.CTRL + Qt.Key_T, self, self.suggestCompletions)

    # --- Methods related to the completer's underlying data model

    def setModel(self, model: QAbstractItemModel):
        self._completer.setModel(model)

    def _updateModelSignals(self):
        """ We do not need to check for column changes due to
        the way our PluginModel is structured. """

        if self._dataModel is not None:
            self._dataModel.rowsMoved.disconnect(self.resetData)
            self._dataModel.rowsInserted.disconnect(self.resetData)
            self._dataModel.rowsRemoved.disconnect(self.resetData)
            self._dataModel.modelReset.disconnect(self.resetData)
            self._dataModel.dataChanged.disconnect(self.resetData)
            self._dataModel.layoutChanged.disconnect(self.resetData)

        if self._monitorDbChanges:
            self._dataModel = self._completer.model()
            if self._dataModel is not None:
                self._dataModel.rowsMoved.connect(self.resetData)
                self._dataModel.rowsInserted.connect(self.resetData)
                self._dataModel.rowsRemoved.connect(self.resetData)
                self._dataModel.modelReset.connect(self.resetData)
                self._dataModel.dataChanged.connect(self.resetData)
                self._dataModel.layoutChanged.connect(self.resetData)
        else:
            self._dataModel = None

    def monitorDbChanges(self, enable: bool):
        """ Enable invalidating line status when
        the data model changes.

        Depending on the underlying data model, it may
        be unnecessary to monitor these changes, or, a
        higher level class can monitor specific signals
        more efficiently.  So, this is not enabled
        by default.  """

        if self._monitorDbChanges == enable:
            return

        self._monitorDbChanges = enable
        if enable:
            self._dataModel = self._completer.model()
            self._completer.completionModel().sourceModelChanged.connect(
                self._updateModelSignals)
        else:
            self._completer.completionModel().sourceModelChanged.disconnect(
                self._updateModelSignals)
        self._updateModelSignals()

    # ---- Methods related to line completion

    def completer(self):
        return self._completer

    def enableAutoCompletion(self, enable: bool):
        self._enableAutoCompletion = enable

    def toggleAutoCompletion(self):
        self.enableAutoCompletion(not self._enableAutoCompletion)

    def _textUnderCursor(self):
        tc = self.textCursor()
        if tc.positionInBlock() == 0 and len(tc.block().text()) > 1:
            tc.movePosition(QTextCursor.NextCharacter)
        tc.movePosition(QTextCursor.StartOfLine, QTextCursor.KeepAnchor)
        return tc.selectedText().lstrip()

    def suggestCompletions(self):
        if self.isLineInvalid(self.textCursor().blockNumber()):
            self._suggestCompletionsForText(self._textUnderCursor())

    def _suggestCompletionsForText(self, prefix: str):
        if not prefix:
            return
        if prefix != self._completer.completionPrefix():
            self._completer.setCompletionPrefix(prefix)
            self._completer.popup().setCurrentIndex(
                self._completer.completionModel().index(0, 0))
        if self._completer.completionCount() == 1:
            self._insertSuggestion(self._completer.currentCompletion())
        else:
            rect = self.cursorRect()
            rect.moveRight(self.statusAreaWidth())
            rect.setWidth(
                self._completer.popup().sizeHintForColumn(
                    self._completer.completionColumn()) +
                self._completer.popup().verticalScrollBar().sizeHint().width())
            self._completer.complete(rect)

    def _insertSuggestion(self, text: str):
        """ Only one suggestion matched, prefill line """

        cursor = self.textCursor()
        # handle when cursor is in middle of line
        if not cursor.atBlockEnd():
            cursor.beginEditBlock()
            cursor.select(QTextCursor.LineUnderCursor)
            cursor.removeSelectedText()
            cursor.insertText(text)
            cursor.movePosition(QTextCursor.StartOfLine)
            cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor)
            self._completedAndSelected = True
            self.setTextCursor(cursor)
            cursor.endEditBlock()
            return

        # handle when cursor at end of line
        cursor.beginEditBlock()
        numCharsToComplete = len(text) - len(
            self._completer.completionPrefix())
        insertionPosition = cursor.position()
        cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor)
        cursor.removeSelectedText()
        cursor.insertText(text[-numCharsToComplete:])
        cursor.setPosition(insertionPosition)
        cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor)
        self._completedAndSelected = True
        self.setTextCursor(cursor)
        cursor.endEditBlock()

    def keyPressEvent(self, event: QKeyEvent):
        if self._completedAndSelected and self.handledCompletedAndSelected(
                event):
            return

        self._completedAndSelected = False
        if self._completer.popup().isVisible():
            ignoredKeys = [
                Qt.Key_Up,
                Qt.Key_Down,
                Qt.Key_Enter,
                Qt.Key_Return,
                Qt.Key_Tab,
                Qt.Key_Escape,
            ]
            if event.key() in ignoredKeys:
                event.ignore()
                return
            self._completer.popup().hide()

        super().keyPressEvent(event)
        if not self._enableAutoCompletion:
            return

        ctrlOrShift = (event.modifiers() & Qt.ShiftModifier == Qt.ShiftModifier
                       or event.modifiers() & Qt.ControlModifier
                       == Qt.ControlModifier)

        if ctrlOrShift and not event.text():
            return

        if self.textCursor().atBlockEnd():
            self.suggestCompletions()

    def mousePressEvent(self, event: QMouseEvent):
        if self._completedAndSelected:
            self._completedAndSelected = False
            self.document().undo()
        super().mousePressEvent(event)

    def handledCompletedAndSelected(self, event: QKeyEvent):
        """ The line is prefilled when only one completion matches. The user
        can accept the suggestion by pressing 'Enter'. The user can reject
        the suggestion by pressing 'Esc' or by continuing to type. """

        self._completedAndSelected = False
        cursor = self.textCursor()
        acceptKeys = [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab]
        if event.key() in acceptKeys:
            self.replaceLine(self._completer.currentCompletion())
        elif event.key() == Qt.Key_Escape:
            self.document().undo()
        else:
            self.document().undo()
            return False

        self.setTextCursor(cursor)
        event.accept()
        return True

    def replaceLine(self, text: str):
        cursor = self.textCursor()
        cursor.beginEditBlock()
        cursor.select(QTextCursor.LineUnderCursor)
        cursor.removeSelectedText()
        cursor.insertText(text)
        cursor.movePosition(QTextCursor.EndOfLine)
        self.setTextCursor(cursor)
        cursor.endEditBlock()

    # ---- Methods related to Context Menu

    def createStandardContextMenu(self, pos: QPoint):
        menu = super().createStandardContextMenu(pos)
        menu.addSeparator()
        autoCompletionAction = menu.addAction(
            QIcon(),
            self.tr("Enable Auto Complete"),
            self.toggleAutoCompletion,
            QKeySequence(Qt.CTRL + Qt.Key_E),
        )
        autoCompletionAction.setCheckable(True)
        autoCompletionAction.setChecked(self._enableAutoCompletion)

        completionAction = menu.addAction(
            QIcon(),
            self.tr("Suggest Completions"),
            self.suggestCompletions,
            QKeySequence(Qt.CTRL + Qt.Key_T),
        )
        completionAction.setEnabled(
            self.isLineInvalid(self.textCursor().blockNumber()))
        return menu
Beispiel #4
0
class TextEditTechnique(QTextEdit):
    def __init__(self, text_file, input_trigger=None, active=True):
        super(TextEditTechnique, self).__init__()
        self._completer = None
        self.isActive = active
        word_list = self.get_suggestion_words(text_file)
        self.input_trigger = input_trigger
        # connects the text textChanged event
        self.textChanged.connect(self.handle_input)
        self.char_length = 1
        self.set_completer(word_list)
        self.technique_used = False
        self.no_suggestion_input = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-= "

    # gets words for suggestion
    def get_suggestion_words(self, text_file):
        words = []
        file = open(text_file)
        for line in file:
            for word in line.split(' '):
                word = word.strip().lower().replace('.', '').replace(
                    ',', '').replace(':', '').replace('?', '')
                if word and word not in words:
                    words.append(word)
        file.close()
        return words

    # initializes the completer
    def set_completer(self, words):
        if self._completer is not None:
            self._completer.activated.disconnect()
        self._completer = QCompleter(words, self)
        self._completer.setCaseSensitivity(Qt.CaseInsensitive)
        self._completer.setWrapAround(False)
        self._completer.setWidget(self)
        self._completer.setCompletionMode(QCompleter.PopupCompletion)
        self._completer.activated.connect(self.insert_sel_suggestion)

    # insert the selected suggestion and moves the text cursor
    def insert_sel_suggestion(self, suggestion):
        if self._completer.widget() is not self:
            return
        text_cursor = self.textCursor()
        extra = len(suggestion) - len(self._completer.completionPrefix())
        text_cursor.movePosition(QTextCursor.Left)
        text_cursor.movePosition(QTextCursor.EndOfWord)
        text_cursor.insertText(suggestion[-extra:])
        self.setTextCursor(text_cursor)

    # returns the current typed word
    def text_under_cursor(self):
        text_cursor = self.textCursor()
        text_cursor.select(QTextCursor.WordUnderCursor)
        return text_cursor.selectedText()

    # sets the widget
    def focusInEvent(self, e):
        if self._completer is not None and self.isActive:
            self._completer.setWidget(self)
        super(TextEditTechnique, self).focusInEvent(e)

    def keyPressEvent(self, e):

        # behaves like a normal input field!
        if not self.isActive or self._completer is None:
            super(TextEditTechnique, self).keyPressEvent(e)
            return

        # ignore events funktion keys on Textedit if popup is visible --> popup behaves default
        if self._completer.popup().isVisible():
            if e.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape,
                           Qt.Key_Tab, Qt.Key_Backtab):
                e.ignore()
                self.technique_used = True
                return
        # writes text in the editfield
        super(TextEditTechnique, self).keyPressEvent(e)

        ctrl_or_shift = e.modifiers() & (Qt.ControlModifier | Qt.ShiftModifier)
        if self._completer is None or (ctrl_or_shift and len(e.text()) == 0):
            return

        has_modifier = (e.modifiers() != Qt.NoModifier) and not ctrl_or_shift
        completion_prefix = self.text_under_cursor()
        # hide the popup for no chars, modifiers, shift and no text
        if (has_modifier or len(e.text()) == 0
                or len(completion_prefix) < self.char_length
                or e.text()[-1] in self.no_suggestion_input):
            self._completer.popup().hide()
            return

        # shows the popup with the suggested words
        if completion_prefix != self._completer.completionPrefix():
            self._completer.setCompletionPrefix(completion_prefix)
            self._completer.popup().setCurrentIndex(
                self._completer.completionModel().index(0, 0))

        cursor = self.cursorRect()
        cursor.setWidth(
            self._completer.popup().sizeHintForColumn(0) +
            self._completer.popup().verticalScrollBar().sizeHint().width())
        self._completer.complete(cursor)

    # event called if textedit input has changed
    def handle_input(self):
        timestamp = time.time()
        self.input_trigger.emit(self.toPlainText(), timestamp)

    # clears the text input
    def clear_input(self):
        self.setHtml('')

    # deactivates the completer
    def deactivate_completer(self):
        self.isActive = False

    # activates the completer
    def activate_completer(self):
        self.isActive = True