eee_alarm_clock/clock/ui/list_alarms.py

171 lines
6.5 KiB
Python
Raw Permalink Normal View History

import curses
from datetime import datetime
class ListAlarmsView:
def __init__(self, storage):
"""Initialize the list alarms view"""
self.storage = storage
self.weekday_names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
self.selected_index = 0
self.alarms = []
def reset_state(self):
"""Reset the view state"""
self.selected_index = 0
self.alarms = self.storage.get_saved_alerts()
def update_alarms(self, alarms):
"""Update the list of alarms to display."""
self.alarms = alarms
self.selected_index = 0
def get_selected_alarm(self):
"""Get the currently selected alarm."""
if 0 <= self.selected_index < len(self.alarms):
return self.alarms[self.selected_index]
return None
def draw(self, stdscr):
"""Draw the list of alarms screen"""
height, width = stdscr.getmaxyx()
self._draw_header(stdscr, width)
visible_range = self._calculate_visible_range(height)
self._draw_alarm_list(stdscr, height, width, visible_range)
self._draw_instructions(stdscr, height, width)
def handle_input(self, key):
"""Handle user input and return the next view name or None to stay"""
total_items = len(self.alarms) + 1 # +1 for "Add new alarm" option
if key == 27: # ESC
return 'CLOCK'
elif key in [ord('j'), curses.KEY_DOWN]:
self.selected_index = (self.selected_index + 1) % total_items
elif key in [ord('k'), curses.KEY_UP]:
self.selected_index = (self.selected_index - 1) % total_items
elif key == ord('d'):
return self._handle_delete()
elif key in [ord('a'), 10]: # 'a' or Enter
return self._handle_add_edit()
return None
def _calculate_visible_range(self, height):
"""Calculate the visible range for scrolling"""
max_visible_items = height - 8 # Space for header and footer
total_items = len(self.alarms) + 1 # +1 for "Add new alarm" option
# Calculate scroll position
start_idx = max(0, min(self.selected_index - max_visible_items // 2,
total_items - max_visible_items))
if start_idx < 0:
start_idx = 0
end_idx = min(start_idx + max_visible_items, total_items)
return (start_idx, end_idx)
def _draw_header(self, stdscr, width):
"""Draw the header text"""
header_text = "Alarms"
stdscr.attron(curses.color_pair(1) | curses.A_BOLD)
stdscr.addstr(2, self._center_x(width, header_text), header_text)
stdscr.attroff(curses.color_pair(1) | curses.A_BOLD)
def _draw_alarm_list(self, stdscr, height, width, visible_range):
"""Draw the list of alarms"""
start_idx, end_idx = visible_range
for i in range(start_idx, end_idx):
y_pos = 4 + (i - start_idx)
display_str = self._format_alarm_display(i)
# Truncate if too long
max_length = width - 6
if len(display_str) > max_length:
display_str = display_str[:max_length-3] + "..."
x_pos = self._center_x(width, display_str)
self._draw_alarm_item(stdscr, y_pos, x_pos, display_str, i == self.selected_index)
def _format_alarm_display(self, index):
"""Format the display string for an alarm"""
if index == len(self.alarms):
return "Add new alarm..."
alarm = self.alarms[index]
time_str = alarm.get('time', 'Unknown')
repeat_info = self._format_repeat_info(alarm)
status = "" if alarm.get('enabled', True) else ""
return f"{status} {time_str} {alarm.get('name', 'Unnamed')}{repeat_info}"
def _format_repeat_info(self, alarm):
"""Format the repeat information for an alarm"""
repeat_rule = alarm.get('repeat_rule', {})
if not repeat_rule:
return ""
if repeat_rule.get('type') == 'weekly':
days = repeat_rule.get('days', [])
return f" (Every {', '.join(self.weekday_names[d] for d in days)})"
elif repeat_rule.get('type') == 'once' and repeat_rule.get('date'):
return f" (On {repeat_rule['date']})"
return ""
def _draw_alarm_item(self, stdscr, y_pos, x_pos, display_str, is_selected):
"""Draw a single alarm item"""
if is_selected:
# Draw selection brackets in green
stdscr.attron(curses.color_pair(1))
stdscr.addstr(y_pos, x_pos - 2, "[ ")
stdscr.addstr(y_pos, x_pos + len(display_str), " ]")
stdscr.attroff(curses.color_pair(1))
# Draw text in yellow (highlighted)
stdscr.attron(curses.color_pair(2))
stdscr.addstr(y_pos, x_pos, display_str)
stdscr.attroff(curses.color_pair(2))
else:
# Draw normal items in green
stdscr.attron(curses.color_pair(1))
stdscr.addstr(y_pos, x_pos, display_str)
stdscr.attroff(curses.color_pair(1))
def _draw_instructions(self, stdscr, height, width):
"""Draw the instructions at the bottom of the screen"""
instructions = "j/k: Move d: Delete a: Add/Edit Esc: Back"
stdscr.attron(curses.color_pair(1))
stdscr.addstr(height - 2, self._center_x(width, instructions), instructions)
stdscr.attroff(curses.color_pair(1))
def _center_x(self, width, text):
"""Calculate x coordinate to center text"""
return width // 2 - len(text) // 2
def _handle_delete(self):
"""Handle alarm deletion"""
if self.selected_index < len(self.alarms):
try:
alarm_to_delete = self.alarms[self.selected_index]
self.storage.remove_saved_alert(alarm_to_delete['id'])
self.alarms = self.storage.get_saved_alerts()
# Adjust selected item if needed
if self.selected_index >= len(self.alarms):
self.selected_index = len(self.alarms)
except Exception as e:
# You might want to add error handling here
pass
return None
def _handle_add_edit(self):
"""Handle add/edit action"""
if self.selected_index == len(self.alarms):
# "Add new alarm" option selected
return 'ADD_ALARM'
else:
# Edit existing alarm
selected_alarm = self.alarms[self.selected_index]
return ('ADD_ALARM', selected_alarm) # Pass alarm to AddAlarmView