# coding: utf-8 from typing import List, Union from PyQt5.QtCore import Qt, QMargins, QModelIndex, QItemSelectionModel from PyQt5.QtGui import QPainter, QColor, QKeyEvent, QPalette from PyQt5.QtWidgets import (QStyledItemDelegate, QApplication, QStyleOptionViewItem, QTableView, QTableWidget, QWidget, QTableWidgetItem) from ...common.font import getFont from ...common.style_sheet import isDarkTheme, FluentStyleSheet, themeColor from .line_edit import LineEdit from .scroll_bar import SmoothScrollDelegate class TableItemDelegate(QStyledItemDelegate): def __init__(self, parent: QTableView): super().__init__(parent) self.margin = 2 self.hoverRow = -1 self.pressedRow = -1 self.selectedRows = set() def setHoverRow(self, row: int): self.hoverRow = row def setPressedRow(self, row: int): self.pressedRow = row def setSelectedRows(self, indexes: List[QModelIndex]): self.selectedRows.clear() for index in indexes: self.selectedRows.add(index.row()) if index.row() == self.pressedRow: self.pressedRow = -1 def sizeHint(self, option, index): # increase original sizeHint to accommodate space needed for border size = super().sizeHint(option, index) size = size.grownBy(QMargins(0, self.margin, 0, self.margin)) return size def createEditor(self, parent: QWidget, option: QStyleOptionViewItem, index: QModelIndex) -> QWidget: lineEdit = LineEdit(parent) lineEdit.setProperty("transparent", False) lineEdit.setStyle(QApplication.style()) lineEdit.setText(option.text) lineEdit.setClearButtonEnabled(True) return lineEdit def updateEditorGeometry(self, editor: QWidget, option: QStyleOptionViewItem, index: QModelIndex): rect = option.rect y = rect.y() + (rect.height() - editor.height()) // 2 x, w = max(8, rect.x()), rect.width() if index.column() == 0: w -= 8 editor.setGeometry(x, y, w, rect.height()) def _drawBackground(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex): """ draw row background """ r = 5 if index.column() == 0: rect = option.rect.adjusted(4, 0, r + 1, 0) painter.drawRoundedRect(rect, r, r) elif index.column() == index.model().columnCount(index.parent()) - 1: rect = option.rect.adjusted(-r - 1, 0, -4, 0) painter.drawRoundedRect(rect, r, r) else: rect = option.rect.adjusted(-1, 0, 1, 0) painter.drawRect(rect) def _drawIndicator(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex): """ draw indicator """ y, h = option.rect.y(), option.rect.height() ph = round(0.35*h if self.pressedRow == index.row() else 0.257*h) painter.setBrush(themeColor()) painter.drawRoundedRect(4, ph + y, 3, h - 2*ph, 1.5, 1.5) def initStyleOption(self, option: QStyleOptionViewItem, index: QModelIndex): super().initStyleOption(option, index) option.font = getFont(13) if isDarkTheme(): option.palette.setColor(QPalette.Text, Qt.white) option.palette.setColor(QPalette.HighlightedText, Qt.white) else: option.palette.setColor(QPalette.Text, Qt.black) option.palette.setColor(QPalette.HighlightedText, Qt.black) def paint(self, painter, option, index): painter.save() painter.setPen(Qt.NoPen) painter.setRenderHint(QPainter.Antialiasing) # set clipping rect of painter to avoid painting outside the borders painter.setClipping(True) painter.setClipRect(option.rect) # call original paint method where option.rect is adjusted to account for border option.rect.adjust(0, self.margin, 0, -self.margin) # draw highlight background isHover = self.hoverRow == index.row() isPressed = self.pressedRow == index.row() isAlternate = index.row() % 2 == 0 and self.parent().alternatingRowColors() isDark = isDarkTheme() c = 255 if isDark else 0 alpha = 0 if index.row() not in self.selectedRows: if isPressed: alpha = 9 if isDark else 6 elif isHover: alpha = 12 elif isAlternate: alpha = 5 else: if isPressed: alpha = 15 if isDark else 9 elif isHover: alpha = 25 else: alpha = 17 # draw indicator if index.column() == 0 and self.parent().horizontalScrollBar().value() == 0: self._drawIndicator(painter, option, index) painter.setBrush(QColor(c, c, c, alpha)) self._drawBackground(painter, option, index) painter.restore() super().paint(painter, option, index) class TableBase: """ Table base class """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.delegate = TableItemDelegate(self) self.scrollDelagate = SmoothScrollDelegate(self) # set style sheet FluentStyleSheet.TABLE_VIEW.apply(self) self.setShowGrid(False) self.setMouseTracking(True) self.setAlternatingRowColors(True) self.setItemDelegate(self.delegate) self.setSelectionBehavior(TableWidget.SelectRows) self.entered.connect(lambda i: self._setHoverRow(i.row())) self.pressed.connect(lambda i: self._setPressedRow(i.row())) self.verticalHeader().sectionClicked.connect(self.selectRow) def showEvent(self, e): QTableView.showEvent(self, e) self.resizeRowsToContents() def _setHoverRow(self, row: int): """ set hovered row """ self.delegate.setHoverRow(row) self.viewport().update() def _setPressedRow(self, row: int): """ set pressed row """ self.delegate.setPressedRow(row) self.viewport().update() def _setSelectedRows(self, indexes: List[QModelIndex]): self.delegate.setSelectedRows(indexes) self.viewport().update() def leaveEvent(self, e): QTableView.leaveEvent(self, e) self._setHoverRow(-1) def resizeEvent(self, e): QTableView.resizeEvent(self, e) self.viewport().update() def keyPressEvent(self, e: QKeyEvent): QTableView.keyPressEvent(self, e) self.updateSelectedRows() def mouseReleaseEvent(self, e): QTableView.mouseReleaseEvent(self, e) row = self.indexAt(e.pos()).row() if row >= 0 and e.button() != Qt.RightButton: self.updateSelectedRows() else: self._setPressedRow(-1) def setItemDelegate(self, delegate: TableItemDelegate): self.delegate = delegate super().setItemDelegate(delegate) def selectAll(self): QTableView.selectAll(self) self.updateSelectedRows() def selectRow(self, row: int): QTableView.selectRow(self, row) self.updateSelectedRows() def clearSelection(self): QTableView.clearSelection(self) self.updateSelectedRows() def setCurrentIndex(self, index: QModelIndex): QTableView.setCurrentIndex(self, index) self.updateSelectedRows() def updateSelectedRows(self): self._setSelectedRows(self.selectedIndexes()) class TableWidget(TableBase, QTableWidget): """ Table widget """ def __init__(self, parent=None): super().__init__(parent) def setCurrentCell(self, row: int, column: int, command: Union[QItemSelectionModel.SelectionFlag, QItemSelectionModel.SelectionFlags] = None): self.setCurrentItem(self.item(row, column), command) def setCurrentItem(self, item: QTableWidgetItem, command: Union[QItemSelectionModel.SelectionFlag, QItemSelectionModel.SelectionFlags] = None): if not command: super().setCurrentItem(item) else: super().setCurrentItem(item, command) self.updateSelectedRows() class TableView(TableBase, QTableView): """ Table view """ def __init__(self, parent=None): super().__init__(parent)