source: orange/Orange/OrangeWidgets/Data/OWDataTable.py @ 11573:f92bad28c999

Revision 11573:f92bad28c999, 30.7 KB checked in by Ales Erjavec <ales.erjavec@…>, 11 months ago (diff)

Update the table view after color selection.

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