Ejemplo n.º 1
0
    def _complete_options(self):
        """Find available completion options."""
        text = to_text_string(self.currentText())
        opts = glob.glob(text + "*")
        opts = sorted([opt for opt in opts if osp.isdir(opt)])

        completer = QCompleter(opts, self)
        qss = str(APP_STYLESHEET)
        completer.popup().setStyleSheet(qss)
        self.setCompleter(completer)

        return opts
Ejemplo n.º 2
0
    def _complete_options(self):
        """Find available completion options."""
        text = to_text_string(self.currentText())
        opts = glob.glob(text + "*")
        opts = sorted([opt for opt in opts if osp.isdir(opt)])

        completer = QCompleter(opts, self)
        if is_dark_interface():
            dark_qss = qdarkstyle.load_stylesheet_from_environment()
            completer.popup().setStyleSheet(dark_qss)
        self.setCompleter(completer)

        return opts
Ejemplo n.º 3
0
class AutoCompleteLineEdit(QLineEdit):
    # http://blog.elentok.com/2011/08/autocomplete-textbox-for-multiple.html
    def __init__(self, items, parent=None):
        super().__init__(parent)

        self._separators = [",", " "]

        self._completer = QCompleter(items, self)
        self._completer.setWidget(self)
        self._completer.activated[str].connect(self.__insertCompletion)
        self._completer.setCaseSensitivity(Qt.CaseInsensitive)

        self.__keysToIgnore = [
            Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab
        ]

    def __insertCompletion(self, completion):
        extra = len(completion) - len(self._completer.completionPrefix())
        extra_text = completion[-extra:]
        extra_text += ", "
        self.setText(self.text() + extra_text)

    def textUnderCursor(self):
        text = self.text()
        text_under_cursor = ""
        i = self.cursorPosition() - 1
        while i >= 0 and text[i] not in self._separators:
            text_under_cursor = text[i] + text_under_cursor
            i -= 1
        return text_under_cursor

    def keyPressEvent(self, event):
        if self._completer.popup().isVisible():
            if event.key() in self.__keysToIgnore:
                event.ignore()
                return

        super().keyPressEvent(event)

        completion_prefix = self.textUnderCursor()
        if completion_prefix != self._completer.completionPrefix():
            self.__updateCompleterPopupItems(completion_prefix)
        if len(event.text()) > 0 and len(completion_prefix) > 0:
            self._completer.complete()
        if len(completion_prefix) == 0:
            self._completer.popup().hide()

    def __updateCompleterPopupItems(self, completionPrefix):
        self._completer.setCompletionPrefix(completionPrefix)
        self._completer.popup().setCurrentIndex(
            self._completer.completionModel().index(0, 0))
