source: orange-bioinformatics/orangecontrib/bio/widgets/OWSelectGenes.py @ 1922:3c4b5ab67b84

Revision 1922:3c4b5ab67b84, 49.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 5 months ago (diff)

Ignore wheel events in organism combo box.

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