""" Delegate per la griglia Linker. - MatchTriangleDelegate: disegna un triangolino nell'angolo in alto a destra: ROSSO → archivio ha sovrascritto il valore originale dell'utente VERDE → archivio ha confermato lo stesso valore dell'utente Il valore originale dell'utente è salvato in Qt.ItemDataRole.UserRole. """ from PyQt6.QtWidgets import QStyledItemDelegate, QStyleOptionViewItem, QToolTip, QStyle from PyQt6.QtCore import Qt, QPointF, QEvent from PyQt6.QtGui import QPainter, QBrush, QColor, QPolygonF, QFont class RedTriangleDelegate(QStyledItemDelegate): """ Disegna il triangolino sulle celle input toccate dall'archivio. - Verde: archivio ha confermato il valore originale (UserRole == DisplayRole) - Rosso: archivio ha sovrascritto il valore originale (UserRole != DisplayRole) """ TRIANGLE_SIZE = 7 COLOR_OVERRIDE = QColor('#CC0000') # rosso — valore sovrascritto def paint(self, painter: QPainter, option, index): # Prima il paint standard (sfondo, testo, selezione) super().paint(painter, option, index) user_val = index.data(Qt.ItemDataRole.UserRole) if user_val is None: return user_str = str(user_val) if not user_str: return # campo originariamente vuoto — nessun triangolino display_val = str(index.data(Qt.ItemDataRole.DisplayRole) or '') if user_str.lower() == display_val.lower(): return # valore confermato — nessun triangolino s = self.TRIANGLE_SIZE x = option.rect.right() y = option.rect.top() painter.save() painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) painter.setPen(Qt.PenStyle.NoPen) painter.setBrush(self.COLOR_OVERRIDE) painter.drawPolygon(QPolygonF([ QPointF(x - s, y), QPointF(x, y), QPointF(x, y + s), ])) painter.restore() def helpEvent(self, event, view, option, index): """Tooltip sul triangolino rosso: mostra il valore originale.""" if event.type() == QEvent.Type.ToolTip: user_val = index.data(Qt.ItemDataRole.UserRole) user_str = str(user_val) if user_val else '' display_str = str(index.data(Qt.ItemDataRole.DisplayRole) or '') if user_str and user_str.lower() != display_str.lower(): QToolTip.showText(event.globalPos(), user_str, view) return True QToolTip.hideText() return True return super().helpEvent(event, view, option, index) class CenteredDelegate(QStyledItemDelegate): """Testo centrato orizzontalmente e verticalmente.""" def initStyleOption(self, option, index): super().initStyleOption(option, index) option.displayAlignment = Qt.AlignmentFlag.AlignCenter class RightAlignedDelegate(QStyledItemDelegate): """Testo allineato a destra e verticalmente centrato.""" def initStyleOption(self, option, index): super().initStyleOption(option, index) option.displayAlignment = Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignRight class ThousandsDelegate(RightAlignedDelegate): """Allineato a destra, formatta il valore numerico con separatore migliaia (punto).""" def initStyleOption(self, option, index): super().initStyleOption(option, index) text = str(index.data(Qt.ItemDataRole.DisplayRole) or '').strip() try: option.text = f"{int(float(text)):,}".replace(',', '.') except (ValueError, TypeError): pass class FlatBgDelegate(QStyledItemDelegate): """Dipinge BackgroundRole come fillRect piatto, bypassando l'interferenza del CSS stylesheet. Usato per celle editabili con colore di sfondo fisso.""" def paint(self, painter: QPainter, option: QStyleOptionViewItem, index): selected = bool(option.state & QStyle.StateFlag.State_Selected) if not selected: bg = index.data(Qt.ItemDataRole.BackgroundRole) if bg is not None: painter.save() painter.fillRect(option.rect, bg) painter.restore() opt = QStyleOptionViewItem(option) opt.backgroundBrush = QBrush() super().paint(painter, opt, index) return super().paint(painter, option, index) class RiferLinkDelegate(QStyledItemDelegate): """Rende le celle come hyperlink (testo blu sottolineato). Parametro center=True per allineamento centrato (default: sinistra).""" LINK_COLOR = QColor('#0563C1') def __init__(self, parent=None, center: bool = False, right: bool = False): super().__init__(parent) if right: self._align = Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignRight elif center: self._align = Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignHCenter else: self._align = Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft def paint(self, painter: QPainter, option, index): text = str(index.data(Qt.ItemDataRole.DisplayRole) or '').strip() if not text: super().paint(painter, option, index) return painter.save() selected = bool(option.state & QStyle.StateFlag.State_Selected) if selected: painter.fillRect(option.rect, option.palette.highlight()) color = option.palette.highlightedText().color() else: bg = index.data(Qt.ItemDataRole.BackgroundRole) if bg is not None: painter.fillRect(option.rect, bg) else: painter.fillRect(option.rect, option.palette.base()) color = self.LINK_COLOR font = QFont(option.font) font.setUnderline(True) painter.setFont(font) painter.setPen(color) painter.drawText( option.rect.adjusted(3, 0, -3, 0), self._align, text, ) painter.restore() class FlatLinkDelegate(RiferLinkDelegate): """RiferLinkDelegate con sfondo piatto da BackgroundRole (come FlatBgDelegate). Per celle che devono essere hyperlink E avere un colore di sfondo fisso.""" def paint(self, painter: QPainter, option, index): selected = bool(option.state & QStyle.StateFlag.State_Selected) if not selected: bg = index.data(Qt.ItemDataRole.BackgroundRole) if bg is not None: painter.save() painter.fillRect(option.rect, bg) painter.restore() opt = QStyleOptionViewItem(option) opt.backgroundBrush = QBrush() RiferLinkDelegate.paint(self, painter, opt, index) return RiferLinkDelegate.paint(self, painter, option, index)