Changeset 1853:124130167b7d in orange-bioinformatics


Ignore:
Timestamp:
10/02/13 15:51:14 (7 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

Added inline completion of the common prefix.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • _bioinformatics/widgets/OWSelectGenes.py

    r1851 r1853  
    11import re 
    22import unicodedata 
    3 from collections import defaultdict 
     3from collections import defaultdict, namedtuple 
    44 
    55from contextlib import contextmanager 
     
    5454        # Output changed flag 
    5555        self._changedFlag = False 
     56        self.data = None 
    5657 
    5758        box = OWGUI.widgetBox(self.controlArea, "Gene Attribute") 
     
    6768        self.entryField.setTabChangesFocus(True) 
    6869        self.entryField.setToolTip("Enter selected gene names") 
    69         self.entryField.textChanged.connect(self._textChanged) 
     70        self.entryField.itemsChanged.connect(self._itemsChanged) 
    7071 
    7172        box.layout().addWidget(self.entryField) 
     
    176177        self.invalidateOutput() 
    177178 
    178     def _textChanged(self): 
    179         names = self.entryField.list() 
     179    def _itemsChanged(self, names): 
    180180        selection = set(names).intersection(self.geneNames) 
    181181        curr_selection = set(self.selection).intersection(self.geneNames) 
     
    206206 
    207207 
     208_CompletionState = namedtuple( 
     209    "_CompletionState", 
     210    ["start",  # completion prefix start position 
     211     "pos",  # cursor position 
     212     "anchor"]  # anchor position (inline completion end) 
     213) 
     214 
     215 
    208216class ListTextEdit(QPlainTextEdit): 
     217    itemsChanged = Signal(list) 
     218 
    209219    def __init__(self, parent=None, **kwargs): 
    210220        QPlainTextEdit.__init__(self, parent, **kwargs) 
    211221 
     222        self._items = None 
    212223        self._completer = None 
     224        self._completionState = _CompletionState(-1, -1, -1) 
     225 
     226        self.cursorPositionChanged.connect(self._cursorPositionChanged) 
     227        self.textChanged.connect(self._textChanged) 
    213228 
    214229    def setCompleter(self, completer): 
     
    232247        return self._completer 
    233248 
    234     def setList(self, list): 
    235         text = " ".join(list) 
     249    def setItems(self, items): 
     250        text = " ".join(items) 
    236251        self.setPlainText(text) 
    237252 
    238     def list(self): 
    239         return [name for name in unicode(self.toPlainText()).split() 
    240                 if name.strip()] 
     253    def items(self): 
     254        if self._items is None: 
     255            self._items = self._getItems() 
     256        return self._items 
    241257 
    242258    def keyPressEvent(self, event): 
     259        # TODO: in Qt 4.8 QPlainTextEdit uses inputMethodEvent for 
     260        # non-ascii input 
     261 
    243262        if self._completer.popup().isVisible(): 
    244263            if event.key() in [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, 
     
    254273 
    255274        text = unicode(self.toPlainText()) 
    256         pos = self.textCursor().position() 
     275        cursor = self.textCursor() 
     276        pos = cursor.position() 
    257277 
    258278        if pos == len(text) or not(text[pos].strip()): 
    259             # At end of text or whitespace 
    260             # TODO: Match all whitespace characters. 
    261             start_sp = text.rfind(" ", 0, pos) + 1 
    262             start_n = text.rfind("\n", 0, pos) + 1 
    263             start = max(start_sp, start_n) 
     279            # cursor is at end of text or whitespace 
     280            # find the beginning of the current word 
     281            whitespace = " \t\n\r\f\v" 
     282            start = max([text.rfind(c, 0, pos) for c in whitespace]) + 1 
    264283 
    265284            prefix = text[start:pos] 
     
    277296                                  popup.verticalScrollBar().sizeHint().width()) 
    278297 
     298                # Popup the completer list 
    279299                self._completer.complete(rect) 
    280300 
     301                # Inline completion of a common prefix 
     302                inline = self._commonCompletionPrefix() 
     303                inline = inline[len(prefix):] 
     304 
     305                self._completionState = \ 
     306                    _CompletionState(start, pos, pos + len(inline)) 
     307 
     308                cursor.insertText(inline) 
     309                cursor.setPosition(pos, QTextCursor.KeepAnchor) 
     310                self.setTextCursor(cursor) 
     311 
    281312            elif self._completer.popup().isVisible(): 
    282                 self._completer.popup().hide() 
     313                self._stopCompletion() 
     314 
     315    def _cursorPositionChanged(self): 
     316        cursor = self.textCursor() 
     317        pos = cursor.position() 
     318        start, _, _ = self._completionState 
     319 
     320        if start == -1: 
     321            # completion not in progress 
     322            return 
     323 
     324        if pos <= start: 
     325            # cursor moved before the start of the prefix 
     326            self._stopCompletion() 
     327            return 
     328 
     329        text = unicode(self.toPlainText()) 
     330        # Find the end of the word started by completion prefix 
     331        word_end = len(text) 
     332        for i in range(start, len(text)): 
     333            if text[i] in " \t\n\r\f\v": 
     334                word_end = i 
     335                break 
     336 
     337        if pos > word_end: 
     338            # cursor moved past the word boundary 
     339            self._stopCompletion() 
     340 
     341        # TODO: Update the prefix when moving the cursor 
     342        # inside the word 
    283343 
    284344    def _insertCompletion(self, item): 
     
    288348            completion = unicode(item) 
    289349 
    290         prefix = self._completer.completionPrefix() 
     350        start, _, end = self._completionState 
     351 
     352        self._stopCompletion() 
    291353 
    292354        cursor = self.textCursor() 
    293         # Replace the prefix with the full completion (correcting for the 
    294         # case-insensitive search). 
    295         cursor.setPosition(cursor.position() - len(prefix), 
    296                           QTextCursor.KeepAnchor) 
     355        # Replace the prefix+inline with the full completion 
     356        # (correcting for the case-insensitive search). 
     357        cursor.setPosition(min(end, self.document().characterCount())) 
     358        cursor.setPosition(start, QTextCursor.KeepAnchor) 
    297359 
    298360        cursor.insertText(completion + " ") 
     361 
     362    def _commonCompletionPrefix(self): 
     363        model = self._completer.completionModel() 
     364        column = self._completer.completionColumn() 
     365        role = self._completer.completionRole() 
     366        items = [toString(model.index(i, column).data(role)) 
     367                 for i in range(model.rowCount())] 
     368 
     369        if not items: 
     370            return "" 
     371 
     372        first = min(items) 
     373        last = max(items) 
     374        for i, c in enumerate(first): 
     375            if c != last[i]: 
     376                return first[:i] 
     377 
     378        return first 
     379 
     380    def _stopCompletion(self): 
     381        self._completionState = _CompletionState(-1, -1, -1) 
     382        if self._completer.popup().isVisible(): 
     383            self._completer.popup().hide() 
     384 
     385    def _textChanged(self): 
     386        items = self._getItems() 
     387        if self._items != items: 
     388            self._items = items 
     389            self.itemsChanged.emit(items) 
     390 
     391    def _getItems(self): 
     392        text = unicode(self.toPlainText()) 
     393        if self._completionState[0] != -1: 
     394            # Remove the inline completion text from the text 
     395            _, pos, end = self._completionState 
     396            text = text[:pos] + text[end:] 
     397        return [item for item in text.split() if item.strip()] 
    299398 
    300399 
    301400class NameHighlight(QSyntaxHighlighter): 
    302401    def __init__(self, parent=None, **kwargs): 
    303         super(NameHighlight, self).__init__(parent) 
     402        super(NameHighlight, self).__init__(parent, **kwargs) 
    304403 
    305404        self._names = set() 
     
    397496            self.popup().hide() 
    398497 
    399         self.activated.emit(items) 
     498        if items: 
     499            self.activated.emit(items) 
    400500 
    401501 
Note: See TracChangeset for help on using the changeset viewer.