source: orange-bioinformatics/orangecontrib/bio/widgets/OWSelectGenes.py @ 1925:7c31d7eb40ec

Revision 1925:7c31d7eb40ec, 50.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 5 months ago (diff)

Added "Import from text file" action.

Line 
1import sys
2import os
3import re
4import unicodedata
5import operator
6from collections import defaultdict, namedtuple
7from operator import itemgetter
8from xml.sax.saxutils import escape
9
10from contextlib import contextmanager
11
12from PyQt4.QtGui import (
13    QFrame, QHBoxLayout, QPlainTextEdit, QSyntaxHighlighter, QTextCharFormat,
14    QTextCursor, QCompleter, QStandardItemModel, QSortFilterProxyModel,
15    QStandardItem, QListView, QTreeView, QStyle, QStyledItemDelegate,
16    QStyleOptionViewItemV4, QPalette, QColor, QApplication, QAction,
17    QToolButton, QItemSelectionModel, QPlainTextDocumentLayout, QTextDocument,
18    QRadioButton, QButtonGroup, QStyleOptionButton, QMenu, QDialog,
19    QStackedWidget, QComboBox, QFileDialog
20)
21
22from PyQt4.QtCore import Qt, QEvent, QVariant, QThread
23from PyQt4.QtCore import pyqtSignal as Signal
24
25import Orange
26
27from Orange.OrangeWidgets.OWWidget import (
28    OWWidget, DomainContextHandler, Default
29)
30
31from Orange.OrangeWidgets.OWConcurrent import (
32    ThreadExecutor, Task, methodinvoke
33)
34
35from Orange.OrangeWidgets.OWItemModels import VariableListModel
36from Orange.OrangeWidgets import OWGUI
37from Orange.orng.orngDataCaching import data_hints
38from Orange.bio import obiGene as geneinfo
39from Orange.bio import obiTaxonomy as taxonomy
40
41
42NAME = "Select Genes"
43DESCRIPTION = "Select a specified subset of the input genes."
44ICON = "icons/SelectGenes.svg"
45
46INPUTS = [("Data", Orange.data.Table, "setData", Default),
47          ("Gene Subset", Orange.data.Table, "setGeneSubset")]
48
49OUTPUTS = [("Selected Data", Orange.data.Table)]
50
51
52def toString(variant):
53    if isinstance(variant, QVariant):
54        return unicode(variant.toString())
55    else:
56        return unicode(variant)
57
58
59def toBool(variant):
60    if isinstance(variant, QVariant):
61        return bool(variant.toPyObject())
62    else:
63        return bool(variant)
64
65
66def toPyObject(variant):
67    if isinstance(variant, QVariant):
68        return variant.toPyObject()
69    else:
70        return variant
71
72
73class SaveSlot(QStandardItem):
74    ModifiedRole = next(OWGUI.OrangeUserRole)
75
76    def __init__(self, name, savedata=None, modified=False):
77        super(SaveSlot, self).__init__(name)
78
79        self.savedata = savedata
80        self.modified = modified
81        self.document = None
82
83    @property
84    def name(self):
85        return unicode(self.text())
86
87    @property
88    def modified(self):
89        return toBool(self.data(SaveSlot.ModifiedRole))
90
91    @modified.setter
92    def modified(self, state):
93        self.setData(bool(state), SaveSlot.ModifiedRole)
94
95
96class SavedSlotDelegate(QStyledItemDelegate):
97
98    def paint(self, painter, option, index):
99        option = QStyleOptionViewItemV4(option)
100        self.initStyleOption(option, index)
101
102        modified = toBool(index.data(SaveSlot.ModifiedRole))
103        if modified:
104            option.palette.setColor(QPalette.Text, QColor(Qt.red))
105            option.palette.setColor(QPalette.Highlight, QColor(Qt.darkRed))
106            option.text = "*" + option.text
107
108        if option.widget:
109            widget = option.widget
110            style = widget.style()
111        else:
112            widget = None
113            style = QApplication.style()
114
115        style.drawControl(QStyle.CE_ItemViewItem, option, painter, widget)
116
117
118def radio_indicator_width(button):
119    button.ensurePolished()
120    style = button.style()
121    option = QStyleOptionButton()
122    button.initStyleOption(option)
123
124    w = style.pixelMetric(QStyle.PM_ExclusiveIndicatorWidth, option, button)
125    return w
126
127
128class OWSelectGenes(OWWidget):
129
130    contextHandlers = {
131        "": DomainContextHandler(
132            "", ["geneIndex", "taxid"]
133        ),
134        "subset": DomainContextHandler(
135            "subset", ["subsetGeneIndex"]
136        )
137    }
138
139    settingsList = ["autoCommit", "preserveOrder", "savedSelections",
140                    "selectedSelectionIndex", "selectedSource",
141                    "completeOnSymbols"]
142
143    SelectInput, SelectCustom = 0, 1
144    CompletionRole = Qt.UserRole + 1
145
146    def __init__(self, parent=None, signalManager=None, title=NAME):
147        OWWidget.__init__(self, parent, signalManager, title,
148                          wantMainArea=False)
149
150        self.geneIndex = None
151        self.taxid = None
152        self.autoCommit = False
153        self.preserveOrder = True
154        self.savedSelections = [
155            ("Example", ["MRE11A", "RAD51", "MLH1", "MSH2", "DMC1"])
156        ]
157
158        self.selectedSelectionIndex = -1
159        self.selectedSource = OWSelectGenes.SelectCustom
160        self.completeOnSymbols = True
161
162        self.loadSettings()
163
164        # Input variables that could contain names
165        self.variables = VariableListModel()
166        # All gene names and their symbols
167        self.geneNames = []
168        # A list of (name, info) where name is from the input
169        # (geneVar column) and info is the NCBIGeneInfo object if available
170        # or None
171        self.genes = []
172        # Output changed flag
173        self._changedFlag = False
174        # Current gene names
175        self.selection = []
176        # Input data
177        self.data = None
178        self.subsetData = None
179        # Input variables that could contain gene names from "Gene Subset"
180        self.subsetVariables = VariableListModel()
181        # Selected subset variable index
182        self.subsetGeneIndex = -1
183        self.organisms = []
184        self.taxidindex = {}
185        self.geneinfo = (None, None)
186        self._executor = ThreadExecutor()
187
188        self._infotask = None
189
190        box = OWGUI.widgetBox(self.controlArea, "Gene Attribute")
191        box.setToolTip("Column with gene names")
192        self.attrsCombo = OWGUI.comboBox(
193            box, self, "geneIndex",
194            callback=self._onGeneIndexChanged,
195        )
196        self.attrsCombo.setModel(self.variables)
197
198        box = OWGUI.widgetBox(self.controlArea, "Gene Selection")
199
200        button1 = QRadioButton("Select genes from 'Gene Subset' input")
201        button2 = QRadioButton("Select specified genes")
202
203        box.layout().addWidget(button1)
204
205        # Subset gene variable selection
206        self.subsetbox = OWGUI.widgetBox(box, None)
207        offset = radio_indicator_width(button1)
208        self.subsetbox.layout().setContentsMargins(offset, 0, 0, 0)
209        self.subsetbox.setEnabled(
210            self.selectedSource == OWSelectGenes.SelectInput)
211
212        box1 = OWGUI.widgetBox(self.subsetbox, "Gene Attribute", flat=True)
213        self.subsetVarCombo = OWGUI.comboBox(
214            box1, self, "subsetGeneIndex",
215            callback=self._onSubsetGeneIndexChanged
216        )
217        self.subsetVarCombo.setModel(self.subsetVariables)
218        self.subsetVarCombo.setToolTip(
219            "Column with gene names in the 'Gene Subset' input"
220        )
221        OWGUI.button(box1, self, "Copy genes to saved subsets",
222                     callback=self.copyToSaved)
223
224        OWGUI.button(box1, self, "Append genes to current saved selection",
225                     callback=self.appendToSelected)
226
227        box.layout().addWidget(button2)
228
229        self.selectedSourceButtons = group = QButtonGroup(box)
230        group.addButton(button1, OWSelectGenes.SelectInput)
231        group.addButton(button2, OWSelectGenes.SelectCustom)
232        group.buttonClicked[int].connect(self._selectionSourceChanged)
233
234        if self.selectedSource == OWSelectGenes.SelectInput:
235            button1.setChecked(True)
236        else:
237            button2.setChecked(True)
238
239        self.entrybox = OWGUI.widgetBox(box, None)
240        offset = radio_indicator_width(button2)
241        self.entrybox.layout().setContentsMargins(offset, 0, 0, 0)
242
243        self.entrybox.setEnabled(
244            self.selectedSource == OWSelectGenes.SelectCustom)
245
246        box = OWGUI.widgetBox(self.entrybox, "Select Genes", flat=True)
247        box.setToolTip("Enter gene names to select")
248        box.layout().setSpacing(1)
249
250        self.entryField = ListTextEdit(box)
251        self.entryField.setTabChangesFocus(True)
252        self.entryField.setDocument(self._createDocument())
253        self.entryField.itemsChanged.connect(self._onItemsChanged)
254
255        box.layout().addWidget(self.entryField)
256
257        completer = ListCompleter()
258        completer.setCompletionMode(QCompleter.PopupCompletion)
259        completer.setCompletionRole(
260            self.CompletionRole if self.completeOnSymbols else Qt.DisplayRole
261        )
262        completer.setCaseSensitivity(Qt.CaseInsensitive)
263        completer.setMaxVisibleItems(10)
264
265        popup = QTreeView()
266        popup.setSelectionMode(QTreeView.ExtendedSelection)
267        popup.setEditTriggers(QTreeView.NoEditTriggers)
268        popup.setRootIsDecorated(False)
269        popup.setAlternatingRowColors(True)
270        popup.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
271        popup.setMaximumWidth(500)
272        popup.header().setStretchLastSection(False)
273        popup.header().hide()
274
275        completer.setPopup(popup)
276        completer.setModel(SetFilterProxyModel(self))
277
278        self.entryField.setCompleter(completer)
279
280        toolbar = QFrame()
281        toolbar.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
282        layout = QHBoxLayout()
283        layout.setContentsMargins(0, 0, 0, 0)
284        layout.setSpacing(1)
285
286        addaction = QAction("+", self)
287        addmenu = QMenu()
288
289        action = addmenu.addAction("Import names from gene sets...")
290        action.triggered.connect(self.importGeneSet)
291
292        action = addmenu.addAction("Import names from text file...")
293        action.triggered.connect(self.importFromFile)
294
295        addaction.setMenu(addmenu)
296
297        def button(action, popupMode=None):
298            b = QToolButton()
299            b.setDefaultAction(action)
300            if popupMode is not None:
301                b.setPopupMode(QToolButton.InstantPopup)
302            return b
303
304        b = button(addaction, popupMode=QToolButton.InstantPopup)
305        layout.addWidget(b)
306
307        moreaction = QAction("More", self)
308
309        moremenu = QMenu()
310        self.completeOnSymbolsAction = QAction(
311            "Complete on gene symbol names", self,
312            toolTip="Use symbol names for auto completion.",
313            checkable=True,
314            checked=self.completeOnSymbols
315        )
316
317        self.completeOnSymbolsAction.toggled[bool].connect(
318            self._onToggleSymbolCompletion
319        )
320
321        moremenu.addAction(self.completeOnSymbolsAction)
322
323        self.translateAction = QAction(
324            "Translate all names to official symbol names", self,
325            enabled=False
326        )
327        self.translateAction.triggered.connect(self._onTranslate)
328
329        moremenu.addAction(self.translateAction)
330        moreaction.setMenu(moremenu)
331
332        b = button(moreaction, popupMode=QToolButton.InstantPopup)
333        layout.addWidget(b)
334
335        self.organismsCombo = ComboBox(focusPolicy=Qt.StrongFocus)
336        self.organismsCombo.addItem("...")
337        self.organismsCombo.model().item(0).setEnabled(False)
338        self.organismsCombo.setMinimumWidth(200)
339        self.organismsCombo.activated[int].connect(self._onOrganismActivated)
340
341        layout.addSpacing(10)
342        layout.addWidget(self.organismsCombo)
343        self.pb = QProgressBar()
344        self.pb.hide()
345        self.pbstack = QStackedWidget()
346        self.pbstack.addWidget(self.pb)
347
348        layout.addSpacing(10)
349        layout.addWidget(self.pbstack, 0, Qt.AlignRight | Qt.AlignVCenter)
350        toolbar.setLayout(layout)
351
352        box.layout().addWidget(toolbar)
353
354        box = OWGUI.widgetBox(self.entrybox, "Saved Selections", flat=True)
355        box.setToolTip("Save/Select/Update saved gene selections")
356        box.layout().setSpacing(1)
357
358        self.selectionsModel = QStandardItemModel()
359        self.selectionsView = QListView()
360        self.selectionsView.setAlternatingRowColors(True)
361        self.selectionsView.setModel(self.selectionsModel)
362        self.selectionsView.setItemDelegate(SavedSlotDelegate(self))
363        self.selectionsView.selectionModel().selectionChanged.connect(
364            self._onSelectedSaveSlotChanged
365        )
366
367        box.layout().addWidget(self.selectionsView)
368
369        self.actionSave = QAction(
370            "Save", self,
371            toolTip="Save/Update the current selection")
372
373        self.actionAdd = QAction(
374            "+", self,
375            toolTip="Create a new saved selection")
376
377        self.actionRemove = QAction(
378            u"\u2212", self,
379            toolTip="Delete the current saved selection")
380
381        toolbar = QFrame()
382        layout = QHBoxLayout()
383        layout.setContentsMargins(0, 0, 0, 0)
384        layout.setSpacing(1)
385
386        b = button(self.actionAdd)
387        layout.addWidget(b)
388
389        b = button(self.actionRemove)
390        layout.addWidget(b)
391
392        b = button(self.actionSave)
393        layout.addWidget(b)
394
395        layout.addStretch(100)
396        toolbar.setLayout(layout)
397
398        box.layout().addWidget(toolbar)
399
400        self.actionSave.triggered.connect(self.saveSelection)
401        self.actionAdd.triggered.connect(self.addSelection)
402        self.actionRemove.triggered.connect(self.removeSelection)
403
404        box = OWGUI.widgetBox(self.controlArea, "Output")
405        OWGUI.checkBox(box, self, "preserveOrder", "Preserve input order",
406                       tooltip="Preserve the order of the input data "
407                               "instances.",
408                       callback=self.invalidateOutput)
409        cb = OWGUI.checkBox(box, self, "autoCommit", "Auto commit")
410        button = OWGUI.button(box, self, "Commit", callback=self.commit)
411
412        OWGUI.setStopper(self, button, cb, "_changedFlag", self.commit)
413
414        # Gene set import dialog (initialized when required)
415        self._genesetDialog = None
416
417        # restore saved selections model.
418        for name, names in self.savedSelections:
419            item = SaveSlot(name, names)
420            self.selectionsModel.appendRow([item])
421
422        if self.selectedSelectionIndex != -1:
423            self.selectionsView.selectionModel().select(
424                self.selectionsModel.index(self.selectedSelectionIndex, 0),
425                QItemSelectionModel.Select
426            )
427
428        self._updateActions()
429
430        self._inittask = Task(
431            function=lambda:
432                [(taxid, taxonomy.name(taxid))
433                  for taxid in taxonomy.common_taxids()])
434
435        self._inittask.finished.connect(self._onInit)
436        self._executor.submit(self._inittask)
437        self.pb.show()
438        self.pb.setRange(0, 0)
439
440    def _onInit(self):
441        self.organisms = self._inittask.result()
442        self.organismsCombo.clear()
443        self.organismsCombo.addItems([name for _, name in self.organisms])
444
445        self.taxidindex = \
446            {taxid: i for i, (taxid, _) in enumerate(self.organisms)}
447
448        self.organismsCombo.setCurrentIndex(
449            self.taxidindex.get(self.taxid, -1))
450
451        self.pb.hide()
452
453    def _updateGeneInfo(self):
454        self.geneinfo = (self.taxid, None)
455        self.translateAction.setEnabled(False)
456
457        if self._infotask:
458            # Try to cancel existing pending task
459            self._infotask.future().cancel()
460            self._infotask.resultReady.disconnect(self._onGeneInfoReady)
461            self._infotask.exceptionReady.disconnect(self._onGeneInfoError)
462
463        def ncbi_gene_info(taxid=self.taxid):
464            try:
465                return (taxid, geneinfo.NCBIGeneInfo(taxid))
466            except BaseException:
467                sys.excepthook(*sys.exc_info())
468                raise
469
470        task = Task(function=ncbi_gene_info)
471        task.resultReady.connect(self._onGeneInfoReady)
472        task.exceptionReady.connect(self._onGeneInfoError)
473        self._infotask = task
474        self._executor.submit(task)
475        self.pb.show()
476
477    def _onOrganismActivated(self, index):
478        try:
479            taxid, _ = self.organisms[index]
480        except IndexError:
481            pass
482        else:
483            if taxid != self.taxid:
484                self.taxid = taxid
485                self._updateGeneInfo()
486
487    def setData(self, data):
488        """
489        Set the input data.
490        """
491        self.closeContext("")
492        self.warning(0)
493        self.data = data
494        if data is not None:
495            attrs = gene_candidates(data)
496            self.variables[:] = attrs
497            self.attrsCombo.setCurrentIndex(0)
498            if attrs:
499                self.geneIndex = 0
500            else:
501                self.geneIndex = -1
502                self.warning(0, "No suitable columns for gene names.")
503        else:
504            self.variables[:] = []
505            self.geneIndex = -1
506
507        self._changedFlag = True
508
509        oldtaxid = self.taxid
510        if data is not None:
511            self.taxid = data_hints.get_hint(data, "taxid", None)
512        else:
513            self.taxid = None
514
515        self.openContext("", data)
516
517        if self.taxid is None:
518            self.geneinfo = (None, None)
519
520        if oldtaxid != self.taxid:
521            self.organismsCombo.setCurrentIndex(
522                self.taxidindex.get(self.taxid, -1))
523
524            if self.taxid in self.taxidindex:
525                self._updateGeneInfo()
526
527        self._updateCompletionModel()
528
529        self.commit()
530
531    def setGeneSubset(self, data):
532        """
533        Set the gene subset input.
534        """
535        self.closeContext("subset")
536        self.warning(1)
537        self.subsetData = data
538        if data is not None:
539            variables = gene_candidates(data)
540            self.subsetVariables[:] = variables
541            self.subsetVarCombo.setCurrentIndex(0)
542            if variables:
543                self.subsetGeneIndex = 0
544            else:
545                self.subsetGeneIndex = -1
546                self.warning(1, "No suitable column for subset gene names.")
547        else:
548            self.subsetVariables[:] = []
549            self.subsetGeneIndex = -1
550
551        self.openContext("subset", data)
552
553        if self.selectedSource == OWSelectGenes.SelectInput:
554            self.commit()
555
556    @property
557    def geneVar(self):
558        """
559        Current gene attribute or None if none available.
560        """
561        index = self.attrsCombo.currentIndex()
562        if self.data is not None and index >= 0:
563            return self.variables[index]
564        else:
565            return None
566
567    @property
568    def subsetGeneVar(self):
569        """
570        Current subset gene attribute or None if not available.
571        """
572        index = self.subsetVarCombo.currentIndex()
573        if self.subsetData is not None and index >= 0:
574            return self.subsetVariables[index]
575        else:
576            return None
577
578    def invalidateOutput(self):
579        if self.autoCommit:
580            self.commit()
581        else:
582            self._changedFlag = True
583
584    def selectedGenes(self):
585        """
586        Return the names of the current selected genes.
587        """
588        selection = []
589        if self.selectedSource == OWSelectGenes.SelectInput:
590            var = self.subsetGeneVar
591            if var is not None:
592                values = [inst[var] for inst in self.subsetData]
593                selection = [str(val) for val in values
594                             if not val.is_special()]
595        else:
596            selection = self.selection
597        return selection
598
599    def commit(self):
600        """
601        Send the selected data subset to the output.
602        """
603        selection = self.selectedGenes()
604
605        if self.geneinfo[1] is not None:
606            backmap = dict((info.symbol, name) for name, info in self.genes
607                           if info is not None)
608            names = set([name for name, _ in self.genes])
609            selection = [backmap.get(name, name) for name in selection
610                         and name not in names]
611
612        if self.geneVar is not None:
613            data = select_by_genes(self.data, self.geneVar,
614                                   gene_list=selection,
615                                   preserve_order=self.preserveOrder)
616        else:
617            data = None
618
619        self.send("Selected Data", data)
620        self._changedFlag = False
621
622    def setSelectionSource(self, source):
623        if self.selectedSource != source:
624            self.selectedSource = source
625            self.subsetbox.setEnabled(source == OWSelectGenes.SelectInput)
626            self.entrybox.setEnabled(source == OWSelectGenes.SelectCustom)
627            b = self.selectedSourceButtons.button(source)
628            b.setChecked(True)
629
630    def _selectionSourceChanged(self, source):
631        if self.selectedSource != source:
632            self.selectedSource = source
633            self.subsetbox.setEnabled(source == OWSelectGenes.SelectInput)
634            self.entrybox.setEnabled(source == OWSelectGenes.SelectCustom)
635            self.invalidateOutput()
636
637    def _updateCompletionModel(self):
638        var = self.geneVar
639        if var is not None:
640            names = [str(inst[var]) for inst in self.data
641                     if not inst[var].is_special()]
642        else:
643            names = []
644
645        infodict = {}
646
647        if self.geneinfo[1] is not None:
648            info = [(name, self.geneinfo[1].get_info(name, None))
649                    for name in names]
650            info = filter(itemgetter(1), info)
651            infodict = dict(info)
652
653        names = sorted(set(names))
654        genes = zip(names, map(infodict.get, names))
655
656        symbols = [info.symbol for _, info in genes if info is not None]
657
658        model = QStandardItemModel()
659
660        def make_row(name, info):
661            if info is not None:
662                col1 = QStandardItem(name)
663                col1.setData(info.symbol, OWSelectGenes.CompletionRole)
664                return [col1,
665                        QStandardItem(info.symbol),
666                        QStandardItem(info.description)]
667            else:
668                col1 = QStandardItem(name)
669                col1.setData(name, OWSelectGenes.CompletionRole)
670                return [col1]
671
672        for name, info in genes:
673            model.appendRow(make_row(name, info))
674
675        self.geneNames = sorted(set(names) | set(symbols))
676        self.genes = genes
677        self.entryField.completer().model().setSourceModel(model)
678        self.entryField.document().highlighter.setNames(names + symbols)
679
680        self._updatePopupSections()
681
682    def _onGeneIndexChanged(self):
683        self._updateCompletionModel()
684        self.invalidateOutput()
685
686    def _onSubsetGeneIndexChanged(self):
687        if self.selectedSource == OWSelectGenes.SelectInput:
688            self.invalidateOutput()
689
690    def _onItemsChanged(self, names):
691        selection = set(names).intersection(self.geneNames)
692        curr_selection = set(self.selection).intersection(self.geneNames)
693
694        self.selection = names
695
696        if selection != curr_selection:
697            self.invalidateOutput()
698            to_complete = sorted(set(self.geneNames) - set(names))
699            self.entryField.completer().model().setFilterFixedSet(to_complete)
700
701        item = self._selectedSaveSlot()
702        if item:
703            item.modified = item.savedata != names
704
705    def _onToggleSymbolCompletion(self, state):
706        completer = self.entryField.completer()
707        completer.setCompletionRole(
708            self.CompletionRole if state else Qt.DisplayRole
709        )
710        self.completeOnSymbols = state
711        self._updatePopupSections()
712
713    def _onTranslate(self):
714        if self.geneinfo[1] is not None:
715            items = self.entryField.items()
716            entries = map(self.geneinfo[1].get_info, items)
717            items = [info.symbol if info is not None else item
718                     for item, info in zip(items, entries)]
719            self.entryField.selectAll()
720            self.entryField.insertPlainText(" ".join(items))
721
722    def _onGeneInfoReady(self, geneinfo):
723        assert QThread.currentThread() is self.thread()
724        # Check if the gene info is for the correct (current requested)
725        # organism (we might receive a late response from a previous
726        # request)
727        if self.geneinfo[0] == geneinfo[0]:
728            self.geneinfo = geneinfo
729            self.translateAction.setEnabled(True)
730            self.pb.hide()
731            self._updateCompletionModel()
732
733    def _onGeneInfoError(self, exc):
734        self.error(0, str(exc))
735
736    def _updatePopupSections(self):
737        completer = self.entryField.completer()
738        popup = completer.popup()
739        assert isinstance(popup, QTreeView)
740        header = popup.header()
741        # The column in which the symbols should be
742        symbol_col = 0 if self.completeOnSymbols else 1
743        if symbol_col != header.sectionPosition(1):
744            header.moveSection(0, 1)
745
746    def _selectedSaveSlot(self):
747        """
748        Return the current selected saved selection slot.
749        """
750        indexes = self.selectionsView.selectedIndexes()
751        if indexes:
752            return self.selectionsModel.item(indexes[0].row())
753        else:
754            return None
755
756    def saveSelection(self):
757        """
758        Save (update) the items in the current selected selection.
759        """
760        item = self._selectedSaveSlot()
761        if item:
762            item.savedata = self.entryField.items()
763            item.modified = False
764
765    def copyToSaved(self):
766        """
767        Copy the current 'Gene Subset' names to saved selections.
768        """
769        if self.subsetGeneVar and \
770                self.selectedSource == OWSelectGenes.SelectInput:
771            names = self.selectedGenes()
772            item = SaveSlot("New selection")
773            item.savedata = names
774            self.selectionsModel.appendRow([item])
775            self.setSelectionSource(OWSelectGenes.SelectCustom)
776            self.selectionsView.setCurrentIndex(item.index())
777            self.selectionsView.edit(item.index())
778
779    def appendToSelected(self):
780        """
781        Append the current 'Gene Subset' names to 'Select Genes' entry field.
782        """
783        if self.subsetGeneVar and \
784                self.selectedSource == OWSelectGenes.SelectInput:
785            names = self.selectedGenes()
786            text = " ".join(names)
787            self.entryField.appendPlainText(text)
788            self.setSelectionSource(OWSelectGenes.SelectCustom)
789            self.entryField.setFocus()
790            self.entryField.moveCursor(QTextCursor.End)
791
792    def addSelection(self, name=None):
793        """
794        Add a new saved selection entry initialized by the current items.
795
796        The new slot will be selected.
797
798        """
799        item = SaveSlot(name or "New selection")
800        item.savedata = self.entryField.items()
801        self.selectionsModel.appendRow([item])
802        self.selectionsView.setCurrentIndex(item.index())
803
804        if not name:
805            self.selectionsView.edit(item.index())
806
807    def removeSelection(self):
808        """
809        Remove the current selected save slot.
810        """
811        item = self._selectedSaveSlot()
812        if item:
813            self.selectionsModel.removeRow(item.row())
814
815    def importGeneSet(self):
816        if self._genesetDialog is None:
817            self._genesetDialog = GeneSetDialog(
818                self, windowTitle="Import Gene Set Names")
819
820        dialog = self._genesetDialog
821
822        if self.taxid is not None:
823            dialog.setCurrentOrganism(self.taxid)
824
825        result = dialog.exec_()
826        if result == QDialog.Accepted:
827            gsets = dialog.selectedGeneSets()
828            genes = reduce(operator.ior, (gs.genes for gs in gsets), set())
829            text = " ".join(genes)
830            self.entryField.appendPlainText(text)
831            self.entryField.setFocus()
832            self.entryField.moveCursor(QTextCursor.End)
833
834    def importFromFile(self):
835        filename = QFileDialog.getOpenFileName(
836            self, "Open File", os.path.expanduser("~/"))
837
838        if filename:
839            filename = unicode(filename)
840            with open(filename, "rU") as f:
841                text = f.read()
842            self.entryField.appendPlainText(text)
843            self.entryField.setFocus()
844            self.entryField.moveCursor(QTextCursor.End)
845
846    def _onSelectedSaveSlotChanged(self):
847        item = self._selectedSaveSlot()
848        if item:
849            if not item.document:
850                item.document = self._createDocument()
851                if item.savedata:
852                    item.document.setPlainText(" ".join(item.savedata))
853
854            item.document.highlighter.setNames(self.geneNames)
855
856            self.entryField.setDocument(item.document)
857
858        self._updateActions()
859
860    def _createDocument(self):
861        """
862        Create and new QTextDocument instance for editing gene names.
863        """
864        doc = QTextDocument(self)
865        doc.setDocumentLayout(QPlainTextDocumentLayout(doc))
866        doc.highlighter = NameHighlight(doc)
867        return doc
868
869    def _updateActions(self):
870        """
871        Update the Save/remove action enabled state.
872        """
873        selected = bool(self._selectedSaveSlot())
874        self.actionRemove.setEnabled(selected)
875        self.actionSave.setEnabled(selected)
876
877    def getSettings(self, *args, **kwargs):
878        # copy the saved selections model back to widget settings.
879        selections = []
880        for i in range(self.selectionsModel.rowCount()):
881            item = self.selectionsModel.item(i)
882            selections.append((item.name, item.savedata))
883        self.savedSelections = selections
884
885        item = self._selectedSaveSlot()
886        if item is None:
887            self.selectedSelectionIndex = -1
888        else:
889            self.selectedSelectionIndex = item.row()
890
891        return OWWidget.getSettings(self, *args, **kwargs)
892
893    def sendReport(self):
894        report = []
895        if self.data is not None:
896            report.append("%i instances on input." % len(self.data))
897        else:
898            report.append("No data on input.")
899
900        if self.geneVar is not None:
901            report.append("Gene names taken from %r attribute." %
902                          escape(self.geneVar.name))
903
904        self.reportSection("Input")
905        self.startReportList()
906        for item in report:
907            self.addToReportList(item)
908        self.finishReportList()
909        report = []
910        selection = self.selectedGenes()
911        if self.selectedSource == OWSelectGenes.SelectInput:
912            self.reportRaw(
913                "<p>Gene Selection (from 'Gene Subset' input): %s</p>" %
914                escape(" ".join(selection))
915            )
916        else:
917            self.reportRaw(
918                "<p>Gene Selection: %s</p>" %
919                escape(" ".join(selection))
920            )
921        self.reportSettings(
922            "Settings",
923            [("Preserve order", self.preserveOrder)]
924        )
925
926    def onDeleteWidget(self):
927        self._inittask.future().cancel()
928
929        if self._infotask:
930            self._infotask.future().cancel()
931
932        self._executor.shutdown(wait=True)
933        OWWidget.onDeleteWidget(self)
934
935
936def is_string(feature):
937    return isinstance(feature, Orange.feature.String)
938
939
940def domain_variables(domain):
941    """
942    Return all feature descriptors from the domain.
943    """
944    vars = (domain.features +
945            domain.class_vars +
946            domain.getmetas().values())
947    return vars
948
949
950def gene_candidates(data):
951    """
952    Return features that could contain gene names.
953    """
954    vars = domain_variables(data.domain)
955    vars = filter(is_string, vars)
956    return vars
957
958
959def select_by_genes(data, gene_feature, gene_list, preserve_order=True):
960    if preserve_order:
961        selection = set(gene_list)
962        sel = [inst for inst in data
963               if str(inst[gene_feature]) in selection]
964    else:
965        by_genes = defaultdict(list)
966        for inst in data:
967            by_genes[str(inst[gene_feature])].append(inst)
968
969        sel = []
970        for name in gene_list:
971            sel.extend(by_genes.get(name, []))
972
973    if sel:
974        data = Orange.data.Table(data.domain, sel)
975    else:
976        data = Orange.data.Table(data.domain)
977
978    return data
979
980
981_CompletionState = namedtuple(
982    "_CompletionState",
983    ["start",  # completion prefix start position
984     "pos",  # cursor position
985     "anchor"]  # anchor position (inline completion end)
986)
987
988
989class ListTextEdit(QPlainTextEdit):
990    """
991    A text editor specialized for editing a list of items.
992    """
993    #: Emitted when the list items change.
994    itemsChanged = Signal(list)
995
996    def __init__(self, parent=None, **kwargs):
997        QPlainTextEdit.__init__(self, parent, **kwargs)
998
999        self._items = None
1000        self._completer = None
1001        self._completionState = _CompletionState(-1, -1, -1)
1002
1003        self.cursorPositionChanged.connect(self._cursorPositionChanged)
1004        self.textChanged.connect(self._textChanged)
1005
1006    def setCompleter(self, completer):
1007        """
1008        Set a completer for list items.
1009        """
1010        if self._completer is not None:
1011            self._completer.setWidget(None)
1012            self._completer.activated.disconnect(self._insertCompletion)
1013
1014        self._completer = completer
1015
1016        if self._completer:
1017            self._completer.setWidget(self)
1018            self._completer.activated.connect(self._insertCompletion)
1019
1020    def completer(self):
1021        """
1022        Return the completer.
1023        """
1024        return self._completer
1025
1026    def setItems(self, items):
1027        text = " ".join(items)
1028        self.setPlainText(text)
1029
1030    def items(self):
1031        if self._items is None:
1032            self._items = self._getItems()
1033        return self._items
1034
1035    def keyPressEvent(self, event):
1036        # TODO: in Qt 4.8 QPlainTextEdit uses inputMethodEvent for
1037        # non-ascii input
1038
1039        if self._completer.popup().isVisible():
1040            if event.key() in [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape,
1041                               Qt.Key_Tab, Qt.Key_Backtab]:
1042                # These need to propagate to the completer.
1043                event.ignore()
1044                return
1045
1046        QPlainTextEdit.keyPressEvent(self, event)
1047
1048        if not len(event.text()) or not is_printable(unicode(event.text())[0]):
1049            return
1050
1051        text = unicode(self.toPlainText())
1052        cursor = self.textCursor()
1053        pos = cursor.position()
1054
1055        if pos == len(text) or not text[pos].strip():
1056            # cursor is at end of text or whitespace
1057            # find the beginning of the current word
1058            whitespace = " \t\n\r\f\v"
1059            start = max([text.rfind(c, 0, pos) for c in whitespace]) + 1
1060
1061            prefix = text[start:pos]
1062
1063            if prefix:
1064                if self._completer.completionPrefix() != prefix:
1065                    self._completer.setCompletionPrefix(text[start:pos])
1066
1067                rect = self.cursorRect()
1068                popup = self._completer.popup()
1069                if popup.isVisible():
1070                    rect.setWidth(popup.width())
1071                else:
1072                    view_adjust_size_to_contents(popup)
1073                    rect.setWidth(popup.width())
1074
1075                # Popup the completer list
1076                self._completer.complete(rect)
1077
1078                # Inline completion of a common prefix
1079                inline = self._commonCompletionPrefix()
1080                inline = inline[len(prefix):]
1081
1082                self._completionState = \
1083                    _CompletionState(start, pos, pos + len(inline))
1084
1085                cursor.insertText(inline)
1086                cursor.setPosition(pos, QTextCursor.KeepAnchor)
1087                self.setTextCursor(cursor)
1088
1089            elif self._completer.popup().isVisible():
1090                self._stopCompletion()
1091
1092    def _cursorPositionChanged(self):
1093        cursor = self.textCursor()
1094        pos = cursor.position()
1095        start, _, _ = self._completionState
1096
1097        if start == -1:
1098            # completion not in progress
1099            return
1100
1101        if pos <= start:
1102            # cursor moved before the start of the prefix
1103            self._stopCompletion()
1104            return
1105
1106        text = unicode(self.toPlainText())
1107        # Find the end of the word started by completion prefix
1108        word_end = len(text)
1109        for i in range(start, len(text)):
1110            if text[i] in " \t\n\r\f\v":
1111                word_end = i
1112                break
1113
1114        if pos > word_end:
1115            # cursor moved past the word boundary
1116            self._stopCompletion()
1117
1118        # TODO: Update the prefix when moving the cursor
1119        # inside the word
1120
1121    def _insertCompletion(self, item):
1122        if isinstance(item, list):
1123            completion = " ".join(item)
1124        else:
1125            completion = unicode(item)
1126
1127        start, _, end = self._completionState
1128
1129        self._stopCompletion()
1130
1131        cursor = self.textCursor()
1132        # Replace the prefix+inline with the full completion
1133        # (correcting for the case-insensitive search).
1134        cursor.setPosition(min(end, self.document().characterCount()))
1135        cursor.setPosition(start, QTextCursor.KeepAnchor)
1136
1137        cursor.insertText(completion + " ")
1138
1139    def _commonCompletionPrefix(self):
1140        """
1141        Return the common prefix of items in the current completion model.
1142        """
1143        model = self._completer.completionModel()
1144        column = self._completer.completionColumn()
1145        role = self._completer.completionRole()
1146        items = [toString(model.index(i, column).data(role))
1147                 for i in range(model.rowCount())]
1148
1149        if not items:
1150            return ""
1151
1152        first = min(items)
1153        last = max(items)
1154        for i, c in enumerate(first):
1155            if c != last[i]:
1156                return first[:i]
1157
1158        return first
1159
1160    def _stopCompletion(self):
1161        self._completionState = _CompletionState(-1, -1, -1)
1162        if self._completer.popup().isVisible():
1163            self._completer.popup().hide()
1164
1165    def _textChanged(self):
1166        items = self._getItems()
1167        if self._items != items:
1168            self._items = items
1169            self.itemsChanged.emit(items)
1170
1171    def _getItems(self):
1172        """
1173        Return the current items (a list of strings).
1174
1175        .. note:: The inline completion text is not included.
1176
1177        """
1178        text = unicode(self.toPlainText())
1179        if self._completionState[0] != -1:
1180            # Remove the inline completion text
1181            _, pos, end = self._completionState
1182            text = text[:pos] + text[end:]
1183        return [item for item in text.split() if item.strip()]
1184
1185
1186def view_adjust_column_sizes(view, maxWidth=None):
1187    """
1188    Adjust view's column sizes to to contents.
1189    """
1190    if maxWidth is None:
1191        maxWidth = sys.maxint
1192
1193    for col in range(view.model().columnCount()):
1194        width = min(view.sizeHintForColumn(col), maxWidth)
1195        view.setColumnWidth(col, width)
1196
1197
1198def view_adjust_size_to_contents(view):
1199    """
1200    Adjust the view to a reasonable size based in it's contents.
1201    """
1202    view_adjust_column_sizes(view)
1203    w = sum([view.columnWidth(col)
1204             for col in range(view.model().columnCount())])
1205    w += view.verticalScrollBar().sizeHint().width()
1206
1207    h = view.sizeHintForRow(0) * 7
1208    h += view.horizontalScrollBar().sizeHint().height()
1209    view.resize(w, h)
1210
1211
1212class NameHighlight(QSyntaxHighlighter):
1213    def __init__(self, parent=None, **kwargs):
1214        super(NameHighlight, self).__init__(parent, **kwargs)
1215
1216        self._names = set()
1217
1218        self._format = QTextCharFormat()
1219        self._format.setForeground(Qt.blue)
1220
1221        self._unrecognized_format = QTextCharFormat()
1222#         self._unrecognized_format.setFontStrikeOut(True)
1223
1224    def setNames(self, names):
1225        self._names = set(names)
1226        self.rehighlight()
1227
1228    def names(self):
1229        return set(self._names)
1230
1231    def highlightBlock(self, text):
1232        text = unicode(text)
1233        pattern = re.compile(r"\S+")
1234        for match in pattern.finditer(text):
1235            name = text[match.start(): match.end()]
1236            match_len = match.end() - match.start()
1237
1238            if not name.strip():
1239                continue
1240
1241            if name in self._names:
1242                format = self._format
1243            else:
1244                format = self._unrecognized_format
1245
1246            self.setFormat(match.start(), match_len, format)
1247
1248
1249@contextmanager
1250def signals_blocked(obj):
1251    blocked = obj.signalsBlocked()
1252    obj.blockSignals(True)
1253    try:
1254        yield
1255    finally:
1256        obj.blockSignals(blocked)
1257
1258
1259class ListCompleter(QCompleter):
1260    """
1261    A completer supporting selection of multiple list items.
1262    """
1263    activated = Signal(list)
1264
1265    def __init__(self, *args, **kwargs):
1266        QCompleter.__init__(self, *args, **kwargs)
1267
1268        popup = QListView()
1269        popup.setEditTriggers(QListView.NoEditTriggers)
1270        popup.setSelectionMode(QListView.ExtendedSelection)
1271
1272        self.setPopup(popup)
1273
1274    def setPopup(self, popup):
1275        QCompleter.setPopup(self, popup)
1276
1277        popup.viewport().installEventFilter(self)
1278        popup.doubleClicked.connect(self._complete)
1279
1280    def eventFilter(self, receiver, event):
1281        if event.type() == QEvent.KeyPress and receiver is self.popup():
1282            if event.key() in [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab]:
1283                self._complete()
1284                return True
1285
1286        elif event.type() == QEvent.MouseButtonRelease and \
1287                receiver is self.popup().viewport():
1288            # Process the event without emitting 'clicked', ... signal to
1289            # override the default QCompleter behavior
1290            with signals_blocked(self.popup()):
1291                QApplication.sendEvent(self.popup(), event)
1292                return True
1293
1294        return QCompleter.eventFilter(self, receiver, event)
1295
1296    def _complete(self):
1297        column = self.completionColumn()
1298        role = self.completionRole()
1299        indexes = self.popup().selectionModel().selectedRows(column)
1300
1301        items = [toString(index.data(role)) for index in indexes]
1302
1303        if self.popup().isVisible():
1304            self.popup().hide()
1305
1306        if items:
1307            self.activated.emit(items)
1308
1309
1310class SetFilterProxyModel(QSortFilterProxyModel):
1311    def __init__(self, *args, **kwargs):
1312        super(SetFilterProxyModel, self).__init__(*args, **kwargs)
1313        self._filterFixedSet = None
1314
1315    def setFilterFixedSet(self, items):
1316        if items is None:
1317            self._filterFixedSet = None
1318        else:
1319            self._filterFixedSet = set(items)
1320        self.invalidateFilter()
1321
1322    def filterAcceptsRow(self, row, parent):
1323        if self._filterFixedSet is None:
1324            return True
1325
1326        model = self.sourceModel()
1327        col = self.filterKeyColumn()
1328        var = model.data(model.index(row, col, parent),
1329                         self.filterRole())
1330        var = toString(var)
1331        return var in self._filterFixedSet
1332
1333
1334class ComboBox(QComboBox):
1335    """
1336    A combo box ignoring mouse wheel events.
1337    """
1338    def wheelEvent(self, event):
1339        event.ignore()
1340        return
1341
1342# All control character categories.
1343_control = set(["Cc", "Cf", "Cs", "Co", "Cn"])
1344
1345
1346def is_printable(unichar):
1347    """
1348    Return True if the unicode character `unichar` is a printable character.
1349    """
1350    return unicodedata.category(unichar) not in _control
1351
1352
1353import sys
1354import itertools
1355
1356from PyQt4.QtGui import (
1357    QVBoxLayout, QLineEdit, QDialogButtonBox,
1358    QProgressBar, QSizePolicy
1359)
1360
1361from PyQt4.QtCore import QSize
1362from Orange.bio import obiGeneSets as genesets
1363from Orange.utils import serverfiles
1364
1365
1366class GeneSetView(QFrame):
1367    selectedOrganismChanged = Signal(str)
1368    selectionChanged = Signal()
1369    geneSetsLoaded = Signal()
1370
1371    def __init__(self, *args, **kwargs):
1372        super(GeneSetView, self).__init__(*args, **kwargs)
1373
1374        self._taxid = None
1375
1376        layout = QVBoxLayout()
1377        layout.setContentsMargins(0, 0, 0, 0)
1378
1379        self._stack = QStackedWidget()
1380        self._stack.setContentsMargins(0, 0, 0, 0)
1381        self._stack.setSizePolicy(QSizePolicy.MinimumExpanding,
1382                                  QSizePolicy.Fixed)
1383        self.orgcombo = ComboBox(minimumWidth=150, focusPolicy=Qt.StrongFocus)
1384        self.orgcombo.activated[int].connect(self._on_organismSelected)
1385        self._stack.addWidget(self.orgcombo)
1386
1387        self.progressbar = QProgressBar()
1388        self._stack.addWidget(self.progressbar)
1389
1390        layout.addWidget(self._stack)
1391
1392        self.searchline = QLineEdit()
1393        self.searchline.setPlaceholderText("Filter...")
1394        completer = QCompleter()
1395        self.searchline.setCompleter(completer)
1396        layout.addWidget(self.searchline)
1397
1398        self.gsview = QTreeView()
1399        self.gsview.setAlternatingRowColors(True)
1400        self.gsview.setRootIsDecorated(False)
1401        self.gsview.setSelectionMode(QTreeView.ExtendedSelection)
1402        self.proxymodel = QSortFilterProxyModel(
1403            filterKeyColumn=1, sortCaseSensitivity=Qt.CaseInsensitive
1404        )
1405
1406        self.gsview.setModel(self.proxymodel)
1407        self.gsview.selectionModel().selectionChanged.connect(
1408            self._on_selectionChanged)
1409
1410        self.searchline.textChanged.connect(
1411            self.proxymodel.setFilterFixedString)
1412
1413        layout.addWidget(self.gsview)
1414        self.setLayout(layout)
1415
1416        self._executor = ThreadExecutor(self)
1417        self.initialize()
1418
1419    def initialize(self):
1420        self.gs_hierarchy = gs = genesets.list_all()
1421        taxids = set(taxid for _, taxid, _ in gs)
1422        self.organisms = [(taxid, taxonomy.name(taxid)) for taxid in taxids]
1423        for taxid, name in self.organisms:
1424            self.orgcombo.addItem(name, taxid)
1425
1426        self.orgcombo.setCurrentIndex(-1)
1427
1428    def sizeHint(self):
1429        return QSize(500, 550)
1430
1431    def setCurrentOrganism(self, taxid):
1432        taxids = [tid for tid, _ in self.organisms]
1433        if taxid is not None and taxid not in taxids:
1434            taxid = None
1435
1436        if taxid != self._taxid:
1437            self._taxid = taxid
1438            if taxid is None:
1439                self.orgcombo.setCurrentIndex(-1)
1440            else:
1441                index = taxids.index(taxid)
1442                self.orgcombo.setCurrentIndex(index)
1443            self._updateGeneSetsModel()
1444            self.selectedOrganismChanged.emit(taxid)
1445
1446    def currentOrganism(self):
1447        return self._taxid
1448
1449    def selectedGeneSets(self):
1450        selmod = self.gsview.selectionModel()
1451        model = self.proxymodel.sourceModel()
1452        rows = [self.proxymodel.mapToSource(row)
1453                for row in selmod.selectedRows(1)]
1454        gsets = [model.data(row, Qt.UserRole) for row in rows]
1455        return map(toPyObject, gsets)
1456
1457    def _updateGeneSetsModel(self):
1458        taxid = self._taxid
1459        if taxid is None:
1460            self.proxymodel.setSourceModel(None)
1461        else:
1462            currentsets = [(hier, tid)
1463                           for hier, tid, _ in self.gs_hierarchy
1464                           if tid == taxid]
1465
1466            gsmissing = [(hier, tid, local)
1467                         for hier, tid, local in self.gs_hierarchy
1468                         if tid == taxid and not local]
1469
1470            self._stack.setCurrentWidget(self.progressbar)
1471
1472            if gsmissing:
1473                self.progressbar.setRange(0, 100)
1474                progress_info = methodinvoke(
1475                    self.progressbar, "setValue", (int,))
1476            else:
1477                self.progressbar.setRange(0, 0)
1478                progress_info = None
1479
1480            def load():
1481                gs_ensure_downloaded(
1482                    gsmissing,
1483                    progress_info=progress_info)
1484
1485                return [((hier, tid), genesets.load(hier, tid))
1486                        for hier, tid in currentsets]
1487
1488            self._task = Task(function=load)
1489            self._task.finished.connect(self._on_loadFinished)
1490            self._executor.submit(self._task)
1491
1492    def _on_loadFinished(self):
1493        assert QThread.currentThread() is self.thread()
1494        self._stack.setCurrentWidget(self.orgcombo)
1495
1496        try:
1497            sets = self._task.result()
1498        except Exception:
1499            # Should do something better here.
1500            sys.excepthook(*sys.exc_info())
1501            sets = []
1502
1503        model = sets_to_model(sets)
1504        self.proxymodel.setSourceModel(model)
1505        self.gsview.resizeColumnToContents(0)
1506        self.geneSetsLoaded.emit()
1507
1508    def _on_organismSelected(self, index):
1509        if index != -1:
1510            item = self.orgcombo.model().item(index)
1511            taxid = toString(item.data(Qt.UserRole))
1512            self.setCurrentOrganism(taxid)
1513
1514    def _on_selectionChanged(self, *args):
1515        self.selectionChanged.emit()
1516
1517
1518def sets_to_model(gsets):
1519    model = QStandardItemModel()
1520    model.setHorizontalHeaderLabels(["Category", "Name"])
1521
1522    for (hier, tid), sets in gsets:
1523        for gset in sets:
1524            ngenes = len(gset.genes)
1525            names = [escape(name) for name in list(gset.genes)[:30]]
1526            names = ", ".join(names)
1527            tooltip = "<p>{0}</p>{1}".format(escape(gset.name), names)
1528            if ngenes > 30:
1529                tooltip += ", ... ({0} names not shown)".format(ngenes - 30)
1530
1531            category = QStandardItem(" ".join(hier))
1532            category.setData((hier, tid), Qt.UserRole)
1533            category.setEditable(False)
1534            category.setToolTip(tooltip)
1535            name = QStandardItem(gset.name)
1536            name.setData(gset, Qt.UserRole)
1537            name.setEditable(False)
1538            name.setToolTip(tooltip)
1539            model.appendRow([category, name])
1540
1541    return model
1542
1543
1544def gs_ensure_downloaded(gslist, progress_info=None):
1545    hierlist = [(hier, taxid) for hier, taxid, local in gslist
1546                if not local]
1547
1548    files = [(genesets.sfdomain, genesets.filename(hier, taxid))
1549             for hier, taxid in hierlist]
1550
1551    download_list(files, progress_info)
1552
1553
1554def download_list(files_list, progress_callback=None):
1555    nfiles = len(files_list)
1556    count = 100 * nfiles
1557    counter = itertools.count()
1558
1559    def advance():
1560        progress_callback(100.0 * next(counter) / count)
1561
1562    for domain, filename in files_list:
1563        serverfiles.download(domain, filename,
1564                             callback=advance if progress_callback else None)
1565
1566
1567class GeneSetDialog(QDialog):
1568    selectionChanged = Signal()
1569
1570    def __init__(self, *args, **kwargs):
1571        super(GeneSetDialog, self).__init__(*args, **kwargs)
1572        layout = QVBoxLayout()
1573        layout.setContentsMargins(4, 4, 4, 4)
1574        self.gsview = GeneSetView()
1575        self.gsview.selectionChanged.connect(self.selectionChanged)
1576
1577        layout.addWidget(self.gsview)
1578
1579        buttonbox = QDialogButtonBox(
1580            QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
1581            Qt.Horizontal
1582        )
1583        buttonbox.accepted.connect(self.accept)
1584        buttonbox.rejected.connect(self.reject)
1585
1586        layout.addWidget(buttonbox)
1587
1588        self.setLayout(layout)
1589
1590    def selectedGeneSets(self):
1591        return self.gsview.selectedGeneSets()
1592
1593    def setCurrentOrganism(self, taxid):
1594        self.gsview.setCurrentOrganism(taxid)
1595
1596    def currentOrganism(self):
1597        return self.gsview.currentOrganism()
1598
1599
1600def test1():
1601    app = QApplication([])
1602    dlg = GeneSetDialog()
1603    dlg.exec_()
1604    del dlg
1605    app.processEvents()
1606
1607
1608def test():
1609    app = QApplication([])
1610    w = OWSelectGenes()
1611    data = Orange.data.Table("brown-selected")
1612    w.setData(data)
1613    w.setGeneSubset(Orange.data.Table(data[:10]))
1614    w.show()
1615    app.exec_()
1616    w.saveSettings()
1617    w.deleteLater()
1618    del w
1619    app.processEvents()
1620
1621if __name__ == "__main__":
1622    test()
Note: See TracBrowser for help on using the repository browser.