SXXXXXXX_CppPythonDebug/cpp_python_debug/gui/profile_manager_window.py
2025-09-25 08:49:13 +02:00

1501 lines
63 KiB
Python

# File: cpp_python_debug/gui/profile_manager_window.py
import tkinter as tk
from tkinter import (
ttk,
messagebox,
filedialog,
) # filedialog non sembra usato qui, ma lascio
import logging
import json # Mantenuto per deep copy e potenziale debug
import os
import threading
import time # Non usato direttamente, ma potrebbe servire in futuro per ritardi
from typing import (
TYPE_CHECKING,
List,
Dict,
Any,
Optional,
Callable,
Union,
Tuple,
) # Aggiunto Tuple
# MODIFICA: Import assoluti
from cpp_python_debug.gui.dialogs import (
SymbolAnalysisProgressDialog,
SymbolListViewerDialog,
FunctionSelectorDialog,
)
from cpp_python_debug.core import file_utils # Per calculate_file_checksum
from cpp_python_debug.core.symbol_analyzer import SymbolAnalyzer
from cpp_python_debug.core.gdb_mi_session import GDBMISession
from cpp_python_debug.gui.action_editor_window import ActionEditorWindow
if TYPE_CHECKING:
from cpp_python_debug.core.config_manager import AppSettings
# Evita import diretto di GDBGui per prevenire potenziale import circolare stretto se GDBGui importasse ProfileManagerWindow
# Useremo 'tk.Widget' o 'Any' per il parent se GDBGui non è strettamente necessario per type checking qui.
# In questo caso, GDBGui è il parent di ProfileManagerWindow, quindi il type hint è utile.
from cpp_python_debug.gui.main_window import GDBGui
logger = logging.getLogger(__name__) # Logger specifico per questo modulo
# Default per una nuova azione, copiato da ActionEditorWindow per coerenza locale
# Idealmente, questo potrebbe essere in un modulo di costanti condiviso.
DEFAULT_ACTION_CONFIG = {
"breakpoint_location": "main",
"variables_to_dump": ["my_variable"],
"output_format": "json",
"output_directory": "./gdb_dumps", # Default generico, potrebbe essere sovrascritto da AppSettings
"filename_pattern": "{profile_name}_{app_name}_{breakpoint}_{variable}_{timestamp}.{format}",
"continue_after_dump": True,
"dump_on_every_hit": True,
}
DEFAULT_PROFILE_CONFIG = {
"profile_name": "New Profile",
"target_executable": "",
"program_parameters": "",
"symbol_analysis": None, # Inizializzato a None
"actions": [DEFAULT_ACTION_CONFIG.copy()], # Inizia con una azione di default
}
class ProfileManagerWindow(tk.Toplevel):
# (Costruttore e metodi come prima, con correzioni agli import e type hints)
def __init__(
self, parent: "GDBGui", app_settings: "AppSettings"
): # Parent è GDBGui
super().__init__(parent)
self.parent_window = parent # Riferimento al parent (GDBGui)
self.app_settings: "AppSettings" = app_settings
self.title("Profile Manager")
# La geometria potrebbe essere salvata/caricata da AppSettings se desiderato
# default_geometry = self.app_settings.get_setting("gui", "profile_manager_geometry", "1050x750")
# self.geometry(default_geometry)
# Per ora, usiamo una dimensione fissa come nel tuo codice originale
window_width = 1050
window_height = 850
self.parent_window.update_idletasks()
parent_x = self.parent_window.winfo_x()
parent_y = self.parent_window.winfo_y()
parent_width = self.parent_window.winfo_width()
parent_height = self.parent_window.winfo_height()
position_x = max(0, parent_x + (parent_width // 2) - (window_width // 2))
position_y = max(0, parent_y + (parent_height // 2) - (window_height // 2))
self.geometry(f"{window_width}x{window_height}+{position_x}+{position_y}")
self.transient(parent)
self.grab_set()
self._profiles_data: List[Dict[str, Any]] = []
self._selected_profile_index: Optional[int] = None
self._selected_action_index_in_profile: Optional[int] = None
self._current_profile_modified_in_form: bool = False
self._profiles_list_changed_overall: bool = (
False # Traccia se la lista profili è cambiata
)
# Variabili Tkinter per il form del profilo
self.profile_name_var = tk.StringVar()
self.target_exe_var = tk.StringVar()
self.program_params_var = tk.StringVar()
# Variabili Tkinter per lo stato dell'analisi simboli
self._current_profile_target_exe_details_label_var = tk.StringVar(
value="Target Executable: N/A"
)
self._current_profile_analysis_status_label_var = tk.StringVar(
value="Symbol Analysis: Not Performed"
)
self.progress_dialog: Optional[SymbolAnalysisProgressDialog] = None
# Variabili Tkinter per i conteggi dei simboli
self.functions_count_var = tk.StringVar(value="Functions: N/A")
self.variables_count_var = tk.StringVar(value="Globals: N/A")
self.types_count_var = tk.StringVar(value="Types: N/A")
self.sources_count_var = tk.StringVar(value="Sources: N/A")
self._load_profiles_from_settings()
self._create_widgets()
self._populate_profiles_listbox() # Popola la listbox con i profili caricati
if self._profiles_data: # Se ci sono profili, seleziona il primo
self._select_profile_by_index(0)
else: # Altrimenti, aggiorna lo stato per riflettere nessun profilo selezionato
self._update_analysis_status_display() # Questo dovrebbe gestire il caso "N/A"
self.protocol("WM_DELETE_WINDOW", self._on_closing_button)
# Aggiungi trace per marcare modifiche nel form
self.profile_name_var.trace_add("write", self._mark_form_as_modified)
self.target_exe_var.trace_add(
"write", lambda *args: self._on_target_exe_changed_in_form()
)
self.program_params_var.trace_add("write", self._mark_form_as_modified)
# self.wait_window() # Rimosso, gestito da GDBGui
def _mark_form_as_modified(self, *args):
# (Implementazione come prima)
self._current_profile_modified_in_form = True
def _on_target_exe_changed_in_form(self, *args):
# (Implementazione come prima)
self._mark_form_as_modified(*args)
self._update_analysis_status_display() # Aggiorna lo stato dell'analisi quando il target cambia
def _load_profiles_from_settings(self) -> None:
# (Implementazione come prima, ma con logging e deep copy più espliciti)
self._profiles_data = [] # Resetta la lista interna
loaded_profiles_from_settings = (
self.app_settings.get_profiles()
) # Questo dovrebbe già restituire una copia
for profile_dict_original in loaded_profiles_from_settings:
# Crea una deep copy per evitare modifiche all'oggetto in AppSettings
# fino a un salvataggio esplicito. json.loads(json.dumps(...)) è un modo semplice per una deep copy.
try:
copied_profile = json.loads(json.dumps(profile_dict_original))
except (TypeError, json.JSONDecodeError) as e:
logger.error(
f"Could not deep copy profile, skipping: {profile_dict_original}. Error: {e}"
)
continue
# Assicura che le chiavi essenziali esistano e siano del tipo corretto
if "actions" not in copied_profile or not isinstance(
copied_profile["actions"], list
):
copied_profile["actions"] = []
if (
"symbol_analysis" not in copied_profile
): # Può essere None se non ancora analizzato
copied_profile["symbol_analysis"] = None
# Assicura che ogni azione sia un dizionario
copied_profile["actions"] = [
action
for action in copied_profile["actions"]
if isinstance(action, dict)
]
self._profiles_data.append(copied_profile)
self._profiles_list_changed_overall = (
False # Flag resettato dopo il caricamento iniziale
)
logger.debug(
f"Loaded {len(self._profiles_data)} profiles into ProfileManagerWindow from AppSettings."
)
def _create_widgets(self) -> None:
# (Implementazione come prima, ma con parent=self per dialoghi se necessario,
# e assicurandosi che i type hint per i callback siano corretti)
main_frame = ttk.Frame(self, padding="10")
main_frame.pack(expand=True, fill=tk.BOTH)
main_frame.columnconfigure(
0, weight=1, minsize=250
) # Pane sinistro (lista profili)
main_frame.columnconfigure(1, weight=3) # Pane destro (dettagli profilo)
main_frame.rowconfigure(0, weight=1) # Riga principale per i due pani
main_frame.rowconfigure(1, weight=0) # Riga per i bottoni in basso
# --- Left Pane: Profiles List and Controls ---
left_pane = ttk.Frame(main_frame)
left_pane.grid(row=0, column=0, sticky="nsew", padx=(0, 10))
left_pane.rowconfigure(0, weight=1) # Listbox si espande
left_pane.rowconfigure(1, weight=0) # Controlli listbox fissi
left_pane.columnconfigure(0, weight=1)
profiles_list_frame = ttk.LabelFrame(left_pane, text="Profiles", padding="5")
profiles_list_frame.grid(row=0, column=0, sticky="nsew", pady=(0, 5))
profiles_list_frame.rowconfigure(0, weight=1)
profiles_list_frame.columnconfigure(0, weight=1)
self.profiles_listbox = tk.Listbox(
profiles_list_frame, exportselection=False, selectmode=tk.SINGLE, width=30
)
self.profiles_listbox.grid(row=0, column=0, sticky="nsew")
self.profiles_listbox.bind("<<ListboxSelect>>", self._on_profile_select)
listbox_scrollbar_y = ttk.Scrollbar(
profiles_list_frame, orient=tk.VERTICAL, command=self.profiles_listbox.yview
)
listbox_scrollbar_y.grid(row=0, column=1, sticky="ns")
self.profiles_listbox.configure(yscrollcommand=listbox_scrollbar_y.set)
profiles_list_controls_frame = ttk.Frame(left_pane) # Non un LabelFrame
profiles_list_controls_frame.grid(row=1, column=0, sticky="ew", pady=5)
button_width = 10
ttk.Button(
profiles_list_controls_frame,
text="New",
command=self._new_profile,
width=button_width,
).pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True)
self.duplicate_button = ttk.Button(
profiles_list_controls_frame,
text="Duplicate",
command=self._duplicate_profile,
state=tk.DISABLED,
width=button_width,
)
self.duplicate_button.pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True)
self.delete_button = ttk.Button(
profiles_list_controls_frame,
text="Delete",
command=self._delete_profile,
state=tk.DISABLED,
width=button_width,
)
self.delete_button.pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True)
# --- Right Pane: Profile Details, Analysis, Actions ---
right_pane = ttk.Frame(main_frame)
right_pane.grid(row=0, column=1, sticky="nsew")
right_pane.rowconfigure(0, weight=0) # Details Form
right_pane.rowconfigure(1, weight=0) # Analysis Control
right_pane.rowconfigure(2, weight=0) # Symbols Summary
right_pane.rowconfigure(3, weight=1) # Actions UI (espandibile)
right_pane.columnconfigure(0, weight=1)
# Profile Details Form
details_form_frame = ttk.LabelFrame(
right_pane, text="Profile Details", padding="10"
)
details_form_frame.grid(
row=0, column=0, sticky="new", pady=(0, 5)
) # new = North, East, West
details_form_frame.columnconfigure(
1, weight=1
) # Colonna degli entry si espande
ttk.Label(details_form_frame, text="Profile Name:").grid(
row=0, column=0, sticky=tk.W, padx=5, pady=3
)
self.profile_name_entry = ttk.Entry(
details_form_frame, textvariable=self.profile_name_var, state=tk.DISABLED
)
self.profile_name_entry.grid(
row=0, column=1, columnspan=2, sticky="ew", padx=5, pady=3
)
ttk.Label(details_form_frame, text="Target Executable:").grid(
row=1, column=0, sticky=tk.W, padx=5, pady=3
)
self.target_exe_entry = ttk.Entry(
details_form_frame, textvariable=self.target_exe_var, state=tk.DISABLED
)
self.target_exe_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=3)
self.browse_exe_button = ttk.Button(
details_form_frame,
text="Browse...",
command=self._browse_target_executable,
state=tk.DISABLED,
)
self.browse_exe_button.grid(row=1, column=2, padx=5, pady=3)
ttk.Label(details_form_frame, text="Program Parameters:").grid(
row=2, column=0, sticky=tk.W, padx=5, pady=3
)
self.program_params_entry = ttk.Entry(
details_form_frame, textvariable=self.program_params_var, state=tk.DISABLED
)
self.program_params_entry.grid(
row=2, column=1, columnspan=2, sticky="ew", padx=5, pady=3
)
# Symbol Analysis Control
analysis_control_frame = ttk.LabelFrame(
right_pane, text="Symbol Analysis Status & Control", padding="10"
)
analysis_control_frame.grid(row=1, column=0, sticky="new", pady=5)
analysis_control_frame.columnconfigure(0, weight=1) # Label di stato si espande
analysis_control_frame.columnconfigure(1, weight=0) # Bottone fisso
self.target_exe_details_label = ttk.Label(
analysis_control_frame,
textvariable=self._current_profile_target_exe_details_label_var,
wraplength=500,
justify=tk.LEFT,
) # wraplength per testo lungo
self.target_exe_details_label.grid(
row=0, column=0, columnspan=2, sticky="ew", padx=5, pady=(2, 5)
)
self.analysis_status_label = ttk.Label(
analysis_control_frame,
textvariable=self._current_profile_analysis_status_label_var,
foreground="blue",
wraplength=350,
)
self.analysis_status_label.grid(row=1, column=0, sticky="w", padx=5, pady=2)
self.analyse_symbols_button = ttk.Button(
analysis_control_frame,
text="Analyse Target Symbols",
command=self._trigger_symbol_analysis,
state=tk.DISABLED,
)
self.analyse_symbols_button.grid(row=1, column=1, sticky="e", padx=5, pady=2)
# Analyzed Symbols Summary
symbols_summary_frame = ttk.LabelFrame(
right_pane, text="Analyzed Symbols Summary", padding="10"
)
symbols_summary_frame.grid(row=2, column=0, sticky="new", pady=5)
symbols_summary_frame.columnconfigure(0, weight=1) # Label si espande
symbols_summary_frame.columnconfigure(1, weight=0) # Bottone fisso
row_s = 0
ttk.Label(symbols_summary_frame, textvariable=self.functions_count_var).grid(
row=row_s, column=0, sticky="w", padx=5, pady=2
)
self.view_functions_button = ttk.Button(
symbols_summary_frame,
text="View...",
command=self._view_analyzed_functions,
state=tk.DISABLED,
width=8,
)
self.view_functions_button.grid(
row=row_s, column=1, padx=(10, 5), pady=2, sticky="w"
)
row_s += 1
ttk.Label(symbols_summary_frame, textvariable=self.variables_count_var).grid(
row=row_s, column=0, sticky="w", padx=5, pady=2
)
self.view_variables_button = ttk.Button(
symbols_summary_frame,
text="View...",
command=self._view_analyzed_variables,
state=tk.DISABLED,
width=8,
)
self.view_variables_button.grid(
row=row_s, column=1, padx=(10, 5), pady=2, sticky="w"
)
row_s += 1
ttk.Label(symbols_summary_frame, textvariable=self.types_count_var).grid(
row=row_s, column=0, sticky="w", padx=5, pady=2
)
self.view_types_button = ttk.Button(
symbols_summary_frame,
text="View...",
command=self._view_analyzed_types,
state=tk.DISABLED,
width=8,
)
self.view_types_button.grid(
row=row_s, column=1, padx=(10, 5), pady=2, sticky="w"
)
row_s += 1
ttk.Label(symbols_summary_frame, textvariable=self.sources_count_var).grid(
row=row_s, column=0, sticky="w", padx=5, pady=2
)
self.view_sources_button = ttk.Button(
symbols_summary_frame,
text="View...",
command=self._view_analyzed_sources,
state=tk.DISABLED,
width=8,
)
self.view_sources_button.grid(
row=row_s, column=1, padx=(10, 5), pady=2, sticky="w"
)
# row_s += 1 # Non necessario se è l'ultimo
# Debug Actions UI
actions_ui_frame = ttk.LabelFrame(
right_pane, text="Debug Actions", padding="10"
)
actions_ui_frame.grid(
row=3, column=0, sticky="nsew", pady=5
) # Modificato da row=4 a row=3
actions_ui_frame.rowconfigure(0, weight=1) # Listbox si espande
actions_ui_frame.columnconfigure(0, weight=1) # Listbox si espande
actions_ui_frame.columnconfigure(1, weight=0) # Scrollbar
actions_ui_frame.columnconfigure(2, weight=0) # Bottoni laterali
self.actions_listbox = tk.Listbox(
actions_ui_frame, exportselection=False, selectmode=tk.SINGLE, height=6
)
self.actions_listbox.grid(
row=0, column=0, sticky="nsew", pady=5, padx=(0, 5)
) # padx a dx per scrollbar
self.actions_listbox.bind(
"<<ListboxSelect>>", self._on_action_select_in_listbox
)
actions_listbox_scrolly = ttk.Scrollbar(
actions_ui_frame, orient=tk.VERTICAL, command=self.actions_listbox.yview
)
actions_listbox_scrolly.grid(
row=0, column=1, sticky="ns", pady=5
) # pady=5 per allineare
self.actions_listbox.configure(yscrollcommand=actions_listbox_scrolly.set)
action_buttons_frame = ttk.Frame(actions_ui_frame) # Non LabelFrame
action_buttons_frame.grid(
row=0, column=2, sticky="ns", padx=(5, 0), pady=5
) # Allineato con listbox
action_btn_width = 8
self.add_action_button = ttk.Button(
action_buttons_frame,
text="Add...",
command=self._add_action,
state=tk.DISABLED,
width=action_btn_width,
)
self.add_action_button.pack(fill=tk.X, pady=2, anchor="n")
self.edit_action_button = ttk.Button(
action_buttons_frame,
text="Edit...",
command=self._edit_action,
state=tk.DISABLED,
width=action_btn_width,
)
self.edit_action_button.pack(fill=tk.X, pady=2, anchor="n")
self.remove_action_button = ttk.Button(
action_buttons_frame,
text="Remove",
command=self._remove_action,
state=tk.DISABLED,
width=action_btn_width,
)
self.remove_action_button.pack(fill=tk.X, pady=2, anchor="n")
# --- Bottom Buttons (Save All, Close) ---
bottom_buttons_frame = ttk.Frame(main_frame)
bottom_buttons_frame.grid(
row=1, column=0, columnspan=2, sticky="sew", pady=(10, 0)
) # columnspan=2 per coprire entrambi i pani
# Frame interno per allineare i bottoni a destra
bottom_buttons_inner_frame = ttk.Frame(bottom_buttons_frame)
bottom_buttons_inner_frame.pack(
side=tk.RIGHT
) # Allinea il frame dei bottoni a destra
self.save_button = ttk.Button(
bottom_buttons_inner_frame,
text="Save All Changes",
command=self._save_all_profiles_to_settings,
)
self.save_button.pack(
side=tk.LEFT, padx=5, pady=5
) # Usare LEFT per ordine corretto se il frame è pack(RIGHT)
ttk.Button(
bottom_buttons_inner_frame, text="Close", command=self._on_closing_button
).pack(side=tk.LEFT, padx=5, pady=5)
def _populate_profiles_listbox(self) -> None:
# (Implementazione come prima)
self.profiles_listbox.delete(0, tk.END)
for i, profile in enumerate(self._profiles_data):
display_name = profile.get("profile_name", f"Unnamed Profile {i+1}")
self.profiles_listbox.insert(tk.END, display_name)
self._update_profile_action_buttons_state() # Aggiorna stato bottoni New/Dup/Del
self._update_action_buttons_state() # Aggiorna stato bottoni Add/Edit/Remove Action
def _on_profile_select(self, event: Optional[tk.Event] = None) -> None:
# (Implementazione come prima)
selection_indices = self.profiles_listbox.curselection()
new_selected_index: Optional[int] = None
if selection_indices:
new_selected_index = selection_indices[0]
if (
self._selected_profile_index is not None
and self._selected_profile_index != new_selected_index
and self._current_profile_modified_in_form
):
current_profile_name_in_form = self.profile_name_var.get()
if (
not current_profile_name_in_form
and self._profiles_data
and 0 <= self._selected_profile_index < len(self._profiles_data)
):
current_profile_name_in_form = self._profiles_data[
self._selected_profile_index
].get("profile_name", "the previously selected profile")
prompt = (
f"Profile '{current_profile_name_in_form}' has unsaved changes in the form.\n"
"Do you want to apply these changes before switching?"
)
response = messagebox.askyesnocancel(
"Unsaved Changes", prompt, parent=self, default=messagebox.CANCEL
)
if response is True: # Yes, save
if not self._save_current_form_to_profile_data(
self._selected_profile_index
):
# Save failed, revert selection in listbox
self.profiles_listbox.selection_clear(0, tk.END)
if (
self._selected_profile_index is not None
): # Check again as it might have been cleared
self.profiles_listbox.selection_set(
self._selected_profile_index
)
self.profiles_listbox.activate(self._selected_profile_index)
return # Abort switch
elif response is None: # Cancelled switch
# Revert selection in listbox to the previously selected one
self.profiles_listbox.selection_clear(0, tk.END)
if self._selected_profile_index is not None:
self.profiles_listbox.selection_set(self._selected_profile_index)
self.profiles_listbox.activate(self._selected_profile_index)
return # Abort switch
# If response is False (No, discard), proceed with selection change
# Proceed with selection change
if new_selected_index is not None:
self._select_profile_by_index(new_selected_index)
else: # No selection or selection cleared
self._clear_profile_form()
self._selected_profile_index = None # Explicitly set to None
self._update_profile_action_buttons_state()
self._update_analysis_status_display() # Aggiorna anche qui
def _get_data_from_form(
self,
) -> Dict[str, Any]: # Non usata direttamente, ma utile per coerenza
# (Implementazione come prima)
return {
"profile_name": self.profile_name_var.get(),
"target_executable": self.target_exe_var.get(),
"program_parameters": self.program_params_var.get(),
}
def _select_profile_by_index(self, index: int) -> None:
# (Implementazione come prima)
if not (0 <= index < len(self._profiles_data)):
logger.warning(f"Attempted to select invalid profile index: {index}")
self._clear_profile_form()
self._selected_profile_index = None
return
self._selected_profile_index = index
profile = self._profiles_data[index]
self.profile_name_var.set(profile.get("profile_name", ""))
self.target_exe_var.set(profile.get("target_executable", ""))
self.program_params_var.set(profile.get("program_parameters", ""))
self._populate_actions_listbox() # Popola azioni per il profilo selezionato
self._enable_profile_form_editing(True)
self._current_profile_modified_in_form = False # Resetta flag dopo caricamento
# Sincronizza la selezione della listbox se non già fatto dall'evento
if (
not self.profiles_listbox.curselection()
or self.profiles_listbox.curselection()[0] != index
):
self.profiles_listbox.selection_clear(0, tk.END)
self.profiles_listbox.selection_set(index)
self.profiles_listbox.activate(index)
self.profiles_listbox.see(index)
self.add_action_button.config(state=tk.NORMAL) # Abilita aggiunta azioni
self._update_analysis_status_display() # Aggiorna stato analisi
def _clear_profile_form(self) -> None:
# (Implementazione come prima)
self.profile_name_var.set("")
self.target_exe_var.set("")
self.program_params_var.set("")
self._populate_actions_listbox() # Svuota la lista azioni
self._enable_profile_form_editing(False)
self._current_profile_modified_in_form = False
self.add_action_button.config(state=tk.DISABLED)
self.edit_action_button.config(state=tk.DISABLED)
self.remove_action_button.config(state=tk.DISABLED)
self._update_analysis_status_display() # Resetta anche lo stato dell'analisi
def _enable_profile_form_editing(self, enable: bool) -> None:
# (Implementazione come prima)
state = tk.NORMAL if enable else tk.DISABLED
self.profile_name_entry.config(state=state)
self.target_exe_entry.config(state=state)
self.browse_exe_button.config(state=state)
self.program_params_entry.config(state=state)
def _update_profile_action_buttons_state(self) -> None:
# (Implementazione come prima)
profile_selected = self._selected_profile_index is not None
self.duplicate_button.config(
state=tk.NORMAL if profile_selected else tk.DISABLED
)
self.delete_button.config(state=tk.NORMAL if profile_selected else tk.DISABLED)
def _browse_target_executable(self) -> None:
# (Implementazione come prima)
current_path = self.target_exe_var.get()
initial_dir = (
os.path.dirname(current_path)
if current_path and os.path.exists(os.path.dirname(current_path))
else None
)
path = filedialog.askopenfilename(
title="Select Target Executable",
filetypes=[("Executable files", ("*.exe", "*")), ("All files", "*.*")],
initialdir=initial_dir,
parent=self,
)
if path:
self.target_exe_var.set(
path
) # Questo attiverà _on_target_exe_changed_in_form
def _save_current_form_to_profile_data(self, profile_index: int) -> bool:
# (Implementazione come prima, ma con controllo indice migliorato)
if not (self._profiles_data and 0 <= profile_index < len(self._profiles_data)):
logger.error(f"Invalid profile index {profile_index} for saving form data.")
return False
profile_name = self.profile_name_var.get().strip()
if not profile_name:
messagebox.showerror(
"Validation Error", "Profile Name cannot be empty.", parent=self
)
self.profile_name_entry.focus_set()
return False
for i, p_item in enumerate(self._profiles_data):
if i != profile_index and p_item.get("profile_name") == profile_name:
messagebox.showerror(
"Validation Error",
f"Profile name '{profile_name}' already exists. Names must be unique.",
parent=self,
)
self.profile_name_entry.focus_set()
return False
target_profile_in_list = self._profiles_data[profile_index]
old_name = target_profile_in_list.get("profile_name")
old_target_exe = target_profile_in_list.get("target_executable")
new_target_exe = self.target_exe_var.get().strip()
target_profile_in_list["profile_name"] = profile_name
target_profile_in_list["target_executable"] = new_target_exe
target_profile_in_list["program_parameters"] = (
self.program_params_var.get()
) # Strip non necessario se è vuoto
self._current_profile_modified_in_form = False # Form salvato nei dati interni
self._profiles_list_changed_overall = True
if old_name != profile_name: # Aggiorna la listbox se il nome è cambiato
self.profiles_listbox.delete(profile_index)
self.profiles_listbox.insert(profile_index, profile_name)
self.profiles_listbox.selection_set(profile_index) # Riseleziona
if (
old_target_exe != new_target_exe
): # Se il target exe è cambiato, l'analisi precedente è invalidata
logger.info(
f"Target executable changed for profile '{profile_name}'. Invalidating previous symbol analysis."
)
target_profile_in_list["symbol_analysis"] = (
None # Invalida i dati di analisi
)
self._update_analysis_status_display() # Aggiorna UI per riflettere questo
logger.info(
f"Profile '{profile_name}' (index {profile_index}) details from form saved to internal list."
)
return True
def _new_profile(self) -> None:
# (Implementazione come prima)
if (
self._selected_profile_index is not None
and self._current_profile_modified_in_form
):
current_profile_name_display = self.profile_name_var.get() or (
self._profiles_data[self._selected_profile_index].get("profile_name")
if self._profiles_data
and 0 <= self._selected_profile_index < len(self._profiles_data)
else "current profile"
)
prompt = (
f"Profile '{current_profile_name_display}' has unsaved changes in the form.\n"
"Do you want to save them before creating a new profile?"
)
response = messagebox.askyesnocancel(
"Unsaved Changes", prompt, default=messagebox.CANCEL, parent=self
)
if response is True:
if not self._save_current_form_to_profile_data(
self._selected_profile_index
):
return
elif response is None:
return
new_p = json.loads(
json.dumps(DEFAULT_PROFILE_CONFIG)
) # Usa DEFAULT_PROFILE_CONFIG
base_name = new_p["profile_name"]
name_candidate = base_name
count = 1
existing_names = {p.get("profile_name") for p in self._profiles_data}
while name_candidate in existing_names:
name_candidate = f"{base_name} ({count})"
count += 1
new_p["profile_name"] = name_candidate
self._profiles_data.append(new_p)
self._profiles_list_changed_overall = True
self._populate_profiles_listbox()
self._select_profile_by_index(len(self._profiles_data) - 1)
self.profile_name_entry.focus_set()
self.profile_name_entry.selection_range(0, tk.END)
self._mark_form_as_modified()
def _duplicate_profile(self) -> None:
# (Implementazione come prima)
if self._selected_profile_index is None:
return
if self._current_profile_modified_in_form:
if not self._save_current_form_to_profile_data(
self._selected_profile_index
):
return
original_profile = self._profiles_data[self._selected_profile_index]
duplicated_profile = json.loads(json.dumps(original_profile))
base_name = f"{original_profile.get('profile_name', 'Profile')}_copy"
name_candidate = base_name
count = 1
existing_names = {p.get("profile_name") for p in self._profiles_data}
while name_candidate in existing_names:
name_candidate = f"{base_name}_{count}"
count += 1
duplicated_profile["profile_name"] = name_candidate
self._profiles_data.append(duplicated_profile)
self._profiles_list_changed_overall = True
self._populate_profiles_listbox()
self._select_profile_by_index(len(self._profiles_data) - 1)
self._mark_form_as_modified()
def _delete_profile(self) -> None:
# (Implementazione come prima)
if self._selected_profile_index is None or not self._profiles_data:
return
profile_to_delete = self._profiles_data[self._selected_profile_index]
profile_name = profile_to_delete.get("profile_name", "this profile")
if not messagebox.askyesno(
"Confirm Delete",
f"Are you sure you want to delete profile '{profile_name}'?",
parent=self,
):
return
idx_to_delete = self._selected_profile_index
del self._profiles_data[idx_to_delete]
self._profiles_list_changed_overall = True
new_list_size = len(self._profiles_data)
self._selected_profile_index = None # Resetta prima di ripopolare
self._populate_profiles_listbox()
if new_list_size == 0:
self._clear_profile_form()
else:
new_selection_idx = min(idx_to_delete, new_list_size - 1)
if new_selection_idx >= 0:
self._select_profile_by_index(new_selection_idx)
else:
self._clear_profile_form()
def _save_all_profiles_to_settings(self) -> None:
# (Implementazione come prima)
if (
self._selected_profile_index is not None
and self._current_profile_modified_in_form
):
if not self._save_current_form_to_profile_data(
self._selected_profile_index
):
messagebox.showerror(
"Save Error",
"Could not save changes from the form for the selected profile. Aborting save to settings.",
parent=self,
)
return
profile_names_seen = set()
for i, profile in enumerate(self._profiles_data):
name = profile.get("profile_name", "").strip()
if not name:
messagebox.showerror(
"Validation Error",
f"Profile at index {i} (0-based) has an empty name.",
parent=self,
)
if self._selected_profile_index != i:
self._select_profile_by_index(i)
self.profile_name_entry.focus_set()
return
if name in profile_names_seen:
messagebox.showerror(
"Validation Error",
f"Duplicate profile name '{name}' found.",
parent=self,
)
first_occurrence_index = next(
(
idx
for idx, p in enumerate(self._profiles_data)
if p.get("profile_name") == name
),
-1,
)
if (
first_occurrence_index != -1
and self._selected_profile_index != first_occurrence_index
):
self._select_profile_by_index(first_occurrence_index)
self.profile_name_entry.focus_set()
return
profile_names_seen.add(name)
actions = profile.get("actions")
if not isinstance(actions, list):
messagebox.showerror(
"Data Error",
f"Profile '{name}' has malformed actions (not a list).",
parent=self,
)
return
for idx_a, action in enumerate(actions):
if not isinstance(action, dict):
messagebox.showerror(
"Data Error",
f"Profile '{name}', action #{idx_a+1} is malformed (not a dict).",
parent=self,
)
return
self.app_settings.set_profiles(self._profiles_data)
if self.app_settings.save_settings():
logger.info(
f"All {len(self._profiles_data)} profiles saved to AppSettings."
)
messagebox.showinfo(
"Profiles Saved", "All profile changes have been saved.", parent=self
)
self._current_profile_modified_in_form = False
self._profiles_list_changed_overall = False
else:
messagebox.showerror(
"Save Error",
"Could not save profiles to the settings file. Check logs.",
parent=self,
)
def _on_closing_button(self) -> None:
# (Implementazione come prima)
needs_save_prompt = False
prompt_message = ""
if (
self._selected_profile_index is not None
and self._current_profile_modified_in_form
):
needs_save_prompt = True
profile_name = self.profile_name_var.get() or (
self._profiles_data[self._selected_profile_index].get("profile_name")
if self._profiles_data
and 0 <= self._selected_profile_index < len(self._profiles_data)
else "current profile"
)
prompt_message = (
f"Profile '{profile_name}' has unsaved changes in the form.\n"
)
if self._profiles_list_changed_overall:
needs_save_prompt = True
prompt_message += (
"Additionally, the overall list of profiles (or their content) has changed.\n"
if prompt_message
else "The list of profiles (or their content) has changed.\n"
)
if needs_save_prompt:
prompt_message += "Do you want to save all changes before closing?"
response = messagebox.askyesnocancel(
"Unsaved Changes",
prompt_message,
default=messagebox.CANCEL,
parent=self,
)
if response is True:
self._save_all_profiles_to_settings()
elif response is None:
return
if self.progress_dialog and self.progress_dialog.winfo_exists():
logger.warning(
"Closing ProfileManagerWindow while symbol analysis dialog open."
)
if hasattr(self.parent_window, "focus_set") and callable(
self.parent_window.focus_set
):
self.parent_window.focus_set()
self.destroy()
# --- Metodi per la gestione delle Azioni (Listbox, Add, Edit, Remove) ---
def _populate_actions_listbox(self) -> None:
# (Implementazione come prima)
self.actions_listbox.delete(0, tk.END)
self._selected_action_index_in_profile = None
if (
self._selected_profile_index is not None
and 0 <= self._selected_profile_index < len(self._profiles_data)
):
profile = self._profiles_data[self._selected_profile_index]
actions = profile.get("actions", [])
for i, action in enumerate(actions):
bp = action.get("breakpoint_location", "N/A")[:30]
num_vars = len(action.get("variables_to_dump", []))
fmt = action.get("output_format", "N/A")
cont = "Yes" if action.get("continue_after_dump", False) else "No"
dump_freq = (
"Always" if action.get("dump_on_every_hit", True) else "Once"
)
summary = (
f"BP: {bp}{'...' if len(action.get('breakpoint_location', '')) > 30 else ''} "
f"(Vars:{num_vars}, Fmt:{fmt}, Cont:{cont}, Hit:{dump_freq})"
)
self.actions_listbox.insert(tk.END, summary)
self._update_action_buttons_state()
def _on_action_select_in_listbox(self, event: Optional[tk.Event] = None) -> None:
# (Implementazione come prima)
selection_indices = self.actions_listbox.curselection()
if selection_indices:
self._selected_action_index_in_profile = selection_indices[0]
else:
self._selected_action_index_in_profile = None
self._update_action_buttons_state()
def _update_action_buttons_state(self) -> None:
# (Implementazione come prima)
profile_selected = self._selected_profile_index is not None
action_selected = self._selected_action_index_in_profile is not None
self.add_action_button.config(
state=tk.NORMAL if profile_selected else tk.DISABLED
)
self.edit_action_button.config(
state=tk.NORMAL if action_selected else tk.DISABLED
)
self.remove_action_button.config(
state=tk.NORMAL if action_selected else tk.DISABLED
)
def _add_action(self) -> None:
# (Implementazione come prima, ma con type hint e logica AppSettings corretta)
if self._selected_profile_index is None:
return
if self._current_profile_modified_in_form:
if not self._save_current_form_to_profile_data(
self._selected_profile_index
):
return
current_profile = self._profiles_data[self._selected_profile_index]
current_profile_target_exe = current_profile.get("target_executable", "")
current_profile_program_params = current_profile.get("program_parameters", "")
symbol_analysis_data = current_profile.get("symbol_analysis")
editor = ActionEditorWindow(
self,
action_data=None,
is_new=True,
target_executable_path=current_profile_target_exe,
app_settings=self.app_settings, # Passa l'istanza corretta
symbol_analysis_data=symbol_analysis_data,
program_parameters_for_scope=current_profile_program_params,
)
self.wait_window(editor) # Rendi modale
new_action_data = editor.get_result()
if new_action_data:
if "actions" not in current_profile or not isinstance(
current_profile["actions"], list
):
current_profile["actions"] = []
current_profile["actions"].append(new_action_data)
self._profiles_list_changed_overall = True
self._populate_actions_listbox()
self.actions_listbox.selection_set(tk.END)
self._on_action_select_in_listbox()
def _edit_action(self) -> None:
# (Implementazione come prima, ma con type hint e logica AppSettings corretta)
if (
self._selected_profile_index is None
or self._selected_action_index_in_profile is None
):
return
if self._current_profile_modified_in_form:
if not self._save_current_form_to_profile_data(
self._selected_profile_index
):
return
current_profile = self._profiles_data[self._selected_profile_index]
actions_list = current_profile.get("actions", [])
if not (0 <= self._selected_action_index_in_profile < len(actions_list)):
logger.error("Selected action index out of bounds.")
return
action_to_edit = actions_list[self._selected_action_index_in_profile]
current_profile_target_exe = current_profile.get("target_executable", "")
current_profile_program_params = current_profile.get("program_parameters", "")
symbol_analysis_data = current_profile.get("symbol_analysis")
editor = ActionEditorWindow(
self,
action_data=action_to_edit,
is_new=False,
target_executable_path=current_profile_target_exe,
app_settings=self.app_settings, # Passa l'istanza corretta
symbol_analysis_data=symbol_analysis_data,
program_parameters_for_scope=current_profile_program_params,
)
self.wait_window(editor) # Rendi modale
updated_action_data = editor.get_result()
if updated_action_data:
current_profile["actions"][
self._selected_action_index_in_profile
] = updated_action_data
self._profiles_list_changed_overall = True
idx_to_reselect = self._selected_action_index_in_profile
self._populate_actions_listbox()
if (
idx_to_reselect is not None
and 0 <= idx_to_reselect < self.actions_listbox.size()
):
self.actions_listbox.selection_set(idx_to_reselect)
self._on_action_select_in_listbox()
def _remove_action(self) -> None:
# (Implementazione come prima)
if (
self._selected_profile_index is None
or self._selected_action_index_in_profile is None
):
return
profile = self._profiles_data[self._selected_profile_index]
action_summary_to_delete = self.actions_listbox.get(
self._selected_action_index_in_profile
)
if not messagebox.askyesno(
"Confirm Delete Action",
f"Delete action?\n\n{action_summary_to_delete}",
parent=self,
):
return
actions_list = profile.get("actions")
if isinstance(
actions_list, list
) and 0 <= self._selected_action_index_in_profile < len(actions_list):
del actions_list[self._selected_action_index_in_profile]
self._profiles_list_changed_overall = True
idx_to_reselect_after_delete = self._selected_action_index_in_profile
self._populate_actions_listbox()
num_actions_remaining = len(actions_list)
if num_actions_remaining > 0:
new_selection = min(
idx_to_reselect_after_delete, num_actions_remaining - 1
)
if new_selection >= 0:
self.actions_listbox.selection_set(new_selection)
self._on_action_select_in_listbox()
else:
logger.error("Could not remove action: list missing or index invalid.")
# --- Metodi per Symbol Analysis (come prima, ma con type hint e logica AppSettings corretta) ---
def _update_analysis_status_display(self) -> None:
# (Implementazione come prima, ma assicurati che file_utils sia importato per calculate_file_checksum)
if self._selected_profile_index is None or not (
0 <= self._selected_profile_index < len(self._profiles_data)
):
self._current_profile_target_exe_details_label_var.set(
"Target Executable: N/A (No profile selected)"
)
self._current_profile_analysis_status_label_var.set(
"Symbol Analysis: Select a profile."
)
self.analyse_symbols_button.config(state=tk.DISABLED)
self.functions_count_var.set("Functions: N/A")
self.view_functions_button.config(state=tk.DISABLED)
self.variables_count_var.set("Globals: N/A")
self.view_variables_button.config(state=tk.DISABLED)
self.types_count_var.set("Types: N/A")
self.view_types_button.config(state=tk.DISABLED)
self.sources_count_var.set("Sources: N/A")
self.view_sources_button.config(state=tk.DISABLED)
return
profile = self._profiles_data[self._selected_profile_index]
target_exe_in_form = self.target_exe_var.get().strip()
exe_display_name = (
os.path.basename(target_exe_in_form) if target_exe_in_form else "N/A"
)
details_text_lines = [f"Target in Form: {exe_display_name}"]
status_text = "Symbol Analysis: "
status_color = "blue" # Default color
funcs_count_text = "Functions: N/A"
view_funcs_btn_state = tk.DISABLED
vars_count_text = "Globals: N/A"
view_vars_btn_state = tk.DISABLED
types_count_text = "Types: N/A"
view_types_btn_state = tk.DISABLED
sources_count_text = "Sources: N/A"
view_sources_btn_state = tk.DISABLED
analysis_button_state = tk.DISABLED
if target_exe_in_form and os.path.isfile(target_exe_in_form):
analysis_button_state = tk.NORMAL # Can analyse if exe path is valid
if not target_exe_in_form:
status_text += "Target executable not specified in form."
elif not os.path.isfile(target_exe_in_form):
status_text += f"Target '{exe_display_name}' (from form) not found on disk."
status_color = "red"
else: # Target in form is a valid file
analysis_data = profile.get("symbol_analysis")
if analysis_data and isinstance(analysis_data, dict):
symbols_dict = analysis_data.get("symbols", {})
num_functions = symbols_dict.get("functions_count", 0)
funcs_count_text = f"Functions: {num_functions}"
if num_functions > 0:
view_funcs_btn_state = tk.NORMAL
num_variables = symbols_dict.get("global_variables_count", 0)
vars_count_text = f"Globals: {num_variables}"
if num_variables > 0:
view_vars_btn_state = tk.NORMAL
num_types = symbols_dict.get("types_count", 0)
types_count_text = f"Types: {num_types}"
if num_types > 0:
view_types_btn_state = tk.NORMAL
num_sources = symbols_dict.get("source_files_count", 0)
sources_count_text = f"Sources: {num_sources}"
if num_sources > 0:
view_sources_btn_state = tk.NORMAL
saved_exe_at_analysis = analysis_data.get(
"analyzed_executable_path", "Unknown"
)
details_text_lines.append(
f"Last Analysis on File: {os.path.basename(saved_exe_at_analysis)}"
)
details_text_lines.append(
f" File Timestamp (at analysis): {analysis_data.get('executable_timestamp', 'N/A')}"
)
details_text_lines.append(
f" Analysis Date: {analysis_data.get('analysis_timestamp', 'N/A')}"
)
saved_checksum = analysis_data.get("executable_checksum")
details_text_lines.append(
f" Saved Checksum: {saved_checksum or 'N/A'}"
)
current_checksum_for_form_exe = file_utils.calculate_file_checksum(
target_exe_in_form
)
details_text_lines.append(
f" Current Form Exe Checksum: {current_checksum_for_form_exe or 'N/A (calc failed)'}"
)
if os.path.normpath(saved_exe_at_analysis) != os.path.normpath(
target_exe_in_form
):
status_text += (
"TARGET CHANGED since last analysis. RE-ANALYSIS RECOMMENDED."
)
status_color = "orange red"
view_funcs_btn_state = view_vars_btn_state = (
view_types_btn_state
) = view_sources_btn_state = tk.DISABLED
elif (
saved_checksum
and current_checksum_for_form_exe
and saved_checksum == current_checksum_for_form_exe
):
status_text += "Up-to-date."
status_color = "dark green"
elif saved_checksum and current_checksum_for_form_exe: # Mismatch
status_text += (
"EXECUTABLE CHANGED (checksum mismatch). RE-ANALYSIS REQUIRED."
)
status_color = "red"
view_funcs_btn_state = view_vars_btn_state = (
view_types_btn_state
) = view_sources_btn_state = tk.DISABLED
else: # Checksum missing or calc failed for current
status_text += "Status unclear (checksums). Consider re-analysing."
status_color = "orange"
# Mantieni i bottoni view abilitati se c'è data, ma con avviso
else:
status_text += "Not performed. Click 'Analyse' to generate."
status_color = "blue"
self.analyse_symbols_button.config(state=analysis_button_state)
self._current_profile_target_exe_details_label_var.set(
"\n".join(details_text_lines)
)
self._current_profile_analysis_status_label_var.set(status_text)
self.analysis_status_label.config(foreground=status_color)
self.functions_count_var.set(funcs_count_text)
self.view_functions_button.config(state=view_funcs_btn_state)
self.variables_count_var.set(vars_count_text)
self.view_variables_button.config(state=view_vars_btn_state)
self.types_count_var.set(types_count_text)
self.view_types_button.config(state=view_types_btn_state)
self.sources_count_var.set(sources_count_text)
self.view_sources_button.config(state=view_sources_btn_state)
def _trigger_symbol_analysis(self) -> None:
# (Implementazione come prima, assicurati che SymbolAnalyzer sia importato correttamente)
if self._selected_profile_index is None:
messagebox.showerror("Error", "No profile selected.", parent=self)
return
target_exe_for_analysis = self.target_exe_var.get().strip()
if not target_exe_for_analysis or not os.path.isfile(target_exe_for_analysis):
messagebox.showerror(
"Error", "Target executable path in the form is invalid.", parent=self
)
self._update_analysis_status_display()
return
gdb_exe_path = self.app_settings.get_setting("general", "gdb_executable_path")
if not gdb_exe_path or not os.path.isfile(gdb_exe_path):
messagebox.showerror(
"GDB Error",
f"GDB executable not configured or not found: {gdb_exe_path}",
parent=self,
)
return
profile_to_update = self._profiles_data[self._selected_profile_index]
self.progress_dialog = SymbolAnalysisProgressDialog(
self
) # parent è self (ProfileManagerWindow)
# Passa GDBMISession come classe per l'analisi
symbol_analyzer = SymbolAnalyzer(
gdb_exe_path, self.app_settings, gdb_session_class=GDBMISession
)
analysis_thread = threading.Thread(
target=self._perform_symbol_analysis_thread,
args=(
profile_to_update,
target_exe_for_analysis,
symbol_analyzer,
self.progress_dialog,
),
daemon=True,
)
analysis_thread.start()
def _perform_symbol_analysis_thread(
self,
profile_to_update: Dict[str, Any],
target_exe_path: str,
symbol_analyzer: SymbolAnalyzer,
progress_dialog: SymbolAnalysisProgressDialog,
):
# (Implementazione come prima)
analysis_data_dict: Dict[str, Any] = {}
analysis_succeeded_overall = False
def gui_log(msg: str):
if progress_dialog and progress_dialog.winfo_exists():
self.after(0, progress_dialog.log_message, msg)
def gui_set_status(msg: str):
if progress_dialog and progress_dialog.winfo_exists():
self.after(0, progress_dialog.set_status, msg)
try:
gui_log(
f"Starting symbol analysis for: {os.path.basename(target_exe_path)}"
)
gui_set_status(f"Analyzing {os.path.basename(target_exe_path)}...")
analysis_data_dict = symbol_analyzer.analyze(
target_exe_path=target_exe_path,
progress_callback=gui_log,
status_callback=gui_set_status,
)
if analysis_data_dict:
analysis_succeeded_overall = True
gui_set_status("Symbol analysis successfully completed.")
gui_log("\nSymbol analysis successfully completed.")
else:
gui_set_status("Symbol analysis failed. Check logs.")
gui_log("\nSymbol analysis failed.")
except Exception as e:
logger.error(
f"Error in symbol analysis thread for '{target_exe_path}': {e}",
exc_info=True,
)
error_msg = f"THREAD ERROR: {type(e).__name__} - {e}"
gui_log(f"\n{error_msg}")
gui_set_status(error_msg)
finally:
if self.winfo_exists(): # Assicura che ProfileManagerWindow esista ancora
self.after(
0,
self._finalize_symbol_analysis,
profile_to_update,
analysis_data_dict,
analysis_succeeded_overall,
progress_dialog,
)
def _finalize_symbol_analysis(
self,
profile_to_update: Dict[str, Any],
analysis_data: Dict[str, Any],
success: bool,
progress_dialog: SymbolAnalysisProgressDialog,
):
# (Implementazione come prima)
if success:
profile_to_update["symbol_analysis"] = analysis_data
self._profiles_list_changed_overall = True
logger.info(
f"Symbol analysis data updated for profile: '{profile_to_update.get('profile_name')}'."
)
if self.winfo_exists():
messagebox.showinfo(
"Analysis Complete",
"Symbol analysis has finished successfully.",
parent=self,
)
else:
logger.error(
f"Symbol analysis failed for profile: '{profile_to_update.get('profile_name')}'."
)
if self.winfo_exists():
messagebox.showerror(
"Analysis Failed",
"Symbol analysis did not complete successfully. Check logs.",
parent=self,
)
if progress_dialog and progress_dialog.winfo_exists():
progress_dialog.analysis_complete_or_failed(success)
if self.winfo_exists():
self._update_analysis_status_display()
def _get_symbols_for_display(
self, category: str
) -> List[Union[str, Dict[str, Any]]]:
# (Implementazione come prima)
if self._selected_profile_index is None or not (
0 <= self._selected_profile_index < len(self._profiles_data)
):
return []
profile = self._profiles_data[self._selected_profile_index]
analysis_data = profile.get("symbol_analysis")
if not analysis_data or not isinstance(analysis_data.get("symbols"), dict):
return []
return analysis_data["symbols"].get(category, [])
def _view_analyzed_functions(self) -> None: # (Come prima)
functions_list = self._get_symbols_for_display("functions")
if not functions_list:
messagebox.showinfo(
"No Functions", "No functions from analysis.", parent=self
)
return
self._show_symbol_list_dialog("Functions", functions_list)
def _view_analyzed_variables(self) -> None: # (Come prima)
variables_list = self._get_symbols_for_display("global_variables")
if not variables_list:
messagebox.showinfo(
"No Global Variables", "No globals from analysis.", parent=self
)
return
self._show_symbol_list_dialog("Global Variables", variables_list)
def _view_analyzed_types(self) -> None: # (Come prima)
types_list = self._get_symbols_for_display("types")
if not types_list:
messagebox.showinfo("No Types", "No types from analysis.", parent=self)
return
self._show_symbol_list_dialog("Types", types_list)
def _view_analyzed_sources(self) -> None: # (Come prima)
source_files_list = self._get_symbols_for_display("source_files")
if not source_files_list:
messagebox.showinfo(
"No Source Files", "No sources from analysis.", parent=self
)
return
self._show_symbol_list_dialog("Source Files", source_files_list)
def _show_symbol_list_dialog(
self, symbol_type: str, symbols: List[Union[str, Dict[str, Any]]]
) -> None:
# (Implementazione come prima, ma assicurati che file_utils sia importato per calculate_file_checksum)
if self._selected_profile_index is None:
return
target_exe_in_form = self.target_exe_var.get().strip() # Usa il valore del form
profile = self._profiles_data[self._selected_profile_index]
analysis_data = profile.get("symbol_analysis")
if not analysis_data:
messagebox.showerror("Error", "No analysis data available.", parent=self)
return
analyzed_exe_path = analysis_data.get("analyzed_executable_path", "")
exe_name_for_title = (
os.path.basename(target_exe_in_form)
if target_exe_in_form
else "Unknown Target"
)
is_obsolete = True
if (
target_exe_in_form
and os.path.isfile(target_exe_in_form)
and os.path.normpath(analyzed_exe_path)
== os.path.normpath(target_exe_in_form)
):
current_checksum = file_utils.calculate_file_checksum(
target_exe_in_form
) # USA file_utils
saved_checksum = analysis_data.get("executable_checksum")
if (
current_checksum
and saved_checksum
and current_checksum == saved_checksum
):
is_obsolete = False
title_suffix = " (Analysis data might be obsolete)" if is_obsolete else ""
dialog_title = (
f"Analyzed {symbol_type} for '{exe_name_for_title}'{title_suffix}"
)
# SymbolListViewerDialog è già importato
SymbolListViewerDialog(self, symbols, title=dialog_title)