300 lines
11 KiB
Python
300 lines
11 KiB
Python
# File: cpp_python_debug/gui/action_editor_window.py
|
|
# Provides a Toplevel window for editing a single debug action.
|
|
|
|
import tkinter as tk
|
|
from tkinter import ttk, scrolledtext, messagebox
|
|
import logging
|
|
from typing import Dict, Any, Optional, List
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ActionEditorWindow(tk.Toplevel):
|
|
"""
|
|
A modal dialog for creating or editing a single debug action for a profile.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
parent: tk.Widget,
|
|
action_data: Optional[Dict[str, Any]] = None,
|
|
is_new: bool = True,
|
|
):
|
|
super().__init__(parent)
|
|
self.parent_window = parent
|
|
self.is_new_action = is_new
|
|
self.result: Optional[Dict[str, Any]] = None # To store the action data on OK
|
|
|
|
title = "Add New Action" if self.is_new_action else "Edit Action"
|
|
self.title(title)
|
|
self.geometry("600x550") # Adjusted size
|
|
self.resizable(False, False)
|
|
|
|
self.transient(parent)
|
|
self.grab_set() # Make modal
|
|
|
|
# --- StringVars and other Tkinter variables for action fields ---
|
|
self.breakpoint_var = tk.StringVar()
|
|
# variables_to_dump will be handled by ScrolledText
|
|
self.output_format_var = tk.StringVar()
|
|
self.output_directory_var = tk.StringVar()
|
|
self.filename_pattern_var = tk.StringVar()
|
|
self.continue_after_dump_var = tk.BooleanVar()
|
|
# self.max_hits_var = tk.StringVar() # For later
|
|
|
|
self._initial_action_data = (
|
|
action_data.copy() if action_data else None
|
|
) # Store original for cancel/comparison
|
|
|
|
self._create_widgets()
|
|
self._load_action_data(action_data)
|
|
|
|
self.protocol("WM_DELETE_WINDOW", self._on_cancel)
|
|
self.wait_window() # Important for modal dialog behavior to get result
|
|
|
|
def _create_widgets(self) -> None:
|
|
main_frame = ttk.Frame(self, padding="15")
|
|
main_frame.pack(expand=True, fill=tk.BOTH)
|
|
main_frame.columnconfigure(1, weight=1) # Make entry fields expand
|
|
|
|
row_idx = 0
|
|
|
|
# Breakpoint Location
|
|
ttk.Label(main_frame, text="Breakpoint Location:").grid(
|
|
row=row_idx, column=0, sticky=tk.W, padx=5, pady=5
|
|
)
|
|
ttk.Entry(main_frame, textvariable=self.breakpoint_var, width=60).grid(
|
|
row=row_idx, column=1, columnspan=2, sticky="ew", padx=5, pady=5
|
|
)
|
|
row_idx += 1
|
|
|
|
# MODIFIED LINE:
|
|
bp_help_label = ttk.Label(
|
|
main_frame,
|
|
text="(e.g., main, file.cpp:123, MyClass::foo)",
|
|
foreground="gray",
|
|
)
|
|
bp_help_label.grid(
|
|
row=row_idx, column=1, columnspan=2, sticky=tk.W, padx=7, pady=(0, 10)
|
|
)
|
|
row_idx += 1
|
|
|
|
# Variables to Dump
|
|
ttk.Label(main_frame, text="Variables to Dump:").grid(
|
|
row=row_idx, column=0, sticky="nw", padx=5, pady=5
|
|
) # nw for top-alignment
|
|
self.variables_text = scrolledtext.ScrolledText(
|
|
main_frame, wrap=tk.WORD, height=5, width=58, font=("Consolas", 9)
|
|
)
|
|
self.variables_text.grid(
|
|
row=row_idx, column=1, columnspan=2, sticky="ew", padx=5, pady=5
|
|
)
|
|
row_idx += 1
|
|
|
|
# MODIFIED LINE:
|
|
vars_help_label = ttk.Label(
|
|
main_frame, text="(One variable/expression per line)", foreground="gray"
|
|
)
|
|
vars_help_label.grid(
|
|
row=row_idx, column=1, columnspan=2, sticky=tk.W, padx=7, pady=(0, 10)
|
|
)
|
|
row_idx += 1
|
|
|
|
# Output Format
|
|
ttk.Label(main_frame, text="Output Format:").grid(
|
|
row=row_idx, column=0, sticky=tk.W, padx=5, pady=5
|
|
)
|
|
self.output_format_combo = ttk.Combobox(
|
|
main_frame,
|
|
textvariable=self.output_format_var,
|
|
values=["json", "csv"],
|
|
state="readonly",
|
|
width=10,
|
|
)
|
|
self.output_format_combo.grid(row=row_idx, column=1, sticky="w", padx=5, pady=5)
|
|
row_idx += 1
|
|
|
|
# Output Directory
|
|
ttk.Label(main_frame, text="Output Directory:").grid(
|
|
row=row_idx, column=0, sticky=tk.W, padx=5, pady=5
|
|
)
|
|
ttk.Entry(main_frame, textvariable=self.output_directory_var, width=50).grid(
|
|
row=row_idx, column=1, sticky="ew", padx=5, pady=5
|
|
)
|
|
ttk.Button(main_frame, text="Browse...", command=self._browse_output_dir).grid(
|
|
row=row_idx, column=2, padx=5, pady=5
|
|
)
|
|
row_idx += 1
|
|
|
|
# Filename Pattern
|
|
ttk.Label(main_frame, text="Filename Pattern:").grid(
|
|
row=row_idx, column=0, sticky=tk.W, padx=5, pady=5
|
|
)
|
|
ttk.Entry(main_frame, textvariable=self.filename_pattern_var, width=60).grid(
|
|
row=row_idx, column=1, columnspan=2, sticky="ew", padx=5, pady=5
|
|
)
|
|
row_idx += 1
|
|
|
|
# MODIFIED LINE:
|
|
pattern_help_label = ttk.Label(
|
|
main_frame,
|
|
text="(Placeholders: {profile_name}, {breakpoint}, {variable}, {timestamp}, {format})",
|
|
foreground="gray",
|
|
)
|
|
pattern_help_label.grid(
|
|
row=row_idx, column=1, columnspan=2, sticky=tk.W, padx=7, pady=(0, 10)
|
|
)
|
|
row_idx += 1
|
|
|
|
# Continue After Dump
|
|
self.continue_check = ttk.Checkbutton(
|
|
main_frame,
|
|
text="Continue execution after dump",
|
|
variable=self.continue_after_dump_var,
|
|
)
|
|
self.continue_check.grid(
|
|
row=row_idx, column=0, columnspan=3, sticky="w", padx=5, pady=10
|
|
)
|
|
row_idx += 1
|
|
|
|
# --- Buttons Frame ---
|
|
buttons_frame = ttk.Frame(main_frame)
|
|
buttons_frame.grid(
|
|
row=row_idx, column=0, columnspan=3, pady=(15, 5), sticky="e"
|
|
)
|
|
|
|
ttk.Button(buttons_frame, text="OK", command=self._on_ok, width=10).pack(
|
|
side=tk.RIGHT, padx=5
|
|
)
|
|
ttk.Button(
|
|
buttons_frame, text="Cancel", command=self._on_cancel, width=10
|
|
).pack(side=tk.RIGHT, padx=5)
|
|
|
|
def _load_action_data(self, action_data: Optional[Dict[str, Any]]) -> None:
|
|
"""Loads action data into the form fields."""
|
|
from ..gui.profile_manager_window import (
|
|
DEFAULT_ACTION,
|
|
) # Lazy import for default
|
|
|
|
data_to_load = action_data if action_data else DEFAULT_ACTION.copy()
|
|
|
|
self.breakpoint_var.set(
|
|
data_to_load.get(
|
|
"breakpoint_location", DEFAULT_ACTION["breakpoint_location"]
|
|
)
|
|
)
|
|
|
|
variables_list = data_to_load.get(
|
|
"variables_to_dump", DEFAULT_ACTION["variables_to_dump"]
|
|
)
|
|
if isinstance(variables_list, list):
|
|
self.variables_text.insert(tk.END, "\n".join(variables_list))
|
|
else: # Fallback if it's not a list for some reason
|
|
self.variables_text.insert(tk.END, str(variables_list))
|
|
|
|
self.output_format_var.set(
|
|
data_to_load.get("output_format", DEFAULT_ACTION["output_format"])
|
|
)
|
|
self.output_directory_var.set(
|
|
data_to_load.get("output_directory", DEFAULT_ACTION["output_directory"])
|
|
)
|
|
self.filename_pattern_var.set(
|
|
data_to_load.get("filename_pattern", DEFAULT_ACTION["filename_pattern"])
|
|
)
|
|
self.continue_after_dump_var.set(
|
|
data_to_load.get(
|
|
"continue_after_dump", DEFAULT_ACTION["continue_after_dump"]
|
|
)
|
|
)
|
|
|
|
def _browse_output_dir(self) -> None:
|
|
current_path = self.output_directory_var.get()
|
|
initial_dir = (
|
|
current_path if current_path and os.path.isdir(current_path) else None
|
|
)
|
|
path = filedialog.askdirectory(
|
|
title="Select Output Directory for Dumps",
|
|
initialdir=initial_dir,
|
|
parent=self, # Ensure dialog is on top
|
|
)
|
|
if path:
|
|
self.output_directory_var.set(path)
|
|
|
|
def _validate_data(self) -> bool:
|
|
"""Validates the current form data."""
|
|
if not self.breakpoint_var.get().strip():
|
|
messagebox.showerror(
|
|
"Validation Error", "Breakpoint Location cannot be empty.", parent=self
|
|
)
|
|
return False
|
|
|
|
variables_str = self.variables_text.get("1.0", tk.END).strip()
|
|
if not variables_str:
|
|
messagebox.showerror(
|
|
"Validation Error", "Variables to Dump cannot be empty.", parent=self
|
|
)
|
|
return False
|
|
|
|
if not self.output_directory_var.get().strip():
|
|
messagebox.showerror(
|
|
"Validation Error", "Output Directory cannot be empty.", parent=self
|
|
)
|
|
return False
|
|
|
|
if not self.filename_pattern_var.get().strip():
|
|
messagebox.showerror(
|
|
"Validation Error", "Filename Pattern cannot be empty.", parent=self
|
|
)
|
|
return False
|
|
# Basic check for common placeholders
|
|
if not any(
|
|
p in self.filename_pattern_var.get()
|
|
for p in ["{breakpoint}", "{variable}", "{timestamp}"]
|
|
):
|
|
messagebox.showwarning(
|
|
"Validation Warning",
|
|
"Filename Pattern seems to be missing common placeholders like {breakpoint}, {variable}, or {timestamp}. This might lead to overwritten files.",
|
|
parent=self,
|
|
)
|
|
|
|
return True
|
|
|
|
def _on_ok(self) -> None:
|
|
"""Handles the OK button click."""
|
|
if not self._validate_data():
|
|
return
|
|
|
|
variables_list = [
|
|
line.strip()
|
|
for line in self.variables_text.get("1.0", tk.END).strip().splitlines()
|
|
if line.strip()
|
|
]
|
|
if not variables_list: # Double check after stripping
|
|
messagebox.showerror(
|
|
"Validation Error",
|
|
"Variables to Dump cannot be empty after processing.",
|
|
parent=self,
|
|
)
|
|
return
|
|
|
|
self.result = {
|
|
"breakpoint_location": self.breakpoint_var.get().strip(),
|
|
"variables_to_dump": variables_list,
|
|
"output_format": self.output_format_var.get(),
|
|
"output_directory": self.output_directory_var.get().strip(),
|
|
"filename_pattern": self.filename_pattern_var.get().strip(),
|
|
"continue_after_dump": self.continue_after_dump_var.get(),
|
|
}
|
|
# logger.debug(f"ActionEditorWindow result on OK: {self.result}")
|
|
self.destroy()
|
|
|
|
def _on_cancel(self) -> None:
|
|
"""Handles the Cancel button click or window close."""
|
|
self.result = None # Explicitly set result to None on cancel
|
|
# logger.debug("ActionEditorWindow cancelled.")
|
|
self.destroy()
|
|
|
|
# Public method to get the result after the dialog closes
|
|
def get_result(self) -> Optional[Dict[str, Any]]:
|
|
return self.result
|