#!/usr/bin/env python3 """ Scan2Doc Pro v6.0 — GUI Cyberpunk Red-Dark Theme Created by Us.Abolfazl """ import os import sys import threading import tkinter as tk from tkinter import filedialog, messagebox, ttk sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from scan2doc_pro_v6 import process_file, SUPPORTED_EXTS, find_tesseract # ─── Theme Colors ───────────────────────────────────────────────── BG_DARK = "#0d0d0d" BG_CARD = "#1a1a1a" BG_INPUT = "#252525" RED = "#ff1744" RED_HOVER = "#ff5252" RED_DARK = "#b71c1c" WHITE = "#ffffff" GRAY = "#b0b0b0" DIM = "#666666" GREEN = "#00e676" BORDER = "#333333" class Scan2DocGUI: def __init__(self): self.root = tk.Tk() self.root.title("Scan2Doc Pro v6.0 — Created by Us.Abolfazl") self.root.geometry("850x700") self.root.minsize(700, 600) self.root.configure(bg=BG_DARK) # Icon icon_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "icon.ico") if os.path.exists(icon_path): try: self.root.iconbitmap(icon_path) except Exception: pass self.files = [] self.processing = False self._build_ui() def _build_ui(self): # ─── Header ───────────────────────────────────────────── hdr = tk.Frame(self.root, bg=BG_CARD, bd=0, highlightthickness=0) hdr.pack(fill=tk.X, padx=10, pady=(10, 5)) tk.Label(hdr, text="⚡ Scan2Doc Pro v6.0", font=("Arial", 20, "bold"), fg=RED, bg=BG_CARD).pack(anchor="w", padx=15, pady=8) tk.Label(hdr, text="Created by Us.Abolfazl", font=("Arial", 11), fg=DIM, bg=BG_CARD).pack(anchor="w", padx=15, pady=(0, 8)) # ─── File List ────────────────────────────────────────── file_frame = tk.Frame(self.root, bg=BG_CARD, bd=0, highlightthickness=0) file_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) tk.Label(file_frame, text="📁 Input Files", font=("Arial", 13, "bold"), fg=RED, bg=BG_CARD).pack(anchor="w", padx=10, pady=(8, 0)) list_frame = tk.Frame(file_frame, bg=BG_INPUT, bd=0) list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) scrollbar = tk.Scrollbar(list_frame) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.file_listbox = tk.Listbox(list_frame, bg=BG_INPUT, fg=WHITE, selectbackground=RED, selectforeground=WHITE, font=("Consolas", 11), bd=0, highlightthickness=0, yscrollcommand=scrollbar.set) self.file_listbox.pack(fill=tk.BOTH, expand=True) scrollbar.config(command=self.file_listbox.yview) # Buttons btn_frame = tk.Frame(file_frame, bg=BG_CARD) btn_frame.pack(fill=tk.X, padx=10, pady=5) self._make_btn(btn_frame, "➕ Add Files", self._add_files, RED).pack(side=tk.LEFT, padx=3) self._make_btn(btn_frame, "📂 Add Folder", self._add_folder, RED_DARK).pack(side=tk.LEFT, padx=3) self._make_btn(btn_frame, "❌ Remove", self._remove, "#444444").pack(side=tk.LEFT, padx=3) self._make_btn(btn_frame, "🗑️ Clear All", self._clear, "#444444").pack(side=tk.LEFT, padx=3) # ─── Settings ─────────────────────────────────────────── settings = tk.Frame(self.root, bg=BG_CARD, bd=0) settings.pack(fill=tk.X, padx=10, pady=5) tk.Label(settings, text="⚙️ Settings", font=("Arial", 13, "bold"), fg=RED, bg=BG_CARD).pack(anchor="w", padx=10, pady=(8, 0)) grid = tk.Frame(settings, bg=BG_CARD) grid.pack(fill=tk.X, padx=10, pady=5) # Language tk.Label(grid, text="Language:", fg=GRAY, bg=BG_CARD).grid(row=0, column=0, sticky="w", padx=5, pady=3) self.lang_var = tk.StringVar(value="eng+fas") tk.Entry(grid, textvariable=self.lang_var, width=15, bg=BG_INPUT, fg=WHITE, insertbackground=WHITE, bd=0).grid(row=0, column=1, sticky="w", padx=5, pady=3) # DPI tk.Label(grid, text="DPI:", fg=GRAY, bg=BG_CARD).grid(row=0, column=2, sticky="w", padx=15, pady=3) self.dpi_var = tk.StringVar(value="300") tk.Entry(grid, textvariable=self.dpi_var, width=8, bg=BG_INPUT, fg=WHITE, insertbackground=WHITE, bd=0).grid(row=0, column=3, sticky="w", padx=5, pady=3) # Title tk.Label(grid, text="Title:", fg=GRAY, bg=BG_CARD).grid(row=1, column=0, sticky="w", padx=5, pady=3) self.title_var = tk.StringVar(value="Scan2Doc Output") tk.Entry(grid, textvariable=self.title_var, width=15, bg=BG_INPUT, fg=WHITE, insertbackground=WHITE, bd=0).grid(row=1, column=1, sticky="w", padx=5, pady=3) # Font tk.Label(grid, text="Font:", fg=GRAY, bg=BG_CARD).grid(row=1, column=2, sticky="w", padx=15, pady=3) self.font_var = tk.StringVar(value="B Nazanin") tk.Entry(grid, textvariable=self.font_var, width=15, bg=BG_INPUT, fg=WHITE, insertbackground=WHITE, bd=0).grid(row=1, column=3, sticky="w", padx=5, pady=3) # ─── Progress ─────────────────────────────────────────── prog_frame = tk.Frame(self.root, bg=BG_CARD, bd=0) prog_frame.pack(fill=tk.X, padx=10, pady=5) self.progress_label = tk.Label(prog_frame, text="Ready", font=("Arial", 12), fg=GRAY, bg=BG_CARD) self.progress_label.pack(anchor="w", padx=10, pady=(5, 0)) style = ttk.Style() style.theme_use("default") style.configure("Red.Horizontal.TProgressbar", troughcolor=BG_INPUT, background=RED, borderwidth=0) self.progress_bar = ttk.Progressbar(prog_frame, style="Red.Horizontal.TProgressbar", length=400, mode="determinate") self.progress_bar.pack(fill=tk.X, padx=10, pady=5) # ─── Log ──────────────────────────────────────────────── log_frame = tk.Frame(self.root, bg=BG_CARD, bd=0) log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) tk.Label(log_frame, text="📋 Log", font=("Arial", 13, "bold"), fg=RED, bg=BG_CARD).pack(anchor="w", padx=10, pady=(8, 0)) log_inner = tk.Frame(log_frame, bg=BG_INPUT, bd=0) log_inner.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) log_scroll = tk.Scrollbar(log_inner) log_scroll.pack(side=tk.RIGHT, fill=tk.Y) self.log_text = tk.Text(log_inner, bg=BG_INPUT, fg=WHITE, font=("Consolas", 11), bd=0, highlightthickness=0, wrap=tk.WORD, yscrollcommand=log_scroll.set) self.log_text.pack(fill=tk.BOTH, expand=True) log_scroll.config(command=self.log_text.yview) # ─── Bottom ───────────────────────────────────────────── bottom = tk.Frame(self.root, bg=BG_DARK) bottom.pack(fill=tk.X, padx=10, pady=5) tk.Label(bottom, text="⚡ Created by Us.Abolfazl", font=("Arial", 10), fg=RED, bg=BG_DARK).pack(side=tk.LEFT, padx=10) self.process_btn = tk.Button(bottom, text="🚀 Process", width=18, font=("Arial", 14, "bold"), bg=RED, fg=WHITE, activebackground=RED_HOVER, activeforeground=WHITE, bd=0, cursor="hand2", command=self._start_processing) self.process_btn.pack(side=tk.RIGHT, padx=10) def _make_btn(self, parent, text, cmd, bg_color): return tk.Button(parent, text=text, command=cmd, bg=bg_color, fg=WHITE, activebackground=RED_HOVER, activeforeground=WHITE, bd=0, font=("Arial", 10), cursor="hand2", padx=8, pady=4) def _add_files(self): filetypes = [ ("All supported", " ".join(f"*{e}" for e in SUPPORTED_EXTS)), ("Images", "*.jpg *.jpeg *.png *.tiff *.tif *.bmp *.gif *.webp"), ("PDFs", "*.pdf"), ] paths = filedialog.askopenfilenames(filetypes=filetypes) for p in paths: if p not in self.files: self.files.append(p) self.file_listbox.insert(tk.END, p) self._update_label() def _add_folder(self): folder = filedialog.askdirectory() if not folder: return for f in sorted(os.listdir(folder)): ext = os.path.splitext(f)[1].lower() if ext in SUPPORTED_EXTS: full = os.path.join(folder, f) if full not in self.files: self.files.append(full) self.file_listbox.insert(tk.END, full) self._update_label() def _remove(self): sel = self.file_listbox.curselection() for idx in reversed(sel): self.file_listbox.delete(idx) del self.files[idx] self._update_label() def _clear(self): self.files.clear() self.file_listbox.delete(0, tk.END) self._update_label() def _update_label(self): self.progress_label.config(text=f"Files: {len(self.files)}") def _log(self, msg): self.root.after(0, lambda: ( self.log_text.insert(tk.END, msg + "\n"), self.log_text.see(tk.END) )) def _start_processing(self): if self.processing: messagebox.showinfo("Info", "Processing is already running.") return if not self.files: messagebox.showwarning("Warning", "No files to process. Add files first.") return output_path = filedialog.asksaveasfilename( defaultextension=".docx", filetypes=[("Word Document", "*.docx")], initialfile="output.docx" ) if not output_path: return self.processing = True self.process_btn.config(state="disabled", bg=DIM) self.log_text.delete("1.0", tk.END) lang = self.lang_var.get() title = self.title_var.get() font = self.font_var.get() try: dpi = int(self.dpi_var.get()) except ValueError: dpi = 300 def worker(): try: self._log("⚡ Starting...") # Process each file for idx, fpath in enumerate(self.files): self._log(f" [{idx+1}/{len(self.files)}] {os.path.basename(fpath)}") out = os.path.join(os.path.dirname(output_path) or ".", f"output_{idx+1}.docx") if len(self.files) > 1 else output_path success = process_file(fpath, out, lang, title, font, dpi) if success: self._log(f" ✅ Saved: {out}") else: self._log(f" ❌ Failed: {fpath}") self._log(f"\n✅ All done! Check: {output_path}") self.root.after(0, lambda: self.progress_label.config(text="Done!", fg=GREEN)) except Exception as e: import traceback self._log(f"❌ Error: {e}") self._log(traceback.format_exc()) self.root.after(0, lambda: self.progress_label.config(text="Error!", fg="#ff1744")) finally: self.processing = False self.root.after(0, lambda: self.process_btn.config(state="normal", bg=RED)) threading.Thread(target=worker, daemon=True).start() def run(self): self.root.mainloop() def main(): gui = Scan2DocGUI() gui.run() if __name__ == "__main__": main()