# markdownconverter/gui/gui.py import os import sys import logging import subprocess import tkinter as tk from datetime import date import ttkbootstrap as tb from tkinter.scrolledtext import ScrolledText from ttkbootstrap.constants import * from tkinter import filedialog, messagebox, StringVar, BooleanVar from ..core.core import convert_markdown, convert_docx_to_pdf, ConverterNotFoundError from ..utils.config import save_configuration, load_configuration from ..utils.logger import ( setup_basic_logging, add_tkinter_handler, shutdown_logging_system, get_logger ) from .editor import EditorWindow log = get_logger(__name__) def open_with_default_app(filepath): if not filepath or not os.path.exists(filepath): log.warning("Open file/folder requested, but path is invalid or not provided.") messagebox.showwarning("Warning", "No output file or folder to open.") return try: log.info(f"Opening '{filepath}' with default application.") if sys.platform == "win32": os.startfile(filepath) elif sys.platform == "darwin": subprocess.run(["open", filepath], check=True) else: subprocess.run(["xdg-open", filepath], check=True) except Exception as e: log.error(f"Failed to open '{filepath}': {e}", exc_info=True) messagebox.showerror("Error", f"Could not open the file/folder:\n{str(e)}") def open_output_folder(filepath): if not filepath: log.warning("Open folder requested, but no output file has been generated yet.") messagebox.showwarning("Warning", "No output file has been generated yet.") return folder = os.path.dirname(filepath) open_with_default_app(folder) def run_app(): app = tb.Window(themename="sandstone") app.title("Markdown Converter") app.geometry("900x850") app.resizable(True, True) log_config = { "default_root_level": logging.DEBUG, "format": "%(asctime)s [%(levelname)-8s] %(name)-20s: %(message)s", "date_format": "%H:%M:%S", "enable_console": True, "enable_file": True, "file_path": "markdown_converter.log", "colors": { logging.DEBUG: "gray", logging.INFO: "black", logging.WARNING: "orange", logging.ERROR: "red", logging.CRITICAL: "purple", }, } setup_basic_logging(root_tk_instance_for_processor=app, logging_config_dict=log_config) config = load_configuration() metadata_config = config.get("metadata", {}) # --- Variabili di stato della GUI --- selected_file = StringVar(value=config.get("last_markdown_file", "")) selected_template = StringVar(value=config.get("last_template_file", "")) add_toc_var = BooleanVar(value=config.get("add_toc", True)) doc_security_var = StringVar(value=metadata_config.get("DOC_SECURITY", "")) doc_number_var = StringVar(value=metadata_config.get("DOC_NUMBER", "")) doc_rev_var = StringVar(value=metadata_config.get("DOC_REV", "")) doc_project_var = StringVar(value=metadata_config.get("DOC_PROJECT", "")) customer_var = StringVar(value=metadata_config.get("DOC_CUSTOMER", "")) docx_output_path = StringVar() pdf_direct_output_path = StringVar() pdf_from_docx_output_path = StringVar() # --- Funzioni di supporto --- def _confirm_overwrite(filepath: str) -> bool: """Checks if a file exists and asks the user for overwrite confirmation.""" if os.path.exists(filepath): return messagebox.askyesno( title="Confirm Overwrite", message=f"The file already exists:\n\n{filepath}\n\nDo you want to overwrite it?" ) return True # File doesn't exist, proceed. def _update_output_paths(*args): source_path = selected_file.get() if not source_path: return output_dir = os.path.dirname(source_path) project = doc_project_var.get() or "NoProject" doc_num = doc_number_var.get() or "NoNum" rev = doc_rev_var.get() or "NoRev" today = date.today().strftime("%Y%m%d") docx_filename = f"{project}_SUM_{doc_num}_{rev}_{today}.docx" docx_output_path.set(os.path.join(output_dir, docx_filename).replace("\\", "/")) pdf_from_docx_filename = f"{project}_SUM_{doc_num}_{rev}_{today}.pdf" pdf_from_docx_output_path.set(os.path.join(output_dir, pdf_from_docx_filename).replace("\\", "/")) pdf_direct_filename = f"{project}_SUM_{today}.pdf" pdf_direct_output_path.set(os.path.join(output_dir, pdf_direct_filename).replace("\\", "/")) for var in [selected_file, doc_project_var, doc_number_var, doc_rev_var]: var.trace_add("write", _update_output_paths) # --- Funzioni associate agli eventi della GUI --- def open_editor(): file_path = selected_file.get() if not file_path or not os.path.exists(file_path): messagebox.showerror("Error", "Please select a valid Markdown file first.") return EditorWindow(parent=app, file_path=file_path) def browse_markdown(): path = filedialog.askopenfilename(filetypes=[("Markdown files", "*.md"), ("All files", "*.*")]) if path: selected_file.set(path) def browse_template(): path = filedialog.askopenfilename( title="Select a Template Document", filetypes=[("Word Documents", "*.docx"), ("All files", "*.*")] ) if path: selected_template.set(path) edit_template_btn.config(state=tk.NORMAL) docx_to_pdf_btn = None def convert_from_docx_to_pdf(): input_path = docx_output_path.get() output_path = pdf_from_docx_output_path.get() if not input_path or not os.path.exists(input_path): messagebox.showerror("Error", "The source DOCX file does not exist. Generate it first.") return if not output_path: messagebox.showerror("Error", "Please specify a valid output path for the PDF.") return if not _confirm_overwrite(output_path): return # User cancelled overwrite try: pdf_output = convert_docx_to_pdf(input_path, output_path) messagebox.showinfo("Success", f"DOCX successfully converted to PDF:\n{pdf_output}") except ConverterNotFoundError as e: messagebox.showerror("Converter Not Found", str(e)) except Exception as e: messagebox.showerror( "DOCX to PDF Conversion Error", f"An unexpected error occurred.\nError: {str(e)}" ) def convert(fmt): nonlocal docx_to_pdf_btn if docx_to_pdf_btn: docx_to_pdf_btn.config(state=tk.DISABLED) input_path = selected_file.get() if not input_path: messagebox.showerror("Error", "Please select a Markdown file.") return template = selected_template.get() if fmt == "DOCX" and not template: messagebox.showwarning("Warning", "A DOCX template is required.") return output_path_for_conversion = "" if fmt == "DOCX": output_path_for_conversion = docx_output_path.get() elif fmt == "PDF": output_path_for_conversion = pdf_direct_output_path.get() if not output_path_for_conversion: messagebox.showerror("Error", "Please specify a valid output path.") return if not _confirm_overwrite(output_path_for_conversion): return # User cancelled overwrite metadata_to_pass = { 'DOC_SECURITY': doc_security_var.get(), 'DOC_NUMBER': doc_number_var.get(), 'DOC_REV': doc_rev_var.get(), 'DOC_PROJECT': doc_project_var.get(), 'DOC_CUSTOMER': customer_var.get(), } try: output = convert_markdown( input_file=input_path, output_path=output_path_for_conversion, # Pass the final path to the core output_format=fmt, add_toc=add_toc_var.get(), template_path=template, metadata=metadata_to_pass, ) messagebox.showinfo("Success", f"File converted successfully:\n{output}") save_configuration( last_markdown=input_path, last_template=template, add_toc=add_toc_var.get(), metadata=metadata_to_pass ) if fmt == "DOCX" and docx_to_pdf_btn: docx_to_pdf_btn.config(state=tk.NORMAL) except Exception as e: log.critical(f"An error occurred during conversion: {e}", exc_info=True) messagebox.showerror("Error", f"An error occurred during conversion:\n{str(e)}") # --- Costruzione della GUI --- container = tb.Frame(app, padding=10) container.pack(fill=tk.BOTH, expand=True) input_frame = tb.Labelframe(container, text="Input Files", padding=10) input_frame.pack(fill=tk.X, side=tk.TOP, pady=(0, 10)) input_frame.columnconfigure(1, weight=1) tb.Label(input_frame, text="Markdown File:").grid(row=0, column=0, padx=(0, 10), pady=5, sticky="w") tb.Entry(input_frame, textvariable=selected_file).grid(row=0, column=1, padx=5, pady=5, sticky="ew") file_button_frame = tb.Frame(input_frame) file_button_frame.grid(row=0, column=2, sticky="w") tb.Button(file_button_frame, text="Browse...", command=browse_markdown, bootstyle=PRIMARY).pack(side=tk.LEFT, padx=(0, 5)) tb.Button(file_button_frame, text="Edit...", command=open_editor, bootstyle=INFO).pack(side=tk.LEFT) tb.Label(input_frame, text="Template File:").grid(row=1, column=0, padx=(0, 10), pady=5, sticky="w") tb.Entry(input_frame, textvariable=selected_template).grid(row=1, column=1, padx=5, pady=5, sticky="ew") template_button_frame = tb.Frame(input_frame) template_button_frame.grid(row=1, column=2, sticky="w") tb.Button(template_button_frame, text="Browse...", command=browse_template, bootstyle=SECONDARY).pack(side=tk.LEFT, padx=(0, 5)) edit_template_btn = tb.Button(template_button_frame, text="Edit...", command=lambda: open_with_default_app(selected_template.get()), bootstyle="info", state=tk.DISABLED) edit_template_btn.pack(side=tk.LEFT) metadata_frame = tb.Labelframe(container, text="Template Placeholders", padding=10) metadata_frame.pack(fill=tk.X, side=tk.TOP, pady=(0, 10)) metadata_frame.columnconfigure(1, weight=1) metadata_frame.columnconfigure(3, weight=1) tb.Label(metadata_frame, text="Project Name:").grid(row=0, column=0, padx=5, pady=3, sticky="w") tb.Entry(metadata_frame, textvariable=doc_project_var).grid(row=0, column=1, padx=5, pady=3, sticky="ew") tb.Label(metadata_frame, text="Customer:").grid(row=0, column=2, padx=(10, 5), pady=3, sticky="w") tb.Entry(metadata_frame, textvariable=customer_var).grid(row=0, column=3, padx=5, pady=3, sticky="ew") tb.Label(metadata_frame, text="Document Number:").grid(row=1, column=0, padx=5, pady=3, sticky="w") tb.Entry(metadata_frame, textvariable=doc_number_var).grid(row=1, column=1, padx=5, pady=3, sticky="ew") tb.Label(metadata_frame, text="Revision:").grid(row=1, column=2, padx=(10, 5), pady=3, sticky="w") tb.Entry(metadata_frame, textvariable=doc_rev_var).grid(row=1, column=3, padx=5, pady=3, sticky="ew") tb.Label(metadata_frame, text="Security Class:").grid(row=2, column=0, padx=5, pady=3, sticky="w") tb.Entry(metadata_frame, textvariable=doc_security_var).grid(row=2, column=1, padx=5, pady=3, sticky="ew") options_frame = tb.Frame(container) options_frame.pack(fill=tk.X, pady=(0, 10)) tb.Checkbutton(options_frame, text="Add Table of Contents", variable=add_toc_var, bootstyle="primary-round-toggle").pack(side=tk.LEFT, padx=(0, 20)) tb.Button(options_frame, text="Open Source Folder", command=lambda: open_output_folder(selected_file.get()), bootstyle=WARNING).pack(side=tk.LEFT, padx=5) action_frame = tb.Labelframe(container, text="Conversion Actions", padding=10) action_frame.pack(fill=tk.X, pady=10) action_frame.columnconfigure(1, weight=1) tb.Button(action_frame, text="MD -> DOCX", command=lambda: convert("DOCX"), bootstyle=SUCCESS, width=12).grid(row=0, column=0, padx=5, pady=5) tb.Entry(action_frame, textvariable=docx_output_path).grid(row=0, column=1, padx=5, pady=5, sticky="ew") tb.Button(action_frame, text="Open", command=lambda: open_with_default_app(docx_output_path.get()), bootstyle="secondary").grid(row=0, column=2, padx=5, pady=5) tb.Button(action_frame, text="MD -> PDF", command=lambda: convert("PDF"), bootstyle=SUCCESS, width=12).grid(row=1, column=0, padx=5, pady=5) tb.Entry(action_frame, textvariable=pdf_direct_output_path).grid(row=1, column=1, padx=5, pady=5, sticky="ew") tb.Button(action_frame, text="Open", command=lambda: open_with_default_app(pdf_direct_output_path.get()), bootstyle="secondary").grid(row=1, column=2, padx=5, pady=5) docx_to_pdf_btn = tb.Button(action_frame, text="DOCX -> PDF", command=convert_from_docx_to_pdf, bootstyle="info", width=12, state=tk.DISABLED) docx_to_pdf_btn.grid(row=2, column=0, padx=5, pady=5) tb.Entry(action_frame, textvariable=pdf_from_docx_output_path).grid(row=2, column=1, padx=5, pady=5, sticky="ew") tb.Button(action_frame, text="Open", command=lambda: open_with_default_app(pdf_from_docx_output_path.get()), bootstyle="secondary").grid(row=2, column=2, padx=5, pady=5) log_frame = tb.Labelframe(container, text="Log Viewer", padding=10) log_frame.pack(fill=tk.BOTH, expand=True) log_text_widget = ScrolledText(log_frame, wrap=tk.WORD, state=tk.DISABLED, height=10) log_text_widget.pack(fill=tk.BOTH, expand=True) add_tkinter_handler(gui_log_widget=log_text_widget, root_tk_instance_for_gui_handler=app, logging_config_dict=log_config) _update_output_paths() if selected_template.get(): edit_template_btn.config(state=tk.NORMAL) def on_closing(): shutdown_logging_system() app.destroy() app.protocol("WM_DELETE_WINDOW", on_closing) log.info("Application GUI initialized successfully.") app.mainloop()