class Completer(QGraphicsProxyWidget, object):
    ''' Class for handling text autocompletion in the SDL scene '''
    def __init__(self, parent):
        ''' Create an autocompletion list popup '''
        widget = QListWidget()
        super(Completer, self).__init__(parent)
        self.setWidget(widget)
        self.string_list = QStringListModel()
        self._completer = QCompleter()
        self.parent = parent
        self._completer.setCaseSensitivity(Qt.CaseInsensitive)
        # For some reason the default minimum size is (61,61)
        # Set it to 0 so that the size of the box is not taken
        # into account when it is hidden.
        self.setMinimumSize(0, 0)
        self.prepareGeometryChange()
        self.resize(0, 0)
        self.hide()

    def set_completer_list(self):
        ''' Set list of items for the autocompleter popup '''
        compl = [item.replace('-', '_') for item in
                 self.parent.parentItem().completion_list]
        self.string_list.setStringList(compl)
        self._completer.setModel(self.string_list)

    def set_completion_prefix(self, completion_prefix):
        '''
            Set the current completion prefix (user-entered text)
            and set the corresponding list of words in the popup widget
        '''
        self._completer.setCompletionPrefix(completion_prefix)
        self.widget().clear()
        count = self._completer.completionCount()
        for i in xrange(count):
            self._completer.setCurrentRow(i)
            self.widget().addItem(self._completer.currentCompletion())
        self.prepareGeometryChange()
        if count:
            self.resize(self.widget().sizeHintForColumn(0) + 40, 70)
        else:
            self.resize(0, 0)
        return count

    # pylint: disable=C0103
    def keyPressEvent(self, e):
        super(Completer, self).keyPressEvent(e)
        if e.key() == Qt.Key_Escape:
            self.parentItem().setFocus()
        # Consume the event so that it is not repeated at EditableText level
        e.accept()

    # pylint: disable=C0103
    def focusOutEvent(self, event):
        ''' When the user leaves the popup, return focus to parent '''
        super(Completer, self).focusOutEvent(event)
        self.hide()
        self.resize(0, 0)
        self.parentItem().setFocus()
class Completer(QGraphicsProxyWidget, object):
    ''' Class for handling text autocompletion in the SDL scene '''
    def __init__(self, parent):
        ''' Create an autocompletion list popup '''
        widget = QListWidget()
        super(Completer, self).__init__(parent)
        self.setWidget(widget)
        self.string_list = QStringListModel()
        self._completer = QCompleter()
        self.parent = parent
        self._completer.setCaseSensitivity(Qt.CaseInsensitive)
        # For some reason the default minimum size is (61,61)
        # Set it to 0 so that the size of the box is not taken
        # into account when it is hidden.
        self.setMinimumSize(0, 0)
        self.prepareGeometryChange()
        self.resize(0, 0)
        self.hide()

    def set_completer_list(self):
        ''' Set list of items for the autocompleter popup '''
        compl = list(self.parent.parentItem().completion_list)
        self.string_list.setStringList(compl)
        self._completer.setModel(self.string_list)

    def set_completion_prefix(self, completion_prefix):
        '''
            Set the current completion prefix (user-entered text)
            and set the corresponding list of words in the popup widget
        '''
        self._completer.setCompletionPrefix(completion_prefix)
        self.widget().clear()
        count = self._completer.completionCount()
        for i in xrange(count):
            self._completer.setCurrentRow(i)
            self.widget().addItem(self._completer.currentCompletion())
        self.prepareGeometryChange()
        if count:
            self.resize(self.widget().sizeHintForColumn(0) + 40, 70)
        else:
            self.resize(0, 0)
        return count

    # pylint: disable=C0103
    def keyPressEvent(self, e):
        super(Completer, self).keyPressEvent(e)
        if e.key() == Qt.Key_Escape:
            self.parentItem().setFocus()
        # Consume the event so that it is not repeated at EditableText level
        e.accept()

    # pylint: disable=C0103
    def focusOutEvent(self, event):
        ''' When the user leaves the popup, return focus to parent '''
        super(Completer, self).focusOutEvent(event)
        self.hide()
        self.resize(0, 0)
        self.parentItem().setFocus()
