source: orange/Orange/OrangeWidgets/Data/OWDataTable.py @ 11731:200a9e2ae4d7

Revision 11731:200a9e2ae4d7, 31.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 6 months ago (diff)

GUI style fix for corner button text on OSX.

RevLine 
[8042]1"""
2<name>Data Table</name>
3<description>Shows data in a spreadsheet.</description>
[11358]4<icon>icons/DataTable.svg</icon>
[8042]5<priority>100</priority>
6<contact>Peter Juvan (peter.juvan@fri.uni-lj.si)</contact>
7"""
8
[11731]9import sys
[11565]10from xml.sax.saxutils import escape
[11677]11from functools import wraps
12
13import sip
14
[11564]15import Orange
16
[8042]17from OWWidget import *
18import OWGUI
19import math
20from orngDataCaching import *
21import OWColorPalette
22
[11095]23NAME = "Data Table"
24
[11719]25DESCRIPTION = "Displays data in a spreadsheet."
[11095]26
27LONG_DESCRIPTION = """Data Table widget takes one or more data sets
28on its input and presents them in a spreadsheet format.
29"""
30
[11096]31ICON = "icons/DataTable.svg"
[11095]32
33PRIORITY = 100
34
35AUTHOR = "Peter Juvan"
36
37AUTHOR_EMAIL = "peter.juvan(@at@)fri.uni-lj.si"
38
39INPUTS = [("Data", ExampleTable, "dataset", Multiple + Default)]
40
41OUTPUTS = [("Selected Data", ExampleTable, Default),
42           ("Other Data", ExampleTable)]
43
[8042]44
[11565]45def header_text(feature, labels=None):
46    """
47    Return an header text for an `Orange.feature.Descriptor` instance
48    `feature`. If labels is not none it should be a sequence of keys into
49    `feature.attributes` to include in the header (one per line). If the
50    `feature.attribures` does not contain a value for the key the returned
51    text will include an empty line for it.
52
53    """
54    lines = [feature.name]
55    if labels is not None:
56        lines += [str(feature.attributes.get(label, ""))
57                  for label in labels]
58    return "\n".join(lines)
59
60
61def header_tooltip(feature, labels=None):
62    """
63    Return an header tooltip text for an `Orange.feature.Decriptor` instance.
64    """
65
66    if labels is None:
67        labels = feature.attributes.keys()
68
69    pairs = [(escape(key), escape(str(feature.attributes[key])))
70             for key in labels if key in feature.attributes]
71    tip = "<b>%s</b>" % escape(feature.name)
72    tip = "<br/>".join([tip] + ["%s = %s" % pair for pair in pairs])
73    return tip
74
[8042]75
[11677]76def api_qvariant(func):
77    @wraps(func)
78    def data_get(*args, **kwargs):
79        return QVariant(func(*args, **kwargs))
80    return data_get
81
82if hasattr(sip, "getapi") and sip.getapi("QVariant") > 1:
83    def api_qvariant(func):
84        return func
85
86
[8042]87class ExampleTableModel(QAbstractItemModel):
[11564]88    Attribute, ClassVar, ClassVars, Meta = range(4)
89
[8042]90    def __init__(self, examples, dist, *args):
91        QAbstractItemModel.__init__(self, *args)
92        self.examples = examples
[11564]93        self.domain = examples.domain
[8042]94        self.dist = dist
[11564]95        self.attributes = list(examples.domain.attributes)
96        self.class_var = self.examples.domain.class_var
97        self.class_vars = list(self.examples.domain.class_vars)
[8042]98        self.metas = self.examples.domain.getmetas().values()
[11564]99        # Attributes/features for all table columns
100        self.all_attrs = (self.attributes +
101                          ([self.class_var] if self.class_var else []) +
102                          self.class_vars + self.metas)
103        # Table roles for all table columns
104        self.table_roles = \
105            (([ExampleTableModel.Attribute] * len(self.attributes)) +
106             ([ExampleTableModel.ClassVar] if self.class_var else []) +
107             ([ExampleTableModel.ClassVars] * len(self.class_vars)) +
108             ([ExampleTableModel.Meta] * len(self.metas)))
109
110        # True if an feature at column i is continuous
111        self._continuous_mask = [isinstance(attr, Orange.feature.Continuous)
112                                 for attr in self.all_attrs]
113
114        self._meta_mask = [role == ExampleTableModel.Meta
115                           for role in self.table_roles]
116
117        self.cls_color = QColor(160, 160, 160)
118        self.meta_color = QColor(220, 220, 200)
119
120        role_to_color = {ExampleTableModel.Attribute: None,
121                         ExampleTableModel.ClassVar: self.cls_color,
122                         ExampleTableModel.ClassVars: self.cls_color,
123                         ExampleTableModel.Meta: self.meta_color}
124
125        self.background_colors = map(role_to_color.get, self.table_roles)
126
127        # all attribute labels (annotation) keys
128        self.attr_labels = sorted(
129            reduce(set.union,
130                   [attr.attributes for attr in self.all_attrs],
131                   set())
132        )
133
[11565]134        # text for all header items (no attr labels by default)
135        self.header_labels = [header_text(feature)
136                              for feature in self.all_attrs]
137
138        self.sorted_map = range(len(self.examples))
139
[10601]140        self._show_attr_labels = False
[8042]141        self._other_data = {}
[11564]142
[10601]143    def get_show_attr_labels(self):
144        return self._show_attr_labels
[11564]145
[10601]146    def set_show_attr_labels(self, val):
[11565]147        if self._show_attr_labels != val:
148            self.emit(SIGNAL("layoutAboutToBeChanged()"))
149            self._show_attr_labels = val
150            if val:
151                labels = self.attr_labels
152            else:
153                labels = None
154            self.header_labels = [header_text(feature, labels)
155                                  for feature in self.all_attrs]
156
157            self.emit(SIGNAL("headerDataChanged(Qt::Orientation, int, int)"),
158                      Qt.Horizontal,
159                      0,
160                      len(self.all_attrs) - 1)
161
162            self.emit(SIGNAL("layoutChanged()"))
163
164            self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
165                      self.index(0, 0),
166                      self.index(len(self.examples) - 1,
167                                 len(self.all_attrs) - 1)
168                      )
[11564]169
170    show_attr_labels = pyqtProperty("bool",
171                                    fget=get_show_attr_labels,
172                                    fset=set_show_attr_labels)
173
[11677]174    @api_qvariant
[11564]175    def data(self, index, role,
176             # For optimizing out LOAD_GLOBAL byte code instructions in
177             # the item role tests (goes from 14 us to 9 us average).
178             _str=str,
179             _Qt_DisplayRole=Qt.DisplayRole,
180             _Qt_BackgroundRole=Qt.BackgroundRole,
181             _OWGUI_TableBarItem_BarRole=OWGUI.TableBarItem.BarRole,
182             _OWGUI_TableValueRole=OWGUI.TableValueRole,
183             _OWGUI_TableClassValueRole=OWGUI.TableClassValueRole,
184             _OWGUI_TableVariable=OWGUI.TableVariable,
185             # Some cached local precomputed values.
186             # All of the above roles we respond to
187             _recognizedRoles=set([Qt.DisplayRole,
188                                   Qt.BackgroundRole,
189                                   OWGUI.TableBarItem.BarRole,
190                                   OWGUI.TableValueRole,
191                                   OWGUI.TableClassValueRole,
192                                   OWGUI.TableVariable]),
193             ):
194        """
195        Return the data for `role` for an value at `index`.
196        """
197        if role not in _recognizedRoles:
198            return self._other_data.get((index.row(), index.column(), role),
199                                        None)
200
[8042]201        row, col = self.sorted_map[index.row()], index.column()
202        example, attr = self.examples[row], self.all_attrs[col]
[11564]203
[8042]204        val = example[attr]
[11564]205
206        if role == _Qt_DisplayRole:
207            return _str(val)
208        elif role == _Qt_BackgroundRole:
209            return self.background_colors[col]
210        elif role == _OWGUI_TableBarItem_BarRole and \
211                self._continuous_mask[col] and not val.isSpecial() and \
212                col < len(self.dist):
[8042]213            dist = self.dist[col]
[11564]214            return (float(val) - dist.min) / (dist.max - dist.min or 1)
215        elif role == _OWGUI_TableValueRole:
216            # The actual value instance (should it be EditRole?)
217            return val
218        elif role == _OWGUI_TableClassValueRole and self.class_var is not None:
[10601]219            # The class value for the row's example
[11564]220            return example.get_class()
221        elif role == _OWGUI_TableVariable:
[10601]222            # The variable descriptor for column
[11564]223            return attr
224
225        return None
226
[8042]227    def setData(self, index, variant, role):
228        self._other_data[index.row(), index.column(), role] = variant
229        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
230       
231    def index(self, row, col, parent=QModelIndex()):
232        return self.createIndex(row, col, 0)
233   
234    def parent(self, index):
235        return QModelIndex()
236   
237    def rowCount(self, parent=QModelIndex()):
238        if parent.isValid():
239            return 0
240        else:
[11565]241            return len(self.examples)
242
243    def columnCount(self, parent=QModelIndex()):
244        if parent.isValid():
245            return 0
246        else:
247            return len(self.all_attrs)
248
[11677]249    @api_qvariant
[8042]250    def headerData(self, section, orientation, role):
251        if orientation == Qt.Horizontal:
252            attr = self.all_attrs[section]
[11565]253            if role == Qt.DisplayRole:
254                return self.header_labels[section]
[8042]255            if role == Qt.ToolTipRole:
[11565]256                return header_tooltip(attr, self.attr_labels)
[8042]257        else:
258            if role == Qt.DisplayRole:
259                return QVariant(section + 1)
260        return QVariant()
[11565]261
[8042]262    def sort(self, column, order=Qt.AscendingOrder):
263        self.emit(SIGNAL("layoutAboutToBeChanged()"))
264        attr = self.all_attrs[column] 
265        values = [(ex[attr], i) for i, ex in enumerate(self.examples)]
266        values = sorted(values, key=lambda t: t[0] if not t[0].isSpecial() else sys.maxint, reverse=(order!=Qt.AscendingOrder))
267        self.sorted_map = [v[1] for v in values]
268        self.emit(SIGNAL("layoutChanged()"))
[10601]269        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
270                  self.index(0,0),
271                  self.index(len(self.examples) - 1, len(self.all_attrs) - 1)
272                  )
[10703]273
274
275class TableViewWithCopy(QTableView):
276    def keyPressEvent(self, event):
277        if event == QKeySequence.Copy:
278            sel_model = self.selectionModel()
279            try:
280                self.copy_selection_to_clipboard(sel_model)
281            except Exception:
282                import traceback
283                traceback.print_exc(file=sys.stderr)
284        else:
285            return QTableView.keyPressEvent(self, event)
[8042]286           
[10703]287    def copy_selection_to_clipboard(self, selection_model):
288        """Copy table selection to the clipboard.
289        """
290        # TODO: html/rtf table
291        import csv
292        from StringIO import StringIO
293        rows = selection_model.selectedRows(0)
294        lines = []
295        csv_str = StringIO()
296        csv_writer = csv.writer(csv_str, dialect="excel")
297        tsv_str = StringIO()
298        tsv_writer = csv.writer(tsv_str, dialect="excel-tab")
299        for row in rows:
300            line = []
301            for i in range(self.model().columnCount()):
302                index = self.model().index(row.row(), i)
303                val = index.data(Qt.DisplayRole)
304                line.append(unicode(val.toString()))
305
306            csv_writer.writerow(line)
307            tsv_writer.writerow(line)
308
309        csv_lines = csv_str.getvalue()
310        tsv_lines = tsv_str.getvalue()
311
312        mime = QMimeData()
313        mime.setData("text/csv", QByteArray(csv_lines))
314        mime.setData("text/tab-separated-values", QByteArray(tsv_lines))
315        mime.setData("text/plain", QByteArray(tsv_lines))
316        QApplication.clipboard().setMimeData(mime, QClipboard.Clipboard)
317
[8042]318
319class OWDataTable(OWWidget):
320    settingsList = ["showDistributions", "showMeta", "distColorRgb", "showAttributeLabels", "autoCommit", "selectedSchemaIndex", "colorByClass"]
321
322    def __init__(self, parent=None, signalManager = None):
323        OWWidget.__init__(self, parent, signalManager, "Data Table")
324
[9546]325        self.inputs = [("Data", ExampleTable, self.dataset, Multiple + Default)]
326        self.outputs = [("Selected Data", ExampleTable, Default), ("Other Data", ExampleTable)]
[8042]327
328        self.data = {}          # key: id, value: ExampleTable
329        self.showMetas = {}     # key: id, value: (True/False, columnList)
330        self.showMeta = 1
331        self.showAttributeLabels = 1
332        self.showDistributions = 1
333        self.distColorRgb = (220,220,220, 255)
334        self.distColor = QColor(*self.distColorRgb)
335        self.locale = QLocale()
336        self.autoCommit = False
337        self.colorSettings = None
338        self.selectedSchemaIndex = 0
339        self.colorByClass = True
340       
341        self.loadSettings()
342
343        # info box
344        infoBox = OWGUI.widgetBox(self.controlArea, "Info")
345        self.infoEx = OWGUI.widgetLabel(infoBox, 'No data on input.')
346        self.infoMiss = OWGUI.widgetLabel(infoBox, ' ')
347        OWGUI.widgetLabel(infoBox, ' ')
348        self.infoAttr = OWGUI.widgetLabel(infoBox, ' ')
349        self.infoMeta = OWGUI.widgetLabel(infoBox, ' ')
350        OWGUI.widgetLabel(infoBox, ' ')
351        self.infoClass = OWGUI.widgetLabel(infoBox, ' ')
352        infoBox.setMinimumWidth(200)
353        OWGUI.separator(self.controlArea)
354
355        # settings box
356        boxSettings = OWGUI.widgetBox(self.controlArea, "Settings", addSpace=True)
357        self.cbShowMeta = OWGUI.checkBox(boxSettings, self, "showMeta", 'Show meta attributes', callback = self.cbShowMetaClicked)
358        self.cbShowMeta.setEnabled(False)
359        self.cbShowAttLbls = OWGUI.checkBox(boxSettings, self, "showAttributeLabels", 'Show attribute labels (if any)', callback = self.cbShowAttLabelsClicked)
360        self.cbShowAttLbls.setEnabled(True)
361
362        box = OWGUI.widgetBox(self.controlArea, "Colors")
363        OWGUI.checkBox(box, self, "showDistributions", 'Visualize continuous values', callback = self.cbShowDistributions)
364        OWGUI.checkBox(box, self, "colorByClass", 'Color by class value', callback = self.cbShowDistributions)
365        OWGUI.button(box, self, "Set colors", self.setColors, tooltip = "Set the canvas background color and color palette for coloring continuous variables", debuggingEnabled = 0)
366
367        resizeColsBox = OWGUI.widgetBox(boxSettings, 0, "horizontal", 0)
368        OWGUI.label(resizeColsBox, self, "Resize columns: ")
369        OWGUI.toolButton(resizeColsBox, self, "+", self.increaseColWidth, tooltip = "Increase the width of the columns", width=20, height=20)
370        OWGUI.toolButton(resizeColsBox, self, "-", self.decreaseColWidth, tooltip = "Decrease the width of the columns", width=20, height=20)
371        OWGUI.rubber(resizeColsBox)
372
373        self.btnResetSort = OWGUI.button(boxSettings, self, "Restore Order of Examples", callback = self.btnResetSortClicked, tooltip = "Show examples in the same order as they appear in the file")
374       
375        OWGUI.separator(self.controlArea)
376        selectionBox = OWGUI.widgetBox(self.controlArea, "Selection")
377        self.sendButton = OWGUI.button(selectionBox, self, "Send selections", self.commit, default=True)
378        cb = OWGUI.checkBox(selectionBox, self, "autoCommit", "Commit on any change", callback=self.commitIf)
379        OWGUI.setStopper(self, self.sendButton, cb, "selectionChangedFlag", self.commit)
380
381        OWGUI.rubber(self.controlArea)
382
383        dlg = self.createColorDialog()
384        self.discPalette = dlg.getDiscretePalette("discPalette")
385
386        # GUI with tabs
387        self.tabs = OWGUI.tabWidget(self.mainArea)
388        self.id2table = {}  # key: widget id, value: table
389        self.table2id = {}  # key: table, value: widget id
390        self.connect(self.tabs, SIGNAL("currentChanged(QWidget*)"), self.tabClicked)
391       
392        self.selectionChangedFlag = False
393       
394
395    def createColorDialog(self):
396        c = OWColorPalette.ColorPaletteDlg(self, "Color Palette")
397        c.createDiscretePalette("discPalette", "Discrete Palette")
398        box = c.createBox("otherColors", "Other Colors")
399        c.createColorButton(box, "Default", "Default color", QColor(Qt.white))
400        c.setColorSchemas(self.colorSettings, self.selectedSchemaIndex)
401        return c
402
403    def setColors(self):
404        dlg = self.createColorDialog()
405        if dlg.exec_():
406            self.colorSettings = dlg.getColorSchemas()
407            self.selectedSchemaIndex = dlg.selectedSchemaIndex
408            self.discPalette = dlg.getDiscretePalette("discPalette")
[11573]409            self.distColor = color = dlg.getColor("Default")
410            self.distColorRgb = (color.red(), color.green(), color.red())
411
412            if self.showDistributions:
413                # Update the views
414                self.cbShowDistributions()
[8042]415
416    def increaseColWidth(self):
417        table = self.tabs.currentWidget()
418        if table:
419            for col in range(table.model().columnCount(QModelIndex())):
420                w = table.columnWidth(col)
421                table.setColumnWidth(col, w + 10)
422
423    def decreaseColWidth(self):
424        table = self.tabs.currentWidget()
425        if table:
426            for col in range(table.model().columnCount(QModelIndex())):
427                w = table.columnWidth(col)
428                minW = table.sizeHintForColumn(col)
429                table.setColumnWidth(col, max(w - 10, minW))
430
431
432    def dataset(self, data, id=None):
433        """Generates a new table and adds it to a new tab when new data arrives;
434        or hides the table and removes a tab when data==None;
435        or replaces the table when new data arrives together with already existing id."""
436        if data != None:  # can be an empty table!
437            if self.data.has_key(id):
438                # remove existing table
439                self.data.pop(id)
440                self.showMetas.pop(id)
441                self.id2table[id].hide()
442                self.tabs.removeTab(self.tabs.indexOf(self.id2table[id]))
443                self.table2id.pop(self.id2table.pop(id))
444            self.data[id] = data
445            self.showMetas[id] = (True, [])
446
[10703]447            table = TableViewWithCopy() #QTableView()
[8042]448            table.setSelectionBehavior(QAbstractItemView.SelectRows)
449            table.setSortingEnabled(True)
450            table.setHorizontalScrollMode(QTableWidget.ScrollPerPixel)
451            table.horizontalHeader().setMovable(True)
452            table.horizontalHeader().setClickable(True)
453            table.horizontalHeader().setSortIndicatorShown(False)
454           
455            option = table.viewOptions()
456            size = table.style().sizeFromContents(QStyle.CT_ItemViewItem, option, QSize(20, 20), table) #QSize(20, QFontMetrics(option.font).lineSpacing()), table)
457           
458            table.verticalHeader().setDefaultSectionSize(size.height() + 2) #int(size.height() * 1.25) + 2)
459
460            self.id2table[id] = table
461            self.table2id[table] = id
462            if data.name:
463                tabName = "%s " % data.name
464            else:
465                tabName = ""
466            tabName += "(" + str(id[1]) + ")"
467            if id[2] != None:
468                tabName += " [" + str(id[2]) + "]"
469            self.tabs.addTab(table, tabName)
470
471            self.progressBarInit()
472            self.setTable(table, data)
473            self.progressBarFinished()
474            self.tabs.setCurrentIndex(self.tabs.indexOf(table))
475            self.setInfo(data)
476            self.sendButton.setEnabled(not self.autoCommit)
477
478        elif self.data.has_key(id):
479            table = self.id2table[id]
480            self.data.pop(id)
481            self.showMetas.pop(id)
482            table.hide()
483            self.tabs.removeTab(self.tabs.indexOf(table))
484            self.table2id.pop(self.id2table.pop(id))
485            self.setInfo(self.data.get(self.table2id.get(self.tabs.currentWidget(),None),None))
486
487        if len(self.data) == 0:
488            self.sendButton.setEnabled(False)
489
490        self.setCbShowMeta()
491
492    def setCbShowMeta(self):
493        for ti in range(self.tabs.count()):
494            if len(self.tabs.widget(ti).model().metas)>0:
495                self.cbShowMeta.setEnabled(True)
496                break
497        else:
498            self.cbShowMeta.setEnabled(False)
499           
500    def sendReport(self):
501        qTableInstance = self.tabs.currentWidget()
502        id = self.table2id.get(qTableInstance, None)
503        data = self.data.get(id, None)
504        self.reportData(data)
505        table = self.id2table[id]
506        import OWReport
507        self.reportRaw(OWReport.reportTable(table))
508       
509       
510    # Writes data into table, adjusts the column width.
511    def setTable(self, table, data):
512        if data==None:
513            return
514        qApp.setOverrideCursor(Qt.WaitCursor)
515        vars = data.domain.variables
516        m = data.domain.getmetas(False)
517        ml = [(k, m[k]) for k in m]
518        ml.sort(lambda x,y: cmp(y[0], x[0]))
519        metas = [x[1] for x in ml]
520        metaKeys = [x[0] for x in ml]
521
522        mo = data.domain.getmetas(True).items()
523        if mo:
524            mo.sort(lambda x,y: cmp(x[1].name.lower(),y[1].name.lower()))
525            metas.append(None)
526            metaKeys.append(None)
527
528        varsMetas = vars + metas
529
530        numVars = len(data.domain.variables)
531        numMetas = len(metas)
532        numVarsMetas = numVars + numMetas
533        numEx = len(data)
534        numSpaces = int(math.log(max(numEx,1), 10))+1
535
536#        table.clear()
537        table.oldSortingIndex = -1
538        table.oldSortingOrder = 1
539#        table.setColumnCount(numVarsMetas)
540#        table.setRowCount(numEx)
541
542        dist = getCached(data, orange.DomainBasicAttrStat, (data,))
543       
544        datamodel = ExampleTableModel(data, dist, self)
545       
546#        proxy = QSortFilterProxyModel(self)
547#        proxy.setSourceModel(datamodel)
548       
549        color_schema = self.discPalette if self.colorByClass else None
550        table.setItemDelegate(OWGUI.TableBarItem(self, color=self.distColor, color_schema=color_schema) \
551                              if self.showDistributions else QStyledItemDelegate(self)) #TableItemDelegate(self, table))
552       
553        table.setModel(datamodel)
554        def p():
555            try:
556                table.updateGeometries()
557                table.viewport().update()
558            except RuntimeError:
559                pass
560       
561        size = table.verticalHeader().sectionSizeHint(0)
562        table.verticalHeader().setDefaultSectionSize(size)
563       
564        self.connect(datamodel, SIGNAL("layoutChanged()"), lambda *args: QTimer.singleShot(50, p))
565       
566        id = self.table2id.get(table, None)
567
568        # set the header (attribute names)
569
570        self.drawAttributeLabels(table)
571
572        self.showMetas[id][1].extend([i for i, attr in enumerate(table.model().all_attrs) if attr in table.model().metas])
573        self.connect(table.horizontalHeader(), SIGNAL("sectionClicked(int)"), self.sortByColumn)
574        self.connect(table.selectionModel(), SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.updateSelection)
575        #table.verticalHeader().setMovable(False)
576
577        qApp.restoreOverrideCursor() 
578
579    def setCornerText(self, table, text):
580        """
581        Set table corner text. As this is an ugly hack, do everything in
582        try - except blocks, as it may stop working in newer Qt.
583        """
584
585        if not hasattr(table, "btn") and not hasattr(table, "btnfailed"):
586            try:
587                btn = table.findChild(QAbstractButton)
588
589                class efc(QObject):
590                    def eventFilter(self, o, e):
591                        if (e.type() == QEvent.Paint):
592                            if isinstance(o, QAbstractButton):
593                                btn = o
594                                #paint by hand (borrowed from QTableCornerButton)
595                                opt = QStyleOptionHeader()
596                                opt.init(btn)
597                                state = QStyle.State_None;
598                                if (btn.isEnabled()):
599                                    state |= QStyle.State_Enabled;
600                                if (btn.isActiveWindow()):
601                                    state |= QStyle.State_Active;
602                                if (btn.isDown()):
603                                    state |= QStyle.State_Sunken;
604                                opt.state = state;
605                                opt.rect = btn.rect();
606                                opt.text = btn.text();
607                                opt.position = QStyleOptionHeader.OnlyOneSection;
608                                painter = QStylePainter(btn);
609                                painter.drawControl(QStyle.CE_Header, opt);
610                                return True # eat evebt
611                        return False
612               
613                table.efc = efc()
614                btn.installEventFilter(table.efc)
[11731]615
616                if sys.platform == "darwin":
617                    btn.setAttribute(Qt.WA_MacSmallSize)
618
[8042]619                table.btn = btn
620            except:
621                table.btnfailed = True
622
623        if hasattr(table, "btn"):
624            try:
625                btn = table.btn
626                btn.setText(text)
627                opt = QStyleOptionHeader()
628                opt.text = btn.text()
629                s = btn.style().sizeFromContents(QStyle.CT_HeaderSection, opt, QSize(), btn).expandedTo(QApplication.globalStrut())
630                if s.isValid():
631                    table.verticalHeader().setMinimumWidth(s.width())
632                   
633            except:
634                pass
635
636    def sortByColumn(self, index):
637        table = self.tabs.currentWidget()
638        table.horizontalHeader().setSortIndicatorShown(1)
639        header = table.horizontalHeader()
640        if index == table.oldSortingIndex:
641            order = table.oldSortingOrder == Qt.AscendingOrder and Qt.DescendingOrder or Qt.AscendingOrder
642        else:
643            order = Qt.AscendingOrder
644        table.sortByColumn(index, order)
645        table.oldSortingIndex = index
646        table.oldSortingOrder = order
647        #header.setSortIndicator(index, order)
648
649    def tabClicked(self, qTableInstance):
650        """Updates the info box and showMetas checkbox when a tab is clicked.
651        """
652        id = self.table2id.get(qTableInstance,None)
653        self.setInfo(self.data.get(id,None))
654        show_col = self.showMetas.get(id,None)
655        if show_col:
656            self.cbShowMeta.setChecked(show_col[0])
657            self.cbShowMeta.setEnabled(len(show_col[1])>0)
658        self.updateSelection()
659
660    def cbShowMetaClicked(self):
661        table = self.tabs.currentWidget()
662        id = self.table2id.get(table, None)
663        if self.showMetas.has_key(id):
664            show,col = self.showMetas[id]
665            self.showMetas[id] = (not show,col)
666        if show:
667            for c in col:
668                table.hideColumn(c)
669        else:
670            for c in col:
671                table.showColumn(c)
672                table.resizeColumnToContents(c)
673
674    def drawAttributeLabels(self, table):
675#        table.setHorizontalHeaderLabels(table.variableNames)
[10601]676        table.model().show_attr_labels = bool(self.showAttributeLabels)
[8042]677        if self.showAttributeLabels:
678            labelnames = set()
679            for a in table.model().examples.domain:
680                labelnames.update(a.attributes.keys())
681            labelnames = sorted(list(labelnames))
682#            if len(labelnames):
683#                table.setHorizontalHeaderLabels([table.variableNames[i] + "\n" + "\n".join(["%s" % a.attributes.get(lab, "") for lab in labelnames]) for (i, a) in enumerate(table.data.domain.attributes)])
684            self.setCornerText(table, "\n".join([""] + labelnames))
685        else:
686            self.setCornerText(table, "")
687        table.repaint()
688
689    def cbShowAttLabelsClicked(self):
690        for table in self.table2id.keys():
691            self.drawAttributeLabels(table)
692
693    def cbShowDistributions(self):
694        for ti in range(self.tabs.count()):
695            color_schema = self.discPalette if self.colorByClass else None
696            delegate = OWGUI.TableBarItem(self, color=self.distColor,
697                                          color_schema=color_schema) \
698                       if self.showDistributions else QStyledItemDelegate(self)
699            self.tabs.widget(ti).setItemDelegate(delegate)
700        tab = self.tabs.currentWidget()
701        if tab:
702            tab.reset()
703
704    # show data in the default order
705    def btnResetSortClicked(self):
706        table = self.tabs.currentWidget()
707        if table:
708            id = self.table2id[table]
709            data = self.data[id]
710            table.horizontalHeader().setSortIndicatorShown(False)
711            self.progressBarInit()
712            self.setTable(table, data)
713            self.progressBarFinished()
714
715    def setInfo(self, data):
716        """Updates data info.
717        """
718        def sp(l, capitalize=False):
719            n = len(l)
720            if n == 0:
721                if capitalize:
722                    return "No", "s"
723                else:
724                    return "no", "s"
725            elif n == 1:
726                return str(n), ''
727            else:
728                return str(n), 's'
729
730        if data == None:
731            self.infoEx.setText('No data on input.')
732            self.infoMiss.setText('')
733            self.infoAttr.setText('')
734            self.infoMeta.setText('')
735            self.infoClass.setText('')
736        else:
737            self.infoEx.setText("%s example%s," % sp(data))
738            missData = orange.Preprocessor_takeMissing(data)
739            self.infoMiss.setText('%s (%.1f%s) with missing values.' % (len(missData), len(data) and 100.*len(missData)/len(data), "%"))
740            self.infoAttr.setText("%s attribute%s," % sp(data.domain.attributes,True))
741            self.infoMeta.setText("%s meta attribute%s." % sp(data.domain.getmetas()))
742            if data.domain.classVar:
743                if data.domain.classVar.varType == orange.VarTypes.Discrete:
744                    self.infoClass.setText('Discrete class with %s value%s.' % sp(data.domain.classVar.values))
745                elif data.domain.classVar.varType == orange.VarTypes.Continuous:
746                    self.infoClass.setText('Continuous class.')
747                else:
748                    self.infoClass.setText("Class is neither discrete nor continuous.")
749            else:
750                self.infoClass.setText('Classless domain.')
751
752    def updateSelection(self, *args):
753        self.sendButton.setEnabled(bool(self.getCurrentSelection()) and not self.autoCommit)
754        self.commitIf()
755           
756    def getCurrentSelection(self):
757        table = self.tabs.currentWidget()
758        if table and table.model():
759            model = table.model()
760            new = table.selectionModel().selectedIndexes()
761            return sorted(set([model.sorted_map[ind.row()] for ind in new]))
762       
763    def commitIf(self):
764        if self.autoCommit:
765            self.commit()
766        else:
767            self.selectionChangedFlag = True
768           
769    def commit(self):
770        table = self.tabs.currentWidget()
771        if table and table.model():
772            model = table.model()
773            selected = self.getCurrentSelection()
774            selection = [1 if i in selected else 0 for i in range(len(model.examples))]
775            data = model.examples.select(selection)
[9546]776            self.send("Selected Data", data if len(data) > 0 else None)
[8042]777            data = model.examples.select(selection, 0)
[9546]778            self.send("Other Data", data if len(data) > 0 else None)
[8042]779        else:
[9546]780            self.send("Selected Data", None)
781            self.send("Other Data", None)
[8042]782           
783        self.selectionChangedFlag = False
784
[11564]785
786def test():
[8042]787    a = QApplication(sys.argv)
788    ow = OWDataTable()
789
[11564]790    d1 = orange.ExampleTable("auto-mpg")
791    d2 = orange.ExampleTable("sponge.tab")
792    d3 = orange.ExampleTable("wpbc.csv")
793    d4 = orange.ExampleTable("adult_sample.tab")
794    d5 = orange.ExampleTable("wine.tab")
795
[8042]796    ow.show()
[11564]797    ow.dataset(d1, "auto-mpg")
798    ow.dataset(d2, "sponge")
799    ow.dataset(d3, "wpbc")
800    ow.dataset(d4, "adult_sample")
801    ow.dataset(d5, "wine")
[8042]802    a.exec_()
803    ow.saveSettings()
[11564]804
805
806if __name__ == "__main__":
807    test()
Note: See TracBrowser for help on using the repository browser.