eee_alarm_clock/clock/ui/add_alarm.py

299 lines
12 KiB
Python
Raw Permalink Normal View History

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