310 lines
14 KiB
Python
310 lines
14 KiB
Python
# 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() |