source: orange-bioinformatics/orangecontrib/bio/widgets/OWGeneInfo.py @ 1928:ef73516807fb

Revision 1928:ef73516807fb, 25.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 4 months ago (diff)

Threaded initialization and record retrieval.

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    contextHandlers = {
205        "": DomainContextHandler(
206            "", ["organismIndex", "geneAttr", "useAttr", "useAltSource"]
207        )
208    }
209
210    def __init__(self, parent=None, signalManager=None, name="Gene Info"):
211        OWWidget.__init__(self, parent, signalManager, name)
212
213        self.inputs = [("Examples", ExampleTable, self.setData)]
214        self.outputs = [("Selected Examples", ExampleTable)]
215
216        self.organismIndex = 0
217        self.geneAttr = 0
218        self.useAttr = False
219        self.autoCommit = False
220        self.searchString = ""
221        self.selectionChangedFlag = False
222        self.useAltSource = 0
223        self.loadSettings()
224
225        self.__initialized = False
226        self.initfuture = None
227        self.itemsfuture = None
228
229        self.infoLabel = OWGUI.widgetLabel(
230            OWGUI.widgetBox(self.controlArea, "Info", addSpace=True),
231            "Initializing\n"
232        )
233
234        self.organisms = None
235        self.organismBox = OWGUI.widgetBox(
236            self.controlArea, "Organism", addSpace=True)
237
238        self.organismComboBox = OWGUI.comboBox(
239            self.organismBox, self, "organismIndex",
240            callback=self.updateInfoItems,
241            debuggingEnabled=0)
242
243        # For now only support one alt source, with a checkbox
244        # In the future this can be extended to multiple selections
245        self.altSourceCheck = OWGUI.checkBox(self.organismBox, self,
246                            "useAltSource", "Show information from dictyBase",
247                            callback=self.onAltSourceChange,
248#                            debuggingEnabled=0,
249                            )
250        self.altSourceCheck.hide()
251       
252        box = OWGUI.widgetBox(self.controlArea, "Gene names", addSpace=True)
253        self.geneAttrComboBox = OWGUI.comboBox(box, self, "geneAttr",
254                                "Gene atttibute", callback=self.updateInfoItems)
255       
256        c = OWGUI.checkBox(box, self, "useAttr", "Use attribute names",
257                           callback=self.updateInfoItems,
258                           disables=[(-1, self.geneAttrComboBox)])
259       
260        self.geneAttrComboBox.setDisabled(bool(self.useAttr))
261
262        box = OWGUI.widgetBox(self.controlArea, "Commit", addSpace=True)
263        b = OWGUI.button(box, self, "Commit", callback=self.commit)
264        c = OWGUI.checkBox(box, self, "autoCommit", "Commit on change")
265        OWGUI.setStopper(self, b, c, "selectionChangedFlag",
266                         callback=self.commit)
267       
268        ## A label for dictyExpress link
269        self.dictyExpressBox = OWGUI.widgetBox(self.controlArea, "Dicty Express")
270        self.linkLabel = OWGUI.widgetLabel(self.dictyExpressBox, "")
271        self.linkLabel.setOpenExternalLinks(False)
272        self.connect(self.linkLabel, SIGNAL("linkActivated(QString)"),
273                     self.onDictyExpressLink)
274        self.dictyExpressBox.hide()
275       
276        OWGUI.rubber(self.controlArea)
277
278        OWGUI.lineEdit(self.mainArea, self, "searchString", "Filter",
279                       callbackOnType=True, callback=self.searchUpdate)
280       
281        self.treeWidget = QTreeView(self.mainArea)
282        self.treeWidget.setRootIsDecorated(False)
283        self.treeWidget.setSelectionMode(QAbstractItemView.ExtendedSelection)
284        self.treeWidget.setItemDelegate(LinkStyledItemDelegate(self.treeWidget))
285        #self.connect(self.treeWidget, SIGNAL("itemSelectionChanged()"), self.commitIf)
286        self.treeWidget.viewport().setMouseTracking(True)
287        self.treeWidget.setSortingEnabled(True)
288        self.mainArea.layout().addWidget(self.treeWidget)
289       
290        box = OWGUI.widgetBox(self.mainArea, "",
291                              orientation="horizontal")
292        OWGUI.button(box, self, "Select Filtered",
293                     callback=self.selectFiltered)
294        OWGUI.button(box, self, "Clear Selection",
295                     callback=self.treeWidget.clearSelection)
296       
297        self.resize(1000, 700)
298
299        self.geneinfo = []
300        self.cells = []
301        self.row2geneinfo = {}
302        self.data = None
303        self.currentLoaded = None, None
304        self.selectionUpdateInProgress = False
305
306        self.setBlocking(True)
307        self.executor = ThreadExecutor(self)
308
309        self.progressBarInit()
310
311        task = Task(
312            function=partial(
313                obiTaxonomy.ensure_downloaded,
314                callback=methodinvoke(self, "advance", ())
315            )
316        )
317
318        task.resultReady.connect(self.initialize)
319        task.exceptionReady.connect(self._onInitializeError)
320
321        self.initfuture = self.executor.submit(task)
322
323    @Slot()
324    def advance(self):
325        assert self.thread() is QThread.currentThread()
326
327        self.progressBarSet(self.progressBarValue + 1,
328                            processEventsFlags=None)
329
330    def initialize(self):
331        if self.__initialized:
332            # Already initialized
333            return
334
335        self.progressBarFinished()
336
337        self.organisms = sorted(
338            set([name.split(".")[-2] for name in
339                 orngServerFiles.listfiles("NCBI_geneinfo")] +
340                obiGene.NCBIGeneInfo.essential_taxids())
341        )
342
343        self.organismComboBox.addItems(
344            [obiTaxonomy.name(tax_id) for tax_id in self.organisms]
345        )
346
347        self.infoLabel.setText("No data on input\n")
348        self.__initialized = True
349        self.initfuture = None
350
351        self.setBlocking(False)
352
353    def _onInitializeError(self, exc):
354        sys.excepthook(type(exc), exc.args, None)
355        self.error(0, "Could not download the necessary files.")
356
357    def setData(self, data=None):
358        if not self.__initialized:
359            self.initfuture.result()
360            self.initialize()
361
362        if self.itemsfuture is not None:
363            raise Exception("Already processing")
364
365        self.closeContext()
366        self.data = data
367
368        if data:
369            self.geneAttrComboBox.clear()
370            self.attributes = [attr for attr in self.data.domain.variables + \
371                               self.data.domain.getmetas().values() \
372                               if attr.varType in [orange.VarTypes.String,
373                                                   orange.VarTypes.Discrete]]
374            self.geneAttrComboBox.addItems([attr.name for attr in self.attributes])
375            self.openContext("", data)
376            self.geneAttr = min(self.geneAttr, len(self.attributes) - 1)
377
378            taxid = data_hints.get_hint(self.data, "taxid", "")
379            if taxid in self.organisms:
380                self.organismIndex = self.organisms.index(taxid)
381
382            self.useAttr = data_hints.get_hint(self.data, "genesinrows",  self.useAttr)
383
384            self.updateInfoItems()
385        else:
386            self.clear()
387
388    def infoSource(self):
389        """ Return the current selected info source getter function from
390        INFO_SOURCES
391        """
392        org = self.organisms[min(self.organismIndex, len(self.organisms) - 1)]
393        if org not in INFO_SOURCES:
394            org = "default"
395        sources = INFO_SOURCES[org]
396        name, func =  sources[min(self.useAltSource, len(sources) - 1)]
397        return name, func
398
399    def inputGenes(self):
400        if self.useAttr:
401            genes = [attr.name for attr in self.data.domain.attributes]
402        elif self.attributes:
403            attr = self.attributes[self.geneAttr]
404            genes = [str(ex[attr]) for ex in self.data if not ex[attr].isSpecial()]
405        else:
406            genes = []
407        return genes
408
409    def updateInfoItems(self):
410        self.warning(0)
411        if not self.data:
412            return
413
414        genes = self.inputGenes()
415        if self.useAttr:
416            genes = [attr.name for attr in self.data.domain.attributes]
417        elif self.attributes:
418            attr = self.attributes[self.geneAttr]
419            genes = [str(ex[attr]) for ex in self.data if not ex[attr].isSpecial()]
420        else:
421            genes = []
422        if not genes:
423            self.warning(0, "Could not extract genes from input dataset.")
424
425        self.warning(1)
426        org = self.organisms[min(self.organismIndex, len(self.organisms) - 1)]
427        source_name, info_getter = self.infoSource()
428        info, currorg = self.currentLoaded
429        self.error(0)
430
431        self.updateDictyExpressLink(genes, show=org == "352472")
432        self.altSourceCheck.setVisible(org == "352472")
433
434        self.progressBarInit()
435        self.setBlocking(True)
436        self.setEnabled(False)
437        self.infoLabel.setText("Retrieving info records.\n")
438
439        self.genes = genes
440
441        task = Task(function=partial(info_getter, org, genes))
442        self.itemsfuture = self.executor.submit(task)
443        task.finished.connect(self._onItemsCompleted)
444
445    def _onItemsCompleted(self):
446        self.setBlocking(False)
447        self.progressBarFinished()
448        self.setEnabled(True)
449
450        try:
451            schema, geneinfo = self.itemsfuture.result()
452        finally:
453            self.itemsfuture = None
454
455        self.geneinfo = geneinfo = list(zip(self.genes, geneinfo))
456
457        self.cells = cells = []
458        self.row2geneinfo = {}
459        links = []
460        for i, (_, gi) in enumerate(geneinfo):
461            if gi:
462                row = []
463                for _, item in zip(schema, gi):
464                    if isinstance(item, Link):
465                        # TODO: This should be handled by delegates
466                        row.append(item.text)
467                        links.append(item.link)
468                    else:
469                        row.append(item)
470                cells.append(row)
471                self.row2geneinfo[len(cells) - 1] = i
472
473        model = TreeModel(cells, [str(col) for col in schema], None)
474
475        model.setColumnLinks(0, links)
476        proxyModel = QSortFilterProxyModel(self)
477        proxyModel.setSourceModel(model)
478        self.treeWidget.setModel(proxyModel)
479        self.connect(self.treeWidget.selectionModel(),
480                     SIGNAL("selectionChanged(QItemSelection , QItemSelection )"),
481                     self.commitIf)
482
483        for i in range(7):
484            self.treeWidget.resizeColumnToContents(i)
485            self.treeWidget.setColumnWidth(i, min(self.treeWidget.columnWidth(i), 200))
486
487        self.infoLabel.setText("%i genes\n%i matched NCBI's IDs" %
488                               (len(self.genes), len(cells)))
489        self.matchedInfo = len(self.genes), len(cells)
490
491    def clear(self):
492        self.infoLabel.setText("No data on input\n")
493        self.treeWidget.setModel(TreeModel([], ["NCBI ID", "Symbol", "Locus Tag",
494                                            "Chromosome", "Description", "Synonyms",
495                                            "Nomenclature"], self.treeWidget))
496        self.geneAttrComboBox.clear()
497        self.send("Selected Examples", None)
498
499    def commitIf(self, *args):
500        if self.autoCommit and not self.selectionUpdateInProgress:
501            self.commit()
502        else:
503            self.selectionChangedFlag = True
504
505    def commit(self):
506        if not self.data:
507            return
508        model = self.treeWidget.model()
509        mapToSource = model.mapToSource
510        selectedIds = [self.cells[mapToSource(index).row()][0] for index in self.treeWidget.selectedIndexes()]
511        selectedRows = self.treeWidget.selectedIndexes()
512        selectedRows = [mapToSource(index).row() for index in selectedRows]
513        model = model.sourceModel()
514       
515        selectedGeneids = [self.row2geneinfo[row] for row in selectedRows]
516        selectedIds = [self.geneinfo[i][0] for i in selectedGeneids]
517        selectedIds = set(selectedIds)
518        gene2row = dict((self.geneinfo[self.row2geneinfo[row]][0], row) \
519                        for row in selectedRows)
520       
521        if self.useAttr:
522            def is_selected(attr):
523                return attr.name in selectedIds
524            attrs = [attr for attr in self.data.domain.attributes if is_selected(attr)]
525            domain = orange.Domain(attrs, self.data.domain.classVar)
526            domain.addmetas(self.data.domain.getmetas())
527            newdata = orange.ExampleTable(domain, self.data)
528            self.send("Selected Examples", newdata)
529        elif self.attributes:
530            attr = self.attributes[self.geneAttr]
531            geneinfo = dict(self.geneinfo)
532            examples = [ex for ex in self.data if str(ex[attr]) in selectedIds]
533            if True:  # Add gene info
534                domain = orange.Domain(self.data.domain, self.data.domain.classVar)
535                domain.addmetas(self.data.domain.getmetas())
536                n_columns = model.columnCount()
537
538                headers = [str(model.headerData(i, Qt.Horizontal, Qt.DisplayRole).toString()) \
539                           for i in range(n_columns)]
540                new_meta_attrs = [(orange.newmetaid(), orange.StringVariable(name)) \
541                                  for name in headers]
542                domain.addmetas(dict(new_meta_attrs))
543                examples = [orange.Example(domain, ex) for ex in examples]
544                for ex in examples:
545                    for i, (_, meta) in enumerate(new_meta_attrs):
546                        row = gene2row[str(ex[attr])]
547                        ex[meta] = str(model.data(model.index(row, i), Qt.DisplayRole).toString())
548
549            if examples:
550                newdata = orange.ExampleTable(examples)
551            else:
552                newdata = None
553            self.send("Selected Examples", newdata)
554        else:
555            self.send("Selected Examples", None)
556           
557    def rowFiltered(self, row):
558        searchStrings = self.searchString.lower().split()
559        row = unicode(" ".join(self.cells[row]).lower(), errors="ignore")
560        return not all([s in row for s in searchStrings])
561   
562    def searchUpdate(self):
563        if not self.data:
564            return
565        searchStrings = self.searchString.lower().split()
566        index = self.treeWidget.model().sourceModel().index
567        mapFromSource = self.treeWidget.model().mapFromSource
568        for i, row in enumerate(self.cells):
569            row = unicode(" ".join(row).lower(), errors="ignore")
570            self.treeWidget.setRowHidden(mapFromSource(index(i, 0)).row(), QModelIndex(), not all([s in row for s in searchStrings]))
571        #self.treeWidget.model().setFilterRegExp(QRegExp(self.searchString, Qt.CaseInsensitive, QRegExp.FixedString))
572           
573    def selectFiltered(self):
574        if not self.data:
575            return
576        itemSelection = QItemSelection()
577       
578        index = self.treeWidget.model().sourceModel().index
579        mapFromSource = self.treeWidget.model().mapFromSource
580        for i, row in enumerate(self.cells):
581            if not self.rowFiltered(i):
582                itemSelection.select(mapFromSource(index(i, 0)), mapFromSource(index(i, 0)))
583        self.treeWidget.selectionModel().select(itemSelection, QItemSelectionModel.Select | QItemSelectionModel.Rows)
584       
585    def sendReport(self):
586        from Orange.OrangeWidgets import OWReport
587        genes, matched = self.matchedInfo
588        info, org = self.currentLoaded
589        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))
590        self.reportSubsection("Gene list")
591        self.reportRaw(reportItemView(self.treeWidget))
592       
593    def updateDictyExpressLink(self, genes, show=False):
594        def fix(ddb):
595            if ddb.startswith("DDB"): 
596                if not ddb.startswith("DDB_G"):
597                    ddb = ddb.replace("DDB", "DDB_G")
598                return ddb
599            return None 
600        if show:
601            genes = [fix(gene) for gene in genes if fix(gene)]
602            link1 = '<a href="http://dictyexpress.biolab.si/run/index.php?gene=%s">Microarray profile</a>'
603            link2 = '<a href="http://dictyexpress.biolab.si/run/index.php?gene=%s&db=rnaseq">RNA-Seq profile</a>'
604            self.linkLabel.setText(link1 + "<br/>" + link2)
605           
606            show = any(genes)
607               
608        if show:
609            self.dictyExpressBox.show()
610        else:
611            self.dictyExpressBox.hide()
612
613    def onDictyExpressLink(self, link):
614        if not self.data:
615            return
616
617        selectedIndexes = self.treeWidget.selectedIndexes()
618        if not len(selectedIndexes):
619            QMessageBox.information(self, "No gene ids selected", "Please select some genes and try again.")
620            return
621        model = self.treeWidget.model()
622        mapToSource = model.mapToSource
623        selectedIds = [self.cells[mapToSource(index).row()][0] for index in selectedIndexes]
624        selectedRows = self.treeWidget.selectedIndexes()
625        selectedRows = [mapToSource(index).row() for index in selectedRows]
626        model = model.sourceModel()
627
628        selectedGeneids = [self.row2geneinfo[row] for row in selectedRows]
629        selectedIds = [self.geneinfo[i][0] for i in selectedGeneids]
630        selectedIds = set(selectedIds)
631
632        def fix(ddb):
633            if ddb.startswith("DDB"):
634                if not ddb.startswith("DDB_G"):
635                    ddb = ddb.replace("DDB", "DDB_G")
636                return ddb
637            return None
638
639        genes = [fix(gene) for gene in selectedIds if fix(gene)]
640        url = str(link) % " ".join(genes)
641        QDesktopServices.openUrl(QUrl(url))
642           
643    def onAltSourceChange(self):
644        self.updateInfoItems()
645
646    def onDeleteWidget(self):
647        OWWidget.onDeleteWidget(self)
648
649        # try to cancel pending tasks
650        if self.initfuture:
651            self.initfuture.cancel()
652        if self.itemsfuture:
653            self.itemsfuture.cancel()
654
655        self.executor.shutdown()
656
657
658def reportItemView(view):
659    model = view.model()
660    return reportItemModel(view, model)
661   
662def reportItemModel(view, model, index=QModelIndex()):
663    if not index.isValid() or model.hasChildren(index):
664        columnCount, rowCount = model.columnCount(index), model.rowCount(index)
665        if not index.isValid():
666            text = '<table>\n<tr>' + ''.join('<th>%s</th>' % model.headerData(i, Qt.Horizontal, Qt.DisplayRole).toString() for i in range(columnCount)) +'</tr>\n'
667        else:
668#            variant = model.data(index, Qt.DisplayRole)
669#            text = '<table' + (' caption="%s"' % variant.toString() if variant.isValid() else '') + '>\n'
670            pass
671        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))
672        text += '</table>'
673        return text
674    else:
675        variant = model.data(index, Qt.DisplayRole)
676        return str(variant.toString()) if variant.isValid() else ""
677       
678if __name__ == "__main__":
679    app = QApplication(sys.argv)
680    data = orange.ExampleTable("brown-selected.tab")
681    w = OWGeneInfo()
682    w.show()
683
684    w.setData(data)
685    app.exec_()
686    w.saveSettings()
Note: See TracBrowser for help on using the repository browser.