eee_alarm_clock/alert_api/ncurses_ui_draw.py
2025-01-26 22:02:23 +02:00

222 lines
7.9 KiB
Python

import curses
from datetime import datetime
from big_digits import BIG_DIGITS
def _init_colors():
"""Initialize color pairs matching specification"""
curses.start_color()
curses.use_default_colors()
# Green text on black background (primary color)
curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK)
# Highlight color (yellow)
curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK)
# Error color (red)
curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK)
def _draw_error(stdscr, error_message):
"""Draw error message following specification"""
height, width = stdscr.getmaxyx()
# Truncate message if too long
error_message = error_message[:width-4]
error_x = max(0, width // 2 - len(error_message) // 2)
error_y = height - 4 # Show near bottom of screen
stdscr.attron(curses.color_pair(3) | curses.A_BOLD)
stdscr.addstr(error_y, error_x, error_message)
stdscr.attroff(curses.color_pair(3) | curses.A_BOLD)
def _draw_active_alarms(stdscr, context):
"""Draw the active alarms screen"""
height, width = stdscr.getmaxyx()
active_alarms = context.get('active_alarms', {})
if not active_alarms:
return
# Get the first (or only) active alarm
alarm_id = list(active_alarms.keys())[0]
alarm_info = active_alarms[alarm_id]
alarm_config = alarm_info['config']
# Draw alarm details
stdscr.addstr(height // 2 - 2, width // 2 - 10, "ALARM TRIGGERED")
stdscr.addstr(height // 2, width // 2 - len(alarm_config.get('name', 'Unnamed Alarm'))//2,
alarm_config.get('name', 'Unnamed Alarm'))
# Time info
time_str = alarm_config.get('time', 'Unknown Time')
stdscr.addstr(height // 2 + 2, width // 2 - len(time_str)//2, time_str)
# Instructions
stdscr.addstr(height - 2, width // 2 - 15, "S: Snooze D: Dismiss")
def _draw_big_digit(stdscr, y, x, digit):
"""Draw a big digit using predefined patterns"""
patterns = BIG_DIGITS[digit]
for i, line in enumerate(patterns):
stdscr.addstr(y + i, x, line)
def _draw_main_clock(stdscr, context=None):
"""Draw the main clock screen"""
_init_colors()
height, width = stdscr.getmaxyx()
current_time = datetime.now()
# Big time display
time_str = current_time.strftime("%H:%M:%S")
digit_width = 14 # Width of each digit pattern
total_width = digit_width * len(time_str)
start_x = (width - total_width) // 2
start_y = (height - 7) // 2 - 4
# Green color for big time
stdscr.attron(curses.color_pair(1))
for i, digit in enumerate(time_str):
_draw_big_digit(stdscr, start_y, start_x + i * digit_width, digit)
stdscr.attroff(curses.color_pair(1))
# Date display
date_str = current_time.strftime("%Y-%m-%d")
date_x = width // 2 - len(date_str) // 2
date_y = height // 2 + 4
stdscr.attron(curses.color_pair(2))
stdscr.addstr(date_y, date_x, date_str)
stdscr.attroff(curses.color_pair(2))
# Menu options
menu_str = "A: Add Alarm S: List Alarms Q: Quit"
menu_x = width // 2 - len(menu_str) // 2
stdscr.addstr(height - 2, menu_x, menu_str)
def _draw_add_alarm(stdscr, context):
"""Draw the add alarm screen following specification"""
# Ensure context is a dictionary with default values
if context is None:
context = {}
# Provide default values with more explicit checks
context = {
'new_alarm_selected': context.get('new_alarm_selected', 0),
'new_alarm_name': context.get('new_alarm_name', 'New Alarm'),
'new_alarm_hour': context.get('new_alarm_hour', datetime.now().hour),
'new_alarm_minute': context.get('new_alarm_minute', datetime.now().minute),
'new_alarm_enabled': context.get('new_alarm_enabled', True),
'new_alarm_date': context.get('new_alarm_date') or None,
'new_alarm_weekdays': context.get('new_alarm_weekdays', []) or [],
'weekday_names': context.get('weekday_names', ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'])
}
_init_colors()
height, width = stdscr.getmaxyx()
form_y = height // 2 - 3
# Title
stdscr.addstr(form_y - 1, width // 2 - 10, "Add New Alarm")
# Name input
name_str = str(context['new_alarm_name'])
if context['new_alarm_selected'] == 4:
stdscr.attron(curses.color_pair(2))
stdscr.addstr(form_y + 1, width // 2 - 10, f"Name: {name_str}")
if context['new_alarm_selected'] == 4:
stdscr.attroff(curses.color_pair(2))
# Time selection
hour_str = f"{int(context['new_alarm_hour']):02d}"
minute_str = f"{int(context['new_alarm_minute']):02d}"
# Highlight time components
if context['new_alarm_selected'] == 0:
stdscr.attron(curses.color_pair(2))
stdscr.addstr(form_y + 2, width // 2 - 2, hour_str)
if context['new_alarm_selected'] == 0:
stdscr.attroff(curses.color_pair(2))
stdscr.addstr(form_y + 2, width // 2, ":")
if context['new_alarm_selected'] == 1:
stdscr.attron(curses.color_pair(2))
stdscr.addstr(form_y + 2, width // 2 + 1, minute_str)
if context['new_alarm_selected'] == 1:
stdscr.attroff(curses.color_pair(2))
# Enabled toggle
enabled_str = "Enabled" if context['new_alarm_enabled'] else "Disabled"
if context['new_alarm_selected'] == 5:
stdscr.attron(curses.color_pair(2))
stdscr.addstr(form_y + 4, width // 2 - len(enabled_str)//2, enabled_str)
if context['new_alarm_selected'] == 5:
stdscr.attroff(curses.color_pair(2))
# Date selection
if context['new_alarm_date'] and hasattr(context['new_alarm_date'], 'strftime'):
date_str = context['new_alarm_date'].strftime("%Y-%m-%d")
else:
date_str = 'No specific date'
if context['new_alarm_selected'] == 2:
stdscr.attron(curses.color_pair(2))
stdscr.addstr(form_y + 3, width // 2 - len(date_str) // 2, date_str)
if context['new_alarm_selected'] == 2:
stdscr.attroff(curses.color_pair(2))
# Weekday selection
weekday_names = context['weekday_names']
weekday_str = "Repeat: " + " ".join(
weekday_names[i] if i in context['new_alarm_weekdays'] else "___"
for i in range(len(weekday_names))
)
if context['new_alarm_selected'] == 3:
stdscr.attron(curses.color_pair(2))
stdscr.addstr(form_y + 5, width // 2 - len(weekday_str) // 2, weekday_str)
if context['new_alarm_selected'] == 3:
stdscr.attroff(curses.color_pair(2))
# Instructions
stdscr.addstr(height - 2, 2,
"jk: Change hl: Switch Space: Toggle Enter: Save Esc: Cancel")
def _draw_list_alarms(stdscr, context):
"""Draw the list of alarms screen"""
_init_colors()
height, width = stdscr.getmaxyx()
# Header
stdscr.addstr(2, width // 2 - 5, "Alarms")
if not context.get('alarms'):
stdscr.addstr(4, width // 2 - 10, "No alarms set")
else:
weekday_names = context.get('weekday_names', ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'])
for i, alarm in enumerate(context['alarms'][:height-6]):
# Format time
time_str = alarm.get('time', 'Unknown')
# Format repeat info
repeat_info = ""
repeat_rule = alarm.get('repeat_rule', {})
if repeat_rule:
if repeat_rule.get('type') == 'weekly':
days = repeat_rule.get('days', [])
repeat_info = f" (Every {', '.join(weekday_names[d] for d in days)})"
elif repeat_rule.get('type') == 'once' and repeat_rule.get('date'):
repeat_info = f" (On {repeat_rule['date']})"
# Status indicator
status = "" if alarm.get('enabled', True) else ""
display_str = f"{status} {time_str} {alarm.get('name', 'Unnamed')}{repeat_info}"
# Truncate if too long
display_str = display_str[:width-4]
stdscr.addstr(4 + i, 2, display_str)
# Instructions
stdscr.addstr(height - 2, 2, "D: Delete Enter: Edit Esc: Back")