source: orange-bioinformatics/_bioinformatics/widgets/OWSelectGenes.py @ 1848:8870b3c7cf34

Revision 1848:8870b3c7cf34, 11.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 7 months ago (diff)

Remove already entered names from the completion model.

Line 
1import re
2import unicodedata
3
4from PyQt4.QtGui import (
5    QLabel, QWidget, QPlainTextEdit, QSyntaxHighlighter, QTextCharFormat,
6    QTextCursor, QCompleter, QStringListModel, QListView
7)
8
9from PyQt4.QtCore import Qt, pyqtSignal as Signal
10
11import Orange
12
13from Orange.OrangeWidgets.OWWidget import *
14from Orange.OrangeWidgets.OWItemModels import VariableListModel
15from Orange.OrangeWidgets import OWGUI
16
17
18NAME = "Select Genes"
19DESCRIPTION = "Select a specified subset of the input genes."
20ICON = "icons/SelectGenes.svg"
21
22INPUTS = [("Data", Orange.data.Table, "set_data")]
23OUTPUTS = [("Selected Data", Orange.data.Table)]
24
25
26class OWSelectGenes(OWWidget):
27
28    contextHandlers = {
29        "": DomainContextHandler(
30            "", ["geneIndex", "selection"]
31        )
32    }
33
34    settingsList = ["autoCommit"]
35
36    def __init__(self, parent=None, signalManager=None, title=NAME):
37        OWWidget.__init__(self, parent, signalManager, title,
38                          wantMainArea=False)
39
40        self.selection = []
41        self.geneIndex = None
42        self.autoCommit = False
43
44        self.loadSettings()
45
46        # Input variables that could contain names
47        self.variables = VariableListModel()
48        # All gene names from the input (in self.geneIndex column)
49        self.geneNames = []
50        # Output changed flag
51        self._changedFlag = False
52
53        box = OWGUI.widgetBox(self.controlArea, "Gene Attribute")
54        self.attrsCombo = OWGUI.comboBox(
55            box, self, "geneIndex",
56            callback=self._onGeneIndexChanged,
57            tooltip="Column with gene names"
58        )
59        self.attrsCombo.setModel(self.variables)
60
61        box = OWGUI.widgetBox(self.controlArea, "Gene Selection")
62        self.entryField = ListTextEdit(box)
63        self.entryField.setTabChangesFocus(True)
64        self.entryField.setToolTip("Enter selected gene names")
65        self.entryField.textChanged.connect(self._textChanged)
66
67        box.layout().addWidget(self.entryField)
68
69        completer = QCompleter(self)
70        completer.setCompletionMode(QCompleter.PopupCompletion)
71        completer.setCaseSensitivity(Qt.CaseInsensitive)
72        completer.popup().setAlternatingRowColors(True)
73        completer.setModel(QStringListModel([], self))
74
75        self.entryField.setCompleter(completer)
76
77        self.hightlighter = NameHighlight(self.entryField.document())
78
79        box = OWGUI.widgetBox(self.controlArea, "Output")
80
81        cb = OWGUI.checkBox(box, self, "autoCommit", "Auto commit")
82        button = OWGUI.button(box, self, "Commit", callback=self.commit)
83
84        OWGUI.setStopper(self, button, cb, "_changedFlag", self.commit)
85
86    def set_data(self, data):
87        """
88        Set the input data.
89        """
90        self.closeContext("")
91        self.warning()
92        self.data = data
93        if data is not None:
94            attrs = gene_candidates(data)
95            self.variables[:] = attrs
96            self.attrsCombo.setCurrentIndex(0)
97            self.geneIndex = 0
98            self.selection = []
99        else:
100            self.variables[:] = []
101            self.geneIndex = -1
102            self.warning(0, "No suitable columns for gene names.")
103
104        self._changedFlag = True
105        self._updateCompletionModel()
106
107        self.openContext("", data)
108
109        self.entryField.setPlainText(" ".join(self.selection))
110
111        self.commit()
112
113    @property
114    def geneVar(self):
115        if self.data is not None and self.geneIndex >= 0:
116            return self.variables[self.geneIndex]
117        else:
118            return None
119
120    def invalidateOutput(self):
121        if self.autoCommit:
122            self.commit()
123        else:
124            self._changedFlag = True
125
126    def commit(self):
127        gene = self.geneVar
128
129        if gene is not None:
130            selection = set(self.selection)
131
132            sel = [inst for inst in self.data
133                   if str(inst[gene]) in selection]
134
135            if sel:
136                data = Orange.data.Table(self.data.domain, sel)
137            else:
138                data = Orange.data.Table(self.data.domain)
139
140        else:
141            data = None
142
143        self.send("Selected Data", data)
144        self._changedFlag = False
145
146    def _updateCompletionModel(self):
147        var = self.geneVar
148        if var is not None:
149            names = [str(inst[var]) for inst in self.data
150                     if not inst[var].isSpecial()]
151        else:
152            names = []
153
154        self.geneNames = names
155        self.entryField.completer().model().setStringList(sorted(set(names)))
156        self.hightlighter.setNames(names)
157
158    def _onGeneIndexChanged(self):
159        self._updateCompletionModel()
160        self.invalidateOutput()
161
162    def _textChanged(self):
163        names = self.entryField.list()
164        selection = set(names).intersection(self.geneNames)
165        curr_selection = set(self.selection).intersection(self.geneNames)
166
167        if selection != curr_selection:
168            self.selection = names
169            self.invalidateOutput()
170
171            names = set(self.geneNames) - set(names)
172            self.entryField.completer().model().setStringList(sorted(names))
173
174
175def is_string(feature):
176    return isinstance(feature, Orange.feature.String)
177
178
179def domain_variables(domain):
180    vars = (domain.features +
181            domain.class_vars +
182            domain.getmetas().values())
183    return vars
184
185
186def gene_candidates(data):
187    vars = domain_variables(data.domain)
188    vars = filter(is_string, vars)
189    return vars
190
191
192class ListTextEdit(QPlainTextEdit):
193    listChanged = Signal()
194
195    def __init__(self, parent=None, **kwargs):
196        QPlainTextEdit.__init__(self, parent, **kwargs)
197
198        self._completer = None
199
200    def setCompleter(self, completer):
201        """
202        Set a completer for list items.
203        """
204        if self._completer is not None:
205            self._completer.setWidget(None)
206            if isinstance(self._completer, ListCompleter):
207                self._completer.activatedList.disconnect(self._insertCompletion)
208            else:
209                self._completer.activated.disconnect(self._insertCompletion)
210
211        self._completer = completer
212
213        if self._completer:
214            self._completer.setWidget(self)
215            if isinstance(self._completer, ListCompleter):
216                self._completer.activatedList.connect(self._insertCompletion)
217            else:
218                self._completer.activated.connect(self._insertCompletion)
219
220    def completer(self):
221        """
222        Return the completer.
223        """
224        return self._completer
225
226    def setList(self, list):
227        text = " ".join(list)
228        self.setPlainText(text)
229
230    def list(self):
231        return [name for name in unicode(self.toPlainText()).split()
232                if name.strip()]
233
234    def keyPressEvent(self, event):
235        if self._completer.popup().isVisible():
236            if event.key() in [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape,
237                               Qt.Key_Tab, Qt.Key_Backtab]:
238                # These need to propagate to the completer.
239                event.ignore()
240                return
241
242        QPlainTextEdit.keyPressEvent(self, event)
243
244        if not len(event.text()) or not is_printable(unicode(event.text())[0]):
245            return
246
247        text = unicode(self.toPlainText())
248        pos = self.textCursor().position()
249
250        if pos == len(text) or not(text[pos].strip()):
251            # At end of text or whitespace
252            # TODO: Match all whitespace characters.
253            start_sp = text.rfind(" ", 0, pos) + 1
254            start_n = text.rfind("\n", 0, pos) + 1
255            start = max(start_sp, start_n)
256
257            prefix = text[start:pos]
258
259            if prefix:
260                if self._completer.completionPrefix() != prefix:
261                    self._completer.setCompletionPrefix(text[start:pos])
262
263                rect = self.cursorRect()
264                popup = self._completer.popup()
265                if popup.isVisible():
266                    rect.setWidth(popup.width())
267                else:
268                    rect.setWidth(popup.sizeHintForColumn(0) +
269                                  popup.verticalScrollBar().sizeHint().width())
270
271                self._completer.complete(rect)
272
273            elif self._completer.popup().isVisible():
274                self._completer.popup().hide()
275
276    def _insertCompletion(self, item):
277        completion = unicode(item)
278        prefix = self._completer.completionPrefix()
279
280        cursor = self.textCursor()
281        # Replace the prefix with the full completion (correcting for the
282        # case-insensitive search).
283        cursor.setPosition(cursor.position() - len(prefix),
284                           QTextCursor.KeepAnchor)
285
286        cursor.insertText(completion + " ")
287
288
289class NameHighlight(QSyntaxHighlighter):
290    def __init__(self, parent=None, **kwargs):
291        super(NameHighlight, self).__init__(parent)
292
293        self._names = set()
294
295        self._format = QTextCharFormat()
296        self._format.setForeground(Qt.blue)
297
298        self._unrecognized_format = QTextCharFormat()
299#         self._unrecognized_format.setFontStrikeOut(True)
300
301    def setNames(self, names):
302        self._names = set(names)
303        self.rehighlight()
304
305    def names(self):
306        return set(self._names)
307
308    def highlightBlock(self, text):
309        text = unicode(text)
310        pattern = re.compile(r"\S+")
311        for match in pattern.finditer(text):
312            name = text[match.start(): match.end()]
313            match_len = match.end() - match.start()
314
315            if not name.strip():
316                continue
317
318            if name in self._names:
319                format = self._format
320            else:
321                format = self._unrecognized_format
322
323            self.setFormat(match.start(), match_len, format)
324
325
326def toString(variant):
327    if isinstance(variant, QVariant):
328        return unicode(variant.toString())
329    else:
330        return unicode(variant)
331
332
333class ListCompleter(QCompleter):
334    activatedList = Signal(list)
335
336    def __init__(self, *args, **kwargs):
337        QCompleter.__init__(self, *args, **kwargs)
338
339        popup = QListView()
340        popup.setSelectionMode(QListView.ExtendedSelection)
341        self.setPopup(popup)
342
343    def setPopup(self, popup):
344        QCompleter.setPopup(self, popup)
345
346        popup.selectionModel().selectionChanged.connect(
347            self._completionSelected)
348
349    def _completionSelected(self, selected, deselected):
350        selection = self.popup().selectionModel().selection()
351        indexes = selection.indexes()
352
353        items = [toString(index.data(self.completionRole()))
354                 for index in indexes]
355
356        self.activatedList.emit(items)
357
358
359# All control character categories.
360_control = set(["Cc", "Cf", "Cs", "Co", "Cn"])
361
362
363def is_printable(unichar):
364    """
365    Return True if the unicode character `unichar` is a printable character.
366    """
367    return unicodedata.category(unichar) not in _control
368
369
370def test():
371    app = QApplication([])
372    w = OWSelectGenes()
373    data = Orange.data.Table("brown-selected")
374    w.set_data(data)
375    w.show()
376    app.exec_()
377    w.deleteLater()
378    del w
379    app.processEvents()
380
381if __name__ == "__main__":
382    test()
Note: See TracBrowser for help on using the repository browser.