218 lines
7.6 KiB
Python
218 lines
7.6 KiB
Python
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}") |