Source code for GUIBRUSHR.GUI.Input_Output_Panels.Input_Panels.TabPanels.FrameGenerationHRData.StellarDialog

import tkinter as tk
from tkinter import Frame

from GUIBRUSHR.GUI.LAYOUT.ScaleManager import ScaleManager
from GUIBRUSHR.GUI.WIDGET.MyEntry import MyEntry
from GUIBRUSHR.GUI.WIDGET.MyLabel import MyLabel
from GUIBRUSHR.GUI.WIDGET.MyButton import MyButton


def _round_to_nearest(value, step=1.0):
    """
    Round value to the nearest multiple of step using half-up rule.
    
    Args:
        value: The numeric value to round
        step: The step size to round to (default: 1.0)
        
    Returns:
        The rounded value as a multiple of step
    """
    return round(value / step) * step


[docs] class StellarDialog(tk.Toplevel): """ Custom dialog for downloading stellar spectrum parameters. This dialog asks whether to download a stellar spectrum and collects stellar parameters (temperature, log gravity, metallicity) if the user confirms. The parameters are validated, rounded to appropriate precision, and returned in a formatted tuple. Attributes: stellar_teff: The initial stellar temperature value result: Tuple containing the validated parameters or (None, None, None) _entries: Dictionary mapping parameter labels to MyEntry widgets _message_label: MyLabel widget displaying the main message _no_button: MyButton widget for "No" action _ok_button: MyButton widget for "OK" action """ # Constants for dialog styling DIALOG_COLOR = "#FFFFFF" BUTTON_COLOR = "#E0E0E0" # Parameter configuration: (label, default_value, rounding_step) PARAMETER_CONFIG = [ ("Stellar Temperature [K]", None, 100), # default set dynamically ("Log Gravity [cm s⁻²]", "4.5", 0.5), ("Metallicity [Fe/H]", "0.0", 0.2), ]
[docs] def __init__(self, parent, stellar_teff): """ Initialize the stellar parameter dialog. Args: parent: The parent widget stellar_teff: Initial stellar temperature value """ super().__init__(parent) self.stellar_teff = stellar_teff self.result = (None, None, None) self._entries = {} self._setup_dialog_properties() self._create_widgets() self._setup_bindings() self._setup_modal_behavior(parent)
def _setup_dialog_properties(self): """Configure basic dialog properties.""" self.title("Download stellar spectrum?") self.resizable(False, False) self.configure(bg=self.DIALOG_COLOR) def _create_widgets(self): """Create and arrange all dialog widgets.""" self._create_message_label() self._create_parameter_entries() self._create_buttons() def _create_message_label(self): """Create the main message label.""" self._message_label = MyLabel( parent=self, row=0, column=0, color=self.DIALOG_COLOR, label_text="Stellar spectrum not present.\nDo you want to download it?", font=ScaleManager.get().font_label_normal if ScaleManager.get() else ("Sans", 10, "normal"), columnspan=2 ) def _create_parameter_entries(self): """Create entry fields for stellar parameters.""" for i, (label, default_value, _) in enumerate(self.PARAMETER_CONFIG, start=1): # Set default value for temperature dynamically if "Temperature" in label: default_value = str(self.stellar_teff) # Create entry with label entry_widget = MyEntry( parent=self, row=i, column=0, text=default_value, label_text=label, color=self.DIALOG_COLOR, columnspan=2, entry_width=12 ) # Store reference to the entry widget for later access self._entries[label] = entry_widget def _create_buttons(self): """Create and arrange dialog buttons.""" # Create a frame to hold the buttons button_frame = Frame(self, bg=self.DIALOG_COLOR) button_frame.grid(row=4, column=0, columnspan=2, pady=(8, 10)) # Create "No" button self._no_button = MyButton( parent=button_frame, row=0, column=0, text="No", bg=self.BUTTON_COLOR, command=self._handle_no_action, color_panel=self.DIALOG_COLOR, size_text=10 ) # Create "OK" button self._ok_button = MyButton( parent=button_frame, row=0, column=1, text="OK", bg=self.BUTTON_COLOR, command=self._handle_ok_action, color_panel=self.DIALOG_COLOR, size_text=10 ) def _setup_bindings(self): """Setup keyboard bindings for the dialog.""" self.bind("<Return>", lambda event: self._handle_ok_action()) self.bind("<Escape>", lambda event: self._handle_no_action()) def _setup_modal_behavior(self, parent): """Configure modal dialog behavior.""" self.transient(parent) self.grab_set() parent.wait_window(self) def _handle_no_action(self): """ Handle the "No" button click or Escape key press. Sets result to (None, None, None) and closes the dialog. """ self.result = (None, None, None) self.destroy() def _handle_ok_action(self): """ Handle the "OK" button click or Enter key press. Validates and processes the entered parameters, then closes the dialog. If validation fails, behaves like "No" action. """ try: # Process each parameter according to its configuration processed_params = [] for label, _, rounding_step in self.PARAMETER_CONFIG: raw_value = self._entries[label].get_value() processed_value = self._process_parameter( raw_value, label, rounding_step ) processed_params.append(processed_value) self.result = tuple(processed_params) except ValueError: # Invalid input → behave like "No" action self.result = (None, None, None) finally: self.destroy() def _process_parameter(self, raw_value, label, rounding_step): """ Process and format a single parameter value. Args: raw_value: The raw string value from the entry field label: The parameter label for identification rounding_step: The step size for rounding Returns: The processed parameter value Raises: ValueError: If the input cannot be converted to float """ numeric_value = float(raw_value) rounded_value = _round_to_nearest(numeric_value, rounding_step) # Special formatting for temperature (5-digit, zero-padded string) if "Temperature" in label: return f"{int(rounded_value):05d}" if "Metallicity" in label: if rounded_value > 0: rounded_value = f"+{abs(rounded_value)}" else: rounded_value = f"-{abs(rounded_value)}" # Return as float for other parameters return rounded_value