# File: cpp_python_debug/gui/dialogs.py import tkinter as tk from tkinter import ttk, messagebox, scrolledtext # scrolledtext è usato import logging from typing import List, Optional, Dict, Any, Union, Callable # Aggiunto Callable logger = logging.getLogger(__name__) # Logger specifico per questo modulo class SymbolAnalysisProgressDialog(tk.Toplevel): """ Dialog to show progress of symbol analysis. """ def __init__( self, parent: tk.Widget ): # tk.Widget è un buon tipo generico per il parent super().__init__(parent) self.title("Symbol Analysis") self.parent_window = parent window_width = 600 window_height = 400 # Assicurati che il parent sia visibile e abbia una geometria prima di centrare self.parent_window.update_idletasks() # Importante per ottenere geometrie corrette del parent 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 = parent_x + (parent_width // 2) - (window_width // 2) position_y = parent_y + (parent_height // 2) - (window_height // 2) # Assicurati che le posizioni non siano negative (es. se il parent è molto piccolo o fuori schermo) position_x = max(0, position_x) position_y = max(0, position_y) self.geometry(f"{window_width}x{window_height}+{position_x}+{position_y}") self.transient(parent) self.grab_set() self.protocol("WM_DELETE_WINDOW", self._on_attempt_close) self.analysis_can_be_closed = False # Flag per controllare la chiusura main_frame = ttk.Frame(self, padding="10") main_frame.pack(expand=True, fill=tk.BOTH) self.log_text_widget = scrolledtext.ScrolledText( main_frame, wrap=tk.WORD, height=15, state=tk.DISABLED, font=("Consolas", 9) ) self.log_text_widget.pack(pady=5, padx=5, fill=tk.BOTH, expand=True) self.status_label_var = tk.StringVar(value="Initializing symbol analysis...") ttk.Label(main_frame, textvariable=self.status_label_var, justify=tk.LEFT).pack( pady=(5, 2), fill=tk.X ) self.progressbar = ttk.Progressbar(main_frame, mode="indeterminate", length=300) self.progressbar.pack(pady=(0, 10), fill=tk.X, padx=5) self.progressbar.start(15) # Intervallo di animazione in ms self.close_button = ttk.Button( main_frame, text="Close", command=self._on_close_button_click, state=tk.DISABLED, ) self.close_button.pack(pady=5) self.update_idletasks() def _on_attempt_close(self): if not self.analysis_can_be_closed: messagebox.showwarning( "Analysis in Progress", "Symbol analysis is still in progress. Please wait for it to complete or fail.", parent=self, ) # Specifica parent per il messagebox else: self.destroy() def _on_close_button_click(self): self.destroy() def log_message(self, message: str): if not self.winfo_exists(): return # Non fare nulla se il widget è distrutto self.log_text_widget.config(state=tk.NORMAL) self.log_text_widget.insert(tk.END, message + "\n") self.log_text_widget.see(tk.END) self.log_text_widget.config(state=tk.DISABLED) try: self.update_idletasks() # Forza l'aggiornamento della GUI except ( tk.TclError ): # Può accadere se il widget viene distrutto concorrentemente logger.warning( "TclError during log_message in SymbolAnalysisProgressDialog, widget might be destroyed." ) pass def set_status(self, status_message: str): if not self.winfo_exists(): return self.status_label_var.set(status_message) try: self.update_idletasks() except tk.TclError: logger.warning( "TclError during set_status in SymbolAnalysisProgressDialog, widget might be destroyed." ) pass def analysis_complete_or_failed(self, success: bool): if not self.winfo_exists(): return self.progressbar.stop() self.progressbar.config(mode="determinate") # Cambia in modalità determinata self.progressbar["value"] = 100 if success else 0 # Imposta al 100% o 0% self.analysis_can_be_closed = True self.close_button.config(state=tk.NORMAL) if success: self.set_status("Analysis complete. You can close this window.") else: self.set_status( "Analysis failed or was aborted. Check log. You can close this window." ) class SymbolListViewerDialog(tk.Toplevel): # (Implementazione come prima, ma con type hinting e logica di centratura migliorati) def __init__( self, parent: tk.Widget, symbols: List[Union[str, Dict[str, Any]]], title: str = "Symbol List", allow_multiple_selection: bool = False, return_full_dict: bool = False, ): super().__init__(parent) self.title(title) self.parent_window = parent self.result: Optional[ Union[str, Dict[str, Any], List[Union[str, Dict[str, Any]]]] ] = None self.allow_multiple_selection = allow_multiple_selection self.return_full_dict = return_full_dict window_width = 800 window_height = 550 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._original_symbols_data = symbols self._currently_displayed_data: List[Union[str, Dict[str, Any]]] = [] self.is_list_of_dicts = bool( self._original_symbols_data and isinstance(self._original_symbols_data[0], dict) ) main_frame = ttk.Frame(self, padding="10") main_frame.pack(expand=True, fill=tk.BOTH) main_frame.rowconfigure(1, weight=1) # Riga del Treeview si espande main_frame.columnconfigure(0, weight=1) # Colonna del Treeview si espande 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 ) select_mode = ( "extended" if self.allow_multiple_selection else "browse" ) # browse per singola selezione self.treeview = ttk.Treeview( main_frame, selectmode=select_mode, show="headings" ) # Configurazione colonne treeview self.treeview.column( "#0", width=0, stretch=tk.NO ) # Nasconde la prima colonna fittizia self.treeview.heading("#0", text="") self.treeview.grid(row=1, column=0, sticky="nsew") self._setup_treeview_columns() # Imposta le colonne reali scrollbar_y = ttk.Scrollbar( main_frame, orient=tk.VERTICAL, command=self.treeview.yview ) scrollbar_y.grid(row=1, column=1, sticky="ns") self.treeview.configure(yscrollcommand=scrollbar_y.set) scrollbar_x = ttk.Scrollbar( main_frame, orient=tk.HORIZONTAL, command=self.treeview.xview ) scrollbar_x.grid(row=2, column=0, sticky="ew") # Sotto il treeview self.treeview.configure(xscrollcommand=scrollbar_x.set) self._populate_treeview(self._original_symbols_data) self.treeview.bind("", self._on_ok) # Doppio click per confermare button_frame = ttk.Frame(main_frame, padding=(0, 10, 0, 0)) button_frame.grid( row=3, column=0, columnspan=2, sticky="e" ) # columnspan=2 se scrollbar_y è col 1 ttk.Button(button_frame, text="OK", command=self._on_ok).pack( side=tk.RIGHT, padx=5 ) ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack( side=tk.RIGHT ) self.protocol("WM_DELETE_WINDOW", self._on_cancel) # self.wait_window() # Rimosso, gestito dal chiamante def _setup_treeview_columns(self): # (Implementazione come prima) if self.is_list_of_dicts and self._original_symbols_data: first_item = self._original_symbols_data[0] if isinstance(first_item, dict): columns = list(first_item.keys()) self.treeview["columns"] = columns for col_name in columns: text_header = col_name.replace("_", " ").title() self.treeview.heading(col_name, text=text_header, anchor=tk.W) if col_name == "name": self.treeview.column( col_name, width=200, minwidth=120, stretch=tk.YES ) elif col_name == "signature": self.treeview.column( col_name, width=300, minwidth=150, stretch=tk.YES ) elif col_name == "file" or col_name == "path": self.treeview.column( col_name, width=250, minwidth=150, stretch=tk.YES ) elif col_name == "line": self.treeview.column( col_name, width=60, minwidth=40, stretch=tk.NO ) elif col_name == "type": self.treeview.column( col_name, width=150, minwidth=100, stretch=tk.YES ) elif col_name == "status": self.treeview.column( col_name, width=80, minwidth=70, stretch=tk.NO ) elif col_name == "definition": self.treeview.column( col_name, width=300, minwidth=150, stretch=tk.YES ) else: self.treeview.column( col_name, width=120, minwidth=80, stretch=tk.YES ) return self.treeview["columns"] = ["value"] self.treeview.heading("value", text="Value", anchor=tk.W) self.treeview.column("value", width=700, minwidth=200, stretch=tk.YES) def _populate_treeview(self, symbols_to_show: List[Union[str, Dict[str, Any]]]): # (Implementazione come prima) self.treeview.delete(*self.treeview.get_children()) self._currently_displayed_data = symbols_to_show for idx, item_data in enumerate(symbols_to_show): iid = str(idx) if self.is_list_of_dicts and isinstance(item_data, dict): values_for_row = [ item_data.get(col, "") for col in self.treeview["columns"] ] self.treeview.insert("", tk.END, iid=iid, values=values_for_row) else: self.treeview.insert("", tk.END, iid=iid, values=[str(item_data)]) def _apply_filter(self, *args): # (Implementazione come prima) filter_text = self.filter_var.get().lower() if not filter_text: self._populate_treeview(self._original_symbols_data) else: filtered_list: List[Union[str, Dict[str, Any]]] = [] if self.is_list_of_dicts: for ( item_data_unfiltered ) in self._original_symbols_data: # Itera sempre sulla lista originale if isinstance(item_data_unfiltered, dict): if any( str(v).lower().find(filter_text) != -1 for v in item_data_unfiltered.values() ): filtered_list.append(item_data_unfiltered) else: filtered_list = [ s for s in self._original_symbols_data if str(s).lower().find(filter_text) != -1 ] self._populate_treeview(filtered_list) def _on_ok(self, event: Optional[tk.Event] = None): # Aggiunto tipo per event # (Implementazione come prima) selected_iids = self.treeview.selection() if not selected_iids: self.result = [] if self.allow_multiple_selection else None self.destroy() return selected_items_to_return = [] for iid_str in selected_iids: try: current_list_index = int(iid_str) if 0 <= current_list_index < len(self._currently_displayed_data): selected_data_item_from_current_list = ( self._currently_displayed_data[current_list_index] ) if self.return_full_dict: selected_items_to_return.append( selected_data_item_from_current_list ) else: if isinstance(selected_data_item_from_current_list, dict): name_to_return = selected_data_item_from_current_list.get( "name" ) if name_to_return is None: name_to_return = ( selected_data_item_from_current_list.get("path") ) if name_to_return is None: name_to_return = str( next( iter( selected_data_item_from_current_list.values() ), "", ) ) selected_items_to_return.append(name_to_return) else: selected_items_to_return.append( str(selected_data_item_from_current_list) ) else: logger.warning( f"Selected iid index {current_list_index} out of bounds for _currently_displayed_data (len: {len(self._currently_displayed_data)})" ) except ValueError: logger.error(f"Could not convert iid '{iid_str}' to int.") except Exception as e: logger.error(f"Error processing selected item with iid {iid_str}: {e}") if self.allow_multiple_selection: self.result = selected_items_to_return else: self.result = ( selected_items_to_return[0] if selected_items_to_return else None ) self.destroy() def _on_cancel(self): # (Implementazione come prima) self.result = [] if self.allow_multiple_selection else None self.destroy() class FunctionSelectorDialog(tk.Toplevel): # (Implementazione come prima, ma con type hinting e logica di centratura migliorati) def __init__( self, parent: tk.Widget, functions_list: List[str], title: str = "Select Function", ): super().__init__(parent) self.title(title) self.parent_window = parent self.result: Optional[str] = None window_width = 500 window_height = 400 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() main_frame = ttk.Frame(self, padding="10") main_frame.pack(expand=True, fill=tk.BOTH) main_frame.rowconfigure(1, weight=1) # Riga della Listbox si espande main_frame.columnconfigure(0, weight=1) # Colonna della Listbox si espande filter_frame = ttk.Frame(main_frame) filter_frame.grid(row=0, column=0, columnspan=2, 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_to_listbox) 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 ) # selectmode SINGLE per Listbox self.listbox.grid(row=1, column=0, sticky="nsew") scrollbar = ttk.Scrollbar( main_frame, orient=tk.VERTICAL, command=self.listbox.yview ) scrollbar.grid(row=1, column=1, sticky="ns") self.listbox.configure(yscrollcommand=scrollbar.set) self._original_functions_list = sorted( functions_list ) # Salva e ordina l'originale self._populate_listbox(self._original_functions_list) self.listbox.bind("", self._on_ok) button_frame = ttk.Frame(main_frame, padding=(0, 10, 0, 0)) button_frame.grid(row=2, column=0, columnspan=2, sticky="e") ttk.Button(button_frame, text="OK", command=self._on_ok).pack( side=tk.RIGHT, padx=5 ) ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack( side=tk.RIGHT ) if self.listbox.size() > 0: self.listbox.selection_set(0) # Seleziona il primo elemento self.listbox.focus_set() # Imposta il focus sulla listbox self.protocol("WM_DELETE_WINDOW", self._on_cancel) # self.wait_window() # Rimosso, gestito dal chiamante def _populate_listbox(self, items: List[str]): # (Implementazione come prima) self.listbox.delete(0, tk.END) for item in items: self.listbox.insert(tk.END, item) if self.listbox.size() > 0: self.listbox.selection_set(0) # Mantiene la selezione del primo elemento def _apply_filter_to_listbox(self, *args): # (Implementazione come prima) filter_text = self.filter_var.get().lower() if not filter_text: self._populate_listbox(self._original_functions_list) else: filtered_list = [ f for f in self._original_functions_list if filter_text in f.lower() ] self._populate_listbox(filtered_list) def _on_ok(self, event: Optional[tk.Event] = None): # Aggiunto tipo per event # (Implementazione come prima) selection = self.listbox.curselection() if selection: self.result = self.listbox.get(selection[0]) self.destroy() def _on_cancel(self): # (Implementazione come prima) self.result = None self.destroy()