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