add configuration manager, fix minor display ppi
This commit is contained in:
parent
870d79889d
commit
276cae0692
10
settings.json
Normal file
10
settings.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"general": {
|
||||
"scan_limit": 60,
|
||||
"max_range": 200,
|
||||
"connection_port": null,
|
||||
"geometry": "1200x900+338+338",
|
||||
"last_selected_scenario": null
|
||||
},
|
||||
"scenarios": {}
|
||||
}
|
||||
@ -4,7 +4,9 @@
|
||||
Main view of the application, containing the primary window and widgets.
|
||||
"""
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, scrolledtext
|
||||
from tkinter import ttk, scrolledtext, messagebox
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from .ppi_display import PPIDisplay
|
||||
from .settings_window import SettingsWindow
|
||||
@ -15,57 +17,70 @@ from .add_target_window import AddTargetWindow
|
||||
from core.serial_communicator import SerialCommunicator
|
||||
from core.models import Scenario, Target
|
||||
from utils.logger import get_logger, shutdown_logging_system
|
||||
from utils.config_manager import ConfigManager
|
||||
|
||||
class MainView(tk.Tk):
|
||||
"""The main application window."""
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.logger = get_logger(__name__)
|
||||
self.config_manager = ConfigManager()
|
||||
|
||||
# -- Load Settings --
|
||||
settings = self.config_manager.get_general_settings()
|
||||
self.scan_limit = settings.get('scan_limit', 60)
|
||||
self.max_range = settings.get('max_range', 100)
|
||||
self.connection_port = settings.get('connection_port', None)
|
||||
|
||||
self.communicator = SerialCommunicator()
|
||||
self.scenario = Scenario()
|
||||
self.connection_port: str | None = None
|
||||
self.scan_limit = 60
|
||||
self.max_range = 100
|
||||
|
||||
self.current_scenario_name: Optional[str] = None
|
||||
|
||||
self.title("Radar Target Simulator")
|
||||
self.geometry("1200x900")
|
||||
self.geometry(settings.get('geometry', '1200x900'))
|
||||
self.minsize(900, 700)
|
||||
|
||||
self._create_menubar()
|
||||
self._create_main_layout()
|
||||
self._create_statusbar()
|
||||
|
||||
self._load_scenarios_into_ui()
|
||||
last_scenario = settings.get("last_selected_scenario")
|
||||
if last_scenario:
|
||||
self._on_load_scenario(last_scenario)
|
||||
|
||||
self._update_window_title()
|
||||
|
||||
self.protocol("WM_DELETE_WINDOW", self._on_closing)
|
||||
self.logger.info("MainView initialized successfully.")
|
||||
|
||||
def _create_main_layout(self):
|
||||
"""Creates the main paned layout based on the new design."""
|
||||
# Main vertical pane: Top (Content) / Bottom (Logs)
|
||||
v_pane = ttk.PanedWindow(self, orient=tk.VERTICAL)
|
||||
v_pane.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
# Top horizontal pane: Left (Controls) / Right (PPI)
|
||||
self.h_pane = ttk.PanedWindow(v_pane, orient=tk.HORIZONTAL)
|
||||
v_pane.add(self.h_pane, weight=4)
|
||||
|
||||
# --- Left Side Frame (for controls) ---
|
||||
left_frame = ttk.Frame(self.h_pane)
|
||||
self.h_pane.add(left_frame, weight=1)
|
||||
|
||||
# --- Right Side Frame (for PPI) ---
|
||||
self.ppi_widget = PPIDisplay(self.h_pane, max_range_nm=self.max_range, scan_limit_deg=self.scan_limit)
|
||||
self.h_pane.add(self.ppi_widget, weight=2)
|
||||
|
||||
# --- Populate Left Frame ---
|
||||
self.scenario_controls = ScenarioControlsFrame(left_frame)
|
||||
self.scenario_controls = ScenarioControlsFrame(
|
||||
left_frame,
|
||||
load_scenario_command=self._on_load_scenario,
|
||||
save_as_command=self._on_save_scenario_as,
|
||||
update_command=self._on_update_scenario,
|
||||
delete_command=self._on_delete_scenario
|
||||
)
|
||||
self.scenario_controls.pack(fill=tk.X, expand=False, padx=5, pady=(0, 5))
|
||||
|
||||
self.target_list = TargetListFrame(left_frame)
|
||||
self.target_list.pack(fill=tk.BOTH, expand=True, padx=5)
|
||||
# Connect button command
|
||||
self.target_list.add_button.config(command=self._on_add_target)
|
||||
|
||||
# --- Logger (Bottom) ---
|
||||
log_frame_container = ttk.LabelFrame(v_pane, text="Logs")
|
||||
v_pane.add(log_frame_container, weight=1)
|
||||
|
||||
@ -75,7 +90,6 @@ class MainView(tk.Tk):
|
||||
self.log_text_widget.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
def _create_menubar(self):
|
||||
# (This method remains unchanged)
|
||||
menubar = tk.Menu(self)
|
||||
self.config(menu=menubar)
|
||||
|
||||
@ -85,24 +99,33 @@ class MainView(tk.Tk):
|
||||
settings_menu.add_command(label="Radar Config...", command=self._open_radar_config)
|
||||
|
||||
def _create_statusbar(self):
|
||||
# (This method remains unchanged)
|
||||
self.status_var = tk.StringVar(value="Status: Disconnected")
|
||||
status_bar = ttk.Label(self, textvariable=self.status_var, anchor=tk.W, relief=tk.SUNKEN)
|
||||
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
|
||||
|
||||
def _update_window_title(self):
|
||||
"""Updates the window title with the current scenario name."""
|
||||
title = "Radar Target Simulator"
|
||||
if self.current_scenario_name:
|
||||
title = f"{self.current_scenario_name} - {title}"
|
||||
self.title(title)
|
||||
|
||||
def _update_all_views(self):
|
||||
"""Refreshes all GUI components based on the current scenario."""
|
||||
self._update_window_title()
|
||||
all_targets = self.scenario.get_all_targets()
|
||||
self.target_list.update_target_list(all_targets)
|
||||
self.ppi_widget.update_targets(all_targets)
|
||||
self.logger.info(f"Views updated. Scenario now has {len(all_targets)} target(s).")
|
||||
|
||||
self.logger.info(f"Views updated for scenario '{self.current_scenario_name}'. It has {len(all_targets)} target(s).")
|
||||
|
||||
def _load_scenarios_into_ui(self):
|
||||
"""Loads scenario names from config and updates the combobox."""
|
||||
scenario_names = self.config_manager.get_scenario_names()
|
||||
self.scenario_controls.update_scenario_list(scenario_names, self.current_scenario_name)
|
||||
|
||||
def _on_add_target(self):
|
||||
"""Handles the 'Add Target' button click."""
|
||||
self.logger.debug("'Add Target' button clicked. Opening dialog.")
|
||||
existing_ids = list(self.scenario.targets.keys())
|
||||
|
||||
# This will block until the dialog is closed
|
||||
dialog = AddTargetWindow(self, existing_ids)
|
||||
|
||||
new_target = dialog.new_target
|
||||
@ -113,6 +136,50 @@ class MainView(tk.Tk):
|
||||
else:
|
||||
self.logger.debug("Add target dialog was cancelled.")
|
||||
|
||||
def _on_load_scenario(self, scenario_name: str):
|
||||
self.logger.info(f"Loading scenario: {scenario_name}")
|
||||
scenario_data = self.config_manager.get_scenario(scenario_name)
|
||||
if scenario_data:
|
||||
try:
|
||||
self.scenario = Scenario.from_dict(scenario_data)
|
||||
self.current_scenario_name = scenario_name
|
||||
self._update_all_views()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to parse scenario data for '{scenario_name}': {e}")
|
||||
messagebox.showerror("Load Error", f"Could not load scenario '{scenario_name}'.\n{e}")
|
||||
else:
|
||||
self.logger.warning(f"Attempted to load a non-existent scenario: {scenario_name}")
|
||||
|
||||
def _on_save_scenario_as(self, scenario_name: str):
|
||||
self.logger.info(f"Saving current state as new scenario: {scenario_name}")
|
||||
scenario_data = self.scenario.to_dict()
|
||||
self.config_manager.save_scenario(scenario_name, scenario_data)
|
||||
self.current_scenario_name = scenario_name
|
||||
self._load_scenarios_into_ui() # Refresh list
|
||||
self._update_window_title()
|
||||
messagebox.showinfo("Success", f"Scenario '{scenario_name}' saved successfully.", parent=self)
|
||||
|
||||
def _on_update_scenario(self, scenario_name: str):
|
||||
self.logger.info(f"Updating scenario: {scenario_name}")
|
||||
scenario_data = self.scenario.to_dict()
|
||||
self.config_manager.save_scenario(scenario_name, scenario_data)
|
||||
self._load_scenarios_into_ui() # Refresh list to be safe
|
||||
messagebox.showinfo("Success", f"Scenario '{scenario_name}' updated successfully.", parent=self)
|
||||
|
||||
def _on_delete_scenario(self, scenario_name: str):
|
||||
self.logger.info(f"Deleting scenario: {scenario_name}")
|
||||
self.config_manager.delete_scenario(scenario_name)
|
||||
self.logger.info(f"Scenario '{scenario_name}' deleted.")
|
||||
|
||||
# Clear current view if the deleted scenario was active
|
||||
if self.current_scenario_name == scenario_name:
|
||||
self.scenario = Scenario()
|
||||
self.current_scenario_name = None
|
||||
self._update_all_views()
|
||||
|
||||
self._load_scenarios_into_ui()
|
||||
messagebox.showinfo("Deleted", f"Scenario '{scenario_name}' has been deleted.", parent=self)
|
||||
|
||||
def _open_settings(self):
|
||||
self.logger.info("Opening settings window.")
|
||||
SettingsWindow(self, self.communicator)
|
||||
@ -135,9 +202,18 @@ class MainView(tk.Tk):
|
||||
self.logger.info(f"Connection port set to: {self.connection_port}")
|
||||
self.status_var.set(f"Status: Configured for {port}. Ready to connect.")
|
||||
|
||||
|
||||
def _on_closing(self):
|
||||
self.logger.info("Application shutting down.")
|
||||
# Save general settings
|
||||
settings_to_save = {
|
||||
'scan_limit': self.scan_limit,
|
||||
'max_range': self.max_range,
|
||||
'connection_port': self.connection_port,
|
||||
'geometry': self.winfo_geometry(),
|
||||
'last_selected_scenario': self.current_scenario_name
|
||||
}
|
||||
self.config_manager.save_general_settings(settings_to_save)
|
||||
|
||||
if self.communicator.is_open:
|
||||
self.communicator.disconnect()
|
||||
shutdown_logging_system()
|
||||
|
||||
@ -10,6 +10,8 @@ import math
|
||||
import numpy as np
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
import matplotlib.transforms as transforms
|
||||
import matplotlib.markers as markers
|
||||
|
||||
from core.models import Target
|
||||
from typing import List
|
||||
@ -22,6 +24,7 @@ class PPIDisplay(ttk.Frame):
|
||||
super().__init__(master)
|
||||
self.max_range = max_range_nm
|
||||
self.scan_limit_deg = scan_limit_deg
|
||||
self.target_artists = []
|
||||
|
||||
self._create_controls()
|
||||
self._create_plot()
|
||||
@ -62,6 +65,20 @@ class PPIDisplay(ttk.Frame):
|
||||
self.ax.set_theta_direction(-1) # Clockwise rotation
|
||||
self.ax.set_rlabel_position(90)
|
||||
self.ax.set_ylim(0, self.max_range)
|
||||
|
||||
# Set custom theta labels for sector scan view
|
||||
angles = np.arange(0, 360, 30)
|
||||
labels = []
|
||||
for angle in angles:
|
||||
if angle == 0:
|
||||
labels.append("0°")
|
||||
elif angle < 180:
|
||||
labels.append(f"+{angle}°")
|
||||
elif angle == 180:
|
||||
labels.append("±180°")
|
||||
else: # angle > 180
|
||||
labels.append(f"-{360 - angle}°")
|
||||
self.ax.set_thetagrids(angles, labels)
|
||||
|
||||
# Style the plot
|
||||
self.ax.tick_params(axis='x', colors='white')
|
||||
@ -76,9 +93,6 @@ class PPIDisplay(ttk.Frame):
|
||||
self.ax.plot([limit_rad, limit_rad], [0, self.max_range], color='yellow', linestyle='--', linewidth=1)
|
||||
self.ax.plot([-limit_rad, -limit_rad], [0, self.max_range], color='yellow', linestyle='--', linewidth=1)
|
||||
|
||||
# Initial plot for targets (an empty plot to be updated later)
|
||||
self.target_plot, = self.ax.plot([], [], 'ro', markersize=6) # 'ro' = red circle
|
||||
|
||||
# Embed the plot in a Tkinter canvas
|
||||
self.canvas = FigureCanvasTkAgg(fig, master=self)
|
||||
self.canvas.draw()
|
||||
@ -88,6 +102,8 @@ class PPIDisplay(ttk.Frame):
|
||||
"""Handles the selection of a new range from the combobox."""
|
||||
new_range = self.range_var.get()
|
||||
self.ax.set_ylim(0, new_range)
|
||||
# Update heading vector lengths on zoom
|
||||
self.update_targets([]) # Pass empty list to redraw existing ones with new scale
|
||||
self.canvas.draw()
|
||||
|
||||
def update_targets(self, targets: List[Target]):
|
||||
@ -97,15 +113,47 @@ class PPIDisplay(ttk.Frame):
|
||||
Args:
|
||||
targets: A list of Target objects to display.
|
||||
"""
|
||||
active_targets = [t for t in targets if t.active]
|
||||
|
||||
if not active_targets:
|
||||
self.target_plot.set_data([], [])
|
||||
else:
|
||||
# Matplotlib polar plot needs angles in radians
|
||||
theta = [math.radians(t.azimuth_deg) for t in active_targets]
|
||||
r = [t.range_nm for t in active_targets]
|
||||
self.target_plot.set_data(theta, r)
|
||||
# If an empty list is passed, it means we just want to redraw existing targets
|
||||
# (e.g., after a zoom change), not clear them.
|
||||
if targets:
|
||||
self.active_targets = [t for t in targets if t.active]
|
||||
|
||||
# Clear previously drawn targets
|
||||
for artist in self.target_artists:
|
||||
artist.remove()
|
||||
self.target_artists.clear()
|
||||
|
||||
vector_len = self.range_var.get() / 25 # Length of heading vector relative to current view
|
||||
|
||||
for target in getattr(self, 'active_targets', []):
|
||||
# Target position in polar
|
||||
r1 = target.range_nm
|
||||
th1 = np.deg2rad(target.azimuth_deg)
|
||||
|
||||
# Plot the target's center dot
|
||||
dot, = self.ax.plot(th1, r1, 'o', markersize=5, color='red')
|
||||
self.target_artists.append(dot)
|
||||
|
||||
# --- Calculate and plot heading vector ---
|
||||
# Convert target polar to cartesian
|
||||
x1 = r1 * np.sin(th1)
|
||||
y1 = r1 * np.cos(th1)
|
||||
|
||||
# Calculate heading vector components
|
||||
h_rad = np.deg2rad(target.heading_deg)
|
||||
dx = vector_len * np.sin(h_rad)
|
||||
dy = vector_len * np.cos(h_rad)
|
||||
|
||||
# Calculate end point of the vector in cartesian
|
||||
x2, y2 = x1 + dx, y1 + dy
|
||||
|
||||
# Convert end point back to polar
|
||||
r2 = np.sqrt(x2**2 + y2**2)
|
||||
th2 = np.arctan2(x2, y2)
|
||||
|
||||
# Plot the heading line
|
||||
line, = self.ax.plot([th1, th2], [r1, r2], color='red', linewidth=1.2)
|
||||
self.target_artists.append(line)
|
||||
|
||||
# Redraw the canvas to show the changes
|
||||
self.canvas.draw()
|
||||
@ -1,32 +1,113 @@
|
||||
# target_simulator/gui/scenario_controls_frame.py
|
||||
|
||||
"""
|
||||
A frame containing widgets for managing scenarios (loading, saving, deleting).
|
||||
A frame containing widgets for managing scenarios.
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter import ttk, simpledialog, messagebox
|
||||
from typing import Callable, List
|
||||
|
||||
class ScenarioControlsFrame(ttk.LabelFrame):
|
||||
"""Frame for scenario management controls."""
|
||||
|
||||
def __init__(self, master):
|
||||
super().__init__(master, text="Scenario Management")
|
||||
def __init__(self, master, *,
|
||||
load_scenario_command: Callable[[str], None],
|
||||
save_as_command: Callable[[str], None],
|
||||
update_command: Callable[[str], None],
|
||||
delete_command: Callable[[str], None]):
|
||||
super().__init__(master, text="Scenario Controls")
|
||||
|
||||
self.columnconfigure(1, weight=1)
|
||||
self._load_scenario_command = load_scenario_command
|
||||
self._save_as_command = save_as_command
|
||||
self._update_command = update_command
|
||||
self._delete_command = delete_command
|
||||
|
||||
self.current_scenario = tk.StringVar()
|
||||
|
||||
# --- Widgets ---
|
||||
ttk.Label(self, text="Scenario:").grid(row=0, column=0, padx=(5, 0), pady=5, sticky=tk.W)
|
||||
top_frame = ttk.Frame(self)
|
||||
top_frame.pack(fill=tk.X, padx=5, pady=(5, 2))
|
||||
|
||||
self.scenario_combobox = ttk.Combobox(self, state="readonly")
|
||||
self.scenario_combobox.grid(row=0, column=1, padx=5, pady=5, sticky=tk.EW)
|
||||
scenario_label = ttk.Label(top_frame, text="Scenario:")
|
||||
scenario_label.pack(side=tk.LEFT, padx=(0, 5))
|
||||
|
||||
self.scenario_combobox = ttk.Combobox(
|
||||
top_frame,
|
||||
textvariable=self.current_scenario,
|
||||
state="readonly"
|
||||
)
|
||||
self.scenario_combobox.pack(side=tk.LEFT, expand=True, fill=tk.X)
|
||||
self.scenario_combobox.bind("<<ComboboxSelected>>", self._on_scenario_select)
|
||||
|
||||
# --- Buttons ---
|
||||
button_frame = ttk.Frame(self)
|
||||
button_frame.grid(row=1, column=0, columnspan=2, pady=5, sticky=tk.W)
|
||||
button_frame.pack(fill=tk.X, padx=5, pady=5)
|
||||
|
||||
self.save_button = ttk.Button(button_frame, text="Save Current...")
|
||||
self.save_button.pack(side=tk.LEFT, padx=5)
|
||||
self.save_as_button = ttk.Button(
|
||||
button_frame, text="Save As...", command=self._on_save_as
|
||||
)
|
||||
self.save_as_button.pack(side=tk.LEFT, padx=(0, 5))
|
||||
|
||||
self.delete_button = ttk.Button(button_frame, text="Delete Selected")
|
||||
self.delete_button.pack(side=tk.LEFT, padx=5)
|
||||
self.update_button = ttk.Button(
|
||||
button_frame, text="Update", command=self._on_update
|
||||
)
|
||||
self.update_button.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self.delete_button = ttk.Button(
|
||||
button_frame, text="Delete", command=self._on_delete
|
||||
)
|
||||
self.delete_button.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
def _on_scenario_select(self, event):
|
||||
"""Callback for when a scenario is selected from the combobox."""
|
||||
scenario_name = self.current_scenario.get()
|
||||
if scenario_name:
|
||||
self._load_scenario_command(scenario_name)
|
||||
|
||||
def _on_save_as(self):
|
||||
"""Handle the 'Save As' button click."""
|
||||
scenario_name = simpledialog.askstring(
|
||||
"Save Scenario As",
|
||||
"Enter a name for the new scenario:",
|
||||
parent=self
|
||||
)
|
||||
if scenario_name:
|
||||
self._save_as_command(scenario_name)
|
||||
|
||||
def _on_update(self):
|
||||
"""Handle the 'Update' button click."""
|
||||
scenario_name = self.current_scenario.get()
|
||||
if not scenario_name:
|
||||
messagebox.showwarning("No Scenario Selected", "Please select a scenario to update.", parent=self)
|
||||
return
|
||||
self._update_command(scenario_name)
|
||||
|
||||
def _on_delete(self):
|
||||
"""Handle the 'Delete' button click."""
|
||||
scenario_name = self.current_scenario.get()
|
||||
if not scenario_name:
|
||||
messagebox.showwarning("No Scenario Selected", "Please select a scenario to delete.", parent=self)
|
||||
return
|
||||
|
||||
if messagebox.askyesno(
|
||||
"Confirm Delete",
|
||||
f"Are you sure you want to delete the scenario '{scenario_name}'?",
|
||||
parent=self
|
||||
):
|
||||
self._delete_command(scenario_name)
|
||||
|
||||
def update_scenario_list(self, scenarios: List[str], select_scenario: str = None):
|
||||
"""
|
||||
Updates the list of scenarios in the combobox.
|
||||
|
||||
Args:
|
||||
scenarios: A list of scenario names.
|
||||
select_scenario: The name of the scenario to select after updating, if any.
|
||||
"""
|
||||
self.scenario_combobox['values'] = scenarios
|
||||
if select_scenario and select_scenario in scenarios:
|
||||
self.current_scenario.set(select_scenario)
|
||||
elif scenarios:
|
||||
self.current_scenario.set(scenarios[0]) # Select the first one
|
||||
else:
|
||||
self.current_scenario.set("")
|
||||
|
||||
99
target_simulator/utils/config_manager.py
Normal file
99
target_simulator/utils/config_manager.py
Normal file
@ -0,0 +1,99 @@
|
||||
# target_simulator/utils/config_manager.py
|
||||
|
||||
"""
|
||||
Manages loading and saving of application settings and scenarios to a single JSON file.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Dict, Any, Optional, List
|
||||
|
||||
class ConfigManager:
|
||||
"""Handles reading and writing application settings and scenarios from a JSON file."""
|
||||
|
||||
def __init__(self, filename: str = "settings.json"):
|
||||
"""
|
||||
Initializes the ConfigManager.
|
||||
|
||||
Args:
|
||||
filename: The name of the settings file. It will be stored in
|
||||
the project root directory.
|
||||
"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
application_path = os.path.dirname(sys.executable)
|
||||
else:
|
||||
application_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
|
||||
self.filepath = os.path.join(application_path, filename)
|
||||
self._settings = self._load_or_initialize_settings()
|
||||
|
||||
def _load_or_initialize_settings(self) -> Dict[str, Any]:
|
||||
"""Loads settings from the JSON file or initializes with a default structure."""
|
||||
if not os.path.exists(self.filepath):
|
||||
return {"general": {}, "scenarios": {}}
|
||||
try:
|
||||
with open(self.filepath, 'r', encoding='utf-8') as f:
|
||||
settings = json.load(f)
|
||||
if not isinstance(settings, dict) or "general" not in settings or "scenarios" not in settings:
|
||||
return {"general": settings, "scenarios": {}}
|
||||
return settings
|
||||
except (json.JSONDecodeError, IOError):
|
||||
return {"general": {}, "scenarios": {}}
|
||||
|
||||
def _save_settings(self):
|
||||
"""Saves the current settings to the JSON file."""
|
||||
try:
|
||||
with open(self.filepath, 'w', encoding='utf-8') as f:
|
||||
json.dump(self._settings, f, indent=4)
|
||||
except IOError as e:
|
||||
print(f"Error saving settings to {self.filepath}: {e}")
|
||||
|
||||
def get_general_settings(self) -> Dict[str, Any]:
|
||||
"""Returns the general settings."""
|
||||
return self._settings.get("general", {})
|
||||
|
||||
def save_general_settings(self, data: Dict[str, Any]):
|
||||
"""Saves the general settings."""
|
||||
self._settings["general"] = data
|
||||
self._save_settings()
|
||||
|
||||
def get_scenario_names(self) -> List[str]:
|
||||
"""Returns a list of all scenario names."""
|
||||
return list(self._settings.get("scenarios", {}).keys())
|
||||
|
||||
def get_scenario(self, name: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Retrieves a specific scenario by name.
|
||||
|
||||
Args:
|
||||
name: The name of the scenario to retrieve.
|
||||
|
||||
Returns:
|
||||
A dictionary with the scenario data, or None if not found.
|
||||
"""
|
||||
return self._settings.get("scenarios", {}).get(name)
|
||||
|
||||
def save_scenario(self, name: str, data: Dict[str, Any]):
|
||||
"""
|
||||
Saves or updates a scenario.
|
||||
|
||||
Args:
|
||||
name: The name of the scenario to save.
|
||||
data: The dictionary of scenario data to save.
|
||||
"""
|
||||
if "scenarios" not in self._settings:
|
||||
self._settings["scenarios"] = {}
|
||||
self._settings["scenarios"][name] = data
|
||||
self._save_settings()
|
||||
|
||||
def delete_scenario(self, name: str):
|
||||
"""
|
||||
Deletes a scenario by name.
|
||||
|
||||
Args:
|
||||
name: The name of the scenario to delete.
|
||||
"""
|
||||
if "scenarios" in self._settings and name in self._settings["scenarios"]:
|
||||
del self._settings["scenarios"][name]
|
||||
self._save_settings()
|
||||
Loading…
Reference in New Issue
Block a user