source: orange-bioinformatics/orangecontrib/bio/widgets/OWGeneInfo.py @ 1942:8fd077fd97ff

Revision 1942:8fd077fd97ff, 25.5 KB checked in by Ales Erjavec <ales.erjavec@…>, 4 months ago (diff)

Use 'uniformRowHeights' tree view property for faster layout.

Line 
1"""
2<name>Gene Info</name>
3<description>Displays gene information from NCBI and other sources.</description>
4<priority>2010</priority>
5<contact>Ales Erjavec (ales.erjavec(@at@)fri.uni-lj.si)</contact>
6<icon>icons/GeneInfo.svg</icon>
7"""
8
9from __future__ import absolute_import, with_statement
10
11import sys
12from collections import defaultdict
13from functools import partial
14
15from PyQt4.QtCore import pyqtSlot as Slot
16
17from Orange.orng import orngServerFiles
18from Orange.orng.orngDataCaching import data_hints
19from Orange.OrangeWidgets import OWGUI
20from Orange.OrangeWidgets.OWWidget import *
21
22from Orange.OrangeWidgets.OWConcurrent import \
23    ThreadExecutor, Task, methodinvoke
24
25from Orange.utils import progress_bar_milestones
26
27import orange
28
29from .. import obiGene, obiTaxonomy
30
31
32NAME = "Gene Info"
33DESCRIPTION = "Displays gene information from NCBI and other sources."
34ICON = "icons/GeneInfo.svg"
35PRIORITY = 2010
36
37INPUTS = [("Examples", Orange.data.Table, "setData")]
38OUTPUTS = [("Selected Examples", Orange.data.Table)]
39
40REPLACES = ["_bioinformatics.widgets.OWGeneInfo.OWGeneInfo"]
41
42
43class TreeModel(QAbstractItemModel):
44    def __init__(self, data, header, parent):
45        QAbstractItemModel.__init__(self, parent)
46        self._data = [[QVariant(s) for s in row] for row in data]
47        self._dataDict = {}
48        self._header = header
49        self._roleData = {Qt.DisplayRole:self._data}
50        self._roleData = partial(defaultdict, partial(defaultdict, partial(defaultdict, QVariant)))(self._roleData)
51   
52    def setColumnLinks(self, column, links):
53        font =QFont()
54        font.setUnderline(True)
55        font = QVariant(font)
56        for i, link in enumerate(links):
57            self._roleData[LinkRole][i][column] = QVariant(link)
58            self._roleData[Qt.FontRole][i][column] = font
59            self._roleData[Qt.ForegroundRole][i][column] = QVariant(QColor(Qt.blue))
60   
61    def setRoleData(self, role, row, col, data):
62        self._roleData[role][row][col] = data
63       
64    def data(self, index, role=Qt.DisplayRole):
65        row, col = index.row(), index.column()
66        return self._roleData[role][row][col]
67       
68    def index(self, row, col, parent=QModelIndex()):
69        return self.createIndex(row, col, 0)
70   
71    def parent(self, index):
72        return QModelIndex()
73   
74    def rowCount(self, index=QModelIndex()):
75        if index.isValid():
76            return 0
77        else:
78            return len(self._data)
79       
80    def columnCount(self, index=QModelIndex()):
81        return len(self._header)
82
83    def headerData(self, section, orientation, role=Qt.DisplayRole):
84        if role==Qt.DisplayRole:
85            return QVariant(self._header[section])
86        return QVariant()
87
88from Orange.OrangeWidgets.OWGUI import LinkStyledItemDelegate, LinkRole
89
90def lru_cache(maxsize=100):
91    """ A least recently used cache function decorator.
92    """
93   
94    def decorating_function(func):
95        import functools
96        cache = {}
97       
98        @functools.wraps(func)
99        def wrapped(*args, **kwargs):
100            key = args + tuple(sorted(kwargs.items()))
101            if key not in cache:
102                res = func(*args, **kwargs)
103                cache[key] = (time.time(), res)
104                if len(cache) > maxsize:
105                    key, (_, _) = min(cache.iteritems(), key=lambda item: item[1][0])
106                    del cache[key]
107            else:
108                _, res = cache[key]
109                cache[key] = (time.time(), res) # update the time
110               
111            return res
112       
113        def clear():
114            cache.clear()
115       
116        wrapped.clear = clear
117       
118        return wrapped
119    return decorating_function
120               
121class LinkFmt(object):
122    def __init__(self, link_fmt, name):
123        self.link_fmt = link_fmt
124        self.name = name
125       
126    def format(self, *args, **kwargs):
127        return Link(self.link_fmt.format(*args, **kwargs), **kwargs)
128   
129    def __repr__(self):
130        return "<LinkFmt " + repr(self.name) + " >"
131   
132    def __str__(self):
133        return self.name
134   
135class Link(object):
136    def __init__(self, link, text=None, **kwargs):
137        self.link = link
138        self.text = text if text is not None else "link"
139        self.__dict__.update(kwargs)
140       
141    def str(self):
142        return link
143   
144   
145@lru_cache(maxsize=2)
146def get_ncbi_info(taxid):
147    return obiGene.NCBIGeneInfo(taxid)
148
149def ncbi_info(taxid, genes):
150    info = get_ncbi_info(taxid)
151    schema_link = LinkFmt("http://www.ncbi.nlm.nih.gov/sites/entrez?Db=gene&Cmd=ShowDetailView&TermToSearch={gene_id}", name="NCBI ID")
152    schema = [schema_link, "Symbol", "Locus Tag", "Chromosome",
153              "Description", "Synonyms", "Nomenclature"]
154    ret = []
155    for gene in genes:
156        gi = info.get_info(gene)
157        if gi:
158            ret.append([schema_link.format(gene_id=gi.gene_id, text=gi.gene_id),
159                        gi.symbol + " (%s)" % gene if gene != gi.symbol else gi.symbol,
160                        gi.locus_tag or "",
161                        gi.chromosome or "",
162                        gi.description or "",
163                        ", ".join(gi.synonyms),
164                        gi.symbol_from_nomenclature_authority or ""
165                        ])
166        else:
167            ret.append(None)
168    return schema, ret
169   
170def dicty_info(taxid, genes):
171    from .. import obiDicty
172    info = obiDicty.DictyBase()
173    name_matcher = obiGene.GMDicty()
174    name_matcher.set_targets(info.info.keys())
175    schema_link = LinkFmt("http://dictybase.org/db/cgi-bin/gene_page.pl?dictybaseid={gene_id}", name="Dicty Base Id")
176    schema = [schema_link, "Name", "Synonyms", "Gene Products"]
177   
178    ret = []
179    for gene in genes:
180        gene = name_matcher.umatch(gene)
181        gi = info.info.get(gene, None)
182        if gi:
183            ret.append([schema_link.format(gene_id=gene, text=gene),
184                        gi[0] + " (%s)" % gene if gene != gi[0] else gi[0], # Gene Name
185                        ", ".join(gi[1]), # Synonyms
186                        gi[2] or "", # Gene Products
187                        ])
188           
189        else:
190            ret.append(None)
191   
192    return schema, ret
193
194
195INFO_SOURCES = {
196    "default": [("NCBI Info", ncbi_info)],
197    "352472": [("NCBI Info", ncbi_info),
198               ("Dicty Base", dicty_info)]
199}
200
201
202class OWGeneInfo(OWWidget):
203    settingsList = ["organismIndex", "geneAttr", "useAttr", "autoCommit",
204                    "taxid"]
205    contextHandlers = {
206        "": DomainContextHandler(
207            "", ["organismIndex", "geneAttr", "useAttr", "useAltSource",
208                 "taxid"]
209        )
210    }
211
212    def __init__(self, parent=None, signalManager=None, name="Gene Info"):
213        OWWidget.__init__(self, parent, signalManager, name)
214
215        self.inputs = [("Examples", ExampleTable, self.setData)]
216        self.outputs = [("Selected Examples", ExampleTable)]
217
218        self.organismIndex = 0
219        self.taxid = None
220        self.geneAttr = 0
221        self.useAttr = False
222        self.autoCommit = False
223        self.searchString = ""
224        self.selectionChangedFlag = False
225        self.useAltSource = 0
226        self.loadSettings()
227
228        self.__initialized = False
229        self.initfuture = None
230        self.itemsfuture = None
231
232        self.infoLabel = OWGUI.widgetLabel(
233            OWGUI.widgetBox(self.controlArea, "Info", addSpace=True),
234            "Initializing\n"
235        )
236
237        self.organisms = None
238        self.organismBox = OWGUI.widgetBox(
239            self.controlArea, "Organism", addSpace=True)
240
241        self.organismComboBox = OWGUI.comboBox(
242            self.organismBox, self, "organismIndex",
243            callback=self._onSelectedOrganismChanged,
244            debuggingEnabled=0)
245
246        # For now only support one alt source, with a checkbox
247        # In the future this can be extended to multiple selections
248        self.altSourceCheck = OWGUI.checkBox(self.organismBox, self,
249                            "useAltSource", "Show information from dictyBase",
250                            callback=self.onAltSourceChange,
251#                            debuggingEnabled=0,
252                            )
253        self.altSourceCheck.hide()
254       
255        box = OWGUI.widgetBox(self.controlArea, "Gene names", addSpace=True)
256        self.geneAttrComboBox = OWGUI.comboBox(box, self, "geneAttr",
257                                "Gene atttibute", callback=self.updateInfoItems)
258       
259        c = OWGUI.checkBox(box, self, "useAttr", "Use attribute names",
260                           callback=self.updateInfoItems,
261                           disables=[(-1, self.geneAttrComboBox)])
262       
263        self.geneAttrComboBox.setDisabled(bool(self.useAttr))
264
265        box = OWGUI.widgetBox(self.controlArea, "Commit", addSpace=True)
266        b = OWGUI.button(box, self, "Commit", callback=self.commit)
267        c = OWGUI.checkBox(box, self, "autoCommit", "Commit on change")
268        OWGUI.setStopper(self, b, c, "selectionChangedFlag",
269                         callback=self.commit)
270       
271        ## A label for dictyExpress link
272        self.dictyExpressBox = OWGUI.widgetBox(self.controlArea, "Dicty Express")
273        self.linkLabel = OWGUI.widgetLabel(self.dictyExpressBox, "")
274        self.linkLabel.setOpenExternalLinks(False)
275        self.connect(self.linkLabel, SIGNAL("linkActivated(QString)"),
276                     self.onDictyExpressLink)
277        self.dictyExpressBox.hide()
278       
279        OWGUI.rubber(self.controlArea)
280
281        OWGUI.lineEdit(self.mainArea, self, "searchString", "Filter",
282                       callbackOnType=True, callback=self.searchUpdate)
283       
284        self.treeWidget = QTreeView(self.mainArea)
285        self.treeWidget.setRootIsDecorated(False)
286        self.treeWidget.setSelectionMode(QAbstractItemView.ExtendedSelection)
287        self.treeWidget.setItemDelegate(LinkStyledItemDelegate(self.treeWidget))
288        self.treeWidget.setUniformRowHeights(True)
289        self.treeWidget.viewport().setMouseTracking(True)
290        self.treeWidget.setSortingEnabled(True)
291        self.mainArea.layout().addWidget(self.treeWidget)
292       
293        box = OWGUI.widgetBox(self.mainArea, "",
294                              orientation="horizontal")
295        OWGUI.button(box, self, "Select Filtered",
296                     callback=self.selectFiltered)
297        OWGUI.button(box, self, "Clear Selection",
298                     callback=self.treeWidget.clearSelection)
299       
300        self.resize(1000, 700)
301
302        self.geneinfo = []
303        self.cells = []
304        self.row2geneinfo = {}
305        self.data = None
306        self.currentLoaded = None, None
307        self.selectionUpdateInProgress = False
308
309        self.setBlocking(True)
310        self.executor = ThreadExecutor(self)
311
312        self.progressBarInit()
313
314        task = Task(
315            function=partial(
316                obiTaxonomy.ensure_downloaded,
317                callback=methodinvoke(self, "advance", ())
318            )
319        )
320
321        task.resultReady.connect(self.initialize)
322        task.exceptionReady.connect(self._onInitializeError)
323
324        self.initfuture = self.executor.submit(task)
325
326    @Slot()
327    def advance(self):
328        assert self.thread() is QThread.currentThread()
329
330        self.progressBarSet(self.progressBarValue + 1,
331                            processEventsFlags=None)
332
333    def initialize(self):
334        if self.__initialized:
335            # Already initialized
336            return
337
338        self.progressBarFinished()
339
340        self.organisms = sorted(
341            set([name.split(".")[-2] for name in
342                 orngServerFiles.listfiles("NCBI_geneinfo")] +
343                obiGene.NCBIGeneInfo.essential_taxids())
344        )
345
346        self.organismComboBox.addItems(
347            [obiTaxonomy.name(tax_id) for tax_id in self.organisms]
348        )
349        if self.taxid in self.organisms:
350            self.organismIndex = self.organisms.index(self.taxid)
351
352        self.infoLabel.setText("No data on input\n")
353        self.__initialized = True
354        self.initfuture = None
355
356        self.setBlocking(False)
357
358    def _onInitializeError(self, exc):
359        sys.excepthook(type(exc), exc.args, None)
360        self.error(0, "Could not download the necessary files.")
361
362    def _onSelectedOrganismChanged(self):
363        self.taxid = self.organisms[self.organismIndex]
364        if self.data is not None:
365            self.updateInfoItems()
366
367    def setData(self, data=None):
368        if not self.__initialized:
369            self.initfuture.result()
370            self.initialize()
371
372        if self.itemsfuture is not None:
373            raise Exception("Already processing")
374
375        self.closeContext()
376        self.data = data
377
378        if data:
379            self.geneAttrComboBox.clear()
380            self.attributes = [attr for attr in self.data.domain.variables + \
381                               self.data.domain.getmetas().values() \
382                               if attr.varType in [orange.VarTypes.String,
383                                                   orange.VarTypes.Discrete]]
384            self.geneAttrComboBox.addItems([attr.name for attr in self.attributes])
385
386            self.taxid = data_hints.get_hint(self.data, "taxid", self.taxid)
387            self.useAttr = data_hints.get_hint(self.data, "genesinrows",  self.useAttr)
388
389            self.openContext("", data)
390            self.geneAttr = min(self.geneAttr, len(self.attributes) - 1)
391
392            if self.taxid in self.organisms:
393                self.organismIndex = self.organisms.index(self.taxid)
394
395            self.useAttr = data_hints.get_hint(self.data, "genesinrows",  self.useAttr)
396
397            self.updateInfoItems()
398        else:
399            self.clear()
400
401    def infoSource(self):
402        """ Return the current selected info source getter function from
403        INFO_SOURCES
404        """
405        org = self.organisms[min(self.organismIndex, len(self.organisms) - 1)]
406        if org not in INFO_SOURCES:
407            org = "default"
408        sources = INFO_SOURCES[org]
409        name, func =  sources[min(self.useAltSource, len(sources) - 1)]
410        return name, func
411
412    def inputGenes(self):
413        if self.useAttr:
414            genes = [attr.name for attr in self.data.domain.attributes]
415        elif self.attributes:
416            attr = self.attributes[self.geneAttr]
417            genes = [str(ex[attr]) for ex in self.data if not ex[attr].isSpecial()]
418        else:
419            genes = []
420        return genes
421
422    def updateInfoItems(self):
423        self.warning(0)
424        if not self.data:
425            return
426
427        genes = self.inputGenes()
428        if self.useAttr:
429            genes = [attr.name for attr in self.data.domain.attributes]
430        elif self.attributes:
431            attr = self.attributes[self.geneAttr]
432            genes = [str(ex[attr]) for ex in self.data if not ex[attr].isSpecial()]
433        else:
434            genes = []
435        if not genes:
436            self.warning(0, "Could not extract genes from input dataset.")
437
438        self.warning(1)
439        org = self.organisms[min(self.organismIndex, len(self.organisms) - 1)]
440        source_name, info_getter = self.infoSource()
441        info, currorg = self.currentLoaded
442        self.error(0)
443
444        self.updateDictyExpressLink(genes, show=org == "352472")
445        self.altSourceCheck.setVisible(org == "352472")
446
447        self.progressBarInit()
448        self.setBlocking(True)
449        self.setEnabled(False)
450        self.infoLabel.setText("Retrieving info records.\n")
451
452        self.genes = genes
453
454        task = Task(function=partial(info_getter, org, genes))
455        self.itemsfuture = self.executor.submit(task)
456        task.finished.connect(self._onItemsCompleted)
457
458    def _onItemsCompleted(self):
459        self.setBlocking(False)
460        self.progressBarFinished()
461        self.setEnabled(True)
462
463        try:
464            schema, geneinfo = self.itemsfuture.result()
465        finally:
466            self.itemsfuture = None
467
468        self.geneinfo = geneinfo = list(zip(self.genes, geneinfo))
469
470        self.cells = cells = []
471        self.row2geneinfo = {}
472        links = []
473        for i, (_, gi) in enumerate(geneinfo):
474            if gi:
475                row = []
476                for _, item in zip(schema, gi):
477                    if isinstance(item, Link):
478                        # TODO: This should be handled by delegates
479                        row.append(item.text)
480                        links.append(item.link)
481                    else:
482                        row.append(item)
483                cells.append(row)
484                self.row2geneinfo[len(cells) - 1] = i
485
486        model = TreeModel(cells, [str(col) for col in schema], None)
487
488        model.setColumnLinks(0, links)
489        proxyModel = QSortFilterProxyModel(self)
490        proxyModel.setSourceModel(model)
491        self.treeWidget.setModel(proxyModel)
492        self.connect(self.treeWidget.selectionModel(),
493                     SIGNAL("selectionChanged(QItemSelection , QItemSelection )"),
494                     self.commitIf)
495
496        for i in range(7):
497            self.treeWidget.resizeColumnToContents(i)
498            self.treeWidget.setColumnWidth(i, min(self.treeWidget.columnWidth(i), 200))
499
500        self.infoLabel.setText("%i genes\n%i matched NCBI's IDs" %
501                               (len(self.genes), len(cells)))
502        self.matchedInfo = len(self.genes), len(cells)
503
504    def clear(self):
505        self.infoLabel.setText("No data on input\n")
506        self.treeWidget.setModel(TreeModel([], ["NCBI ID", "Symbol", "Locus Tag",
507                                            "Chromosome", "Description", "Synonyms",
508                                            "Nomenclature"], self.treeWidget))
509        self.geneAttrComboBox.clear()
510        self.send("Selected Examples", None)
511
512    def commitIf(self, *args):
513        if self.autoCommit and not self.selectionUpdateInProgress:
514            self.commit()
515        else:
516            self.selectionChangedFlag = True
517
518    def commit(self):
519        if not self.data:
520            return
521        model = self.treeWidget.model()
522        mapToSource = model.mapToSource
523        selectedIds = [self.cells[mapToSource(index).row()][0] for index in self.treeWidget.selectedIndexes()]
524        selectedRows = self.treeWidget.selectedIndexes()
525        selectedRows = [mapToSource(index).row() for index in selectedRows]
526        model = model.sourceModel()
527       
528        selectedGeneids = [self.row2geneinfo[row] for row in selectedRows]
529        selectedIds = [self.geneinfo[i][0] for i in selectedGeneids]
530        selectedIds = set(selectedIds)
531        gene2row = dict((self.geneinfo[self.row2geneinfo[row]][0], row) \
532                        for row in selectedRows)
533       
534        if self.useAttr:
535            def is_selected(attr):
536                return attr.name in selectedIds
537            attrs = [attr for attr in self.data.domain.attributes if is_selected(attr)]
538            domain = orange.Domain(attrs, self.data.domain.classVar)
539            domain.addmetas(self.data.domain.getmetas())
540            newdata = orange.ExampleTable(domain, self.data)
541            self.send("Selected Examples", newdata)
542        elif self.attributes:
543            attr = self.attributes[self.geneAttr]
544            geneinfo = dict(self.geneinfo)
545            examples = [ex for ex in self.data if str(ex[attr]) in selectedIds]
546            if True:  # Add gene info
547                domain = orange.Domain(self.data.domain, self.data.domain.classVar)
548                domain.addmetas(self.data.domain.getmetas())
549                n_columns = model.columnCount()
550
551                headers = [str(model.headerData(i, Qt.Horizontal, Qt.DisplayRole).toString()) \
552                           for i in range(n_columns)]
553                new_meta_attrs = [(orange.newmetaid(), orange.StringVariable(name)) \
554                                  for name in headers]
555                domain.addmetas(dict(new_meta_attrs))
556                examples = [orange.Example(domain, ex) for ex in examples]
557                for ex in examples:
558                    for i, (_, meta) in enumerate(new_meta_attrs):
559                        row = gene2row[str(ex[attr])]
560                        ex[meta] = str(model.data(model.index(row, i), Qt.DisplayRole).toString())
561
562            if examples:
563                newdata = orange.ExampleTable(examples)
564            else:
565                newdata = None
566            self.send("Selected Examples", newdata)
567        else:
568            self.send("Selected Examples", None)
569           
570    def rowFiltered(self, row):
571        searchStrings = self.searchString.lower().split()
572        row = unicode(" ".join(self.cells[row]).lower(), errors="ignore")
573        return not all([s in row for s in searchStrings])
574   
575    def searchUpdate(self):
576        if not self.data:
577            return
578        searchStrings = self.searchString.lower().split()
579        index = self.treeWidget.model().sourceModel().index
580        mapFromSource = self.treeWidget.model().mapFromSource
581        for i, row in enumerate(self.cells):
582            row = unicode(" ".join(row).lower(), errors="ignore")
583            self.treeWidget.setRowHidden(mapFromSource(index(i, 0)).row(), QModelIndex(), not all([s in row for s in searchStrings]))
584        #self.treeWidget.model().setFilterRegExp(QRegExp(self.searchString, Qt.CaseInsensitive, QRegExp.FixedString))
585           
586    def selectFiltered(self):
587        if not self.data:
588            return
589        itemSelection = QItemSelection()
590       
591        index = self.treeWidget.model().sourceModel().index
592        mapFromSource = self.treeWidget.model().mapFromSource
593        for i, row in enumerate(self.cells):
594            if not self.rowFiltered(i):
595                itemSelection.select(mapFromSource(index(i, 0)), mapFromSource(index(i, 0)))
596        self.treeWidget.selectionModel().select(itemSelection, QItemSelectionModel.Select | QItemSelectionModel.Rows)
597       
598    def sendReport(self):
599        from Orange.OrangeWidgets import OWReport
600        genes, matched = self.matchedInfo
601        info, org = self.currentLoaded
602        self.reportRaw("<p>Input: %i genes of which %i (%.1f%%) matched NCBI synonyms<br>Organism: %s<br>Filter: %s</p>" % (genes, matched, 100.0 * matched / genes, obiTaxonomy.name(org), self.searchString))
603        self.reportSubsection("Gene list")
604        self.reportRaw(reportItemView(self.treeWidget))
605       
606    def updateDictyExpressLink(self, genes, show=False):
607        def fix(ddb):
608            if ddb.startswith("DDB"): 
609                if not ddb.startswith("DDB_G"):
610                    ddb = ddb.replace("DDB", "DDB_G")
611                return ddb
612            return None 
613        if show:
614            genes = [fix(gene) for gene in genes if fix(gene)]
615            link1 = '<a href="http://dictyexpress.biolab.si/run/index.php?gene=%s">Microarray profile</a>'
616            link2 = '<a href="http://dictyexpress.biolab.si/run/index.php?gene=%s&db=rnaseq">RNA-Seq profile</a>'
617            self.linkLabel.setText(link1 + "<br/>" + link2)
618           
619            show = any(genes)
620               
621        if show:
622            self.dictyExpressBox.show()
623        else:
624            self.dictyExpressBox.hide()
625
626    def onDictyExpressLink(self, link):
627        if not self.data:
628            return
629
630        selectedIndexes = self.treeWidget.selectedIndexes()
631        if not len(selectedIndexes):
632            QMessageBox.information(self, "No gene ids selected", "Please select some genes and try again.")
633            return
634        model = self.treeWidget.model()
635        mapToSource = model.mapToSource
636        selectedIds = [self.cells[mapToSource(index).row()][0] for index in selectedIndexes]
637        selectedRows = self.treeWidget.selectedIndexes()
638        selectedRows = [mapToSource(index).row() for index in selectedRows]
639        model = model.sourceModel()
640
641        selectedGeneids = [self.row2geneinfo[row] for row in selectedRows]
642        selectedIds = [self.geneinfo[i][0] for i in selectedGeneids]
643        selectedIds = set(selectedIds)
644
645        def fix(ddb):
646            if ddb.startswith("DDB"):
647                if not ddb.startswith("DDB_G"):
648                    ddb = ddb.replace("DDB", "DDB_G")
649                return ddb
650            return None
651
652        genes = [fix(gene) for gene in selectedIds if fix(gene)]
653        url = str(link) % " ".join(genes)
654        QDesktopServices.openUrl(QUrl(url))
655           
656    def onAltSourceChange(self):
657        self.updateInfoItems()
658
659    def onDeleteWidget(self):
660        OWWidget.onDeleteWidget(self)
661
662        # try to cancel pending tasks
663        if self.initfuture:
664            self.initfuture.cancel()
665        if self.itemsfuture:
666            self.itemsfuture.cancel()
667
668        self.executor.shutdown()
669
670
671def reportItemView(view):
672    model = view.model()
673    return reportItemModel(view, model)
674   
675def reportItemModel(view, model, index=QModelIndex()):
676    if not index.isValid() or model.hasChildren(index):
677        columnCount, rowCount = model.columnCount(index), model.rowCount(index)
678        if not index.isValid():
679            text = '<table>\n<tr>' + ''.join('<th>%s</th>' % model.headerData(i, Qt.Horizontal, Qt.DisplayRole).toString() for i in range(columnCount)) +'</tr>\n'
680        else:
681#            variant = model.data(index, Qt.DisplayRole)
682#            text = '<table' + (' caption="%s"' % variant.toString() if variant.isValid() else '') + '>\n'
683            pass
684        text += ''.join('<tr>' + ''.join('<td>' + reportItemModel(view, model, model.index(row, column, index)) + '</td>' for column in range(columnCount)) + '</tr>\n' for row in range(rowCount) if not view.isRowHidden(row, index))
685        text += '</table>'
686        return text
687    else:
688        variant = model.data(index, Qt.DisplayRole)
689        return str(variant.toString()) if variant.isValid() else ""
690       
691if __name__ == "__main__":
692    app = QApplication(sys.argv)
693    data = orange.ExampleTable("brown-selected.tab")
694    w = OWGeneInfo()
695    w.show()
696
697    w.setData(data)
698    app.exec_()
699    w.saveSettings()
Note: See TracBrowser for help on using the repository browser.