Add theme management and settings functionality
Build and Push Docker Image / build-and-push (push) Has been cancelled

- Introduced `ThemeManager` to handle application themes using `ttkthemes`.
- Added `SettingsWindow` for user preferences including theme selection and UI settings.
- Integrated theme selection into the main application with a menu for quick access.
- Enhanced UI components with custom styles based on the selected theme.
- Implemented tooltips for better user guidance across various UI elements.
- Updated dependencies to include `ttkthemes` for improved visual appeal.
This commit is contained in:
William Valentin
2025-08-05 11:58:25 -07:00
parent 86606d56b6
commit c3c88c63d2
14 changed files with 1287 additions and 70 deletions
+98 -16
View File
@@ -11,6 +11,7 @@ from PIL import Image, ImageTk
from medicine_manager import MedicineManager
from pathology_manager import PathologyManager
from tooltip_system import TooltipManager
class UIManager:
@@ -22,17 +23,22 @@ class UIManager:
logger: logging.Logger,
medicine_manager: MedicineManager,
pathology_manager: PathologyManager,
theme_manager, # Import would create circular dependency
) -> None:
self.root: tk.Tk = root
self.logger: logging.Logger = logger
self.medicine_manager = medicine_manager
self.pathology_manager = pathology_manager
self.theme_manager = theme_manager
# Status bar attributes
self.status_bar: tk.Frame | None = None
self.status_label: tk.Label | None = None
self.file_info_label: tk.Label | None = None
# Initialize tooltip manager
self.tooltip_manager = TooltipManager(theme_manager)
def setup_application_icon(self, img_path: str) -> bool:
"""Set up the application icon."""
try:
@@ -70,13 +76,20 @@ class UIManager:
def create_input_frame(self, parent_frame: ttk.Frame) -> dict[str, Any]:
"""Create and configure the input frame with all widgets."""
# Create main container for the scrollable input frame
main_container = ttk.LabelFrame(parent_frame, text="New Entry")
main_container = ttk.LabelFrame(
parent_frame, text="New Entry", style="Card.TLabelframe"
)
main_container.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
main_container.grid_rowconfigure(0, weight=1)
main_container.grid_columnconfigure(0, weight=1)
# Create canvas and scrollbar for scrolling
canvas = tk.Canvas(main_container, highlightthickness=0)
theme_colors = self.theme_manager.get_theme_colors()
canvas = tk.Canvas(
main_container,
highlightthickness=0,
bg=theme_colors["bg"],
)
scrollbar = ttk.Scrollbar(
main_container, orient="vertical", command=canvas.yview
)
@@ -164,7 +177,9 @@ class UIManager:
ttk.Label(input_frame, text="Treatment:").grid(
row=medicine_row, column=0, sticky="w", padx=5, pady=2
)
medicine_frame = ttk.LabelFrame(input_frame, text="Medicine")
medicine_frame = ttk.LabelFrame(
input_frame, text="Medicine", style="Card.TLabelframe"
)
medicine_frame.grid(row=medicine_row, column=1, padx=0, pady=10, sticky="nsew")
medicine_frame.grid_columnconfigure(0, weight=1)
@@ -178,11 +193,19 @@ class UIManager:
text = f"{medicine.display_name} {medicine.dosage_info}"
medicine_vars[medicine_key] = (var, text)
for idx, (_med_name, (var, text)) in enumerate(medicine_vars.items()):
for idx, (med_key, (var, text)) in enumerate(medicine_vars.items()):
# Just checkbox for medicine taken
ttk.Checkbutton(medicine_frame, text=text, variable=var).grid(
row=idx, column=0, sticky="w", padx=5, pady=2
checkbox = ttk.Checkbutton(
medicine_frame, text=text, variable=var, style="Modern.TCheckbutton"
)
checkbox.grid(row=idx, column=0, sticky="w", padx=5, pady=2)
# Add tooltip for medicine checkbox
medicine = self.medicine_manager.get_medicine(med_key)
if medicine:
self.tooltip_manager.add_medicine_tooltip(
checkbox, medicine.display_name
)
# Note and Date fields - adjust row numbers
note_row = medicine_row + 1
@@ -194,16 +217,19 @@ class UIManager:
ttk.Label(input_frame, text="Note:").grid(
row=note_row, column=0, sticky="w", padx=5, pady=2
)
ttk.Entry(input_frame, textvariable=note_var).grid(
ttk.Entry(input_frame, textvariable=note_var, style="Modern.TEntry").grid(
row=note_row, column=1, sticky="ew", padx=5, pady=2
)
ttk.Label(input_frame, text="Date (mm/dd/yyyy):").grid(
row=date_row, column=0, sticky="w", padx=5, pady=2
)
ttk.Entry(input_frame, textvariable=date_var, justify="center").grid(
row=date_row, column=1, sticky="ew", padx=5, pady=2
)
ttk.Entry(
input_frame,
textvariable=date_var,
justify="center",
style="Modern.TEntry",
).grid(row=date_row, column=1, sticky="ew", padx=5, pady=2)
# Set default date to today
date_var.set(datetime.now().strftime("%m/%d/%Y"))
@@ -225,7 +251,7 @@ class UIManager:
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)"
parent_frame, text="Log (Double-click to edit)", style="Card.TLabelframe"
)
table_frame.grid(row=1, column=1, padx=10, pady=10, sticky="nsew")
@@ -258,7 +284,34 @@ class UIManager:
col_labels.append("Note")
col_settings.append(("Note", 300, "w"))
tree: ttk.Treeview = ttk.Treeview(table_frame, columns=columns, show="headings")
tree: ttk.Treeview = ttk.Treeview(
table_frame, columns=columns, show="headings", style="Modern.Treeview"
)
# Configure treeview selection behavior
tree.configure(selectmode="browse") # Single selection mode
# Configure row tags for alternating colors
theme_colors = self.theme_manager.get_theme_colors()
tree.tag_configure("evenrow", background=theme_colors["bg"])
tree.tag_configure("oddrow", background=theme_colors["alt_bg"])
# Configure selection highlighting
tree.tag_configure(
"selected",
background=theme_colors["select_bg"],
foreground=theme_colors["select_fg"],
)
# Bind selection events to ensure proper highlighting
def on_selection_change(event):
"""Handle treeview selection changes to ensure proper highlighting."""
selection = tree.selection()
if selection:
# Force focus to ensure selection is visible
tree.focus(selection[0])
tree.bind("<<TreeviewSelect>>", on_selection_change)
for col, label in zip(columns, col_labels, strict=False):
tree.heading(col, text=label)
@@ -277,7 +330,9 @@ class UIManager:
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: ttk.LabelFrame = ttk.LabelFrame(
parent_frame, text="Evolution", style="Card.TLabelframe"
)
graph_frame.grid(row=0, column=0, columnspan=2, padx=10, pady=10, sticky="nsew")
return graph_frame
@@ -289,23 +344,40 @@ class UIManager:
button_frame.grid(row=7, column=0, columnspan=2, pady=10)
for btn_config in buttons_config:
ttk.Button(
button = ttk.Button(
button_frame,
text=btn_config["text"],
command=btn_config["command"],
).pack(
style="Action.TButton",
)
button.pack(
side="left",
padx=5,
fill=btn_config.get("fill", None),
expand=btn_config.get("expand", False),
)
# Add tooltips based on button text
button_text = btn_config["text"].lower()
if "add" in button_text or "save" in button_text:
self.tooltip_manager.add_button_tooltip(button, "save")
elif "quit" in button_text or "exit" in button_text:
self.tooltip_manager.add_button_tooltip(button, "quit")
return button_frame
def create_status_bar(self, parent_frame: tk.Widget) -> tk.Frame:
"""Create and configure the status bar at the bottom of the application."""
# Get theme colors for consistent styling
theme_colors = self.theme_manager.get_theme_colors()
# Create the status bar frame
self.status_bar = tk.Frame(parent_frame, relief=tk.SUNKEN, bd=1)
self.status_bar = tk.Frame(
parent_frame,
relief=tk.SUNKEN,
bd=1,
bg=theme_colors["bg"],
)
self.status_bar.grid(row=2, column=0, columnspan=2, sticky="ew", padx=5, pady=2)
# Configure the parent to make the status bar stretch
@@ -319,6 +391,8 @@ class UIManager:
font=("TkDefaultFont", 9),
padx=10,
pady=2,
bg=theme_colors["bg"],
fg=theme_colors["fg"],
)
self.status_label.pack(side=tk.LEFT, fill=tk.X, expand=True)
@@ -330,6 +404,8 @@ class UIManager:
font=("TkDefaultFont", 9),
padx=10,
pady=2,
bg=theme_colors["bg"],
fg=theme_colors["fg"],
)
self.file_info_label.pack(side=tk.RIGHT)
@@ -793,9 +869,15 @@ class UIManager:
variable=vars_dict[key],
orient=tk.HORIZONTAL,
length=250,
style="Modern.Horizontal.TScale",
)
scale.grid(row=0, column=1, sticky="ew")
# Add tooltip for the scale
pathology = self.pathology_manager.get_pathology(key)
if pathology:
self.tooltip_manager.add_scale_tooltip(scale, pathology.display_name)
# Scale labels
labels_frame = ttk.Frame(scale_container)
labels_frame.grid(row=1, column=0, sticky="ew", pady=(5, 0))