source: orange-bioinformatics/orangecontrib/bio/widgets/OWSelectGenes.py @ 1945:639f70df6b39

Revision 1945:639f70df6b39, 50.4 KB checked in by markotoplak, 3 months ago (diff)

Temporary fix in Select Genes.

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        def list_available_organisms():
431            return [(taxid, taxonomy.name(taxid))
432                    for taxid in taxonomy.common_taxids()]
433
434        self._inittask = Task(function=list_available_organisms)
435
436        self._inittask.finished.connect(self._onInit)
437        self._executor.submit(self._inittask)
438        self.pb.show()
439        self.pb.setRange(0, 0)
440
441    def _onInit(self):
442        self.organisms = self._inittask.result()
443        self.organismsCombo.clear()
444        self.organismsCombo.addItems([name for _, name in self.organisms])
445
446        self.taxidindex = \
447            {taxid: i for i, (taxid, _) in enumerate(self.organisms)}
448
449        self.organismsCombo.setCurrentIndex(
450            self.taxidindex.get(self.taxid, -1))
451
452        self.pb.hide()
453
454        if self.taxid in self.taxidindex:
455            self._updateGeneInfo()
456
457    def _updateGeneInfo(self):
458        self.geneinfo = (self.taxid, None)
459        self.translateAction.setEnabled(False)
460
461        if self._infotask:
462            # Try to cancel existing pending task
463            self._infotask.future().cancel()
464            self._infotask.resultReady.disconnect(self._onGeneInfoReady)
465            self._infotask.exceptionReady.disconnect(self._onGeneInfoError)
466
467        def ncbi_gene_info(taxid=self.taxid):
468            try:
469                return (taxid, geneinfo.NCBIGeneInfo(taxid))
470            except BaseException:
471                sys.excepthook(*sys.exc_info())
472                raise
473
474        task = Task(function=ncbi_gene_info)
475        task.resultReady.connect(self._onGeneInfoReady)
476        task.exceptionReady.connect(self._onGeneInfoError)
477        self._infotask = task
478        self._executor.submit(task)
479        self.pb.show()
480
481    def _onOrganismActivated(self, index):
482        try:
483            taxid, _ = self.organisms[index]
484        except IndexError:
485            pass
486        else:
487            if taxid != self.taxid:
488                self.taxid = taxid
489                self._updateGeneInfo()
490
491    def setData(self, data):
492        """
493        Set the input data.
494        """
495        self.closeContext("")
496        self.warning(0)
497        self.data = data
498        if data is not None:
499            attrs = gene_candidates(data)
500            self.variables[:] = attrs
501            self.attrsCombo.setCurrentIndex(0)
502            if attrs:
503                self.geneIndex = 0
504            else:
505                self.geneIndex = -1
506                self.warning(0, "No suitable columns for gene names.")
507        else:
508            self.variables[:] = []
509            self.geneIndex = -1
510
511        self._changedFlag = True
512
513        oldtaxid = self.taxid
514        if data is not None:
515            self.taxid = data_hints.get_hint(data, "taxid", None)
516        else:
517            self.taxid = None
518
519        self.openContext("", data)
520
521        if self.taxid is None:
522            self.geneinfo = (None, None)
523
524        if oldtaxid != self.taxid:
525            self.organismsCombo.setCurrentIndex(
526                self.taxidindex.get(self.taxid, -1))
527
528            if self.taxid in self.taxidindex:
529                self._updateGeneInfo()
530
531        self._updateCompletionModel()
532
533        self.commit()
534
535    def setGeneSubset(self, data):
536        """
537        Set the gene subset input.
538        """
539        self.closeContext("subset")
540        self.warning(1)
541        self.subsetData = data
542        if data is not None:
543            variables = gene_candidates(data)
544            self.subsetVariables[:] = variables
545            self.subsetVarCombo.setCurrentIndex(0)
546            if variables:
547                self.subsetGeneIndex = 0
548            else:
549                self.subsetGeneIndex = -1
550                self.warning(1, "No suitable column for subset gene names.")
551        else:
552            self.subsetVariables[:] = []
553            self.subsetGeneIndex = -1
554
555        self.openContext("subset", data)
556
557        if self.selectedSource == OWSelectGenes.SelectInput:
558            self.commit()
559
560    @property
561    def geneVar(self):
562        """
563        Current gene attribute or None if none available.
564        """
565        index = self.attrsCombo.currentIndex()
566        if self.data is not None and index >= 0:
567            return self.variables[index]
568        else:
569            return None
570
571    @property
572    def subsetGeneVar(self):
573        """
574        Current subset gene attribute or None if not available.
575        """
576        index = self.subsetVarCombo.currentIndex()
577        if self.subsetData is not None and index >= 0:
578            return self.subsetVariables[index]
579        else:
580            return None
581
582    def invalidateOutput(self):
583        if self.autoCommit:
584            self.commit()
585        else:
586            self._changedFlag = True
587
588    def selectedGenes(self):
589        """
590        Return the names of the current selected genes.
591        """
592        selection = []
593        if self.selectedSource == OWSelectGenes.SelectInput:
594            var = self.subsetGeneVar
595            if var is not None:
596                values = [inst[var] for inst in self.subsetData]
597                selection = [str(val) for val in values
598                             if not val.is_special()]
599        else:
600            selection = self.selection
601        return selection
602
603    def commit(self):
604        """
605        Send the selected data subset to the output.
606        """
607        selection = self.selectedGenes()
608
609        if self.geneinfo[1] is not None:
610            backmap = dict((info.symbol, name) for name, info in self.genes
611                           if info is not None)
612            names = set([name for name, _ in self.genes])
613            selection = [backmap.get(name, name) for name in selection]
614   
615        if self.geneVar is not None:
616            data = select_by_genes(self.data, self.geneVar,
617                                   gene_list=selection,
618                                   preserve_order=self.preserveOrder)
619        else:
620            data = None
621
622        self.send("Selected Data", data)
623        self._changedFlag = False
624
625    def setSelectionSource(self, source):
626        if self.selectedSource != source:
627            self.selectedSource = source
628            self.subsetbox.setEnabled(source == OWSelectGenes.SelectInput)
629            self.entrybox.setEnabled(source == OWSelectGenes.SelectCustom)
630            b = self.selectedSourceButtons.button(source)
631            b.setChecked(True)
632
633    def _selectionSourceChanged(self, source):
634        if self.selectedSource != source:
635            self.selectedSource = source
636            self.subsetbox.setEnabled(source == OWSelectGenes.SelectInput)
637            self.entrybox.setEnabled(source == OWSelectGenes.SelectCustom)
638            self.invalidateOutput()
639
640    def _updateCompletionModel(self):
641        var = self.geneVar
642        if var is not None:
643            names = [str(inst[var]) for inst in self.data
644                     if not inst[var].is_special()]
645        else:
646            names = []
647
648        infodict = {}
649
650        if self.geneinfo[1] is not None:
651            info = [(name, self.geneinfo[1].get_info(name, None))
652                    for name in names]
653            info = filter(itemgetter(1), info)
654            infodict = dict(info)
655
656        names = sorted(set(names))
657        genes = zip(names, map(infodict.get, names))
658
659        symbols = [info.symbol for _, info in genes if info is not None]
660
661        model = QStandardItemModel()
662
663        def make_row(name, info):
664            if info is not None:
665                col1 = QStandardItem(name)
666                col1.setData(info.symbol, OWSelectGenes.CompletionRole)
667                return [col1,
668                        QStandardItem(info.symbol),
669                        QStandardItem(info.description)]
670            else:
671                col1 = QStandardItem(name)
672                col1.setData(name, OWSelectGenes.CompletionRole)
673                return [col1]
674
675        for name, info in genes:
676            model.appendRow(make_row(name, info))
677
678        self.geneNames = sorted(set(names) | set(symbols))
679        self.genes = genes
680        self.entryField.completer().model().setSourceModel(model)
681        self.entryField.document().highlighter.setNames(names + symbols)
682
683        self._updatePopupSections()
684
685    def _onGeneIndexChanged(self):
686        self._updateCompletionModel()
687        self.invalidateOutput()
688
689    def _onSubsetGeneIndexChanged(self):
690        if self.selectedSource == OWSelectGenes.SelectInput:
691            self.invalidateOutput()
692
693    def _onItemsChanged(self, names):
694        selection = set(names).intersection(self.geneNames)
695        curr_selection = set(self.selection).intersection(self.geneNames)
696
697        self.selection = names
698
699        if selection != curr_selection:
700            self.invalidateOutput()
701            to_complete = sorted(set(self.geneNames) - set(names))
702            self.entryField.completer().model().setFilterFixedSet(to_complete)
703
704        item = self._selectedSaveSlot()
705        if item:
706            item.modified = item.savedata != names
707
708    def _onToggleSymbolCompletion(self, state):
709        completer = self.entryField.completer()
710        completer.setCompletionRole(
711            self.CompletionRole if state else Qt.DisplayRole
712        )
713        self.completeOnSymbols = state
714        self._updatePopupSections()
715
716    def _onTranslate(self):
717        if self.geneinfo[1] is not None:
718            items = self.entryField.items()
719            entries = map(self.geneinfo[1].get_info, items)
720            items = [info.symbol if info is not None else item
721                     for item, info in zip(items, entries)]
722            self.entryField.selectAll()
723            self.entryField.insertPlainText(" ".join(items))
724
725    def _onGeneInfoReady(self, geneinfo):
726        assert QThread.currentThread() is self.thread()
727        # Check if the gene info is for the correct (current requested)
728        # organism (we might receive a late response from a previous
729        # request)
730        if self.geneinfo[0] == geneinfo[0]:
731            self.geneinfo = geneinfo
732            self.translateAction.setEnabled(True)
733            self.pb.hide()
734            self._updateCompletionModel()
735
736    def _onGeneInfoError(self, exc):
737        self.error(0, str(exc))
738
739    def _updatePopupSections(self):
740        completer = self.entryField.completer()
741        popup = completer.popup()
742        assert isinstance(popup, QTreeView)
743        header = popup.header()
744        # The column in which the symbols should be
745        symbol_col = 0 if self.completeOnSymbols else 1
746        if symbol_col != header.sectionPosition(1):
747            header.moveSection(0, 1)
748
749    def _selectedSaveSlot(self):
750        """
751        Return the current selected saved selection slot.
752        """
753        indexes = self.selectionsView.selectedIndexes()
754        if indexes:
755            return self.selectionsModel.item(indexes[0].row())
756        else:
757            return None
758
759    def saveSelection(self):
760        """
761        Save (update) the items in the current selected selection.
762        """
763        item = self._selectedSaveSlot()
764        if item:
765            item.savedata = self.entryField.items()
766            item.modified = False
767
768    def copyToSaved(self):
769        """
770        Copy the current 'Gene Subset' names to saved selections.
771        """
772        if self.subsetGeneVar and \
773                self.selectedSource == OWSelectGenes.SelectInput:
774            names = self.selectedGenes()
775            item = SaveSlot("New selection")
776            item.savedata = names
777            self.selectionsModel.appendRow([item])
778            self.setSelectionSource(OWSelectGenes.SelectCustom)
779            self.selectionsView.setCurrentIndex(item.index())
780            self.selectionsView.edit(item.index())
781
782    def appendToSelected(self):
783        """
784        Append the current 'Gene Subset' names to 'Select Genes' entry field.
785        """
786        if self.subsetGeneVar and \
787                self.selectedSource == OWSelectGenes.SelectInput:
788            names = self.selectedGenes()
789            text = " ".join(names)
790            self.entryField.appendPlainText(text)
791            self.setSelectionSource(OWSelectGenes.SelectCustom)
792            self.entryField.setFocus()
793            self.entryField.moveCursor(QTextCursor.End)
794
795    def addSelection(self, name=None):
796        """
797        Add a new saved selection entry initialized by the current items.
798
799        The new slot will be selected.
800
801        """
802        item = SaveSlot(name or "New selection")
803        item.savedata = self.entryField.items()
804        self.selectionsModel.appendRow([item])
805        self.selectionsView.setCurrentIndex(item.index())
806
807        if not name:
808            self.selectionsView.edit(item.index())
809
810    def removeSelection(self):
811        """
812        Remove the current selected save slot.
813        """
814        item = self._selectedSaveSlot()
815        if item:
816            self.selectionsModel.removeRow(item.row())
817
818    def importGeneSet(self):
819        if self._genesetDialog is None:
820            self._genesetDialog = GeneSetDialog(
821                self, windowTitle="Import Gene Set Names")
822
823        dialog = self._genesetDialog
824
825        if self.taxid is not None:
826            dialog.setCurrentOrganism(self.taxid)
827
828        result = dialog.exec_()
829        if result == QDialog.Accepted:
830            gsets = dialog.selectedGeneSets()
831            genes = reduce(operator.ior, (gs.genes for gs in gsets), set())
832            text = " ".join(genes)
833            self.entryField.appendPlainText(text)
834            self.entryField.setFocus()
835            self.entryField.moveCursor(QTextCursor.End)
836
837    def importFromFile(self):
838        filename = QFileDialog.getOpenFileName(
839            self, "Open File", os.path.expanduser("~/"))
840
841        if filename:
842            filename = unicode(filename)
843            with open(filename, "rU") as f:
844                text = f.read()
845            self.entryField.appendPlainText(text)
846            self.entryField.setFocus()
847            self.entryField.moveCursor(QTextCursor.End)
848
849    def _onSelectedSaveSlotChanged(self):
850        item = self._selectedSaveSlot()
851        if item:
852            if not item.document:
853                item.document = self._createDocument()
854                if item.savedata:
855                    item.document.setPlainText(" ".join(item.savedata))
856
857            item.document.highlighter.setNames(self.geneNames)
858
859            self.entryField.setDocument(item.document)
860
861        self._updateActions()
862
863    def _createDocument(self):
864        """
865        Create and new QTextDocument instance for editing gene names.
866        """
867        doc = QTextDocument(self)
868        doc.setDocumentLayout(QPlainTextDocumentLayout(doc))
869        doc.highlighter = NameHighlight(doc)
870        return doc
871
872    def _updateActions(self):
873        """
874        Update the Save/remove action enabled state.
875        """
876        selected = bool(self._selectedSaveSlot())
877        self.actionRemove.setEnabled(selected)
878        self.actionSave.setEnabled(selected)
879
880    def getSettings(self, *args, **kwargs):
881        # copy the saved selections model back to widget settings.
882        selections = []
883        for i in range(self.selectionsModel.rowCount()):
884            item = self.selectionsModel.item(i)
885            selections.append((item.name, item.savedata))
886        self.savedSelections = selections
887
888        item = self._selectedSaveSlot()
889        if item is None:
890            self.selectedSelectionIndex = -1
891        else:
892            self.selectedSelectionIndex = item.row()
893
894        return OWWidget.getSettings(self, *args, **kwargs)
895
896    def sendReport(self):
897        report = []
898        if self.data is not None:
899            report.append("%i instances on input." % len(self.data))
900        else:
901            report.append("No data on input.")
902
903        if self.geneVar is not None:
904            report.append("Gene names taken from %r attribute." %
905                          escape(self.geneVar.name))
906
907        self.reportSection("Input")
908        self.startReportList()
909        for item in report:
910            self.addToReportList(item)
911        self.finishReportList()
912        report = []
913        selection = self.selectedGenes()
914        if self.selectedSource == OWSelectGenes.SelectInput:
915            self.reportRaw(
916                "<p>Gene Selection (from 'Gene Subset' input): %s</p>" %
917                escape(" ".join(selection))
918            )
919        else:
920            self.reportRaw(
921                "<p>Gene Selection: %s</p>" %
922                escape(" ".join(selection))
923            )
924        self.reportSettings(
925            "Settings",
926            [("Preserve order", self.preserveOrder)]
927        )
928
929    def onDeleteWidget(self):
930        self._inittask.future().cancel()
931
932        if self._infotask:
933            self._infotask.future().cancel()
934
935        self._executor.shutdown(wait=True)
936        OWWidget.onDeleteWidget(self)
937
938
939def is_string(feature):
940    return isinstance(feature, Orange.feature.String)
941
942
943def domain_variables(domain):
944    """
945    Return all feature descriptors from the domain.
946    """
947    vars = (domain.features +
948            domain.class_vars +
949            domain.getmetas().values())
950    return vars
951
952
953def gene_candidates(data):
954    """
955    Return features that could contain gene names.
956    """
957    vars = domain_variables(data.domain)
958    vars = filter(is_string, vars)
959    return vars
960
961
962def select_by_genes(data, gene_feature, gene_list, preserve_order=True):
963    if preserve_order:
964        selection = set(gene_list)
965        sel = [inst for inst in data
966               if str(inst[gene_feature]) in selection]
967    else:
968        by_genes = defaultdict(list)
969        for inst in data:
970            by_genes[str(inst[gene_feature])].append(inst)
971
972        sel = []
973        for name in gene_list:
974            sel.extend(by_genes.get(name, []))
975
976    if sel:
977        data = Orange.data.Table(data.domain, sel)
978    else:
979        data = Orange.data.Table(data.domain)
980
981    return data
982
983
984_CompletionState = namedtuple(
985    "_CompletionState",
986    ["start",  # completion prefix start position
987     "pos",  # cursor position
988     "anchor"]  # anchor position (inline completion end)
989)
990
991
992class ListTextEdit(QPlainTextEdit):
993    """
994    A text editor specialized for editing a list of items.
995    """
996    #: Emitted when the list items change.
997    itemsChanged = Signal(list)
998
999    def __init__(self, parent=None, **kwargs):
1000        QPlainTextEdit.__init__(self, parent, **kwargs)
1001
1002        self._items = None
1003        self._completer = None
1004        self._completionState = _CompletionState(-1, -1, -1)
1005
1006        self.cursorPositionChanged.connect(self._cursorPositionChanged)
1007        self.textChanged.connect(self._textChanged)
1008
1009    def setCompleter(self, completer):
1010        """
1011        Set a completer for list items.
1012        """
1013        if self._completer is not None:
1014            self._completer.setWidget(None)
1015            self._completer.activated.disconnect(self._insertCompletion)
1016
1017        self._completer = completer
1018
1019        if self._completer:
1020            self._completer.setWidget(self)
1021            self._completer.activated.connect(self._insertCompletion)
1022
1023    def completer(self):
1024        """
1025        Return the completer.
1026        """
1027        return self._completer
1028
1029    def setItems(self, items):
1030        text = " ".join(items)
1031        self.setPlainText(text)
1032
1033    def items(self):
1034        if self._items is None:
1035            self._items = self._getItems()
1036        return self._items
1037
1038    def keyPressEvent(self, event):
1039        # TODO: in Qt 4.8 QPlainTextEdit uses inputMethodEvent for
1040        # non-ascii input
1041
1042        if self._completer.popup().isVisible():
1043            if event.key() in [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape,
1044                               Qt.Key_Tab, Qt.Key_Backtab]:
1045                # These need to propagate to the completer.
1046                event.ignore()
1047                return
1048
1049        QPlainTextEdit.keyPressEvent(self, event)
1050
1051        if not len(event.text()) or not is_printable(unicode(event.text())[0]):
1052            return
1053
1054        text = unicode(self.toPlainText())
1055        cursor = self.textCursor()
1056        pos = cursor.position()
1057
1058        if pos == len(text) or not text[pos].strip():
1059            # cursor is at end of text or whitespace
1060            # find the beginning of the current word
1061            whitespace = " \t\n\r\f\v"
1062            start = max([text.rfind(c, 0, pos) for c in whitespace]) + 1
1063
1064            prefix = text[start:pos]
1065
1066            if prefix:
1067                if self._completer.completionPrefix() != prefix:
1068                    self._completer.setCompletionPrefix(text[start:pos])
1069
1070                rect = self.cursorRect()
1071                popup = self._completer.popup()
1072                if popup.isVisible():
1073                    rect.setWidth(popup.width())
1074                else:
1075                    view_adjust_size_to_contents(popup)
1076                    rect.setWidth(popup.width())
1077
1078                # Popup the completer list
1079                self._completer.complete(rect)
1080
1081                # Inline completion of a common prefix
1082                inline = self._commonCompletionPrefix()
1083                inline = inline[len(prefix):]
1084
1085                self._completionState = \
1086                    _CompletionState(start, pos, pos + len(inline))
1087
1088                cursor.insertText(inline)
1089                cursor.setPosition(pos, QTextCursor.KeepAnchor)
1090                self.setTextCursor(cursor)
1091
1092            elif self._completer.popup().isVisible():
1093                self._stopCompletion()
1094
1095    def _cursorPositionChanged(self):
1096        cursor = self.textCursor()
1097        pos = cursor.position()
1098        start, _, _ = self._completionState
1099
1100        if start == -1:
1101            # completion not in progress
1102            return
1103
1104        if pos <= start:
1105            # cursor moved before the start of the prefix
1106            self._stopCompletion()
1107            return
1108
1109        text = unicode(self.toPlainText())
1110        # Find the end of the word started by completion prefix
1111        word_end = len(text)
1112        for i in range(start, len(text)):
1113            if text[i] in " \t\n\r\f\v":
1114                word_end = i
1115                break
1116
1117        if pos > word_end:
1118            # cursor moved past the word boundary
1119            self._stopCompletion()
1120
1121        # TODO: Update the prefix when moving the cursor
1122        # inside the word
1123
1124    def _insertCompletion(self, item):
1125        if isinstance(item, list):
1126            completion = " ".join(item)
1127        else:
1128            completion = unicode(item)
1129
1130        start, _, end = self._completionState
1131
1132        self._stopCompletion()
1133
1134        cursor = self.textCursor()
1135        # Replace the prefix+inline with the full completion
1136        # (correcting for the case-insensitive search).
1137        cursor.setPosition(min(end, self.document().characterCount()))
1138        cursor.setPosition(start, QTextCursor.KeepAnchor)
1139
1140        cursor.insertText(completion + " ")
1141
1142    def _commonCompletionPrefix(self):
1143        """
1144        Return the common prefix of items in the current completion model.
1145        """
1146        model = self._completer.completionModel()
1147        column = self._completer.completionColumn()
1148        role = self._completer.completionRole()
1149        items = [toString(model.index(i, column).data(role))
1150                 for i in range(model.rowCount())]
1151
1152        if not items:
1153            return ""
1154
1155        first = min(items)
1156        last = max(items)
1157        for i, c in enumerate(first):
1158            if c != last[i]:
1159                return first[:i]
1160
1161        return first
1162
1163    def _stopCompletion(self):
1164        self._completionState = _CompletionState(-1, -1, -1)
1165        if self._completer.popup().isVisible():
1166            self._completer.popup().hide()
1167
1168    def _textChanged(self):
1169        items = self._getItems()
1170        if self._items != items:
1171            self._items = items
1172            self.itemsChanged.emit(items)
1173
1174    def _getItems(self):
1175        """
1176        Return the current items (a list of strings).
1177
1178        .. note:: The inline completion text is not included.
1179
1180        """
1181        text = unicode(self.toPlainText())
1182        if self._completionState[0] != -1:
1183            # Remove the inline completion text
1184            _, pos, end = self._completionState
1185            text = text[:pos] + text[end:]
1186        return [item for item in text.split() if item.strip()]
1187
1188
1189def view_adjust_column_sizes(view, maxWidth=None):
1190    """
1191    Adjust view's column sizes to to contents.
1192    """
1193    if maxWidth is None:
1194        maxWidth = sys.maxint
1195
1196    for col in range(view.model().columnCount()):
1197        width = min(view.sizeHintForColumn(col), maxWidth)
1198        view.setColumnWidth(col, width)
1199
1200
1201def view_adjust_size_to_contents(view):
1202    """
1203    Adjust the view to a reasonable size based in it's contents.
1204    """
1205    view_adjust_column_sizes(view)
1206    w = sum([view.columnWidth(col)
1207             for col in range(view.model().columnCount())])
1208    w += view.verticalScrollBar().sizeHint().width()
1209
1210    h = view.sizeHintForRow(0) * 7
1211    h += view.horizontalScrollBar().sizeHint().height()
1212    view.resize(w, h)
1213
1214
1215class NameHighlight(QSyntaxHighlighter):
1216    def __init__(self, parent=None, **kwargs):
1217        super(NameHighlight, self).__init__(parent, **kwargs)
1218
1219        self._names = set()
1220
1221        self._format = QTextCharFormat()
1222        self._format.setForeground(Qt.blue)
1223
1224        self._unrecognized_format = QTextCharFormat()
1225#         self._unrecognized_format.setFontStrikeOut(True)
1226
1227    def setNames(self, names):
1228        self._names = set(names)
1229        self.rehighlight()
1230
1231    def names(self):
1232        return set(self._names)
1233
1234    def highlightBlock(self, text):
1235        text = unicode(text)
1236        pattern = re.compile(r"\S+")
1237        for match in pattern.finditer(text):
1238            name = text[match.start(): match.end()]
1239            match_len = match.end() - match.start()
1240
1241            if not name.strip():
1242                continue
1243
1244            if name in self._names:
1245                format = self._format
1246            else:
1247                format = self._unrecognized_format
1248
1249            self.setFormat(match.start(), match_len, format)
1250
1251
1252@contextmanager
1253def signals_blocked(obj):
1254    blocked = obj.signalsBlocked()
1255    obj.blockSignals(True)
1256    try:
1257        yield
1258    finally:
1259        obj.blockSignals(blocked)
1260
1261
1262class ListCompleter(QCompleter):
1263    """
1264    A completer supporting selection of multiple list items.
1265    """
1266    activated = Signal(list)
1267
1268    def __init__(self, *args, **kwargs):
1269        QCompleter.__init__(self, *args, **kwargs)
1270
1271        popup = QListView()
1272        popup.setEditTriggers(QListView.NoEditTriggers)
1273        popup.setSelectionMode(QListView.ExtendedSelection)
1274
1275        self.setPopup(popup)
1276
1277    def setPopup(self, popup):
1278        QCompleter.setPopup(self, popup)
1279
1280        popup.viewport().installEventFilter(self)
1281        popup.doubleClicked.connect(self._complete)
1282
1283    def eventFilter(self, receiver, event):
1284        if event.type() == QEvent.KeyPress and receiver is self.popup():
1285            if event.key() in [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab]:
1286                self._complete()
1287                return True
1288
1289        elif event.type() == QEvent.MouseButtonRelease and \
1290                receiver is self.popup().viewport():
1291            # Process the event without emitting 'clicked', ... signal to
1292            # override the default QCompleter behavior
1293            with signals_blocked(self.popup()):
1294                QApplication.sendEvent(self.popup(), event)
1295                return True
1296
1297        return QCompleter.eventFilter(self, receiver, event)
1298
1299    def _complete(self):
1300        column = self.completionColumn()
1301        role = self.completionRole()
1302        indexes = self.popup().selectionModel().selectedRows(column)
1303
1304        items = [toString(index.data(role)) for index in indexes]
1305
1306        if self.popup().isVisible():
1307            self.popup().hide()
1308
1309        if items:
1310            self.activated.emit(items)
1311
1312
1313class SetFilterProxyModel(QSortFilterProxyModel):
1314    def __init__(self, *args, **kwargs):
1315        super(SetFilterProxyModel, self).__init__(*args, **kwargs)
1316        self._filterFixedSet = None
1317
1318    def setFilterFixedSet(self, items):
1319        if items is None:
1320            self._filterFixedSet = None
1321        else:
1322            self._filterFixedSet = set(items)
1323        self.invalidateFilter()
1324
1325    def filterAcceptsRow(self, row, parent):
1326        if self._filterFixedSet is None:
1327            return True
1328
1329        model = self.sourceModel()
1330        col = self.filterKeyColumn()
1331        var = model.data(model.index(row, col, parent),
1332                         self.filterRole())
1333        var = toString(var)
1334        return var in self._filterFixedSet
1335
1336
1337class ComboBox(QComboBox):
1338    """
1339    A combo box ignoring mouse wheel events.
1340    """
1341    def wheelEvent(self, event):
1342        event.ignore()
1343        return
1344
1345# All control character categories.
1346_control = set(["Cc", "Cf", "Cs", "Co", "Cn"])
1347
1348
1349def is_printable(unichar):
1350    """
1351    Return True if the unicode character `unichar` is a printable character.
1352    """
1353    return unicodedata.category(unichar) not in _control
1354
1355
1356import sys
1357import itertools
1358
1359from PyQt4.QtGui import (
1360    QVBoxLayout, QLineEdit, QDialogButtonBox,
1361    QProgressBar, QSizePolicy
1362)
1363
1364from PyQt4.QtCore import QSize
1365from Orange.bio import obiGeneSets as genesets
1366from Orange.utils import serverfiles
1367
1368
1369class GeneSetView(QFrame):
1370    selectedOrganismChanged = Signal(str)
1371    selectionChanged = Signal()
1372    geneSetsLoaded = Signal()
1373
1374    def __init__(self, *args, **kwargs):
1375        super(GeneSetView, self).__init__(*args, **kwargs)
1376
1377        self._taxid = None
1378
1379        layout = QVBoxLayout()
1380        layout.setContentsMargins(0, 0, 0, 0)
1381
1382        self._stack = QStackedWidget()
1383        self._stack.setContentsMargins(0, 0, 0, 0)
1384        self._stack.setSizePolicy(QSizePolicy.MinimumExpanding,
1385                                  QSizePolicy.Fixed)
1386        self.orgcombo = ComboBox(minimumWidth=150, focusPolicy=Qt.StrongFocus)
1387        self.orgcombo.activated[int].connect(self._on_organismSelected)
1388        self._stack.addWidget(self.orgcombo)
1389
1390        self.progressbar = QProgressBar()
1391        self._stack.addWidget(self.progressbar)
1392
1393        layout.addWidget(self._stack)
1394
1395        self.searchline = QLineEdit()
1396        self.searchline.setPlaceholderText("Filter...")
1397        completer = QCompleter()
1398        self.searchline.setCompleter(completer)
1399        layout.addWidget(self.searchline)
1400
1401        self.gsview = QTreeView()
1402        self.gsview.setAlternatingRowColors(True)
1403        self.gsview.setRootIsDecorated(False)
1404        self.gsview.setSelectionMode(QTreeView.ExtendedSelection)
1405        self.proxymodel = QSortFilterProxyModel(
1406            filterKeyColumn=1, sortCaseSensitivity=Qt.CaseInsensitive
1407        )
1408
1409        self.gsview.setModel(self.proxymodel)
1410        self.gsview.selectionModel().selectionChanged.connect(
1411            self._on_selectionChanged)
1412
1413        self.searchline.textChanged.connect(
1414            self.proxymodel.setFilterFixedString)
1415
1416        layout.addWidget(self.gsview)
1417        self.setLayout(layout)
1418
1419        self._executor = ThreadExecutor(self)
1420        self.initialize()
1421
1422    def initialize(self):
1423        self.gs_hierarchy = gs = genesets.list_all()
1424        taxids = set(taxid for _, taxid, _ in gs)
1425        self.organisms = [(taxid, taxonomy.name(taxid)) for taxid in taxids]
1426        for taxid, name in self.organisms:
1427            self.orgcombo.addItem(name, taxid)
1428
1429        self.orgcombo.setCurrentIndex(-1)
1430
1431    def sizeHint(self):
1432        return QSize(500, 550)
1433
1434    def setCurrentOrganism(self, taxid):
1435        taxids = [tid for tid, _ in self.organisms]
1436        if taxid is not None and taxid not in taxids:
1437            taxid = None
1438
1439        if taxid != self._taxid:
1440            self._taxid = taxid
1441            if taxid is None:
1442                self.orgcombo.setCurrentIndex(-1)
1443            else:
1444                index = taxids.index(taxid)
1445                self.orgcombo.setCurrentIndex(index)
1446            self._updateGeneSetsModel()
1447            self.selectedOrganismChanged.emit(taxid)
1448
1449    def currentOrganism(self):
1450        return self._taxid
1451
1452    def selectedGeneSets(self):
1453        selmod = self.gsview.selectionModel()
1454        model = self.proxymodel.sourceModel()
1455        rows = [self.proxymodel.mapToSource(row)
1456                for row in selmod.selectedRows(1)]
1457        gsets = [model.data(row, Qt.UserRole) for row in rows]
1458        return map(toPyObject, gsets)
1459
1460    def _updateGeneSetsModel(self):
1461        taxid = self._taxid
1462        if taxid is None:
1463            self.proxymodel.setSourceModel(None)
1464        else:
1465            currentsets = [(hier, tid)
1466                           for hier, tid, _ in self.gs_hierarchy
1467                           if tid == taxid]
1468
1469            gsmissing = [(hier, tid, local)
1470                         for hier, tid, local in self.gs_hierarchy
1471                         if tid == taxid and not local]
1472
1473            self._stack.setCurrentWidget(self.progressbar)
1474
1475            if gsmissing:
1476                self.progressbar.setRange(0, 100)
1477                progress_info = methodinvoke(
1478                    self.progressbar, "setValue", (int,))
1479            else:
1480                self.progressbar.setRange(0, 0)
1481                progress_info = None
1482
1483            def load():
1484                gs_ensure_downloaded(
1485                    gsmissing,
1486                    progress_info=progress_info)
1487
1488                return [((hier, tid), genesets.load(hier, tid))
1489                        for hier, tid in currentsets]
1490
1491            self._task = Task(function=load)
1492            self._task.finished.connect(self._on_loadFinished)
1493            self._executor.submit(self._task)
1494
1495    def _on_loadFinished(self):
1496        assert QThread.currentThread() is self.thread()
1497        self._stack.setCurrentWidget(self.orgcombo)
1498
1499        try:
1500            sets = self._task.result()
1501        except Exception:
1502            # Should do something better here.
1503            sys.excepthook(*sys.exc_info())
1504            sets = []
1505
1506        model = sets_to_model(sets)
1507        self.proxymodel.setSourceModel(model)
1508        self.gsview.resizeColumnToContents(0)
1509        self.geneSetsLoaded.emit()
1510
1511    def _on_organismSelected(self, index):
1512        if index != -1:
1513            item = self.orgcombo.model().item(index)
1514            taxid = toString(item.data(Qt.UserRole))
1515            self.setCurrentOrganism(taxid)
1516
1517    def _on_selectionChanged(self, *args):
1518        self.selectionChanged.emit()
1519
1520
1521def sets_to_model(gsets):
1522    model = QStandardItemModel()
1523    model.setHorizontalHeaderLabels(["Category", "Name"])
1524
1525    for (hier, tid), sets in gsets:
1526        for gset in sets:
1527            ngenes = len(gset.genes)
1528            names = [escape(name) for name in list(gset.genes)[:30]]
1529            names = ", ".join(names)
1530            tooltip = "<p>{0}</p>{1}".format(escape(gset.name), names)
1531            if ngenes > 30:
1532                tooltip += ", ... ({0} names not shown)".format(ngenes - 30)
1533
1534            category = QStandardItem(" ".join(hier))
1535            category.setData((hier, tid), Qt.UserRole)
1536            category.setEditable(False)
1537            category.setToolTip(tooltip)
1538            name = QStandardItem(gset.name)
1539            name.setData(gset, Qt.UserRole)
1540            name.setEditable(False)
1541            name.setToolTip(tooltip)
1542            model.appendRow([category, name])
1543
1544    return model
1545
1546
1547def gs_ensure_downloaded(gslist, progress_info=None):
1548    hierlist = [(hier, taxid) for hier, taxid, local in gslist
1549                if not local]
1550
1551    files = [(genesets.sfdomain, genesets.filename(hier, taxid))
1552             for hier, taxid in hierlist]
1553
1554    download_list(files, progress_info)
1555
1556
1557def download_list(files_list, progress_callback=None):
1558    nfiles = len(files_list)
1559    count = 100 * nfiles
1560    counter = itertools.count()
1561
1562    def advance():
1563        progress_callback(100.0 * next(counter) / count)
1564
1565    for domain, filename in files_list:
1566        serverfiles.download(domain, filename,
1567                             callback=advance if progress_callback else None)
1568
1569
1570class GeneSetDialog(QDialog):
1571    selectionChanged = Signal()
1572
1573    def __init__(self, *args, **kwargs):
1574        super(GeneSetDialog, self).__init__(*args, **kwargs)
1575        layout = QVBoxLayout()
1576        layout.setContentsMargins(4, 4, 4, 4)
1577        self.gsview = GeneSetView()
1578        self.gsview.selectionChanged.connect(self.selectionChanged)
1579
1580        layout.addWidget(self.gsview)
1581
1582        buttonbox = QDialogButtonBox(
1583            QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
1584            Qt.Horizontal
1585        )
1586        buttonbox.accepted.connect(self.accept)
1587        buttonbox.rejected.connect(self.reject)
1588
1589        layout.addWidget(buttonbox)
1590
1591        self.setLayout(layout)
1592
1593    def selectedGeneSets(self):
1594        return self.gsview.selectedGeneSets()
1595
1596    def setCurrentOrganism(self, taxid):
1597        self.gsview.setCurrentOrganism(taxid)
1598
1599    def currentOrganism(self):
1600        return self.gsview.currentOrganism()
1601
1602
1603def test1():
1604    app = QApplication([])
1605    dlg = GeneSetDialog()
1606    dlg.exec_()
1607    del dlg
1608    app.processEvents()
1609
1610
1611def test():
1612    app = QApplication([])
1613    w = OWSelectGenes()
1614    data = Orange.data.Table("brown-selected")
1615    w.setData(data)
1616    w.setGeneSubset(Orange.data.Table(data[:10]))
1617    w.show()
1618    app.exec_()
1619    w.saveSettings()
1620    w.deleteLater()
1621    del w
1622    app.processEvents()
1623
1624if __name__ == "__main__":
1625    test()
Note: See TracBrowser for help on using the repository browser.