gui.dialogs.new_presentation_dialog
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 16import re 17from pathlib import Path 18 19from PySide6.QtWidgets import ( 20 QDialog, QVBoxLayout, QFormLayout, QHBoxLayout, 21 QLabel, QLineEdit, QDialogButtonBox, 22) 23from PySide6.QtCore import Qt 24 25from gui.theme import c 26from lib.i18n import _ 27 28 29class NewPresentationDialog(QDialog): 30 """Diálogo para crear un nuevo fichero YAML de presentación en el curso activo.""" 31 32 def __init__(self, course_dir: Path, parent=None): 33 super().__init__(parent) 34 self.setWindowTitle(_('Nueva presentación')) 35 self.setMinimumWidth(440) 36 self._course_dir = course_dir 37 self._build_ui() 38 self._suggest_defaults() 39 40 # ── Construcción ───────────────────────────────────────────────────────── 41 42 def _build_ui(self) -> None: 43 lay = QVBoxLayout(self) 44 lay.setContentsMargins(20, 16, 20, 16) 45 lay.setSpacing(12) 46 47 form = QFormLayout() 48 form.setSpacing(10) 49 50 self._portada = QLineEdit() 51 self._portada.setPlaceholderText(_('Título visible en la portada')) 52 form.addRow(_('Título:'), self._portada) 53 54 self._unidad = QLineEdit() 55 self._unidad.setPlaceholderText('Unidad 01') 56 form.addRow(_('Unidad:'), self._unidad) 57 58 output_row = QHBoxLayout() 59 self._output = QLineEdit() 60 self._output.setPlaceholderText('01_introduccion') 61 output_row.addWidget(self._output) 62 suffix = QLabel('.yaml') 63 suffix.setObjectName('hint') 64 output_row.addWidget(suffix) 65 hint = QLabel(_('La extensión .yaml se añade automáticamente')) 66 hint.setObjectName('hint') 67 form.addRow(_('Output:'), output_row) 68 form.addRow('', hint) 69 70 lay.addLayout(form) 71 72 self._preview = QLabel() 73 self._preview.setObjectName('hint') 74 self._preview.setAlignment(Qt.AlignmentFlag.AlignCenter) 75 lay.addWidget(self._preview) 76 77 bbox = QDialogButtonBox( 78 QDialogButtonBox.StandardButton.Ok | 79 QDialogButtonBox.StandardButton.Cancel 80 ) 81 bbox.accepted.connect(self._validate_and_accept) 82 bbox.rejected.connect(self.reject) 83 lay.addWidget(bbox) 84 85 self._portada.textChanged.connect(self._update_preview) 86 self._output.textChanged.connect(self._update_preview) 87 88 # ── Lógica ──────────────────────────────────────────────────────────────── 89 90 def _suggest_defaults(self) -> None: 91 """Sugiere número de unidad y prefijo basándose en los YAMLs existentes.""" 92 existing = sorted((self._course_dir / 'content').glob('*.yaml')) 93 n = len(existing) 94 num = f'{n:02d}' 95 self._unidad.setText(f'Unidad {num}') 96 self._output.setText(f'{num}_nueva_presentacion') 97 self._update_preview() 98 99 def _update_preview(self) -> None: 100 output = self._output.text().strip() 101 course = self._course_dir.name 102 if output: 103 filename = f'{course}_{output}.yaml' 104 self._preview.setText(f'→ content/{filename}') 105 else: 106 self._preview.setText('') 107 108 def _validate_and_accept(self) -> None: 109 portada = self._portada.text().strip() 110 unidad = self._unidad.text().strip() 111 output = self._output.text().strip() 112 113 if not portada: 114 self._portada.setFocus() 115 return 116 if not unidad: 117 self._unidad.setFocus() 118 return 119 if not output: 120 self._output.setFocus() 121 return 122 123 # Limpiar output: sin espacios ni caracteres problemáticos 124 output_clean = re.sub(r'[^a-zA-Z0-9_\-]', '_', output).strip('_') 125 if not output_clean: 126 self._output.setFocus() 127 return 128 self._output.setText(output_clean) 129 130 dest = self._course_dir / 'content' / f'{self._course_dir.name}_{output_clean}.yaml' 131 if dest.exists(): 132 self._preview.setText( 133 f'⚠ {dest.name} ya existe' 134 ) 135 return 136 137 self.accept() 138 139 # ── API pública ──────────────────────────────────────────────────────────── 140 141 def create_yaml(self) -> Path: 142 """Crea el fichero YAML y devuelve su ruta. Llamar solo tras accept().""" 143 course = self._course_dir.name 144 portada = self._portada.text().strip() 145 unidad = self._unidad.text().strip() 146 output = self._output.text().strip() 147 dest = self._course_dir / 'content' / f'{course}_{output}.yaml' 148 149 dest.parent.mkdir(parents=True, exist_ok=True) 150 dest.write_text( 151 f'meta:\n' 152 f' portada: "{portada}"\n' 153 f' unidad: "{unidad}"\n' 154 f' output: "{output}"\n' 155 f'\n' 156 f'objetivos:\n' 157 f' - "Objetivo 1"\n' 158 f'\n' 159 f'indice:\n' 160 f' - "Sección 1"\n' 161 f'\n' 162 f'secciones:\n' 163 f' - indice: 1\n' 164 f' slides:\n' 165 f' - tipo: texto\n' 166 f' texto: "Texto introductorio."\n' 167 f'\n' 168 f'conclusion:\n' 169 f' - "Conclusión principal"\n' 170 f'\n' 171 f'referencias:\n' 172 f' - ["Recurso", "https://ejemplo.com"]\n', 173 encoding='utf-8', 174 ) 175 return dest
class
NewPresentationDialog(PySide6.QtWidgets.QDialog):
30class NewPresentationDialog(QDialog): 31 """Diálogo para crear un nuevo fichero YAML de presentación en el curso activo.""" 32 33 def __init__(self, course_dir: Path, parent=None): 34 super().__init__(parent) 35 self.setWindowTitle(_('Nueva presentación')) 36 self.setMinimumWidth(440) 37 self._course_dir = course_dir 38 self._build_ui() 39 self._suggest_defaults() 40 41 # ── Construcción ───────────────────────────────────────────────────────── 42 43 def _build_ui(self) -> None: 44 lay = QVBoxLayout(self) 45 lay.setContentsMargins(20, 16, 20, 16) 46 lay.setSpacing(12) 47 48 form = QFormLayout() 49 form.setSpacing(10) 50 51 self._portada = QLineEdit() 52 self._portada.setPlaceholderText(_('Título visible en la portada')) 53 form.addRow(_('Título:'), self._portada) 54 55 self._unidad = QLineEdit() 56 self._unidad.setPlaceholderText('Unidad 01') 57 form.addRow(_('Unidad:'), self._unidad) 58 59 output_row = QHBoxLayout() 60 self._output = QLineEdit() 61 self._output.setPlaceholderText('01_introduccion') 62 output_row.addWidget(self._output) 63 suffix = QLabel('.yaml') 64 suffix.setObjectName('hint') 65 output_row.addWidget(suffix) 66 hint = QLabel(_('La extensión .yaml se añade automáticamente')) 67 hint.setObjectName('hint') 68 form.addRow(_('Output:'), output_row) 69 form.addRow('', hint) 70 71 lay.addLayout(form) 72 73 self._preview = QLabel() 74 self._preview.setObjectName('hint') 75 self._preview.setAlignment(Qt.AlignmentFlag.AlignCenter) 76 lay.addWidget(self._preview) 77 78 bbox = QDialogButtonBox( 79 QDialogButtonBox.StandardButton.Ok | 80 QDialogButtonBox.StandardButton.Cancel 81 ) 82 bbox.accepted.connect(self._validate_and_accept) 83 bbox.rejected.connect(self.reject) 84 lay.addWidget(bbox) 85 86 self._portada.textChanged.connect(self._update_preview) 87 self._output.textChanged.connect(self._update_preview) 88 89 # ── Lógica ──────────────────────────────────────────────────────────────── 90 91 def _suggest_defaults(self) -> None: 92 """Sugiere número de unidad y prefijo basándose en los YAMLs existentes.""" 93 existing = sorted((self._course_dir / 'content').glob('*.yaml')) 94 n = len(existing) 95 num = f'{n:02d}' 96 self._unidad.setText(f'Unidad {num}') 97 self._output.setText(f'{num}_nueva_presentacion') 98 self._update_preview() 99 100 def _update_preview(self) -> None: 101 output = self._output.text().strip() 102 course = self._course_dir.name 103 if output: 104 filename = f'{course}_{output}.yaml' 105 self._preview.setText(f'→ content/{filename}') 106 else: 107 self._preview.setText('') 108 109 def _validate_and_accept(self) -> None: 110 portada = self._portada.text().strip() 111 unidad = self._unidad.text().strip() 112 output = self._output.text().strip() 113 114 if not portada: 115 self._portada.setFocus() 116 return 117 if not unidad: 118 self._unidad.setFocus() 119 return 120 if not output: 121 self._output.setFocus() 122 return 123 124 # Limpiar output: sin espacios ni caracteres problemáticos 125 output_clean = re.sub(r'[^a-zA-Z0-9_\-]', '_', output).strip('_') 126 if not output_clean: 127 self._output.setFocus() 128 return 129 self._output.setText(output_clean) 130 131 dest = self._course_dir / 'content' / f'{self._course_dir.name}_{output_clean}.yaml' 132 if dest.exists(): 133 self._preview.setText( 134 f'⚠ {dest.name} ya existe' 135 ) 136 return 137 138 self.accept() 139 140 # ── API pública ──────────────────────────────────────────────────────────── 141 142 def create_yaml(self) -> Path: 143 """Crea el fichero YAML y devuelve su ruta. Llamar solo tras accept().""" 144 course = self._course_dir.name 145 portada = self._portada.text().strip() 146 unidad = self._unidad.text().strip() 147 output = self._output.text().strip() 148 dest = self._course_dir / 'content' / f'{course}_{output}.yaml' 149 150 dest.parent.mkdir(parents=True, exist_ok=True) 151 dest.write_text( 152 f'meta:\n' 153 f' portada: "{portada}"\n' 154 f' unidad: "{unidad}"\n' 155 f' output: "{output}"\n' 156 f'\n' 157 f'objetivos:\n' 158 f' - "Objetivo 1"\n' 159 f'\n' 160 f'indice:\n' 161 f' - "Sección 1"\n' 162 f'\n' 163 f'secciones:\n' 164 f' - indice: 1\n' 165 f' slides:\n' 166 f' - tipo: texto\n' 167 f' texto: "Texto introductorio."\n' 168 f'\n' 169 f'conclusion:\n' 170 f' - "Conclusión principal"\n' 171 f'\n' 172 f'referencias:\n' 173 f' - ["Recurso", "https://ejemplo.com"]\n', 174 encoding='utf-8', 175 ) 176 return dest
Diálogo para crear un nuevo fichero YAML de presentación en el curso activo.
def
create_yaml(self) -> pathlib.Path:
142 def create_yaml(self) -> Path: 143 """Crea el fichero YAML y devuelve su ruta. Llamar solo tras accept().""" 144 course = self._course_dir.name 145 portada = self._portada.text().strip() 146 unidad = self._unidad.text().strip() 147 output = self._output.text().strip() 148 dest = self._course_dir / 'content' / f'{course}_{output}.yaml' 149 150 dest.parent.mkdir(parents=True, exist_ok=True) 151 dest.write_text( 152 f'meta:\n' 153 f' portada: "{portada}"\n' 154 f' unidad: "{unidad}"\n' 155 f' output: "{output}"\n' 156 f'\n' 157 f'objetivos:\n' 158 f' - "Objetivo 1"\n' 159 f'\n' 160 f'indice:\n' 161 f' - "Sección 1"\n' 162 f'\n' 163 f'secciones:\n' 164 f' - indice: 1\n' 165 f' slides:\n' 166 f' - tipo: texto\n' 167 f' texto: "Texto introductorio."\n' 168 f'\n' 169 f'conclusion:\n' 170 f' - "Conclusión principal"\n' 171 f'\n' 172 f'referencias:\n' 173 f' - ["Recurso", "https://ejemplo.com"]\n', 174 encoding='utf-8', 175 ) 176 return dest
Crea el fichero YAML y devuelve su ruta. Llamar solo tras accept().