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.

OnboardingDialog(parent=None)
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()
def selected_dir(self) -> pathlib.Path | None:
156    def selected_dir(self) -> Path | None:
157        """Devuelve el directorio elegido, o None si se omitió."""
158        return self._selected

Devuelve el directorio elegido, o None si se omitió.

staticMetaObject = PySide6.QtCore.QMetaObject("OnboardingDialog" inherits "QDialog": )