Exemple #3
0
class CommandBox(QtGui.QPlainTextEdit, object):
    newCommand = QtCore.Signal(str)

    def reset_history(self):
        self.history_index = len(self.history)

    def __init__(self, history, commands, prefix):
        self.prefix = prefix
        self.history_index = 0
        self.history = history
        self.reset_history()
        super(CommandBox, self).__init__()

        # Autocompleter
        self.completer = QCompleter([prefix + name for name in commands], self)
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer.setWidget(self)
        self.completer.activated.connect(self.onAutoComplete)
        self.autocompleteStart = None

    def onAutoComplete(self, text):
        # Select the text from autocompleteStart until the current cursor
        cursor = self.textCursor()
        cursor.setPosition(0, cursor.KeepAnchor)
        # Replace it with the selected text
        cursor.insertText(text)
        self.autocompleteStart = None

    # noinspection PyStringFormat
    def keyPressEvent(self, *args, **kwargs):
        event = args[0]
        key = event.key()
        ctrl = event.modifiers() == QtCore.Qt.ControlModifier

        # don't disturb the completer behavior
        if self.completer.popup().isVisible() and key in (
                Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab, Qt.Key_Backtab):
            event.ignore()
            return

        if self.autocompleteStart is not None and not event.text().isalnum() and \
                not (key == Qt.Key_Backspace and self.textCursor().position() > self.autocompleteStart):
            self.completer.popup().hide()
            self.autocompleteStart = None

        if key == Qt.Key_Space and ctrl:
            # Pop-up the autocompleteList
            rect = self.cursorRect(self.textCursor())
            rect.setSize(QtCore.QSize(100, 150))
            self.autocompleteStart = self.textCursor().position()
            self.completer.complete(
                rect)  # The popup is positioned in the next if block

        if self.autocompleteStart:
            prefix = self.toPlainText()
            cur = self.textCursor()
            cur.setPosition(self.autocompleteStart)

            self.completer.setCompletionPrefix(prefix)
            # Select the first one of the matches
            self.completer.popup().setCurrentIndex(
                self.completer.completionModel().index(0, 0))

        if key == Qt.Key_Up and ctrl:
            if self.history_index > 0:
                self.history_index -= 1
                self.setPlainText(BOT_PREFIX +
                                  '%s %s' % self.history[self.history_index])
                key.ignore()
                return
        elif key == Qt.Key_Down and ctrl:
            if self.history_index < len(self.history) - 1:
                self.history_index += 1
                self.setPlainText(BOT_PREFIX +
                                  '%s %s' % self.history[self.history_index])
                key.ignore()
                return
        elif key == QtCore.Qt.Key_Return and ctrl:
            self.newCommand.emit(self.toPlainText())
            self.reset_history()
        super(CommandBox, self).keyPressEvent(*args, **kwargs)
Exemple #4
0
class CommandBox(QtGui.QPlainTextEdit, object):
    newCommand = QtCore.Signal(str)

    def reset_history(self):
        self.history_index = len(self.history)

    def __init__(self, history, commands):
        self.history_index = 0
        self.history = history
        self.reset_history()
        super(CommandBox, self).__init__()

        #Autocompleter
        self.completer = QCompleter([BOT_PREFIX + name for name in commands], self)
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer.setWidget(self)
        self.completer.activated.connect(self.onAutoComplete)
        self.autocompleteStart = None

    def onAutoComplete(self, text):
        #Select the text from autocompleteStart until the current cursor
        cursor = self.textCursor()
        cursor.setPosition(0, cursor.KeepAnchor)
        #Replace it with the selected text
        cursor.insertText(text)
        self.autocompleteStart = None

    #noinspection PyStringFormat
    def keyPressEvent(self, *args, **kwargs):
        event = args[0]
        key = event.key()
        ctrl = event.modifiers() == QtCore.Qt.ControlModifier

        # don't disturb the completer behavior
        if self.completer.popup().isVisible() and key in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab, Qt.Key_Backtab):
            event.ignore()
            return

        if self.autocompleteStart is not None and not event.text().isalnum() and \
                not (key == Qt.Key_Backspace and self.textCursor().position() > self.autocompleteStart):
            self.completer.popup().hide()
            self.autocompleteStart = None

        if key == Qt.Key_Space and ctrl:
            #Pop-up the autocompleteList
            rect = self.cursorRect(self.textCursor())
            rect.setSize(QtCore.QSize(100, 150))
            self.autocompleteStart = self.textCursor().position()
            self.completer.complete(rect)  # The popup is positioned in the next if block

        if self.autocompleteStart:
            prefix = self.toPlainText()
            cur = self.textCursor()
            cur.setPosition(self.autocompleteStart)

            self.completer.setCompletionPrefix(prefix)
            #Select the first one of the matches
            self.completer.popup().setCurrentIndex(self.completer.completionModel().index(0, 0))

        if key == Qt.Key_Up and ctrl:
            if self.history_index > 0:
                self.history_index -= 1
                self.setPlainText(BOT_PREFIX + '%s %s' % self.history[self.history_index])
                key.ignore()
                return
        elif key == Qt.Key_Down and ctrl:
            if self.history_index < len(self.history) - 1:
                self.history_index += 1
                self.setPlainText(BOT_PREFIX + '%s %s' % self.history[self.history_index])
                key.ignore()
                return
        elif key == QtCore.Qt.Key_Return and ctrl:
            self.newCommand.emit(self.toPlainText())
            self.reset_history()
        super(CommandBox, self).keyPressEvent(*args, **kwargs)
