GPT Translator

 import tkinter as tk

from tkinter import ttk, messagebox, filedialog

import webbrowser

import urllib.parse

import json

import os

import sys

import logging

from pathlib import Path

from datetime import datetime

from PIL import ImageGrab, Image, ImageDraw

import time

import subprocess

import tempfile


# Configure logging

logging.basicConfig(

    level=logging.INFO,

    format='%(asctime)s - %(levelname)s - %(message)s',

    handlers=[

        logging.StreamHandler(sys.stdout)

    ]

)

logger = logging.getLogger(__name__)


class ChatGPTBrowserTool:

    def __init__(self, root):

        self.root = root

        self.root.title("ChatGPT Browser Tool")

        self.root.geometry("700x970")  # Aumentata l'altezza per la checkbox

        

        logger.info("Initializing ChatGPT Browser Tool")

        

        # Load settings

        self.settings_file = Path.cwd() / "chatgpt_browser_tool_settings.json"

        self.settings = self.load_settings()

        logger.info(f"Settings loaded from {self.settings_file}")

        

        # Screenshot state

        self.is_capturing = False

        self.selection_window = None

        self.start_x = None

        self.start_y = None

        self.rect_id = None

        self.bg_rect_id = None  # Background rectangle for better visibility

        

        # Last screenshot path

        self.last_screenshot_path = None

        

        # Theme state

        self.dark_mode = self.settings.get("dark_mode", False)

        

        # System tray icon

        self.tray_icon = None

        

        # Selection cursor color

        self.cursor_color = self.settings.get("cursor_color", "Red")

        

        # Latency settings

        self.startup_latency = self.settings.get("startup_latency", 2.0)  # Valore predefinito: 2 secondi

        self.wait_gpt_loaded = self.settings.get("wait_gpt_loaded", False)  # Nuova impostazione

        

        # Available cursor colors with configurations (like the example tool)

        self.cursor_colors = {

            "Red": {

                "bg_outline": "#8B0000",  # Dark red background

                "bg_width": 3,

                "fg_outline": "#FF0000",  # Bright red foreground

                "fg_width": 2,

                "fg_dash": (5, 5),

                "rainbow": False,

                "bg_alpha": 0.3

            },

            "Green": {

                "bg_outline": "#006400",  # Dark green background

                "bg_width": 3,

                "fg_outline": "#00FF00",  # Bright green foreground

                "fg_width": 2,

                "fg_dash": (5, 5),

                "rainbow": False,

                "bg_alpha": 0.3

            },

            "Blue": {

                "bg_outline": "#00008B",  # Dark blue background

                "bg_width": 3,

                "fg_outline": "#0000FF",  # Bright blue foreground

                "fg_width": 2,

                "fg_dash": (5, 5),

                "rainbow": False,

                "bg_alpha": 0.3

            },

            "Yellow": {

                "bg_outline": "#8B8B00",  # Dark yellow background

                "bg_width": 3,

                "fg_outline": "#FFFF00",  # Bright yellow foreground

                "fg_width": 2,

                "fg_dash": (5, 5),

                "rainbow": False,

                "bg_alpha": 0.3

            },

            "White": {

                "bg_outline": "#808080",  # Gray background

                "bg_width": 3,

                "fg_outline": "#FFFFFF",  # White foreground

                "fg_width": 2,

                "fg_dash": (5, 5),

                "rainbow": False,

                "bg_alpha": 0.3

            },

            "Black & White": {

                "bg_outline": "black",  # Black background

                "bg_width": 3,

                "fg_outline": "white",  # White foreground

                "fg_width": 2,

                "fg_dash": (5, 5),

                "rainbow": False,

                "bg_alpha": 0.3

            },

            "Rainbow Stripes": {

                "bg_outline": "black",  # Black background

                "bg_width": 3,

                "fg_outline": "red",  # Starting color

                "fg_width": 2,

                "fg_dash": (5, 5),

                "rainbow": True,  # Special rainbow mode

                "bg_alpha": 0.3

            },

            "Purple": {

                "bg_outline": "#4B0082",  # Dark purple background

                "bg_width": 3,

                "fg_outline": "#9370DB",  # Light purple foreground

                "fg_width": 2,

                "fg_dash": (5, 5),

                "rainbow": False,

                "bg_alpha": 0.3

            },

            "Orange": {

                "bg_outline": "#8B4500",  # Dark orange background

                "bg_width": 3,

                "fg_outline": "#FFA500",  # Bright orange foreground

                "fg_width": 2,

                "fg_dash": (5, 5),

                "rainbow": False,

                "bg_alpha": 0.3

            },

            "Cyan": {

                "bg_outline": "#008B8B",  # Dark cyan background

                "bg_width": 3,

                "fg_outline": "#00FFFF",  # Bright cyan foreground

                "fg_width": 2,

                "fg_dash": (5, 5),

                "rainbow": False,

                "bg_alpha": 0.3

            }

        }

        

        # Rainbow colors for animation

        self.rainbow_colors = ["red", "orange", "yellow", "green", "cyan", "blue", "purple", "magenta"]

        

        # Language configuration

        self.languages = {

            "English": "Please translate all the text visible in this image to English.",

            "Italian": "Per favore, traducete tutto il testo visibile in questa immagine in italiano.",

            "Spanish": "Por favor, traduce todo el texto visible en esta imagen al español.",

            "French": "Veuillez traduire tout le texte visible dans cette image en français.",

            "German": "Bitte übersetzen Sie den gesamten in diesem Bild sichtbaren Text ins Deutsche.",

            "Portuguese": "Por favor, traduza todo o texto visível nesta imagem para português.",

            "Japanese": "この画像に見えるすべてのテキストを日本語に翻訳してください。",

            "Chinese": "请将此图片中所有可见文本翻译成中文。",

            "Korean": "この画像に見えるすべてのテキストを日本語に翻訳してください。",

            "Russian": "Пожалуйста, переведите весь видимый текст на этом изображении на русский язык。",

            "Arabic": "يرجى ترجمة كل النص المرئي في هذه الصورة إلى العربية。",

            "Custom": "Custom text..."

        }

        

        # Configure initial style

        self.setup_theme()

        

        # Main frame

        self.main_frame = ttk.Frame(root, padding="10")

        self.main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        

        # Configure grid weights

        root.columnconfigure(0, weight=1)

        root.rowconfigure(0, weight=1)

        self.main_frame.columnconfigure(0, weight=1)

        

        # =====================================================================

        # TOP TOOLBAR FRAME

        # =====================================================================

        toolbar_frame = ttk.Frame(self.main_frame)

        toolbar_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))

        toolbar_frame.columnconfigure(0, weight=1)

        

        # Theme toggle button

        theme_text = "🌙 Dark" if not self.dark_mode else "☀️ Light"

        self.theme_button = ttk.Button(toolbar_frame, 

                                      text=theme_text,

                                      command=self.toggle_theme,

                                      width=12)

        self.theme_button.grid(row=0, column=0, sticky=tk.W, padx=(0, 5))

        

        # Minimize to tray button

        self.tray_button = ttk.Button(toolbar_frame,

                                     text="📌 Minimize to Tray",

                                     command=self.minimize_to_tray,

                                     width=25)

        self.tray_button.grid(row=0, column=1, sticky=tk.W, padx=(0, 5))

        

        # Start as icon checkbox

        self.start_as_icon_var = tk.BooleanVar(value=self.settings.get("start_as_icon", False))

        self.start_as_icon_check = ttk.Checkbutton(toolbar_frame,

                                                  text="Start as Icon",

                                                  variable=self.start_as_icon_var,

                                                  command=self.save_settings)

        self.start_as_icon_check.grid(row=0, column=2, sticky=tk.W, padx=(0, 5))

        

        # Spacer

        ttk.Label(toolbar_frame, text="").grid(row=0, column=3, sticky=tk.W, padx=(0, 5))

        

        # =====================================================================

        # CURSOR COLOR SELECTION

        # =====================================================================

        cursor_frame = ttk.LabelFrame(self.main_frame, text="Selection Cursor Color", padding="5")

        cursor_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(0, 10))

        cursor_frame.columnconfigure(1, weight=1)

        

        # Cursor color selection

        ttk.Label(cursor_frame, text="Cursor:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))

        self.cursor_color_var = tk.StringVar(value=self.cursor_color)

        cursor_combo = ttk.Combobox(cursor_frame, textvariable=self.cursor_color_var, 

                                   values=list(self.cursor_colors.keys()),

                                   state="readonly", width=15)

        cursor_combo.grid(row=0, column=1, sticky=tk.W, padx=(0, 10))

        cursor_combo.bind("<<ComboboxSelected>>", self.on_cursor_color_change)

        

        # Preview frame for cursor color

        self.preview_canvas = tk.Canvas(cursor_frame, width=30, height=30, bg="white", highlightthickness=1)

        self.preview_canvas.grid(row=0, column=2, sticky=tk.W, padx=(10, 0))

        self.update_cursor_preview()

        

        # =====================================================================

        # LATENCY SETTINGS

        # =====================================================================

        latency_frame = ttk.LabelFrame(self.main_frame, text="Startup Latency", padding="5")

        latency_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(0, 10))

        latency_frame.columnconfigure(1, weight=1)

        

        # Latency label

        ttk.Label(latency_frame, text="Delay before auto-fill:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))

        

        # Latency slider

        self.latency_var = tk.DoubleVar(value=self.startup_latency)

        self.latency_slider = ttk.Scale(latency_frame, from_=0.5, to=10.0, orient=tk.HORIZONTAL,

                                       variable=self.latency_var, length=200,

                                       command=self.on_latency_change)

        self.latency_slider.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 10))

        

        # Latency value display

        self.latency_value_label = ttk.Label(latency_frame, text=f"{self.latency_var.get():.1f}s")

        self.latency_value_label.grid(row=0, column=2, sticky=tk.W, padx=(0, 5))

        

        # Wait GPT checkbox

        self.wait_gpt_var = tk.BooleanVar(value=self.wait_gpt_loaded)

        self.wait_gpt_check = ttk.Checkbutton(latency_frame,

                                            text="Wait GPT - Wait for page to fully load",

                                            variable=self.wait_gpt_var,

                                            command=self.on_wait_gpt_change)

        self.wait_gpt_check.grid(row=1, column=0, columnspan=3, sticky=tk.W, pady=(5, 0))

        

        # Info label

        info_text = "Adjust if browser is slow to load. Higher = more wait time."

        ttk.Label(latency_frame, text=info_text, font=('Arial', 8)).grid(

            row=2, column=0, columnspan=3, sticky=tk.W, pady=(5, 0))

        

        # =====================================================================

        # LANGUAGE SELECTION

        # =====================================================================

        lang_frame = ttk.LabelFrame(self.main_frame, text="Translation Language", padding="5")

        lang_frame.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=(0, 10))

        lang_frame.columnconfigure(1, weight=1)

        

        # Language selection

        ttk.Label(lang_frame, text="Translate to:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))

        self.language_var = tk.StringVar(value=self.settings.get("translation_language", "English"))

        self.language_combo = ttk.Combobox(lang_frame, textvariable=self.language_var, 

                                          values=list(self.languages.keys()),

                                          state="readonly", width=20)

        self.language_combo.grid(row=0, column=1, sticky=tk.W, padx=(0, 10))

        self.language_combo.bind("<<ComboboxSelected>>", self.on_language_change)

        

        # Custom text entry (shown only when "Custom" is selected)

        self.custom_text_var = tk.StringVar(value=self.settings.get("custom_text", ""))

        self.custom_label = ttk.Label(lang_frame, text="Custom text:")

        self.custom_entry = ttk.Entry(lang_frame, textvariable=self.custom_text_var, width=40)

        

        self.update_custom_text_visibility()

        

        # =====================================================================

        # BROWSER SETTINGS

        # =====================================================================

        browser_frame = ttk.LabelFrame(self.main_frame, text="Browser Settings", padding="5")

        browser_frame.grid(row=4, column=0, sticky=(tk.W, tk.E), pady=(0, 10))

        browser_frame.columnconfigure(1, weight=1)

        

        # Browser type selection

        ttk.Label(browser_frame, text="Browser:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))

        self.browser_var = tk.StringVar(value=self.settings.get("browser_type", "default"))

        browser_combo = ttk.Combobox(browser_frame, textvariable=self.browser_var, 

                                     values=["default", "chrome", "firefox", "edge", "brave", "custom"],

                                     state="readonly", width=15)

        browser_combo.grid(row=0, column=1, sticky=tk.W, padx=(0, 5))

        browser_combo.bind("<<ComboboxSelected>>", self.on_browser_change)

        

        # Custom browser path

        self.custom_path_var = tk.StringVar(value=self.settings.get("custom_path", ""))

        self.path_label = ttk.Label(browser_frame, text="Custom Path:")

        self.path_entry = ttk.Entry(browser_frame, textvariable=self.custom_path_var)

        self.path_button = ttk.Button(browser_frame, text="Browse...", command=self.browse_browser)

        

        # Show/hide custom path widgets based on selection

        self.update_custom_path_visibility()

        

        # Auto-submit option

        self.auto_submit_var = tk.BooleanVar(value=self.settings.get("auto_submit", True))

        auto_submit_check = ttk.Checkbutton(browser_frame, text="Auto-submit question (uses PyAutoGUI - press Enter automatically)", 

                                           variable=self.auto_submit_var,

                                           command=self.save_settings)

        auto_submit_check.grid(row=2, column=0, columnspan=3, sticky=tk.W, pady=(5, 0))

        

        # =====================================================================

        # SCREENSHOT SECTION

        # =====================================================================

        screenshot_frame = ttk.LabelFrame(self.main_frame, text="Screenshot to ChatGPT", padding="10")

        screenshot_frame.grid(row=5, column=0, sticky=(tk.W, tk.E), pady=(10, 10))

        

        # Frame per i pulsanti screenshot

        screenshot_buttons_frame = ttk.Frame(screenshot_frame)

        screenshot_buttons_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))

        screenshot_buttons_frame.columnconfigure(0, weight=1)

        screenshot_buttons_frame.columnconfigure(1, weight=1)

        

        # Pulsante per screenshot area

        self.area_button = ttk.Button(screenshot_buttons_frame, 

                                     text="📷 CAPTURE AREA\n(Alt+Q)", 

                                     command=self.capture_area, 

                                     width=25,

                                     style="Large.TButton")

        self.area_button.grid(row=0, column=0, padx=5, pady=5, sticky=tk.NSEW)

        

        # Pulsante per screenshot schermo intero

        self.fullscreen_button = ttk.Button(screenshot_buttons_frame, 

                                          text="📸 CAPTURE FULLSCREEN\n(Alt+X)", 

                                          command=self.capture_fullscreen, 

                                          width=25,

                                          style="Large.TButton")

        self.fullscreen_button.grid(row=0, column=1, padx=5, pady=5, sticky=tk.NSEW)

        

        # Pulsante per aprire l'ultimo screenshot

        self.open_last_button = ttk.Button(screenshot_buttons_frame,

                                         text="📂 OPEN LAST SCREENSHOT",

                                         command=self.open_last_screenshot,

                                         width=20)

        self.open_last_button.grid(row=1, column=0, columnspan=2, padx=5, pady=(10, 5), sticky=tk.NSEW)

        

        # Frame per impostazioni shortcuts

        shortcut_frame = ttk.Frame(screenshot_frame)

        shortcut_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(5, 0))

        

        ttk.Label(shortcut_frame, text="Area Shortcut:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))

        self.area_screenshot_key = tk.StringVar(value=self.settings.get("area_screenshot_key", "alt+q"))

        area_entry = ttk.Entry(shortcut_frame, textvariable=self.area_screenshot_key, width=15)

        area_entry.grid(row=0, column=1, sticky=tk.W, padx=(0, 5))

        area_entry.bind('<FocusOut>', lambda e: self.save_settings())

        

        ttk.Label(shortcut_frame, text="Fullscreen Shortcut:").grid(row=0, column=2, sticky=tk.W, padx=(20, 5))

        self.fullscreen_screenshot_key = tk.StringVar(value=self.settings.get("fullscreen_screenshot_key", "alt+x"))

        fullscreen_entry = ttk.Entry(shortcut_frame, textvariable=self.fullscreen_screenshot_key, width=15)

        fullscreen_entry.grid(row=0, column=3, sticky=tk.W, padx=(0, 5))

        fullscreen_entry.bind('<FocusOut>', lambda e: self.save_settings())

        

        ttk.Button(shortcut_frame, text="Update Shortcuts", 

                  command=self.update_hotkeys).grid(row=0, column=4, padx=(10, 0))

        

        # =====================================================================

        # QUESTION SECTION

        # =====================================================================

        question_frame = ttk.LabelFrame(self.main_frame, text="Your Question", padding="5")

        question_frame.grid(row=6, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))

        question_frame.columnconfigure(0, weight=1)

        question_frame.rowconfigure(0, weight=1)

        

        # Text area for question

        self.text_area = tk.Text(question_frame, wrap=tk.WORD, height=8)

        self.text_area.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 5))

        self.text_area.focus()

        

        # Apply dark theme to text area if needed

        if self.dark_mode:

            self.apply_dark_theme_to_widgets()

        

        # Insert default text based on language

        self.insert_default_text()

        

        # Scrollbar for text area

        scrollbar = ttk.Scrollbar(question_frame, orient=tk.VERTICAL, command=self.text_area.yview)

        scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))

        self.text_area.configure(yscrollcommand=scrollbar.set)

        

        # Bind Enter key (Ctrl+Enter to send)

        self.text_area.bind("<Control-Return>", lambda e: self.send_to_chatgpt())

        

        # =====================================================================

        # ACTION BUTTONS

        # =====================================================================

        button_frame = ttk.Frame(self.main_frame)

        button_frame.grid(row=7, column=0, sticky=(tk.W, tk.E), pady=(0, 10))

        button_frame.columnconfigure(0, weight=1)

        

        # Send button

        send_button = ttk.Button(button_frame, text="📤 SEND TO ChatGPT (Ctrl+Enter)", 

                                command=self.send_to_chatgpt,

                                style="Accent.TButton")

        send_button.grid(row=0, column=0, sticky=tk.W, padx=(0, 5))

        

        # Clear button

        clear_button = ttk.Button(button_frame, text="🗑️ CLEAR", command=self.clear_text)

        clear_button.grid(row=0, column=1, sticky=tk.E, padx=(0, 5))

        

        # Open ChatGPT button

        open_chatgpt_button = ttk.Button(button_frame, text="🌐 OPEN ChatGPT", 

                                       command=self.open_chatgpt_only)

        open_chatgpt_button.grid(row=0, column=2, sticky=tk.E)

        

        # =====================================================================

        # STATUS BAR

        # =====================================================================

        self.status_var = tk.StringVar(value="Ready")

        status_bar = ttk.Label(self.main_frame, textvariable=self.status_var, 

                              relief=tk.SUNKEN, anchor=tk.W)

        status_bar.grid(row=8, column=0, sticky=(tk.W, tk.E), pady=(0, 0))

        

        # Setup hotkeys immediately

        self.root.after(500, self.setup_hotkeys)

        

        # Check if should start as icon

        if self.start_as_icon_var.get():

            self.root.after(1000, self.minimize_to_tray)

        

        # Setup window close handler

        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

    

    def update_cursor_preview(self):

        """Update the cursor color preview"""

        self.preview_canvas.delete("all")

        color_config = self.cursor_colors.get(self.cursor_color_var.get(), self.cursor_colors["Red"])

        

        # Draw preview rectangle

        bg_color = color_config["bg_outline"]

        fg_color = color_config["fg_outline"]

        

        # Draw background (thicker)

        self.preview_canvas.create_rectangle(

            3, 3, 27, 27,

            outline=bg_color,

            width=color_config["bg_width"]

        )

        

        # Draw foreground (thinner with dashes)

        self.preview_canvas.create_rectangle(

            5, 5, 25, 25,

            outline=fg_color,

            width=color_config["fg_width"],

            dash=color_config["fg_dash"]

        )

    

    def on_cursor_color_change(self, event=None):

        """Handle cursor color selection change"""

        self.cursor_color = self.cursor_color_var.get()

        self.update_cursor_preview()

        self.save_settings()

        logger.info(f"Cursor color changed to: {self.cursor_color}")

    

    def on_latency_change(self, value):

        """Handle latency slider change"""

        self.startup_latency = float(value)

        self.latency_value_label.config(text=f"{self.startup_latency:.1f}s")

        self.save_settings()

        logger.info(f"Startup latency changed to: {self.startup_latency:.1f}s")

    

    def on_wait_gpt_change(self):

        """Handle Wait GPT checkbox change"""

        self.wait_gpt_loaded = self.wait_gpt_var.get()

        self.save_settings()

        

        if self.wait_gpt_loaded:

            logger.info("Wait GPT enabled - will wait for page to fully load")

            self.status_var.set("Wait GPT enabled - will detect page load")

        else:

            logger.info("Wait GPT disabled - using fixed delay")

            self.status_var.set("Wait GPT disabled - using fixed delay")

    

    def setup_theme(self):

        """Setup light/dark theme"""

        style = ttk.Style()

        

        if self.dark_mode:

            # Dark theme colors

            bg_color = "#2b2b2b"

            fg_color = "#ffffff"

            entry_bg = "#3c3f41"

            button_bg = "#4c5052"

            frame_bg = "#3c3f41"

            select_bg = "#4c5052"

            select_fg = "#ffffff"

            

            # Configure root window

            self.root.configure(bg=bg_color)

            

            # Configure ttk styles

            style.theme_use('clam')

            style.configure(".", 

                           background=bg_color,

                           foreground=fg_color,

                           fieldbackground=entry_bg,

                           selectbackground=select_bg,

                           selectforeground=select_fg)

            

            style.configure("TLabel", background=bg_color, foreground=fg_color)

            style.configure("TFrame", background=bg_color)

            style.configure("TLabelframe", background=frame_bg, foreground=fg_color)

            style.configure("TLabelframe.Label", background=frame_bg, foreground=fg_color)

            style.configure("TButton", 

                           background=button_bg, 

                           foreground=fg_color,

                           borderwidth=1,

                           focusthickness=3,

                           focuscolor='none')

            style.map("TButton",

                     background=[('active', '#5c6062')])

            

            style.configure("TEntry",

                           fieldbackground=entry_bg,

                           foreground=fg_color,

                           insertcolor=fg_color)

            

            style.configure("TCombobox",

                           fieldbackground="#ffffff",  # Sfondo bianco

                           foreground="#000000",  # Testo nero per contrasto

                           background=button_bg)

            style.map("TCombobox",

                     fieldbackground=[('readonly', '#ffffff')],

                     foreground=[('readonly', '#000000')])

            

            style.configure("TCheckbutton",

                           background=bg_color,

                           foreground=fg_color)

            

            style.configure("Large.TButton", 

                           font=('Arial', 10, 'bold'), 

                           padding=10,

                           background=button_bg,

                           foreground=fg_color)

            

            style.configure("Accent.TButton", 

                           font=('Arial', 10, 'bold'), 

                           foreground='#64b5f6',

                           background=button_bg)

        else:

            # Light theme (default)

            style.theme_use('clam')

            

            # Configurazione Combobox per tema chiaro

            style.configure("TCombobox",

                           fieldbackground="#ffffff",  # Sfondo bianco

                           foreground="#000000")  # Testo nero

            style.map("TCombobox",

                     fieldbackground=[('readonly', '#ffffff')],

                     foreground=[('readonly', '#000000')])

            

            style.configure("Large.TButton", font=('Arial', 10, 'bold'), padding=10)

            style.configure("Accent.TButton", font=('Arial', 10, 'bold'), foreground='blue')

    

    def apply_dark_theme_to_widgets(self):

        """Apply dark theme colors to tkinter widgets"""

        # Text widget

        self.text_area.configure(

            bg='#3c3f41',

            fg='#ffffff',

            insertbackground='#ffffff',

            selectbackground='#4c5052',

            selectforeground='#ffffff'

        )

        

        # Preview canvas

        if hasattr(self, 'preview_canvas'):

            self.preview_canvas.configure(bg='#3c3f41')

    

    def toggle_theme(self):

        """Toggle between light and dark theme"""

        self.dark_mode = not self.dark_mode

        self.save_settings()

        

        # Update button text

        theme_text = "🌙 Dark" if not self.dark_mode else "☀️ Light"

        self.theme_button.config(text=theme_text)

        

        # Recreate the UI with new theme

        # Store current values

        current_question = self.text_area.get(1.0, tk.END).strip()

        current_language = self.language_var.get()

        current_custom_text = self.custom_text_var.get()

        current_cursor_color = self.cursor_color_var.get()

        current_latency = self.latency_var.get()

        current_wait_gpt = self.wait_gpt_var.get()

        

        # Clear the window

        for widget in self.root.winfo_children():

            widget.destroy()

        

        # Reinitialize with new theme

        self.__init__(self.root)

        

        # Restore values

        self.language_var.set(current_language)

        self.custom_text_var.set(current_custom_text)

        self.cursor_color_var.set(current_cursor_color)

        self.latency_var.set(current_latency)

        self.wait_gpt_var.set(current_wait_gpt)

        self.on_latency_change(current_latency)  # Update label

        self.on_wait_gpt_change()  # Update state

        self.update_cursor_preview()

        self.insert_default_text()

        if current_question and current_question != self.languages.get(current_language, ""):

            self.text_area.delete(1.0, tk.END)

            self.text_area.insert(1.0, current_question)

        

        logger.info(f"Theme changed to: {'Dark' if self.dark_mode else 'Light'}")

    

    def minimize_to_tray(self):

        """Minimize window to system tray"""

        try:

            import pystray

            from PIL import Image, ImageDraw

        except ImportError:

            messagebox.showwarning("Missing Dependency", 

                                 "pystray module not installed!\n\n"

                                 "Install with: pip install pystray")

            return

        

        # Create tray icon

        def create_image():

            # Create a simple icon with ChatGPT colors

            image = Image.new('RGB', (64, 64), color='#4c5052' if self.dark_mode else '#10A37F')

            draw = ImageDraw.Draw(image)

            draw.rectangle([16, 16, 48, 48], fill='white', outline='black')

            # Simple GPT logo style

            draw.ellipse([20, 20, 44, 44], fill='#10A37F', outline='white')

            return image

        

        # Tray menu actions

        def restore_window(icon, item):

            icon.stop()

            self.restore_from_tray()

        

        def show_about(icon, item):

            messagebox.showinfo("About", "ChatGPT Browser Tool\n\nVersion 1.0\n\nA tool for sending screenshots to chat.openai.com")

        

        def quit_app(icon, item):

            icon.stop()

            self.root.quit()

        

        # Create tray menu

        menu = pystray.Menu(

            pystray.MenuItem("Restore", restore_window),

            pystray.MenuItem("About", show_about),

            pystray.MenuItem("Quit", quit_app)

        )

        

        # Create and run tray icon

        self.tray_icon = pystray.Icon("chatgpt_tool", create_image(), "ChatGPT Browser Tool", menu)

        

        # Hide the main window

        self.root.withdraw()

        self.status_var.set("Minimized to tray - Right-click icon to restore")

        

        # Run tray icon in background thread

        import threading

        tray_thread = threading.Thread(target=self.tray_icon.run, daemon=True)

        tray_thread.start()

        

        logger.info("Application minimized to system tray")

    

    def restore_from_tray(self):

        """Restore window from system tray"""

        if self.tray_icon:

            self.tray_icon.stop()

            self.tray_icon = None

        

        self.root.deiconify()

        self.root.lift()

        self.root.focus_force()

        self.status_var.set("Restored from tray")

        

        logger.info("Application restored from system tray")

    

    def on_closing(self):

        """Handle window closing event"""

        if self.tray_icon:

            # If minimized to tray, just hide the window

            self.root.withdraw()

        else:

            # Otherwise, quit the application

            self.save_settings()

            self.root.quit()

    

    def insert_default_text(self):

        """Insert default text based on selected language"""

        self.text_area.delete(1.0, tk.END)

        lang = self.language_var.get()

        if lang == "Custom":

            custom_text = self.custom_text_var.get()

            if custom_text:

                self.text_area.insert(1.0, custom_text)

        elif lang in self.languages:

            self.text_area.insert(1.0, self.languages[lang])

    

    def on_language_change(self, event=None):

        """Handle language selection change"""

        logger.info(f"Language changed to: {self.language_var.get()}")

        self.update_custom_text_visibility()

        self.insert_default_text()

        self.save_settings()

    

    def update_custom_text_visibility(self):

        """Show/hide custom text widgets based on language selection"""

        if self.language_var.get() == "Custom":

            self.custom_label.grid(row=1, column=0, sticky=tk.W, padx=(0, 5), pady=(5, 0))

            self.custom_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(0, 5), pady=(5, 0))

        else:

            self.custom_label.grid_remove()

            self.custom_entry.grid_remove()

    

    def setup_hotkeys(self):

        """Setup hotkeys using keyboard module"""

        try:

            import keyboard

            

            # Remove existing hotkeys first

            try:

                keyboard.remove_hotkey(self.area_screenshot_key.get())

                keyboard.remove_hotkey(self.fullscreen_screenshot_key.get())

            except:

                pass

            

            # Add new hotkeys

            keyboard.add_hotkey(self.area_screenshot_key.get(), self.capture_area)

            keyboard.add_hotkey(self.fullscreen_screenshot_key.get(), self.capture_fullscreen)

            

            logger.info(f"Hotkeys registered: {self.area_screenshot_key.get()} (area), {self.fullscreen_screenshot_key.get()} (fullscreen)")

            self.status_var.set(f"Hotkeys ready: {self.area_screenshot_key.get()} / {self.fullscreen_screenshot_key.get()}")

            

        except ImportError:

            logger.warning("keyboard module not installed, hotkeys disabled")

            self.status_var.set("Install 'keyboard' module for hotkeys (pip install keyboard)")

        except Exception as e:

            logger.error(f"Failed to setup hotkeys: {e}")

    

    def update_hotkeys(self):

        """Update hotkeys when shortcuts are changed"""

        self.save_settings()

        self.setup_hotkeys()

        messagebox.showinfo("Success", "Shortcuts updated!")

    

    def load_settings(self):

        """Load settings from file"""

        if self.settings_file.exists():

            try:

                with open(self.settings_file, 'r') as f:

                    settings = json.load(f)

                    logger.info(f"Settings loaded successfully: {settings}")

                    return settings

            except Exception as e:

                logger.error(f"Failed to load settings: {e}")

                return {}

        logger.info("No existing settings file found, using defaults")

        return {}

    

    def save_settings(self):

        """Save settings to file"""

        settings = {

            "dark_mode": self.dark_mode,

            "cursor_color": self.cursor_color_var.get(),

            "start_as_icon": self.start_as_icon_var.get(),

            "startup_latency": self.startup_latency,

            "wait_gpt_loaded": self.wait_gpt_loaded,

            "translation_language": self.language_var.get(),

            "custom_text": self.custom_text_var.get(),

            "browser_type": self.browser_var.get(),

            "custom_path": self.custom_path_var.get(),

            "auto_submit": self.auto_submit_var.get(),

            "area_screenshot_key": self.area_screenshot_key.get(),

            "fullscreen_screenshot_key": self.fullscreen_screenshot_key.get()

        }

        try:

            with open(self.settings_file, 'w') as f:

                json.dump(settings, f, indent=2)

            logger.info(f"Settings saved: {settings}")

        except Exception as e:

            logger.error(f"Failed to save settings: {e}")

    

    def on_browser_change(self, event=None):

        """Handle browser selection change"""

        logger.info(f"Browser changed to: {self.browser_var.get()}")

        self.update_custom_path_visibility()

        self.save_settings()

    

    def update_custom_path_visibility(self):

        """Show/hide custom path widgets based on browser selection"""

        if self.browser_var.get() == "custom":

            self.path_label.grid(row=1, column=0, sticky=tk.W, padx=(0, 5), pady=(5, 0))

            self.path_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(0, 5), pady=(5, 0))

            self.path_button.grid(row=1, column=2, sticky=tk.W, pady=(5, 0))

        else:

            self.path_label.grid_remove()

            self.path_entry.grid_remove()

            self.path_button.grid_remove()

    

    def browse_browser(self):

        """Browse for custom browser executable"""

        filename = filedialog.askopenfilename(

            title="Select Browser Executable",

            filetypes=[("Executable Files", "*.exe"), ("All Files", "*.*")]

        )

        if filename:

            self.custom_path_var.set(filename)

            self.save_settings()

    

    def clear_text(self):

        """Clear the text area"""

        self.text_area.delete(1.0, tk.END)

        self.text_area.focus()

        self.status_var.set("Text cleared")

    

    def open_chatgpt_only(self):

        """Just open ChatGPT without sending anything"""

        browser_type = self.browser_var.get()

        chatgpt_url = "https://chat.openai.com"

        

        # Prima minimizza questa finestra

        self.root.iconify()

        

        # Poi apre il browser

        if browser_type == "default":

            webbrowser.open(chatgpt_url)

        else:

            browser_path = self._get_browser_path()

            if browser_path and os.path.exists(browser_path):

                try:

                    subprocess.Popen([browser_path, chatgpt_url])

                except Exception as e:

                    webbrowser.open(chatgpt_url)

            else:

                webbrowser.open(chatgpt_url)

        

        # Aspetta un po' e poi porta il browser in primo piano

        time.sleep(0.8)

        self._bring_browser_to_foreground_simple()

        

        self.status_var.set("ChatGPT opened - this window minimized")

    

    def capture_area(self):

        """Capture area screenshot"""

        if self.is_capturing:

            return

        self.is_capturing = True

        self.status_var.set("Select area to capture (ESC to cancel)...")

        self.root.withdraw()

        self.root.after(200, self._show_selection_overlay)  # Increased delay

    

    def _show_selection_overlay(self):

        """Show overlay for area selection - using approach from example tool"""

        self.selection_window = tk.Toplevel(self.root)

        self.selection_window.attributes('-fullscreen', True)

        self.selection_window.attributes('-alpha', 0.3)  # Changed: Semi-transparent gray overlay

        self.selection_window.attributes('-topmost', True)

        

        # Get selected cursor configuration

        cursor_config = self.cursor_colors.get(self.cursor_color_var.get(), self.cursor_colors["Red"])

        

        # Create canvas with gray background (like the example)

        canvas = tk.Canvas(self.selection_window, cursor="cross", bg='grey', highlightthickness=0)

        canvas.pack(fill=tk.BOTH, expand=True)

        

        self.start_x = None

        self.start_y = None

        self.rect_id = None

        self.bg_rect_id = None

        

        # For rainbow animation

        self.rainbow_color_index = [0]

        

        def update_rainbow_animation():

            """Update rainbow colors for animation"""

            if cursor_config["rainbow"] and self.rect_id and canvas.winfo_exists():

                self.rainbow_color_index[0] = (self.rainbow_color_index[0] + 1) % len(self.rainbow_colors)

                canvas.itemconfig(self.rect_id, outline=self.rainbow_colors[self.rainbow_color_index[0]])

                canvas.after(100, update_rainbow_animation)

        

        def on_mouse_down(event):

            self.start_x = event.x

            self.start_y = event.y

            

            # Create background rectangle (thicker, solid)

            self.bg_rect_id = canvas.create_rectangle(

                self.start_x, self.start_y, self.start_x, self.start_y,

                outline=cursor_config["bg_outline"],

                width=cursor_config["bg_width"]

            )

            

            # Create foreground rectangle (thinner, dashed)

            self.rect_id = canvas.create_rectangle(

                self.start_x, self.start_y, self.start_x, self.start_y,

                outline=cursor_config["fg_outline"],

                width=cursor_config["fg_width"],

                dash=cursor_config["fg_dash"]

            )

            

            # Start rainbow animation if enabled

            if cursor_config["rainbow"]:

                update_rainbow_animation()

        

        def on_mouse_move(event):

            if self.start_x and self.start_y and self.rect_id:

                x1 = min(self.start_x, event.x)

                y1 = min(self.start_y, event.y)

                x2 = max(self.start_x, event.x)

                y2 = max(self.start_y, event.y)

                

                # Update both rectangles

                canvas.coords(self.rect_id, x1, y1, x2, y2)

                if self.bg_rect_id:

                    canvas.coords(self.bg_rect_id, x1, y1, x2, y2)

        

        def on_mouse_up(event):

            if self.start_x and self.start_y:

                x1 = min(self.start_x, event.x)

                y1 = min(self.start_y, event.y)

                x2 = max(self.start_x, event.x)

                y2 = max(self.start_y, event.y)

                

                # Check minimum size

                if (x2 - x1) < 10 or (y2 - y1) < 10:

                    messagebox.showwarning("Area too small", "Please select a larger area.")

                    self._cancel_selection()

                    return

                

                self.selection_window.destroy()

                self._capture_screen_area(x1, y1, x2, y2)

        

        def on_right_click(event):

            """Right click to cancel"""

            self._cancel_selection()

        

        def on_escape(event):

            self._cancel_selection()

        

        canvas.bind("<ButtonPress-1>", on_mouse_down)

        canvas.bind("<B1-Motion>", on_mouse_move)

        canvas.bind("<ButtonRelease-1>", on_mouse_up)

        canvas.bind("<ButtonPress-3>", on_right_click)  # Right click support

        canvas.bind("<Escape>", on_escape)

        

        canvas.focus_set()

        

        # Add instruction label

        instruction = canvas.create_text(

            canvas.winfo_screenwidth() // 2,

            30,

            text="Click and drag to select area. Right-click or ESC to cancel.",

            fill="white",

            font=("Arial", 12, "bold"),

            anchor="center"

        )

        

        # Make instruction visible above gray background

        canvas.tag_raise(instruction)

    

    def _cancel_selection(self):

        """Cancel selection"""

        if self.selection_window:

            self.selection_window.destroy()

            self.selection_window = None

        self.is_capturing = False

        self.root.deiconify()

        self.status_var.set("Area selection cancelled")

    

    def _capture_screen_area(self, x1, y1, x2, y2):

        """Capture the selected area"""

        try:

            time.sleep(0.2)

            

            screenshot = ImageGrab.grab(bbox=(x1, y1, x2, y2))

            self._process_and_send_screenshot(screenshot, "area")

            

        except Exception as e:

            logger.error(f"Failed to capture area: {e}", exc_info=True)

            messagebox.showerror("Error", f"Failed to capture screenshot:\n{e}")

            self.status_var.set("Capture failed")

        finally:

            self.is_capturing = False

            self.root.deiconify()

    

    def capture_fullscreen(self):

        """Capture fullscreen screenshot"""

        if self.is_capturing:

            return

        self.is_capturing = True

        self.status_var.set("Capturing fullscreen...")

        self.root.withdraw()

        self.root.after(200, self._do_fullscreen_capture)  # Increased delay

    

    def _do_fullscreen_capture(self):

        """Do the fullscreen capture"""

        try:

            time.sleep(0.2)

            screenshot = ImageGrab.grab()

            self._process_and_send_screenshot(screenshot, "fullscreen")

            

        except Exception as e:

            logger.error(f"Failed to capture fullscreen: {e}", exc_info=True)

            messagebox.showerror("Error", f"Failed to capture screenshot:\n{e}")

            self.status_var.set("Capture failed")

        finally:

            self.is_capturing = False

            self.root.deiconify()

    

    def _copy_image_to_clipboard(self, image):

        """Copy a PIL Image to the Windows clipboard as CF_DIB."""

        import io

        try:

            import win32clipboard

            import win32con

            bmp_buffer = io.BytesIO()

            image.save(bmp_buffer, format='BMP')

            dib_data = bmp_buffer.getvalue()[14:]


            win32clipboard.OpenClipboard()

            win32clipboard.EmptyClipboard()

            win32clipboard.SetClipboardData(win32con.CF_DIB, dib_data)

            win32clipboard.CloseClipboard()

            logger.info("Image copied to clipboard as CF_DIB successfully")

            return True

        except ImportError:

            logger.warning("pywin32 not installed – install with: pip install pywin32")

            return False

        except Exception as e:

            logger.error(f"Failed to copy image to clipboard: {e}", exc_info=True)

            return False


    def _find_chatgpt_input_box(self):

        """

        Find the ChatGPT input box using a more reliable method.

        Returns (x, y) coordinates if found, None otherwise.

        """

        try:

            import pyautogui

            import pygetwindow as gw

            

            # Try to find ChatGPT window by title (più veloce)

            windows = gw.getWindowsWithTitle('ChatGPT')

            if not windows:

                windows = gw.getWindowsWithTitle('chatgpt')

            

            if windows:

                window = windows[0]

                window.activate()

                time.sleep(0.2)

                

                # Get window dimensions for relative positioning

                window_x, window_y = window.topleft

                window_width, window_height = window.size

                

                # ChatGPT's input box is typically at the bottom center

                input_x = window_x + (window_width // 2)

                input_y = window_y + window_height - 100  # About 100px from bottom

                

                logger.info(f"[find-input] Found ChatGPT window, input at: ({input_x}, {input_y})")

                return (input_x, input_y)

            

            # Fallback: Use screen coordinates based on common resolutions

            screen_width, screen_height = pyautogui.size()

            

            # Try different positions (ChatGPT's UI might vary)

            input_x = screen_width // 2

            input_y = screen_height - 100  # Bottom center

            

            logger.info(f"[find-input] Using fallback position: ({input_x}, {input_y})")

            return (input_x, input_y)

            

        except ImportError:

            logger.warning("[find-input] pygetwindow not available")

            return None

        except Exception as e:

            logger.error(f"[find-input] Error finding input box: {e}")

            return None

    

    def _detect_gpt_page_loaded(self, max_wait_time=30):

        """

        Detect if ChatGPT page is fully loaded by checking for specific elements.

        Returns True when page is ready, False if timeout.

        """

        try:

            import pyautogui

            import pygetwindow as gw

            

            start_time = time.time()

            logger.info(f"[detect-gpt] Starting detection (max {max_wait_time}s)")

            

            while time.time() - start_time < max_wait_time:

                # 1. Check if any browser window with ChatGPT is active

                browser_names = ['Chrome', 'Firefox', 'Edge', 'Brave', 'Safari', 'Opera']

                chatgpt_window = None

                

                for name in browser_names:

                    windows = gw.getWindowsWithTitle(name)

                    for window in windows:

                        if window.title and ('ChatGPT' in window.title or 'chat.openai.com' in window.title):

                            chatgpt_window = window

                            break

                    if chatgpt_window:

                        break

                

                if not chatgpt_window:

                    logger.info(f"[detect-gpt] No ChatGPT window found yet, waiting... ({time.time() - start_time:.1f}s)")

                    time.sleep(1)

                    continue

                

                # 2. Activate the window

                try:

                    chatgpt_window.activate()

                    time.sleep(0.5)

                except:

                    pass

                

                # 3. Try to detect if page is loaded by looking for specific elements

                # Method: Look for common ChatGPT UI elements by color/screenshot

                

                # Get window region

                window_x, window_y = chatgpt_window.topleft

                window_width, window_height = chatgpt_window.size

                

                # Define regions to check (ChatGPT specific)

                check_regions = [

                    (window_x + window_width // 2, window_y + window_height - 50),  # Input area (bottom center)

                    (window_x + 50, window_y + 50),  # Top-left logo area

                    (window_x + window_width - 50, window_y + 50),  # Top-right menu

                ]

                

                # Take a screenshot of a small region to check

                try:

                    # Check a small area in the input region (should be white/gray when loaded)

                    check_region = (

                        window_x + window_width // 2 - 10,

                        window_y + window_height - 60,

                        window_x + window_width // 2 + 10,

                        window_y + window_height - 40

                    )

                    

                    screenshot = pyautogui.screenshot(region=check_region)

                    

                    # Simple check: count non-white pixels (input box should not be pure white when loaded)

                    pixels = list(screenshot.getdata())

                    white_pixels = sum(1 for pixel in pixels if pixel[0] > 200 and pixel[1] > 200 and pixel[2] > 200)

                    total_pixels = len(pixels)

                    

                    # If less than 90% white, probably the input box is loaded

                    if white_pixels / total_pixels < 0.9:

                        logger.info(f"[detect-gpt] Page appears loaded! ({time.time() - start_time:.1f}s)")

                        return True

                    

                except Exception as e:

                    logger.warning(f"[detect-gpt] Error checking region: {e}")

                

                # 4. Alternative: Check if window is not minimized and has reasonable size

                if window_width > 100 and window_height > 100:

                    # Try a simple interaction test

                    try:

                        # Click in the center to ensure focus

                        pyautogui.click(window_x + window_width // 2, window_y + window_height // 2)

                        time.sleep(0.1)

                        

                        # Check if we can type (simple test)

                        pyautogui.write('t', interval=0.05)

                        time.sleep(0.1)

                        pyautogui.press('backspace')

                        

                        logger.info(f"[detect-gpt] Page is interactive! ({time.time() - start_time:.1f}s)")

                        return True

                    except:

                        pass

                

                logger.info(f"[detect-gpt] Page not fully loaded yet, waiting... ({time.time() - start_time:.1f}s)")

                time.sleep(2)  # Wait 2 seconds between checks

            

            logger.warning(f"[detect-gpt] Timeout after {max_wait_time}s")

            return False

            

        except ImportError:

            logger.warning("[detect-gpt] Required modules not available")

            return False

        except Exception as e:

            logger.error(f"[detect-gpt] Error detecting page: {e}")

            return False


    def _ensure_focus_on_input_box(self, click_x, click_y):

        """

        Ensure focus is on the input box with multiple strategies.

        Returns True if successful.

        """

        try:

            import pyautogui

            

            # Strategy più semplice e veloce

            # Click rapido 2 volte

            pyautogui.click(click_x, click_y)

            time.sleep(0.05)

            pyautogui.click(click_x, click_y)

            time.sleep(0.05)

            

            # Verifica rapida scrivendo e cancellando un carattere

            pyautogui.write('t', interval=0.03)

            time.sleep(0.05)

            pyautogui.press('backspace')

            

            logger.info("[focus] Focus strategy completed")

            return True

            

        except Exception as e:

            logger.error(f"[focus] Error ensuring focus: {e}")

            return False


    def _bring_browser_to_foreground_simple(self):

        """Simple method to bring browser to foreground - minimizza prima questa app"""

        try:

            # Prima minimizza questa applicazione (se non è già minimizzata)

            if self.root.state() != 'iconic':

                self.root.iconify()

            time.sleep(0.1)

            

            # Prova a trovare e attivare il browser

            import pygetwindow as gw

            import pyautogui

            

            # Cerca finestre del browser con ChatGPT

            browser_names = ['Chrome', 'Firefox', 'Edge', 'Brave', 'Safari', 'Opera']

            

            # Primo tentativo: cerca finestre con ChatGPT

            for name in browser_names:

                windows = gw.getWindowsWithTitle(name)

                for window in windows:

                    try:

                        if window.title and ('ChatGPT' in window.title or 'chat.openai.com' in window.title):

                            window.activate()

                            time.sleep(0.1)

                            return True

                    except:

                        continue

            

            # Secondo tentativo: cerca qualsiasi finestra del browser

            for name in browser_names:

                windows = gw.getWindowsWithTitle(name)

                if windows:

                    try:

                        window = windows[-1]  # Prendi l'ultima finestra

                        window.activate()

                        time.sleep(0.1)

                        return True

                    except:

                        continue

            

            # Terzo tentativo: usa Alt+Tab

            try:

                pyautogui.keyDown('alt')

                time.sleep(0.05)

                pyautogui.press('tab')

                time.sleep(0.05)

                pyautogui.keyUp('alt')

                time.sleep(0.1)

            except:

                pass

            

            return False

            

        except ImportError:

            logger.warning("pygetwindow not available for foreground control")

            return False

        except Exception as e:

            logger.error(f"Error bringing browser to foreground: {e}")

            return False


    def _process_and_send_screenshot(self, screenshot, screenshot_type):

        """Save screenshot, copy image to clipboard, open ChatGPT, auto-paste via thread."""

        import threading


        try:

            # 1. Save to disk

            with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:

                screenshot_path = tmp.name

                screenshot.save(screenshot_path, 'PNG')

                self.last_screenshot_path = screenshot_path

            logger.info(f"Screenshot saved to: {screenshot_path}")


            # 2. Copy IMAGE to clipboard as CF_DIB

            clipboard_ok = self._copy_image_to_clipboard(screenshot)


            # 3. Get question from text area

            question = self.text_area.get(1.0, tk.END).strip()

            

            # 4. Prima minimizza questa finestra

            self.root.iconify()

            time.sleep(0.1)

            

            # 5. Open ChatGPT via subprocess

            browser_path = self._get_browser_path()

            self.status_var.set("Opening ChatGPT…")

            

            if browser_path and os.path.exists(browser_path):

                # Apri il browser in una nuova finestra

                try:

                    subprocess.Popen([browser_path, "https://chat.openai.com"])

                    logger.info(f"Launched browser via subprocess: {browser_path}")

                except:

                    webbrowser.open("https://chat.openai.com")

                    logger.info("Launched browser via webbrowser (fallback)")

            else:

                webbrowser.open("https://chat.openai.com")

                logger.info("Launched browser via webbrowser (fallback)")


            # 6. Porta il browser in primo piano

            time.sleep(0.8)  # Aspetta che il browser si apra

            self._bring_browser_to_foreground_simple()


            # 7. Auto-paste in background thread

            if clipboard_ok:

                t = threading.Thread(

                    target=self._auto_paste_screenshot_into_chatgpt,

                    args=(question,),

                    daemon=True

                )

                t.start()

            else:

                # Ripristina la finestra per mostrare il messaggio

                self.root.deiconify()

                self.root.lift()

                messagebox.showinfo(

                    "Screenshot saved – manual paste needed",

                    f"✅ Screenshot saved to:\n{screenshot_path}\n\n"

                    f"⚠️ pywin32 not installed – can't auto-paste.\n"

                    f"Install with:  pip install pywin32\n\n"

                    f"📝 Manual: click 📎 in ChatGPT, select the file, send."

                )

                self.status_var.set("Manual attachment needed")


        except Exception as e:

            logger.error(f"Failed to process screenshot: {e}", exc_info=True)

            # Ripristina la finestra in caso di errore

            self.root.deiconify()

            self.root.lift()

            messagebox.showerror("Error", f"Failed to process screenshot:\n{str(e)}")

            self.status_var.set("Screenshot processing failed")


    def _get_browser_path(self):

        """Return the resolved browser executable path based on current settings."""

        browser_type = self.browser_var.get()

        if browser_type == "chrome":

            return self.find_chrome_path()

        elif browser_type == "firefox":

            return self.find_firefox_path()

        elif browser_type == "edge":

            return self.find_edge_path()

        elif browser_type == "brave":

            return self.find_brave_path()

        elif browser_type == "custom":

            return self.custom_path_var.get()

        return None


    def _auto_paste_screenshot_into_chatgpt(self, question):

        """

        Optimized auto-paste with detection of page load when Wait GPT is enabled.

        """

        import pyautogui


        # TIMING OTTIMIZZATO - USO LA LATENZA IMPOSTATA DALL'UTENTE O DETECTION

        WAIT_AFTER_FOCUS = 0.1

        WAIT_IMG_CONSUME = 0.2

        

        try:

            # --- 1. Prima di tutto, assicurati che il browser sia in primo piano ---

            logger.info("[paste-thread] Ensuring browser is in foreground...")

            self._bring_browser_to_foreground_simple()

            time.sleep(0.3)

            

            # --- 2. Wait for page to load (USANDO LA LATENZA O DETECTION) ---

            if self.wait_gpt_loaded:

                # Modalità "Wait GPT": aspetta che la pagina sia effettivamente caricata

                logger.info("[paste-thread] Wait GPT enabled - detecting page load...")

                self.status_var.set("Detecting ChatGPT page load...")

                

                page_loaded = self._detect_gpt_page_loaded()

                

                if page_loaded:

                    logger.info("[paste-thread] ChatGPT page detected as loaded!")

                    self.status_var.set("ChatGPT page loaded - proceeding...")

                else:

                    logger.warning("[paste-thread] Page not detected, using fallback delay")

                    # Fallback: usa la latenza impostata

                    self.status_var.set(f"Using fallback delay ({self.startup_latency:.1f}s)...")

                    time.sleep(self.startup_latency)

            else:

                # Modalità normale: usa la latenza impostata

                logger.info(f"[paste-thread] Wait GPT disabled - waiting {self.startup_latency:.1f}s")

                self.status_var.set(f"Waiting for ChatGPT ({self.startup_latency:.1f}s)...")

                time.sleep(self.startup_latency)


            # --- 3. Find and focus on input box ---

            logger.info("[paste-thread] Finding ChatGPT input box...")

            

            input_coords = self._find_chatgpt_input_box()

            

            if input_coords:

                click_x, click_y = input_coords

                logger.info(f"[paste-thread] Input box at: ({click_x}, {click_y})")

            else:

                # Fallback rapido

                screen_w, screen_h = pyautogui.size()

                click_x = screen_w // 2

                click_y = screen_h - 100

                logger.info(f"[paste-thread] Using default position: ({click_x}, {click_y})")


            # --- 4. Assicura il focus ---

            logger.info("[paste-thread] Ensuring focus on input box...")

            self._ensure_focus_on_input_box(click_x, click_y)

            time.sleep(WAIT_AFTER_FOCUS)


            # --- 5. Clear any existing text in input (più veloce) ---

            logger.info("[paste-thread] Clearing any existing text...")

            pyautogui.hotkey('ctrl', 'a')

            time.sleep(0.05)

            pyautogui.press('delete')

            time.sleep(0.2)


            # --- 6. Paste IMAGE (UNA SOLA VOLTA) ---

            logger.info("[paste-thread] Pasting IMAGE (Ctrl+V)…")

            pyautogui.hotkey('ctrl', 'v')

            logger.info("[paste-thread] Image Ctrl+V sent (solo una volta).")

            time.sleep(0.5)


            # --- 7. Wait for browser to consume image (ridotto) ---

            logger.info(f"[paste-thread] Waiting {WAIT_IMG_CONSUME}s for browser to consume image…")

            time.sleep(WAIT_IMG_CONSUME)


            # --- 8. Overwrite clipboard with question text (più veloce) ---

            if question:

                try:

                    import win32clipboard

                    win32clipboard.OpenClipboard()

                    win32clipboard.EmptyClipboard()

                    

                    if isinstance(question, str):

                        question = question.encode('utf-8')

                    

                    win32clipboard.SetClipboardData(win32clipboard.CF_TEXT, question)

                    win32clipboard.CloseClipboard()

                    logger.info("[paste-thread] Question text set on clipboard")

                except Exception as e:

                    logger.error(f"[paste-thread] Failed to set text on clipboard: {e}")

                    try:

                        import pyperclip

                        pyperclip.copy(question.decode('utf-8') if isinstance(question, bytes) else question)

                        logger.info("[paste-thread] Question text set via pyperclip")

                    except:

                        pass


                time.sleep(0.2)


                # --- 9. Paste TEXT (più veloce) ---

                logger.info("[paste-thread] Pasting TEXT (Ctrl+V)…")

                pyautogui.hotkey('ctrl', 'v')

                logger.info("[paste-thread] Text Ctrl+V sent.")

                time.sleep(0.3)


                # --- 10. Auto-submit (più veloce) ---

                if self.auto_submit_var.get():

                    logger.info("[paste-thread] Auto-submitting (Enter)…")

                    pyautogui.press('enter')

                    logger.info("[paste-thread] Enter pressed – question submitted.")

                    # IMPORTANTE: NON ripristinare la finestra qui!

                    # Aggiorna solo lo status

                    self.status_var.set("✅ Screenshot + question sent to ChatGPT!")

                else:

                    self.status_var.set("✅ Screenshot + question pasted – press Enter to send")

            else:

                self.status_var.set("✅ Screenshot pasted – type your question and press Enter")


        except ImportError as e:

            logger.error(f"[paste-thread] Missing module: {e}")

            self.status_var.set("Auto-paste failed – missing module")

        except Exception as e:

            logger.error(f"[paste-thread] Auto-paste failed: {e}", exc_info=True)

            self.status_var.set("Auto-paste failed – see log")

    

    def open_last_screenshot(self):

        """Open the last captured screenshot"""

        if self.last_screenshot_path and os.path.exists(self.last_screenshot_path):

            try:

                if sys.platform == "win32":

                    os.startfile(self.last_screenshot_path)

                elif sys.platform == "darwin":

                    subprocess.run(["open", self.last_screenshot_path])

                else:

                    subprocess.run(["xdg-open", self.last_screenshot_path])

                self.status_var.set(f"Opened: {os.path.basename(self.last_screenshot_path)}")

            except Exception as e:

                messagebox.showerror("Error", f"Cannot open file:\n{e}")

        else:

            messagebox.showinfo("No Screenshot", "No screenshot has been captured yet.")

    

    # =========================================================================

    # BROWSER METHODS

    # =========================================================================

    

    def send_to_chatgpt(self):

        """Send the question to ChatGPT via browser"""

        question = self.text_area.get(1.0, tk.END).strip()

        

        if not question:

            messagebox.showwarning("Empty Question", "Please enter a question first!")

            return

        

        browser_type = self.browser_var.get()

        auto_submit = self.auto_submit_var.get()

        

        if auto_submit:

            self.send_with_pyautogui(question, browser_type)

        else:

            self.send_with_url(question, browser_type)

    

    def send_with_pyautogui(self, question, browser_type):

        """Send question using PyAutoGUI for auto-submit"""

        try:

            import pyautogui

            

            encoded_question = urllib.parse.quote(question)

            chatgpt_url = f"https://chat.openai.com/?q={encoded_question}"

            

            # Prima minimizza questa finestra

            self.root.iconify()

            time.sleep(0.1)

            

            # Apri il browser

            if browser_type == "default":

                webbrowser.open(chatgpt_url)

            else:

                browser_path = self._get_browser_path()

                if browser_path and os.path.exists(browser_path):

                    try:

                        subprocess.Popen([browser_path, chatgpt_url])

                    except:

                        webbrowser.open(chatgpt_url)

                else:

                    webbrowser.open(chatgpt_url)

            

            # Porta il browser in primo piano

            time.sleep(0.8)

            self._bring_browser_to_foreground_simple()

            

            # Usa la latenza impostata dall'utente prima di premere Enter

            time.sleep(self.startup_latency)

            pyautogui.press('enter')

            

            self.status_var.set("Question submitted successfully!")

            

        except ImportError:

            # Ripristina la finestra

            self.root.deiconify()

            self.root.lift()

            messagebox.showerror("PyAutoGUI Required", 

                               "PyAutoGUI is not installed!\n\n"

                               "Install: pip install pyautogui\n"

                               "Or disable auto-submit.")

            self.status_var.set("Error: PyAutoGUI not installed")

        except Exception as e:

            # Ripristina la finestra

            self.root.deiconify()

            self.root.lift()

            messagebox.showerror("Error", f"Failed to auto-submit:\n{str(e)}")

            self.status_var.set(f"Error: {str(e)}")

    

    def send_with_url(self, question, browser_type):

        """Send question using URL parameter"""

        encoded_question = urllib.parse.quote(question)

        chatgpt_url = f"https://chat.openai.com/?q={encoded_question}"

        

        try:

            # Prima minimizza questa finestra

            self.root.iconify()

            time.sleep(0.1)

            

            # Apri il browser

            if browser_type == "default":

                webbrowser.open(chatgpt_url)

            else:

                browser_path = self._get_browser_path()

                if browser_path and os.path.exists(browser_path):

                    try:

                        subprocess.Popen([browser_path, chatgpt_url])

                    except:

                        webbrowser.open(chatgpt_url)

                else:

                    webbrowser.open(chatgpt_url)

            

            # Porta il browser in primo piano

            time.sleep(0.8)

            self._bring_browser_to_foreground_simple()

            

            self.status_var.set("Opened in browser (press Enter to send)")

        except Exception as e:

            # Ripristina la finestra

            self.root.deiconify()

            self.root.lift()

            messagebox.showerror("Error", f"Failed to open browser:\n{str(e)}")

            self.status_var.set(f"Error: {str(e)}")

    

    def open_browser(self, browser_type, url):

        """Open URL in specified browser (legacy method)"""

        logger.info(f"Opening {browser_type} with URL: {url}")

        

        if browser_type == "default":

            webbrowser.open(url)

            self.status_var.set(f"Opened: {url}")

            return

        

        browser_path = None

        if browser_type == "chrome":

            browser_path = self.find_chrome_path()

        elif browser_type == "firefox":

            browser_path = self.find_firefox_path()

        elif browser_type == "edge":

            browser_path = self.find_edge_path()

        elif browser_type == "brave":

            browser_path = self.find_brave_path()

        elif browser_type == "custom":

            browser_path = self.custom_path_var.get()

        

        if browser_path and os.path.exists(browser_path):

            try:

                browser_name = f"custom_{browser_type}"

                webbrowser.register(browser_name, None, webbrowser.BackgroundBrowser(browser_path))

                webbrowser.get(browser_name).open(url)

                self.status_var.set(f"Opened in {browser_type}")

            except Exception as e:

                logger.warning(f"Failed to open with {browser_type}: {e}, falling back to default")

                webbrowser.open(url)

                self.status_var.set(f"{browser_type} failed, using default")

        else:

            logger.warning(f"{browser_type} not found, falling back to default")

            webbrowser.open(url)

            self.status_var.set(f"{browser_type} not found, using default")

    

    def find_chrome_path(self):

        """Find Chrome executable path"""

        paths = [

            r"C:\Program Files\Google\Chrome\Application\chrome.exe",

            r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",

        ]

        return self._find_first_existing(paths)

    

    def find_firefox_path(self):

        """Find Firefox executable path"""

        paths = [

            r"C:\Program Files\Mozilla Firefox\firefox.exe",

            r"C:\Program Files (x86)\Mozilla Firefox\firefox.exe",

        ]

        return self._find_first_existing(paths)

    

    def find_edge_path(self):

        """Find Edge executable path"""

        paths = [

            r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",

            r"C:\Program Files\Microsoft\Edge\Application\msedge.exe",

        ]

        return self._find_first_existing(paths)

    

    def find_brave_path(self):

        """Find Brave executable path"""

        paths = [

            r"C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe",

            r"C:\Program Files (x86)\BraveSoftware\Brave-Browser\Application\brave.exe",

            os.path.expanduser(r"~\AppData\Local\BraveSoftware\Brave-Browser\Application\brave.exe"),

        ]

        return self._find_first_existing(paths)

    

    def _find_first_existing(self, paths):

        """Find first existing path in list"""

        for path in paths:

            if os.path.exists(path):

                return path

        return None



def main():

    root = tk.Tk()

    app = ChatGPTBrowserTool(root)

    root.mainloop()



if __name__ == "__main__":

    main()


Commenti

Post popolari in questo blog

No Man's Sky Similar Games

Tower Defense Games

Auto Copy Folder - Source Code