
Savannah Wild
by Oriflame
```python import tkinter as tk from tkinter import ttk, messagebox import json import os class CustomPeriodsManager: """ A Tkinter application to manage custom analysis periods, allowing users to add, edit, delete, and persist period definitions. """ DATA_FILE = "custom_periods.json" def __init__(self, master): self.master = master master.title("Custom Analysis Periods Manager") master.geometry("500x400") master.resizable(True, True) self.periods = self._load_periods() self.selected_period_index = None self._create_widgets() self._update_listbox() # Ensure data is saved on window close master.protocol("WM_DELETE_WINDOW", self.on_closing) def _load_periods(self): """Loads custom periods from a JSON file or initializes with defaults.""" if os.path.exists(self.DATA_FILE): try: with open(self.DATA_FILE, 'r') as f: data = json.load(f) # Basic validation for loaded data structure if isinstance(data, list) and all(isinstance(p, dict) and 'name' in p and 'duration' in p for p in data): return data except (json.JSONDecodeError, IOError): messagebox.showerror("Error", "Could not read custom periods file. Using default periods.") # Default periods if file is missing, corrupted, or invalid return [ {"name": "Week", "duration": 7}, {"name": "Fortnight", "duration": 14} ] def _save_periods(self): """Saves current custom periods to a JSON file.""" try: with open(self.DATA_FILE, 'w') as f: json.dump(self.periods, f, indent=4) except IOError: messagebox.showerror("Error", "Could not save custom periods to file.") def _create_widgets(self): """Initializes and places all GUI widgets.""" main_frame = ttk.Frame(self.master, padding="10") main_frame.pack(fill=tk.BOTH, expand=True) # Configure grid to be resizable main_frame.columnconfigure(0, weight=1) main_frame.columnconfigure(1, weight=1) main_frame.rowconfigure(0, weight=1) for i in range(1, 6): # For input fields and buttons main_frame.rowconfigure(i, weight=0) # --- Display Existing Periods --- list_frame = ttk.LabelFrame(main_frame, text="Defined Periods", padding="5") list_frame.grid(row=0, column=0, columnspan=2, sticky="nsew", pady=(0, 10)) list_frame.columnconfigure(0, weight=1) list_frame.rowconfigure(0, weight=1) self.period_listbox = tk.Listbox(list_frame, height=8, selectmode=tk.SINGLE, font=('Arial', 10)) self.period_listbox.grid(row=0, column=0, sticky="nsew") self.period_listbox.bind("<<ListboxSelect>>", self.select_period) scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=self.period_listbox.yview) scrollbar.grid(row=0, column=1, sticky="ns") self.period_listbox.config(yscrollcommand=scrollbar.set) # --- Input Fields for Add/Edit --- input_frame = ttk.LabelFrame(main_frame, text="Period Details", padding="5") input_frame.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(0, 10)) input_frame.columnconfigure(1, weight=1) # Make entry fields expand ttk.Label(input_frame, text="Name:").grid(row=0, column=0, sticky="w", pady=2) self.name_entry = ttk.Entry(input_frame) self.name_entry.grid(row=0, column=1, sticky="ew", padx=5, pady=2) ttk.Label(input_frame, text="Duration (days):").grid(row=1, column=0, sticky="w", pady=2) self.duration_entry = ttk.Entry(input_frame) self.duration_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=2) # --- Action Buttons --- button_frame = ttk.Frame(main_frame) button_frame.grid(row=2, column=0, columnspan=2, sticky="ew", pady=(0, 10)) button_frame.columnconfigure(0, weight=1) button_frame.columnconfigure(1, weight=1) button_frame.columnconfigure(2, weight=1) ttk.Button(button_frame, text="Add New Period", command=self.add_period).grid(row=0, column=0, padx=5, sticky="ew") ttk.Button(button_frame, text="Edit Selected", command=self.edit_period).grid(row=0, column=1, padx=5, sticky="ew") ttk.Button(button_frame, text="Delete Selected", command=self.delete_period).grid(row=0, column=2, padx=5, sticky="ew") # --- Status Label --- self.status_label = ttk.Label(main_frame, text="", foreground="red") self.status_label.grid(row=3, column=0, columnspan=2, sticky="ew", pady=(0, 5)) def _update_listbox(self): """Refreshes the Listbox with current period data.""" self.period_listbox.delete(0, tk.END) for i, period in enumerate(self.periods): self.period_listbox.insert(tk.END, f"{period['name']}: {period['duration']} days") self._clear_entry_fields() self.selected_period_index = None self.status_label.config(text="") def _clear_entry_fields(self): """Clears the name and duration input fields.""" self.name_entry.delete(0, tk.END) self.duration_entry.delete(0, tk.END) def _validate_input(self, name, duration_str): """ Validates the name and duration inputs. Returns (True, int_duration) on success, (False, error_message) on failure. """ if not name.strip(): return False, "Period name cannot be empty." try: duration = int(duration_str) if duration <= 0: return False, "Duration must be a positive integer." return True, duration except ValueError: return False, "Duration must be a valid integer." def select_period(self, event=None): """ Handles selection of a period in the Listbox, pre-filling input fields for editing. """ selected_indices = self.period_listbox.curselection() if selected_indices: self.selected_period_index = selected_indices[0] period = self.periods[self.selected_period_index] self._clear_entry_fields() self.name_entry.insert(0, period['name']) self.duration_entry.insert(0, str(period['duration'])) self.status_label.config(text="") else: self.selected_period_index = None self._clear_entry_fields() def add_period(self): """Adds a new custom analysis period.""" name = self.name_entry.get() duration_str = self.duration_entry.get() is_valid, result = self._validate_input(name, duration_str) if not is_valid: self.status_label.config(text=result, foreground="red") return new_period = {"name": name.strip(), "duration": result} self.periods.append(new_period) self._save_periods() self._update_listbox() self.status_label.config(text=f"'{name.strip()}' added successfully.", foreground="green") def edit_period(self): """Edits the currently selected custom analysis period.""" if self.selected_period_index is None: self.status_label.config(text="Please select a period to edit.", foreground="red") return name = self.name_entry.get() duration_str = self.duration_entry.get() is_valid, result = self._validate_input(name, duration_str) if not is_valid: self.status_label.config(text=result, foreground="red") return self.periods[self.selected_period_index]['name'] = name.strip() self.periods[self.selected_period_index]['duration'] = result self._save_periods() self._update_listbox() self.status_label.config(text=f"Period updated successfully.", foreground="green") def delete_period(self): """Deletes the currently selected custom analysis period.""" if self.selected_period_index is None: self.status_label.config(text="Please select a period to delete.", foreground="red") return period_name = self.periods[self.selected_period_index]['name'] if messagebox.askyesno("Confirm Delete", f"Are you sure you want to delete '{period_name}'?"): del self.periods[self.selected_period_index] self._save_periods() self._update_listbox() self.status_label.config(text=f"'{period_name}' deleted successfully.", foreground="green") else: self.status_label.config(text="Deletion cancelled.", foreground="blue") def on_closing(self): """Handles operations when the window is closed.""" self._save_periods() # Ensure data is saved on explicit close self.master.destroy() if __name__ == "__main__": root = tk.Tk() app = CustomPeriodsManager(root) root.mainloop() ```





















