gui.panels.slide_navigator
1# Copyright (C) 2026 David Vaquero <pepesan@gmail.com> 2# 3# This program is free software: you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation, either version 3 of the License, or 6# (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program. If not, see <https://www.gnu.org/licenses/>. 15 16from PySide6.QtWidgets import QTreeWidget, QTreeWidgetItem 17from PySide6.QtCore import Signal, Qt 18from PySide6.QtGui import QColor, QBrush, QFont 19 20from gui.theme import c, signal as theme_signal 21 22 23_TIPO_COLOR: dict[str, str] = { 24 **{t: '#60a5fa' for t in ( 25 'java', 'python', 'javascript', 'html', 'xml', 26 'jsp', 'css', 'json', 'yaml', 'codigo', 27 )}, 28 'texto': '#fb7185', 29 'bullets': '#818cf8', 30 'dos_columnas': '#2dd4bf', 31 'cita': '#f9a8d4', 32 **{t: '#4ade80' for t in ( 33 'comparar', 'comparar_python', 'comparar_js', 34 'comparar_xml', 'comparar_html', 'comparar_json', 'comparar_css', 'comparar_jsp', 35 )}, 36 'tabla': '#fb923c', 37 'aviso': '#fbbf24', 38 **{t: '#c084fc' for t in ('terminal', 'salida', 'code')}, 39 'diagrama': '#22d3ee', 40 **{t: '#f472b6' for t in ('imagen', 'imagen_texto')}, 41} 42 43# Valores especiales de section_idx para los nodos fijos 44NODE_PORTADA = -10 45NODE_OBJETIVOS = -9 46NODE_CONCLUSION = -8 47NODE_REFERENCIAS = -7 48 49 50def _slide_label(slide: dict, n: int) -> str: 51 tipo = slide.get('tipo', '?') 52 if tipo == 'aviso': 53 nivel = slide.get('nivel', '') 54 return f'aviso: {nivel} ({n})' 55 return f'{tipo} ({n})' 56 57 58class SlideNavigator(QTreeWidget): 59 """Árbol semántico del YAML activo. 60 61 Emite slide_selected(section_idx, slide_idx) al hacer click: 62 - section_idx >= 0, slide_idx >= 0 → slide concreto (0 = primer slide de la sección) 63 - section_idx == NODE_* → nodo fijo (portada, objetivos…) 64 """ 65 slide_selected = Signal(int, int) 66 67 def __init__(self, parent=None): 68 super().__init__(parent) 69 self.setHeaderHidden(True) 70 self.setAnimated(True) 71 self.setIndentation(16) 72 self.setMinimumWidth(160) 73 self.itemClicked.connect(self._on_item_clicked) 74 theme_signal().changed.connect(self._on_theme_changed) 75 self._last_data: dict = {} 76 77 # ── API pública ─────────────────────────────────────────────────────────── 78 79 def _on_theme_changed(self) -> None: 80 if self._last_data: 81 self.rebuild(self._last_data) 82 83 def rebuild(self, data: dict) -> None: 84 self._last_data = data 85 """Reconstruye el árbol desde el dict YAML parseado.""" 86 self.clear() 87 88 self._add_fixed('Portada', NODE_PORTADA, 0) 89 self._add_fixed('Objetivos', NODE_OBJETIVOS, 0) 90 91 indice_list = data.get('indice', []) 92 93 for sec_idx, sec in enumerate(data.get('secciones', [])): 94 if 'titulo' in sec: 95 titulo = sec['titulo'] 96 elif 'indice' in sec and indice_list: 97 idx = sec['indice'] - 1 98 titulo = indice_list[idx] if 0 <= idx < len(indice_list) else f'Sección {sec_idx + 1}' 99 else: 100 titulo = f'Sección {sec_idx + 1}' 101 102 sec_item = QTreeWidgetItem(self) 103 sec_item.setText(0, f'Sec {sec_idx + 1}: {titulo}') 104 sec_item.setData(0, Qt.ItemDataRole.UserRole, (sec_idx, 0)) 105 sec_item.setForeground(0, QBrush(QColor(c('text')))) 106 bold = QFont() 107 bold.setBold(True) 108 sec_item.setFont(0, bold) 109 110 for slide_idx, slide in enumerate(sec.get('slides', [])): 111 tipo = slide.get('tipo', '?') 112 s_item = QTreeWidgetItem(sec_item) 113 s_item.setText(0, f' {_slide_label(slide, slide_idx + 1)}') 114 s_item.setData(0, Qt.ItemDataRole.UserRole, (sec_idx, slide_idx)) 115 s_item.setForeground(0, QBrush(QColor(_TIPO_COLOR.get(tipo, '#94a3b8')))) 116 117 sec_item.setExpanded(True) 118 119 self._add_fixed('Conclusión', NODE_CONCLUSION, 0) 120 self._add_fixed('Referencias', NODE_REFERENCIAS, 0) 121 122 # ── Slots privados ──────────────────────────────────────────────────────── 123 124 def _add_fixed(self, label: str, sec_idx: int, slide_idx: int) -> QTreeWidgetItem: 125 item = QTreeWidgetItem(self) 126 item.setText(0, label) 127 item.setData(0, Qt.ItemDataRole.UserRole, (sec_idx, slide_idx)) 128 item.setForeground(0, QBrush(QColor(c('text_dim')))) 129 return item 130 131 def _on_item_clicked(self, item: QTreeWidgetItem, _column: int) -> None: 132 data = item.data(0, Qt.ItemDataRole.UserRole) 133 if data is not None: 134 self.slide_selected.emit(data[0], data[1])
NODE_PORTADA =
-10
NODE_OBJETIVOS =
-9
NODE_CONCLUSION =
-8
NODE_REFERENCIAS =
-7