Source code for frontend.ui_main_menu

"""
Main menu module.
Contains the main application window and exit confirmation dialog.
"""

# Standard library
import os
import sys
from pathlib import Path
from tkinter import Tk, Toplevel, TOP, LEFT, RIGHT
from tkinter import ttk
from typing import Callable

# Third-party
from PIL import Image, ImageTk

# Local imports
from config import (
    UI_STYLE,
    __version__,
    configure_ttk_styles,
)
from frontend.keyboard_nav import setup_arrow_enter_navigation
from frontend.ui_dialogs.tooltip import bind_tooltip
from frontend.window_utils import place_window_centered
from i18n import t


[docs] def create_main_menu( normal_fitting_callback: Callable, single_fit_multiple_datasets_callback: Callable, multiple_fits_single_dataset_callback: Callable, all_fits_single_dataset_callback: Callable, watch_data_callback: Callable, help_callback: Callable, ) -> Tk: """ Create and display the main application menu window. Args: normal_fitting_callback: Function to call for normal fitting single_fit_multiple_datasets_callback: Function to call for single fit on multiple datasets multiple_fits_single_dataset_callback: Function to call for multiple fits on single dataset all_fits_single_dataset_callback: Function to call for all fits on single dataset watch_data_callback: Function to call for viewing data help_callback: Function to display help information Returns: The main Tk window instance """ menu = Tk() menu.title(f"{t('menu.title')} — v{__version__}") menu.attributes("-fullscreen", False) menu.configure(background=UI_STYLE["bg"]) menu.resizable(width=False, height=False) configure_ttk_styles(menu) # Closing with X: same as Exit button (show confirmation, then close app) menu.protocol("WM_DELETE_WINDOW", lambda: show_exit_confirmation(menu)) # Main frame: ttk with Raised.TFrame (lighter border) and inner content frame outer_frame = ttk.Frame(menu, style="Raised.TFrame") main_frame = ttk.Frame(outer_frame, padding=UI_STYLE["border_width"]) # Load and display logo (PNG with possible transparency; scale by width, keep aspect ratio) logo_label = None project_root = Path(__file__).resolve().parent.parent.parent logo_path = project_root / "images" / "RegressionLab_logo_app.png" if logo_path.exists(): try: logo_image = Image.open( logo_path ) # do not convert to RGB to preserve transparency max_width = 600 aspect_ratio = logo_image.height / logo_image.width new_height = int(max_width * aspect_ratio) logo_image = logo_image.resize( (max_width, new_height), Image.Resampling.LANCZOS ) logo_photo = ImageTk.PhotoImage(logo_image) logo_label = ttk.Label(main_frame, image=logo_photo) logo_label.image = ( logo_photo # keep reference to prevent garbage collection ) except Exception: pass # show menu without logo if load fails # Welcome message message = ttk.Label(main_frame, text=t("menu.welcome"), style="LargeBold.TLabel") version_label = ttk.Label(main_frame, text=f"v{__version__}") # Primary actions: main fitting and data options (green) normal_fitting_button = ttk.Button( main_frame, text=t("menu.normal_fitting"), command=normal_fitting_callback, style="Primary.TButton", width=UI_STYLE["button_width_wide"], ) multiple_datasets_button = ttk.Button( main_frame, text=t("menu.multiple_datasets"), command=single_fit_multiple_datasets_callback, style="Primary.TButton", width=UI_STYLE["button_width_wide"], ) multiple_fits_button = ttk.Button( main_frame, text=t("menu.checker_fitting"), command=multiple_fits_single_dataset_callback, style="Primary.TButton", width=UI_STYLE["button_width_wide"], ) all_fits_button = ttk.Button( main_frame, text=t("menu.total_fitting"), command=all_fits_single_dataset_callback, style="Primary.TButton", width=UI_STYLE["button_width_wide"], ) view_data_button = ttk.Button( main_frame, text=t("menu.view_data"), command=watch_data_callback, style="Primary.TButton", width=UI_STYLE["button_width_wide"], ) help_button = ttk.Button( main_frame, text=t("menu.information"), command=help_callback, style="Primary.TButton", width=UI_STYLE["button_width_wide"], ) # Secondary: config (neutral) config_button = ttk.Button( main_frame, text=t("menu.config"), command=lambda: _handle_config(menu), style="Secondary.TButton", width=UI_STYLE["button_width"], ) # Danger: exit (red) exit_button = ttk.Button( main_frame, text=t("menu.exit"), command=lambda: show_exit_confirmation(menu), style="Danger.TButton", width=UI_STYLE["button_width"], ) # Tooltips for menu buttons bind_tooltip(normal_fitting_button, t("menu.tooltip_normal_fitting")) bind_tooltip(multiple_datasets_button, t("menu.tooltip_multiple_datasets")) bind_tooltip(multiple_fits_button, t("menu.tooltip_checker_fitting")) bind_tooltip(all_fits_button, t("menu.tooltip_total_fitting")) bind_tooltip(view_data_button, t("menu.tooltip_view_data")) bind_tooltip(help_button, t("menu.tooltip_information")) bind_tooltip(config_button, t("menu.tooltip_config")) bind_tooltip(exit_button, t("menu.tooltip_exit")) # Layout outer_frame.grid(column=0, row=0) main_frame.pack(fill="both", expand=True) # Place logo if it was loaded successfully _pad = UI_STYLE["padding"] current_row = 0 if logo_label: logo_label.grid(column=0, row=current_row, columnspan=2, padx=_pad, pady=6) current_row += 1 message.grid(column=0, row=current_row, columnspan=2, padx=_pad, pady=6) current_row += 1 version_label.grid(column=0, row=current_row, columnspan=2, padx=_pad, pady=(0, 6)) current_row += 1 normal_fitting_button.grid(column=0, row=current_row, padx=_pad, pady=_pad) multiple_datasets_button.grid(column=1, row=current_row, padx=_pad, pady=_pad) current_row += 1 multiple_fits_button.grid(column=0, row=current_row, padx=_pad, pady=_pad) all_fits_button.grid(column=1, row=current_row, padx=_pad, pady=_pad) current_row += 1 help_button.grid(column=0, row=current_row, padx=_pad, pady=_pad) view_data_button.grid(column=1, row=current_row, padx=_pad, pady=_pad) current_row += 1 config_button.grid(column=0, row=current_row, padx=_pad, pady=_pad) exit_button.grid(column=1, row=current_row, padx=_pad, pady=_pad) setup_arrow_enter_navigation( [ [normal_fitting_button, multiple_datasets_button], [multiple_fits_button, all_fits_button], [help_button, view_data_button], [config_button, exit_button], ] ) normal_fitting_button.focus_set() return menu
def _handle_config(menu: Tk) -> None: """ Open configuration dialog and restart application if user saves. Displays the configuration dialog. If the user accepts and saves changes, the application is restarted to apply the new configuration. Args: menu: The main menu Tkinter window (``Tk`` instance). """ from frontend.ui_dialogs import show_config_dialog if show_config_dialog(menu): menu.destroy() os.execv(sys.executable, [sys.executable] + sys.argv)
[docs] def show_exit_confirmation(parent_menu: Tk) -> None: """ Display exit confirmation dialog. Args: parent_menu: The parent menu window to close if user confirms exit """ exit_level = Toplevel() exit_level.title(t("menu.exit_title")) exit_level.resizable(width=False, height=False) exit_level.configure(background=UI_STYLE["bg"]) # Message message = ttk.Label(exit_level, text=t("menu.exit_confirm"), style="Large.TLabel") # Buttons: confirm exit = danger, cancel = accent close_button = ttk.Button( exit_level, text=t("menu.yes"), command=lambda: _close_application(parent_menu), style="Danger.TButton", width=UI_STYLE["button_width"], ) abort_button = ttk.Button( exit_level, text=t("menu.no"), command=exit_level.destroy, style="Accent.TButton", width=UI_STYLE["button_width"], ) # Layout message.pack(side=TOP, padx=5, pady=UI_STYLE["padding"]) close_button.pack(side=LEFT, padx=UI_STYLE["padding"], pady=UI_STYLE["padding"]) abort_button.pack(side=RIGHT, padx=UI_STYLE["padding"], pady=UI_STYLE["padding"]) setup_arrow_enter_navigation([[close_button, abort_button]]) close_button.focus_set() # Closing with X = cancel exit (same as "No") exit_level.protocol("WM_DELETE_WINDOW", exit_level.destroy) place_window_centered(exit_level, preserve_size=True) parent_menu.wait_window(exit_level)
def _close_application(menu: Tk) -> None: """ Close the application and exit. Args: menu: The main menu window to destroy """ menu.destroy() sys.exit()
[docs] def start_main_menu( normal_fitting_callback: Callable, single_fit_multiple_datasets_callback: Callable, multiple_fits_single_dataset_callback: Callable, all_fits_single_dataset_callback: Callable, watch_data_callback: Callable, help_callback: Callable, ) -> None: """ Create and run the main application menu. This is the entry point for the GUI application. Args: normal_fitting_callback: Function to call for normal fitting single_fit_multiple_datasets_callback: Function to call for single fit on multiple datasets multiple_fits_single_dataset_callback: Function to call for multiple fits on single dataset all_fits_single_dataset_callback: Function to call for all fits on single dataset watch_data_callback: Function to call for viewing data help_callback: Function to display help information """ menu = create_main_menu( normal_fitting_callback=normal_fitting_callback, single_fit_multiple_datasets_callback=single_fit_multiple_datasets_callback, multiple_fits_single_dataset_callback=multiple_fits_single_dataset_callback, all_fits_single_dataset_callback=all_fits_single_dataset_callback, watch_data_callback=watch_data_callback, help_callback=help_callback, ) # Store menu globally for callbacks that need it import __main__ __main__.menu = menu menu.mainloop()