Exemple #5
0
class CodeCompletionMode(Mode):
    """
    This mode provides code completion to the CodeEdit widget.

    The list of suggestion is supplied by a CodeCompletionModel.

    Code completion may use more than one completion model. The suggestions
    list is then filled model per model by beginning by the highest priority as
    long as the number of suggestions is lower than
    :attr:`pcef.modes.code_completion.CodeCompletion.minSuggestions`.

    For example, a python editor will use a smart completion model with a high
    priority and use the DocumentWordsCompletion model as a fallback system
    when the smart model fails to provide enough suggestions.

    The mode uses a QCompleter to provides display the list of suggestions.

    Code completion is triggered using ctrl+space or when there is at least
    three characters in the word being typed.
    """
    #: Mode identifier
    IDENTIFIER = "Code completion"

    #: Mode description
    DESCRIPTION = "Provides code completion though completion models"

    def __init__(self):
        super(CodeCompletionMode, self).__init__(
            self.IDENTIFIER, self.DESCRIPTION)
        self.thread_pool = QThreadPool()
        self.thread_pool.setMaxThreadCount(2)
        self.__cached_request = None
        self.__active_thread_count = 0
        self.__updating_models = False
        self.__timer = QTimer()

        #: Defines the min number of suggestions. This is used to know we should
        #  avoid using lower priority models.
        #  If there is at least minSuggestions in the suggestions list, we won't
        #  use other completion model.
        self.minSuggestions = 50

        #: Trigger key (automatically associated with the control modifier)
        self.triggerKey = Qt.Key_Space

        #: Number of chars needed to trigger the code completion
        self.nbTriggerChars = 1

        #: Tells if the completion should be triggered automatically (when
        #  len(wordUnderCursor) > nbTriggerChars )
        #  Default is True. Turning this option off might enhance performances
        #  and usability
        self.autoTrigger = True
        self.periodIsTrigger = True

        #: Show/Hide current suggestion tooltip
        self.displayTooltips = True

        self.__caseSensitivity = Qt.CaseSensitive
        #: The internal QCompleter
        self.__completer = QCompleter()
        # self.__completer.activated.connect(self._insertCompletion)
        self.__completer.highlighted.connect(self._onHighlighted)
        self.__completer.activated.connect(self._insertCompletion)
        self.__prev_txt_len = 0
        #: List of completion models
        self._models = []
        self.__tooltips = {}

    def __del__(self):
        self.__completer.setWidget(None)
        self.__completer = None

    def addModel(self, model):
        """
        Adds a completion model to the completion models list.

        :param model: CompletionModel to add
        """
        self._models.append(model)
        self._models = sorted(self._models, key=lambda mdl: mdl.priority,
                              reverse=True)

    def install(self, editor):
        """
        Setup the completer with the CodeEdit.

        :param editor: CodeEditorWidget instance
        """
        super(CodeCompletionMode, self).install(editor)
        self.__completer.setWidget(editor.codeEdit)
        self.__completer.setCaseSensitivity(self.__caseSensitivity)
        self.__completer.setCompletionMode(QCompleter.PopupCompletion)

    def __set_case(self, case):
        if case != self.__caseSensitivity:
            self.__caseSensitivity = case
            self.__completer.setCaseSensitivity(case)

    def __get_case(self):
        return self.__caseSensitivity

    #: The completion case sensitivity
    caseSensitivity = property(__get_case, __set_case)

    def _onStateChanged(self, state):
        """
        Enables/Disables code completion.

        :param state: True to enable, False to disable
        """
        if state:
            self.editor.codeEdit.keyPressed.connect(self._onKeyPressed)
            self.editor.codeEdit.postKeyPressed.connect(self._onKeyReleased)
            self.editor.codeEdit.focusedIn.connect(self._onFocusIn)
            self.__completer.highlighted.connect(
                self._displayHighlightedTooltip)
        else:
            self.editor.codeEdit.keyPressed.disconnect(self._onKeyPressed)
            self.editor.codeEdit.postKeyPressed.disconnect(self._onKeyReleased)
            self.editor.codeEdit.focusedIn.disconnect(self._onFocusIn)
            self.__completer.highlighted.disconnect(
                self._displayHighlightedTooltip)

    def _onFocusIn(self, event):
        """
        Resets completer widget

        :param event: QFocusEvent
        """
        self.__completer.setWidget(self.editor.codeEdit)

    def _onHighlighted(self, completion):
        """
        Remembers the current completion when the hilighted signal is emitted.

        :param completion: Current completion
        """
        self.currentCompletion = completion

    def _onKeyReleased(self, event):
        """
        Handles the key released event to adapt completer prefix, run the cc
        library if necessary or prevent completer popup when removing text.

        :param event:

        :return:
        """
        word = self._textUnderCursor()
        isShortcut = self._isShortcut(event) or event.key() == Qt.Key_Period
        tooShort = len(word) < self.nbTriggerChars
        # closes popup if completion prefix is empty and we are not removing
        # some text
        if (not self.__completer.popup().isVisible() and
                event.key() == Qt.Key_Backspace or event.key() == Qt.Key_Delete) or\
                (not isShortcut and event.modifiers() == 0 and (
                word.isspace() or word == "")):
            self._hideCompletions()
            return
        # . is an auto-trigger
        if self.periodIsTrigger and \
                (event.key() == Qt.Key_Period and self.autoTrigger):
            self._requestCompletion(completionPrefix=word, onlyAdapt=False)
            return
        # adapt completion prefix
        if self.__completer.popup().isVisible():
            self._requestCompletion(completionPrefix=word, onlyAdapt=True)
        # run cc if word is long enough and auto trigger is on
        elif not tooShort and self.autoTrigger and event.text() != "" \
                and (event.modifiers() == 0 or
                             event.modifiers() & Qt.ShiftModifier):
            self._requestCompletion(completionPrefix=word, onlyAdapt=False)

    def _isShortcut(self, event):
        """
        Checks if the event's key and modifiers make the completion shortcut.

        :param event: QKeyEvent

        :return: bool
        """
        return ((event.modifiers() & Qt.ControlModifier > 0) and
                event.key() == self.triggerKey)

    def _onKeyPressed(self, event):
        """
        Trigger the completion with ctrl+triggerKey and handle completion events
        ourselves (insert completion and hide completer)

        :param event: QKeyEvent
        """
        isShortcut = self._isShortcut(event)
        completionPrefix = self._textUnderCursor()
        # handle completer popup events ourselves
        if self.__completer.popup().isVisible():
            # complete
            if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return:
                self._insertCompletion(self.currentCompletion)
                self.__completer.popup().hide()
                event.stop = True
                return
            # hide
            elif event.key() == Qt.Key_Escape or event.key() == Qt.Key_Backtab:
                self.__completer.popup().hide()
                event.stop = True
                return
        # user completion request: update models and show completions
        if isShortcut:
            self._requestCompletion(completionPrefix, onlyAdapt=False)
            if event.key() == self.triggerKey:
                event.stop = True

    def selectWordUnderCursor(self):
        tc = self.editor.codeEdit.textCursor()
        original_pos = pos = tc.position()
        space_found = False
        how_many = 0
        while not space_found and pos != 0:
            tc.movePosition(QTextCursor.Left, QTextCursor.MoveAnchor, 1)
            tc.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
            ch = tc.selectedText()[0]
            if tc.selectedText() in WORD_SEPARATORS or ch.isspace():
                space_found = True
            how_many += 1
            pos = tc.position()
            tc.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor, 1)
        tc.setPosition(original_pos)
        tc.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, how_many)
        return tc

    def _textUnderCursor(self):
        """
        Returns the word under the cursor
        """
        tc = self.selectWordUnderCursor()
        selectedText = tc.selectedText()
        tokens = selectedText.split('.')
        wuc = tokens[len(tokens) - 1]
        if selectedText == ".":
            wuc = '.'
        return wuc

    def _lastCharOfLine(self):
        """
        Returns the last char of the active line.

        :return: unicode
        """
        tc = self.editor.codeEdit.textCursor()
        tc.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
        return tc.selectedText()

    def _getCCRequest(self, completionPrefix):
        """
        Creates a CompletionRequest from context (line nbr, ...)

        :param completionPrefix: the completion request prefix
        """
        tc = self.editor.codeEdit.textCursor()
        line = tc.blockNumber() + 1
        col = tc.columnNumber()
        fn = self.editor.codeEdit.tagFilename
        encoding = self.editor.codeEdit.tagEncoding
        source = self.editor.codeEdit.toPlainText()
        return CompletionRequest(
            col=col, encoding=encoding, filename=fn, line=line,
            source_code=source, completionPrefix=completionPrefix)

    def _execRequest(self, request):
        """
        Executes a cc request and emit __completionResultsAvailable when the
        execution is done.

        :param request: The CodeCompletionRequest to execute.
        """
        pass

    def _createCompleterModel(self, completionPrefix):
        """
        Creates a QStandardModel that holds the suggestion from the completion
        models for the QCompleter

        :param completionPrefix:
        """
        # build the completion model
        cc_model = QStandardItemModel()
        cptSuggestion = 0
        displayedTexts = []
        self.__tooltips.clear()
        for model in self._models:
            for s in model.suggestions:
                # skip redundant completion
                if s.display != completionPrefix and \
                        not s.display in displayedTexts:
                    displayedTexts.append(s.display)
                    items = []
                    item = QStandardItem()
                    items.append(item)
                    item.setData(s.display, Qt.DisplayRole)
                    if s.description is not None:
                        self.__tooltips[s.display] = s.description
                    if s.decoration is not None:
                        item.setData(QIcon(s.decoration), Qt.DecorationRole)
                    cc_model.appendRow(items)
                    cptSuggestion += 1
            # do we need to use more completion model?
            if cptSuggestion >= self.minSuggestions:
                break  # enough suggestions
        return cc_model, cptSuggestion

    def _showCompletions(self, completionPrefix):
        """
        Shows the completion popup

        :param completionPrefix: completion prefix use to set the popup pos
        """
        c = self.__completer
        c.setCompletionPrefix(completionPrefix)
        c.popup().setCurrentIndex(
            self.__completer.completionModel().index(0, 0))
        cr = self.editor.codeEdit.cursorRect()
        charWidth = self.editor.codeEdit.fm.width('A')
        cr.setX(cr.x() - len(completionPrefix) * charWidth)
        cr.setWidth(400)
        c.complete(cr)  # popup it up!
        self._displayHighlightedTooltip(c.currentCompletion())

    def _hideCompletions(self):
        """
        Hides the completion popup
        """
        self.__completer.popup().hide()
        QToolTip.hideText()

    def _requestCompletion(self, completionPrefix, onlyAdapt=False):
        """
        Requests a code completion. The request will be transmitted to the
        background thread and treated by the __applyRequestResults slot when
        __completionResultsAvailable is emitted.

        :param completionPrefix:
        :param onlyAdapt:
        :return:
        """
        # cancel prev running request
        if not onlyAdapt:
            request = self._getCCRequest(completionPrefix)
        else:
            request = CompletionRequest(completionPrefix=completionPrefix,
                                        onlyAdapt=True)
        # only one at a time
        if self.__active_thread_count == 0:
            if self.__cached_request:
                self.__cached_request, request = request, self.__cached_request
            self.__active_thread_count += 1
            runnable = RunnableCompleter(self._models, request)
            runnable.connect(self._applyRequestResults)
            self.thread_pool.start(runnable)
        # cache last request
        else:
            self.__cached_request = request

    def _applyRequestResults(self, request):
        """
        Updates the completer model and show the popup
        """
        self.__active_thread_count -= 1
        # is the request still up to date ?
        if request.completionPrefix == self._textUnderCursor():
            if not request.onlyAdapt:
                # update completion model and show completer
                cc_model, cptSuggestion = self._createCompleterModel(
                    request.completionPrefix)
                if cptSuggestion > 1:
                    self.__completer.setModel(cc_model)
                    self.__cc_model = cc_model
                    self._showCompletions(request.completionPrefix)
                else:
                    self._hideCompletions()
            else:
                # only adapt completion prefix, the completer is already visible
                self.__completer.setCompletionPrefix(request.completionPrefix)
                idx = self.__completer.completionModel().index(0, 0)
                self.__completer.popup().setCurrentIndex(idx)
                if self.__completer.currentCompletion() == "" or \
                        self.__completer.currentCompletion() == \
                        request.completionPrefix:
                    self._hideCompletions()
        # do we have any cached requests?
        if self.__cached_request and self.__active_thread_count == 0:
            self.__active_thread_count += 1  # prevent normal start immediately
            self.__timer.singleShot(10, self.__start_cached_request)

    def __start_cached_request(self):
        request = self.__cached_request
        self.__cached_request = None
        runnable = RunnableCompleter(self._models, request)
        runnable.connect(self._applyRequestResults)
        self.thread_pool.start(runnable)

    @Slot(unicode)
    def _displayHighlightedTooltip(self, txt):
        """
        Shows/hides current suggestion tooltip next to the completer popup
        :param txt:
        :return:
        """
        if not self.displayTooltips or not txt in self.__tooltips:
            QToolTip.hideText()
            return
        tooltip = self.__tooltips[txt]
        charWidth = self.editor.codeEdit.fm.width('A')
        # show tooltip
        pos = self.__completer.popup().pos()
        pos.setX(pos.x() + 400)
        pos.setY(pos.y() - 15)
        QToolTip.showText(pos, tooltip, self.editor.codeEdit)

    def _insertCompletion(self, completion):
        """
        Inserts the completion (replace the word under cursor)

        :param completion: the completion text to insert
        """
        offset = 0
        if len(self._textUnderCursor()) > 1:
            offset = 1
        tc = self.selectWordUnderCursor()
        tc.insertText(completion)
        self.editor.codeEdit.setTextCursor(tc)
