import curses from datetime import datetime, date, timedelta class AddAlarmView: def __init__(self, storage, control_queue): self.storage = storage self.control_queue = control_queue self.weekday_names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] self.reset_state() def set_alarm_data(self, alarm): """Load existing alarm data for editing.""" self.alarm_data = alarm # Store for pre-filling the form # Debug log to inspect the alarm structure import logging logging.getLogger(__name__).debug(f"set_alarm_data received: {alarm}") # Ensure alarm is a dictionary if not isinstance(alarm, dict): logging.getLogger(__name__).error(f"Invalid alarm format: {type(alarm)}") # Parse time string hour, minute, _ = map(int, alarm['time'].split(':')) # Extract hour and minute return # Pre-fill the form fields with alarm data self.alarm_draft = { 'hour': hour, 'minute': minute, 'name': alarm.get('name', 'Unnamed Alarm'), 'enabled': alarm.get('enabled', True), 'date': None, # Your structure doesn't use date directly 'weekdays': alarm.get('repeat_rule', {}).get('days_of_week', []), 'current_weekday': 0, 'editing_name': False, 'temp_name': alarm.get('name', 'Unnamed Alarm'), 'selected_item': 0 } def reset_state(self): """Reset all state variables to their initial values""" self.alarm_draft = { 'hour': datetime.now().hour, 'minute': datetime.now().minute, 'name': 'New Alarm', 'enabled': True, 'date': None, 'weekdays': [], 'current_weekday': 0, 'editing_name': False, 'temp_name': '', 'selected_item': 0 } self.date_edit_pos = 2 # Default to editing the day def draw(self, stdscr): """Draw the add alarm screen""" height, width = stdscr.getmaxyx() form_y = height // 2 - 8 self._draw_title(stdscr, form_y, width) self._draw_time_field(stdscr, form_y + 2, width) self._draw_date_field(stdscr, form_y + 4, width) self._draw_weekdays(stdscr, form_y + 6, width) self._draw_name_field(stdscr, form_y + 8, width) self._draw_status_field(stdscr, form_y + 10, width) self._draw_instructions(stdscr, height - 2, width) def handle_input(self, key): """Handle user input and return the next view name or None to stay""" if key == 27: # ESC return self._handle_escape() elif key == 10: # ENTER return self._handle_enter() if not self.alarm_draft['editing_name']: if key in [ord('h'), curses.KEY_LEFT]: self.alarm_draft['selected_item'] = (self.alarm_draft['selected_item'] - 1) % 6 elif key in [ord('l'), curses.KEY_RIGHT]: self.alarm_draft['selected_item'] = (self.alarm_draft['selected_item'] + 1) % 6 self._handle_field_input(key) return None def _handle_field_input(self, key): """Handle input for the currently selected field""" selected_item = self.alarm_draft['selected_item'] if key in [ord('k'), curses.KEY_UP, ord('j'), curses.KEY_DOWN]: is_up = key in [ord('k'), curses.KEY_UP] if selected_item == 0: self._adjust_hour(is_up) elif selected_item == 1: self._adjust_minute(is_up) elif selected_item == 2: self._adjust_date(is_up) elif selected_item == 3: self._handle_weekday_input(key) elif selected_item == 4: self._handle_name_input(key) elif selected_item == 5 and key == 32: self.alarm_draft['enabled'] = not self.alarm_draft['enabled'] def _draw_title(self, stdscr, y, width): """Draw the title of the form""" title = "Add New Alarm" stdscr.attron(curses.color_pair(1) | curses.A_BOLD) stdscr.addstr(y, width // 2 - len(title) // 2, title) stdscr.attroff(curses.color_pair(1) | curses.A_BOLD) def _draw_field(self, stdscr, y, label, value, is_selected): """Draw a form field with label and value""" label_str = f"{label}: " x = self._center_text_x(label_str + str(value)) stdscr.attron(curses.color_pair(1)) stdscr.addstr(y, x, label_str) stdscr.attroff(curses.color_pair(1)) if is_selected: stdscr.attron(curses.color_pair(2)) stdscr.addstr(y, x + len(label_str), str(value)) if is_selected: stdscr.attroff(curses.color_pair(2)) def _draw_time_field(self, stdscr, y, width): """Draw the time field""" self._draw_field(stdscr, y, "Time", f"{self.alarm_draft['hour']:02d}:{self.alarm_draft['minute']:02d}", self.alarm_draft['selected_item'] in [0, 1]) def _draw_date_field(self, stdscr, y, width): """Draw the date field""" date_str = "Repeating weekly" if self.alarm_draft['weekdays'] else ( self.alarm_draft['date'].strftime("%Y-%m-%d") if self.alarm_draft['date'] else "None" ) self._draw_field(stdscr, y, "Date", date_str, self.alarm_draft['selected_item'] == 2) def _draw_weekdays(self, stdscr, y, width): """Draw the weekday selection field""" label_x = width // 2 - 20 stdscr.attron(curses.color_pair(1)) stdscr.addstr(y, label_x, "Repeat: ") stdscr.attroff(curses.color_pair(1)) weekday_x = label_x + len("Repeat: ") for i, day in enumerate(self.weekday_names): self._draw_weekday(stdscr, y, weekday_x + i * 4, day, i) def _draw_weekday(self, stdscr, y, x, day, index): """Draw a single weekday""" is_selected = (self.alarm_draft['selected_item'] == 3 and index == self.alarm_draft['current_weekday']) is_active = index in self.alarm_draft['weekdays'] if is_selected: stdscr.attron(curses.color_pair(2)) elif is_active: stdscr.attron(curses.color_pair(1) | curses.A_BOLD) else: stdscr.attron(curses.color_pair(1)) stdscr.addstr(y, x, day) if is_selected: stdscr.attroff(curses.color_pair(2)) elif is_active: stdscr.attroff(curses.color_pair(1) | curses.A_BOLD) else: stdscr.attroff(curses.color_pair(1)) def _draw_name_field(self, stdscr, y, width): """Draw the name field""" self._draw_field(stdscr, y, "Name", self.alarm_draft['name'], self.alarm_draft['selected_item'] == 4) def _draw_status_field(self, stdscr, y, width): """Draw the enabled/disabled status field""" enabled_str = "● Enabled" if self.alarm_draft['enabled'] else "○ Disabled" self._draw_field(stdscr, y, "Status", enabled_str, self.alarm_draft['selected_item'] == 5) def _draw_instructions(self, stdscr, y, width): """Draw the instructions at the bottom of the screen""" instructions = "j/k: Change h/l: Move Space: Toggle Enter: Save Esc: Cancel" stdscr.attron(curses.color_pair(1)) stdscr.addstr(y, width // 2 - len(instructions) // 2, instructions) stdscr.attroff(curses.color_pair(1)) def _center_text_x(self, text): """Calculate x position to center text""" return curses.COLS // 2 - len(text) // 2 def _adjust_hour(self, increase): """Adjust the hour value""" delta = 1 if increase else -1 self.alarm_draft['hour'] = (self.alarm_draft['hour'] + delta) % 24 def _adjust_minute(self, increase): """Adjust the minute value""" delta = 1 if increase else -1 self.alarm_draft['minute'] = (self.alarm_draft['minute'] + delta) % 60 def _adjust_date(self, increase): """Adjust the date value""" if not self.alarm_draft['date']: self.alarm_draft['date'] = datetime.now().date() return delta = 1 if increase else -1 current_date = self.alarm_draft['date'] try: if self.date_edit_pos == 0: # Year new_year = max(current_date.year + delta, datetime.now().year) self.alarm_draft['date'] = current_date.replace(year=new_year) elif self.date_edit_pos == 1: # Month new_month = max(1, min(12, current_date.month + delta)) max_day = (datetime(current_date.year, new_month, 1) + timedelta(days=31)).replace(day=1) - timedelta(days=1) self.alarm_draft['date'] = current_date.replace( month=new_month, day=min(current_date.day, max_day.day) ) elif self.date_edit_pos == 2: # Day max_day = (datetime(current_date.year, current_date.month, 1) + timedelta(days=31)).replace(day=1) - timedelta(days=1) new_day = max(1, min(max_day.day, current_date.day + delta)) self.alarm_draft['date'] = current_date.replace(day=new_day) except ValueError as e: # Handle date validation errors pass def _handle_weekday_input(self, key): """Handle input for weekday selection""" if key in [ord('h'), curses.KEY_LEFT]: self.alarm_draft['current_weekday'] = (self.alarm_draft['current_weekday'] - 1) % 7 elif key in [ord('l'), curses.KEY_RIGHT]: self.alarm_draft['current_weekday'] = (self.alarm_draft['current_weekday'] + 1) % 7 elif key == 32: # SPACE current_day = self.alarm_draft['current_weekday'] if current_day in self.alarm_draft['weekdays']: self.alarm_draft['weekdays'].remove(current_day) else: self.alarm_draft['weekdays'].append(current_day) self.alarm_draft['weekdays'].sort() if self.alarm_draft['weekdays']: self.alarm_draft['date'] = None def _handle_name_input(self, key): """Handle input for name editing""" if key == 32: # SPACE if not self.alarm_draft['editing_name']: self.alarm_draft['editing_name'] = True self.alarm_draft['temp_name'] = self.alarm_draft['name'] self.alarm_draft['name'] = '' elif self.alarm_draft['editing_name']: if key == curses.KEY_BACKSPACE or key == 127: self.alarm_draft['name'] = self.alarm_draft['name'][:-1] elif 32 <= key <= 126: # Printable ASCII self.alarm_draft['name'] += chr(key) def _handle_escape(self): """Handle escape key press""" if self.alarm_draft['editing_name']: self.alarm_draft['name'] = self.alarm_draft['temp_name'] self.alarm_draft['editing_name'] = False return None return 'CLOCK' def _handle_enter(self): """Handle enter key press""" if self.alarm_draft['editing_name']: self.alarm_draft['editing_name'] = False self.alarm_draft['selected_item'] = 0 return None try: alarm_data = { "name": self.alarm_draft['name'], "time": f"{self.alarm_draft['hour']:02d}:{self.alarm_draft['minute']:02d}:00", "enabled": self.alarm_draft['enabled'], "repeat_rule": { "type": "weekly" if self.alarm_draft['weekdays'] else "once", "days_of_week": [self.weekday_names[day].lower() for day in self.alarm_draft['weekdays']], "at": (self.alarm_draft['date'].strftime("%Y-%m-%d") if self.alarm_draft['date'] and not self.alarm_draft['weekdays'] else None) } } self.storage.save_new_alert(alarm_data) return 'CLOCK' except Exception as e: # Handle save errors return None