Ejemplo n.º 4
0
class IdePanel(QPlainTextEdit):
    def __init__(self):
        QPlainTextEdit.__init__(self)
        self.setWordWrapMode(QTextOption.NoWrap)
        self.setFont(QFont("monospace", 10))
        self.setCursorWidth(2)
        self.installEventFilter(self)

        self.completer = QCompleter(self)
        self.completer.setWidget(self)
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer.activated.connect(self.insertCompletion)

        auto_complete = QShortcut(QKeySequence("Ctrl+Space"), self)
        auto_complete.activated.connect(self.activateCompleter)

        copy_line = QShortcut(QKeySequence("Ctrl+D"), self)
        copy_line.activated.connect(self.duplicateLine)

        select_fragment = QShortcut(QKeySequence("Ctrl+J"), self)
        select_fragment.activated.connect(self.selectFragment)

    def getText(self):
        return self.document().toPlainText()

    def eventFilter(self, qobject, qevent):
        if qobject == self and qevent.type() == QEvent.ToolTip:
            text_cursor = self.cursorForPosition(qevent.pos())
            pos = text_cursor.positionInBlock()

            user_data = text_cursor.block().userData()
            if user_data is not None:
                #: :type: ConfigurationLine
                configuration_line = user_data.configuration_line
                # if configuration_line.keyword().hasKeywordDefinition():
                #     print(configuration_line.keyword().keywordDefinition().documentation)

                if pos in configuration_line.keyword():
                    self.setToolTip(
                        configuration_line.validationStatusForToken(
                            configuration_line.keyword()).message())
                else:
                    for argument in configuration_line.arguments():
                        if pos in argument:
                            self.setToolTip(
                                configuration_line.validationStatusForToken(
                                    argument).message())

            else:
                self.setToolTip("")

        return QPlainTextEdit.eventFilter(self, qobject, qevent)

    def activateCompleter(self):
        text_cursor = self.textCursor()
        block = self.document().findBlock(text_cursor.position())
        position_in_block = text_cursor.positionInBlock()

        self.selectWordUnderCursor(text_cursor)
        word = unicode(text_cursor.selectedText())

        user_data = block.userData()

        self.completer.setCompletionPrefix(word)

        show_completer = False
        if user_data is None:
            self.completer.setModel(QStringListModel(self.handler_names))
            show_completer = True

        else:
            keyword = user_data.keyword
            options = keyword.handler.parameterOptions(keyword, word,
                                                       position_in_block)

            if len(options) == 1:
                self.insertCompletion(options[0])
            elif len(options) > 1:
                self.completer.setModel(QStringListModel(options))
                if self.completer.completionCount() == 1:
                    self.insertCompletion(self.completer.currentCompletion())
                else:
                    show_completer = True

        if show_completer:
            rect = self.cursorRect(text_cursor)
            rect.setWidth(
                self.completer.popup().sizeHintForColumn(0) +
                self.completer.popup().verticalScrollBar().sizeHint().width())
            self.completer.complete(rect)

    def keyPressEvent(self, qkeyevent):
        if self.completer.popup().isVisible():
            dead_keys = [
                Qt.Key_Enter,
                Qt.Key_Return,
                Qt.Key_Escape,
                Qt.Key_Tab,
                Qt.Key_Backtab,
            ]
            if qkeyevent.key() in dead_keys:
                qkeyevent.ignore()
                return

        if qkeyevent.modifiers() == Qt.ShiftModifier:
            if qkeyevent.key() & Qt.Key_Delete == Qt.Key_Delete:
                self.deleteLine()

        QPlainTextEdit.keyPressEvent(self, qkeyevent)

    def insertCompletion(self, string):
        text_cursor = self.textCursor()
        self.selectWordUnderCursor(text_cursor)
        text_cursor.insertText(string)

    def isCursorInSpace(self):
        text_cursor = self.textCursor()
        if text_cursor.positionInBlock() > 0:
            text_cursor.movePosition(QTextCursor.Left, QTextCursor.MoveAnchor)
            text_cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)

        if text_cursor.positionInBlock() < text_cursor.block().length() - 1:
            text_cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor)

        if unicode(text_cursor.selectedText()).strip() == "":
            return True

        return False

    def selectWordUnderCursor(self, text_cursor):
        if not self.isCursorInSpace():
            # text_cursor.select(QTextCursor.WordUnderCursor)

            # pattern = "[\s|\v|\f|\n|\r|\t|\xe2\x80\xa8|\xe2\x80\xa9]"
            # pattern = "[\\s|\\xe2\\x80\\xa9|\\xe2\\x80\\xa8]"

            block_start = 0
            block_end = text_cursor.block().length()

            cursor_pos = text_cursor.positionInBlock()
            pos = cursor_pos
            pattern = u"[\\s\u2029\u2028]"
            while pos >= block_start:
                text_cursor.movePosition(QTextCursor.Left,
                                         QTextCursor.KeepAnchor)
                text = text_cursor.selectedText()
                if re.search(pattern, text):
                    break
                pos -= 1

            text_cursor.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor)

            while pos < block_end:
                text_cursor.movePosition(QTextCursor.Right,
                                         QTextCursor.KeepAnchor)
                text = text_cursor.selectedText()
                if re.search(pattern, text):
                    break
                pos += 1

            text_cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor)

            # pattern = "[\\s]"
            # start = self.document().find(QRegExp(pattern), text_cursor, QTextDocument.FindBackward | QTextDocument.FindCaseSensitively)
            # end = self.document().find(QRegExp(pattern), text_cursor, QTextDocument.FindCaseSensitively)
            # block_end_pos = text_cursor.block().position() + text_cursor.block().length()
            #
            # text_cursor.setPosition(start.position(), QTextCursor.MoveAnchor)
            # # text_cursor.setPosition(min(block_end_pos, end.position() - 1), QTextCursor.KeepAnchor)
            # text_cursor.setPosition(end.position() - 1, QTextCursor.KeepAnchor)

    def deleteLine(self):
        text_cursor = self.textCursor()
        text_cursor.beginEditBlock()
        text_cursor.select(QTextCursor.LineUnderCursor)
        text_cursor.removeSelectedText()
        text_cursor.deletePreviousChar()

        text_cursor.movePosition(QTextCursor.NextBlock)
        text_cursor.movePosition(QTextCursor.StartOfLine)
        self.setTextCursor(text_cursor)
        text_cursor.endEditBlock()

    def duplicateLine(self):
        text_cursor = self.textCursor()
        text_cursor.beginEditBlock()
        text_cursor.select(QTextCursor.LineUnderCursor)
        text = text_cursor.selectedText()
        text_cursor.movePosition(QTextCursor.EndOfLine)
        text_cursor.insertBlock()
        text_cursor.insertText(text)
        text_cursor.endEditBlock()

    def selectFragment(self):
        text_cursor = self.textCursor()
        self.selectWordUnderCursor(text_cursor)
        self.setTextCursor(text_cursor)
