File: tableview.py

package info (click to toggle)
orange3 3.40.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 15,908 kB
  • sloc: python: 162,745; ansic: 622; makefile: 322; sh: 93; cpp: 77
file content (134 lines) | stat: -rw-r--r-- 4,687 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import io
import csv

from AnyQt.QtCore import Signal, QItemSelectionModel, Qt, QSize, QEvent, \
    QByteArray, QMimeData, QT_VERSION_INFO
from AnyQt.QtGui import QMouseEvent
from AnyQt.QtWidgets import QTableView, QStyleOptionViewItem, QStyle

from .headerview import HeaderView


def table_view_compact(view: QTableView) -> None:
    """
    Give the view a more compact default vertical header section size.
    """
    vheader = view.verticalHeader()
    option = view.viewOptions()
    option.text = "X"
    option.features |= QStyleOptionViewItem.HasDisplay
    size = view.style().sizeFromContents(
        QStyle.CT_ItemViewItem, option,
        QSize(20, 20), view
    )
    vheader.ensurePolished()
    vheader.setDefaultSectionSize(
        max(size.height(), vheader.minimumSectionSize())
    )


class TableView(QTableView):
    """
    A QTableView subclass that is more suited for displaying large data models.
    """
    #: Signal emitted when selection finished. It is not emitted during
    #: mouse drag selection updates.
    selectionFinished = Signal()

    __mouseDown = False
    __selectionDidChange = False

    def __init__(self, *args, **kwargs,):
        kwargs.setdefault("horizontalScrollMode", QTableView.ScrollPerPixel)
        kwargs.setdefault("verticalScrollMode", QTableView.ScrollPerPixel)
        super().__init__(*args, **kwargs)
        hheader = HeaderView(Qt.Horizontal, self, highlightSections=True)
        vheader = HeaderView(Qt.Vertical, self, highlightSections=True)
        hheader.setSectionsClickable(True)
        vheader.setSectionsClickable(True)
        self.setHorizontalHeader(hheader)
        self.setVerticalHeader(vheader)
        table_view_compact(self)
        if QT_VERSION_INFO < (5, 13):
            hheader.sortIndicatorChanged.connect(self.__sort_reset)

    if QT_VERSION_INFO < (5, 13):
        def __sort_reset(self, column, order):
            # Prior to Qt 5.13 QTableView did not propagate sort by -1 column
            # (i.e. sort reset) to models.
            if self.model() is not None and column == -1 and \
                    self.isSortingEnabled():
                self.model().sort(column, order)

    def setSelectionModel(self, selectionModel: QItemSelectionModel) -> None:
        """Reimplemented from QTableView"""
        sm = self.selectionModel()
        if sm is not None:
            sm.selectionChanged.disconnect(self.__on_selectionChanged)
        super().setSelectionModel(selectionModel)
        if selectionModel is not None:
            selectionModel.selectionChanged.connect(self.__on_selectionChanged)

    def __on_selectionChanged(self):
        if self.__mouseDown:
            self.__selectionDidChange = True
        else:
            self.selectionFinished.emit()

    def mousePressEvent(self, event: QMouseEvent) -> None:
        self.__mouseDown = event.button() == Qt.LeftButton
        super().mousePressEvent(event)

    def mouseReleaseEvent(self, event: QMouseEvent) -> None:
        super().mouseReleaseEvent(event)
        if self.__mouseDown and event.button() == Qt.LeftButton:
            self.__mouseDown = False
        if self.__selectionDidChange:
            self.__selectionDidChange = False
            self.selectionFinished.emit()

    def changeEvent(self, event: QEvent) -> None:
        if event.type() in (QEvent.StyleChange, QEvent.FontChange):
            table_view_compact(self)
        super().changeEvent(event)


def table_selection_to_mime_data(table):
    """Copy the current selection in a QTableView to the clipboard.
    """
    lines = table_selection_to_list(table)

    as_csv = lines_to_csv_string(lines, dialect="excel").encode("utf-8")
    as_tsv = lines_to_csv_string(lines, dialect="excel-tab").encode("utf-8")

    mime = QMimeData()
    mime.setData("text/csv", QByteArray(as_csv))
    mime.setData("text/tab-separated-values", QByteArray(as_tsv))
    mime.setData("text/plain", QByteArray(as_tsv))
    return mime


def lines_to_csv_string(lines, dialect="excel"):
    stream = io.StringIO()
    writer = csv.writer(stream, dialect=dialect)
    writer.writerows(lines)
    return stream.getvalue()


def table_selection_to_list(table):
    model = table.model()
    indexes = table.selectedIndexes()

    rows = sorted(set(index.row() for index in indexes))
    columns = sorted(set(index.column() for index in indexes))

    lines = []
    for row in rows:
        line = []
        for col in columns:
            val = model.index(row, col).data(Qt.DisplayRole)
            # TODO: use style item delegate displayText?
            line.append("" if val is None else str(val))
        lines.append(line)

    return lines