import curses import time import threading import logging import queue from datetime import datetime from .utils import init_colors, draw_error, ColorScheme from .active_alarm import ActiveAlarmView from .add_alarm import AddAlarmView from .list_alarms import ListAlarmsView from .main_clock import MainClockView class UI: def __init__(self, alarm_system_manager, control_queue): """Initialize the UI system""" # System components self.alarm_system = alarm_system_manager self.stop_event = alarm_system_manager.stop_event self.storage = alarm_system_manager.storage self.control_queue = control_queue # Logging self.logger = logging.getLogger(__name__) # Initialize views self._init_views() # UI State self.current_view = 'CLOCK' self.error_message = None self.error_timestamp = None def _init_views(self): """Initialize all view classes""" self.views = { 'CLOCK': MainClockView(), 'ADD_ALARM': AddAlarmView(self.storage, self.control_queue), 'LIST_ALARMS': ListAlarmsView(self.storage), 'ACTIVE_ALARMS': ActiveAlarmView(self.storage, self.control_queue) } def run(self): """Start the ncurses UI in a separate thread""" def ui_thread(): try: # Start control queue monitor monitor_thread = threading.Thread( target=self._monitor_control_queue, daemon=True ) monitor_thread.start() # Start UI curses.wrapper(self._main_loop) except Exception as e: self.logger.error(f"UI Thread Error: {e}") finally: self.stop_event.set() ui_thread_obj = threading.Thread(target=ui_thread, daemon=True) ui_thread_obj.start() return ui_thread_obj def _monitor_control_queue(self): """Monitor the control queue for alarm events""" while not self.stop_event.is_set(): try: # Non-blocking check of control queue try: control_msg = self.control_queue.get(timeout=1) if control_msg['type'] == 'trigger': # Update active alarms view active_view = self.views['ACTIVE_ALARMS'] alarm_id = control_msg['alarm_id'] active_view.update_active_alarms({ alarm_id: control_msg['info'] }) # Switch to active alarms view self.current_view = 'ACTIVE_ALARMS' except queue.Empty: pass time.sleep(0.1) except Exception as e: self.logger.error(f"Error monitoring control queue: {e}") time.sleep(1) def _show_error(self, message, duration=3): """Display an error message""" self.error_message = message self.error_timestamp = time.time() def _clear_error_if_expired(self): """Clear error message if expired""" if self.error_message and self.error_timestamp: if time.time() - self.error_timestamp > 3: self.error_message = None self.error_timestamp = None def _main_loop(self, stdscr): """Main ncurses event loop""" # Setup curses curses.curs_set(0) # Hide cursor stdscr.keypad(1) # Enable keypad stdscr.timeout(100) # Non-blocking input init_colors() # Initialize color pairs while not self.stop_event.is_set(): # Clear screen stdscr.erase() # Get current view current_view = self.views.get(self.current_view) if not current_view: self.logger.error(f"Invalid view: {self.current_view}") break try: # Draw current view current_view.draw(stdscr) # Handle any error messages if self.error_message: draw_error(stdscr, self.error_message) self._clear_error_if_expired() # Refresh screen stdscr.refresh() # Handle input key = stdscr.getch() if key != -1: # Handle quit key globally if key == ord('q'): if self.current_view == 'CLOCK': break # Exit application from clock view else: self.current_view = 'CLOCK' # Return to clock from other views continue # Let current view handle input result = current_view.handle_input(key) # Handle tuple result (view, data) or just a view change if isinstance(result, tuple): next_view, data = result else: next_view, data = result, None # Handle quitting if next_view == 'QUIT': break elif next_view: # Update list alarms view data when switching to it if next_view == 'LIST_ALARMS': self.views['LIST_ALARMS'].update_alarms( self.storage.get_saved_alerts() ) # Handle editing an alarm by passing data to AddAlarmView elif next_view == 'ADD_ALARM' and data: self.views['ADD_ALARM'].set_alarm_data(data) self.current_view = next_view except Exception as e: self.logger.error(f"Error in main loop: {e}") self._show_error(str(e)) time.sleep(0.1) # Prevent CPU hogging def stop(self): """Stop the UI system""" self.stop_event.set()