Ejemplo n.º 5
0
class AutoComplete(QObject):
    def __init__(self, parent):
        super(AutoComplete, self).__init__(parent)
        self.mode = COMPLETE_MODE.INLINE
        self.completer = None
        self._last_key = None

        parent.edit.installEventFilter(self)
        self.init_completion_list([])

    def eventFilter(self, widget, event):
        if event.type() == QEvent.KeyPress:
            return bool(self.key_pressed_handler(event))
        return False

    def key_pressed_handler(self, event):
        intercepted = False
        key = event.key()

        if key == Qt.Key_Tab:
            intercepted = self.handle_tab_key(event)
        elif key in (Qt.Key_Return, Qt.Key_Enter, Qt.Key_Space):
            intercepted = self.handle_complete_key(event)
        elif key == Qt.Key_Escape:
            intercepted = self.hide_completion_suggestions()

        self._last_key = key
        self.update_completion(key)
        return intercepted

    def handle_tab_key(self, event):
        if self.parent()._textCursor().hasSelection():
            return False

        if self.mode == COMPLETE_MODE.DROPDOWN:
            if self.parent().input_buffer().strip():
                if self.completing():
                    self.complete()
                else:
                    self.trigger_complete()

                event.accept()
                return True

        elif self.mode == COMPLETE_MODE.INLINE:
            if self._last_key == Qt.Key_Tab:
                self.trigger_complete()

            event.accept()
            return True

    def handle_complete_key(self, event):
        if self.completing():
            self.complete()
            event.accept()
            return True

    def init_completion_list(self, words):
        self.completer = QCompleter(words, self)
        self.completer.setCompletionPrefix(self.parent().input_buffer())
        self.completer.setWidget(self.parent().edit)
        self.completer.setCaseSensitivity(Qt.CaseSensitive)
        self.completer.setModelSorting(QCompleter.CaseSensitivelySortedModel)

        if self.mode == COMPLETE_MODE.DROPDOWN:
            self.completer.setCompletionMode(QCompleter.PopupCompletion)
            self.completer.activated.connect(self.insert_completion)
        else:
            self.completer.setCompletionMode(QCompleter.InlineCompletion)

    def trigger_complete(self):
        _buffer = self.parent().input_buffer().strip()
        self.show_completion_suggestions(_buffer)

    def show_completion_suggestions(self, _buffer):
        words = self.parent().get_completions(_buffer)

        # No words to show, just return
        if len(words) == 0:
            return

        # Close any popups before creating a new one
        if self.completer.popup():
            self.completer.popup().close()

        self.init_completion_list(words)

        leastcmn = long_substr(words)
        self.insert_completion(leastcmn)

        # If only one word to complete, just return and don't display options
        if len(words) == 1:
            return

        if self.mode == COMPLETE_MODE.DROPDOWN:
            cr = self.parent().edit.cursorRect()
            sbar_w = self.completer.popup().verticalScrollBar()
            popup_width = self.completer.popup().sizeHintForColumn(0)
            popup_width += sbar_w.sizeHint().width()
            cr.setWidth(popup_width)
            self.completer.complete(cr)
        elif self.mode == COMPLETE_MODE.INLINE:
            cl = columnize(words, colsep='  |  ')
            self.parent()._insert_output_text('\n\n' + cl + '\n',
                                              lf=True,
                                              keep_buffer=True)

    def hide_completion_suggestions(self):
        if self.completing():
            self.completer.popup().close()
            return True

    def completing(self):
        if self.mode == COMPLETE_MODE.DROPDOWN:
            return (self.completer.popup()
                    and self.completer.popup().isVisible())
        else:
            return False

    def insert_completion(self, completion):
        _buffer = self.parent().input_buffer().strip()

        # Handling the . operator in object oriented languages so we don't
        # overwrite the . when we are inserting the completion. Its not the .
        # operator If the buffer starts with a . (dot), but something else
        # perhaps terminal specific so do nothing.
        if '.' in _buffer and _buffer[0] != '.':
            idx = _buffer.rfind('.') + 1
            _buffer = _buffer[idx:]

        if self.mode == COMPLETE_MODE.DROPDOWN:
            self.parent().insert_input_text(completion[len(_buffer):])
        elif self.mode == COMPLETE_MODE.INLINE:
            self.parent().clear_input_buffer()
            self.parent().insert_input_text(completion)

            words = self.parent().get_completions(completion)

            if len(words) == 1:
                self.parent().insert_input_text(' ')

    def update_completion(self, key):
        if self.completing():
            _buffer = self.parent().input_buffer()

            if len(_buffer) > 1:
                self.show_completion_suggestions(_buffer)
                self.completer.setCurrentRow(0)
                model = self.completer.completionModel()
                self.completer.popup().setCurrentIndex(model.index(0, 0))
            else:
                self.completer.popup().hide()

    def complete(self):
        if self.completing() and self.mode == COMPLETE_MODE.DROPDOWN:
            index = self.completer.popup().currentIndex()
            model = self.completer.completionModel()
            word = model.itemData(index)[0]
            self.insert_completion(word)
            self.completer.popup().hide()
