svg-processor/utils.py
2025-05-16 18:15:31 +04:00

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}")