import os import sys import io import tkinter as tk # Needed for RedirectText TclError handling # Attempt to import PIL, handle if not found try: from PIL import Image, ImageTk _PIL_AVAILABLE = True except ImportError: _PIL_AVAILABLE = False # Define dummy classes if PIL not available to avoid NameErrors later class Image: @staticmethod def open(path): raise ImportError("PIL/Pillow is not installed.") class Resampling: LANCZOS = None # Placeholder class ImageTk: @staticmethod def PhotoImage(img): raise ImportError("PIL/Pillow is not installed.") def get_application_path(): """Get the base path for the application, works for both dev and PyInstaller""" if getattr(sys, 'frozen', False): # If the application is run as a bundle (PyInstaller) # Use the directory of the executable return os.path.dirname(sys.executable) else: # If running as a regular Python script return os.path.dirname(os.path.abspath(__file__)) def resource_path(relative_path): """ Get absolute path to resource, works for dev and for PyInstaller. This function helps locate resources whether running from source or from a packaged executable. Args: relative_path (str): Path relative to the script or executable Returns: str: Absolute path to the resource """ try: # PyInstaller creates a temp folder and stores path in _MEIPASS base_path = getattr(sys, '_MEIPASS', None) if base_path is None: # Fall back to application directory base_path = get_application_path() except Exception: base_path = get_application_path() return os.path.join(base_path, relative_path) class RedirectText: """ Redirect stdout to a tkinter widget. This class provides a file-like interface to redirect standard output to a tkinter text widget. It's used to capture console output for display in the GUI. """ def __init__(self, text_widget): """ Initialize the stdout redirector. Args: text_widget: A tkinter text or scrolledtext widget to receive the output. """ self.text_widget = text_widget # self.buffer = io.StringIO() # Removed: Not used, text_buffer handles accumulation self.text_buffer = "" def write(self, string): """ Write string to the buffer and text widget. This method is called when text is written to stdout. Args: string (str): The string to write to the widget. """ try: # Accumulate text and schedule updates in batches for smoother UI self.text_buffer += str(string) # Ensure string conversion # Flush more frequently or on newline to see logs sooner if len(self.text_buffer) >= 50 or '\n' in self.text_buffer: self._flush_text_buffer() except Exception as e: # Handle potential errors, e.g., if widget is destroyed prematurely print(f"RedirectText Error writing: {e}") # Print to actual stderr def _flush_text_buffer(self): """Flush accumulated text to the widget for smoother UI updates.""" if not self.text_buffer: return text_to_insert = self.text_buffer self.text_buffer = "" # Clear buffer before potential error try: if self.text_widget and self.text_widget.winfo_exists(): # Add the text to the widget self.text_widget.insert(tk.END, text_to_insert) self.text_widget.see(tk.END) # Auto-scroll to the end # Only call update_idletasks occasionally to reduce UI freezing # Maybe only on newlines? if '\n' in text_to_insert: self.text_widget.update_idletasks() else: # Widget might have been destroyed print(f"RedirectText Warning: Target widget does not exist.") except tk.TclError as e: # Handle tkinter errors (e.g., widget destroyed) print(f"RedirectText TclError flushing buffer: {e}") except Exception as e: # Handle other unexpected errors during flush print(f"RedirectText Unexpected error flushing buffer: {e}") def flush(self): """ Flush the buffer. This method is called when the buffer needs to be flushed. It's required for file-like objects. Ensures any remaining text is written. """ try: self._flush_text_buffer() # Ensure Tkinter buffer is flushed except Exception as e: print(f"RedirectText Error during flush: {e}") def getvalue(self): """ Get the current value of the internal text buffer. Returns: str: The current accumulated text in the buffer. """ return self.text_buffer # Add isatty() method for compatibility with some libraries that check it def isatty(self): return False # --- Icon Handling Functions --- def find_icon_file(): """ Find the specific icon file using resource_path. Returns: str or None: Absolute path to the automation_standard_logo.png file, or None if not found. """ # Only look for automation_standard_logo.png as requested icon_file = "automation_standard_logo.png" try: path = resource_path(icon_file) # resource_path handles packaged app paths if os.path.exists(path): # print(f"Found icon: {path}") # Optional debug return path except Exception as e: # Log error if needed print(f"Error finding icon {icon_file}: {e}") return None # Return None if icon not found def set_window_icon(root_window): """ Set the window icon using resource_path and handling different platforms/formats. Args: root_window: The tkinter root window (tk.Tk instance). """ if not root_window or not isinstance(root_window, tk.Tk): print("Warning: Invalid root window provided to set_window_icon.") return try: icon_path = find_icon_file() # Uses resource_path internally if not icon_path: print("Warning: Could not find icon file.") return # Always use PhotoImage method for .png file set_window_icon_photoimage(root_window, icon_path) except Exception as e: print(f"Error setting window icon: {e}") def set_window_icon_photoimage(root_window, icon_path): """Helper to set window icon using PhotoImage (handles PIL dependency).""" if not _PIL_AVAILABLE: print("Warning: PIL/Pillow not found. Cannot set image icon.") return try: icon_img = Image.open(icon_path) # Resize if needed, e.g., to 32x32 or 64x64 (adjust as desired) icon_img = icon_img.resize((32, 32), Image.Resampling.LANCZOS) icon_photo = ImageTk.PhotoImage(icon_img) # Store reference on the root window to avoid garbage collection root_window.icon_photo_ref = icon_photo root_window.iconphoto(True, icon_photo) # print(f"Applied icon using iconphoto: {icon_path}") except ImportError: # This case should be caught by _PIL_AVAILABLE, but added for safety print("Warning: PIL/Pillow import failed. Cannot set image icon.") except tk.TclError as tcl_err: print(f"Error setting iconphoto: {tcl_err}") except Exception as img_e: print(f"Error applying image icon: {img_e}")