diff --git a/main.py b/main.py
index f27e987..f7247f3 100644
--- a/main.py
+++ b/main.py
@@ -9,6 +9,7 @@ import csv
import io
import os
import json
+
try:
from zoneinfo import ZoneInfo
except Exception:
@@ -37,49 +38,76 @@ DEFAULT_SETTINGS = {
"weather_row": 3,
"vehicle_row": 4,
"column": 12,
- "hide_mission_name": False,
+ "hide_mission_name": False
}
# default timezone: 'local' uses system local tz, otherwise an IANA name or 'UTC'
-DEFAULT_SETTINGS.setdefault('timezone', 'local')
+DEFAULT_SETTINGS.setdefault("timezone", "local")
# Appearance defaults
-DEFAULT_SETTINGS.setdefault('bg_color', '#000000')
-DEFAULT_SETTINGS.setdefault('text_color', '#FFFFFF')
-DEFAULT_SETTINGS.setdefault('font_family', 'Consolas')
-DEFAULT_SETTINGS.setdefault('mission_font_px', 24)
-DEFAULT_SETTINGS.setdefault('timer_font_px', 80)
-DEFAULT_SETTINGS.setdefault('gn_bg_color', '#111111')
-DEFAULT_SETTINGS.setdefault('gn_border_color', '#FFFFFF')
-DEFAULT_SETTINGS.setdefault('gn_go_color', '#00FF00')
-DEFAULT_SETTINGS.setdefault('gn_nogo_color', '#FF0000')
-DEFAULT_SETTINGS.setdefault('gn_font_px', 20)
-DEFAULT_SETTINGS.setdefault('appearance_mode', 'dark')
+DEFAULT_SETTINGS.setdefault("bg_color", "#000000")
+DEFAULT_SETTINGS.setdefault("text_color", "#FFFFFF")
+DEFAULT_SETTINGS.setdefault("font_family", "Consolas")
+DEFAULT_SETTINGS.setdefault("mission_font_px", 24)
+DEFAULT_SETTINGS.setdefault("timer_font_px", 80)
+DEFAULT_SETTINGS.setdefault("gn_bg_color", "#111111")
+DEFAULT_SETTINGS.setdefault("gn_border_color", "#FFFFFF")
+DEFAULT_SETTINGS.setdefault("gn_go_color", "#00FF00")
+DEFAULT_SETTINGS.setdefault("gn_nogo_color", "#FF0000")
+DEFAULT_SETTINGS.setdefault("gn_font_px", 20)
+DEFAULT_SETTINGS.setdefault("appearance_mode", "dark")
# HTML-only appearance defaults (these should not affect the Python GUI)
-DEFAULT_SETTINGS.setdefault('html_bg_color', DEFAULT_SETTINGS.get('bg_color', '#000000'))
-DEFAULT_SETTINGS.setdefault('html_text_color', DEFAULT_SETTINGS.get('text_color', '#FFFFFF'))
-DEFAULT_SETTINGS.setdefault('html_font_family', DEFAULT_SETTINGS.get('font_family', 'Consolas'))
-DEFAULT_SETTINGS.setdefault('html_mission_font_px', DEFAULT_SETTINGS.get('mission_font_px', 24))
-DEFAULT_SETTINGS.setdefault('html_timer_font_px', DEFAULT_SETTINGS.get('timer_font_px', 80))
-DEFAULT_SETTINGS.setdefault('html_gn_bg_color', DEFAULT_SETTINGS.get('gn_bg_color', '#111111'))
-DEFAULT_SETTINGS.setdefault('html_gn_border_color', DEFAULT_SETTINGS.get('gn_border_color', '#FFFFFF'))
-DEFAULT_SETTINGS.setdefault('html_gn_go_color', DEFAULT_SETTINGS.get('gn_go_color', '#00FF00'))
-DEFAULT_SETTINGS.setdefault('html_gn_nogo_color', DEFAULT_SETTINGS.get('gn_nogo_color', '#FF0000'))
-DEFAULT_SETTINGS.setdefault('html_gn_font_px', DEFAULT_SETTINGS.get('gn_font_px', 20))
+DEFAULT_SETTINGS.setdefault(
+ "html_bg_color", DEFAULT_SETTINGS.get("bg_color", "#000000")
+)
+DEFAULT_SETTINGS.setdefault(
+ "html_text_color", DEFAULT_SETTINGS.get("text_color", "#FFFFFF")
+)
+DEFAULT_SETTINGS.setdefault(
+ "html_font_family", DEFAULT_SETTINGS.get("font_family", "Consolas")
+)
+DEFAULT_SETTINGS.setdefault(
+ "html_mission_font_px", DEFAULT_SETTINGS.get("mission_font_px", 24)
+)
+DEFAULT_SETTINGS.setdefault(
+ "html_timer_font_px", DEFAULT_SETTINGS.get("timer_font_px", 80)
+)
+DEFAULT_SETTINGS.setdefault(
+ "html_gn_bg_color", DEFAULT_SETTINGS.get("gn_bg_color", "#111111")
+)
+DEFAULT_SETTINGS.setdefault(
+ "html_gn_border_color", DEFAULT_SETTINGS.get("gn_border_color", "#FFFFFF")
+)
+DEFAULT_SETTINGS.setdefault(
+ "html_gn_go_color", DEFAULT_SETTINGS.get("gn_go_color", "#00FF00")
+)
+DEFAULT_SETTINGS.setdefault(
+ "html_gn_nogo_color", DEFAULT_SETTINGS.get("gn_nogo_color", "#FF0000")
+)
+DEFAULT_SETTINGS.setdefault("html_gn_font_px", DEFAULT_SETTINGS.get("gn_font_px", 20))
# Auto-hold times: list of seconds before T at which timer should automatically enter hold
-DEFAULT_SETTINGS.setdefault('auto_hold_times', [])
+DEFAULT_SETTINGS.setdefault("auto_hold_times", [])
# A small list of common timezone choices.
TIMEZONE_CHOICES = [
- 'local', 'UTC', 'US/Eastern', 'US/Central', 'US/Mountain', 'US/Pacific',
- 'Europe/London', 'Europe/Paris', 'Asia/Tokyo', 'Australia/Sydney'
+ "local",
+ "UTC",
+ "US/Eastern",
+ "US/Central",
+ "US/Mountain",
+ "US/Pacific",
+ "Europe/London",
+ "Europe/Paris",
+ "Asia/Tokyo",
+ "Australia/Sydney",
]
+
def load_settings():
try:
if os.path.exists(SETTINGS_FILE):
- with open(SETTINGS_FILE, 'r', encoding='utf-8') as fh:
+ with open(SETTINGS_FILE, "r", encoding="utf-8") as fh:
return json.load(fh)
except Exception:
pass
@@ -87,9 +115,10 @@ def load_settings():
save_settings(DEFAULT_SETTINGS)
return DEFAULT_SETTINGS.copy()
+
def save_settings(s):
try:
- with open(SETTINGS_FILE, 'w', encoding='utf-8') as fh:
+ with open(SETTINGS_FILE, "w", encoding="utf-8") as fh:
json.dump(s, fh, indent=2)
except Exception:
pass
@@ -101,23 +130,27 @@ def save_settings(s):
def fetch_gonogo():
"""Fetch Go/No-Go parameters either from configured spreadsheet or return manual button values."""
settings = load_settings()
- mode = settings.get('mode', 'spreadsheet')
+ mode = settings.get("mode", "spreadsheet")
# If manual mode, read values from a runtime stash (set by the GUI buttons)
- if mode == 'buttons':
+ if mode == "buttons":
# stored values will be on the app class; fallback to N/A
try:
- return [getattr(fetch_gonogo, 'manual_range', 'N/A'),
- getattr(fetch_gonogo, 'manual_weather', 'N/A'),
- getattr(fetch_gonogo, 'manual_vehicle', 'N/A')]
+ return [
+ getattr(fetch_gonogo, "manual_range", "N/A"),
+ getattr(fetch_gonogo, "manual_weather", "N/A"),
+ getattr(fetch_gonogo, "manual_vehicle", "N/A"),
+ ]
except Exception:
- return ['N/A', 'N/A', 'N/A']
+ return ["N/A", "N/A", "N/A"]
# spreadsheet mode
- link = settings.get('sheet_link', SHEET_LINK)
- col = max(1, int(settings.get('column', 12))) - 1
- rows = [int(settings.get('range_row', 2)) - 1,
- int(settings.get('weather_row', 3)) - 1,
- int(settings.get('vehicle_row', 4)) - 1]
+ link = settings.get("sheet_link", SHEET_LINK)
+ col = max(1, int(settings.get("column", 12))) - 1
+ rows = [
+ int(settings.get("range_row", 2)) - 1,
+ int(settings.get("weather_row", 3)) - 1,
+ int(settings.get("vehicle_row", 4)) - 1,
+ ]
try:
resp = session.get(link, timeout=3)
resp.raise_for_status()
@@ -125,7 +158,7 @@ def fetch_gonogo():
data = list(reader)
gonogo = []
for r in rows:
- val = 'N/A'
+ val = "N/A"
if 0 <= r < len(data) and len(data[r]) > col:
val = data[r][col]
gonogo.append(val.strip().upper())
@@ -141,30 +174,31 @@ def fetch_gonogo():
def get_status_color(status):
"""Return color name for a Go/No-Go status string."""
try:
- s = str(status or '').strip().upper()
+ s = str(status or "").strip().upper()
# normalize to letters only so variants like 'NO GO', 'NO-GO', 'NOGO' match
- norm = re.sub(r'[^A-Z]', '', s)
- if norm == 'GO':
- return 'green'
- if norm == 'NOGO':
- return 'red'
+ norm = re.sub(r"[^A-Z]", "", s)
+ if norm == "GO":
+ return "green"
+ if norm == "NOGO":
+ return "red"
# fallback: treat unknown/empty as white
- return 'white'
+ return "white"
except Exception:
return "white"
def format_status_display(status):
try:
- s = str(status or '').strip().upper()
- norm = re.sub(r'[^A-Z]', '', s)
- if norm == 'GO':
- return 'GO'
- if norm == 'NOGO':
- return 'NO-GO'
+ s = str(status or "").strip().upper()
+ norm = re.sub(r"[^A-Z]", "", s)
+ if norm == "GO":
+ return "GO"
+ if norm == "NOGO":
+ return "NO-GO"
return s
except Exception:
- return str(status or '')
+ return str(status or "")
+
# -------------------------
# Write Countdown HTML
@@ -172,15 +206,17 @@ def format_status_display(status):
def write_countdown_html(mission_name, timer_text):
s = load_settings()
# Prefer HTML-specific settings; fall back to GUI appearance settings for backwards compatibility
- bg = s.get('html_bg_color', s.get('bg_color', '#000000'))
- text = s.get('html_text_color', s.get('text_color', '#FFFFFF'))
- font = s.get('html_font_family', s.get('font_family', 'Consolas, monospace'))
- mission_px = int(s.get('html_mission_font_px', s.get('mission_font_px', 48)))
- timer_px = int(s.get('html_timer_font_px', s.get('timer_font_px', 120)))
-
+ bg = s.get("html_bg_color", s.get("bg_color", "#000000"))
+ text = s.get("html_text_color", s.get("countdown_text_color", "#FFFFFF"))
+ font = s.get("html_font_family", s.get("font_family", "Consolas, monospace"))
+ mission_px = int(s.get("html_mission_font_px", s.get("mission_font_px", 48)))
+ timer_px = int(s.get("html_timer_font_px", s.get("timer_font_px", 120)))
+
# Mission name hidden setting
hide_mission_name = s.get("hide_mission_name", False)
- mission_div_hidden = f'
{mission_name}
' if not hide_mission_name else ''
+ mission_div_hidden = (
+ f'{mission_name}
' if not hide_mission_name else ""
+ )
html = f"""
@@ -212,6 +248,7 @@ setTimeout(() => location.reload(), 1000);
with open(COUNTDOWN_HTML, "w", encoding="utf-8") as f:
f.write(html)
+
# -------------------------
# Write Go/No-Go HTML
# -------------------------
@@ -220,21 +257,21 @@ def write_gonogo_html(gonogo_values=None):
gonogo_values = ["N/A", "N/A", "N/A"]
s = load_settings()
# Prefer HTML-specific settings; fall back to GUI appearance settings for backwards compatibility
- bg = s.get('html_bg_color', s.get('bg_color', '#000000'))
- text = s.get('html_text_color', s.get('text_color', '#FFFFFF'))
- font = s.get('html_font_family', s.get('font_family', 'Consolas, monospace'))
- gn_bg = s.get('html_gn_bg_color', s.get('gn_bg_color', '#111111'))
- gn_border = s.get('html_gn_border_color', s.get('gn_border_color', '#FFFFFF'))
- gn_go = s.get('html_gn_go_color', s.get('gn_go_color', '#00FF00'))
- gn_nogo = s.get('html_gn_nogo_color', s.get('gn_nogo_color', '#FF0000'))
- gn_px = int(s.get('html_gn_font_px', s.get('gn_font_px', 28)))
+ bg = s.get("html_bg_color", s.get("bg_color", "#000000"))
+ text = s.get("html_text_color", s.get("text_color", "#FFFFFF"))
+ font = s.get("html_font_family", s.get("font_family", "Consolas, monospace"))
+ gn_bg = s.get("html_gn_bg_color", s.get("gn_bg_color", "#111111"))
+ gn_border = s.get("html_gn_border_color", s.get("gn_border_color", "#FFFFFF"))
+ gn_go = s.get("html_gn_go_color", s.get("gn_go_color", "#00FF00"))
+ gn_nogo = s.get("html_gn_nogo_color", s.get("gn_nogo_color", "#FF0000"))
+ gn_px = int(s.get("html_gn_font_px", s.get("gn_font_px", 28)))
# normalize and format display values so variants like 'NO GO' become 'NO-GO'
disp0 = format_status_display(gonogo_values[0])
disp1 = format_status_display(gonogo_values[1])
disp2 = format_status_display(gonogo_values[2])
- n0 = re.sub(r'[^A-Z]', '', (str(gonogo_values[0] or '')).strip().upper())
- n1 = re.sub(r'[^A-Z]', '', (str(gonogo_values[1] or '')).strip().upper())
- n2 = re.sub(r'[^A-Z]', '', (str(gonogo_values[2] or '')).strip().upper())
+ n0 = re.sub(r"[^A-Z]", "", (str(gonogo_values[0] or "")).strip().upper())
+ n1 = re.sub(r"[^A-Z]", "", (str(gonogo_values[1] or "")).strip().upper())
+ n2 = re.sub(r"[^A-Z]", "", (str(gonogo_values[2] or "")).strip().upper())
html = f"""
@@ -280,6 +317,7 @@ setTimeout(() => location.reload(), 5000);
with open(GONOGO_HTML, "w", encoding="utf-8") as f:
f.write(html)
+
# -------------------------
# Countdown App
# -------------------------
@@ -305,19 +343,42 @@ class CountdownApp:
self.last_gonogo_update = time.time()
# track which auto-holds we've already triggered for the current run
self._auto_hold_triggered = set()
+ # count mode
+ self.count_mode = tk.StringVar(value="T-")
# Title
- self.titletext = tk.Label(root, text=f"RocketLaunchCountdown {appVersion}", font=("Consolas", 24), fg="white", bg="black")
+ self.titletext = tk.Label(
+ root,
+ text=f"RocketLaunchCountdown {appVersion}",
+ font=("Consolas", 24),
+ fg="white",
+ bg="black",
+ )
self.titletext.pack(pady=(10, 0))
# Display
- self.text = tk.Label(root, text="T-00:00:00", font=("Consolas", 80, "bold"), fg="white", bg="black")
+ frame_count = tk.Frame(root, bg="black")
+ frame_count.pack(pady=(0, 5))
+
+ self.text = tk.Label(frame_count, text="T-00:00:00", font=("Consolas", 80, "bold"), fg="white", bg="black")
+ self.text.pack(side="left", padx=(0, 10))
+
+ # Static label above the button
+ self.mode_label = tk.Label(frame_count, text="T-/L- Toggle", font=("Consolas", 14), fg="white", bg="black")
+ self.mode_label.pack(side="top", pady=(0, 5))
+
+ # Toggle button below the label
+ self.toggle_mode_btn = tk.Button(frame_count, text="↺", command=self.toggle_count_mode, font=("Arial", 14), width=3)
+ self.toggle_mode_btn.pack(side="top")
+
self.text.pack(pady=(0, 5))
# Mission name input
frame_top = tk.Frame(root, bg="black")
frame_top.pack(pady=5)
- tk.Label(frame_top, text="Mission Name:", fg="white", bg="black").pack(side="left")
+ tk.Label(frame_top, text="Mission Name:", fg="white", bg="black").pack(
+ side="left"
+ )
self.mission_entry = tk.Entry(frame_top, width=20, font=("Arial", 18))
self.mission_entry.insert(0, self.mission_name)
self.mission_entry.pack(side="left")
@@ -336,7 +397,7 @@ class CountdownApp:
fg="white",
bg="black",
selectcolor="black", # makes the dot visible
- command=self.update_inputs
+ command=self.update_inputs,
)
self.radio_duration.pack(side="left", padx=5)
@@ -348,7 +409,7 @@ class CountdownApp:
fg="white",
bg="black",
selectcolor="black", # makes the dot visible
- command=self.update_inputs
+ command=self.update_inputs,
)
self.radio_clock.pack(side="left", padx=5)
@@ -372,37 +433,74 @@ class CountdownApp:
def open_autohold_dialog():
dlg = tk.Toplevel(self.root)
dlg.transient(self.root)
- dlg.title('Set Auto-hold')
- dlg.geometry('320x110')
+ dlg.title("Set Auto-hold")
+ dlg.geometry("320x110")
# theme according to appearance mode
ssettings = load_settings()
- mode_local = ssettings.get('appearance_mode', DEFAULT_SETTINGS.get('appearance_mode', 'dark'))
- if mode_local == 'dark':
- dlg_bg = '#000000'; dlg_fg = '#FFFFFF'; entry_bg = '#222222'; btn_bg = '#FFFFFF'; btn_fg = '#000000'
+ mode_local = ssettings.get(
+ "appearance_mode", DEFAULT_SETTINGS.get("appearance_mode", "dark")
+ )
+ if mode_local == "dark":
+ dlg_bg = "#000000"
+ dlg_fg = "#FFFFFF"
+ entry_bg = "#222222"
+ btn_bg = "#FFFFFF"
+ btn_fg = "#000000"
else:
- dlg_bg = '#FFFFFF'; dlg_fg = '#000000'; entry_bg = '#b4b4b4'; btn_bg = '#000000'; btn_fg = '#FFFFFF'
+ dlg_bg = "#FFFFFF"
+ dlg_fg = "#000000"
+ entry_bg = "#b4b4b4"
+ btn_bg = "#000000"
+ btn_fg = "#FFFFFF"
dlg.config(bg=dlg_bg)
- tk.Label(dlg, text='Auto-hold time (H M S):', fg=dlg_fg, bg=dlg_bg).pack(pady=(6,0))
+ tk.Label(dlg, text="Auto-hold time (H M S):", fg=dlg_fg, bg=dlg_bg).pack(
+ pady=(6, 0)
+ )
box = tk.Frame(dlg, bg=dlg_bg)
box.pack(pady=6)
- h_entry = tk.Entry(box, width=3, font=('Arial', 12), bg=entry_bg, fg=dlg_fg, insertbackground=dlg_fg)
- m_entry = tk.Entry(box, width=3, font=('Arial', 12), bg=entry_bg, fg=dlg_fg, insertbackground=dlg_fg)
- s_entry = tk.Entry(box, width=3, font=('Arial', 12), bg=entry_bg, fg=dlg_fg, insertbackground=dlg_fg)
- h_entry.pack(side='left', padx=4)
- tk.Label(box, text='H', fg=dlg_fg, bg=dlg_bg).pack(side='left')
- m_entry.pack(side='left', padx=4)
- tk.Label(box, text='M', fg=dlg_fg, bg=dlg_bg).pack(side='left')
- s_entry.pack(side='left', padx=4)
- tk.Label(box, text='S', fg=dlg_fg, bg=dlg_bg).pack(side='left')
+ h_entry = tk.Entry(
+ box,
+ width=3,
+ font=("Arial", 12),
+ bg=entry_bg,
+ fg=dlg_fg,
+ insertbackground=dlg_fg,
+ )
+ m_entry = tk.Entry(
+ box,
+ width=3,
+ font=("Arial", 12),
+ bg=entry_bg,
+ fg=dlg_fg,
+ insertbackground=dlg_fg,
+ )
+ s_entry = tk.Entry(
+ box,
+ width=3,
+ font=("Arial", 12),
+ bg=entry_bg,
+ fg=dlg_fg,
+ insertbackground=dlg_fg,
+ )
+ h_entry.pack(side="left", padx=4)
+ tk.Label(box, text="H", fg=dlg_fg, bg=dlg_bg).pack(side="left")
+ m_entry.pack(side="left", padx=4)
+ tk.Label(box, text="M", fg=dlg_fg, bg=dlg_bg).pack(side="left")
+ s_entry.pack(side="left", padx=4)
+ tk.Label(box, text="S", fg=dlg_fg, bg=dlg_bg).pack(side="left")
# populate with first configured value if present
try:
ssettings = load_settings()
- a = (ssettings.get('auto_hold_times') or [])
+ a = ssettings.get("auto_hold_times") or []
if a:
secs = int(a[0])
- hh = secs // 3600; mm = (secs % 3600) // 60; ss = secs % 60
- h_entry.insert(0, str(hh)); m_entry.insert(0, str(mm)); s_entry.insert(0, str(ss))
+ hh = secs // 3600
+ mm = (secs % 3600) // 60
+ ss = secs % 60
+ h_entry.insert(0, str(hh))
+ m_entry.insert(0, str(mm))
+ s_entry.insert(0, str(ss))
except Exception:
pass
@@ -411,13 +509,13 @@ class CountdownApp:
hh = int(h_entry.get() or 0)
mm = int(m_entry.get() or 0)
ss = int(s_entry.get() or 0)
- total = max(0, hh*3600 + mm*60 + ss)
+ total = max(0, hh * 3600 + mm * 60 + ss)
except Exception:
total = 0
try:
ssettings = load_settings()
# replace with single auto-hold time (list with one element)
- ssettings['auto_hold_times'] = [int(total)] if total > 0 else []
+ ssettings["auto_hold_times"] = [int(total)] if total > 0 else []
save_settings(ssettings)
# update runtime set so this run will consider the new value
self._auto_hold_triggered = set()
@@ -426,9 +524,25 @@ class CountdownApp:
dlg.destroy()
btnf = tk.Frame(dlg, bg=dlg_bg)
- btnf.pack(fill='x', pady=6)
- tk.Button(btnf, text='Save', command=do_save, width=10, bg=btn_bg, fg=btn_fg, activebackground='#444').pack(side='right', padx=6)
- tk.Button(btnf, text='Cancel', command=dlg.destroy, width=10, bg=btn_bg, fg=btn_fg, activebackground='#444').pack(side='right')
+ btnf.pack(fill="x", pady=6)
+ tk.Button(
+ btnf,
+ text="Save",
+ command=do_save,
+ width=10,
+ bg=btn_bg,
+ fg=btn_fg,
+ activebackground="#444",
+ ).pack(side="right", padx=6)
+ tk.Button(
+ btnf,
+ text="Cancel",
+ command=dlg.destroy,
+ width=10,
+ bg=btn_bg,
+ fg=btn_fg,
+ activebackground="#444",
+ ).pack(side="right")
# recursively theme dialog to ensure consistency
try:
@@ -436,21 +550,51 @@ class CountdownApp:
except Exception:
pass
- tk.Button(frame_duration, text='Auto-hold...', command=open_autohold_dialog, fg='white', bg='#333', width=12).pack(side='left', padx=8)
+ tk.Button(
+ frame_duration,
+ text="Auto-hold...",
+ command=open_autohold_dialog,
+ fg="white",
+ bg="#333",
+ width=12,
+ ).pack(side="left", padx=8)
# Clock time input (separate HH, MM, SS boxes)
frame_clock = tk.Frame(root, bg="black")
frame_clock.pack(pady=5)
- tk.Label(frame_clock, text="Clock (HH:MM:SS)", fg="white", bg="black").pack(side="left")
- self.clock_hours_entry = tk.Entry(frame_clock, width=3, font=("Arial", 18), fg='white', bg='#111', insertbackground='white')
+ tk.Label(frame_clock, text="Clock (HH:MM:SS)", fg="white", bg="black").pack(
+ side="left"
+ )
+ self.clock_hours_entry = tk.Entry(
+ frame_clock,
+ width=3,
+ font=("Arial", 18),
+ fg="white",
+ bg="#111",
+ insertbackground="white",
+ )
self.clock_hours_entry.insert(0, "14")
self.clock_hours_entry.pack(side="left", padx=2)
tk.Label(frame_clock, text=":", fg="white", bg="black").pack(side="left")
- self.clock_minutes_entry = tk.Entry(frame_clock, width=3, font=("Arial", 18), fg='white', bg='#111', insertbackground='white')
+ self.clock_minutes_entry = tk.Entry(
+ frame_clock,
+ width=3,
+ font=("Arial", 18),
+ fg="white",
+ bg="#111",
+ insertbackground="white",
+ )
self.clock_minutes_entry.insert(0, "00")
self.clock_minutes_entry.pack(side="left", padx=2)
tk.Label(frame_clock, text=":", fg="white", bg="black").pack(side="left")
- self.clock_seconds_entry = tk.Entry(frame_clock, width=3, font=("Arial", 18), fg='white', bg='#111', insertbackground='white')
+ self.clock_seconds_entry = tk.Entry(
+ frame_clock,
+ width=3,
+ font=("Arial", 18),
+ fg="white",
+ bg="#111",
+ insertbackground="white",
+ )
self.clock_seconds_entry.insert(0, "00")
self.clock_seconds_entry.pack(side="left", padx=2)
@@ -458,24 +602,44 @@ class CountdownApp:
frame_buttons = tk.Frame(root, bg="black")
frame_buttons.pack(pady=10)
- self.start_btn = tk.Button(frame_buttons, text="▶ Start", command=self.start, font=("Arial", 14))
+ self.start_btn = tk.Button(
+ frame_buttons, text="▶ Start", command=self.start, font=("Arial", 14)
+ )
self.start_btn.grid(row=0, column=0, padx=5)
# Hold and resume share the same position
- self.hold_btn = tk.Button(frame_buttons, text="⏸ Hold", command=self.hold, font=("Arial", 14))
+ self.hold_btn = tk.Button(
+ frame_buttons, text="⏸ Hold", command=self.hold, font=("Arial", 14)
+ )
self.hold_btn.grid(row=0, column=1, padx=5)
- self.resume_btn = tk.Button(frame_buttons, text="⏵ Resume", command=self.resume, font=("Arial", 14))
+ self.resume_btn = tk.Button(
+ frame_buttons, text="⏵ Resume", command=self.resume, font=("Arial", 14)
+ )
self.resume_btn.grid(row=0, column=1, padx=5)
self.resume_btn.grid_remove() # hidden at start
- self.scrub_btn = tk.Button(frame_buttons, text="🚫 Scrub", command=self.scrub, font=("Arial", 14), fg="red")
+ self.scrub_btn = tk.Button(
+ frame_buttons,
+ text="🚫 Scrub",
+ command=self.scrub,
+ font=("Arial", 14),
+ fg="red",
+ )
self.scrub_btn.grid(row=0, column=2, padx=5)
- self.reset_btn = tk.Button(frame_buttons, text="⟳ Reset", command=self.reset, font=("Arial", 14))
+ self.reset_btn = tk.Button(
+ frame_buttons, text="⟳ Reset", command=self.reset, font=("Arial", 14)
+ )
self.reset_btn.grid(row=0, column=3, padx=5)
# Settings button moved next to control buttons (match size/style)
- self.settings_btn = tk.Button(frame_buttons, text="Settings", command=self.show_settings_window, font=("Arial", 14), width=10)
+ self.settings_btn = tk.Button(
+ frame_buttons,
+ text="Settings",
+ command=self.show_settings_window,
+ font=("Arial", 14),
+ width=10,
+ )
self.settings_btn.grid(row=0, column=4, padx=6)
# Note: gonogo mode switching remains in Settings; manual buttons appear when mode == 'buttons'
@@ -485,12 +649,24 @@ class CountdownApp:
self.manual_frame.pack(pady=6)
# Buttons now toggle current state between GO and NOGO
- self.range_toggle_btn = tk.Button(self.manual_frame, text="Range: Toggle", width=12,
- command=lambda: self._toggle_manual('range'))
- self.weather_toggle_btn = tk.Button(self.manual_frame, text="Weather: Toggle", width=12,
- command=lambda: self._toggle_manual('weather'))
- self.vehicle_toggle_btn = tk.Button(self.manual_frame, text="Vehicle: Toggle", width=12,
- command=lambda: self._toggle_manual('vehicle'))
+ self.range_toggle_btn = tk.Button(
+ self.manual_frame,
+ text="Range: Toggle",
+ width=12,
+ command=lambda: self._toggle_manual("range"),
+ )
+ self.weather_toggle_btn = tk.Button(
+ self.manual_frame,
+ text="Weather: Toggle",
+ width=12,
+ command=lambda: self._toggle_manual("weather"),
+ )
+ self.vehicle_toggle_btn = tk.Button(
+ self.manual_frame,
+ text="Vehicle: Toggle",
+ width=12,
+ command=lambda: self._toggle_manual("vehicle"),
+ )
# Placeholders; visibility will be controlled by settings
self.range_toggle_btn.grid(row=0, column=0, padx=4, pady=2)
@@ -500,11 +676,17 @@ class CountdownApp:
frame_gn = tk.Frame(root, bg="black")
frame_gn.pack(pady=10)
# Labels displayed: Range, Weather, Vehicle — match write_gonogo_html ordering
- self.range_label = tk.Label(frame_gn, text="RANGE: N/A", font=("Consolas", 20), fg="white", bg="black")
+ self.range_label = tk.Label(
+ frame_gn, text="RANGE: N/A", font=("Consolas", 20), fg="white", bg="black"
+ )
self.range_label.pack()
- self.weather_label = tk.Label(frame_gn, text="WEATHER: N/A", font=("Consolas", 20), fg="white", bg="black")
+ self.weather_label = tk.Label(
+ frame_gn, text="WEATHER: N/A", font=("Consolas", 20), fg="white", bg="black"
+ )
self.weather_label.pack()
- self.vehicle_label = tk.Label(frame_gn, text="VEHICLE: N/A", font=("Consolas", 20), fg="white", bg="black")
+ self.vehicle_label = tk.Label(
+ frame_gn, text="VEHICLE: N/A", font=("Consolas", 20), fg="white", bg="black"
+ )
self.vehicle_label.pack()
# Footer
@@ -516,7 +698,7 @@ class CountdownApp:
text="Made by HamsterSpaceNerd3000", # or whatever you want
font=("Consolas", 12),
fg="black",
- bg="white"
+ bg="white",
)
self.footer_label.pack(fill="x")
self.update_inputs()
@@ -540,149 +722,228 @@ class CountdownApp:
win.geometry("560x275")
# apply current appearance mode so the settings window matches the main UI
s_local = load_settings()
- mode_local = s_local.get('appearance_mode', 'dark')
- if mode_local == 'dark':
- win_bg = '#000000'; win_text = '#FFFFFF'; btn_bg = '#FFFFFF'; btn_fg = '#000000'
+ mode_local = s_local.get("appearance_mode", "dark")
+ if mode_local == "dark":
+ win_bg = "#000000"
+ win_text = "#FFFFFF"
+ btn_bg = "#FFFFFF"
+ btn_fg = "#000000"
else:
- win_bg = '#FFFFFF'; win_text = '#000000'; btn_bg = '#000000'; btn_fg = '#FFFFFF'
+ win_bg = "#FFFFFF"
+ win_text = "#000000"
+ btn_bg = "#000000"
+ btn_fg = "#FFFFFF"
win.config(bg=win_bg)
# set per-window widget defaults so nested widgets inherit the chosen theme
try:
- win.option_add('*Foreground', win_text)
- win.option_add('*Background', win_bg)
+ win.option_add("*Foreground", win_text)
+ win.option_add("*Background", win_bg)
# entry specific defaults
- win.option_add('*Entry.Background', '#222' if mode_local == 'dark' else '#b4b4b4')
- win.option_add('*Entry.Foreground', win_text if mode_local == 'dark' else '#000000')
+ win.option_add(
+ "*Entry.Background", "#222" if mode_local == "dark" else "#b4b4b4"
+ )
+ win.option_add(
+ "*Entry.Foreground", win_text if mode_local == "dark" else "#000000"
+ )
except Exception:
pass
# keep track of this Toplevel so other dialogs can close it if needed
try:
self.settings_win = win
+
def _clear_settings_ref(evt=None):
try:
self.settings_win = None
except Exception:
pass
- win.bind('', _clear_settings_ref)
+
+ win.bind("", _clear_settings_ref)
except Exception:
pass
# Mode selection
frame_mode = tk.Frame(win)
frame_mode.config(bg=win_bg)
- frame_mode.pack(fill='x', pady=8, padx=8)
- tk.Label(frame_mode, text="Mode:", fg=win_text, bg=win_bg).pack(side='left')
- mode_var = tk.StringVar(value=settings.get('mode', 'spreadsheet'))
- tk.Radiobutton(frame_mode, text='Spreadsheet', variable=mode_var, value='spreadsheet', fg=win_text, bg=win_bg, selectcolor=win_bg).pack(side='left', padx=8)
- tk.Radiobutton(frame_mode, text='Buttons (manual)', variable=mode_var, value='buttons', fg=win_text, bg=win_bg, selectcolor=win_bg).pack(side='left', padx=8)
+ frame_mode.pack(fill="x", pady=8, padx=8)
+ tk.Label(frame_mode, text="Mode:", fg=win_text, bg=win_bg).pack(side="left")
+ mode_var = tk.StringVar(value=settings.get("mode", "spreadsheet"))
+ tk.Radiobutton(
+ frame_mode,
+ text="Spreadsheet",
+ variable=mode_var,
+ value="spreadsheet",
+ fg=win_text,
+ bg=win_bg,
+ selectcolor=win_bg,
+ ).pack(side="left", padx=8)
+ tk.Radiobutton(
+ frame_mode,
+ text="Buttons (manual)",
+ variable=mode_var,
+ value="buttons",
+ fg=win_text,
+ bg=win_bg,
+ selectcolor=win_bg,
+ ).pack(side="left", padx=8)
# Spreadsheet config
- frame_sheet = tk.LabelFrame(win, text='Spreadsheet configuration', fg=win_text, bg=win_bg)
+ frame_sheet = tk.LabelFrame(
+ win, text="Spreadsheet configuration", fg=win_text, bg=win_bg
+ )
frame_sheet.config(bg=win_bg)
- frame_sheet.pack(fill='x', padx=8, pady=6)
- tk.Label(frame_sheet, text='Sheet link (CSV export):', fg=win_text, bg=win_bg).pack(anchor='w')
+ frame_sheet.pack(fill="x", padx=8, pady=6)
+ tk.Label(
+ frame_sheet, text="Sheet link (CSV export):", fg=win_text, bg=win_bg
+ ).pack(anchor="w")
# entry background chosen to contrast with window background
- sheet_entry_bg = '#222' if mode_local == 'dark' else '#b4b4b4'
- sheet_entry_fg = win_text if mode_local == 'dark' else '#000000'
- sheet_entry = tk.Entry(frame_sheet, width=80, fg=sheet_entry_fg, bg=sheet_entry_bg, insertbackground=sheet_entry_fg)
- sheet_entry.pack(fill='x', padx=6, pady=4)
- sheet_entry.insert(0, settings.get('sheet_link', SHEET_LINK))
+ sheet_entry_bg = "#222" if mode_local == "dark" else "#b4b4b4"
+ sheet_entry_fg = win_text if mode_local == "dark" else "#000000"
+ sheet_entry = tk.Entry(
+ frame_sheet,
+ width=80,
+ fg=sheet_entry_fg,
+ bg=sheet_entry_bg,
+ insertbackground=sheet_entry_fg,
+ )
+ sheet_entry.pack(fill="x", padx=6, pady=4)
+ sheet_entry.insert(0, settings.get("sheet_link", SHEET_LINK))
# Accept cells in 'L3' format for each parameter
cell_frame = tk.Frame(frame_sheet)
cell_frame.config(bg=win_bg)
- cell_frame.pack(fill='x', padx=6, pady=2)
- tk.Label(cell_frame, text='Range cell (e.g. L3):', fg=win_text, bg=win_bg).grid(row=0, column=0)
- range_cell_bg = '#222' if mode_local == 'dark' else '#b4b4b4'
- range_cell_fg = win_text if mode_local == 'dark' else '#000000'
- range_cell = tk.Entry(cell_frame, width=8, fg=range_cell_fg, bg=range_cell_bg, insertbackground=range_cell_fg)
+ cell_frame.pack(fill="x", padx=6, pady=2)
+ tk.Label(cell_frame, text="Range cell (e.g. L3):", fg=win_text, bg=win_bg).grid(
+ row=0, column=0
+ )
+ range_cell_bg = "#222" if mode_local == "dark" else "#b4b4b4"
+ range_cell_fg = win_text if mode_local == "dark" else "#000000"
+ range_cell = tk.Entry(
+ cell_frame,
+ width=8,
+ fg=range_cell_fg,
+ bg=range_cell_bg,
+ insertbackground=range_cell_fg,
+ )
range_cell.grid(row=0, column=1, padx=4)
# show as L3 if present, otherwise build from numeric settings
try:
- if 'range_cell' in settings:
- range_cell.insert(0, settings.get('range_cell'))
+ if "range_cell" in settings:
+ range_cell.insert(0, settings.get("range_cell"))
else:
# convert numeric row/column to cell like L3
- col = settings.get('column', DEFAULT_SETTINGS['column'])
- row = settings.get('range_row', DEFAULT_SETTINGS['range_row'])
+ col = settings.get("column", DEFAULT_SETTINGS["column"])
+ row = settings.get("range_row", DEFAULT_SETTINGS["range_row"])
+
# column number to letters
def col_to_letters(n):
- s = ''
+ s = ""
while n > 0:
n, r = divmod(n - 1, 26)
- s = chr(ord('A') + r) + s
+ s = chr(ord("A") + r) + s
return s
+
range_cell.insert(0, f"{col_to_letters(col)}{row}")
except Exception:
range_cell.insert(0, f"L3")
- tk.Label(cell_frame, text='Weather cell (e.g. L4):', fg=win_text, bg=win_bg).grid(row=0, column=2)
- weather_cell = tk.Entry(cell_frame, width=8, fg=range_cell_fg, bg=range_cell_bg, insertbackground=range_cell_fg)
+ tk.Label(
+ cell_frame, text="Weather cell (e.g. L4):", fg=win_text, bg=win_bg
+ ).grid(row=0, column=2)
+ weather_cell = tk.Entry(
+ cell_frame,
+ width=8,
+ fg=range_cell_fg,
+ bg=range_cell_bg,
+ insertbackground=range_cell_fg,
+ )
weather_cell.grid(row=0, column=3, padx=4)
try:
- if 'weather_cell' in settings:
- weather_cell.insert(0, settings.get('weather_cell'))
+ if "weather_cell" in settings:
+ weather_cell.insert(0, settings.get("weather_cell"))
else:
- col = settings.get('column', DEFAULT_SETTINGS['column'])
- row = settings.get('weather_row', DEFAULT_SETTINGS['weather_row'])
+ col = settings.get("column", DEFAULT_SETTINGS["column"])
+ row = settings.get("weather_row", DEFAULT_SETTINGS["weather_row"])
+
def col_to_letters(n):
- s = ''
+ s = ""
while n > 0:
n, r = divmod(n - 1, 26)
- s = chr(ord('A') + r) + s
+ s = chr(ord("A") + r) + s
return s
+
weather_cell.insert(0, f"{col_to_letters(col)}{row}")
except Exception:
weather_cell.insert(0, f"L4")
- tk.Label(cell_frame, text='Vehicle cell (e.g. L5):', fg=win_text, bg=win_bg).grid(row=0, column=4)
- vehicle_cell = tk.Entry(cell_frame, width=8, fg=range_cell_fg, bg=range_cell_bg, insertbackground=range_cell_fg)
+ tk.Label(
+ cell_frame, text="Vehicle cell (e.g. L5):", fg=win_text, bg=win_bg
+ ).grid(row=0, column=4)
+ vehicle_cell = tk.Entry(
+ cell_frame,
+ width=8,
+ fg=range_cell_fg,
+ bg=range_cell_bg,
+ insertbackground=range_cell_fg,
+ )
vehicle_cell.grid(row=0, column=5, padx=4)
try:
- if 'vehicle_cell' in settings:
- vehicle_cell.insert(0, settings.get('vehicle_cell'))
+ if "vehicle_cell" in settings:
+ vehicle_cell.insert(0, settings.get("vehicle_cell"))
else:
- col = settings.get('column', DEFAULT_SETTINGS['column'])
- row = settings.get('vehicle_row', DEFAULT_SETTINGS['vehicle_row'])
+ col = settings.get("column", DEFAULT_SETTINGS["column"])
+ row = settings.get("vehicle_row", DEFAULT_SETTINGS["vehicle_row"])
+
def col_to_letters(n):
- s = ''
+ s = ""
while n > 0:
n, r = divmod(n - 1, 26)
- s = chr(ord('A') + r) + s
+ s = chr(ord("A") + r) + s
return s
+
vehicle_cell.insert(0, f"{col_to_letters(col)}{row}")
except Exception:
vehicle_cell.insert(0, f"L5")
# Manual buttons config
- frame_buttons_cfg = tk.LabelFrame(win, text='Manual Go/No-Go (Buttons mode)', fg=win_text, bg=win_bg)
+ frame_buttons_cfg = tk.LabelFrame(
+ win, text="Manual Go/No-Go (Buttons mode)", fg=win_text, bg=win_bg
+ )
frame_buttons_cfg.config(bg=win_bg)
- frame_buttons_cfg.pack(fill='x', padx=8, pady=6)
+ frame_buttons_cfg.pack(fill="x", padx=8, pady=6)
# (Auto-hold configuration removed from Settings — managed from main UI)
# Appearance settings are in a separate window
frame_appearance_btn = tk.Frame(win, bg=win_bg)
- frame_appearance_btn.pack(fill='x', padx=8, pady=6)
- tk.Button(frame_appearance_btn, text='Appearance...', command=lambda: self.show_appearance_window(), fg=btn_fg, bg=btn_bg, activebackground='#444').pack(side='left')
+ frame_appearance_btn.pack(fill="x", padx=8, pady=6)
+ tk.Button(
+ frame_appearance_btn,
+ text="Appearance...",
+ command=lambda: self.show_appearance_window(),
+ fg=btn_fg,
+ bg=btn_bg,
+ activebackground="#444",
+ ).pack(side="left")
# Timezone selector
tz_frame = tk.Frame(frame_sheet, bg=win_bg)
- tz_frame.pack(fill='x', padx=6, pady=4)
- tk.Label(tz_frame, text='Timezone:', fg=win_text, bg=win_bg).pack(side='left')
- tz_var = tk.StringVar(value=settings.get('timezone', DEFAULT_SETTINGS.get('timezone', 'local')))
+ tz_frame.pack(fill="x", padx=6, pady=4)
+ tk.Label(tz_frame, text="Timezone:", fg=win_text, bg=win_bg).pack(side="left")
+ tz_var = tk.StringVar(
+ value=settings.get("timezone", DEFAULT_SETTINGS.get("timezone", "local"))
+ )
# OptionMenu with a few choices, but user may edit the text to any IANA name
tz_menu = tk.OptionMenu(tz_frame, tz_var, *TIMEZONE_CHOICES)
- tz_menu.config(fg=win_text, bg=range_cell_bg, activebackground='#333')
- tz_menu.pack(side='left', padx=6)
+ tz_menu.config(fg=win_text, bg=range_cell_bg, activebackground="#333")
+ tz_menu.pack(side="left", padx=6)
def set_manual(val_type, val):
# store on fetch_gonogo func for now
- if val_type == 'range':
+ if val_type == "range":
fetch_gonogo.manual_range = val
- elif val_type == 'weather':
+ elif val_type == "weather":
fetch_gonogo.manual_weather = val
- elif val_type == 'vehicle':
+ elif val_type == "vehicle":
fetch_gonogo.manual_vehicle = val
# helper to set manual and update UI from main app
@@ -691,19 +952,28 @@ class CountdownApp:
# update labels and write html
self.gonogo_values = fetch_gonogo()
# update GUI labels immediately
- self.range_label.config(text=f"RANGE: {self.gonogo_values[0]}", fg=get_status_color(self.gonogo_values[0]))
- self.weather_label.config(text=f"WEATHER: {self.gonogo_values[1]}", fg=get_status_color(self.gonogo_values[1]))
- self.vehicle_label.config(text=f"VEHICLE: {self.gonogo_values[2]}", fg=get_status_color(self.gonogo_values[2]))
+ self.range_label.config(
+ text=f"RANGE: {self.gonogo_values[0]}",
+ fg=get_status_color(self.gonogo_values[0]),
+ )
+ self.weather_label.config(
+ text=f"WEATHER: {self.gonogo_values[1]}",
+ fg=get_status_color(self.gonogo_values[1]),
+ )
+ self.vehicle_label.config(
+ text=f"VEHICLE: {self.gonogo_values[2]}",
+ fg=get_status_color(self.gonogo_values[2]),
+ )
write_gonogo_html(self.gonogo_values)
# Save/Cancel
def cell_to_rc(cell_str):
- s = (cell_str or '').strip().upper()
+ s = (cell_str or "").strip().upper()
if not s:
return None, None
# split letters and digits
- letters = ''
- digits = ''
+ letters = ""
+ digits = ""
for ch in s:
if ch.isalpha():
letters += ch
@@ -714,7 +984,7 @@ class CountdownApp:
# convert letters to number
col = 0
for ch in letters:
- col = col * 26 + (ord(ch) - ord('A') + 1)
+ col = col * 26 + (ord(ch) - ord("A") + 1)
row = int(digits)
return row, col
@@ -725,48 +995,52 @@ class CountdownApp:
v_row, v_col = cell_to_rc(vehicle_cell.get())
# fallbacks
if r_row is None:
- r_row = DEFAULT_SETTINGS['range_row']
+ r_row = DEFAULT_SETTINGS["range_row"]
if w_row is None:
- w_row = DEFAULT_SETTINGS['weather_row']
+ w_row = DEFAULT_SETTINGS["weather_row"]
if v_row is None:
- v_row = DEFAULT_SETTINGS['vehicle_row']
+ v_row = DEFAULT_SETTINGS["vehicle_row"]
# determine column to use (prefer range column, else weather, else vehicle, else default)
- col_val = r_col or w_col or v_col or DEFAULT_SETTINGS['column']
+ col_val = r_col or w_col or v_col or DEFAULT_SETTINGS["column"]
new_settings = {
- 'mode': mode_var.get(),
- 'sheet_link': sheet_entry.get().strip() or SHEET_LINK,
- 'range_row': int(r_row),
- 'weather_row': int(w_row),
- 'vehicle_row': int(v_row),
- 'column': int(col_val),
+ "mode": mode_var.get(),
+ "sheet_link": sheet_entry.get().strip() or SHEET_LINK,
+ "range_row": int(r_row),
+ "weather_row": int(w_row),
+ "vehicle_row": int(v_row),
+ "column": int(col_val),
# persist the textual cells for convenience
- 'range_cell': range_cell.get().strip().upper(),
- 'weather_cell': weather_cell.get().strip().upper(),
- 'vehicle_cell': vehicle_cell.get().strip().upper(),
+ "range_cell": range_cell.get().strip().upper(),
+ "weather_cell": weather_cell.get().strip().upper(),
+ "vehicle_cell": vehicle_cell.get().strip().upper(),
# persist manual values if present
- 'manual_range': getattr(fetch_gonogo, 'manual_range', None),
- 'manual_weather': getattr(fetch_gonogo, 'manual_weather', None),
- 'manual_vehicle': getattr(fetch_gonogo, 'manual_vehicle', None),
- 'timezone': tz_var.get(),
+ "manual_range": getattr(fetch_gonogo, "manual_range", None),
+ "manual_weather": getattr(fetch_gonogo, "manual_weather", None),
+ "manual_vehicle": getattr(fetch_gonogo, "manual_vehicle", None),
+ "timezone": tz_var.get(),
# preserve appearance settings (edited in Appearance window)
- 'bg_color': settings.get('bg_color', '#000000'),
- 'text_color': settings.get('text_color', '#FFFFFF'),
- 'gn_bg_color': settings.get('gn_bg_color', '#111111'),
- 'gn_border_color': settings.get('gn_border_color', '#FFFFFF'),
- 'gn_go_color': settings.get('gn_go_color', '#00FF00'),
- 'gn_nogo_color': settings.get('gn_nogo_color', '#FF0000'),
- 'font_family': settings.get('font_family', 'Consolas'),
- 'mission_font_px': int(settings.get('mission_font_px', 48)),
- 'timer_font_px': int(settings.get('timer_font_px', 120)),
- 'gn_font_px': int(settings.get('gn_font_px', 28))
+ "bg_color": settings.get("bg_color", "#000000"),
+ "text_color": settings.get("text_color", "#FFFFFF"),
+ "gn_bg_color": settings.get("gn_bg_color", "#111111"),
+ "gn_border_color": settings.get("gn_border_color", "#FFFFFF"),
+ "gn_go_color": settings.get("gn_go_color", "#00FF00"),
+ "gn_nogo_color": settings.get("gn_nogo_color", "#FF0000"),
+ "font_family": settings.get("font_family", "Consolas"),
+ "mission_font_px": int(settings.get("mission_font_px", 48)),
+ "timer_font_px": int(settings.get("timer_font_px", 120)),
+ "gn_font_px": int(settings.get("gn_font_px", 28)),
}
# Auto-hold editing removed from Settings window; keep existing settings value
- new_settings['auto_hold_times'] = settings.get('auto_hold_times', [])
+ new_settings["auto_hold_times"] = settings.get("auto_hold_times", [])
# preserve the appearance_mode so saving Settings doesn't accidentally remove it
try:
- new_settings['appearance_mode'] = settings.get('appearance_mode', DEFAULT_SETTINGS.get('appearance_mode', 'dark'))
+ new_settings["appearance_mode"] = settings.get(
+ "appearance_mode", DEFAULT_SETTINGS.get("appearance_mode", "dark")
+ )
except Exception:
- new_settings['appearance_mode'] = DEFAULT_SETTINGS.get('appearance_mode', 'dark')
+ new_settings["appearance_mode"] = DEFAULT_SETTINGS.get(
+ "appearance_mode", "dark"
+ )
save_settings(new_settings)
# update immediately
self.gonogo_values = fetch_gonogo()
@@ -780,16 +1054,29 @@ class CountdownApp:
win.destroy()
btn_frame = tk.Frame(win, bg=win_bg)
- btn_frame.pack(fill='x', pady=8)
- tk.Button(btn_frame, text='Save', command=on_save, fg=btn_fg, bg=btn_bg, activebackground='#444').pack(side='right', padx=8)
- tk.Button(btn_frame, text='Cancel', command=on_cancel, fg=btn_fg, bg=btn_bg, activebackground='#444').pack(side='right')
+ btn_frame.pack(fill="x", pady=8)
+ tk.Button(
+ btn_frame,
+ text="Save",
+ command=on_save,
+ fg=btn_fg,
+ bg=btn_bg,
+ activebackground="#444",
+ ).pack(side="right", padx=8)
+ tk.Button(
+ btn_frame,
+ text="Cancel",
+ command=on_cancel,
+ fg=btn_fg,
+ bg=btn_bg,
+ activebackground="#444",
+ ).pack(side="right")
# ensure the new toplevel gets recursively themed like the main window
try:
self._theme_recursive(win, win_bg, win_text, btn_bg, btn_fg)
except Exception:
pass
-
# ----------------------------
# Update input visibility based on mode
# ----------------------------
@@ -814,36 +1101,51 @@ class CountdownApp:
# ----------------------------
def set_manual(self, which, val):
# normalize
- v = (val or '').strip().upper()
- if which == 'range':
+ v = (val or "").strip().upper()
+ if which == "range":
fetch_gonogo.manual_range = v
- elif which == 'weather':
+ elif which == "weather":
fetch_gonogo.manual_weather = v
- elif which == 'vehicle':
+ elif which == "vehicle":
fetch_gonogo.manual_vehicle = v
# update GUI and HTML
self.gonogo_values = fetch_gonogo()
try:
- self.range_label.config(text=f"RANGE: {self.gonogo_values[0]}", fg=get_status_color(self.gonogo_values[0]))
- self.weather_label.config(text=f"WEATHER: {self.gonogo_values[1]}", fg=get_status_color(self.gonogo_values[1]))
- self.vehicle_label.config(text=f"VEHICLE: {self.gonogo_values[2]}", fg=get_status_color(self.gonogo_values[2]))
+ self.range_label.config(
+ text=f"RANGE: {self.gonogo_values[0]}",
+ fg=get_status_color(self.gonogo_values[0]),
+ )
+ self.weather_label.config(
+ text=f"WEATHER: {self.gonogo_values[1]}",
+ fg=get_status_color(self.gonogo_values[1]),
+ )
+ self.vehicle_label.config(
+ text=f"VEHICLE: {self.gonogo_values[2]}",
+ fg=get_status_color(self.gonogo_values[2]),
+ )
except Exception:
pass
write_gonogo_html(self.gonogo_values)
# persist manual values immediately so they survive restarts
try:
s = load_settings()
- s['manual_range'] = getattr(fetch_gonogo, 'manual_range', s.get('manual_range'))
- s['manual_weather'] = getattr(fetch_gonogo, 'manual_weather', s.get('manual_weather'))
- s['manual_vehicle'] = getattr(fetch_gonogo, 'manual_vehicle', s.get('manual_vehicle'))
+ s["manual_range"] = getattr(
+ fetch_gonogo, "manual_range", s.get("manual_range")
+ )
+ s["manual_weather"] = getattr(
+ fetch_gonogo, "manual_weather", s.get("manual_weather")
+ )
+ s["manual_vehicle"] = getattr(
+ fetch_gonogo, "manual_vehicle", s.get("manual_vehicle")
+ )
save_settings(s)
except Exception:
pass
def update_manual_visibility(self):
s = load_settings()
- mode = s.get('mode', 'spreadsheet')
- visible = (mode == 'buttons')
+ mode = s.get("mode", "spreadsheet")
+ visible = mode == "buttons"
# show or hide manual frame
if visible:
self.manual_frame.pack(pady=6)
@@ -854,73 +1156,93 @@ class CountdownApp:
"""Apply appearance-related settings to the running Tk UI."""
s = load_settings()
# If an appearance_mode preset is selected, override specific settings with the preset
- mode = s.get('appearance_mode', None)
- if mode == 'dark':
- s.update({
- 'bg_color': '#000000', 'text_color': '#FFFFFF', 'gn_bg_color': '#111111',
- 'gn_border_color': '#FFFFFF', 'gn_go_color': '#00FF00', 'gn_nogo_color': '#FF0000',
- 'font_family': 'Consolas', 'mission_font_px': 44, 'timer_font_px': 80, 'gn_font_px': 24
- })
- elif mode == 'light':
- s.update({
- 'bg_color': '#FFFFFF', 'text_color': '#000000', 'gn_bg_color': '#EEEEEE',
- 'gn_border_color': '#333333', 'gn_go_color': '#008800', 'gn_nogo_color': '#AA0000',
- 'font_family': 'Consolas', 'mission_font_px': 44, 'timer_font_px': 80, 'gn_font_px': 24
- })
- bg = s.get('bg_color', '#000000')
- text = s.get('text_color', '#FFFFFF')
- font_family = s.get('font_family', 'Consolas')
- timer_px = int(s.get('timer_font_px', 100))
- mission_px = int(s.get('mission_font_px', 48))
- gn_px = int(s.get('gn_font_px', 24))
- gn_bg = s.get('gn_bg_color', '#111111')
- gn_border = s.get('gn_border_color', '#FFFFFF')
- gn_go = s.get('gn_go_color', '#00FF00')
- gn_nogo = s.get('gn_nogo_color', '#FF0000')
+ mode = s.get("appearance_mode", None)
+ if mode == "dark":
+ s.update(
+ {
+ "bg_color": "#000000",
+ "text_color": "#FFFFFF",
+ "countdown_text_color": "#FFFFFF",
+ "font_family": "Consolas",
+ "mission_font_px": 44,
+ "timer_font_px": 80,
+ "gn_font_px": 24,
+ }
+ )
+ elif mode == "light":
+ s.update(
+ {
+ "bg_color": "#FFFFFF",
+ "text_color": "#000000",
+ "countdown_text_color": "#FFFFFF",
+ "font_family": "Consolas",
+ "mission_font_px": 44,
+ "timer_font_px": 80,
+ "gn_font_px": 24,
+ }
+ )
+ bg = s.get("bg_color", "#000000")
+ text = s.get("text_color", "#FFFFFF")
+ font_family = s.get("font_family", "Consolas")
+ timer_px = int(s.get("timer_font_px", 100))
+ mission_px = int(s.get("mission_font_px", 48))
+ gn_px = int(s.get("gn_font_px", 24))
+ gn_bg = s.get("gn_bg_color", "#111111")
+ gn_border = s.get("gn_border_color", "#FFFFFF")
+ gn_go = s.get("gn_go_color", "#00FF00")
+ gn_nogo = s.get("gn_nogo_color", "#FF0000")
# apply to main window elements
try:
self.root.config(bg=bg)
self.titletext.config(fg=text, bg=bg, font=(font_family, 20))
# timer label
- self.text.config(fg=text, bg=bg, font=(font_family, timer_px, 'bold'))
+ self.text.config(fg=text, bg=bg, font=(font_family, timer_px, "bold"))
+
# GN labels: set bg and font, and color depending on GO/NOGO
def style_gn_label(lbl, value):
try:
lbl.config(bg=bg, font=(font_family, gn_px))
- v = (value or '').strip().upper()
- if v == 'GO':
+ v = (value or "").strip().upper()
+ if v == "GO":
lbl.config(fg=gn_go)
- elif v in ('NOGO', 'NO-GO'):
+ elif v in ("NOGO", "NO-GO"):
lbl.config(fg=gn_nogo)
else:
lbl.config(fg=text)
except Exception:
pass
- style_gn_label(self.range_label, getattr(self, 'range_status', None))
- style_gn_label(self.weather_label, getattr(self, 'weather', None))
- style_gn_label(self.vehicle_label, getattr(self, 'vehicle', None))
+ style_gn_label(self.range_label, getattr(self, "range_status", None))
+ style_gn_label(self.weather_label, getattr(self, "weather", None))
+ style_gn_label(self.vehicle_label, getattr(self, "vehicle", None))
# Buttons: invert colors depending on mode
# dark mode -> buttons white bg, black text
# light mode -> buttons black bg, white text
- if mode == 'dark':
- btn_bg = '#FFFFFF'
- btn_fg = '#000000'
- active_bg = '#DDDDDD'
+ if mode == "dark":
+ btn_bg = "#FFFFFF"
+ btn_fg = "#000000"
+ active_bg = "#DDDDDD"
else:
- btn_bg = '#000000'
- btn_fg = '#FFFFFF'
- active_bg = '#222222'
+ btn_bg = "#000000"
+ btn_fg = "#FFFFFF"
+ active_bg = "#222222"
- for btn in (self.start_btn, self.hold_btn, self.resume_btn, self.scrub_btn, self.reset_btn, self.settings_btn):
+ for btn in (
+ self.start_btn,
+ self.hold_btn,
+ self.resume_btn,
+ self.scrub_btn,
+ self.reset_btn,
+ self.settings_btn,
+ ):
try:
# preserve scrub button's custom color (red) if set
try:
- cur_fg = btn.cget('fg')
+ cur_fg = btn.cget("fg")
except Exception:
cur_fg = None
- if btn is getattr(self, 'scrub_btn', None) and cur_fg:
+ if btn is getattr(self, "scrub_btn", None) and cur_fg:
# keep existing foreground (usually red)
btn.config(bg=btn_bg, activebackground=active_bg)
else:
@@ -929,7 +1251,11 @@ class CountdownApp:
pass
# Manual toggle buttons
- for btn in (self.range_toggle_btn, self.weather_toggle_btn, self.vehicle_toggle_btn):
+ for btn in (
+ self.range_toggle_btn,
+ self.weather_toggle_btn,
+ self.vehicle_toggle_btn,
+ ):
try:
btn.config(bg=btn_bg, fg=btn_fg)
except Exception:
@@ -941,13 +1267,13 @@ class CountdownApp:
# Footer should invert colors depending on mode:
# - dark mode -> white background, black text
# - light mode -> black background, white text
- mode = s.get('appearance_mode', 'dark')
- if mode == 'dark':
- footer_bg = '#FFFFFF'
- footer_fg = '#000000'
+ mode = s.get("appearance_mode", "dark")
+ if mode == "dark":
+ footer_bg = "#FFFFFF"
+ footer_fg = "#000000"
else:
- footer_bg = '#000000'
- footer_fg = '#FFFFFF'
+ footer_bg = "#000000"
+ footer_fg = "#FFFFFF"
try:
self.footer_label.config(bg=footer_bg, fg=footer_fg)
except Exception:
@@ -966,21 +1292,23 @@ class CountdownApp:
def update_gn_labels(self, range_val, weather_val, vehicle_val):
"""Update GN label texts and apply theme-aware styling."""
s = load_settings()
- gn_px = int(s.get('gn_font_px', 28))
- font_family = s.get('font_family', 'Consolas')
- bg = s.get('bg_color', '#000000')
- text = s.get('text_color', '#FFFFFF')
- gn_go = s.get('gn_go_color', '#00FF00')
- gn_nogo = s.get('gn_nogo_color', '#FF0000')
+ gn_px = int(s.get("gn_font_px", 28))
+ font_family = s.get("font_family", "Consolas")
+ bg = s.get("bg_color", "#000000")
+ text = s.get("text_color", "#FFFFFF")
+ gn_go = s.get("gn_go_color", "#00FF00")
+ gn_nogo = s.get("gn_nogo_color", "#FF0000")
# Range
try:
display_range = format_status_display(range_val)
- self.range_label.config(text=f"RANGE: {display_range}", bg=bg, font=(font_family, gn_px))
- rv = (range_val or '').strip().upper()
- rnorm = re.sub(r'[^A-Z]', '', rv)
- if rnorm == 'GO':
+ self.range_label.config(
+ text=f"RANGE: {display_range}", bg=bg, font=(font_family, gn_px)
+ )
+ rv = (range_val or "").strip().upper()
+ rnorm = re.sub(r"[^A-Z]", "", rv)
+ if rnorm == "GO":
self.range_label.config(fg=gn_go)
- elif rnorm == 'NOGO':
+ elif rnorm == "NOGO":
self.range_label.config(fg=gn_nogo)
else:
self.range_label.config(fg=text)
@@ -990,12 +1318,14 @@ class CountdownApp:
# Weather
try:
display_weather = format_status_display(weather_val)
- self.weather_label.config(text=f"WEATHER: {display_weather}", bg=bg, font=(font_family, gn_px))
- wv = (weather_val or '').strip().upper()
- wnorm = re.sub(r'[^A-Z]', '', wv)
- if wnorm == 'GO':
+ self.weather_label.config(
+ text=f"WEATHER: {display_weather}", bg=bg, font=(font_family, gn_px)
+ )
+ wv = (weather_val or "").strip().upper()
+ wnorm = re.sub(r"[^A-Z]", "", wv)
+ if wnorm == "GO":
self.weather_label.config(fg=gn_go)
- elif wnorm == 'NOGO':
+ elif wnorm == "NOGO":
self.weather_label.config(fg=gn_nogo)
else:
self.weather_label.config(fg=text)
@@ -1005,12 +1335,14 @@ class CountdownApp:
# Vehicle
try:
display_vehicle = format_status_display(vehicle_val)
- self.vehicle_label.config(text=f"VEHICLE: {display_vehicle}", bg=bg, font=(font_family, gn_px))
- vv = (vehicle_val or '').strip().upper()
- vnorm = re.sub(r'[^A-Z]', '', vv)
- if vnorm == 'GO':
+ self.vehicle_label.config(
+ text=f"VEHICLE: {display_vehicle}", bg=bg, font=(font_family, gn_px)
+ )
+ vv = (vehicle_val or "").strip().upper()
+ vnorm = re.sub(r"[^A-Z]", "", vv)
+ if vnorm == "GO":
self.vehicle_label.config(fg=gn_go)
- elif vnorm == 'NOGO':
+ elif vnorm == "NOGO":
self.vehicle_label.config(fg=gn_nogo)
else:
self.vehicle_label.config(fg=text)
@@ -1032,10 +1364,14 @@ class CountdownApp:
if isinstance(child, tk.Label):
try:
# preserve GN label fg colors and don't override the footer label (it has a special inverted style)
- if child in (getattr(self, 'range_label', None), getattr(self, 'weather_label', None), getattr(self, 'vehicle_label', None)):
+ if child in (
+ getattr(self, "range_label", None),
+ getattr(self, "weather_label", None),
+ getattr(self, "vehicle_label", None),
+ ):
# GN labels keep fg but should have themed bg
- child.config(bg=s.get('gn_bg_color', bg))
- elif child is getattr(self, 'footer_label', None):
+ child.config(bg=s.get("gn_bg_color", bg))
+ elif child is getattr(self, "footer_label", None):
# footer_label was already styled by apply_appearance_settings; don't override it here
pass
else:
@@ -1046,42 +1382,46 @@ class CountdownApp:
if isinstance(child, tk.Entry):
try:
# Set entry bg/fg depending on appearance mode
- mode_local = s.get('appearance_mode', 'dark')
- if mode_local == 'dark':
- child.config(bg='#222222', fg=text, insertbackground=text)
+ mode_local = s.get("appearance_mode", "dark")
+ if mode_local == "dark":
+ child.config(bg="#222222", fg=text, insertbackground=text)
else:
# light mode entries should contrast with the white background
- child.config(bg='#b4b4b4', fg='#000000', insertbackground='#000000')
+ child.config(
+ bg="#b4b4b4", fg="#000000", insertbackground="#000000"
+ )
except Exception:
pass
# OptionMenu/Menubutton
if isinstance(child, tk.Menubutton):
try:
- child.config(bg=btn_bg, fg=btn_fg, activebackground='#555')
+ child.config(bg=btn_bg, fg=btn_fg, activebackground="#555")
except Exception:
pass
# Radiobutton / Checkbutton
if isinstance(child, (tk.Radiobutton, tk.Checkbutton)):
try:
# selectcolor is the indicator background; set it to match the overall bg for neatness
- child.config(bg=bg, fg=text, selectcolor=bg, activebackground=bg)
+ child.config(
+ bg=bg, fg=text, selectcolor=bg, activebackground=bg
+ )
except Exception:
pass
# Buttons: ensure themed background and correct fg
if isinstance(child, tk.Button):
try:
# don't override scrub button's fg if it has a special color
- if child is getattr(self, 'scrub_btn', None):
- child.config(bg=btn_bg, activebackground='#555')
+ if child is getattr(self, "scrub_btn", None):
+ child.config(bg=btn_bg, activebackground="#555")
else:
- child.config(bg=btn_bg, fg=btn_fg, activebackground='#555')
+ child.config(bg=btn_bg, fg=btn_fg, activebackground="#555")
except Exception:
pass
except Exception:
pass
# Recurse
try:
- if hasattr(child, 'winfo_children'):
+ if hasattr(child, "winfo_children"):
self._theme_recursive(child, bg, text, btn_bg, btn_fg)
except Exception:
pass
@@ -1091,46 +1431,72 @@ class CountdownApp:
settings = load_settings()
win = tk.Toplevel(self.root)
win.transient(self.root)
- win.title('Appearance')
- win.geometry('520x475')
+ win.title("Appearance")
+ win.geometry("520x475")
# derive colors from appearance_mode so the dialog matches the main UI
- mode_local = settings.get('appearance_mode', 'dark')
- if mode_local == 'dark':
- win_bg = '#000000'; win_text = '#FFFFFF'; btn_bg = '#FFFFFF'; btn_fg = '#000000'; entry_bg = '#222'; entry_fg = '#FFFFFF'
+ mode_local = settings.get("appearance_mode", "dark")
+ if mode_local == "dark":
+ win_bg = "#000000"
+ win_text = "#FFFFFF"
+ btn_bg = "#FFFFFF"
+ btn_fg = "#000000"
+ entry_bg = "#222"
+ entry_fg = "#FFFFFF"
else:
- win_bg = '#FFFFFF'; win_text = '#000000'; btn_bg = '#000000'; btn_fg = '#FFFFFF'; entry_bg = '#b4b4b4'; entry_fg = '#000000'
+ win_bg = "#FFFFFF"
+ win_text = "#000000"
+ btn_bg = "#000000"
+ btn_fg = "#FFFFFF"
+ entry_bg = "#b4b4b4"
+ entry_fg = "#000000"
win.config(bg=win_bg)
- tk.Label(win, text='Choose UI mode:', fg=win_text, bg=win_bg).pack(anchor='w', padx=12, pady=(10,0))
- mode_var = tk.StringVar(value=settings.get('appearance_mode', 'dark'))
- modes = ['dark', 'light']
+ tk.Label(win, text="Choose UI mode:", fg=win_text, bg=win_bg).pack(
+ anchor="w", padx=12, pady=(10, 0)
+ )
+ mode_var = tk.StringVar(value=settings.get("appearance_mode", "dark"))
+ modes = ["dark", "light"]
mode_menu = tk.OptionMenu(win, mode_var, *modes)
- mode_menu.config(fg=win_text, bg=entry_bg, activebackground='#333')
- mode_menu.pack(anchor='w', padx=12, pady=6)
+ mode_menu.config(fg=win_text, bg=entry_bg, activebackground="#333")
+ mode_menu.pack(anchor="w", padx=12, pady=6)
def on_save_mode():
choice = mode_var.get()
presets = {
- 'dark': {
- 'bg_color': '#000000', 'text_color': '#FFFFFF', 'gn_bg_color': '#111111',
- 'gn_border_color': '#FFFFFF', 'gn_go_color': '#00FF00', 'gn_nogo_color': '#FF0000',
- 'font_family': 'Consolas', 'mission_font_px': 24, 'timer_font_px': 80, 'gn_font_px': 20
+ "dark": {
+ "bg_color": "#000000",
+ "text_color": "#FFFFFF",
+ "gn_bg_color": "#111111",
+ "gn_border_color": "#FFFFFF",
+ "gn_go_color": "#00FF00",
+ "gn_nogo_color": "#FF0000",
+ "font_family": "Consolas",
+ "mission_font_px": 24,
+ "timer_font_px": 80,
+ "gn_font_px": 20,
+ },
+ "light": {
+ "bg_color": "#FFFFFF",
+ "text_color": "#000000",
+ "gn_bg_color": "#EEEEEE",
+ "gn_border_color": "#333333",
+ "gn_go_color": "#008800",
+ "gn_nogo_color": "#AA0000",
+ "font_family": "Consolas",
+ "mission_font_px": 24,
+ "timer_font_px": 80,
+ "gn_font_px": 20,
},
- 'light': {
- 'bg_color': '#FFFFFF', 'text_color': '#000000', 'gn_bg_color': '#EEEEEE',
- 'gn_border_color': '#333333', 'gn_go_color': '#008800', 'gn_nogo_color': '#AA0000',
- 'font_family': 'Consolas', 'mission_font_px': 24, 'timer_font_px': 80, 'gn_font_px': 20
- }
}
p = presets.get(choice, {})
s = load_settings()
- s['appearance_mode'] = choice
+ s["appearance_mode"] = choice
s.update(p)
save_settings(s)
try:
self.apply_appearance_settings()
- write_countdown_html(self.mission_name, self.text.cget('text'))
+ write_countdown_html(self.mission_name, self.text.cget("text"))
write_gonogo_html(self.gonogo_values)
except Exception:
pass
@@ -1138,7 +1504,7 @@ class CountdownApp:
win.destroy()
# also close the settings window if it is open
try:
- if getattr(self, 'settings_win', None):
+ if getattr(self, "settings_win", None):
try:
self.settings_win.destroy()
except Exception:
@@ -1156,89 +1522,203 @@ class CountdownApp:
pass
s = load_settings()
- html_frame = tk.LabelFrame(win, text='HTML appearance (streaming)', fg=win_text, bg=win_bg)
+ html_frame = tk.LabelFrame(
+ win, text="HTML appearance (streaming)", fg=win_text, bg=win_bg
+ )
html_frame.config(bg=win_bg)
- html_frame.pack(fill='x', padx=8, pady=6)
+ html_frame.pack(fill="x", padx=8, pady=6)
# layout HTML appearance fields in a grid
- tk.Label(html_frame, text='Background:', fg=win_text, bg=win_bg).grid(row=0, column=0, sticky='w', padx=6, pady=4)
- bg_entry = tk.Entry(html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
+ tk.Label(html_frame, text="Background:", fg=win_text, bg=win_bg).grid(
+ row=0, column=0, sticky="w", padx=6, pady=4
+ )
+ bg_entry = tk.Entry(
+ html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg
+ )
bg_entry.grid(row=0, column=1, padx=6, pady=4)
- bg_entry.insert(0, s.get('html_bg_color', s.get('bg_color', '#000000')))
- tk.Button(html_frame, text='Choose', command=lambda: choose_color(bg_entry), fg=btn_fg, bg=btn_bg).grid(row=0, column=2, padx=6)
+ bg_entry.insert(0, s.get("html_bg_color", s.get("bg_color", "#000000")))
+ tk.Button(
+ html_frame,
+ text="Choose",
+ command=lambda: choose_color(bg_entry),
+ fg=btn_fg,
+ bg=btn_bg,
+ ).grid(row=0, column=2, padx=6)
- tk.Label(html_frame, text='Text:', fg=win_text, bg=win_bg).grid(row=1, column=0, sticky='w', padx=6, pady=4)
- text_entry = tk.Entry(html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
+ tk.Label(html_frame, text="Text:", fg=win_text, bg=win_bg).grid(
+ row=1, column=0, sticky="w", padx=6, pady=4
+ )
+ text_entry = tk.Entry(
+ html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg
+ )
text_entry.grid(row=1, column=1, padx=6, pady=4)
- text_entry.insert(0, s.get('html_text_color', s.get('text_color', '#FFFFFF')))
- tk.Button(html_frame, text='Choose', command=lambda: choose_color(text_entry), fg=btn_fg, bg=btn_bg).grid(row=1, column=2, padx=6)
+ text_entry.insert(0, s.get("html_text_color", s.get("text_color", "#FFFFFF")))
+ tk.Button(
+ html_frame,
+ text="Choose",
+ command=lambda: choose_color(text_entry),
+ fg=btn_fg,
+ bg=btn_bg,
+ ).grid(row=1, column=2, padx=6)
- tk.Label(html_frame, text='GN GO:', fg=win_text, bg=win_bg).grid(row=2, column=0, sticky='w', padx=6, pady=4)
- gn_go_entry = tk.Entry(html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
+ tk.Label(html_frame, text="GN GO:", fg=win_text, bg=win_bg).grid(
+ row=2, column=0, sticky="w", padx=6, pady=4
+ )
+ gn_go_entry = tk.Entry(
+ html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg
+ )
gn_go_entry.grid(row=2, column=1, padx=6, pady=4)
- gn_go_entry.insert(0, s.get('html_gn_go_color', s.get('gn_go_color', '#00FF00')))
- tk.Button(html_frame, text='Choose', command=lambda: choose_color(gn_go_entry), fg=btn_fg, bg=btn_bg).grid(row=2, column=2, padx=6)
+ gn_go_entry.insert(
+ 0, s.get("html_gn_go_color", s.get("gn_go_color", "#00FF00"))
+ )
+ tk.Button(
+ html_frame,
+ text="Choose",
+ command=lambda: choose_color(gn_go_entry),
+ fg=btn_fg,
+ bg=btn_bg,
+ ).grid(row=2, column=2, padx=6)
- tk.Label(html_frame, text='GN NO-GO:', fg=win_text, bg=win_bg).grid(row=3, column=0, sticky='w', padx=6, pady=4)
- gn_nogo_entry = tk.Entry(html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
+ tk.Label(html_frame, text="GN NO-GO:", fg=win_text, bg=win_bg).grid(
+ row=3, column=0, sticky="w", padx=6, pady=4
+ )
+ gn_nogo_entry = tk.Entry(
+ html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg
+ )
gn_nogo_entry.grid(row=3, column=1, padx=6, pady=4)
- gn_nogo_entry.insert(0, s.get('html_gn_nogo_color', s.get('gn_nogo_color', '#FF0000')))
- tk.Button(html_frame, text='Choose', command=lambda: choose_color(gn_nogo_entry), fg=btn_fg, bg=btn_bg).grid(row=3, column=2, padx=6)
+ gn_nogo_entry.insert(
+ 0, s.get("html_gn_nogo_color", s.get("gn_nogo_color", "#FF0000"))
+ )
+ tk.Button(
+ html_frame,
+ text="Choose",
+ command=lambda: choose_color(gn_nogo_entry),
+ fg=btn_fg,
+ bg=btn_bg,
+ ).grid(row=3, column=2, padx=6)
- tk.Label(html_frame, text='GN box bg:', fg=win_text, bg=win_bg).grid(row=4, column=0, sticky='w', padx=6, pady=4)
- gn_box_bg_entry = tk.Entry(html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
+ tk.Label(html_frame, text="GN box bg:", fg=win_text, bg=win_bg).grid(
+ row=4, column=0, sticky="w", padx=6, pady=4
+ )
+ gn_box_bg_entry = tk.Entry(
+ html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg
+ )
gn_box_bg_entry.grid(row=4, column=1, padx=6, pady=4)
- gn_box_bg_entry.insert(0, s.get('html_gn_bg_color', s.get('gn_bg_color', '#111111')))
- tk.Button(html_frame, text='Choose', command=lambda: choose_color(gn_box_bg_entry), fg=btn_fg, bg=btn_bg).grid(row=4, column=2, padx=6)
+ gn_box_bg_entry.insert(
+ 0, s.get("html_gn_bg_color", s.get("gn_bg_color", "#111111"))
+ )
+ tk.Button(
+ html_frame,
+ text="Choose",
+ command=lambda: choose_color(gn_box_bg_entry),
+ fg=btn_fg,
+ bg=btn_bg,
+ ).grid(row=4, column=2, padx=6)
- tk.Label(html_frame, text='GN border:', fg=win_text, bg=win_bg).grid(row=5, column=0, sticky='w', padx=6, pady=4)
- gn_border_entry = tk.Entry(html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
+ tk.Label(html_frame, text="GN border:", fg=win_text, bg=win_bg).grid(
+ row=5, column=0, sticky="w", padx=6, pady=4
+ )
+ gn_border_entry = tk.Entry(
+ html_frame, width=12, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg
+ )
gn_border_entry.grid(row=5, column=1, padx=6, pady=4)
- gn_border_entry.insert(0, s.get('html_gn_border_color', s.get('gn_border_color', '#FFFFFF')))
- tk.Button(html_frame, text='Choose', command=lambda: choose_color(gn_border_entry), fg=btn_fg, bg=btn_bg).grid(row=5, column=2, padx=6)
+ gn_border_entry.insert(
+ 0, s.get("html_gn_border_color", s.get("gn_border_color", "#FFFFFF"))
+ )
+ tk.Button(
+ html_frame,
+ text="Choose",
+ command=lambda: choose_color(gn_border_entry),
+ fg=btn_fg,
+ bg=btn_bg,
+ ).grid(row=5, column=2, padx=6)
- tk.Label(html_frame, text='Font family:', fg=win_text, bg=win_bg).grid(row=6, column=0, sticky='w', padx=6, pady=4)
- font_entry = tk.Entry(html_frame, width=20, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
- font_entry.grid(row=6, column=1, padx=6, pady=4, columnspan=2, sticky='w')
- font_entry.insert(0, s.get('html_font_family', s.get('font_family', 'Consolas')))
+ tk.Label(html_frame, text="Font family:", fg=win_text, bg=win_bg).grid(
+ row=6, column=0, sticky="w", padx=6, pady=4
+ )
+ font_entry = tk.Entry(
+ html_frame, width=20, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg
+ )
+ font_entry.grid(row=6, column=1, padx=6, pady=4, columnspan=2, sticky="w")
+ font_entry.insert(
+ 0, s.get("html_font_family", s.get("font_family", "Consolas"))
+ )
- tk.Label(html_frame, text='Mission px:', fg=win_text, bg=win_bg).grid(row=7, column=0, sticky='w', padx=6, pady=4)
- mission_px_entry = tk.Entry(html_frame, width=6, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
- mission_px_entry.grid(row=7, column=1, padx=6, pady=4, sticky='w')
- mission_px_entry.insert(0, str(s.get('html_mission_font_px', s.get('mission_font_px', 24))))
+ tk.Label(html_frame, text="Mission px:", fg=win_text, bg=win_bg).grid(
+ row=7, column=0, sticky="w", padx=6, pady=4
+ )
+ mission_px_entry = tk.Entry(
+ html_frame, width=6, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg
+ )
+ mission_px_entry.grid(row=7, column=1, padx=6, pady=4, sticky="w")
+ mission_px_entry.insert(
+ 0, str(s.get("html_mission_font_px", s.get("mission_font_px", 24)))
+ )
- tk.Label(html_frame, text='Timer px:', fg=win_text, bg=win_bg).grid(row=8, column=0, sticky='w', padx=6, pady=4)
- timer_px_entry = tk.Entry(html_frame, width=6, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg)
- timer_px_entry.grid(row=8, column=1, padx=6, pady=4, sticky='w')
- timer_px_entry.insert(0, str(s.get('html_timer_font_px', s.get('timer_font_px', 80))))
+ tk.Label(html_frame, text="Timer px:", fg=win_text, bg=win_bg).grid(
+ row=8, column=0, sticky="w", padx=6, pady=4
+ )
+ timer_px_entry = tk.Entry(
+ html_frame, width=6, fg=entry_fg, bg=entry_bg, insertbackground=entry_fg
+ )
+ timer_px_entry.grid(row=8, column=1, padx=6, pady=4, sticky="w")
+ timer_px_entry.insert(
+ 0, str(s.get("html_timer_font_px", s.get("timer_font_px", 80)))
+ )
# Add a checkbox to hide mission name in HTML output
- self.hide_mission_name_var = tk.BooleanVar(value=s.get("hide_mission_name", False))
- hide_mission_name_cb = tk.Checkbutton(html_frame, text="Hide mission name in HTML output", variable=self.hide_mission_name_var, fg=win_text, bg=win_bg, selectcolor=win_bg, activebackground=win_bg)
+ self.hide_mission_name_var = tk.BooleanVar(
+ value=s.get("hide_mission_name", False)
+ )
+ hide_mission_name_cb = tk.Checkbutton(
+ html_frame,
+ text="Hide mission name in HTML output",
+ variable=self.hide_mission_name_var,
+ fg=win_text,
+ bg=win_bg,
+ selectcolor=win_bg,
+ activebackground=win_bg,
+ )
+
+ hide_mission_name_cb.grid(
+ row=9, column=0, columnspan=3, sticky="w", padx=6, pady=4
+ )
- hide_mission_name_cb.grid(row=9, column=0, columnspan=3, sticky='w', padx=6, pady=4)
-
def save_html_prefs():
try:
s_local = load_settings()
- s_local['html_bg_color'] = bg_entry.get().strip() or s_local.get('html_bg_color')
- s_local['html_text_color'] = text_entry.get().strip() or s_local.get('html_text_color')
- s_local['html_gn_go_color'] = gn_go_entry.get().strip() or s_local.get('html_gn_go_color')
- s_local['html_gn_nogo_color'] = gn_nogo_entry.get().strip() or s_local.get('html_gn_nogo_color')
- s_local['html_gn_bg_color'] = gn_box_bg_entry.get().strip() or s_local.get('html_gn_bg_color')
- s_local['html_gn_border_color'] = gn_border_entry.get().strip() or s_local.get('html_gn_border_color')
- s_local['html_font_family'] = font_entry.get().strip() or s_local.get('html_font_family')
+ s_local["html_bg_color"] = bg_entry.get().strip() or s_local.get(
+ "html_bg_color"
+ )
+ s_local["html_text_color"] = text_entry.get().strip() or s_local.get(
+ "html_text_color"
+ )
+ s_local["html_gn_go_color"] = gn_go_entry.get().strip() or s_local.get(
+ "html_gn_go_color"
+ )
+ s_local["html_gn_nogo_color"] = (
+ gn_nogo_entry.get().strip() or s_local.get("html_gn_nogo_color")
+ )
+ s_local["html_gn_bg_color"] = (
+ gn_box_bg_entry.get().strip() or s_local.get("html_gn_bg_color")
+ )
+ s_local["html_gn_border_color"] = (
+ gn_border_entry.get().strip() or s_local.get("html_gn_border_color")
+ )
+ s_local["html_font_family"] = font_entry.get().strip() or s_local.get(
+ "html_font_family"
+ )
s_local["hide_mission_name"] = self.hide_mission_name_var.get()
try:
- s_local['html_mission_font_px'] = int(mission_px_entry.get())
+ s_local["html_mission_font_px"] = int(mission_px_entry.get())
except Exception:
pass
try:
- s_local['html_timer_font_px'] = int(timer_px_entry.get())
+ s_local["html_timer_font_px"] = int(timer_px_entry.get())
except Exception:
pass
save_settings(s_local)
- write_countdown_html(self.mission_name, self.text.cget('text'))
+ write_countdown_html(self.mission_name, self.text.cget("text"))
write_gonogo_html(self.gonogo_values)
except Exception:
pass
@@ -1246,41 +1726,74 @@ class CountdownApp:
def reset_html_defaults():
try:
s_local = load_settings()
- s_local['html_bg_color'] = DEFAULT_SETTINGS.get('html_bg_color')
- s_local['html_text_color'] = DEFAULT_SETTINGS.get('html_text_color')
- s_local['html_font_family'] = DEFAULT_SETTINGS.get('html_font_family')
- s_local['html_mission_font_px'] = DEFAULT_SETTINGS.get('html_mission_font_px')
- s_local['html_timer_font_px'] = DEFAULT_SETTINGS.get('html_timer_font_px')
- s_local['html_gn_bg_color'] = DEFAULT_SETTINGS.get('html_gn_bg_color')
- s_local['html_gn_border_color'] = DEFAULT_SETTINGS.get('html_gn_border_color')
- s_local['html_gn_go_color'] = DEFAULT_SETTINGS.get('html_gn_go_color')
- s_local['html_gn_nogo_color'] = DEFAULT_SETTINGS.get('html_gn_nogo_color')
- s_local['html_gn_font_px'] = DEFAULT_SETTINGS.get('html_gn_font_px')
+ s_local["html_bg_color"] = DEFAULT_SETTINGS.get("html_bg_color")
+ s_local["html_text_color"] = DEFAULT_SETTINGS.get("html_text_color")
+ s_local["html_font_family"] = DEFAULT_SETTINGS.get("html_font_family")
+ s_local["html_mission_font_px"] = DEFAULT_SETTINGS.get(
+ "html_mission_font_px"
+ )
+ s_local["html_timer_font_px"] = DEFAULT_SETTINGS.get(
+ "html_timer_font_px"
+ )
+ s_local["html_gn_bg_color"] = DEFAULT_SETTINGS.get("html_gn_bg_color")
+ s_local["html_gn_border_color"] = DEFAULT_SETTINGS.get(
+ "html_gn_border_color"
+ )
+ s_local["html_gn_go_color"] = DEFAULT_SETTINGS.get("html_gn_go_color")
+ s_local["html_gn_nogo_color"] = DEFAULT_SETTINGS.get(
+ "html_gn_nogo_color"
+ )
+ s_local["html_gn_font_px"] = DEFAULT_SETTINGS.get("html_gn_font_px")
save_settings(s_local)
# update UI fields
- bg_entry.delete(0, tk.END); bg_entry.insert(0, s_local['html_bg_color'])
- text_entry.delete(0, tk.END); text_entry.insert(0, s_local['html_text_color'])
- gn_go_entry.delete(0, tk.END); gn_go_entry.insert(0, s_local['html_gn_go_color'])
- gn_nogo_entry.delete(0, tk.END); gn_nogo_entry.insert(0, s_local['html_gn_nogo_color'])
- gn_box_bg_entry.delete(0, tk.END); gn_box_bg_entry.insert(0, s_local['html_gn_bg_color'])
- gn_border_entry.delete(0, tk.END); gn_border_entry.insert(0, s_local['html_gn_border_color'])
- font_entry.delete(0, tk.END); font_entry.insert(0, s_local['html_font_family'])
- mission_px_entry.delete(0, tk.END); mission_px_entry.insert(0, str(s_local['html_mission_font_px']))
- timer_px_entry.delete(0, tk.END); timer_px_entry.insert(0, str(s_local['html_timer_font_px']))
- write_countdown_html(self.mission_name, self.text.cget('text'))
+ bg_entry.delete(0, tk.END)
+ bg_entry.insert(0, s_local["html_bg_color"])
+ text_entry.delete(0, tk.END)
+ text_entry.insert(0, s_local["html_text_color"])
+ gn_go_entry.delete(0, tk.END)
+ gn_go_entry.insert(0, s_local["html_gn_go_color"])
+ gn_nogo_entry.delete(0, tk.END)
+ gn_nogo_entry.insert(0, s_local["html_gn_nogo_color"])
+ gn_box_bg_entry.delete(0, tk.END)
+ gn_box_bg_entry.insert(0, s_local["html_gn_bg_color"])
+ gn_border_entry.delete(0, tk.END)
+ gn_border_entry.insert(0, s_local["html_gn_border_color"])
+ font_entry.delete(0, tk.END)
+ font_entry.insert(0, s_local["html_font_family"])
+ mission_px_entry.delete(0, tk.END)
+ mission_px_entry.insert(0, str(s_local["html_mission_font_px"]))
+ timer_px_entry.delete(0, tk.END)
+ timer_px_entry.insert(0, str(s_local["html_timer_font_px"]))
+ write_countdown_html(self.mission_name, self.text.cget("text"))
write_gonogo_html(self.gonogo_values)
except Exception:
pass
html_btns = tk.Frame(html_frame, bg=win_bg)
html_btns.grid(row=10, column=0, columnspan=3, pady=6)
- tk.Button(html_btns, text='Save (HTML only)', command=save_html_prefs, fg=btn_fg, bg=btn_bg).pack(side='right', padx=6)
- tk.Button(html_btns, text='Reset HTML defaults', command=reset_html_defaults, fg=btn_fg, bg=btn_bg).pack(side='right')
+ tk.Button(
+ html_btns,
+ text="Save (HTML only)",
+ command=save_html_prefs,
+ fg=btn_fg,
+ bg=btn_bg,
+ ).pack(side="right", padx=6)
+ tk.Button(
+ html_btns,
+ text="Reset HTML defaults",
+ command=reset_html_defaults,
+ fg=btn_fg,
+ bg=btn_bg,
+ ).pack(side="right")
btn_frame = tk.Frame(win, bg=win_bg)
- btn_frame.pack(fill='x', pady=8, padx=8)
- tk.Button(btn_frame, text='Save', command=on_save_mode, fg=btn_fg, bg=btn_bg).pack(side='right', padx=6)
- tk.Button(btn_frame, text='Cancel', command=win.destroy, fg=btn_fg, bg=btn_bg).pack(side='right')
+ btn_frame.pack(fill="x", pady=8, padx=8)
+ tk.Button(
+ btn_frame, text="Save", command=on_save_mode, fg=btn_fg, bg=btn_bg
+ ).pack(side="right", padx=6)
+ tk.Button(
+ btn_frame, text="Cancel", command=win.destroy, fg=btn_fg, bg=btn_bg
+ ).pack(side="right")
try:
self._theme_recursive(win, win_bg, win_text, btn_bg, btn_fg)
@@ -1291,14 +1804,14 @@ class CountdownApp:
# get current values (Range, Weather, Vehicle)
cur = fetch_gonogo()
# map which to index
- idx_map = {'range': 0, 'weather': 1, 'vehicle': 2}
+ idx_map = {"range": 0, "weather": 1, "vehicle": 2}
idx = idx_map.get(which, 0)
try:
- cur_val = (cur[idx] or '').strip().upper()
+ cur_val = (cur[idx] or "").strip().upper()
except Exception:
- cur_val = 'N/A'
+ cur_val = "N/A"
# toggle: if GO -> NOGO, else -> GO
- new_val = 'NO-GO' if cur_val == 'GO' else 'GO'
+ new_val = "NO-GO" if cur_val == "GO" else "GO"
self.set_manual(which, new_val)
# ----------------------------
@@ -1326,10 +1839,14 @@ class CountdownApp:
s = int(self.clock_seconds_entry.get() or 0)
# determine timezone from settings
ssettings = load_settings()
- tzname = ssettings.get('timezone', DEFAULT_SETTINGS.get('timezone', 'local'))
- if ZoneInfo is None or tzname in (None, '', 'local'):
+ tzname = ssettings.get(
+ "timezone", DEFAULT_SETTINGS.get("timezone", "local")
+ )
+ if ZoneInfo is None or tzname in (None, "", "local"):
# naive local time handling (existing behavior) — use timedelta to roll day
- target_today = now.replace(hour=h, minute=m, second=s, microsecond=0)
+ target_today = now.replace(
+ hour=h, minute=m, second=s, microsecond=0
+ )
if target_today <= now:
target_today = target_today + timedelta(days=1)
total_seconds = (target_today - now).total_seconds()
@@ -1338,7 +1855,9 @@ class CountdownApp:
tz = ZoneInfo(tzname)
# construct aware "now" in that timezone and create the target time
now_tz = datetime.now(tz)
- target = now_tz.replace(hour=h, minute=m, second=s, microsecond=0)
+ target = now_tz.replace(
+ hour=h, minute=m, second=s, microsecond=0
+ )
# if target already passed in that tz, roll to next day
if target <= now_tz:
target = target + timedelta(days=1)
@@ -1346,7 +1865,9 @@ class CountdownApp:
total_seconds = (target - now_tz).total_seconds()
except Exception:
# fallback to naive local behavior
- target_today = now.replace(hour=h, minute=m, second=s, microsecond=0)
+ target_today = now.replace(
+ hour=h, minute=m, second=s, microsecond=0
+ )
if target_today <= now:
target_today = target_today + timedelta(days=1)
total_seconds = (target_today - now).total_seconds()
@@ -1390,8 +1911,12 @@ class CountdownApp:
self.on_hold = False
self.scrubbed = False
self.counting_up = False
- self.text.config(text="T-00:00:00")
- write_countdown_html(self.mission_name, "T-00:00:00")
+
+ prefix = self.count_mode.get() if hasattr(self, "count_mode") else "T-"
+ reset_text = f"{prefix}00:00:00"
+
+ self.text.config(text=reset_text)
+ write_countdown_html(self.mission_name, reset_text)
self.show_hold_button()
# ----------------------------
@@ -1402,6 +1927,19 @@ class CountdownApp:
m = int((seconds % 3600) // 60)
s = int(seconds % 60)
return f"{prefix}{h:02}:{m:02}:{s:02}"
+
+ # Countdown mode toggle
+ def toggle_count_mode(self):
+ current = self.count_mode.get()
+ next_mode = {
+ "T-": "L-",
+ "T+": "L+",
+ "L-": "T-",
+ "L+": "T+",
+ }.get(current, "T-")
+ self.count_mode.set(next_mode)
+ if not self.running:
+ self.reset()
def update_clock(self):
now_time = time.time()
@@ -1416,27 +1954,40 @@ class CountdownApp:
# auto-hold detection: if configured times include this remaining value, enter hold
try:
s = load_settings()
- ah = set(int(x) for x in s.get('auto_hold_times', []) or [])
+ ah = set(int(x) for x in s.get("auto_hold_times", []) or [])
except Exception:
ah = set()
- if diff in ah and diff not in getattr(self, '_auto_hold_triggered', set()):
+ if diff in ah and diff not in getattr(
+ self, "_auto_hold_triggered", set()
+ ):
# trigger hold
self._auto_hold_triggered.add(diff)
self.hold()
# show_hold_button/other UI changes handled by hold()
# After entering hold, update countdown display via next tick
-
+
if diff <= 0 and not self.counting_up:
self.counting_up = True
self.target_time = now_time
diff = 0
if self.counting_up:
elapsed = int(now_time - self.target_time)
- timer_text = self.format_time(elapsed, "T+")
+ prefix = self.count_mode.get()
+
+ # Flip mode from T-/L- to T+/L+ if needed
+ if prefix == "T-":
+ self.count_mode.set("T+")
+ elif prefix == "L-":
+ self.count_mode.set("L+")
+
+ prefix = self.count_mode.get()
+ timer_text = self.format_time(elapsed, prefix)
else:
- timer_text = self.format_time(diff, "T-")
+ prefix = self.count_mode.get()
+ timer_text = self.format_time(diff, prefix)
else:
- timer_text = "T-00:00:00"
+ prefix = self.count_mode.get()
+ timer_text = self.format_time(diff, prefix)
else:
timer_text = self.text.cget("text")
@@ -1471,13 +2022,31 @@ if __name__ == "__main__":
splash.geometry("400x175")
splash.attributes("-topmost", True)
- title = tk.Label(splash, text="RocketLaunchCountdown", fg="white", bg="black", font=("Arial", 20, "bold"))
- title.pack(pady=(10,0))
+ title = tk.Label(
+ splash,
+ text="RocketLaunchCountdown",
+ fg="white",
+ bg="black",
+ font=("Arial", 20, "bold"),
+ )
+ title.pack(pady=(10, 0))
- lbl = tk.Label(splash, text="Loading resources...", fg="white", bg="black", font=("Arial", 14))
- lbl.pack(pady=(0,5))
+ lbl = tk.Label(
+ splash,
+ text="Loading resources...",
+ fg="white",
+ bg="black",
+ font=("Arial", 14),
+ )
+ lbl.pack(pady=(0, 5))
- info = tk.Label(splash, text="Fetching Go/No-Go and preparing HTML files.", fg="#ccc", bg="black", font=("Arial", 10))
+ info = tk.Label(
+ splash,
+ text="Fetching Go/No-Go and preparing HTML files.",
+ fg="#ccc",
+ bg="black",
+ font=("Arial", 10),
+ )
info.pack()
cont_btn = tk.Button(splash, text="Continue", state="disabled", width=12)
@@ -1489,25 +2058,25 @@ if __name__ == "__main__":
# Footer uses inverted colors: white bg/black text in dark mode, black bg/white text in light mode
s = load_settings()
- splash_mode = s.get('appearance_mode', 'dark')
- if splash_mode == 'dark':
- splash_footer_bg = '#FFFFFF'
- splash_footer_fg = '#000000'
+ splash_mode = s.get("appearance_mode", "dark")
+ if splash_mode == "dark":
+ splash_footer_bg = "#FFFFFF"
+ splash_footer_fg = "#000000"
else:
- splash_footer_bg = '#000000'
- splash_footer_fg = '#FFFFFF'
+ splash_footer_bg = "#000000"
+ splash_footer_fg = "#FFFFFF"
footer_label = tk.Label(
footer_frame,
text="Made by HamsterSpaceNerd3000",
font=("Consolas", 12),
fg=splash_footer_fg,
- bg=splash_footer_bg
+ bg=splash_footer_bg,
)
footer_label.pack(fill="x")
# Shared flag to indicate initialization complete
- init_state = { 'done': False, 'error': None }
+ init_state = {"done": False, "error": None}
def init_worker():
try:
@@ -1515,23 +2084,23 @@ if __name__ == "__main__":
gonogo = fetch_gonogo()
write_countdown_html("Placeholder Mission", "T-00:00:00")
write_gonogo_html(gonogo)
- init_state['done'] = True
+ init_state["done"] = True
except Exception as e:
- init_state['error'] = str(e)
- init_state['done'] = True
+ init_state["error"] = str(e)
+ init_state["done"] = True
# Start background initialization
threading.Thread(target=init_worker, daemon=True).start()
def check_init():
- if init_state['done']:
- if init_state['error']:
+ if init_state["done"]:
+ if init_state["error"]:
info.config(text=f"Initialization error: {init_state['error']}")
else:
# show a visible countdown before auto-start; allow Continue to skip
AUTO_START_SECONDS = 5
remaining = AUTO_START_SECONDS
- cont_btn.config(state='normal')
+ cont_btn.config(state="normal")
def tick():
nonlocal remaining
@@ -1560,4 +2129,4 @@ if __name__ == "__main__":
splash.after(100, check_init)
splash.mainloop()
- show_splash_and_start()
\ No newline at end of file
+ show_splash_and_start()