2121"""
2222
2323import json
24+ import os
25+ import sys
2426from datetime import date
2527from pathlib import Path
2628
@@ -244,14 +246,24 @@ def _action_btn(text, accent=False):
244246 return b
245247
246248 self ._daily_sync_btn = _action_btn ("▶ Daily Sync" , accent = True )
249+ self ._daily_sync_btn .setToolTip (
250+ "Download missing days from Garmin Connect.\n "
251+ "Includes intraday data, daily summaries, weather and pollen." )
247252 self ._daily_sync_btn .clicked .connect (self ._on_daily_sync )
248253 right .addWidget (self ._daily_sync_btn )
249254
250255 self ._mirror_btn = _action_btn ("⬡ Mirror" )
256+ self ._mirror_btn .setToolTip (
257+ "Create an encrypted backup of the archive (.gla container).\n "
258+ "Also allows importing data from an existing mirror." )
251259 self ._mirror_btn .clicked .connect (self ._on_mirror )
252260 right .addWidget (self ._mirror_btn )
253261
254262 self ._timer_btn = _action_btn ("⏱ Timer: Off" )
263+ self ._timer_btn .setToolTip (
264+ "Start the background timer.\n "
265+ "Automatically syncs missing days at the configured interval\n "
266+ "while the app is open." )
255267 self ._timer_btn .setStyleSheet (
256268 f"QPushButton {{ background: { self ._app .BG3 } ; color: { self ._app .TEXT2 } ; "
257269 f"border: none; padding: 7px 14px; }}"
@@ -260,6 +272,12 @@ def _action_btn(text, accent=False):
260272 lambda : self ._app ._panel_timer ._toggle_timer ())
261273 right .addWidget (self ._timer_btn )
262274
275+ self ._docs_btn = _action_btn ("📖 Documentation" )
276+ self ._docs_btn .setToolTip (
277+ "Open Quickstart, User Guide or README." )
278+ self ._docs_btn .clicked .connect (self ._home_docs_dialog )
279+ right .addWidget (self ._docs_btn )
280+
263281 right .addStretch ()
264282 cols .addLayout (right , stretch = 1 )
265283
@@ -423,6 +441,81 @@ def _on_garmin_done():
423441
424442 # ── Mirror ─────────────────────────────────────────────────────────────────
425443
444+ def _home_docs_dialog (self ):
445+ """Documentation button handler — opens Quickstart, User Guide or README."""
446+ def _open (filename : str ):
447+ # Dev: src/docs/ | Build T2/T3: info/ next to EXE
448+ if getattr (sys , "frozen" , False ):
449+ base = Path (sys .executable ).parent / "info"
450+ else :
451+ base = Path (__file__ ).parent .parent / "docs"
452+ path = base / filename
453+ if not path .exists ():
454+ QMessageBox .warning (
455+ self ._app , "Documentation" ,
456+ f"File not found:\n { path } " )
457+ return
458+ try :
459+ os .startfile (path )
460+ except Exception as e :
461+ QMessageBox .critical (
462+ self ._app , "Documentation" ,
463+ f"Could not open file:\n { e } " )
464+
465+ dlg = QDialog (self ._app )
466+ dlg .setWindowTitle ("Documentation" )
467+ dlg .setModal (True )
468+ dlg .setFixedWidth (360 )
469+ dlg .setStyleSheet (
470+ f"background: { self ._app .BG } ; color: { self ._app .TEXT } ;" )
471+ lay = QVBoxLayout (dlg )
472+ lay .setContentsMargins (20 , 16 , 20 , 16 )
473+ lay .setSpacing (6 )
474+
475+ title = QLabel ("Documentation" )
476+ title .setFont (QFont ("Segoe UI" , 9 , QFont .Weight .Bold ))
477+ title .setStyleSheet (f"color: { self ._app .TEXT } ;" )
478+ lay .addWidget (title )
479+
480+ sep = QFrame ()
481+ sep .setFrameShape (QFrame .Shape .HLine )
482+ sep .setStyleSheet (f"color: { self ._app .ACCENT } ;" )
483+ sep .setFixedHeight (1 )
484+ lay .addWidget (sep )
485+
486+ for label , filename in [
487+ ("📋 Quickstart" , "QUICKSTART.txt" ),
488+ ("📖 User Guide" , "USER_GUIDE.txt" ),
489+ ("📄 README App" , "README_APP.md" ),
490+ ]:
491+ btn = QPushButton (label )
492+ btn .setFont (QFont ("Segoe UI" , 9 ))
493+ btn .setStyleSheet (
494+ f"QPushButton {{ background: { self ._app .BG3 } ; color: { self ._app .TEXT } ; "
495+ f"border: none; padding: 8px 14px; text-align: left; }}"
496+ f"QPushButton:hover {{ background: { self ._app .ACCENT2 } ; }}" )
497+ btn .setCursor (Qt .CursorShape .PointingHandCursor )
498+ _fn = filename
499+ btn .clicked .connect (lambda checked , fn = _fn : [dlg .accept (), _open (fn )])
500+ lay .addWidget (btn )
501+
502+ sep2 = QFrame ()
503+ sep2 .setFrameShape (QFrame .Shape .HLine )
504+ sep2 .setStyleSheet (f"color: { self ._app .BG3 } ;" )
505+ lay .addWidget (sep2 )
506+
507+ cancel_btn = QPushButton ("Cancel" )
508+ cancel_btn .setFont (QFont ("Segoe UI" , 9 ))
509+ cancel_btn .setStyleSheet (
510+ f"QPushButton {{ background: { self ._app .BG3 } ; color: { self ._app .TEXT2 } ; "
511+ f"border: none; padding: 7px 14px; }}"
512+ f"QPushButton:hover {{ background: { self ._app .ACCENT2 } ; }}" )
513+ cancel_btn .setCursor (Qt .CursorShape .PointingHandCursor )
514+ cancel_btn .clicked .connect (dlg .reject )
515+ lay .addWidget (cancel_btn )
516+
517+ dlg .exec ()
518+
426519 def _on_mirror (self ):
427520 """Mirror button handler. Main Thread only."""
428521 s = self ._app ._panel_settings ._collect_settings ()
@@ -435,7 +528,7 @@ def _on_mirror(self):
435528 def _home_mirror_dialog_no_target (self ):
436529 """Dialog shown when no mirror target is configured."""
437530 dlg = QDialog (self ._app )
438- dlg .setWindowTitle ("Mirror" )
531+ dlg .setWindowTitle ("Export to Mirror" )
439532 dlg .setModal (True )
440533 dlg .setFixedWidth (460 )
441534 dlg .setStyleSheet (
@@ -549,7 +642,7 @@ def _save_and_continue():
549642 def _home_mirror_dialog_with_target (self , mirror_dir : str ):
550643 """Dialog shown when mirror target is already configured."""
551644 dlg = QDialog (self ._app )
552- dlg .setWindowTitle ("Mirror" )
645+ dlg .setWindowTitle ("Export to Mirror" )
553646 dlg .setModal (True )
554647 dlg .setFixedWidth (380 )
555648 dlg .setStyleSheet (
@@ -558,7 +651,7 @@ def _home_mirror_dialog_with_target(self, mirror_dir: str):
558651 lay .setContentsMargins (20 , 16 , 20 , 16 )
559652 lay .setSpacing (6 )
560653
561- title = QLabel ("Mirror" )
654+ title = QLabel ("Export to Mirror" )
562655 title .setFont (QFont ("Segoe UI" , 9 , QFont .Weight .Bold ))
563656 title .setStyleSheet (f"color: { self ._app .TEXT } ;" )
564657 lay .addWidget (title )
0 commit comments