diff --git a/cpp_python_debug/gui/profile_manager_window.py b/cpp_python_debug/gui/profile_manager_window.py index 60bd655..cd6928b 100644 --- a/cpp_python_debug/gui/profile_manager_window.py +++ b/cpp_python_debug/gui/profile_manager_window.py @@ -120,6 +120,71 @@ class SymbolAnalysisProgressDialog(tk.Toplevel): self.set_status("Analysis failed or was aborted. Check log. You can close this window.") +class SymbolListViewerDialog(tk.Toplevel): # NUOVA DIALOG PER VISUALIZZARE LISTE + """A simple dialog to view a list of symbols.""" + def __init__(self, parent: tk.Widget, symbols: List[str], title: str = "Symbol List"): + super().__init__(parent) + self.title(title) + + parent_x = parent.winfo_x() + parent_y = parent.winfo_y() + parent_width = parent.winfo_width() + parent_height = parent.winfo_height() + width = 500 + height = 450 + x = parent_x + (parent_width // 2) - (width // 2) + y = parent_y + (parent_height // 2) - (height // 2) + self.geometry(f'{width}x{height}+{x}+{y}') + + self.transient(parent) + self.grab_set() + + main_frame = ttk.Frame(self, padding="10") + main_frame.pack(expand=True, fill=tk.BOTH) + main_frame.rowconfigure(1, weight=1) + main_frame.columnconfigure(0, weight=1) + + filter_frame = ttk.Frame(main_frame) + filter_frame.grid(row=0, column=0, sticky="ew", pady=(0, 5)) + ttk.Label(filter_frame, text="Filter:").pack(side=tk.LEFT, padx=(0,5)) + self.filter_var = tk.StringVar() + self.filter_var.trace_add("write", self._apply_filter) + ttk.Entry(filter_frame, textvariable=self.filter_var, width=40).pack(side=tk.LEFT, expand=True, fill=tk.X) + + self.listbox = tk.Listbox(main_frame, selectmode=tk.SINGLE) + self.listbox.grid(row=1, column=0, sticky="nsew") + + scrollbar_y = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.listbox.yview) + scrollbar_y.grid(row=1, column=1, sticky="ns") + self.listbox.configure(yscrollcommand=scrollbar_y.set) + + scrollbar_x = ttk.Scrollbar(main_frame, orient=tk.HORIZONTAL, command=self.listbox.xview) + scrollbar_x.grid(row=2, column=0, sticky="ew") + self.listbox.configure(xscrollcommand=scrollbar_x.set) + + self._original_symbols = sorted(symbols) + self._populate_listbox(self._original_symbols) + + button_frame = ttk.Frame(main_frame, padding=(0, 10, 0, 0)) + button_frame.grid(row=3, column=0, columnspan=2, sticky="e") + ttk.Button(button_frame, text="Close", command=self.destroy).pack() + + self.protocol("WM_DELETE_WINDOW", self.destroy) + + def _populate_listbox(self, symbols_to_show: List[str]): + self.listbox.delete(0, tk.END) + for item in symbols_to_show: + self.listbox.insert(tk.END, item) + + def _apply_filter(self, *args): + filter_text = self.filter_var.get().lower() + if not filter_text: + self._populate_listbox(self._original_symbols) + else: + filtered_list = [s for s in self._original_symbols if filter_text in s.lower()] + self._populate_listbox(filtered_list) + + class ProfileManagerWindow(tk.Toplevel): def __init__(self, parent: 'GDBGui', app_settings: 'AppSettings'): super().__init__(parent) @@ -127,7 +192,7 @@ class ProfileManagerWindow(tk.Toplevel): self.app_settings: 'AppSettings' = app_settings self.title("Profile Manager") - self.geometry("1050x700") + self.geometry("1050x750") # Aumentata leggermente l'altezza per i nuovi widget self.transient(parent) self.grab_set() @@ -145,6 +210,12 @@ class ProfileManagerWindow(tk.Toplevel): self._current_profile_target_exe_details_label_var = tk.StringVar(value="Target: N/A") self._current_profile_analysis_status_label_var = tk.StringVar(value="Symbol Analysis: Not Performed") self.progress_dialog: Optional[SymbolAnalysisProgressDialog] = None + + # StringVars per i conteggi + self.functions_count_var = tk.StringVar(value="Functions: N/A") + # self.variables_count_var = tk.StringVar(value="Globals: N/A") # Futuro + # self.types_count_var = tk.StringVar(value="Types: N/A") # Futuro + # self.sources_count_var = tk.StringVar(value="Sources: N/A") # Futuro self._load_profiles_from_settings() self._create_widgets() @@ -183,15 +254,15 @@ class ProfileManagerWindow(tk.Toplevel): def _create_widgets(self) -> None: main_frame = ttk.Frame(self, padding="10") main_frame.pack(expand=True, fill=tk.BOTH) - main_frame.columnconfigure(0, weight=1, minsize=250) + main_frame.columnconfigure(0, weight=1, minsize=250) main_frame.columnconfigure(1, weight=3) main_frame.rowconfigure(0, weight=1) main_frame.rowconfigure(1, weight=0) left_pane = ttk.Frame(main_frame) left_pane.grid(row=0, column=0, sticky="nsew", padx=(0, 10)) - left_pane.rowconfigure(0, weight=1) - left_pane.rowconfigure(1, weight=0) + left_pane.rowconfigure(0, weight=1) + left_pane.rowconfigure(1, weight=0) left_pane.columnconfigure(0, weight=1) profiles_list_frame = ttk.LabelFrame(left_pane, text="Profiles", padding="5") @@ -220,7 +291,8 @@ class ProfileManagerWindow(tk.Toplevel): right_pane.grid(row=0, column=1, sticky="nsew") right_pane.rowconfigure(0, weight=0) right_pane.rowconfigure(1, weight=0) - right_pane.rowconfigure(2, weight=1) + right_pane.rowconfigure(2, weight=0) # Riga per Conteggi Simboli + right_pane.rowconfigure(3, weight=1) # Riga per Azioni di Debug right_pane.columnconfigure(0, weight=1) details_form_frame = ttk.LabelFrame(right_pane, text="Profile Details", padding="10") @@ -241,7 +313,7 @@ class ProfileManagerWindow(tk.Toplevel): 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) - analysis_control_frame = ttk.LabelFrame(right_pane, text="Symbol Analysis", padding="10") + 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) @@ -254,14 +326,32 @@ class ProfileManagerWindow(tk.Toplevel): 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) + 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) + symbols_summary_frame.columnconfigure(1, weight=0) + + 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 + + # Placeholder per futuri conteggi e bottoni (Iterazione 3) + # 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 + # ... (e così via per tipi e sorgenti) + actions_ui_frame = ttk.LabelFrame(right_pane, text="Debug Actions", padding="10") - actions_ui_frame.grid(row=2, column=0, sticky="nsew", pady=5) + actions_ui_frame.grid(row=3, column=0, sticky="nsew", pady=5) # Riga 3 actions_ui_frame.rowconfigure(0, weight=1) actions_ui_frame.columnconfigure(0, weight=1) actions_ui_frame.columnconfigure(1, weight=0) actions_ui_frame.columnconfigure(2, weight=0) - self.actions_listbox = tk.Listbox(actions_ui_frame, exportselection=False, selectmode=tk.SINGLE, height=8) + 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)) self.actions_listbox.bind("<>", self._on_action_select_in_listbox) @@ -273,11 +363,11 @@ class ProfileManagerWindow(tk.Toplevel): action_buttons_frame.grid(row=0, column=2, sticky="ns", padx=(5,0), pady=5) 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) + 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) + 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) + self.remove_action_button.pack(fill=tk.X, pady=2, anchor="n") bottom_buttons_frame = ttk.Frame(main_frame) bottom_buttons_frame.grid(row=1, column=0, columnspan=2, sticky="sew", pady=(10,0)) @@ -347,14 +437,13 @@ class ProfileManagerWindow(tk.Toplevel): if not (0 <= index < len(self._profiles_data)): self._clear_profile_form() self._selected_profile_index = None - # _update_analysis_status_display() è chiamato da _clear_profile_form 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", "")) # Triggera _on_target_exe_changed_in_form + self.target_exe_var.set(profile.get("target_executable", "")) self.program_params_var.set(profile.get("program_parameters", "")) self._populate_actions_listbox() @@ -368,15 +457,12 @@ class ProfileManagerWindow(tk.Toplevel): self.profiles_listbox.see(index) self.add_action_button.config(state=tk.NORMAL) - # _update_analysis_status_display() è chiamato da _on_target_exe_changed_in_form - # o dovrebbe essere chiamato esplicitamente qui se il set di target_exe_var non lo fa sempre. - # Per sicurezza, lo chiamiamo esplicitamente dopo aver impostato tutte le var. self._update_analysis_status_display() def _clear_profile_form(self) -> None: self.profile_name_var.set("") - self.target_exe_var.set("") # Triggera _on_target_exe_changed_in_form + self.target_exe_var.set("") self.program_params_var.set("") self._populate_actions_listbox() self._enable_profile_form_editing(False) @@ -392,7 +478,6 @@ class ProfileManagerWindow(tk.Toplevel): self.target_exe_entry.config(state=state) self.browse_exe_button.config(state=state) self.program_params_entry.config(state=state) - # analyse_symbols_button state è gestito da _update_analysis_status_display def _update_profile_action_buttons_state(self) -> None: profile_selected = self._selected_profile_index is not None @@ -409,7 +494,7 @@ class ProfileManagerWindow(tk.Toplevel): parent=self ) if path: - self.target_exe_var.set(path) # Triggera _on_target_exe_changed_in_form + self.target_exe_var.set(path) def _save_current_form_to_profile_data(self, profile_index: int) -> bool: if not (0 <= profile_index < len(self._profiles_data)): @@ -432,7 +517,7 @@ class ProfileManagerWindow(tk.Toplevel): old_name = target_profile.get("profile_name") target_profile["profile_name"] = profile_name - target_profile["target_executable"] = self.target_exe_var.get().strip() # Questo aggiorna il path + target_profile["target_executable"] = self.target_exe_var.get().strip() target_profile["program_parameters"] = self.program_params_var.get() self._current_profile_modified_in_form = False @@ -443,15 +528,12 @@ class ProfileManagerWindow(tk.Toplevel): self.profiles_listbox.insert(profile_index, profile_name) self.profiles_listbox.selection_set(profile_index) - # Dopo aver salvato il form, lo stato dell'analisi potrebbe dover essere rivalutato - # se target_executable è cambiato. _update_analysis_status_display lo farà. self._update_analysis_status_display() logger.info(f"Profile '{profile_name}' (index {profile_index}) basic details updated.") return True def _new_profile(self) -> None: if self._selected_profile_index is not None and self._current_profile_modified_in_form: - # ... (logica gestione modifiche non salvate come prima) ... response = messagebox.askyesnocancel("Unsaved Changes", f"Profile '{self._profiles_data[self._selected_profile_index].get('profile_name')}' has unsaved changes in the form.\n" "Do you want to save them before creating a new profile?", @@ -462,7 +544,7 @@ class ProfileManagerWindow(tk.Toplevel): elif response is None: return - new_p = json.loads(json.dumps(DEFAULT_PROFILE)) # Deep copy del template + new_p = json.loads(json.dumps(DEFAULT_PROFILE)) base_name = "New Profile" name_candidate = base_name @@ -489,7 +571,7 @@ class ProfileManagerWindow(tk.Toplevel): return original_profile = self._profiles_data[self._selected_profile_index] - duplicated_profile = json.loads(json.dumps(original_profile)) # Deep copy + duplicated_profile = json.loads(json.dumps(original_profile)) base_name = f"{original_profile.get('profile_name', 'Profile')}_copy" name_candidate = base_name @@ -499,9 +581,6 @@ class ProfileManagerWindow(tk.Toplevel): name_candidate = f"{base_name}_{count}" count += 1 duplicated_profile["profile_name"] = name_candidate - # L'analisi dei simboli duplicata si riferirà all'exe originale; - # l'utente dovrà rianalizzare se cambia l'exe del profilo duplicato. - # L'impostazione del nome del profilo non dovrebbe cancellare symbol_analysis self._profiles_data.append(duplicated_profile) self._profiles_list_changed_overall = True @@ -530,9 +609,9 @@ class ProfileManagerWindow(tk.Toplevel): self._clear_profile_form() else: new_selection_idx = min(idx_to_delete, new_list_size - 1) - if new_selection_idx >= 0: # Assicura che l'indice sia valido + if new_selection_idx >= 0: self._select_profile_by_index(new_selection_idx) - else: # Se la lista è diventata vuota, pulisci + else: self._clear_profile_form() @@ -544,7 +623,6 @@ class ProfileManagerWindow(tk.Toplevel): profile_names_seen = set() for i, profile in enumerate(self._profiles_data): - # ... (validazione nomi profili e struttura azioni come prima) ... name = profile.get("profile_name", "").strip() if not name: messagebox.showerror("Validation Error", f"Profile at index {i} has an empty name.", parent=self) @@ -572,18 +650,16 @@ class ProfileManagerWindow(tk.Toplevel): messagebox.showerror("Save Error", "Could not save profiles to the settings file. Check logs.", parent=self) def _on_closing_button(self) -> None: - # ... (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 - # ... (costruzione messaggio) profile_name = self.profile_name_var.get() or self._profiles_data[self._selected_profile_index].get('profile_name', 'current profile') prompt_message = f"Profile '{profile_name}' has unsaved changes in the form.\n" - if self._profiles_list_changed_overall: # Se la lista stessa è cambiata (add/del/rename) + if self._profiles_list_changed_overall: needs_save_prompt = True - if prompt_message: # Se già c'era un messaggio per il form + if prompt_message: prompt_message += "Additionally, the overall list of profiles (or their content) has changed.\n" else: prompt_message = "The list of profiles (or their content) has changed.\n" @@ -593,21 +669,15 @@ class ProfileManagerWindow(tk.Toplevel): response = messagebox.askyesnocancel("Unsaved Changes", prompt_message, default=messagebox.CANCEL, parent=self) if response is True: self._save_all_profiles_to_settings() - # Se il salvataggio fallisce, _save_all_profiles_to_settings mostra un errore, - # ma la finestra si chiuderà comunque. Potremmo voler cambiare questo comportamento. - elif response is None: # Cancel + elif response is None: return - # Se False (No), procedi a chiudere senza salvare if self.progress_dialog and self.progress_dialog.winfo_exists(): logger.warning("Closing ProfileManagerWindow while symbol analysis dialog might be open.") - # Potremmo voler provare a chiudere la dialog di progresso qui, o avvisare. - # Per ora, la dialog di progresso gestisce la sua chiusura. self.parent_window.focus_set() self.destroy() - # --- Gestione Azioni (Listbox e Bottoni Azione) --- def _populate_actions_listbox(self) -> None: self.actions_listbox.delete(0, tk.END) self._selected_action_index_in_profile = None @@ -616,7 +686,7 @@ class ProfileManagerWindow(tk.Toplevel): 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] # Tronca per display + 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" @@ -657,7 +727,7 @@ class ProfileManagerWindow(tk.Toplevel): current_profile["actions"] = [] current_profile["actions"].append(new_action_data) self._profiles_list_changed_overall = True - self._current_profile_modified_in_form = True # Il profilo nel form è cambiato + self._current_profile_modified_in_form = True self._populate_actions_listbox() self.actions_listbox.selection_set(tk.END) self._on_action_select_in_listbox() @@ -665,6 +735,7 @@ class ProfileManagerWindow(tk.Toplevel): def _edit_action(self) -> None: if self._selected_profile_index is None or self._selected_action_index_in_profile is None: 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)): return @@ -692,9 +763,10 @@ class ProfileManagerWindow(tk.Toplevel): def _remove_action(self) -> None: if self._selected_profile_index is None or self._selected_action_index_in_profile is None: return - # ... (come prima) ... + 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"Are you sure you want to delete this action?\n\n{action_summary_to_delete}", parent=self): return @@ -718,28 +790,38 @@ class ProfileManagerWindow(tk.Toplevel): self._current_profile_target_exe_details_label_var.set("Target: N/A") 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) return profile = self._profiles_data[self._selected_profile_index] - target_exe_from_form = self.target_exe_var.get() # Path attuale nel form + target_exe_in_form = self.target_exe_var.get() - exe_display_name = os.path.basename(target_exe_from_form) if target_exe_from_form else "N/A" + 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 + status_color = "blue" + funcs_count_text = "Functions: N/A" + view_funcs_btn_state = tk.DISABLED analysis_button_state = tk.DISABLED - if target_exe_from_form and os.path.isfile(target_exe_from_form): - analysis_button_state = tk.NORMAL # Abilita se il file nel form esiste + if target_exe_in_form and os.path.isfile(target_exe_in_form): + analysis_button_state = tk.NORMAL - if not target_exe_from_form: + if not target_exe_in_form: status_text += "Target executable not specified in form." - elif not os.path.isfile(target_exe_from_form): + elif not os.path.isfile(target_exe_in_form): status_text += f"Target '{exe_display_name}' not found on disk." status_color = "red" - else: # Il file nel form esiste, ora controlla i dati di analisi salvati + else: 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 + saved_checksum = analysis_data.get("executable_checksum") saved_analysis_ts_str = analysis_data.get("analysis_timestamp") saved_exe_at_analysis = analysis_data.get("analyzed_executable_path", "Unknown") @@ -750,22 +832,28 @@ class ProfileManagerWindow(tk.Toplevel): details_text_lines.append(f" Analysis Date: {saved_analysis_ts_str or 'N/A'}") details_text_lines.append(f" Saved Checksum: {saved_checksum or 'N/A'}") - current_checksum_for_form_exe = self._calculate_file_checksum(target_exe_from_form) + current_checksum_for_form_exe = self._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_from_form): + 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" + # Disabilita i bottoni View se il target è cambiato + view_funcs_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 and saved_checksum != current_checksum_for_form_exe: status_text += "EXECUTABLE CHANGED since last analysis. RE-ANALYSIS REQUIRED." status_color = "red" + # Disabilita i bottoni View se l'eseguibile è cambiato + view_funcs_btn_state = tk.DISABLED else: # Checksum mancanti o confronto non possibile status_text += "Status unclear. Consider re-analysing." status_color = "orange red" - else: # Nessuna analisi salvata per questo profilo + # Disabilita i bottoni View se lo stato è incerto + view_funcs_btn_state = tk.DISABLED + else: # Nessuna analisi salvata status_text += "Not performed. Click 'Analyse' to generate." status_color = "blue" @@ -773,6 +861,9 @@ class ProfileManagerWindow(tk.Toplevel): 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) def _calculate_file_checksum(self, filepath: str, hash_type: str = "md5") -> Optional[str]: if not os.path.isfile(filepath): return None @@ -821,7 +912,16 @@ class ProfileManagerWindow(tk.Toplevel): "executable_timestamp": "N/A", "analysis_timestamp": "N/A", "gdb_version_info": "N/A", - "symbols": {"functions": []} + "symbols": { + "functions": [], + "functions_count": 0, + # "global_variables": [], # Per Iterazione 3 + # "global_variables_count": 0,# Per Iterazione 3 + # "types": [], # Per Iterazione 3 + # "types_count": 0, # Per Iterazione 3 + # "source_files": [], # Per Iterazione 3 + # "source_files_count": 0 # Per Iterazione 3 + } } analysis_succeeded_overall = False @@ -848,23 +948,31 @@ class ProfileManagerWindow(tk.Toplevel): gui_log("WARNING: GDB reported no debugging symbols. Analysis may be limited.") gui_set_status("Fetching GDB version..."); gui_log("Fetching GDB version...") - analysis_data_dict["gdb_version_info"] = temp_gdb_session.get_gdb_version(timeout=command_timeout) or "N/A" + gdb_version = temp_gdb_session.get_gdb_version(timeout=command_timeout) + analysis_data_dict["gdb_version_info"] = gdb_version if gdb_version else "N/A" gui_log(f"GDB Version: {analysis_data_dict['gdb_version_info']}") gui_set_status("Fetching function list..."); gui_log("Fetching function list from GDB...") - functions = temp_gdb_session.list_functions(timeout=command_timeout * 3) # Dare più tempo a info functions + functions = temp_gdb_session.list_functions(timeout=command_timeout * 4) # Più tempo per info functions analysis_data_dict["symbols"]["functions"] = functions + analysis_data_dict["symbols"]["functions_count"] = len(functions) # Salva il conteggio gui_log(f"Found {len(functions)} functions.") - # Altre analisi (variabili, tipi) verranno qui nelle iterazioni future - # analysis_data_dict["symbols"]["global_variables"] = temp_gdb_session.list_global_variables(...) + # --- In future iterations, call methods for other symbols and store counts --- + # gui_set_status("Fetching global variables..."); gui_log("Fetching global variables...") + # globals_list = temp_gdb_session.list_global_variables(timeout=command_timeout * 2) + # analysis_data_dict["symbols"]["global_variables"] = globals_list + # analysis_data_dict["symbols"]["global_variables_count"] = len(globals_list) + # gui_log(f"Found {len(globals_list)} global variables.") + # ... (similarly for types and source_files) ... + gui_set_status("Calculating file checksum and timestamp..."); gui_log("Calculating file checksum and timestamp...") analysis_data_dict["executable_checksum"] = self._calculate_file_checksum(target_exe_path) try: mtime = os.path.getmtime(target_exe_path) analysis_data_dict["executable_timestamp"] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(mtime)) - except OSError: pass # Lascia N/A + except OSError: pass gui_log(f"Checksum (MD5): {analysis_data_dict['executable_checksum'] or 'N/A'}") gui_log(f"File Timestamp: {analysis_data_dict['executable_timestamp']}") @@ -899,19 +1007,50 @@ class ProfileManagerWindow(tk.Toplevel): if success: profile_to_update["symbol_analysis"] = analysis_data self._profiles_list_changed_overall = True - self._current_profile_modified_in_form = True # Il contenuto del profilo è cambiato + self._current_profile_modified_in_form = True logger.info(f"Symbol analysis data updated for profile: '{profile_to_update.get('profile_name')}'.") - if self.winfo_exists(): # Assicura che la finestra genitore esista ancora + if self.winfo_exists(): messagebox.showinfo("Analysis Complete", "Symbol analysis has finished successfully.", parent=self) else: - # Non aggiorniamo symbol_analysis nel profilo se fallisce, ma potremmo salvare dati parziali se utile 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) - # La dialog ora ha un bottone Close abilitato, l'utente la chiuderà. - # Oppure: self.after(2000, lambda: progress_dialog.destroy() if progress_dialog.winfo_exists() else None) - self._update_analysis_status_display() \ No newline at end of file + self._update_analysis_status_display() + + def _view_analyzed_functions(self) -> None: + if self._selected_profile_index is None or \ + not (0 <= self._selected_profile_index < len(self._profiles_data)): + messagebox.showinfo("Info", "No profile selected or data available.", parent=self) + 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): + messagebox.showinfo("No Analysis Data", "No symbol analysis data available for this profile.", parent=self) + return + + functions_list = analysis_data["symbols"].get("functions", []) + if not functions_list: + messagebox.showinfo("No Functions", "No functions found in the last analysis for this profile.", parent=self) + return + + target_exe_in_form = self.target_exe_var.get() + 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 Executable" + + is_obsolete = True + if os.path.normpath(analyzed_exe_path) == os.path.normpath(target_exe_in_form): + current_checksum = self._calculate_file_checksum(target_exe_in_form) + saved_checksum = analysis_data.get("executable_checksum") + if current_checksum and saved_checksum and current_checksum == saved_checksum: + is_obsolete = False + + title_suffix = " (Analysis might be obsolete)" if is_obsolete else "" + dialog_title = f"Analyzed Functions for '{exe_name_for_title}'{title_suffix}" + + SymbolListViewerDialog(self, functions_list, title=dialog_title) \ No newline at end of file diff --git a/todo.md b/todo.md index 376b766..2d7dece 100644 --- a/todo.md +++ b/todo.md @@ -54,6 +54,12 @@ DEBUG_STRING_TRACE: _serializ +questo run funziona +(gdb) run /c /t 4:"C:/__Voli/Volo_12_25maggio2025/sar_367-435/_25-05-15-12-22-52_sata_367-n69.out" /b + + + + Assolutamente sì, la tua idea è eccellente e va nella direzione giusta per rendere l'applicazione molto più potente e user-friendly! Sfruttare le capacità introspettive di GDB per assistere l'utente nella creazione di profili di debug è una funzionalità di grande valore. **Concetto Chiave: Introspezione di GDB per l'Assistenza all'Utente**