299 lines
12 KiB
Python
299 lines
12 KiB
Python
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
|