gui.dialogs.onboarding_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 16from pathlib import Path 17 18from PySide6.QtWidgets import ( 19 QDialog, QVBoxLayout, QHBoxLayout, QLabel, 20 QPushButton, QLineEdit, QFileDialog, QDialogButtonBox, 21) 22from PySide6.QtCore import Qt 23from PySide6.QtGui import QFont 24 25from lib.slides_config import save_courses_dir, DEFAULT_COURSES_DIR 26from gui.theme import c 27from lib.i18n import _ 28 29 30def _build_style() -> str: 31 return f""" 32QDialog {{ background: {c('bg')}; color: {c('text')}; }} 33QLabel {{ color: {c('text')}; }} 34QLabel#subtitle {{ color: {c('text_muted')}; font-size: 12px; }} 35QLineEdit {{ 36 background: {c('bg_deep')}; color: {c('text')}; 37 border: 1px solid {c('border')}; border-radius: 4px; 38 padding: 4px 8px; font-size: 12px; 39}} 40QPushButton#browse {{ 41 background: {c('bg_selected')}; color: {c('text')}; 42 border: none; border-radius: 4px; padding: 5px 14px; font-size: 12px; 43}} 44QPushButton#browse:hover {{ background: {c('bg_hover')}; }} 45QDialogButtonBox QPushButton {{ 46 background: {c('accent')}; color: #ffffff; 47 border: none; border-radius: 4px; 48 padding: 6px 20px; font-size: 12px; min-width: 80px; 49}} 50QDialogButtonBox QPushButton:hover {{ background: {c('accent_hover')}; }} 51QDialogButtonBox QPushButton:disabled {{ background: {c('bg_selected')}; color: {c('text_dim')}; }} 52QDialogButtonBox QPushButton[text="Omitir"], 53QDialogButtonBox QPushButton[text="Skip"] {{ 54 background: {c('bg_surface')}; color: {c('text_muted')}; 55}} 56QDialogButtonBox QPushButton[text="Omitir"]:hover, 57QDialogButtonBox QPushButton[text="Skip"]:hover {{ background: {c('bg_hover')}; }} 58""" 59 60 61class OnboardingDialog(QDialog): 62 """Diálogo de primer arranque: permite seleccionar el directorio de cursos.""" 63 64 def __init__(self, parent=None): 65 super().__init__(parent) 66 self.setWindowTitle(_('Configuración inicial')) 67 self.setMinimumWidth(540) 68 self.setStyleSheet(_build_style()) 69 self._selected: Path | None = None 70 self._build_ui() 71 72 # ── Construcción ───────────────────────────────────────────────────────── 73 74 def _build_ui(self) -> None: 75 lay = QVBoxLayout(self) 76 lay.setContentsMargins(28, 24, 28, 20) 77 lay.setSpacing(16) 78 79 title = QLabel(_('Bienvenido a Temario')) 80 f = QFont() 81 f.setPointSize(16) 82 f.setBold(True) 83 title.setFont(f) 84 lay.addWidget(title) 85 86 sub = QLabel(_( 87 'Indica dónde están tus cursos. Si no tienes ninguno todavía,\n' 88 'selecciona o crea una carpeta vacía; después podrás usar\n' 89 '"Nuevo curso" para añadir el primero.' 90 )) 91 sub.setObjectName('subtitle') 92 sub.setWordWrap(True) 93 lay.addWidget(sub) 94 95 # ── selector de directorio ──────────────────────────────────────── 96 dir_row = QHBoxLayout() 97 dir_row.setSpacing(6) 98 99 self._dir_edit = QLineEdit() 100 self._dir_edit.setPlaceholderText(str(DEFAULT_COURSES_DIR)) 101 self._dir_edit.setText(str(DEFAULT_COURSES_DIR)) 102 self._dir_edit.textChanged.connect(self._on_text_changed) 103 dir_row.addWidget(self._dir_edit) 104 105 btn_browse = QPushButton(_('Explorar…')) 106 btn_browse.setObjectName('browse') 107 btn_browse.clicked.connect(self._browse) 108 dir_row.addWidget(btn_browse) 109 110 lay.addLayout(dir_row) 111 112 self._hint = QLabel('') 113 self._hint.setObjectName('subtitle') 114 lay.addWidget(self._hint) 115 116 lay.addSpacing(8) 117 118 # ── botones ─────────────────────────────────────────────────────── 119 bbox = QDialogButtonBox() 120 self._btn_ok = bbox.addButton(_('Confirmar'), QDialogButtonBox.ButtonRole.AcceptRole) 121 btn_skip = bbox.addButton(_('Omitir'), QDialogButtonBox.ButtonRole.RejectRole) 122 self._btn_ok.setEnabled(True) 123 bbox.accepted.connect(self._accept) 124 bbox.rejected.connect(self.reject) 125 lay.addWidget(bbox) 126 127 # ── Lógica ──────────────────────────────────────────────────────────────── 128 129 def _browse(self) -> None: 130 start = self._dir_edit.text().strip() or str(Path.home()) 131 chosen = QFileDialog.getExistingDirectory( 132 self, _('Seleccionar directorio de cursos'), start, 133 QFileDialog.Option.ShowDirsOnly, 134 ) 135 if chosen: 136 self._dir_edit.setText(chosen) 137 138 def _on_text_changed(self, text: str) -> None: 139 path = Path(text.strip()) if text.strip() else None 140 self._btn_ok.setEnabled(bool(text.strip())) 141 if path and path.is_dir(): 142 n = sum(1 for d in path.iterdir() if d.is_dir()) 143 self._hint.setText(_('{} subcarpeta(s) encontrada(s) en ese directorio.').format(n)) 144 elif path: 145 self._hint.setText(_('El directorio se creará al confirmar.')) 146 else: 147 self._hint.setText('') 148 149 def _accept(self) -> None: 150 import shutil 151 text = self._dir_edit.text().strip() 152 if not text: 153 return 154 path = Path(text) 155 if not path.exists(): 156 path.mkdir(parents=True, exist_ok=True) 157 # copiar curso de ejemplo si el directorio estaba vacío 158 _root = Path(__file__).parent.parent.parent.parent # src/gui/dialogs → project root 159 ejemplo_src = _root / 'courses' / 'ejemplo' 160 ejemplo_dst = path / 'ejemplo' 161 if ejemplo_src.exists() and not ejemplo_dst.exists(): 162 shutil.copytree(ejemplo_src, ejemplo_dst, 163 ignore=shutil.ignore_patterns('output')) 164 self._selected = path 165 save_courses_dir(path) 166 self.accept() 167 168 # ── API pública ──────────────────────────────────────────────────────────── 169 170 def selected_dir(self) -> Path | None: 171 """Devuelve el directorio elegido, o None si se omitió.""" 172 return self._selected
class
OnboardingDialog(PySide6.QtWidgets.QDialog):
62class OnboardingDialog(QDialog): 63 """Diálogo de primer arranque: permite seleccionar el directorio de cursos.""" 64 65 def __init__(self, parent=None): 66 super().__init__(parent) 67 self.setWindowTitle(_('Configuración inicial')) 68 self.setMinimumWidth(540) 69 self.setStyleSheet(_build_style()) 70 self._selected: Path | None = None 71 self._build_ui() 72 73 # ── Construcción ───────────────────────────────────────────────────────── 74 75 def _build_ui(self) -> None: 76 lay = QVBoxLayout(self) 77 lay.setContentsMargins(28, 24, 28, 20) 78 lay.setSpacing(16) 79 80 title = QLabel(_('Bienvenido a Temario')) 81 f = QFont() 82 f.setPointSize(16) 83 f.setBold(True) 84 title.setFont(f) 85 lay.addWidget(title) 86 87 sub = QLabel(_( 88 'Indica dónde están tus cursos. Si no tienes ninguno todavía,\n' 89 'selecciona o crea una carpeta vacía; después podrás usar\n' 90 '"Nuevo curso" para añadir el primero.' 91 )) 92 sub.setObjectName('subtitle') 93 sub.setWordWrap(True) 94 lay.addWidget(sub) 95 96 # ── selector de directorio ──────────────────────────────────────── 97 dir_row = QHBoxLayout() 98 dir_row.setSpacing(6) 99 100 self._dir_edit = QLineEdit() 101 self._dir_edit.setPlaceholderText(str(DEFAULT_COURSES_DIR)) 102 self._dir_edit.setText(str(DEFAULT_COURSES_DIR)) 103 self._dir_edit.textChanged.connect(self._on_text_changed) 104 dir_row.addWidget(self._dir_edit) 105 106 btn_browse = QPushButton(_('Explorar…')) 107 btn_browse.setObjectName('browse') 108 btn_browse.clicked.connect(self._browse) 109 dir_row.addWidget(btn_browse) 110 111 lay.addLayout(dir_row) 112 113 self._hint = QLabel('') 114 self._hint.setObjectName('subtitle') 115 lay.addWidget(self._hint) 116 117 lay.addSpacing(8) 118 119 # ── botones ─────────────────────────────────────────────────────── 120 bbox = QDialogButtonBox() 121 self._btn_ok = bbox.addButton(_('Confirmar'), QDialogButtonBox.ButtonRole.AcceptRole) 122 btn_skip = bbox.addButton(_('Omitir'), QDialogButtonBox.ButtonRole.RejectRole) 123 self._btn_ok.setEnabled(True) 124 bbox.accepted.connect(self._accept) 125 bbox.rejected.connect(self.reject) 126 lay.addWidget(bbox) 127 128 # ── Lógica ──────────────────────────────────────────────────────────────── 129 130 def _browse(self) -> None: 131 start = self._dir_edit.text().strip() or str(Path.home()) 132 chosen = QFileDialog.getExistingDirectory( 133 self, _('Seleccionar directorio de cursos'), start, 134 QFileDialog.Option.ShowDirsOnly, 135 ) 136 if chosen: 137 self._dir_edit.setText(chosen) 138 139 def _on_text_changed(self, text: str) -> None: 140 path = Path(text.strip()) if text.strip() else None 141 self._btn_ok.setEnabled(bool(text.strip())) 142 if path and path.is_dir(): 143 n = sum(1 for d in path.iterdir() if d.is_dir()) 144 self._hint.setText(_('{} subcarpeta(s) encontrada(s) en ese directorio.').format(n)) 145 elif path: 146 self._hint.setText(_('El directorio se creará al confirmar.')) 147 else: 148 self._hint.setText('') 149 150 def _accept(self) -> None: 151 import shutil 152 text = self._dir_edit.text().strip() 153 if not text: 154 return 155 path = Path(text) 156 if not path.exists(): 157 path.mkdir(parents=True, exist_ok=True) 158 # copiar curso de ejemplo si el directorio estaba vacío 159 _root = Path(__file__).parent.parent.parent.parent # src/gui/dialogs → project root 160 ejemplo_src = _root / 'courses' / 'ejemplo' 161 ejemplo_dst = path / 'ejemplo' 162 if ejemplo_src.exists() and not ejemplo_dst.exists(): 163 shutil.copytree(ejemplo_src, ejemplo_dst, 164 ignore=shutil.ignore_patterns('output')) 165 self._selected = path 166 save_courses_dir(path) 167 self.accept() 168 169 # ── API pública ──────────────────────────────────────────────────────────── 170 171 def selected_dir(self) -> Path | None: 172 """Devuelve el directorio elegido, o None si se omitió.""" 173 return self._selected
Diálogo de primer arranque: permite seleccionar el directorio de cursos.