""" Main Window - Primary application window with 3-panel layout """ from PyQt5.QtWidgets import ( QMainWindow, QSplitter, QStatusBar, QMenuBar, QMenu, QAction, QMessageBox, QApplication ) from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QKeySequence from app import config from app.models import Course, get_course from app.database import Database, get_database from .navigation_panel import NavigationPanel from .content_viewer import ContentViewer from .progress_panel import ProgressPanel class MainWindow(QMainWindow): """Main application window with 3-panel layout""" def __init__(self): super().__init__() # Load course and database self.course = get_course() self.db = get_database() # Get or create default user self.user_id = self._get_or_create_user() # Current state self.current_lesson_id = None # Initialize UI self.init_ui() self.create_menus() # Connect signals self.connect_signals() # Auto-save timer self.auto_save_timer = QTimer(self) self.auto_save_timer.timeout.connect(self.auto_save) self.auto_save_timer.start(config.AUTO_SAVE_INTERVAL * 1000) # Convert to ms # Load progress and restore state self.load_initial_state() def init_ui(self): """Initialize the user interface""" self.setWindowTitle(f"{config.APP_NAME} v{config.APP_VERSION}") self.setGeometry(100, 100, config.DEFAULT_WINDOW_WIDTH, config.DEFAULT_WINDOW_HEIGHT) # Create 3-panel splitter layout self.splitter = QSplitter(Qt.Horizontal) # Create panels self.navigation_panel = NavigationPanel(self.course, self) self.content_viewer = ContentViewer(self) self.progress_panel = ProgressPanel(self.course, self) # Add panels to splitter self.splitter.addWidget(self.navigation_panel) self.splitter.addWidget(self.content_viewer) self.splitter.addWidget(self.progress_panel) # Set initial splitter sizes self.splitter.setSizes([ config.NAVIGATION_PANEL_DEFAULT_WIDTH, config.DEFAULT_WINDOW_WIDTH - config.NAVIGATION_PANEL_DEFAULT_WIDTH - config.PROGRESS_PANEL_DEFAULT_WIDTH, config.PROGRESS_PANEL_DEFAULT_WIDTH ]) # Set as central widget self.setCentralWidget(self.splitter) # Create status bar self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.showMessage("Ready") def create_menus(self): """Create menu bar""" menubar = self.menuBar() # File Menu file_menu = menubar.addMenu("&File") exit_action = QAction("E&xit", self) exit_action.setShortcut(QKeySequence.Quit) exit_action.triggered.connect(self.close) file_menu.addAction(exit_action) # View Menu view_menu = menubar.addMenu("&View") toggle_nav_action = QAction("Toggle &Navigation Panel", self) toggle_nav_action.setShortcut("Ctrl+1") toggle_nav_action.triggered.connect(lambda: self.navigation_panel.setVisible(not self.navigation_panel.isVisible())) view_menu.addAction(toggle_nav_action) toggle_progress_action = QAction("Toggle &Progress Panel", self) toggle_progress_action.setShortcut("Ctrl+2") toggle_progress_action.triggered.connect(lambda: self.progress_panel.setVisible(not self.progress_panel.isVisible())) view_menu.addAction(toggle_progress_action) view_menu.addSeparator() reset_layout_action = QAction("&Reset Layout", self) reset_layout_action.triggered.connect(self.reset_layout) view_menu.addAction(reset_layout_action) # Help Menu help_menu = menubar.addMenu("&Help") about_action = QAction("&About", self) about_action.triggered.connect(self.show_about) help_menu.addAction(about_action) def connect_signals(self): """Connect signals between components""" self.navigation_panel.lesson_selected.connect(self.on_lesson_selected) def _get_or_create_user(self) -> int: """Get or create default user""" # Check if user exists row = self.db.fetch_one("SELECT user_id FROM users LIMIT 1") if row: return row[0] # Create default user cursor = self.db.execute(""" INSERT INTO users (username, created_at) VALUES (?, datetime('now')) """, ("default",)) self.db.commit() return cursor.lastrowid def load_initial_state(self): """Load progress and restore application state""" # Get overall progress progress = self.db.get_overall_progress(self.user_id) # Update progress panel completed_lessons = progress.get('lessons_completed', 0) total_points = progress.get('total_points', 0) total_time = progress.get('total_time', 0) self.progress_panel.update_progress(completed_lessons, total_points, total_time) # Update part progress for part in self.course.parts: part_completed = 0 for lesson in part.lessons: lesson_prog = self.db.get_lesson_progress(self.user_id, lesson.id) if lesson_prog and lesson_prog['status'] == 'completed': part_completed += 1 part_total = len(part.lessons) self.progress_panel.update_part_progress(part.number, part_completed, part_total) # Update study streak streak = self.db.get_study_streak(self.user_id) if hasattr(self.db, 'get_study_streak') else 0 self.progress_panel.update_streak(streak) # Update exercises if hasattr(self.db, 'get_exercise_progress'): exercise_progress = self.db.get_exercise_progress(self.user_id) if exercise_progress: self.progress_panel.update_exercises( exercise_progress.get('completed', 0), self.course.total_exercises ) else: self.progress_panel.update_exercises(0, self.course.total_exercises) # Update lesson statuses in navigation for lesson in self.course.get_all_lessons(): lesson_progress = self.db.get_lesson_progress(self.user_id, lesson.id) status = lesson_progress['status'] if lesson_progress else 'not_started' self.navigation_panel.update_lesson_status(lesson.id, status) # Get last viewed lesson row = self.db.fetch_one(""" SELECT lesson_id FROM lesson_progress WHERE user_id = ? ORDER BY last_accessed DESC LIMIT 1 """, (self.user_id,)) if row: last_lesson_id = row[0] # Don't auto-load, just highlight it self.navigation_panel.set_current_lesson(last_lesson_id) def on_lesson_selected(self, lesson_id: str): """Handle lesson selection from navigation""" lesson = self.course.get_lesson(lesson_id) if not lesson: return self.current_lesson_id = lesson_id # Update navigation highlight self.navigation_panel.set_current_lesson(lesson_id) # Load lesson content self.content_viewer.load_lesson(lesson) # Update progress panel self.progress_panel.update_current_lesson( lesson.title, lesson.points, lesson.estimated_time ) # Update database (mark as in_progress if not already completed) lesson_progress = self.db.get_lesson_progress(self.user_id, lesson_id) current_status = lesson_progress['status'] if lesson_progress else 'not_started' if current_status == 'not_started': self.db.update_lesson_progress( self.user_id, lesson_id, status='in_progress' ) self.navigation_panel.update_lesson_status(lesson_id, 'in_progress') # Update last accessed self.db.update_lesson_progress( self.user_id, lesson_id, last_accessed=True ) # Update status bar self.status_bar.showMessage(f"Lesson {lesson.order}: {lesson.title}") def auto_save(self): """Auto-save progress""" if not self.current_lesson_id: return # Get scroll position scroll_pos = self.content_viewer.get_scroll_position() # Update in database self.db.update_lesson_progress( self.user_id, self.current_lesson_id, scroll_position=scroll_pos, time_spent_increment=config.AUTO_SAVE_INTERVAL ) def reset_layout(self): """Reset window layout to defaults""" self.splitter.setSizes([ config.NAVIGATION_PANEL_DEFAULT_WIDTH, config.DEFAULT_WINDOW_WIDTH - config.NAVIGATION_PANEL_DEFAULT_WIDTH - config.PROGRESS_PANEL_DEFAULT_WIDTH, config.PROGRESS_PANEL_DEFAULT_WIDTH ]) self.navigation_panel.setVisible(True) self.progress_panel.setVisible(True) def show_about(self): """Show about dialog""" QMessageBox.about(self, "About", f"""

{config.APP_NAME}

Version {config.APP_VERSION}

By {config.APP_AUTHOR}


An interactive desktop application for learning about Tesla coils and electromagnetic theory.


Course Statistics:

""") def closeEvent(self, event): """Handle window close event""" # Final auto-save self.auto_save() # Accept close event.accept()