Exemple #6
0
class TextEditor(RWWidget, Ui_Form):
    """ Contains many features of popular text editors adapted for use with
    Ontologies such as syntax highlighting, and autocompletion. One column on
    the left of the text editor contains line numbers and another contains
    other contextual information such as whether a block of code has been
    hidden/collapsed and can be displayed/expanded later.  It also contains an
    incremental search and an interface to pySUMO's settings so font size and
    family can be changed at will.

    Variables:

    - syntax_highlighter: The syntax highlighter object for the text editor.

    Methods:
    
    - __init__: Initalizes the Object and the QPlainTextEdit
    - commit: Notifies other Widgets of changes.
    - show_autocomplete: Returns autocompletion choices.
    - getWidget: returns the QPlainTextEdit
    - numberbarPaint: Paints the numberbar
    - searchCompletion: Asks QCompleter if a whole word exists starting with user input
    - hideFrom: Starts hides all lines from the ()-block started by line
    - insertCompletion: Puts the selected Completion into the TextEditor
    """
    def __init__(self, mainwindow, settings=None):
        """ Initializes the text editor widget. """
        super(TextEditor, self).__init__(mainwindow)
        self.setupUi(self.mw)
        self.plainTextEdit.clear()
        self.plainTextEdit.setEnabled(False)
        self.plainTextEdit.show()
        self.highlighter = SyntaxHighlighter(self.plainTextEdit.document(),
                                             settings)
        self.initAutocomplete()

        self._initNumberBar()
        self.hidden = {}
        self.printer = QPrinter(QPrinterInfo.defaultPrinter())
        self.plainTextEdit.setTextCursor(
            self.plainTextEdit.cursorForPosition(QPoint(0, 0)))

        self.canUndo = False
        self.canRedo = False

        self.ontologySelector.setCurrentIndex(-1)

        self.timer = QTimer(self)
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.commit)

        #Connects
        self.getWidget().textChanged.connect(self.searchCompletion)
        self.plainTextEdit.undoAvailable.connect(self.setCanUndo)
        self.plainTextEdit.redoAvailable.connect(self.setCanRedo)

        self.ontologySelector.currentIndexChanged[int].connect(
            self.showOtherOntology)
        self.plainTextEdit.textChanged.connect(self.expandIfBracketRemoved)

        self.plainTextEdit.textChanged.connect(self.setTextChanged)

        self._updateOntologySelector()  #must be after connects

    @Slot()
    def setTextChanged(self):
        """Is called if the text changed signal is thrown and 
        sets a timer of 3 seconds to reparse the ontology. """
        self.timer.stop()
        self.timer.start(3000)

    def setCanUndo(self, b):
        self.canUndo = b

    def setCanRedo(self, b):
        self.canRedo = b

    def _print_(self):
        """ Creates a print dialog with the latest text"""
        dialog = QPrintDialog()
        if dialog.exec_() == QDialog.Accepted:
            doc = self.plainTextEdit.document()
            doc.print_(dialog.printer())

    def _quickPrint_(self):
        """ No dialog, just print"""
        if self.printer is None:
            return
        doc = self.plainTextEdit.document()
        doc.print_(self.printer)

    def _printPreview_(self):
        """ Create a print preview"""
        dialog = QPrintPreviewDialog()
        dialog.paintRequested.connect(self.plainTextEdit.print_)
        dialog.exec_()

    def saveOntology(self):
        """ Save the ontology to disk"""
        idx = self.ontologySelector.currentIndex()
        ontology = self.ontologySelector.itemData(idx)
        if ontology is None:
            return
        if type(ontology) is Ontology:
            ontology.save()

    def getActiveOntology(self):
        idx = self.ontologySelector.currentIndex()
        return self.ontologySelector.itemData(idx)

    def undo(self):
        if self.canUndo:
            self.plainTextEdit.undo()
            try:
                self.SyntaxController.add_ontology(
                    self.getActiveOntology(), self.plainTextEdit.toPlainText())
            except ParseError:
                return
            self.commit()
        else:
            super(TextEditor, self).undo()

    def redo(self):
        if self.canRedo:
            self.plainTextEdit.redo()
            try:
                self.SyntaxController.add_ontology(
                    self.getActiveOntology(), self.plainTextEdit.toPlainText())
            except ParseError:
                return
            self.commit()
        else:
            super(TextEditor, self).redo()

    def _initNumberBar(self):
        """ Init the number bar"""
        self.number_bar = NumberBar(self)
        self.number_bar.setMinimumSize(QSize(30, 0))
        self.number_bar.setObjectName("number_bar")
        self.gridLayout.addWidget(self.number_bar, 1, 0, 1, 1)
        self.plainTextEdit.blockCountChanged.connect(
            self.number_bar.adjustWidth)
        self.plainTextEdit.updateRequest.connect(
            self.number_bar.updateContents)

    @Slot(int)
    def jumpToLocation(self, location, ontology):
        if ontology == str(self.getActiveOntology()):
            textBlock = self.plainTextEdit.document().findBlockByNumber(
                location)
            pos = textBlock.position()
            textCursor = self.plainTextEdit.textCursor()
            textCursor.setPosition(pos)
            self.plainTextEdit.setTextCursor(textCursor)
            self.plainTextEdit.centerCursor()

    def _updateOntologySelector(self):
        """ Update the ontology selector where you can select which Ontology to show in the editor"""
        current = self.ontologySelector.currentText()
        self.ontologySelector.currentIndexChanged[int].disconnect(
            self.showOtherOntology)
        self.ontologySelector.clear()
        index = -1
        count = 0
        for i in self.getIndexAbstractor().ontologies:
            if current == i.name:
                index = count
            self.ontologySelector.addItem(i.name, i)
            count = count + 1
        self.ontologySelector.setCurrentIndex(index)
        # if index == -1 :
        # the ontology was removed.
        #    self.showOtherOntology(index)
        if index == -1:
            self.plainTextEdit.setEnabled(False)
            self.plainTextEdit.clear()
        self.ontologySelector.currentIndexChanged[int].connect(
            self.showOtherOntology)

    def setActiveOntology(self, ontology):
        index = -1
        count = 0
        for i in self.getIndexAbstractor().ontologies:
            if ontology.name == i.name:
                index = count
                break
            count = count + 1
        self.ontologySelector.setCurrentIndex(index)

    @Slot(int)
    def showOtherOntology(self, idx):
        """ Show other ontology in the plaintextedit
            
            Arguments:
            
            - idx: The id of the current Ontologyselector
        """
        dced = False
        try:
            self.plainTextEdit.textChanged.disconnect(self.setTextChanged)
        except RuntimeError:
            dced = True

        idx = self.ontologySelector.currentIndex()

        if idx == -1:
            self.plainTextEdit.setEnabled(False)
            self.plainTextEdit.clear()
            return
        ontologyname = self.ontologySelector.currentText()
        for i in self.getIndexAbstractor().ontologies:
            if i.name == ontologyname:
                self.plainTextEdit.setEnabled(True)
                self.getWidget().setPlainText(
                    self.getIndexAbstractor().get_ontology_file(i).getvalue())

                if not dced:
                    self.plainTextEdit.textChanged.connect(self.setTextChanged)

                return
        self.plainTextEdit.textChanged.connect(self.commit)
        assert False

    @Slot()
    def expandIfBracketRemoved(self):
        """ Check if a line with ( or ) was changed and expand the possible hidden lines   
        """
        current_line = self.getWidget().document().findBlock(
            self.getWidget().textCursor().position()).blockNumber() + 1
        if current_line in self.hidden:
            self.toggleVisibility(current_line)

    @Slot()
    def zoomIn(self):
        """ Increase the size of the font in the TextEditor
        
        """
        doc = self.getWidget().document()
        font = doc.defaultFont()
        font.setPointSize(font.pointSize() + 1)
        font = QFont(font)
        doc.setDefaultFont(font)

    @Slot()
    def zoomOut(self):
        """ Decrease the size of the font in the TextEditor"""
        doc = self.getWidget().document()
        font = doc.defaultFont()
        font.setPointSize(font.pointSize() - 1)
        font = QFont(font)
        doc.setDefaultFont(font)

    @Slot()
    def expandAll(self):
        """ Expands all hidden code blocks"""
        for see in list(self.hidden.keys()):
            self.toggleVisibility(see)

    @Slot()
    def collapseAll(self):
        """ Collapse all code blocks (where possible)"""
        block = self.getWidget().document().firstBlock()
        while block.isValid():
            if block.isVisible():
                if block.text().count("(") > block.text().count(")"):
                    self.toggleVisibility(block.blockNumber() + 1)
            block = block.next()

    def _hideLines(self, lines):
        for line in lines:
            if line == 0:
                continue
            block = self.getWidget().document().findBlockByNumber(line - 1)
            assert block.isVisible()
            block.setVisible(False)
            assert not block.isVisible(), "Problem with line %r" % (line)

    def _showLines(self, lines):
        """ Show the lines not visible starting by lines
        
        Arguments:
        
        - lines: The first line followed by an unvisible block
        
        """
        for line in lines:
            block = self.getWidget().document().findBlockByNumber(line - 1)
            block.setVisible(True)

    def getLayoutWidget(self):
        """ Returns the layout widget"""
        return self.widget

    def numberbarPaint(self, number_bar, event):
        """Paints the line numbers of the code file"""
        self.number_bar.link = []
        font_metrics = self.getWidget().fontMetrics()
        current_line = self.getWidget().document().findBlock(
            self.getWidget().textCursor().position()).blockNumber() + 1

        block = self.getWidget().firstVisibleBlock()
        line_count = block.blockNumber()
        painter = QPainter(self.number_bar)
        # TODO: second argument is color -> to settings
        painter.fillRect(self.number_bar.rect(),
                         self.getWidget().palette().base())

        # Iterate over all visible text blocks in the document.
        while block.isValid():
            line_count += 1
            text = str(line_count)
            block_top = self.getWidget().blockBoundingGeometry(
                block).translated(self.getWidget().contentOffset()).top()
            if not block.isVisible():
                block = block.next()
                while not block.isVisible():
                    line_count += 1
                    block = block.next()
                continue
            self.number_bar.link.append((block_top, line_count))
            # Check if the position of the block is out side of the visible
            # area.
            if block_top >= event.rect().bottom():
                break

            # We want the line number for the selected line to be bold.
            if line_count == current_line:
                font = painter.font()
                font.setBold(True)

            else:
                font = painter.font()
                font.setBold(False)
            # line opens a block
            if line_count in self.hidden:
                text += "+"
                font.setUnderline(True)
            elif block.text().count("(") > block.text().count(")"):
                text += "-"
                font.setUnderline(True)
            else:
                font.setUnderline(False)
            painter.setFont(font)
            # Draw the line number right justified at the position of the
            # line.
            paint_rect = QRect(0, block_top, self.number_bar.width(),
                               font_metrics.height())
            painter.drawText(paint_rect, Qt.AlignLeft, text)
            block = block.next()

        painter.end()

    def initAutocomplete(self):
        """Inits the QCompleter and gives him a list of words"""
        self.completer = QCompleter(
            list(
                OrderedDict.fromkeys(
                    re.split("\\W", self.plainTextEdit.toPlainText()))))
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer.setWidget(self.getWidget())
        self.completer.activated.connect(self.insertCompletion)

    def searchCompletion(self):
        """Searches for possible completion from QCompleter to the current text position"""
        tc = self.getWidget().textCursor()
        tc.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor)
        if tc.selectedText() in string.whitespace:
            self.completer.popup().hide()
            return
        tc.movePosition(QTextCursor.StartOfWord, QTextCursor.KeepAnchor)

        beginning = tc.selectedText()
        if len(beginning) >= 3:
            self.completer.setCompletionPrefix(beginning)
            self.completer.complete()
        shortcut = QShortcut(QKeySequence("Ctrl+Enter"), self.getWidget(),
                             self.insertCompletion)

    def toggleVisibility(self, line):
        """ Shows or hides a line """
        if line in self.hidden:
            self._showLines(self.hidden[line])
            del self.hidden[line]
        else:
            self.hideFrom(line)

        # update views
        self.getWidget().hide()
        self.getWidget().show()
        self.number_bar.update()

    def hideFrom(self, line):
        """ Hides a block starting by line. Do nothing if not hidable"""
        block = self.getWidget().document().findBlockByNumber(line - 1)

        openB = block.text().count("(")
        closeB = block.text().count(")")
        startline = line
        # go to line >= line: block starts counting by 0
        block = self.getWidget().document().findBlockByNumber(line - 1)
        hidden = []
        assert block.isValid()
        while openB > closeB and block.isValid():
            assert block.isValid()
            block = block.next()
            line = block.blockNumber() + 1
            if block.isVisible():
                hidden.append(line)
            openB += block.text().count("(")
            closeB += block.text().count(")")

        if hidden == []:
            return
        self._hideLines(hidden)
        self.hidden[startline] = hidden

        # set current line in viewable area
        current_line = self.getWidget().document().findBlock(
            self.getWidget().textCursor().position()).blockNumber() + 1
        if (startline < current_line and current_line <= line):
            block = block.next()
            cursor = QTextCursor(block)
            self.getWidget().setTextCursor(cursor)

    @Slot(str)
    def insertCompletion(self, completion):
        """ Adds the completion to current text"""
        tc = self.getWidget().textCursor()
        tc.movePosition(QTextCursor.StartOfWord, QTextCursor.KeepAnchor)
        tc.removeSelectedText()
        tc.insertText(completion)

    def getWidget(self):
        """ Return the QPlainTextEdit Widget"""
        return self.plainTextEdit

    @Slot()
    def commit(self):
        """ Overrides commit from RWWidget. """

        idx = self.ontologySelector.currentIndex()
        if idx == -1:
            return
        ontology = self.ontologySelector.itemData(idx)
        if ontology is None:
            return
        try:
            QApplication.setOverrideCursor(Qt.BusyCursor)
            self.SyntaxController.add_ontology(
                ontology, self.plainTextEdit.toPlainText())
            QApplication.setOverrideCursor(Qt.ArrowCursor)
        except ParseError:
            return
        super(TextEditor, self).commit()

    @Slot()
    def refresh(self):
        """ Refreshes the content of the TextEditor (syncing with other widgets)"""
        textCursorPos = self.plainTextEdit.textCursor().position()
        super(TextEditor, self).refresh()
        dced = False
        try:
            self.plainTextEdit.textChanged.disconnect(self.setTextChanged)
        except RuntimeError:
            dced = True
        idx = self.ontologySelector.currentIndex()
        ontology = self.ontologySelector.itemData(idx)
        if ontology in self.IA.ontologies:
            f = self.IA.get_ontology_file(ontology)
            self.plainTextEdit.setPlainText(f.getvalue())
        if not dced:
            self.plainTextEdit.textChanged.connect(self.setTextChanged)
        cursor = self.plainTextEdit.textCursor()
        cursor.setPosition(textCursorPos)
        self.plainTextEdit.setTextCursor(cursor)
        self.plainTextEdit.centerCursor()
