Ignore:
Timestamp:
10/23/13 13:37:37 (6 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

Added an option to select individual columns in Data Table widget.

It is now possible to select columns/variables to include in output and/or
clipboard selection.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • Orange/OrangeWidgets/Data/OWDataTable.py

    r11731 r11743  
    88 
    99import sys 
     10import math 
     11import csv 
     12import itertools 
     13 
     14from StringIO import StringIO 
    1015from xml.sax.saxutils import escape 
    1116from functools import wraps 
     
    1722from OWWidget import * 
    1823import OWGUI 
    19 import math 
    2024from orngDataCaching import * 
    2125import OWColorPalette 
     
    96100        self.class_var = self.examples.domain.class_var 
    97101        self.class_vars = list(self.examples.domain.class_vars) 
    98         self.metas = self.examples.domain.getmetas().values() 
     102        # (meta_id, attribute) list 
     103        self.metas = self.examples.domain.getmetas().items() 
    99104        # Attributes/features for all table columns 
    100105        self.all_attrs = (self.attributes + 
    101106                          ([self.class_var] if self.class_var else []) + 
    102                           self.class_vars + self.metas) 
     107                          self.class_vars + 
     108                          [attr for _, attr in self.metas]) 
    103109        # Table roles for all table columns 
    104110        self.table_roles = \ 
     
    264270        attr = self.all_attrs[column]  
    265271        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)) 
     272        values = sorted(values, reverse=order != Qt.AscendingOrder) 
    267273        self.sorted_map = [v[1] for v in values] 
    268274        self.emit(SIGNAL("layoutChanged()")) 
     
    284290        else: 
    285291            return QTableView.keyPressEvent(self, event) 
    286              
     292 
    287293    def copy_selection_to_clipboard(self, selection_model): 
    288         """Copy table selection to the clipboard. 
     294        """ 
     295        Copy table selection to the clipboard. 
    289296        """ 
    290297        # 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)) 
     298        mime = table_selection_to_mime_data(self) 
    316299        QApplication.clipboard().setMimeData(mime, QClipboard.Clipboard) 
    317300 
    318301 
     302def table_selection_to_mime_data(table): 
     303    lines = table_selection_to_list(table) 
     304 
     305    csv = lines_to_csv_string(lines, dialect="excel") 
     306    tsv = lines_to_csv_string(lines, dialect="excel-tab") 
     307 
     308    mime = QMimeData() 
     309    mime.setData("text/csv", QByteArray(csv)) 
     310    mime.setData("text/tab-separated-values", QByteArray(tsv)) 
     311    mime.setData("text/plain", QByteArray(tsv)) 
     312    return mime 
     313 
     314 
     315def lines_to_csv_string(lines, dialect="excel"): 
     316    stream = StringIO() 
     317    writer = csv.writer(stream, dialect=dialect) 
     318    writer.writerows(lines) 
     319    return stream.getvalue() 
     320 
     321 
     322def table_selection_to_list(table): 
     323    model = table.model() 
     324    indexes = table.selectedIndexes() 
     325 
     326    rows = sorted(set(index.row() for index in indexes)) 
     327    columns = sorted(set(index.column() for index in indexes)) 
     328 
     329    lines = [] 
     330    for row in rows: 
     331        line = [] 
     332        for col in columns: 
     333            val = model.index(row, col).data(Qt.DisplayRole) 
     334            # TODO: use style item delegate displayText? 
     335            line.append(unicode(val.toString())) 
     336        lines.append(line) 
     337 
     338    return lines 
     339 
     340 
    319341class OWDataTable(OWWidget): 
    320     settingsList = ["showDistributions", "showMeta", "distColorRgb", "showAttributeLabels", "autoCommit", "selectedSchemaIndex", "colorByClass"] 
     342    settingsList = [ 
     343        "showDistributions", "showMeta", "distColorRgb", 
     344        "showAttributeLabels", "autoCommit", "selectedSchemaIndex", 
     345        "colorByClass", "selectRows"] 
    321346 
    322347    def __init__(self, parent=None, signalManager = None): 
     
    338363        self.selectedSchemaIndex = 0 
    339364        self.colorByClass = True 
    340          
     365        self.selectRows = True 
     366 
    341367        self.loadSettings() 
    342368 
     
    372398 
    373399        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          
     400 
    375401        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) 
     402 
     403        box = OWGUI.widgetBox(self.controlArea, "Selection") 
     404        OWGUI.checkBox(box, self, "selectRows", "Select rows", 
     405                       callback=self.onSelectRowsChanged 
     406        ) 
     407 
     408        cb = OWGUI.checkBox(box, self, "autoCommit", "Commit on any change", callback=self.commitIf) 
     409        self.sendButton = OWGUI.button(box, self, "Send selections", self.commit, default=True) 
     410 
    379411        OWGUI.setStopper(self, self.sendButton, cb, "selectionChangedFlag", self.commit) 
    380412 
     
    446478 
    447479            table = TableViewWithCopy() #QTableView() 
    448             table.setSelectionBehavior(QAbstractItemView.SelectRows) 
     480 
     481            if self.selectRows: 
     482                table.setSelectionBehavior(QTableView.SelectRows) 
     483            else: 
     484                table.setSelectionBehavior(QTableView.SelectItems) 
     485 
    449486            table.setSortingEnabled(True) 
    450487            table.setHorizontalScrollMode(QTableWidget.ScrollPerPixel) 
     
    570607        self.drawAttributeLabels(table) 
    571608 
    572         self.showMetas[id][1].extend([i for i, attr in enumerate(table.model().all_attrs) if attr in table.model().metas]) 
     609        sel_model = BlockSelectionModel(datamodel) 
     610        sel_model.setSelectBlocks(not self.selectRows) 
     611        table.setSelectionModel(sel_model) 
     612 
     613        # meta column indices 
     614        metacol = range(len(datamodel.all_attrs) - len(datamodel.metas), 
     615                        len(datamodel.all_attrs)) 
     616        self.showMetas[id][1].extend(metacol) 
    573617        self.connect(table.horizontalHeader(), SIGNAL("sectionClicked(int)"), self.sortByColumn) 
    574618        self.connect(table.selectionModel(), SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.updateSelection) 
     
    750794                self.infoClass.setText('Classless domain.') 
    751795 
     796    def onSelectRowsChanged(self): 
     797        for table in self.table2id.keys(): 
     798            selection_model = table.selectionModel() 
     799            selection_model.setSelectBlocks(not self.selectRows) 
     800            if self.selectRows: 
     801                table.setSelectionBehavior(QTableView.SelectRows) 
     802                # Expand the current selection to row selection. 
     803                selection_model.select( 
     804                    selection_model.selection(), 
     805                    QItemSelectionModel.Select | QItemSelectionModel.Rows 
     806                ) 
     807            else: 
     808                table.setSelectionBehavior(QTableView.SelectItems) 
     809 
    752810    def updateSelection(self, *args): 
    753         self.sendButton.setEnabled(bool(self.getCurrentSelection()) and not self.autoCommit) 
     811        self.sendButton.setEnabled(bool(self.getCurrentSelection()) and 
     812                                   not self.autoCommit) 
    754813        self.commitIf() 
    755              
     814 
    756815    def getCurrentSelection(self): 
    757816        table = self.tabs.currentWidget() 
     
    760819            new = table.selectionModel().selectedIndexes() 
    761820            return sorted(set([model.sorted_map[ind.row()] for ind in new])) 
    762          
     821 
     822    def getCurrentColumnSelection(self): 
     823        view = self.tabs.currentWidget() 
     824        if view and view.model(): 
     825            model = view.model() 
     826            indexes = view.selectionModel().selectedIndexes() 
     827            cols = sorted(set(ind.column() for ind in indexes)) 
     828            roles = list(enumerate(model.table_roles)) 
     829            roles = [(col, role) for col, role in roles if col in cols] 
     830 
     831            def select(role): 
     832                return [i for i, r in roles if r == role] 
     833 
     834            attrs = select(ExampleTableModel.Attribute) 
     835            class_var = select(ExampleTableModel.ClassVar) 
     836            class_vars = select(ExampleTableModel.ClassVars) 
     837            metas = select(ExampleTableModel.Meta) 
     838 
     839            return attrs, class_var, class_vars, metas 
     840        else: 
     841            return [], [], [], [] 
     842 
     843    def getOutputDomain(self): 
     844        view = self.tabs.currentWidget() 
     845        if view and view.model(): 
     846            model = view.model() 
     847            attrs, class_var, class_vars, metas = \ 
     848                self.getCurrentColumnSelection() 
     849 
     850            domain = model.examples.domain 
     851            attrs = [domain[i] for i in attrs] 
     852            class_var = [domain[i] for i in class_var] 
     853            meta_offset = len(model.all_attrs) - len(model.metas) 
     854            class_vars_offset = meta_offset - len(model.class_vars) 
     855 
     856            class_vars = [domain.class_vars[i - class_vars_offset] 
     857                          for i in class_vars] 
     858            # map column indices into (meta_id, attr) tuples 
     859            metas = [model.metas[i - meta_offset] for i in metas] 
     860            if class_var: 
     861                class_var = class_var[0] 
     862            else: 
     863                class_var = None 
     864 
     865            domain = Orange.data.Domain( 
     866                attrs, class_var, class_vars=class_vars 
     867            ) 
     868            domain.addmetas(dict(metas)) 
     869 
     870            return domain 
     871 
    763872    def commitIf(self): 
    764873        if self.autoCommit: 
     
    766875        else: 
    767876            self.selectionChangedFlag = True 
    768              
     877 
    769878    def commit(self): 
     879        selected = other = None 
    770880        table = self.tabs.currentWidget() 
    771881        if table and table.model(): 
    772882            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) 
    776             self.send("Selected Data", data if len(data) > 0 else None) 
    777             data = model.examples.select(selection, 0) 
    778             self.send("Other Data", data if len(data) > 0 else None) 
    779         else: 
    780             self.send("Selected Data", None) 
    781             self.send("Other Data", None) 
    782              
     883            selected = set(self.getCurrentSelection()) 
     884            selection = [1 if i in selected else 0 for 
     885                         i in range(len(model.examples))] 
     886 
     887            selected = model.examples.select(selection) 
     888            other = model.examples.select(selection, 0) 
     889 
     890            if not self.selectRows: 
     891                domain = self.getOutputDomain() 
     892                selected = Orange.data.Table(domain, selected) 
     893                other = Orange.data.Table(domain, other) 
     894 
     895            selected = selected if len(selected) > 0 else None 
     896            other = other if len(other) > 0 else None 
     897 
     898        self.send("Selected Data", selected) 
     899        self.send("Other Data", other) 
     900 
    783901        self.selectionChangedFlag = False 
     902 
     903 
     904class BlockSelectionModel(QItemSelectionModel): 
     905    def __init__(self, *args, **kwargs): 
     906        super(QItemSelectionModel, self).__init__(*args, **kwargs) 
     907        self._selectBlocks = True 
     908 
     909    def select(self, selection, flags): 
     910        if isinstance(selection, QModelIndex): 
     911            selection = QItemSelection(selection, selection) 
     912 
     913        model = self.model() 
     914        indexes = self.selectedIndexes() 
     915 
     916        rows = set(ind.row() for ind in indexes) 
     917        cols = set(ind.column() for ind in indexes) 
     918 
     919        if flags & QItemSelectionModel.Select and \ 
     920                not flags & QItemSelectionModel.Clear and self._selectBlocks: 
     921            indexes = selection.indexes() 
     922            sel_rows = set(ind.row() for ind in indexes).union(rows) 
     923            sel_cols = set(ind.column() for ind in indexes).union(cols) 
     924 
     925            selection = QItemSelection() 
     926 
     927            for r_start, r_end in ranges(sorted(sel_rows)): 
     928                for c_start, c_end in ranges(sorted(sel_cols)): 
     929                    top_left = model.index(r_start, c_start) 
     930                    bottom_right = model.index(r_end - 1, c_end - 1) 
     931                    selection.select(top_left, bottom_right) 
     932 
     933        QItemSelectionModel.select(self, selection, flags) 
     934 
     935    def selectBlock(self): 
     936        return self._selectBlocks 
     937 
     938    def setSelectBlocks(self, state): 
     939        self._selectBlocks = state 
     940 
     941 
     942def ranges(indices): 
     943    g = itertools.groupby(enumerate(indices), 
     944                          key=lambda t: t[1] - t[0]) 
     945    for _, range_ind in g: 
     946        range_ind = list(range_ind) 
     947        _, start = range_ind[0] 
     948        _, end = range_ind[-1] 
     949        yield start, end + 1 
    784950 
    785951 
Note: See TracChangeset for help on using the changeset viewer.