source: orange/Orange/OrangeWidgets/Data/OWDataTable.py @ 11748:467f952c108d

Revision 11748:467f952c108d, 35.9 KB checked in by blaz <blaz.zupan@…>, 6 months ago (diff)

Changes in headers, widget descriptions text.

Line 
1import sys
2import math
3import csv
4import itertools
5import sip
6import Orange
7import OWGUI
8from StringIO import StringIO
9from xml.sax.saxutils import escape
10from functools import wraps
11from OWWidget import *
12from orngDataCaching import *
13import OWColorPalette
14
15NAME = "Data Table"
16DESCRIPTION = "Displays data in a spreadsheet."
17LONG_DESCRIPTION = ""
18ICON = "icons/DataTable.svg"
19PRIORITY = 100
20AUTHOR = "Ales Erjavec"
21AUTHOR_EMAIL = "ales.erjavec(@at@)fri.uni-lj.si"
22INPUTS = [("Data", Orange.data.Table, "dataset", Multiple + Default)]
23OUTPUTS = [("Selected Data", Orange.data.Table, Default),
24           ("Other Data", Orange.data.Table, )]
25
26
27def header_text(feature, labels=None):
28    """
29    Return an header text for an `Orange.feature.Descriptor` instance
30    `feature`. If labels is not none it should be a sequence of keys into
31    `feature.attributes` to include in the header (one per line). If the
32    `feature.attribures` does not contain a value for the key the returned
33    text will include an empty line for it.
34
35    """
36    lines = [feature.name]
37    if labels is not None:
38        lines += [str(feature.attributes.get(label, ""))
39                  for label in labels]
40    return "\n".join(lines)
41
42
43def header_tooltip(feature, labels=None):
44    """
45    Return an header tooltip text for an `Orange.feature.Decriptor` instance.
46    """
47
48    if labels is None:
49        labels = feature.attributes.keys()
50
51    pairs = [(escape(key), escape(str(feature.attributes[key])))
52             for key in labels if key in feature.attributes]
53    tip = "<b>%s</b>" % escape(feature.name)
54    tip = "<br/>".join([tip] + ["%s = %s" % pair for pair in pairs])
55    return tip
56
57
58def api_qvariant(func):
59    @wraps(func)
60    def data_get(*args, **kwargs):
61        return QVariant(func(*args, **kwargs))
62    return data_get
63
64if hasattr(sip, "getapi") and sip.getapi("QVariant") > 1:
65    def api_qvariant(func):
66        return func
67
68
69class ExampleTableModel(QAbstractItemModel):
70    Attribute, ClassVar, ClassVars, Meta = range(4)
71
72    def __init__(self, examples, dist, *args):
73        QAbstractItemModel.__init__(self, *args)
74        self.examples = examples
75        self.domain = examples.domain
76        self.dist = dist
77        self.attributes = list(examples.domain.attributes)
78        self.class_var = self.examples.domain.class_var
79        self.class_vars = list(self.examples.domain.class_vars)
80        # (meta_id, attribute) list
81        self.metas = self.examples.domain.getmetas().items()
82        # Attributes/features for all table columns
83        self.all_attrs = (self.attributes +
84                          ([self.class_var] if self.class_var else []) +
85                          self.class_vars +
86                          [attr for _, attr in self.metas])
87        # Table roles for all table columns
88        self.table_roles = \
89            (([ExampleTableModel.Attribute] * len(self.attributes)) +
90             ([ExampleTableModel.ClassVar] if self.class_var else []) +
91             ([ExampleTableModel.ClassVars] * len(self.class_vars)) +
92             ([ExampleTableModel.Meta] * len(self.metas)))
93
94        # True if an feature at column i is continuous
95        self._continuous_mask = [isinstance(attr, Orange.feature.Continuous)
96                                 for attr in self.all_attrs]
97
98        self._meta_mask = [role == ExampleTableModel.Meta
99                           for role in self.table_roles]
100
101        self.cls_color = QColor(160, 160, 160)
102        self.meta_color = QColor(220, 220, 200)
103
104        role_to_color = {ExampleTableModel.Attribute: None,
105                         ExampleTableModel.ClassVar: self.cls_color,
106                         ExampleTableModel.ClassVars: self.cls_color,
107                         ExampleTableModel.Meta: self.meta_color}
108
109        self.background_colors = map(role_to_color.get, self.table_roles)
110
111        # all attribute labels (annotation) keys
112        self.attr_labels = sorted(
113            reduce(set.union,
114                   [attr.attributes for attr in self.all_attrs],
115                   set())
116        )
117
118        # text for all header items (no attr labels by default)
119        self.header_labels = [header_text(feature)
120                              for feature in self.all_attrs]
121
122        self.sorted_map = range(len(self.examples))
123
124        self._show_attr_labels = False
125        self._other_data = {}
126
127    def get_show_attr_labels(self):
128        return self._show_attr_labels
129
130    def set_show_attr_labels(self, val):
131        if self._show_attr_labels != val:
132            self.emit(SIGNAL("layoutAboutToBeChanged()"))
133            self._show_attr_labels = val
134            if val:
135                labels = self.attr_labels
136            else:
137                labels = None
138            self.header_labels = [header_text(feature, labels)
139                                  for feature in self.all_attrs]
140
141            self.emit(SIGNAL("headerDataChanged(Qt::Orientation, int, int)"),
142                      Qt.Horizontal,
143                      0,
144                      len(self.all_attrs) - 1)
145
146            self.emit(SIGNAL("layoutChanged()"))
147
148            self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
149                      self.index(0, 0),
150                      self.index(len(self.examples) - 1,
151                                 len(self.all_attrs) - 1)
152                      )
153
154    show_attr_labels = pyqtProperty("bool",
155                                    fget=get_show_attr_labels,
156                                    fset=set_show_attr_labels)
157
158    @api_qvariant
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    @api_qvariant
234    def headerData(self, section, orientation, role):
235        if orientation == Qt.Horizontal:
236            attr = self.all_attrs[section]
237            if role == Qt.DisplayRole:
238                return self.header_labels[section]
239            if role == Qt.ToolTipRole:
240                return header_tooltip(attr, self.attr_labels)
241        else:
242            if role == Qt.DisplayRole:
243                return QVariant(section + 1)
244        return QVariant()
245
246    def sort(self, column, order=Qt.AscendingOrder):
247        self.emit(SIGNAL("layoutAboutToBeChanged()"))
248        attr = self.all_attrs[column] 
249        values = [(ex[attr], i) for i, ex in enumerate(self.examples)]
250        values = sorted(values, reverse=order != Qt.AscendingOrder)
251        self.sorted_map = [v[1] for v in values]
252        self.emit(SIGNAL("layoutChanged()"))
253        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
254                  self.index(0,0),
255                  self.index(len(self.examples) - 1, len(self.all_attrs) - 1)
256                  )
257
258
259class TableViewWithCopy(QTableView):
260    def keyPressEvent(self, event):
261        if event == QKeySequence.Copy:
262            sel_model = self.selectionModel()
263            try:
264                self.copy_selection_to_clipboard(sel_model)
265            except Exception:
266                import traceback
267                traceback.print_exc(file=sys.stderr)
268        else:
269            return QTableView.keyPressEvent(self, event)
270
271    def copy_selection_to_clipboard(self, selection_model):
272        """
273        Copy table selection to the clipboard.
274        """
275        # TODO: html/rtf table
276        mime = table_selection_to_mime_data(self)
277        QApplication.clipboard().setMimeData(mime, QClipboard.Clipboard)
278
279
280def table_selection_to_mime_data(table):
281    lines = table_selection_to_list(table)
282
283    csv = lines_to_csv_string(lines, dialect="excel")
284    tsv = lines_to_csv_string(lines, dialect="excel-tab")
285
286    mime = QMimeData()
287    mime.setData("text/csv", QByteArray(csv))
288    mime.setData("text/tab-separated-values", QByteArray(tsv))
289    mime.setData("text/plain", QByteArray(tsv))
290    return mime
291
292
293def lines_to_csv_string(lines, dialect="excel"):
294    stream = StringIO()
295    writer = csv.writer(stream, dialect=dialect)
296    writer.writerows(lines)
297    return stream.getvalue()
298
299
300def table_selection_to_list(table):
301    model = table.model()
302    indexes = table.selectedIndexes()
303
304    rows = sorted(set(index.row() for index in indexes))
305    columns = sorted(set(index.column() for index in indexes))
306
307    lines = []
308    for row in rows:
309        line = []
310        for col in columns:
311            val = model.index(row, col).data(Qt.DisplayRole)
312            # TODO: use style item delegate displayText?
313            line.append(unicode(val.toString()))
314        lines.append(line)
315
316    return lines
317
318
319class OWDataTable(OWWidget):
320    settingsList = [
321        "showDistributions", "showMeta", "distColorRgb",
322        "showAttributeLabels", "autoCommit", "selectedSchemaIndex",
323        "colorByClass", "selectRows"]
324
325    def __init__(self, parent=None, signalManager = None):
326        OWWidget.__init__(self, parent, signalManager, "Data Table")
327
328        self.inputs = [("Data", ExampleTable, self.dataset, Multiple + Default)]
329        self.outputs = [("Selected Data", ExampleTable, Default), ("Other Data", ExampleTable)]
330
331        self.data = {}          # key: id, value: ExampleTable
332        self.showMetas = {}     # key: id, value: (True/False, columnList)
333        self.showMeta = 1
334        self.showAttributeLabels = 1
335        self.showDistributions = 1
336        self.distColorRgb = (220,220,220, 255)
337        self.distColor = QColor(*self.distColorRgb)
338        self.locale = QLocale()
339        self.autoCommit = False
340        self.colorSettings = None
341        self.selectedSchemaIndex = 0
342        self.colorByClass = True
343        self.selectRows = True
344
345        self.loadSettings()
346
347        # info box
348        infoBox = OWGUI.widgetBox(self.controlArea, "Info")
349        self.infoEx = OWGUI.widgetLabel(infoBox, 'No data on input.')
350        self.infoMiss = OWGUI.widgetLabel(infoBox, ' ')
351        OWGUI.widgetLabel(infoBox, ' ')
352        self.infoAttr = OWGUI.widgetLabel(infoBox, ' ')
353        self.infoMeta = OWGUI.widgetLabel(infoBox, ' ')
354        OWGUI.widgetLabel(infoBox, ' ')
355        self.infoClass = OWGUI.widgetLabel(infoBox, ' ')
356        infoBox.setMinimumWidth(200)
357        OWGUI.separator(self.controlArea)
358
359        # settings box
360        boxSettings = OWGUI.widgetBox(self.controlArea, "Settings", addSpace=True)
361        self.cbShowMeta = OWGUI.checkBox(boxSettings, self, "showMeta", 'Show meta attributes', callback = self.cbShowMetaClicked)
362        self.cbShowMeta.setEnabled(False)
363        self.cbShowAttLbls = OWGUI.checkBox(boxSettings, self, "showAttributeLabels", 'Show attribute labels (if any)', callback = self.cbShowAttLabelsClicked)
364        self.cbShowAttLbls.setEnabled(True)
365
366        box = OWGUI.widgetBox(self.controlArea, "Colors")
367        OWGUI.checkBox(box, self, "showDistributions", 'Visualize continuous values', callback = self.cbShowDistributions)
368        OWGUI.checkBox(box, self, "colorByClass", 'Color by class value', callback = self.cbShowDistributions)
369        OWGUI.button(box, self, "Set colors", self.setColors, tooltip = "Set the canvas background color and color palette for coloring continuous variables", debuggingEnabled = 0)
370
371        resizeColsBox = OWGUI.widgetBox(boxSettings, 0, "horizontal", 0)
372        OWGUI.label(resizeColsBox, self, "Resize columns: ")
373        OWGUI.toolButton(resizeColsBox, self, "+", self.increaseColWidth, tooltip = "Increase the width of the columns", width=20, height=20)
374        OWGUI.toolButton(resizeColsBox, self, "-", self.decreaseColWidth, tooltip = "Decrease the width of the columns", width=20, height=20)
375        OWGUI.rubber(resizeColsBox)
376
377        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")
378
379        OWGUI.separator(self.controlArea)
380
381        box = OWGUI.widgetBox(self.controlArea, "Selection")
382        OWGUI.checkBox(box, self, "selectRows", "Select rows",
383                       callback=self.onSelectRowsChanged
384        )
385
386        cb = OWGUI.checkBox(box, self, "autoCommit", "Commit on any change", callback=self.commitIf)
387        self.sendButton = OWGUI.button(box, self, "Send selections", self.commit, default=True)
388
389        OWGUI.setStopper(self, self.sendButton, cb, "selectionChangedFlag", self.commit)
390
391        OWGUI.rubber(self.controlArea)
392
393        dlg = self.createColorDialog()
394        self.discPalette = dlg.getDiscretePalette("discPalette")
395
396        # GUI with tabs
397        self.tabs = OWGUI.tabWidget(self.mainArea)
398        self.id2table = {}  # key: widget id, value: table
399        self.table2id = {}  # key: table, value: widget id
400        self.connect(self.tabs, SIGNAL("currentChanged(int)"),
401                     self.onCurrentTabChanged)
402
403        self.selectionChangedFlag = False
404
405    def createColorDialog(self):
406        c = OWColorPalette.ColorPaletteDlg(self, "Color Palette")
407        c.createDiscretePalette("discPalette", "Discrete Palette")
408        box = c.createBox("otherColors", "Other Colors")
409        c.createColorButton(box, "Default", "Default color", QColor(Qt.white))
410        c.setColorSchemas(self.colorSettings, self.selectedSchemaIndex)
411        return c
412
413    def setColors(self):
414        dlg = self.createColorDialog()
415        if dlg.exec_():
416            self.colorSettings = dlg.getColorSchemas()
417            self.selectedSchemaIndex = dlg.selectedSchemaIndex
418            self.discPalette = dlg.getDiscretePalette("discPalette")
419            self.distColor = color = dlg.getColor("Default")
420            self.distColorRgb = (color.red(), color.green(), color.red())
421
422            if self.showDistributions:
423                # Update the views
424                self.cbShowDistributions()
425
426    def increaseColWidth(self):
427        table = self.tabs.currentWidget()
428        if table:
429            for col in range(table.model().columnCount(QModelIndex())):
430                w = table.columnWidth(col)
431                table.setColumnWidth(col, w + 10)
432
433    def decreaseColWidth(self):
434        table = self.tabs.currentWidget()
435        if table:
436            for col in range(table.model().columnCount(QModelIndex())):
437                w = table.columnWidth(col)
438                minW = table.sizeHintForColumn(col)
439                table.setColumnWidth(col, max(w - 10, minW))
440
441
442    def dataset(self, data, id=None):
443        """Generates a new table and adds it to a new tab when new data arrives;
444        or hides the table and removes a tab when data==None;
445        or replaces the table when new data arrives together with already existing id."""
446        if data != None:  # can be an empty table!
447            if self.data.has_key(id):
448                # remove existing table
449                self.data.pop(id)
450                self.showMetas.pop(id)
451                self.id2table[id].hide()
452                self.tabs.removeTab(self.tabs.indexOf(self.id2table[id]))
453                self.table2id.pop(self.id2table.pop(id))
454            self.data[id] = data
455            self.showMetas[id] = (True, [])
456
457            table = TableViewWithCopy() #QTableView()
458
459            if self.selectRows:
460                table.setSelectionBehavior(QTableView.SelectRows)
461            else:
462                table.setSelectionBehavior(QTableView.SelectItems)
463
464            table.setSortingEnabled(True)
465            table.setHorizontalScrollMode(QTableWidget.ScrollPerPixel)
466            table.horizontalHeader().setMovable(True)
467            table.horizontalHeader().setClickable(True)
468            table.horizontalHeader().setSortIndicatorShown(False)
469           
470            option = table.viewOptions()
471            size = table.style().sizeFromContents(QStyle.CT_ItemViewItem, option, QSize(20, 20), table) #QSize(20, QFontMetrics(option.font).lineSpacing()), table)
472           
473            table.verticalHeader().setDefaultSectionSize(size.height() + 2) #int(size.height() * 1.25) + 2)
474
475            self.id2table[id] = table
476            self.table2id[table] = id
477            if data.name:
478                tabName = "%s " % data.name
479            else:
480                tabName = ""
481            tabName += "(" + str(id[1]) + ")"
482            if id[2] != None:
483                tabName += " [" + str(id[2]) + "]"
484            self.tabs.addTab(table, tabName)
485
486            self.progressBarInit()
487            self.setTable(table, data)
488            self.progressBarFinished()
489            self.tabs.setCurrentIndex(self.tabs.indexOf(table))
490            self.setInfo(data)
491            self.sendButton.setEnabled(not self.autoCommit)
492
493        elif self.data.has_key(id):
494            table = self.id2table[id]
495            self.data.pop(id)
496            self.showMetas.pop(id)
497            table.hide()
498            self.tabs.removeTab(self.tabs.indexOf(table))
499            self.table2id.pop(self.id2table.pop(id))
500            self.setInfo(self.data.get(self.table2id.get(self.tabs.currentWidget(),None),None))
501
502        if len(self.data) == 0:
503            self.sendButton.setEnabled(False)
504
505        self.setCbShowMeta()
506
507    def setCbShowMeta(self):
508        for ti in range(self.tabs.count()):
509            if len(self.tabs.widget(ti).model().metas)>0:
510                self.cbShowMeta.setEnabled(True)
511                break
512        else:
513            self.cbShowMeta.setEnabled(False)
514           
515    def sendReport(self):
516        qTableInstance = self.tabs.currentWidget()
517        id = self.table2id.get(qTableInstance, None)
518        data = self.data.get(id, None)
519        self.reportData(data)
520        table = self.id2table[id]
521        import OWReport
522        self.reportRaw(OWReport.reportTable(table))
523       
524       
525    # Writes data into table, adjusts the column width.
526    def setTable(self, table, data):
527        if data==None:
528            return
529        qApp.setOverrideCursor(Qt.WaitCursor)
530        vars = data.domain.variables
531        m = data.domain.getmetas(False)
532        ml = [(k, m[k]) for k in m]
533        ml.sort(lambda x,y: cmp(y[0], x[0]))
534        metas = [x[1] for x in ml]
535        metaKeys = [x[0] for x in ml]
536
537        mo = data.domain.getmetas(True).items()
538        if mo:
539            mo.sort(lambda x,y: cmp(x[1].name.lower(),y[1].name.lower()))
540            metas.append(None)
541            metaKeys.append(None)
542
543        varsMetas = vars + metas
544
545        numVars = len(data.domain.variables)
546        numMetas = len(metas)
547        numVarsMetas = numVars + numMetas
548        numEx = len(data)
549        numSpaces = int(math.log(max(numEx,1), 10))+1
550
551#        table.clear()
552        table.oldSortingIndex = -1
553        table.oldSortingOrder = 1
554#        table.setColumnCount(numVarsMetas)
555#        table.setRowCount(numEx)
556
557        dist = getCached(data, orange.DomainBasicAttrStat, (data,))
558       
559        datamodel = ExampleTableModel(data, dist, self)
560       
561#        proxy = QSortFilterProxyModel(self)
562#        proxy.setSourceModel(datamodel)
563       
564        color_schema = self.discPalette if self.colorByClass else None
565        table.setItemDelegate(OWGUI.TableBarItem(self, color=self.distColor, color_schema=color_schema) \
566                              if self.showDistributions else QStyledItemDelegate(self)) #TableItemDelegate(self, table))
567       
568        table.setModel(datamodel)
569        def p():
570            try:
571                table.updateGeometries()
572                table.viewport().update()
573            except RuntimeError:
574                pass
575       
576        size = table.verticalHeader().sectionSizeHint(0)
577        table.verticalHeader().setDefaultSectionSize(size)
578       
579        self.connect(datamodel, SIGNAL("layoutChanged()"), lambda *args: QTimer.singleShot(50, p))
580       
581        id = self.table2id.get(table, None)
582
583        # set the header (attribute names)
584
585        self.drawAttributeLabels(table)
586
587        sel_model = BlockSelectionModel(datamodel)
588        sel_model.setSelectBlocks(not self.selectRows)
589        table.setSelectionModel(sel_model)
590
591        # meta column indices
592        metacol = range(len(datamodel.all_attrs) - len(datamodel.metas),
593                        len(datamodel.all_attrs))
594        self.showMetas[id][1].extend(metacol)
595        self.connect(table.horizontalHeader(), SIGNAL("sectionClicked(int)"), self.sortByColumn)
596        self.connect(table.selectionModel(), SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.updateSelection)
597        #table.verticalHeader().setMovable(False)
598
599        qApp.restoreOverrideCursor() 
600
601    def setCornerText(self, table, text):
602        """
603        Set table corner text. As this is an ugly hack, do everything in
604        try - except blocks, as it may stop working in newer Qt.
605        """
606
607        if not hasattr(table, "btn") and not hasattr(table, "btnfailed"):
608            try:
609                btn = table.findChild(QAbstractButton)
610
611                class efc(QObject):
612                    def eventFilter(self, o, e):
613                        if (e.type() == QEvent.Paint):
614                            if isinstance(o, QAbstractButton):
615                                btn = o
616                                #paint by hand (borrowed from QTableCornerButton)
617                                opt = QStyleOptionHeader()
618                                opt.init(btn)
619                                state = QStyle.State_None;
620                                if (btn.isEnabled()):
621                                    state |= QStyle.State_Enabled;
622                                if (btn.isActiveWindow()):
623                                    state |= QStyle.State_Active;
624                                if (btn.isDown()):
625                                    state |= QStyle.State_Sunken;
626                                opt.state = state;
627                                opt.rect = btn.rect();
628                                opt.text = btn.text();
629                                opt.position = QStyleOptionHeader.OnlyOneSection;
630                                painter = QStylePainter(btn);
631                                painter.drawControl(QStyle.CE_Header, opt);
632                                return True # eat evebt
633                        return False
634               
635                table.efc = efc()
636                btn.installEventFilter(table.efc)
637
638                if sys.platform == "darwin":
639                    btn.setAttribute(Qt.WA_MacSmallSize)
640
641                table.btn = btn
642            except:
643                table.btnfailed = True
644
645        if hasattr(table, "btn"):
646            try:
647                btn = table.btn
648                btn.setText(text)
649                opt = QStyleOptionHeader()
650                opt.text = btn.text()
651                s = btn.style().sizeFromContents(QStyle.CT_HeaderSection, opt, QSize(), btn).expandedTo(QApplication.globalStrut())
652                if s.isValid():
653                    table.verticalHeader().setMinimumWidth(s.width())
654                   
655            except:
656                pass
657
658    def sortByColumn(self, index):
659        table = self.tabs.currentWidget()
660        table.horizontalHeader().setSortIndicatorShown(1)
661        header = table.horizontalHeader()
662        if index == table.oldSortingIndex:
663            order = table.oldSortingOrder == Qt.AscendingOrder and Qt.DescendingOrder or Qt.AscendingOrder
664        else:
665            order = Qt.AscendingOrder
666        table.sortByColumn(index, order)
667        table.oldSortingIndex = index
668        table.oldSortingOrder = order
669        #header.setSortIndicator(index, order)
670
671    def onCurrentTabChanged(self, index):
672        """
673        Updates the info box and showMetas checkbox when a tab is clicked.
674        """
675        if index == -1:
676            self.setInfo(None)
677            return
678
679        tableview = self.tabs.widget(index)
680        id = self.table2id.get(tableview, None)
681        self.setInfo(self.data.get(id, None))
682        show_col = self.showMetas.get(id, None)
683        if show_col is not None:
684            self.cbShowMeta.setChecked(show_col[0])
685            self.cbShowMeta.setEnabled(len(show_col[1]) > 0)
686        self.updateSelection()
687
688    def cbShowMetaClicked(self):
689        table = self.tabs.currentWidget()
690        id = self.table2id.get(table, None)
691        if id in self.showMetas:
692            _, meta_cols = self.showMetas[id]
693            self.showMetas[id] = (self.showMeta, meta_cols)
694            for c in meta_cols:
695                table.setColumnHidden(c, not self.showMeta)
696
697    def drawAttributeLabels(self, table):
698#        table.setHorizontalHeaderLabels(table.variableNames)
699        table.model().show_attr_labels = bool(self.showAttributeLabels)
700        if self.showAttributeLabels:
701            labelnames = set()
702            for a in table.model().examples.domain:
703                labelnames.update(a.attributes.keys())
704            labelnames = sorted(list(labelnames))
705#            if len(labelnames):
706#                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)])
707            self.setCornerText(table, "\n".join([""] + labelnames))
708        else:
709            self.setCornerText(table, "")
710        table.repaint()
711
712    def cbShowAttLabelsClicked(self):
713        for table in self.table2id.keys():
714            self.drawAttributeLabels(table)
715
716    def cbShowDistributions(self):
717        for ti in range(self.tabs.count()):
718            color_schema = self.discPalette if self.colorByClass else None
719            delegate = OWGUI.TableBarItem(self, color=self.distColor,
720                                          color_schema=color_schema) \
721                       if self.showDistributions else QStyledItemDelegate(self)
722            self.tabs.widget(ti).setItemDelegate(delegate)
723        tab = self.tabs.currentWidget()
724        if tab:
725            tab.reset()
726
727    # show data in the default order
728    def btnResetSortClicked(self):
729        table = self.tabs.currentWidget()
730        if table:
731            id = self.table2id[table]
732            data = self.data[id]
733            table.horizontalHeader().setSortIndicatorShown(False)
734            self.progressBarInit()
735            self.setTable(table, data)
736            self.progressBarFinished()
737
738    def setInfo(self, data):
739        """Updates data info.
740        """
741        def sp(l, capitalize=False):
742            n = len(l)
743            if n == 0:
744                if capitalize:
745                    return "No", "s"
746                else:
747                    return "no", "s"
748            elif n == 1:
749                return str(n), ''
750            else:
751                return str(n), 's'
752
753        if data == None:
754            self.infoEx.setText('No data on input.')
755            self.infoMiss.setText('')
756            self.infoAttr.setText('')
757            self.infoMeta.setText('')
758            self.infoClass.setText('')
759        else:
760            self.infoEx.setText("%s example%s," % sp(data))
761            missData = orange.Preprocessor_takeMissing(data)
762            self.infoMiss.setText('%s (%.1f%s) with missing values.' % (len(missData), len(data) and 100.*len(missData)/len(data), "%"))
763            self.infoAttr.setText("%s attribute%s," % sp(data.domain.attributes,True))
764            self.infoMeta.setText("%s meta attribute%s." % sp(data.domain.getmetas()))
765            if data.domain.classVar:
766                if data.domain.classVar.varType == orange.VarTypes.Discrete:
767                    self.infoClass.setText('Discrete class with %s value%s.' % sp(data.domain.classVar.values))
768                elif data.domain.classVar.varType == orange.VarTypes.Continuous:
769                    self.infoClass.setText('Continuous class.')
770                else:
771                    self.infoClass.setText("Class is neither discrete nor continuous.")
772            else:
773                self.infoClass.setText('Classless domain.')
774
775    def onSelectRowsChanged(self):
776        for table in self.table2id.keys():
777            selection_model = table.selectionModel()
778            selection_model.setSelectBlocks(not self.selectRows)
779            if self.selectRows:
780                table.setSelectionBehavior(QTableView.SelectRows)
781                # Expand the current selection to row selection.
782                selection_model.select(
783                    selection_model.selection(),
784                    QItemSelectionModel.Select | QItemSelectionModel.Rows
785                )
786            else:
787                table.setSelectionBehavior(QTableView.SelectItems)
788
789    def updateSelection(self, *args):
790        self.sendButton.setEnabled(bool(self.getCurrentSelection()) and
791                                   not self.autoCommit)
792        self.commitIf()
793
794    def getCurrentSelection(self):
795        table = self.tabs.currentWidget()
796        if table and table.model():
797            model = table.model()
798            new = table.selectionModel().selectedIndexes()
799            return sorted(set([model.sorted_map[ind.row()] for ind in new]))
800
801    def getCurrentColumnSelection(self):
802        view = self.tabs.currentWidget()
803        if view and view.model():
804            model = view.model()
805            indexes = view.selectionModel().selectedIndexes()
806            cols = sorted(set(ind.column() for ind in indexes))
807            roles = list(enumerate(model.table_roles))
808            roles = [(col, role) for col, role in roles if col in cols]
809
810            def select(role):
811                return [i for i, r in roles if r == role]
812
813            attrs = select(ExampleTableModel.Attribute)
814            class_var = select(ExampleTableModel.ClassVar)
815            class_vars = select(ExampleTableModel.ClassVars)
816            metas = select(ExampleTableModel.Meta)
817
818            return attrs, class_var, class_vars, metas
819        else:
820            return [], [], [], []
821
822    def getOutputDomain(self):
823        view = self.tabs.currentWidget()
824        if view and view.model():
825            model = view.model()
826            attrs, class_var, class_vars, metas = \
827                self.getCurrentColumnSelection()
828
829            domain = model.examples.domain
830            attrs = [domain[i] for i in attrs]
831            class_var = [domain[i] for i in class_var]
832            meta_offset = len(model.all_attrs) - len(model.metas)
833            class_vars_offset = meta_offset - len(model.class_vars)
834
835            class_vars = [domain.class_vars[i - class_vars_offset]
836                          for i in class_vars]
837            # map column indices into (meta_id, attr) tuples
838            metas = [model.metas[i - meta_offset] for i in metas]
839            if class_var:
840                class_var = class_var[0]
841            else:
842                class_var = None
843
844            domain = Orange.data.Domain(
845                attrs, class_var, class_vars=class_vars
846            )
847            domain.addmetas(dict(metas))
848
849            return domain
850
851    def commitIf(self):
852        if self.autoCommit:
853            self.commit()
854        else:
855            self.selectionChangedFlag = True
856
857    def commit(self):
858        selected = other = None
859        table = self.tabs.currentWidget()
860        if table and table.model():
861            model = table.model()
862            selected = set(self.getCurrentSelection())
863            selection = [1 if i in selected else 0 for
864                         i in range(len(model.examples))]
865
866            selected = model.examples.select(selection)
867            other = model.examples.select(selection, 0)
868
869            if not self.selectRows:
870                domain = self.getOutputDomain()
871                selected = Orange.data.Table(domain, selected)
872                other = Orange.data.Table(domain, other)
873
874            selected = selected if len(selected) > 0 else None
875            other = other if len(other) > 0 else None
876
877        self.send("Selected Data", selected)
878        self.send("Other Data", other)
879
880        self.selectionChangedFlag = False
881
882
883class BlockSelectionModel(QItemSelectionModel):
884    def __init__(self, *args, **kwargs):
885        super(QItemSelectionModel, self).__init__(*args, **kwargs)
886        self._selectBlocks = True
887
888    def select(self, selection, flags):
889        if isinstance(selection, QModelIndex):
890            selection = QItemSelection(selection, selection)
891
892        model = self.model()
893        indexes = self.selectedIndexes()
894
895        rows = set(ind.row() for ind in indexes)
896        cols = set(ind.column() for ind in indexes)
897
898        if flags & QItemSelectionModel.Select and \
899                not flags & QItemSelectionModel.Clear and self._selectBlocks:
900            indexes = selection.indexes()
901            sel_rows = set(ind.row() for ind in indexes).union(rows)
902            sel_cols = set(ind.column() for ind in indexes).union(cols)
903
904            selection = QItemSelection()
905
906            for r_start, r_end in ranges(sorted(sel_rows)):
907                for c_start, c_end in ranges(sorted(sel_cols)):
908                    top_left = model.index(r_start, c_start)
909                    bottom_right = model.index(r_end - 1, c_end - 1)
910                    selection.select(top_left, bottom_right)
911
912        QItemSelectionModel.select(self, selection, flags)
913
914    def selectBlock(self):
915        return self._selectBlocks
916
917    def setSelectBlocks(self, state):
918        self._selectBlocks = state
919
920
921def ranges(indices):
922    g = itertools.groupby(enumerate(indices),
923                          key=lambda t: t[1] - t[0])
924    for _, range_ind in g:
925        range_ind = list(range_ind)
926        _, start = range_ind[0]
927        _, end = range_ind[-1]
928        yield start, end + 1
929
930if __name__ == "__main__":
931    a = QApplication(sys.argv)
932    ow = OWDataTable()
933
934    d1 = orange.ExampleTable("auto-mpg")
935    d2 = orange.ExampleTable("sponge.tab")
936    d3 = orange.ExampleTable("wpbc.csv")
937    d4 = orange.ExampleTable("adult_sample.tab")
938    d5 = orange.ExampleTable("wine.tab")
939
940    ow.show()
941    ow.dataset(d1, "auto-mpg")
942    ow.dataset(d2, "sponge")
943    ow.dataset(d3, "wpbc")
944    ow.dataset(d4, "adult_sample")
945    ow.dataset(d5, "wine")
946    a.exec_()
947    ow.saveSettings()
Note: See TracBrowser for help on using the repository browser.