import tkinter as tk from tkinter import scrolledtext, messagebox, filedialog, ttk import markdown from fpdf import FPDF from typing import Optional # It's better to manage this path from the main application later, # but for now, we keep it simple. HELP_MARKDOWN_FILE = "help.md" class HelpWindow(tk.Toplevel): """ A Toplevel window that displays help content from a Markdown file. The window provides options to export the content to HTML or PDF. """ def __init__(self, master: tk.Tk): """ Initializes the help window. Args: master: The parent tk.Tk window. """ super().__init__(master) self.title("Radalyze - Help") self.geometry("800x600") self._help_content: Optional[str] = None self._create_widgets() self._load_and_display_help() self._center_window() def _create_widgets(self) -> None: """Creates and arranges the widgets in the window.""" # --- Toolbar --- toolbar_frame = ttk.Frame(self) toolbar_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5) export_html_button = ttk.Button( toolbar_frame, text="Export to HTML", command=self._export_to_html ) export_html_button.pack(side=tk.LEFT, padx=5) export_pdf_button = ttk.Button( toolbar_frame, text="Export to PDF", command=self._export_to_pdf ) export_pdf_button.pack(side=tk.LEFT, padx=5) # --- Text Area --- self._text_widget = scrolledtext.ScrolledText(self, wrap=tk.WORD, padx=5) self._text_widget.pack(expand=True, fill="both") self._text_widget.config(state=tk.DISABLED) # Make it read-only def _load_and_display_help(self) -> None: """Loads the content from the Markdown file and displays it.""" try: with open(HELP_MARKDOWN_FILE, "r", encoding="utf-8") as f: self._help_content = f.read() self._text_widget.config(state=tk.NORMAL) self._text_widget.delete("1.0", tk.END) self._text_widget.insert(tk.INSERT, self._help_content) self._text_widget.config(state=tk.DISABLED) except FileNotFoundError: self._show_error(f"Help file not found: '{HELP_MARKDOWN_FILE}'") except Exception as e: self._show_error(f"An error occurred while reading the help file: {e}") def _export_to_html(self) -> None: """Exports the help content to an HTML file.""" if not self._help_content: self._show_error("Help content is not loaded. Cannot export.") return save_path = filedialog.asksaveasfilename( title="Save Help as HTML", defaultextension=".html", filetypes=[("HTML files", "*.html"), ("All files", "*.*")] ) if not save_path: return # User cancelled try: html_content = markdown.markdown(self._help_content) with open(save_path, "w", encoding="utf-8") as f: f.write(html_content) messagebox.showinfo( "Export Successful", f"Help content successfully saved to:\n{save_path}", parent=self ) except Exception as e: self._show_error(f"Failed to export to HTML: {e}") def _export_to_pdf(self) -> None: """Exports the help content to a PDF file.""" if not self._help_content: self._show_error("Help content is not loaded. Cannot export.") return save_path = filedialog.asksaveasfilename( title="Save Help as PDF", defaultextension=".pdf", filetypes=[("PDF files", "*.pdf"), ("All files", "*.*")] ) if not save_path: return # User cancelled try: pdf = self._create_pdf_from_markdown(self._help_content) pdf.output(save_path) messagebox.showinfo( "Export Successful", f"Help content successfully saved to:\n{save_path}", parent=self ) except Exception as e: self._show_error(f"Failed to export to PDF: {e}") @staticmethod def _create_pdf_from_markdown(markdown_text: str) -> FPDF: """ Creates an FPDF object from a Markdown string. This is a basic parser and handles headings (#) and list items (*). """ pdf = FPDF() pdf.add_page() pdf.set_font("Arial", size=12) for line in markdown_text.split('\n'): line = line.strip() if line.startswith('#'): level = line.find(' ') text = line[level:].strip() size = max(12, 20 - 2 * level) pdf.set_font("Arial", 'B', size=size) pdf.multi_cell(0, 10, text, 0, 'L') pdf.set_font("Arial", size=12) elif line.startswith('*'): text = line[1:].strip() pdf.set_x(15) pdf.multi_cell(0, 5, f"• {text}") pdf.set_x(10) else: pdf.multi_cell(0, 5, line) if not line: # Add a small space for empty lines pdf.ln(5) return pdf def _center_window(self) -> None: """Centers the window on the screen.""" self.update_idletasks() screen_width = self.winfo_screenwidth() screen_height = self.winfo_screenheight() window_width = self.winfo_width() window_height = self.winfo_height() pos_x = (screen_width // 2) - (window_width // 2) pos_y = (screen_height // 2) - (window_height // 2) self.geometry(f"+{pos_x}+{pos_y}") def _show_error(self, message: str) -> None: """Convenience method to show an error message box.""" messagebox.showerror("Help Window Error", message, parent=self)