657 lines
25 KiB
Python
657 lines
25 KiB
Python
# markdownconverter/gui/gui.py
|
||
|
||
import os
|
||
import sys
|
||
import logging
|
||
import re
|
||
import subprocess
|
||
import tkinter as tk
|
||
from datetime import date
|
||
from tkinter import simpledialog
|
||
from pathlib import Path
|
||
import ttkbootstrap as tb
|
||
from tkinter.scrolledtext import ScrolledText
|
||
from ttkbootstrap.scrolled import ScrolledFrame
|
||
from ttkbootstrap.constants import *
|
||
from tkinter import filedialog, messagebox, StringVar, BooleanVar
|
||
|
||
from ..core.core import (
|
||
convert_markdown,
|
||
convert_docx_to_pdf,
|
||
scan_template_for_placeholders,
|
||
ConverterNotFoundError,
|
||
TemplatePlaceholderError,
|
||
)
|
||
from ..utils.config import (
|
||
save_configuration,
|
||
load_configuration,
|
||
KEY_LAST_MARKDOWN,
|
||
KEY_LAST_PROFILE,
|
||
KEY_PROFILES,
|
||
KEY_TEMPLATE_PATH,
|
||
KEY_VALUES,
|
||
)
|
||
from ..utils.logger import (
|
||
setup_basic_logging,
|
||
add_tkinter_handler,
|
||
shutdown_logging_system,
|
||
get_logger,
|
||
)
|
||
from ..utils.error_handler import handle_conversion_error
|
||
from .batch_converter import BatchConverterTab
|
||
from .pdf_to_markdown import PdfToMarkdownTab
|
||
|
||
# EditorWindow non viene usato in questo file, ma lo lasciamo per coerenza
|
||
# from .editor import EditorWindow
|
||
|
||
try:
|
||
from markdownconverter import _version as wrapper_version
|
||
|
||
WRAPPER_APP_VERSION_STRING = f"{wrapper_version.__version__} ({wrapper_version.GIT_BRANCH}/{wrapper_version.GIT_COMMIT_HASH[:7]})"
|
||
except ImportError:
|
||
WRAPPER_APP_VERSION_STRING = "(Dev Wrapper)"
|
||
|
||
log = get_logger(__name__)
|
||
|
||
# ... (open_with_default_app, open_output_folder, ProfileManagerWindow sono INVARIATE)
|
||
# ... (ti fornisco comunque il codice completo per sicurezza)
|
||
|
||
|
||
def open_with_default_app(filepath: str):
|
||
log.info(f"Request to open file at path: '{filepath}'")
|
||
p = Path(filepath)
|
||
if not p.is_file():
|
||
log.warning("Open file requested, but path is invalid or file does not exist.")
|
||
messagebox.showwarning("Warning", "No output file or folder to open.")
|
||
return
|
||
try:
|
||
log.info(f"Opening '{p}' with default application.")
|
||
if sys.platform == "win32":
|
||
os.startfile(p)
|
||
elif sys.platform == "darwin":
|
||
subprocess.run(["open", p], check=True)
|
||
else:
|
||
subprocess.run(["xdg-open", p], check=True)
|
||
except Exception as e:
|
||
log.error(f"Failed to open '{p}': {e}", exc_info=True)
|
||
messagebox.showerror("Error", f"Could not open the file/folder:\n{str(e)}")
|
||
|
||
|
||
def open_output_folder(filepath: str):
|
||
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 = Path(filepath).parent
|
||
open_with_default_app(str(folder))
|
||
|
||
|
||
class ProfileManagerWindow(tb.Toplevel):
|
||
def __init__(self, parent, config_data, app_instance):
|
||
super().__init__(master=parent, title="Manage Profiles")
|
||
|
||
self.geometry("500x400")
|
||
self.transient(parent)
|
||
self.config_data = config_data
|
||
self.app = app_instance
|
||
self.selected_profile_name = None
|
||
self._setup_widgets()
|
||
self._load_profiles()
|
||
|
||
def _setup_widgets(self):
|
||
main_frame = tb.Frame(self, padding=10)
|
||
main_frame.pack(fill=tk.BOTH, expand=True)
|
||
main_frame.rowconfigure(0, weight=1)
|
||
main_frame.columnconfigure(0, weight=1)
|
||
list_frame = tb.Labelframe(main_frame, text="Existing Profiles", padding=5)
|
||
list_frame.grid(row=0, column=0, sticky="nsew", padx=(0, 10))
|
||
list_frame.rowconfigure(0, weight=1)
|
||
list_frame.columnconfigure(0, weight=1)
|
||
self.profile_listbox = tk.Listbox(list_frame, exportselection=False)
|
||
self.profile_listbox.grid(row=0, column=0, sticky="nsew")
|
||
scrollbar = tb.Scrollbar(
|
||
list_frame, orient=tk.VERTICAL, command=self.profile_listbox.yview
|
||
)
|
||
scrollbar.grid(row=0, column=1, sticky="ns")
|
||
self.profile_listbox.config(yscrollcommand=scrollbar.set)
|
||
self.profile_listbox.bind("<<ListboxSelect>>", self._on_profile_select)
|
||
button_frame = tb.Frame(main_frame)
|
||
button_frame.grid(row=0, column=1, sticky="ns")
|
||
tb.Button(
|
||
button_frame, text="New... ➕", command=self._new_profile, bootstyle=SUCCESS
|
||
).pack(fill=tk.X, pady=5)
|
||
self.rename_btn = tb.Button(
|
||
button_frame,
|
||
text="Rename... ✏️",
|
||
command=self._rename_profile,
|
||
state=tk.DISABLED,
|
||
)
|
||
self.rename_btn.pack(fill=tk.X, pady=5)
|
||
self.delete_btn = tb.Button(
|
||
button_frame,
|
||
text="Delete 🗑️",
|
||
command=self._delete_profile,
|
||
state=tk.DISABLED,
|
||
bootstyle=DANGER,
|
||
)
|
||
self.delete_btn.pack(fill=tk.X, pady=5)
|
||
tb.Button(button_frame, text="Close ✖️", command=self.destroy).pack(
|
||
fill=tk.X, pady=(20, 5)
|
||
)
|
||
|
||
def _load_profiles(self):
|
||
self.profile_listbox.delete(0, tk.END)
|
||
for profile_name in sorted(self.config_data[KEY_PROFILES].keys()):
|
||
self.profile_listbox.insert(tk.END, profile_name)
|
||
self._on_profile_select()
|
||
|
||
def _on_profile_select(self, event=None):
|
||
selection = self.profile_listbox.curselection()
|
||
if not selection:
|
||
self.selected_profile_name = None
|
||
self.rename_btn.config(state=tk.DISABLED)
|
||
self.delete_btn.config(state=tk.DISABLED)
|
||
else:
|
||
self.selected_profile_name = self.profile_listbox.get(selection[0])
|
||
self.rename_btn.config(state=tk.NORMAL)
|
||
self.delete_btn.config(state=tk.NORMAL)
|
||
|
||
def _new_profile(self):
|
||
profile_name = simpledialog.askstring(
|
||
"New Profile", "Enter a name for the new profile:", parent=self
|
||
)
|
||
if not profile_name:
|
||
return
|
||
if profile_name in self.config_data[KEY_PROFILES]:
|
||
messagebox.showerror(
|
||
"Error",
|
||
f"A profile named '{profile_name}' already exists.",
|
||
parent=self,
|
||
)
|
||
return
|
||
template_path = filedialog.askopenfilename(
|
||
title=f"Select Template for '{profile_name}'",
|
||
filetypes=[("Word Documents", "*.docx")],
|
||
parent=self,
|
||
)
|
||
if not template_path:
|
||
return
|
||
self.config_data[KEY_PROFILES][profile_name] = {
|
||
KEY_TEMPLATE_PATH: template_path,
|
||
KEY_VALUES: {},
|
||
}
|
||
self.app.update_config(self.config_data)
|
||
self.app.update_profile_combobox(new_selection=profile_name)
|
||
self._load_profiles()
|
||
|
||
def _rename_profile(self):
|
||
if not self.selected_profile_name:
|
||
return
|
||
new_name = simpledialog.askstring(
|
||
"Rename Profile",
|
||
f"Enter a new name for '{self.selected_profile_name}':",
|
||
parent=self,
|
||
)
|
||
if not new_name or new_name == self.selected_profile_name:
|
||
return
|
||
if new_name in self.config_data[KEY_PROFILES]:
|
||
messagebox.showerror(
|
||
"Error", f"A profile named '{new_name}' already exists.", parent=self
|
||
)
|
||
return
|
||
self.config_data[KEY_PROFILES][new_name] = self.config_data[KEY_PROFILES].pop(
|
||
self.selected_profile_name
|
||
)
|
||
if self.config_data[KEY_LAST_PROFILE] == self.selected_profile_name:
|
||
self.config_data[KEY_LAST_PROFILE] = new_name
|
||
self.app.update_config(self.config_data)
|
||
self.app.update_profile_combobox(new_selection=new_name)
|
||
self._load_profiles()
|
||
|
||
def _delete_profile(self):
|
||
if not self.selected_profile_name:
|
||
return
|
||
if not messagebox.askyesno(
|
||
"Confirm Delete",
|
||
f"Are you sure you want to delete profile '{self.selected_profile_name}'?",
|
||
parent=self,
|
||
):
|
||
return
|
||
del self.config_data[KEY_PROFILES][self.selected_profile_name]
|
||
if self.config_data[KEY_LAST_PROFILE] == self.selected_profile_name:
|
||
self.config_data[KEY_LAST_PROFILE] = ""
|
||
self.app.update_config(self.config_data)
|
||
self.app.update_profile_combobox()
|
||
self._load_profiles()
|
||
|
||
|
||
class MarkdownConverterApp:
|
||
def __init__(self, root):
|
||
self.root = root
|
||
base_title = f"Markdown converter - {WRAPPER_APP_VERSION_STRING}"
|
||
self.root.title(base_title)
|
||
self.root.geometry("900x850")
|
||
self._setup_logging()
|
||
self.config = load_configuration()
|
||
self.dynamic_entry_vars = {}
|
||
self.loaded_profile_name = ""
|
||
self.selected_file = StringVar(value=self.config.get(KEY_LAST_MARKDOWN, ""))
|
||
|
||
# --- NEW CODE ---
|
||
# Add a trace to the StringVar. Whenever its value is written,
|
||
# the _on_selected_file_change callback will be executed.
|
||
self.selected_file.trace_add("write", self._on_selected_file_change)
|
||
|
||
self.active_profile_name = StringVar() # Initialized empty
|
||
self.add_toc_var = BooleanVar(value=True)
|
||
self.docx_output_path = StringVar()
|
||
self.pdf_direct_output_path = StringVar()
|
||
self.pdf_from_docx_output_path = StringVar()
|
||
self._build_ui()
|
||
self.update_profile_combobox() # This will set active_profile_name
|
||
self.root.protocol("WM_DELETE_WINDOW", self._on_closing)
|
||
|
||
def _setup_logging(self):
|
||
self.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=self.root,
|
||
logging_config_dict=self.log_config,
|
||
)
|
||
|
||
def _build_ui(self):
|
||
# Crea il Notebook (sistema a tab)
|
||
self.notebook = tb.Notebook(self.root)
|
||
self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||
|
||
# Tab 1: Conversione Singola (funzionalità originale)
|
||
self.single_converter_tab = tb.Frame(self.notebook, padding=10)
|
||
self.notebook.add(self.single_converter_tab, text="MD → DOCX/PDF")
|
||
|
||
# Tab 2: Conversione Batch
|
||
self.batch_converter_tab = BatchConverterTab(self.notebook)
|
||
self.notebook.add(self.batch_converter_tab, text="Batch MD → DOC")
|
||
|
||
# Tab 3: Conversione PDF → Markdown
|
||
self.pdf_to_md_tab = PdfToMarkdownTab(self.notebook)
|
||
self.notebook.add(self.pdf_to_md_tab, text="PDF → Markdown")
|
||
|
||
# Costruisce la UI del tab singolo nel container
|
||
self._build_single_converter_ui()
|
||
|
||
def _build_single_converter_ui(self):
|
||
"""Costruisce l'interfaccia per la conversione singola."""
|
||
container = self.single_converter_tab
|
||
profile_frame = tb.Labelframe(
|
||
container, text="Profile & Source File", padding=10
|
||
)
|
||
profile_frame.pack(fill=tk.X, pady=(0, 10))
|
||
profile_frame.columnconfigure(1, weight=1)
|
||
tb.Label(profile_frame, text="Active Profile:").grid(
|
||
row=0, column=0, padx=5, pady=5, sticky="w"
|
||
)
|
||
self.profile_combobox = tb.Combobox(
|
||
profile_frame, textvariable=self.active_profile_name, state="readonly"
|
||
)
|
||
self.profile_combobox.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
|
||
self.profile_combobox.bind("<<ComboboxSelected>>", self._on_profile_change)
|
||
tb.Button(
|
||
profile_frame, text="Manage... ⚙️", command=self.open_profile_manager
|
||
).grid(row=0, column=2, padx=5, pady=5)
|
||
tb.Label(profile_frame, text="Markdown File:").grid(
|
||
row=1, column=0, padx=5, pady=5, sticky="w"
|
||
)
|
||
tb.Entry(profile_frame, textvariable=self.selected_file).grid(
|
||
row=1, column=1, padx=5, pady=5, sticky="ew"
|
||
)
|
||
tb.Button(
|
||
profile_frame, text="Browse... 📂", command=self.browse_markdown
|
||
).grid(row=1, column=2, padx=5, pady=5)
|
||
self.placeholder_frame = ScrolledFrame(
|
||
container, autohide=True, bootstyle="round"
|
||
)
|
||
self.placeholder_frame.pack(fill=tk.BOTH, expand=True, pady=10)
|
||
options_frame = tb.Frame(container)
|
||
options_frame.pack(fill=tk.X, pady=(0, 10))
|
||
tb.Checkbutton(
|
||
options_frame,
|
||
text="Add TOC",
|
||
variable=self.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(self.selected_file.get()),
|
||
).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: self.convert("DOCX"),
|
||
width=12,
|
||
).grid(row=0, column=0, padx=5, pady=5)
|
||
tb.Entry(action_frame, textvariable=self.docx_output_path).grid(
|
||
row=0, column=1, padx=5, pady=5, sticky="ew"
|
||
)
|
||
tb.Button(action_frame, text="Open 📂", command=self.open_docx_output).grid(
|
||
row=0, column=2, padx=5, pady=5
|
||
)
|
||
tb.Button(
|
||
action_frame,
|
||
text="MD → PDF 📝➡️📕",
|
||
command=lambda: self.convert("PDF"),
|
||
width=12,
|
||
).grid(row=1, column=0, padx=5, pady=5)
|
||
tb.Entry(action_frame, textvariable=self.pdf_direct_output_path).grid(
|
||
row=1, column=1, padx=5, pady=5, sticky="ew"
|
||
)
|
||
tb.Button(
|
||
action_frame, text="Open 📂", command=self.open_pdf_direct_output
|
||
).grid(row=1, column=2, padx=5, pady=5)
|
||
self.docx_to_pdf_btn = tb.Button(
|
||
action_frame,
|
||
text="DOCX → PDF 🧾➡️📕",
|
||
command=self.convert_from_docx_to_pdf,
|
||
bootstyle="info",
|
||
width=12,
|
||
state=tk.DISABLED,
|
||
)
|
||
self.docx_to_pdf_btn.grid(row=2, column=0, padx=5, pady=5)
|
||
tb.Entry(action_frame, textvariable=self.pdf_from_docx_output_path).grid(
|
||
row=2, column=1, padx=5, pady=5, sticky="ew"
|
||
)
|
||
tb.Button(
|
||
action_frame, text="Open 📂", command=self.open_pdf_from_docx_output
|
||
).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, pady=(10, 0))
|
||
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=self.root,
|
||
logging_config_dict=self.log_config,
|
||
)
|
||
|
||
def open_docx_output(self):
|
||
open_with_default_app(self.docx_output_path.get())
|
||
|
||
def open_pdf_direct_output(self):
|
||
open_with_default_app(self.pdf_direct_output_path.get())
|
||
|
||
def open_pdf_from_docx_output(self):
|
||
open_with_default_app(self.pdf_from_docx_output_path.get())
|
||
|
||
def update_profile_combobox(self, new_selection=None):
|
||
profiles = sorted(self.config[KEY_PROFILES].keys())
|
||
self.profile_combobox["values"] = profiles
|
||
last_profile = self.config.get(KEY_LAST_PROFILE)
|
||
if new_selection and new_selection in profiles:
|
||
self.active_profile_name.set(new_selection)
|
||
elif last_profile in profiles:
|
||
self.active_profile_name.set(last_profile)
|
||
else:
|
||
self.active_profile_name.set("")
|
||
self._load_profile_data()
|
||
|
||
def open_profile_manager(self):
|
||
win = ProfileManagerWindow(self.root, self.config, self)
|
||
self.root.wait_window(win)
|
||
|
||
def _on_profile_change(self, event=None):
|
||
self._save_current_values()
|
||
self.config[KEY_LAST_PROFILE] = self.active_profile_name.get()
|
||
self._load_profile_data()
|
||
|
||
def _load_profile_data(self):
|
||
for widget in self.placeholder_frame.winfo_children():
|
||
widget.destroy()
|
||
self.dynamic_entry_vars.clear()
|
||
|
||
profile_name = self.active_profile_name.get()
|
||
self.loaded_profile_name = profile_name # Memorizza il profilo caricato
|
||
|
||
if not profile_name:
|
||
tb.Label(
|
||
self.placeholder_frame,
|
||
text="No profile selected. Create or select a profile.",
|
||
bootstyle=INFO,
|
||
).pack(pady=10)
|
||
self._update_output_paths()
|
||
return
|
||
|
||
profile_data = self.config[KEY_PROFILES].get(profile_name, {})
|
||
template_path = profile_data.get(KEY_TEMPLATE_PATH)
|
||
if not template_path or not os.path.exists(template_path):
|
||
tb.Label(
|
||
self.placeholder_frame,
|
||
text=f"Template file not found:\n{template_path}",
|
||
bootstyle=WARNING,
|
||
wraplength=700,
|
||
).pack(pady=10)
|
||
return
|
||
try:
|
||
dynamic, structural = scan_template_for_placeholders(template_path)
|
||
if not (dynamic or structural):
|
||
tb.Label(
|
||
self.placeholder_frame,
|
||
text="No placeholders found in template.",
|
||
bootstyle=INFO,
|
||
).pack(pady=10)
|
||
return
|
||
|
||
placeholder_grid = tb.Frame(self.placeholder_frame)
|
||
placeholder_grid.pack(fill=tk.X, padx=5, pady=5)
|
||
placeholder_grid.columnconfigure(1, weight=1)
|
||
saved_values = profile_data.get(KEY_VALUES, {})
|
||
row = 0
|
||
for ph in dynamic:
|
||
label_text = ph.replace("%%", "") + ":"
|
||
tb.Label(placeholder_grid, text=label_text).grid(
|
||
row=row, column=0, padx=5, pady=3, sticky="e"
|
||
)
|
||
entry_var = StringVar(value=saved_values.get(ph, ""))
|
||
entry = tb.Entry(placeholder_grid, textvariable=entry_var)
|
||
entry.grid(row=row, column=1, columnspan=2, padx=5, pady=3, sticky="ew")
|
||
entry_var.trace_add(
|
||
"write", lambda *_, ph_key=ph: self._on_placeholder_change(ph_key)
|
||
)
|
||
self.dynamic_entry_vars[ph] = entry_var
|
||
row += 1
|
||
if structural:
|
||
tb.Separator(placeholder_grid).grid(
|
||
row=row, column=0, columnspan=3, pady=10, sticky="ew"
|
||
)
|
||
row += 1
|
||
for ph in structural:
|
||
label_text = ph.replace("%%", "") + ":"
|
||
tb.Label(placeholder_grid, text=label_text).grid(
|
||
row=row, column=0, padx=5, pady=3, sticky="e"
|
||
)
|
||
tb.Entry(
|
||
placeholder_grid,
|
||
state="readonly",
|
||
textvariable=StringVar(value="<Automatic>"),
|
||
).grid(row=row, column=1, padx=5, pady=3, sticky="ew")
|
||
tb.Label(
|
||
placeholder_grid,
|
||
text="Managed by application",
|
||
bootstyle=SECONDARY,
|
||
).grid(row=row, column=2, padx=5, pady=3, sticky="w")
|
||
row += 1
|
||
except (FileNotFoundError, TemplatePlaceholderError) as e:
|
||
tb.Label(
|
||
self.placeholder_frame,
|
||
text=f"Error: {e}",
|
||
bootstyle=DANGER,
|
||
wraplength=700,
|
||
).pack(pady=10)
|
||
self._update_output_paths()
|
||
|
||
def _on_placeholder_change(self, placeholder_key):
|
||
if placeholder_key in ["%%DOC_PROJECT%%", "%%DOC_NUMBER%%", "%%DOC_REV%%"]:
|
||
self._update_output_paths()
|
||
|
||
def _gather_current_values(self):
|
||
return {ph: var.get() for ph, var in self.dynamic_entry_vars.items()}
|
||
|
||
def _save_current_values(self):
|
||
profile_to_save = self.loaded_profile_name
|
||
if not profile_to_save or profile_to_save not in self.config[KEY_PROFILES]:
|
||
return
|
||
self.config[KEY_PROFILES][profile_to_save][
|
||
KEY_VALUES
|
||
] = self._gather_current_values()
|
||
log.info(f"Updated values in memory for profile '{profile_to_save}'.")
|
||
|
||
def _confirm_overwrite(self, filepath: str) -> bool:
|
||
if os.path.exists(filepath):
|
||
return messagebox.askyesno(
|
||
"Confirm Overwrite", f"File exists:\n{filepath}\n\nOverwrite?"
|
||
)
|
||
return True
|
||
|
||
def browse_markdown(self):
|
||
path = filedialog.askopenfilename(filetypes=[("Markdown files", "*.md")])
|
||
if path:
|
||
self.selected_file.set(path)
|
||
|
||
def _update_output_paths(self):
|
||
source_path_str = self.selected_file.get()
|
||
if not source_path_str:
|
||
return
|
||
output_dir = Path(source_path_str).parent
|
||
values = self._gather_current_values()
|
||
project = values.get("%%DOC_PROJECT%%", "NoProject")
|
||
doc_num = values.get("%%DOC_NUMBER%%", "NoNum")
|
||
rev = values.get("%%DOC_REV%%", "NoRev")
|
||
today = date.today().strftime("%Y%m%d")
|
||
|
||
def sanitize(text: str) -> str:
|
||
return re.sub(r'[\s/\\:*?"<>|]+', "_", text)
|
||
|
||
docx_fn = (
|
||
f"{sanitize(project)}_SUM_{sanitize(doc_num)}_{sanitize(rev)}_{today}.docx"
|
||
)
|
||
self.docx_output_path.set(str(output_dir / docx_fn))
|
||
pdf_from_docx_fn = (
|
||
f"{sanitize(project)}_SUM_{sanitize(doc_num)}_{sanitize(rev)}_{today}.pdf"
|
||
)
|
||
self.pdf_from_docx_output_path.set(str(output_dir / pdf_from_docx_fn))
|
||
pdf_direct_fn = f"{sanitize(project)}_SUM_{today}.pdf"
|
||
self.pdf_direct_output_path.set(str(output_dir / pdf_direct_fn))
|
||
|
||
def convert(self, fmt):
|
||
self.docx_to_pdf_btn.config(state=tk.DISABLED)
|
||
input_path = self.selected_file.get()
|
||
if not input_path:
|
||
messagebox.showerror("Error", "Please select a Markdown file.")
|
||
return
|
||
profile_name = self.active_profile_name.get()
|
||
if not profile_name:
|
||
messagebox.showerror("Error", "Please select a profile.")
|
||
return
|
||
template_path = self.config[KEY_PROFILES][profile_name].get(KEY_TEMPLATE_PATH)
|
||
if fmt == "DOCX" and not template_path:
|
||
messagebox.showwarning(
|
||
"Warning", "The selected profile has no template associated."
|
||
)
|
||
return
|
||
output_path = (
|
||
self.docx_output_path.get()
|
||
if fmt == "DOCX"
|
||
else self.pdf_direct_output_path.get()
|
||
)
|
||
if not output_path:
|
||
messagebox.showerror("Error", "Please specify a valid output path.")
|
||
return
|
||
if not self._confirm_overwrite(output_path):
|
||
return
|
||
self._save_current_values()
|
||
save_configuration(self.config)
|
||
metadata = self._gather_current_values()
|
||
try:
|
||
result_path = convert_markdown(
|
||
input_path,
|
||
output_path,
|
||
fmt,
|
||
self.add_toc_var.get(),
|
||
template_path,
|
||
metadata,
|
||
)
|
||
messagebox.showinfo(
|
||
"Success", f"File converted successfully:\n{result_path}"
|
||
)
|
||
if fmt == "DOCX":
|
||
self.docx_to_pdf_btn.config(state=tk.NORMAL)
|
||
except Exception as e:
|
||
# Use unified error handler
|
||
handle_conversion_error(e, show_dialog=True)
|
||
|
||
def convert_from_docx_to_pdf(self):
|
||
input_path = self.docx_output_path.get()
|
||
output_path = self.pdf_from_docx_output_path.get()
|
||
if not input_path or not os.path.exists(input_path):
|
||
messagebox.showerror(
|
||
"Error", "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 self._confirm_overwrite(output_path):
|
||
return
|
||
try:
|
||
pdf_output = convert_docx_to_pdf(input_path, output_path)
|
||
messagebox.showinfo(
|
||
"Success", f"DOCX successfully converted to PDF:\n{pdf_output}"
|
||
)
|
||
except Exception as e:
|
||
# Use unified error handler
|
||
handle_conversion_error(e, show_dialog=True)
|
||
|
||
def update_config(self, new_config):
|
||
self.config = new_config
|
||
save_configuration(self.config)
|
||
|
||
def _on_closing(self):
|
||
self._save_current_values()
|
||
self.config[KEY_LAST_MARKDOWN] = self.selected_file.get()
|
||
self.config[KEY_LAST_PROFILE] = self.active_profile_name.get()
|
||
save_configuration(self.config)
|
||
shutdown_logging_system()
|
||
self.root.destroy()
|
||
|
||
def _on_selected_file_change(self, *args):
|
||
"""
|
||
Callback function that is triggered whenever the self.selected_file
|
||
StringVar is written to. It ensures the output paths are updated.
|
||
"""
|
||
self._update_output_paths()
|
||
|
||
|
||
def run_app():
|
||
root = tb.Window(themename="sandstone")
|
||
app = MarkdownConverterApp(root)
|
||
root.mainloop()
|