Refactor MedTrackerApp and UI components for improved structure and readability
- Simplified initialization logic in init.py - Consolidated testing_mode assignment - Removed unnecessary else statements - Created UIManager class to handle UI-related tasks - Modularized input frame creation, table frame creation, and graph frame creation - Enhanced edit window creation with better organization and error handling - Updated data management methods to improve clarity and maintainability - Improved logging for better debugging and tracking of application flow
This commit is contained in:
@@ -0,0 +1,462 @@
|
||||
import os
|
||||
import logging
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Dict, List, Tuple, Any, Callable, Union
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
|
||||
class UIManager:
|
||||
"""Handle UI creation and management for the application."""
|
||||
|
||||
def __init__(self, root: tk.Tk, logger: logging.Logger) -> None:
|
||||
self.root: tk.Tk = root
|
||||
self.logger: logging.Logger = logger
|
||||
|
||||
def setup_icon(self, img_path: str) -> bool:
|
||||
"""Set up the application icon."""
|
||||
try:
|
||||
self.logger.info(f"Trying to load icon from: {img_path}")
|
||||
# Try to find the icon in various locations
|
||||
if not os.path.exists(img_path):
|
||||
# Check if we're in PyInstaller bundle
|
||||
if hasattr(sys, "_MEIPASS"):
|
||||
# PyInstaller creates a temp folder and stores path in _MEIPASS
|
||||
base_path: str = sys._MEIPASS
|
||||
potential_paths: List[str] = [
|
||||
os.path.join(base_path, os.path.basename(img_path)),
|
||||
os.path.join(base_path, "chart-671.png"),
|
||||
]
|
||||
for path in potential_paths:
|
||||
if os.path.exists(path):
|
||||
self.logger.info(
|
||||
f"Found icon in PyInstaller bundle: {path}"
|
||||
)
|
||||
img_path = path
|
||||
break
|
||||
|
||||
icon_image: Image.Image = Image.open(img_path)
|
||||
icon_image = icon_image.resize(
|
||||
size=(32, 32), resample=Image.Resampling.NEAREST
|
||||
)
|
||||
icon_photo: ImageTk.PhotoImage = ImageTk.PhotoImage(
|
||||
image=icon_image
|
||||
)
|
||||
self.root.iconphoto(True, icon_photo)
|
||||
self.root.wm_iconphoto(True, icon_photo)
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
self.logger.warning(f"Icon file not found at {img_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error setting icon: {str(e)}")
|
||||
return False
|
||||
|
||||
def create_input_frame(self, parent_frame: ttk.Frame) -> Dict[str, Any]:
|
||||
"""Create and configure the input frame with all widgets."""
|
||||
input_frame: ttk.LabelFrame = ttk.LabelFrame(
|
||||
parent_frame, text="New Entry"
|
||||
)
|
||||
input_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
|
||||
input_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
# Create variables for symptoms
|
||||
symptom_vars: Dict[str, tk.IntVar] = {
|
||||
"depression": tk.IntVar(value=0),
|
||||
"anxiety": tk.IntVar(value=0),
|
||||
"sleep": tk.IntVar(value=0),
|
||||
"appetite": tk.IntVar(value=0),
|
||||
}
|
||||
|
||||
# Create scales for symptoms
|
||||
symptom_labels: List[Tuple[str, str]] = [
|
||||
("Depression (0-10):", "depression"),
|
||||
("Anxiety (0-10):", "anxiety"),
|
||||
("Sleep Quality (0-10):", "sleep"),
|
||||
("Appetite (0-10):", "appetite"),
|
||||
]
|
||||
|
||||
for idx, (label, var_name) in enumerate(symptom_labels):
|
||||
ttk.Label(input_frame, text=label).grid(
|
||||
row=idx, column=0, sticky="w", padx=5, pady=2
|
||||
)
|
||||
ttk.Scale(
|
||||
input_frame,
|
||||
from_=0,
|
||||
to=10,
|
||||
orient=tk.HORIZONTAL,
|
||||
variable=symptom_vars[var_name],
|
||||
).grid(row=idx, column=1, sticky="ew")
|
||||
|
||||
# Medicine checkboxes
|
||||
ttk.Label(input_frame, text="Treatment:").grid(
|
||||
row=4, column=0, sticky="w", padx=5, pady=2
|
||||
)
|
||||
medicine_frame = ttk.LabelFrame(input_frame, text="Medicine")
|
||||
medicine_frame.grid(row=4, column=1, padx=0, pady=10, sticky="nsew")
|
||||
|
||||
medicine_vars: Dict[str, Tuple[tk.IntVar, str]] = {
|
||||
"bupropion": (tk.IntVar(value=0), "Bupropion 150mg"),
|
||||
"hydroxyzine": (tk.IntVar(value=0), "Hydroxyzine 25mg"),
|
||||
"gabapentin": (tk.IntVar(value=0), "Gabapentin 100mg"),
|
||||
"propranolol": (tk.IntVar(value=0), "Propranolol 10mg"),
|
||||
}
|
||||
|
||||
for idx, (name, (var, text)) in enumerate(medicine_vars.items()):
|
||||
ttk.Checkbutton(medicine_frame, text=text, variable=var).grid(
|
||||
row=idx, column=0, sticky="w", padx=5, pady=2
|
||||
)
|
||||
|
||||
# Note and Date fields
|
||||
note_var: tk.StringVar = tk.StringVar()
|
||||
date_var: tk.StringVar = tk.StringVar()
|
||||
|
||||
ttk.Label(input_frame, text="Note:").grid(
|
||||
row=5, column=0, sticky="w", padx=5, pady=2
|
||||
)
|
||||
ttk.Entry(input_frame, textvariable=note_var).grid(
|
||||
row=5, column=1, sticky="ew", padx=5, pady=2
|
||||
)
|
||||
|
||||
ttk.Label(input_frame, text="Date (mm/dd/yyyy):").grid(
|
||||
row=6, column=0, sticky="w", padx=5, pady=2
|
||||
)
|
||||
ttk.Entry(input_frame, textvariable=date_var, justify="center").grid(
|
||||
row=6, column=1, sticky="ew", padx=5, pady=2
|
||||
)
|
||||
|
||||
# Return all UI elements and variables
|
||||
return {
|
||||
"frame": input_frame,
|
||||
"symptom_vars": symptom_vars,
|
||||
"medicine_vars": medicine_vars,
|
||||
"note_var": note_var,
|
||||
"date_var": date_var,
|
||||
}
|
||||
|
||||
def create_table_frame(self, parent_frame: ttk.Frame) -> Dict[str, Any]:
|
||||
"""Create and configure the table frame with a treeview."""
|
||||
table_frame: ttk.LabelFrame = ttk.LabelFrame(
|
||||
parent_frame, text="Log (Double-click to edit)"
|
||||
)
|
||||
table_frame.grid(row=1, column=1, padx=10, pady=10, sticky="nsew")
|
||||
|
||||
# Configure table frame to expand
|
||||
table_frame.grid_rowconfigure(0, weight=1)
|
||||
table_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
columns: List[str] = [
|
||||
"Date",
|
||||
"Depression",
|
||||
"Anxiety",
|
||||
"Sleep",
|
||||
"Appetite",
|
||||
"Bupropion",
|
||||
"Hydroxyzine",
|
||||
"Gabapentin",
|
||||
"Propranolol",
|
||||
"Note",
|
||||
]
|
||||
|
||||
tree: ttk.Treeview = ttk.Treeview(
|
||||
table_frame, columns=columns, show="headings"
|
||||
)
|
||||
|
||||
col_labels: List[str] = [
|
||||
"Date",
|
||||
"Depression",
|
||||
"Anxiety",
|
||||
"Sleep",
|
||||
"Appetite",
|
||||
"Bupropion 150mg",
|
||||
"Hydroxyzine 25mg",
|
||||
"Gabapentin 100mg",
|
||||
"Propranolol 10mg",
|
||||
"Note",
|
||||
]
|
||||
|
||||
for col, label in zip(columns, col_labels):
|
||||
tree.heading(col, text=label)
|
||||
|
||||
col_settings: List[Tuple[str, int, str]] = [
|
||||
("Date", 80, "center"),
|
||||
("Depression", 80, "center"),
|
||||
("Anxiety", 80, "center"),
|
||||
("Sleep", 80, "center"),
|
||||
("Appetite", 80, "center"),
|
||||
("Bupropion", 120, "center"),
|
||||
("Hydroxyzine", 120, "center"),
|
||||
("Gabapentin", 120, "center"),
|
||||
("Propranolol", 120, "center"),
|
||||
("Note", 300, "w"),
|
||||
]
|
||||
|
||||
for col, width, anchor in col_settings:
|
||||
tree.column(col, width=width, anchor=anchor)
|
||||
|
||||
tree.pack(side="left", fill="both", expand=True)
|
||||
|
||||
# Add scrollbar
|
||||
scrollbar = ttk.Scrollbar(
|
||||
table_frame, orient="vertical", command=tree.yview
|
||||
)
|
||||
tree.configure(yscrollcommand=scrollbar.set)
|
||||
scrollbar.pack(side="right", fill="y")
|
||||
|
||||
return {"frame": table_frame, "tree": tree}
|
||||
|
||||
def create_graph_frame(self, parent_frame: ttk.Frame) -> ttk.LabelFrame:
|
||||
"""Create and configure the graph frame."""
|
||||
graph_frame: ttk.LabelFrame = ttk.LabelFrame(
|
||||
parent_frame, text="Evolution"
|
||||
)
|
||||
graph_frame.grid(
|
||||
row=0, column=0, columnspan=2, padx=10, pady=10, sticky="nsew"
|
||||
)
|
||||
return graph_frame
|
||||
|
||||
def add_buttons(
|
||||
self, frame: ttk.Frame, buttons_config: List[Dict[str, Any]]
|
||||
) -> ttk.Frame:
|
||||
"""Add buttons to a frame based on configuration."""
|
||||
button_frame: ttk.Frame = ttk.Frame(frame)
|
||||
button_frame.grid(row=7, column=0, columnspan=2, pady=10)
|
||||
|
||||
for btn_config in buttons_config:
|
||||
ttk.Button(
|
||||
button_frame,
|
||||
text=btn_config["text"],
|
||||
command=btn_config["command"],
|
||||
).pack(
|
||||
side="left",
|
||||
padx=5,
|
||||
fill=btn_config.get("fill", None),
|
||||
expand=btn_config.get("expand", False),
|
||||
)
|
||||
|
||||
return button_frame
|
||||
|
||||
def create_edit_window(
|
||||
self, values: Tuple[str, ...], callbacks: Dict[str, Callable]
|
||||
) -> tk.Toplevel:
|
||||
"""Create a new window for editing an entry."""
|
||||
edit_win: tk.Toplevel = tk.Toplevel(master=self.root)
|
||||
edit_win.title("Edit Entry")
|
||||
edit_win.transient(self.root) # Make window modal
|
||||
edit_win.minsize(400, 300)
|
||||
|
||||
# Configure grid columns to expand properly
|
||||
edit_win.grid_columnconfigure(1, weight=1)
|
||||
|
||||
# Unpack values
|
||||
date, dep, anx, slp, app, bup, hydro, gaba, prop, note = values
|
||||
|
||||
# Create variables and fields
|
||||
vars_dict = self._create_edit_fields(
|
||||
edit_win, date, dep, anx, slp, app
|
||||
)
|
||||
|
||||
# Medicine checkboxes
|
||||
current_row = 6 # After the 5 fields (date, dep, anx, slp, app)
|
||||
med_vars = self._create_medicine_checkboxes(
|
||||
edit_win, current_row, bup, hydro, gaba, prop
|
||||
)
|
||||
vars_dict.update(med_vars)
|
||||
|
||||
# Note field
|
||||
current_row += 1
|
||||
vars_dict["note"] = tk.StringVar(value=str(note))
|
||||
ttk.Label(edit_win, text="Note:").grid(
|
||||
row=current_row, column=0, sticky="w", padx=5, pady=2
|
||||
)
|
||||
ttk.Entry(edit_win, textvariable=vars_dict["note"]).grid(
|
||||
row=current_row, column=1, sticky="ew", padx=5, pady=2
|
||||
)
|
||||
|
||||
# Buttons
|
||||
current_row += 1
|
||||
self._add_edit_window_buttons(
|
||||
edit_win, current_row, vars_dict, callbacks
|
||||
)
|
||||
|
||||
# Make window modal
|
||||
edit_win.update_idletasks()
|
||||
edit_win.focus_set()
|
||||
edit_win.grab_set()
|
||||
|
||||
return edit_win
|
||||
|
||||
def _create_edit_fields(
|
||||
self,
|
||||
parent: tk.Toplevel,
|
||||
date: str,
|
||||
dep: int,
|
||||
anx: int,
|
||||
slp: int,
|
||||
app: int,
|
||||
) -> Dict[str, Union[tk.StringVar, tk.IntVar]]:
|
||||
"""Create fields for editing entry values."""
|
||||
vars_dict: Dict[str, Union[tk.StringVar, tk.IntVar]] = {}
|
||||
|
||||
# Ensure values are converted to appropriate types
|
||||
try:
|
||||
app = int(app) if app != "" else 0
|
||||
except (ValueError, TypeError):
|
||||
self.logger.warning(
|
||||
f"Invalid appetite value: {app}, defaulting to 0"
|
||||
)
|
||||
app = 0
|
||||
|
||||
value_map = {
|
||||
"date": date,
|
||||
"depression": dep,
|
||||
"anxiety": anx,
|
||||
"sleep": slp,
|
||||
"appetite": app,
|
||||
}
|
||||
|
||||
fields = [
|
||||
("Date", tk.StringVar, "date"),
|
||||
("Depression (0-10)", tk.IntVar, "depression"),
|
||||
("Anxiety (0-10)", tk.IntVar, "anxiety"),
|
||||
("Sleep (0-10)", tk.IntVar, "sleep"),
|
||||
("Appetite (0-10)", tk.IntVar, "appetite"),
|
||||
]
|
||||
|
||||
for idx, (label, var_type, key) in enumerate(fields):
|
||||
try:
|
||||
value = value_map[key]
|
||||
if var_type == tk.IntVar:
|
||||
try:
|
||||
value = int(float(value))
|
||||
except (ValueError, TypeError):
|
||||
value = 0
|
||||
self.logger.warning(
|
||||
f"Failed to convert {key} value: {value}, defaulting to 0"
|
||||
)
|
||||
else:
|
||||
value = str(value)
|
||||
except (ValueError, TypeError, KeyError):
|
||||
value = 0 if var_type == tk.IntVar else ""
|
||||
self.logger.warning(
|
||||
f"Missing or invalid value for {key}, defaulting to {value}"
|
||||
)
|
||||
|
||||
vars_dict[key] = var_type(value=value)
|
||||
ttk.Label(parent, text=f"{label}:").grid(
|
||||
row=idx + 1, column=0, sticky="w", padx=5, pady=2
|
||||
)
|
||||
|
||||
if var_type == tk.IntVar:
|
||||
self._create_scale_with_label(
|
||||
parent, idx + 1, vars_dict[key], value
|
||||
)
|
||||
else:
|
||||
ttk.Entry(parent, textvariable=vars_dict[key]).grid(
|
||||
row=idx + 1, column=1, sticky="ew"
|
||||
)
|
||||
|
||||
return vars_dict
|
||||
|
||||
def _create_scale_with_label(
|
||||
self, parent: tk.Toplevel, row: int, var: tk.IntVar, value: int
|
||||
) -> None:
|
||||
"""Create a scale with a value label."""
|
||||
scale_frame: ttk.Frame = ttk.Frame(parent)
|
||||
scale_frame.grid(row=row, column=1, sticky="ew", padx=5, pady=2)
|
||||
scale_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
scale = ttk.Scale(
|
||||
scale_frame, from_=0, to=10, variable=var, orient=tk.HORIZONTAL
|
||||
)
|
||||
scale.grid(row=0, column=0, sticky="ew", padx=5)
|
||||
|
||||
# Add a value label to show the current value
|
||||
value_label = ttk.Label(scale_frame, width=3)
|
||||
value_label.grid(row=0, column=1, padx=(5, 0))
|
||||
|
||||
# Update label when scale value changes
|
||||
def update_label(event=None):
|
||||
value_label.configure(text=str(var.get()))
|
||||
|
||||
scale.bind("<Motion>", update_label)
|
||||
scale.bind("<ButtonRelease-1>", update_label)
|
||||
update_label() # Set initial value
|
||||
scale.set(value) # Explicitly set scale value
|
||||
|
||||
def _create_medicine_checkboxes(
|
||||
self,
|
||||
parent: tk.Toplevel,
|
||||
row: int,
|
||||
bup: int,
|
||||
hydro: int,
|
||||
gaba: int,
|
||||
prop: int,
|
||||
) -> Dict[str, tk.IntVar]:
|
||||
"""Create medicine checkboxes in the edit window."""
|
||||
ttk.Label(parent, text="Treatment:").grid(
|
||||
row=row, column=0, sticky="w", padx=5, pady=2
|
||||
)
|
||||
medicine_frame: ttk.LabelFrame = ttk.LabelFrame(
|
||||
parent, text="Medicine"
|
||||
)
|
||||
medicine_frame.grid(row=row, column=1, padx=0, pady=10, sticky="nsew")
|
||||
|
||||
medicine_vars: Dict[str, Tuple[int, str]] = {
|
||||
"bupropion": (bup, "Bupropion 150mg"),
|
||||
"hydroxyzine": (hydro, "Hydroxyzine 25mg"),
|
||||
"gabapentin": (gaba, "Gabapentin 100mg"),
|
||||
"propranolol": (prop, "Propranolol 10mg"),
|
||||
}
|
||||
|
||||
vars_dict: Dict[str, tk.IntVar] = {}
|
||||
for idx, (key, (value, label)) in enumerate(medicine_vars.items()):
|
||||
vars_dict[key] = tk.IntVar(value=int(value))
|
||||
ttk.Checkbutton(
|
||||
medicine_frame, text=label, variable=vars_dict[key]
|
||||
).grid(row=idx, column=0, sticky="w", padx=5, pady=2)
|
||||
|
||||
return vars_dict
|
||||
|
||||
def _add_edit_window_buttons(
|
||||
self,
|
||||
parent: tk.Toplevel,
|
||||
row: int,
|
||||
vars_dict: Dict[str, Any],
|
||||
callbacks: Dict[str, Callable],
|
||||
) -> None:
|
||||
"""Add buttons to the edit window."""
|
||||
button_frame: ttk.Frame = ttk.Frame(parent)
|
||||
button_frame.grid(row=row, column=0, columnspan=2, pady=10)
|
||||
|
||||
# Save button
|
||||
ttk.Button(
|
||||
button_frame,
|
||||
text="Save",
|
||||
command=lambda: callbacks["save"](
|
||||
parent,
|
||||
vars_dict["date"].get(),
|
||||
vars_dict["depression"].get(),
|
||||
vars_dict["anxiety"].get(),
|
||||
vars_dict["sleep"].get(),
|
||||
vars_dict["appetite"].get(),
|
||||
vars_dict["bupropion"].get(),
|
||||
vars_dict["hydroxyzine"].get(),
|
||||
vars_dict["gabapentin"].get(),
|
||||
vars_dict["propranolol"].get(),
|
||||
vars_dict["note"].get(),
|
||||
),
|
||||
).pack(side="left", padx=5)
|
||||
|
||||
# Cancel button
|
||||
ttk.Button(button_frame, text="Cancel", command=parent.destroy).pack(
|
||||
side="left", padx=5
|
||||
)
|
||||
|
||||
# Delete button
|
||||
ttk.Button(
|
||||
button_frame,
|
||||
text="Delete",
|
||||
command=lambda: callbacks["delete"](parent),
|
||||
).pack(side="left", padx=5)
|
||||
Reference in New Issue
Block a user