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
|