Ejemplo n.º 6
0
class AbstractComboListInputWidget(QComboBox):
    """
    A custom QComboBox with a completer / model.  This is
    designed to be an abstract class that will be inherited by the
    the GSV and Node ComboBoxes

    Attributes:
        exists (bool) flag used to determine whether or not the popup menu
            for the menu change should register or not (specific to copy/paste
            of a node.

            In plain english... this flag is toggled to hide the Warning PopUp Box
            from displaying to the user in some events.
        item_list (list): string list of all of the items in the list.  Updating this
            will auto update the default settings for blank setups
        previous_text (str): the previous items text.  This is stored for cancel events
            and allowing the user to return to the previous item after cancelling.
    """
    TYPE = 'list'

    def __init__(self, parent=None):
        super(AbstractComboListInputWidget, self).__init__(parent)
        self.main_widget = self.parent()
        self.previous_text = ''
        self.setExistsFlag(True)

        # setup line edit
        #self.line_edit = QLineEdit("Select & Focus", self)
        self.line_edit = QLineEdit(self)
        self.line_edit.editingFinished.connect(self.userFinishedEditing)
        self.setLineEdit(self.line_edit)

        self.setEditable(True)

        # setup completer
        self.completer = QCompleter(self)
        self.completer.setCompletionMode(QCompleter.PopupCompletion)
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer.setPopup(self.view())
        self.setCompleter(self.completer)
        self.pFilterModel = QSortFilterProxyModel(self)

        # set size policy ( this will ignore the weird resizing effects)
        size_policy = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)
        self.setSizePolicy(size_policy)

        # initialize model...
        model = QStandardItemModel()
        self.setModel(model)
        self.setModelColumn(0)

    def populate(self, item_list):
        """
        Creates all of the items from the item list and
        appends them to the model.

        item_list (list): list of strings to be displayed to the user
            this should be set with the setCleanItemsFunction.
        """
        self.setItemList(item_list)
        for i, item_name in enumerate(self.getItemList()):
            item = QStandardItem(item_name)
            self.model().setItem(i, 0, item)
            self.setExistsFlag(False)

    def update(self):
        """
        Updates the model items with all of the graph state variables.

        This is very similar to the populate call, except that with this, it
        will remove all of the items except for the current one.  Which
        will ensure that an indexChanged event is not registered thus
        updating the UI.
        """
        self.setExistsFlag(False)

        self.model().clear()
        self.populate(self.getCleanItems())
        self.setCurrentIndexToText(self.previous_text)

        self.setExistsFlag(True)

    def setModel(self, model):
        # somehow this super makes the node type not resize...
        super(AbstractListInputWidget, self).setModel(model)
        self.pFilterModel.setSourceModel(model)
        self.completer.setModel(self.pFilterModel)

    def setModelColumn(self, column):
        self.completer.setCompletionColumn(column)
        self.pFilterModel.setFilterKeyColumn(column)
        super(AbstractListInputWidget, self).setModelColumn(column)

    def view(self):
        return self.completer.popup()

    """ UTILS """

    def next_completion(self):
        row = self.completer.currentRow()

        # if does not exist reset
        if not self.completer.setCurrentRow(row + 1):
            self.completer.setCurrentRow(0)

        # if initializing
        if self.completer.popup().currentIndex().row() == -1:
            self.completer.setCurrentRow(0)

        index = self.completer.currentIndex()
        self.completer.popup().setCurrentIndex(index)

    def previous_completion(self):
        row = self.completer.currentRow()
        numRows = self.completer.completionCount()

        # if wrapping
        if not self.completer.setCurrentRow(row - 1):
            self.completer.setCurrentRow(numRows - 1)
        # if initializing
        if self.completer.popup().currentIndex().row() == -1:
            self.completer.setCurrentRow(numRows - 1)

        index = self.completer.currentIndex()
        self.completer.popup().setCurrentIndex(index)

    def setCurrentIndexToText(self, text):
        """
        Sets the current index to the text provided.  If no
        match can be found, this will default back to the
        first index.

        If no first index... create '' ?
        """
        self.setExistsFlag(False)

        # get all matches
        items = self.model().findItems(text, Qt.MatchExactly)

        # set to index of match
        if len(items) > 0:
            index = self.model().indexFromItem(items[0]).row()
            self.setCurrentIndex(index)
        else:
            self.setCurrentIndex(0)
        self.previous_text = self.currentText()
        self.setExistsFlag(True)

    def isUserInputValid(self):
        """
        Determines if the new user input is currently
        in the model.

        Returns True if this is an existing item, Returns
        false if it is not.
        """
        items = self.model().findItems(self.currentText(), Qt.MatchExactly)
        if len(items) > 0:
            return True
        else:
            return False

    def setupStyleSheet(self):
        width = self.width()
        dropdown_width = int(width * 0.35)
        style_sheet_args = iColor.style_sheet_args
        style_sheet_args['width'] = dropdown_width
        # QComboBox {{
        #     border: None;
        #     background-color: rgba{rgba_gray_0}
        # }}
        style_sheet = """
            QComboBox{{
                border: None;
                background-color: rgba{rgba_gray_0};
                color: rgba{rgba_text};
            }}
            QComboBox::drop-down {{
                width: {width}px;
            }}
            QLineEdit{{
                border: None;
                background-color: rgba{rgba_gray_0};
                color: rgba{rgba_text};
            }}
            QListView{{
                border: None;
                background-color: rgba{rgba_gray_0};
                color: rgba{rgba_text};
            }}
            QListView::item:hover{{
                background-color: rgba(255,0,0,255);
            }}
        """.format(**style_sheet_args)

        self.completer.popup().setStyleSheet("""
            QListView{{
                border: None;
                background-color: rgba{rgba_gray_0};
                color: rgba{rgba_text};
            }}
            QListView::item:hover{{
                background-color: rgba(255,0,0,255);
            }}
        """.format(**style_sheet_args))

        self.setStyleSheet(style_sheet)

    """ API """

    def __selectionChangedEmit(self):
        pass

    def setSelectionChangedEmitEvent(self, method):
        """
        sets the method for the selection changed emit call
        this will be called everytime the user hits enter/return
        inside of the line edits as a way of sending an emit
        signal from the current text changed (finalized) before
        input changed event...
        """
        self.__selectionChangedEmit = method

    def __getCleanItems(self):
        return []

    def setCleanItemsFunction(self, function):
        """
        Sets the function to get the list of strings to populate the model

        function (function): function to return a list of strings to be shown
            to the user
        """
        self.__getCleanItems = function

    def getCleanItems(self):
        """
        Returns a list of strings based off of the function set with
        setCleanItemsFunction
        """
        return self.__getCleanItems()

    """ EVENTS """

    def userFinishedEditing(self):
        is_input_valid = self.isUserInputValid()
        if is_input_valid:
            self.__selectionChangedEmit()
            self.previous_text = self.currentText()
            #self.userFinishedEditingEvent(self.currentText())
        else:
            self.setCurrentIndexToText(self.previous_text)

    def resizeEvent(self, event):
        self.setupStyleSheet()
        return QComboBox.resizeEvent(self, event)

    def event(self, event, *args, **kwargs):
        """
        Registering key presses in here as for some reason
        they don't work in the keyPressEvent method...
        """
        if event.type() == QEvent.KeyPress:
            # tab
            if event.key() == Qt.Key_Tab:
                self.next_completion()
                return True

            # shift tab
            elif event.key() == Qt.Key_Tab + 1:
                self.previous_completion()
                return True

            # enter pressed
            elif event.key() in [Qt.Key_Return, Qt.Key_Enter, Qt.Key_Down]:
                self.__selectionChangedEmit()

        elif event.type() == QEvent.MouseButtonRelease:
            self.completer.setPopup(self.view())

        return QComboBox.event(self, event, *args, **kwargs)

    """ PROPERTIES """

    def getExistsFlag(self):
        return self._exists

    def setExistsFlag(self, exists):
        self._exists = exists

    def getItemList(self):
        return self._item_list

    def setItemList(self, item_list):
        if self.previous_text == '':
            item_list.insert(0, '')
        self._item_list = item_list

    @property
    def previous_text(self):
        return self._previous_text

    @previous_text.setter
    def previous_text(self, previous_text):
        self._previous_text = previous_text