344 lines
16 KiB
Python
344 lines
16 KiB
Python
# File: cpp_python_debug/gui/dialogs.py
|
|
import tkinter as tk
|
|
from tkinter import ttk, messagebox, scrolledtext
|
|
import logging
|
|
from typing import List, Optional, Dict, Any, Union
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class SymbolAnalysisProgressDialog(tk.Toplevel):
|
|
"""
|
|
Dialog to show progress of symbol analysis.
|
|
"""
|
|
def __init__(self, parent: tk.Widget):
|
|
super().__init__(parent)
|
|
self.title("Symbol Analysis")
|
|
|
|
# Centra la finestra rispetto al genitore
|
|
parent_x = parent.winfo_x()
|
|
parent_y = parent.winfo_y()
|
|
parent_width = parent.winfo_width()
|
|
parent_height = parent.winfo_height()
|
|
width = 600
|
|
height = 400
|
|
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() # Rendi modale
|
|
self.protocol("WM_DELETE_WINDOW", self._on_attempt_close)
|
|
self.analysis_can_be_closed = False
|
|
|
|
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)
|
|
|
|
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() # Assicura che la finestra sia disegnata
|
|
|
|
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.",
|
|
parent=self)
|
|
else:
|
|
self.destroy()
|
|
|
|
def _on_close_button_click(self):
|
|
self.destroy()
|
|
|
|
def log_message(self, message: str):
|
|
if not self.winfo_exists(): return
|
|
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()
|
|
except tk.TclError: # Può accadere se la finestra viene distrutta mentre si logga
|
|
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:
|
|
pass
|
|
|
|
def analysis_complete_or_failed(self, success: bool):
|
|
if not self.winfo_exists(): return
|
|
self.progressbar.stop()
|
|
self.progressbar.config(mode='determinate')
|
|
self.progressbar['value'] = 100 if success else 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):
|
|
"""
|
|
A dialog to view a list of symbols with a filter and dynamic columns.
|
|
Accepts either a list of strings or a list of dictionaries.
|
|
Allows single or multiple selection.
|
|
"""
|
|
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): # New parameter
|
|
super().__init__(parent)
|
|
self.title(title)
|
|
self.parent_window = parent # Store parent for focus management
|
|
self.result: Optional[Union[str, Dict[str, Any], List[Union[str, Dict[str, Any]]]]] = None # Can be single or list
|
|
self.allow_multiple_selection = allow_multiple_selection
|
|
self.return_full_dict = return_full_dict # If true, returns the dict, else just the 'name' or 'path'
|
|
|
|
# Centra la finestra rispetto al genitore
|
|
parent_x = parent.winfo_x()
|
|
parent_y = parent.winfo_y()
|
|
parent_width = parent.winfo_width()
|
|
parent_height = parent.winfo_height()
|
|
width = 800
|
|
height = 550
|
|
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()
|
|
|
|
self._original_symbols_data = symbols # Store original data which might be list of dicts
|
|
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) # The Treeview expands
|
|
main_frame.columnconfigure(0, weight=1) # The Treeview expands
|
|
|
|
# Filter entry
|
|
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)
|
|
|
|
# Treeview to display symbols
|
|
select_mode = "extended" if self.allow_multiple_selection else "browse"
|
|
self.treeview = ttk.Treeview(main_frame, selectmode=select_mode, show="headings")
|
|
self.treeview.column("#0", width=0, stretch=tk.NO)
|
|
self.treeview.heading("#0", text="")
|
|
self.treeview.grid(row=1, column=0, sticky="nsew")
|
|
|
|
# Define columns dynamically
|
|
self._setup_treeview_columns()
|
|
|
|
# Scrollbars
|
|
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")
|
|
self.treeview.configure(xscrollcommand=scrollbar_x.set)
|
|
|
|
self._populate_treeview(self._original_symbols_data)
|
|
self.treeview.bind("<Double-1>", self._on_ok) # Double click for OK
|
|
|
|
# Buttons
|
|
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="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()
|
|
|
|
|
|
def _setup_treeview_columns(self):
|
|
"""Sets up the Treeview columns based on the data type."""
|
|
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)
|
|
# Define default column widths, adjust as needed
|
|
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
|
|
# Fallback for list of strings or if dict structure is not as expected
|
|
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]]]):
|
|
"""Populates the treeview with the provided symbols."""
|
|
self.treeview.delete(*self.treeview.get_children()) # Clear existing items
|
|
|
|
for idx, item_data in enumerate(symbols_to_show):
|
|
# Store the original index to retrieve the full dict later if needed
|
|
iid = str(idx) # Unique item ID for the treeview
|
|
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: # List of strings
|
|
self.treeview.insert("", tk.END, iid=iid, values=[str(item_data)])
|
|
|
|
|
|
def _apply_filter(self, *args):
|
|
"""Applies the filter to the displayed list of symbols."""
|
|
filter_text = self.filter_var.get().lower()
|
|
|
|
if not filter_text:
|
|
self._populate_treeview(self._original_symbols_data)
|
|
else:
|
|
filtered_list = []
|
|
if self.is_list_of_dicts:
|
|
for item_dict in self._original_symbols_data:
|
|
if isinstance(item_dict, dict):
|
|
if any(str(v).lower().find(filter_text) != -1 for v in item_dict.values()):
|
|
filtered_list.append(item_dict)
|
|
else: # List of strings
|
|
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=None):
|
|
selected_iids = self.treeview.selection()
|
|
if not selected_iids:
|
|
self.result = [] if self.allow_multiple_selection else None
|
|
self.destroy()
|
|
return
|
|
|
|
selected_items = []
|
|
for iid in selected_iids:
|
|
try:
|
|
original_index = int(iid) # Retrieve the original index stored as iid
|
|
selected_data_item = self._original_symbols_data[original_index]
|
|
|
|
if self.return_full_dict:
|
|
selected_items.append(selected_data_item)
|
|
else:
|
|
if isinstance(selected_data_item, dict):
|
|
# Prioritize 'name', then 'path', then the first value as fallback
|
|
name = selected_data_item.get("name", selected_data_item.get("path"))
|
|
if name is None: # Fallback if neither 'name' nor 'path' exists
|
|
name = str(next(iter(selected_data_item.values()), ""))
|
|
selected_items.append(name)
|
|
else: # It's a string
|
|
selected_items.append(str(selected_data_item))
|
|
except (ValueError, IndexError) as e:
|
|
logger.error(f"Error retrieving selected item with iid {iid}: {e}")
|
|
|
|
|
|
if self.allow_multiple_selection:
|
|
self.result = selected_items
|
|
else:
|
|
self.result = selected_items[0] if selected_items else None
|
|
|
|
self.destroy()
|
|
|
|
def _on_cancel(self):
|
|
self.result = [] if self.allow_multiple_selection else None
|
|
self.destroy()
|
|
|
|
# FunctionSelectorDialog è utilizzata da action_editor_window.py
|
|
# La definiamo qui per spostare tutto ciò che è dialogo in questo file.
|
|
# È simile a SymbolListViewerDialog ma gestisce una selezione.
|
|
class FunctionSelectorDialog(tk.Toplevel): # Kept simple as it deals with a list of strings for now
|
|
"""Dialog to select a function from a list."""
|
|
def __init__(self, parent: tk.Widget, functions_list: List[str], title: str = "Select Function"):
|
|
super().__init__(parent)
|
|
self.title(title)
|
|
self.geometry("500x400")
|
|
self.transient(parent)
|
|
self.grab_set()
|
|
self.result: Optional[str] = None
|
|
|
|
main_frame = ttk.Frame(self, padding="10")
|
|
main_frame.pack(expand=True, fill=tk.BOTH)
|
|
main_frame.rowconfigure(1, weight=1) # Listbox row
|
|
main_frame.columnconfigure(0, weight=1) # Listbox column
|
|
|
|
# Filter entry (NEW)
|
|
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)
|
|
self.listbox.grid(row=1, column=0, sticky="nsew") # Moved to row 1
|
|
|
|
scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.listbox.yview)
|
|
scrollbar.grid(row=1, column=1, sticky="ns") # Moved to row 1
|
|
self.listbox.configure(yscrollcommand=scrollbar.set)
|
|
|
|
self._original_functions_list = sorted(functions_list) # Keep original sorted list
|
|
self._populate_listbox(self._original_functions_list)
|
|
|
|
|
|
self.listbox.bind("<Double-Button-1>", 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") # Moved to row 2
|
|
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 : # Check if listbox has items after populating
|
|
self.listbox.selection_set(0)
|
|
self.listbox.focus_set()
|
|
|
|
self.protocol("WM_DELETE_WINDOW", self._on_cancel)
|
|
self.wait_window()
|
|
|
|
def _populate_listbox(self, items: List[str]):
|
|
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) # Reselect first if items exist
|
|
|
|
def _apply_filter_to_listbox(self, *args):
|
|
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=None):
|
|
selection = self.listbox.curselection()
|
|
if selection:
|
|
self.result = self.listbox.get(selection[0])
|
|
self.destroy()
|
|
|
|
def _on_cancel(self):
|
|
self.result = None
|
|
self.destroy() |