SXXXXXXX_PyUCC/pyucc/gui/export_handler.py

301 lines
10 KiB
Python

"""
Export handler for PyUCC GUI.
Handles exporting results to CSV and other formats, including UCC-style reports.
"""
from tkinter import filedialog, messagebox
from pathlib import Path
from datetime import datetime
class ExportHandler:
"""Handles export functionality for results."""
def __init__(self, gui_app):
"""
Args:
gui_app: Reference to the main GUI application instance
"""
self.app = gui_app
def export_to_csv(self):
"""Export current results tree to CSV file."""
from ..utils.csv_exporter import export_rows_to_csv
path = filedialog.asksaveasfilename(
defaultextension=".csv",
filetypes=[("CSV files", "*.csv"), ("All files", "*")],
)
if not path:
return
headers = [c for c in self.app.results_tree["columns"]]
rows = (
self.app.results_tree.item(child, "values")
for child in self.app.results_tree.get_children()
)
try:
written = export_rows_to_csv(path, headers, rows)
messagebox.showinfo("Export", f"Exported {written} rows to {path}")
except Exception as e:
messagebox.showerror("Export Error", str(e))
def save_ucc_report(self):
"""Save UCC-style report based on current action and results."""
# Determine which action is currently active
current_action = getattr(self.app, "_current_action", None)
if not current_action:
messagebox.showwarning("Save Report", "No results available to save")
return
# Get default filename based on action
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
default_name = f"PyUcc_{current_action}_{timestamp}_outfile.txt"
path = filedialog.asksaveasfilename(
defaultextension=".txt",
initialfile=default_name,
filetypes=[("Text files", "*.txt"), ("All files", "*")],
)
if not path:
return
try:
if current_action == "countings":
self._save_counting_report(Path(path))
elif current_action == "differ":
self._save_differ_report(Path(path))
elif current_action == "duplicates":
self._save_duplicates_report(Path(path))
elif current_action == "metrics":
self._save_metrics_report(Path(path))
elif current_action == "scan":
self._save_scan_report(Path(path))
else:
messagebox.showwarning(
"Save Report", f"Report not implemented for {current_action}"
)
return
# Show success dialog with option to open the report
self._show_report_saved_dialog(path)
except Exception as e:
messagebox.showerror("Save Report Error", str(e))
def _show_report_saved_dialog(self, report_path: str):
"""Show dialog with option to open the saved report."""
import tkinter as tk
from tkinter import ttk
import subprocess
import os
# Create custom dialog
dialog = tk.Toplevel(self.app)
dialog.title("Report Saved")
dialog.transient(self.app)
dialog.grab_set()
# Center the dialog
dialog.geometry("500x180")
dialog.resizable(False, False)
# Icon
try:
dialog.iconbitmap(
default=str(Path(__file__).parent.parent / "assets" / "icon.ico")
)
except:
pass
# Main frame
main_frame = ttk.Frame(dialog, padding=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# Success icon and message
ttk.Label(
main_frame,
text="✅ Report saved successfully!",
font=("Segoe UI", 11, "bold"),
).pack(pady=(0, 10))
# File path (with text wrapping)
path_frame = ttk.Frame(main_frame)
path_frame.pack(fill=tk.X, pady=(0, 20))
ttk.Label(path_frame, text="Location:", font=("Segoe UI", 9)).pack(anchor=tk.W)
path_text = tk.Text(
path_frame,
height=2,
wrap=tk.WORD,
font=("Consolas", 9),
bg="#f0f0f0",
relief=tk.FLAT,
)
path_text.insert("1.0", report_path)
path_text.config(state=tk.DISABLED)
path_text.pack(fill=tk.X, pady=(5, 0))
# Buttons frame
btn_frame = ttk.Frame(main_frame)
btn_frame.pack(fill=tk.X)
def open_report():
"""Open report file with default text editor."""
try:
if os.name == "nt": # Windows
os.startfile(report_path)
elif os.name == "posix": # macOS/Linux
subprocess.run(["xdg-open", report_path])
dialog.destroy()
except Exception as e:
messagebox.showerror(
"Open Error", f"Could not open file:\n{e}", parent=dialog
)
def close_dialog():
dialog.destroy()
# Open Report button (primary)
open_btn = ttk.Button(
btn_frame, text="📄 Open Report", command=open_report, width=20
)
open_btn.pack(side=tk.LEFT, padx=(0, 10))
# Close button
close_btn = ttk.Button(btn_frame, text="Close", command=close_dialog, width=15)
close_btn.pack(side=tk.LEFT)
# Center dialog on parent
dialog.update_idletasks()
x = (
self.app.winfo_x()
+ (self.app.winfo_width() // 2)
- (dialog.winfo_width() // 2)
)
y = (
self.app.winfo_y()
+ (self.app.winfo_height() // 2)
- (dialog.winfo_height() // 2)
)
dialog.geometry(f"+{x}+{y}")
# Set focus to Open button
open_btn.focus_set()
# Bind Enter key to open report
dialog.bind("<Return>", lambda e: open_report())
dialog.bind("<Escape>", lambda e: close_dialog())
def _save_counting_report(self, output_path: Path):
"""Save counting results as UCC-style report."""
from ..utils.ucc_report_generator import UCCReportGenerator
# Collect results from cache
results_cache = getattr(self.app, "_results_cache", None)
if not results_cache:
raise ValueError("No counting results available")
# Convert cache to list if it's a dict
if isinstance(results_cache, dict):
results = list(results_cache.values())
elif isinstance(results_cache, list):
results = results_cache
else:
raise ValueError("Invalid results cache format")
# Get project root
project_root = getattr(self.app, "_last_scan_root", "")
# Generate command description
cmd_desc = f"PyUcc Counting Analysis - Project: {project_root}"
UCCReportGenerator.generate_counting_report(
results=results,
output_path=output_path,
command_description=cmd_desc,
base_path=project_root,
)
def _save_differ_report(self, output_path: Path):
"""Save differ results as UCC-style report."""
from ..utils.ucc_report_generator import UCCReportGenerator
# Get differ results from _current_results
differ_result = getattr(self.app, "_current_results", None)
if not differ_result:
raise ValueError("No differ results available")
# Extract matched pairs
pairs = differ_result.get("pairs", [])
baseline_id = differ_result.get("baseline_id", "unknown")
# Generate command description
cmd_desc = f"PyUcc Differential Analysis - Baseline: {baseline_id}"
UCCReportGenerator.generate_differ_report(
diff_results=pairs,
output_path=output_path,
baseline_id=baseline_id,
command_description=cmd_desc,
)
def _save_metrics_report(self, output_path: Path):
"""Save metrics results as UCC-style report (Cyclomatic Complexity)."""
from ..utils.ucc_report_generator import UCCReportGenerator
# Collect results from cache
results_cache = getattr(self.app, "_results_cache", None)
if not results_cache:
raise ValueError("No metrics results available")
# Convert cache to list if it's a dict
if isinstance(results_cache, dict):
results = list(results_cache.values())
elif isinstance(results_cache, list):
results = results_cache
else:
raise ValueError("Invalid results cache format")
# Get project root
project_root = getattr(self.app, "_last_scan_root", "")
# Generate command description
cmd_desc = f"PyUcc Cyclomatic Complexity Analysis - Project: {project_root}"
UCCReportGenerator.generate_metrics_report(
results=results,
output_path=output_path,
command_description=cmd_desc,
base_path=project_root,
)
def _save_scan_report(self, output_path: Path):
"""Save scan results as UCC-style report."""
# Scan is just a file list, can reuse counting format
self._save_counting_report(output_path)
def _save_duplicates_report(self, output_path: Path):
"""Save duplicates results in UCC-style report using UCCReportGenerator."""
from ..utils.ucc_report_generator import UCCReportGenerator
# Expect cached results in app._results_cache as list of dicts
results_cache = getattr(self.app, "_results_cache", None)
if not results_cache:
raise ValueError("No duplicates results available")
# Generate a descriptive command text
project_root = getattr(self.app, "_last_scan_root", "")
cmd_desc = f"PyUcc Duplicate Analysis - Project: {project_root}"
UCCReportGenerator.generate_duplicates_report(
duplicates=results_cache,
output_path=output_path,
command_description=cmd_desc,
base_path=project_root,
params=getattr(self.app, "_current_results", {}).get("params"),
)