Exemple #7
0
class EditorCodeCompletion(QTextEdit):
    def __init__(self, path_dict):
        super(EditorCodeCompletion, self).__init__()  
        self.m_completer = QCompleter(self)
        self.m_completer.setWidget(self)
        words = []
        
        self.flag_open_angle_bracket = False
        self.tag_name = ""
        
        try:
            f = open(path_dict,"r")
            for word in f:
                words.append(word.strip())
            f.close()
        except IOError:
            print ("dictionary not in anticipated location")
       
        model = QStringListModel(words, self.m_completer)
        
        self.m_completer.setModel(model)
        self.m_completer.setCompletionMode(QCompleter.PopupCompletion)
        self.m_completer.activated.connect(self.insertCompletion)

    def insertCompletion (self, completion):
        cursor = self.textCursor()
        cursor.beginEditBlock()
        cursor.movePosition(QTextCursor.Left)
        cursor.movePosition(QTextCursor.EndOfWord)
        extra = len(self.m_completer.completionPrefix())
        cursor.insertText(completion[extra:])
        self.setTextCursor(cursor)
        cursor.endEditBlock()

    def __insertTag(self):
        '''
        inserts the corresponding closing tag to an opening xml tag
        '''
        self.find('<', QTextDocument.FindBackward)
        tc = self.textCursor()        
        tc.select(QTextCursor.WordUnderCursor)
        txt = '' if self.__stringHasBracket(tc.selectedText().replace(' ', '')) else tc.selectedText()
        txt = '</' + txt + '>'
        
        self.find('>')    
        tc = self.textCursor()
        tc.clearSelection()
        
        tc.insertText(txt) 
        tc.movePosition(QTextCursor.Left, QTextCursor.MoveAnchor, len(txt))
        self.setTextCursor(tc)
    
    def __stringHasBracket(self, s):
        return '<' in s or '>' in s

    def __insertClosingTag(self, event):
        operation_sys = sys.platform 
        flag = "linux" in operation_sys        
        if flag :
            self.__insertClosingTag_Unix(event)
        else:
            self.__insertClosingTag_Win(event)
        
    def __insertClosingTag_Unix(self, event):
        '''
        inserts a closing tag after the closing bracket of open tag
        @param key: keyboard input value as int
        '''
        if self.flag_open_angle_bracket :
            if event.key() == 47 : # /
                print ("/")
                self.flag_open_angle_bracket = False 
            elif event.key() == 62  :  # >
                print (">")
                self.__insertTag()
                self.flag_open_angle_bracket = False  
        elif event.key() == 60  :  # <
            print ("<")
            self.flag_open_angle_bracket = True
 
    def __insertClosingTag_Win(self, event) :
        if self.flag_open_angle_bracket :
            if event.modifiers() & Qt.ShiftModifier :
                if event.key() == 55 : # /
                    print ("/")
                    self.flag_open_angle_bracket = False             
                elif event.key() == 60 : # > 
                    print (">")
                    self.__insertTag()
                    self.flag_open_angle_bracket = False  
        elif event.key() == 60  :  # <
            print ("<")
            self.flag_open_angle_bracket = True    
 
    def keyPressEvent(self, event):
        '''
        checks keyboard input to set closing tag or start code completion 
        '''        
        if self.m_completer.popup().isVisible() :
            if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return or event.key() == Qt.Key_Tab or event.key() == Qt.Key_Escape :
                event.ignore()
                return
        
        # open popup
        isShortcut = (event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_Space)                
                
        if not isShortcut:        
            super(EditorCodeCompletion, self).keyPressEvent(event)
            # ============================================================================================= 
            # begin tag inline insertion         
            self.__insertClosingTag(event)
            # end tag inline insertion 
            # ============================================================================================= 
        
        cursor = self.textCursor()
        cursor.select(QTextCursor.WordUnderCursor)        
        completionPrefix = cursor.selectedText()

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

        # if not event.text() != "" and len(completionPrefix) > 2 :
        if len(completionPrefix) > 2 and isShortcut :
            print ("hier")
            cr = self.cursorRect()
            cr.setWidth(2 * (self.m_completer.popup().sizeHintForColumn(0)
                         + self.m_completer.popup().verticalScrollBar().sizeHint().width()))

            self.m_completer.complete(cr)  # # popup it up!