146 lines
5.7 KiB
Python
146 lines
5.7 KiB
Python
"""Tableau des extraits capturés, affiché dans le panneau latéral (voir
|
||
main_window.py) : de vraies colonnes/lignes avec un en-tête, plutôt qu'une
|
||
simple liste de texte.
|
||
|
||
Colonnes : Heure de capture, Titre, Aperçu du texte, et Lien — une icône
|
||
par ligne permettant de fusionner cet extrait avec le suivant en un seul
|
||
chapitre à l'export (voir models.group_into_chapters / pdf_builder.py).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from PySide6.QtCore import Qt, Signal
|
||
from PySide6.QtWidgets import (
|
||
QAbstractItemView,
|
||
QHBoxLayout,
|
||
QHeaderView,
|
||
QTableWidget,
|
||
QTableWidgetItem,
|
||
QToolButton,
|
||
QWidget,
|
||
)
|
||
|
||
from .models import Clip, group_into_chapters
|
||
|
||
COL_TIME = 0
|
||
COL_TITLE = 1
|
||
COL_PREVIEW = 2
|
||
COL_LINK = 3
|
||
|
||
_LINKED_ICON = "🔗"
|
||
_UNLINKED_ICON = "➕"
|
||
|
||
|
||
class ClipTable(QTableWidget):
|
||
"""Tableau affichant les extraits capturés. Émet `renameRequested`
|
||
(double-clic sur une ligne, hors colonne Lien) et `linkToggled` (clic
|
||
sur l'icône de la colonne Lien) plutôt que d'exposer les détails Qt
|
||
du tableau — voir main_window.py pour les branchements."""
|
||
|
||
renameRequested = Signal(int)
|
||
linkToggled = Signal(int)
|
||
|
||
def __init__(self, parent: QWidget | None = None) -> None:
|
||
super().__init__(0, 4, parent)
|
||
self.setHorizontalHeaderLabels(["Heure", "Titre", "Aperçu", "Lien"])
|
||
self.verticalHeader().setVisible(False)
|
||
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||
self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||
self.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
||
self.setAlternatingRowColors(True)
|
||
self.setShowGrid(False)
|
||
self.setToolTip(
|
||
"Double-cliquez sur un extrait pour le renommer.\n"
|
||
"Colonne Lien : 🔗 relie cet extrait au suivant pour former un "
|
||
"seul chapitre à l'export."
|
||
)
|
||
|
||
header = self.horizontalHeader()
|
||
header.setSectionResizeMode(COL_TIME, QHeaderView.ResizeMode.ResizeToContents)
|
||
header.setSectionResizeMode(COL_TITLE, QHeaderView.ResizeMode.Interactive)
|
||
header.setSectionResizeMode(COL_PREVIEW, QHeaderView.ResizeMode.Stretch)
|
||
header.setSectionResizeMode(COL_LINK, QHeaderView.ResizeMode.ResizeToContents)
|
||
self.setColumnWidth(COL_TITLE, 130)
|
||
|
||
self.cellDoubleClicked.connect(self._on_cell_double_clicked)
|
||
|
||
# ------------------------------------------------------------------ #
|
||
# API utilisée par main_window.py
|
||
# ------------------------------------------------------------------ #
|
||
def set_clips(self, clips: list[Clip]) -> None:
|
||
"""(Re)peuple le tableau, en préservant la ligne sélectionnée
|
||
quand son index reste valide (même logique que l'ancienne liste)."""
|
||
preserved_row = self.currentRow()
|
||
continuation_indices = {
|
||
index for chapter in group_into_chapters(clips) for index in chapter[1:]
|
||
}
|
||
|
||
self.setRowCount(len(clips))
|
||
last_index = len(clips) - 1
|
||
for index, clip in enumerate(clips):
|
||
prefix = "↳ " if index in continuation_indices else ""
|
||
|
||
self.setItem(index, COL_TIME, self._readonly_item(clip.captured_at.strftime("%H:%M")))
|
||
|
||
title_item = self._readonly_item(prefix + (clip.title or "Sans titre"))
|
||
title_item.setToolTip(clip.url)
|
||
self.setItem(index, COL_TITLE, title_item)
|
||
|
||
preview_item = self._readonly_item(clip.preview())
|
||
preview_item.setToolTip(clip.url)
|
||
self.setItem(index, COL_PREVIEW, preview_item)
|
||
|
||
if index == last_index:
|
||
link_item = self._readonly_item("")
|
||
link_item.setFlags(Qt.ItemFlag.NoItemFlags)
|
||
link_item.setToolTip("Dernier extrait : rien à lier après lui.")
|
||
self.setItem(index, COL_LINK, link_item)
|
||
else:
|
||
self.setCellWidget(index, COL_LINK, self._build_link_cell(index, clip.linked_to_next))
|
||
|
||
if 0 <= preserved_row < len(clips):
|
||
self.setCurrentCell(preserved_row, COL_TITLE)
|
||
|
||
def select_row(self, row: int) -> None:
|
||
self.setCurrentCell(row, COL_TITLE)
|
||
|
||
def clear_current(self) -> None:
|
||
self.setCurrentCell(-1, -1)
|
||
|
||
# ------------------------------------------------------------------ #
|
||
# Construction des cellules
|
||
# ------------------------------------------------------------------ #
|
||
@staticmethod
|
||
def _readonly_item(text: str) -> QTableWidgetItem:
|
||
item = QTableWidgetItem(text)
|
||
item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable)
|
||
return item
|
||
|
||
def _build_link_cell(self, index: int, linked: bool) -> QWidget:
|
||
button = QToolButton()
|
||
button.setAutoRaise(True)
|
||
button.setText(_LINKED_ICON if linked else _UNLINKED_ICON)
|
||
button.setToolTip(
|
||
"Extraits liés : forment un seul chapitre à l'export (un seul "
|
||
"titre, pas de saut de page entre eux).\nCliquer pour délier."
|
||
if linked
|
||
else "Lier cet extrait au suivant : ils formeront un seul "
|
||
"chapitre à l'export — utile quand un chapitre est réparti sur "
|
||
"plusieurs pages."
|
||
)
|
||
button.clicked.connect(lambda _checked=False, i=index: self.linkToggled.emit(i))
|
||
|
||
# Centré dans la cellule plutôt que collé à gauche (comportement
|
||
# par défaut de setCellWidget).
|
||
container = QWidget()
|
||
layout = QHBoxLayout(container)
|
||
layout.setContentsMargins(0, 0, 0, 0)
|
||
layout.addStretch(1)
|
||
layout.addWidget(button)
|
||
layout.addStretch(1)
|
||
return container
|
||
|
||
def _on_cell_double_clicked(self, row: int, column: int) -> None:
|
||
if column != COL_LINK:
|
||
self.renameRequested.emit(row)
|