replace tooltip animation with exist tooltip, redundancy reduced, search optimized, add new icons copy and stair (for path folllow)
This commit is contained in:
516
animated_icon.py
516
animated_icon.py
@@ -453,21 +453,21 @@ class AnimatedIcon(tk.Canvas):
|
||||
|
||||
def _animate(self) -> None:
|
||||
"""The main animation loop."""
|
||||
if self.running:
|
||||
try:
|
||||
# Check if a modal dialog has the grab on the entire application
|
||||
if self.winfo_toplevel().grab_current() is not None:
|
||||
self.after(100, self._animate) # Check again after a short delay
|
||||
return
|
||||
except Exception:
|
||||
# This can happen if no grab is active. We can safely ignore it.
|
||||
pass
|
||||
if not self.winfo_exists() or not self.running:
|
||||
return
|
||||
|
||||
self.angle += 0.1
|
||||
if self.angle > 2 * pi:
|
||||
self.angle -= 2 * pi
|
||||
self._draw_frame()
|
||||
self.after(30, self._animate)
|
||||
# Do not animate if a grab is active on a different window.
|
||||
toplevel = self.winfo_toplevel()
|
||||
grab_widget = toplevel.grab_current()
|
||||
if grab_widget is not None and grab_widget != toplevel:
|
||||
self.after(100, self._animate) # Check again after a short delay
|
||||
return
|
||||
|
||||
self.angle += 0.1
|
||||
if self.angle > 2 * pi:
|
||||
self.angle -= 2 * pi
|
||||
self._draw_frame()
|
||||
self.after(30, self._animate)
|
||||
|
||||
def start(self, pulse: bool = False) -> None:
|
||||
"""
|
||||
@@ -476,6 +476,8 @@ class AnimatedIcon(tk.Canvas):
|
||||
Args:
|
||||
pulse (bool): If True, plays a pulsing animation instead of the main one.
|
||||
"""
|
||||
if not self.winfo_exists():
|
||||
return
|
||||
if not self.running:
|
||||
self.pulse_animation = pulse
|
||||
self.running = True
|
||||
@@ -483,497 +485,23 @@ class AnimatedIcon(tk.Canvas):
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stops the animation and shows the static 'stopped' frame."""
|
||||
if not self.winfo_exists():
|
||||
return
|
||||
self.running = False
|
||||
self.pulse_animation = False
|
||||
self._draw_stopped_frame()
|
||||
|
||||
def hide(self) -> None:
|
||||
"""Stops the animation and clears the canvas."""
|
||||
if not self.winfo_exists():
|
||||
return
|
||||
self.running = False
|
||||
self.pulse_animation = False
|
||||
self.delete("all")
|
||||
|
||||
def show_full_circle(self) -> None:
|
||||
"""Shows the static 'stopped' frame without starting the animation."""
|
||||
if not self.running:
|
||||
self._draw_stopped_frame()
|
||||
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageDraw, ImageTk
|
||||
PIL_AVAILABLE = True
|
||||
except ImportError:
|
||||
PIL_AVAILABLE = False
|
||||
|
||||
|
||||
def _hex_to_rgb(hex_color):
|
||||
"""Converts a hex color string to an RGB tuple."""
|
||||
hex_color = hex_color.lstrip('#')
|
||||
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
||||
|
||||
|
||||
class AnimatedIcon(tk.Canvas):
|
||||
"""A custom Tkinter Canvas widget for displaying animations."""
|
||||
|
||||
def __init__(self, master, width=20, height=20, animation_type="counter_arc", color="#2a6fde", highlight_color="#5195ff", use_pillow=False, bg=None):
|
||||
"""
|
||||
Initializes the AnimatedIcon widget.
|
||||
|
||||
Args:
|
||||
master: The parent widget.
|
||||
width (int): The width of the icon.
|
||||
height (int): The height of the icon.
|
||||
animation_type (str): The type of animation to display.
|
||||
Options: "counter_arc", "double_arc", "line", "blink".
|
||||
color (str): The primary color of the icon.
|
||||
highlight_color (str): The highlight color of the icon.
|
||||
use_pillow (bool): Whether to use Pillow for drawing if available.
|
||||
bg (str): The background color of the canvas.
|
||||
"""
|
||||
if bg is None:
|
||||
try:
|
||||
bg = master.cget("background")
|
||||
except tk.TclError:
|
||||
bg = "#f0f0f0" # Fallback color
|
||||
super().__init__(master, width=width, height=height, bg=bg, highlightthickness=0)
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.animation_type = animation_type
|
||||
self.color = color
|
||||
self.highlight_color = highlight_color
|
||||
self.use_pillow = use_pillow and PIL_AVAILABLE
|
||||
self.running = False
|
||||
self.angle = 0
|
||||
self.pulse_animation = False
|
||||
|
||||
self.color_rgb = _hex_to_rgb(self.color)
|
||||
self.highlight_color_rgb = _hex_to_rgb(self.highlight_color)
|
||||
|
||||
if self.use_pillow:
|
||||
self.image = Image.new(
|
||||
"RGBA", (width * 4, height * 4), (0, 0, 0, 0))
|
||||
self.draw = ImageDraw.Draw(self.image)
|
||||
self.photo_image = None
|
||||
|
||||
def _draw_frame(self):
|
||||
"""Draws a single frame of the animation."""
|
||||
if self.use_pillow:
|
||||
self._draw_pillow_frame()
|
||||
else:
|
||||
self._draw_canvas_frame()
|
||||
|
||||
def _draw_canvas_frame(self):
|
||||
"""Draws a frame using native Tkinter canvas methods."""
|
||||
self.delete("all")
|
||||
if self.pulse_animation:
|
||||
self._draw_canvas_pulse()
|
||||
elif self.animation_type == "line":
|
||||
self._draw_canvas_line()
|
||||
elif self.animation_type == "double_arc":
|
||||
self._draw_canvas_double_arc()
|
||||
elif self.animation_type == "counter_arc":
|
||||
self._draw_canvas_counter_arc()
|
||||
elif self.animation_type == "blink":
|
||||
self._draw_canvas_blink()
|
||||
|
||||
def _draw_canvas_pulse(self):
|
||||
"""Draws the pulse animation using canvas methods."""
|
||||
center_x, center_y = self.width / 2, self.height / 2
|
||||
alpha = (sin(self.angle * 5) + 1) / 2 # Faster pulse
|
||||
r = int(
|
||||
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||
g = int(
|
||||
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||
b = int(
|
||||
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||
pulse_color = f"#{r:02x}{g:02x}{b:02x}"
|
||||
|
||||
if self.animation_type == "line":
|
||||
for i in range(8):
|
||||
angle = i * (pi / 4)
|
||||
start_x = center_x + cos(angle) * (self.width * 0.2)
|
||||
start_y = center_y + sin(angle) * (self.height * 0.2)
|
||||
end_x = center_x + cos(angle) * (self.width * 0.4)
|
||||
end_y = center_y + sin(angle) * (self.height * 0.4)
|
||||
self.create_line(start_x, start_y, end_x,
|
||||
end_y, fill=pulse_color, width=2)
|
||||
elif self.animation_type == "double_arc":
|
||||
radius = min(center_x, center_y) * 0.8
|
||||
bbox = (center_x - radius, center_y - radius,
|
||||
center_x + radius, center_y + radius)
|
||||
self.create_arc(bbox, start=0, extent=359.9,
|
||||
style=tk.ARC, outline=pulse_color, width=2)
|
||||
elif self.animation_type == "counter_arc":
|
||||
radius_outer = min(center_x, center_y) * 0.8
|
||||
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||
center_x + radius_outer, center_y + radius_outer)
|
||||
self.create_arc(bbox_outer, start=0, extent=359.9,
|
||||
style=tk.ARC, outline=pulse_color, width=2)
|
||||
radius_inner = min(center_x, center_y) * 0.6
|
||||
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||
center_x + radius_inner, center_y + radius_inner)
|
||||
self.create_arc(bbox_inner, start=0, extent=359.9,
|
||||
style=tk.ARC, outline=self.color, width=2)
|
||||
|
||||
def _draw_canvas_line(self):
|
||||
"""Draws the line animation using canvas methods."""
|
||||
center_x, center_y = self.width / 2, self.height / 2
|
||||
for i in range(8):
|
||||
angle = self.angle + i * (pi / 4)
|
||||
start_x = center_x + cos(angle) * (self.width * 0.2)
|
||||
start_y = center_y + sin(angle) * (self.height * 0.2)
|
||||
end_x = center_x + cos(angle) * (self.width * 0.4)
|
||||
end_y = center_y + sin(angle) * (self.height * 0.4)
|
||||
alpha = (cos(self.angle * 2 + i * (pi / 4)) + 1) / 2
|
||||
|
||||
r = int(
|
||||
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||
g = int(
|
||||
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||
b = int(
|
||||
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||
color = f"#{r:02x}{g:02x}{b:02x}"
|
||||
|
||||
self.create_line(start_x, start_y, end_x,
|
||||
end_y, fill=color, width=2)
|
||||
|
||||
def _draw_canvas_double_arc(self):
|
||||
"""Draws the double arc animation using canvas methods."""
|
||||
center_x, center_y = self.width / 2, self.height / 2
|
||||
radius = min(center_x, center_y) * 0.8
|
||||
bbox = (center_x - radius, center_y - radius,
|
||||
center_x + radius, center_y + radius)
|
||||
|
||||
start_angle1 = -self.angle * 180 / pi
|
||||
extent1 = 120 + 60 * sin(-self.angle)
|
||||
self.create_arc(bbox, start=start_angle1, extent=extent1,
|
||||
style=tk.ARC, outline=self.highlight_color, width=2)
|
||||
|
||||
start_angle2 = (-self.angle + pi) * 180 / pi
|
||||
extent2 = 120 + 60 * sin(-self.angle + pi / 2)
|
||||
self.create_arc(bbox, start=start_angle2, extent=extent2,
|
||||
style=tk.ARC, outline=self.color, width=2)
|
||||
|
||||
def _draw_canvas_counter_arc(self):
|
||||
"""Draws the counter arc animation using canvas methods."""
|
||||
center_x, center_y = self.width / 2, self.height / 2
|
||||
|
||||
radius_outer = min(center_x, center_y) * 0.8
|
||||
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||
center_x + radius_outer, center_y + radius_outer)
|
||||
start_angle1 = -self.angle * 180 / pi
|
||||
self.create_arc(bbox_outer, start=start_angle1, extent=150,
|
||||
style=tk.ARC, outline=self.highlight_color, width=2)
|
||||
|
||||
radius_inner = min(center_x, center_y) * 0.6
|
||||
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||
center_x + radius_inner, center_y + radius_inner)
|
||||
start_angle2 = self.angle * 180 / pi + 60
|
||||
self.create_arc(bbox_inner, start=start_angle2, extent=150,
|
||||
style=tk.ARC, outline=self.color, width=2)
|
||||
|
||||
def _draw_canvas_blink(self):
|
||||
"""Draws the blink animation using canvas methods."""
|
||||
center_x, center_y = self.width / 2, self.height / 2
|
||||
radius = min(center_x, center_y) * 0.8
|
||||
alpha = (sin(self.angle * 2) + 1) / 2 # Slower blinking speed
|
||||
r = int(
|
||||
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||
g = int(
|
||||
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||
b = int(
|
||||
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||
blink_color = f"#{r:02x}{g:02x}{b:02x}"
|
||||
self.create_arc(center_x - radius, center_y - radius, center_x + radius, center_y +
|
||||
radius, start=0, extent=359.9, style=tk.ARC, outline=blink_color, width=4)
|
||||
|
||||
def _draw_pillow_frame(self):
|
||||
"""Draws a frame using Pillow for anti-aliased graphics."""
|
||||
self.draw.rectangle(
|
||||
[0, 0, self.width * 4, self.height * 4], fill=(0, 0, 0, 0))
|
||||
if self.pulse_animation:
|
||||
self._draw_pillow_pulse()
|
||||
elif self.animation_type == "line":
|
||||
self._draw_pillow_line()
|
||||
elif self.animation_type == "double_arc":
|
||||
self._draw_pillow_double_arc()
|
||||
elif self.animation_type == "counter_arc":
|
||||
self._draw_pillow_counter_arc()
|
||||
elif self.animation_type == "blink":
|
||||
self._draw_pillow_blink()
|
||||
|
||||
resized_image = self.image.resize(
|
||||
(self.width, self.height), Image.Resampling.LANCZOS)
|
||||
self.photo_image = ImageTk.PhotoImage(resized_image)
|
||||
self.delete("all")
|
||||
self.create_image(0, 0, anchor="nw", image=self.photo_image)
|
||||
|
||||
def _draw_pillow_pulse(self):
|
||||
"""Draws the pulse animation using Pillow."""
|
||||
center_x, center_y = self.width * 2, self.height * 2
|
||||
alpha = (sin(self.angle * 5) + 1) / 2 # Faster pulse
|
||||
r = int(
|
||||
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||
g = int(
|
||||
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||
b = int(
|
||||
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||
pulse_color = (r, g, b)
|
||||
|
||||
if self.animation_type == "line":
|
||||
for i in range(12):
|
||||
angle = i * (pi / 6)
|
||||
start_x = center_x + cos(angle) * (self.width * 0.8)
|
||||
start_y = center_y + sin(angle) * (self.height * 0.8)
|
||||
end_x = center_x + cos(angle) * (self.width * 1.6)
|
||||
end_y = center_y + sin(angle) * (self.height * 1.6)
|
||||
self.draw.line([(start_x, start_y), (end_x, end_y)],
|
||||
fill=pulse_color, width=6, joint="curve")
|
||||
elif self.animation_type == "double_arc":
|
||||
radius = min(center_x, center_y) * 0.8
|
||||
bbox = (center_x - radius, center_y - radius,
|
||||
center_x + radius, center_y + radius)
|
||||
self.draw.arc(bbox, start=0, end=360, fill=pulse_color, width=5)
|
||||
elif self.animation_type == "counter_arc":
|
||||
radius_outer = min(center_x, center_y) * 0.8
|
||||
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||
center_x + radius_outer, center_y + radius_outer)
|
||||
self.draw.arc(bbox_outer, start=0, end=360,
|
||||
fill=pulse_color, width=7)
|
||||
radius_inner = min(center_x, center_y) * 0.6
|
||||
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||
center_x + radius_inner, center_y + radius_inner)
|
||||
self.draw.arc(bbox_inner, start=0, end=360,
|
||||
fill=self.color_rgb, width=7)
|
||||
|
||||
def _draw_pillow_line(self):
|
||||
"""Draws the line animation using Pillow."""
|
||||
center_x, center_y = self.width * 2, self.height * 2
|
||||
for i in range(12):
|
||||
angle = self.angle + i * (pi / 6)
|
||||
start_x = center_x + cos(angle) * (self.width * 0.8)
|
||||
start_y = center_y + sin(angle) * (self.height * 0.8)
|
||||
end_x = center_x + cos(angle) * (self.width * 1.6)
|
||||
end_y = center_y + sin(angle) * (self.height * 1.6)
|
||||
alpha = (cos(self.angle * 2.5 + i * (pi / 6)) + 1) / 2
|
||||
|
||||
r = int(
|
||||
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||
g = int(
|
||||
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||
b = int(
|
||||
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||
color = (r, g, b)
|
||||
|
||||
self.draw.line([(start_x, start_y), (end_x, end_y)],
|
||||
fill=color, width=6, joint="curve")
|
||||
|
||||
def _draw_pillow_double_arc(self):
|
||||
"""Draws the double arc animation using Pillow."""
|
||||
center_x, center_y = self.width * 2, self.height * 2
|
||||
radius = min(center_x, center_y) * 0.8
|
||||
bbox = (center_x - radius, center_y - radius,
|
||||
center_x + radius, center_y + radius)
|
||||
|
||||
start_angle1 = self.angle * 180 / pi
|
||||
extent1 = 120 + 60 * sin(self.angle)
|
||||
self.draw.arc(bbox, start=start_angle1, end=start_angle1 +
|
||||
extent1, fill=self.highlight_color_rgb, width=5)
|
||||
|
||||
start_angle2 = (self.angle + pi) * 180 / pi
|
||||
extent2 = 120 + 60 * sin(self.angle + pi / 2)
|
||||
self.draw.arc(bbox, start=start_angle2, end=start_angle2 +
|
||||
extent2, fill=self.color_rgb, width=5)
|
||||
|
||||
def _draw_pillow_counter_arc(self):
|
||||
"""Draws the counter arc animation using Pillow."""
|
||||
center_x, center_y = self.width * 2, self.height * 2
|
||||
|
||||
radius_outer = min(center_x, center_y) * 0.8
|
||||
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||
center_x + radius_outer, center_y + radius_outer)
|
||||
start_angle1 = self.angle * 180 / pi
|
||||
self.draw.arc(bbox_outer, start=start_angle1, end=start_angle1 +
|
||||
150, fill=self.highlight_color_rgb, width=7)
|
||||
|
||||
radius_inner = min(center_x, center_y) * 0.6
|
||||
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||
center_x + radius_inner, center_y + radius_inner)
|
||||
start_angle2 = -self.angle * 180 / pi + 60
|
||||
self.draw.arc(bbox_inner, start=start_angle2,
|
||||
end=start_angle2 + 150, fill=self.color_rgb, width=7)
|
||||
|
||||
def _draw_pillow_blink(self):
|
||||
"""Draws the blink animation using Pillow."""
|
||||
center_x, center_y = self.width * 2, self.height * 2
|
||||
radius = min(center_x, center_y) * 0.8
|
||||
alpha = (sin(self.angle * 2) + 1) / 2 # Slower blinking speed
|
||||
r = int(
|
||||
alpha * (self.highlight_color_rgb[0] - self.color_rgb[0]) + self.color_rgb[0])
|
||||
g = int(
|
||||
alpha * (self.highlight_color_rgb[1] - self.color_rgb[1]) + self.color_rgb[1])
|
||||
b = int(
|
||||
alpha * (self.highlight_color_rgb[2] - self.color_rgb[2]) + self.color_rgb[2])
|
||||
blink_color = (r, g, b)
|
||||
self.draw.arc((center_x - radius, center_y - radius, center_x + radius,
|
||||
center_y + radius), start=0, end=360, fill=blink_color, width=10)
|
||||
|
||||
def _draw_stopped_frame(self):
|
||||
"""Draws the icon in its stopped (static) state."""
|
||||
self.delete("all")
|
||||
if self.use_pillow:
|
||||
self._draw_pillow_stopped_frame()
|
||||
else:
|
||||
self._draw_canvas_stopped_frame()
|
||||
|
||||
def _draw_canvas_stopped_frame(self):
|
||||
"""Draws the stopped state using canvas methods."""
|
||||
if self.animation_type == "line":
|
||||
self._draw_canvas_line_stopped()
|
||||
elif self.animation_type == "double_arc":
|
||||
self._draw_canvas_double_arc_stopped()
|
||||
elif self.animation_type == "counter_arc":
|
||||
self._draw_canvas_counter_arc_stopped()
|
||||
elif self.animation_type == "blink":
|
||||
self._draw_canvas_blink_stopped()
|
||||
|
||||
def _draw_canvas_line_stopped(self):
|
||||
"""Draws the stopped state for the line animation."""
|
||||
center_x, center_y = self.width / 2, self.height / 2
|
||||
for i in range(8):
|
||||
angle = i * (pi / 4)
|
||||
start_x = center_x + cos(angle) * (self.width * 0.2)
|
||||
start_y = center_y + sin(angle) * (self.height * 0.2)
|
||||
end_x = center_x + cos(angle) * (self.width * 0.4)
|
||||
end_y = center_y + sin(angle) * (self.height * 0.4)
|
||||
self.create_line(start_x, start_y, end_x, end_y,
|
||||
fill=self.highlight_color, width=2)
|
||||
|
||||
def _draw_canvas_double_arc_stopped(self):
|
||||
"""Draws the stopped state for the double arc animation."""
|
||||
center_x, center_y = self.width / 2, self.height / 2
|
||||
radius = min(center_x, center_y) * 0.8
|
||||
bbox = (center_x - radius, center_y - radius,
|
||||
center_x + radius, center_y + radius)
|
||||
self.create_arc(bbox, start=0, extent=359.9, style=tk.ARC,
|
||||
outline=self.highlight_color, width=2)
|
||||
|
||||
def _draw_canvas_counter_arc_stopped(self):
|
||||
"""Draws the stopped state for the counter arc animation."""
|
||||
center_x, center_y = self.width / 2, self.height / 2
|
||||
radius_outer = min(center_x, center_y) * 0.8
|
||||
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||
center_x + radius_outer, center_y + radius_outer)
|
||||
self.create_arc(bbox_outer, start=0, extent=359.9,
|
||||
style=tk.ARC, outline=self.highlight_color, width=2)
|
||||
radius_inner = min(center_x, center_y) * 0.6
|
||||
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||
center_x + radius_inner, center_y + radius_inner)
|
||||
self.create_arc(bbox_inner, start=0, extent=359.9,
|
||||
style=tk.ARC, outline=self.color, width=2)
|
||||
|
||||
def _draw_canvas_blink_stopped(self):
|
||||
"""Draws the stopped state for the blink animation."""
|
||||
center_x, center_y = self.width / 2, self.height / 2
|
||||
radius = min(center_x, center_y) * 0.8
|
||||
self.create_arc(center_x - radius, center_y - radius, center_x + radius, center_y +
|
||||
radius, start=0, extent=359.9, style=tk.ARC, outline=self.highlight_color, width=4)
|
||||
|
||||
def _draw_pillow_stopped_frame(self):
|
||||
"""Draws the stopped state using Pillow."""
|
||||
self.draw.rectangle(
|
||||
[0, 0, self.width * 4, self.height * 4], fill=(0, 0, 0, 0))
|
||||
if self.animation_type == "line":
|
||||
self._draw_pillow_line_stopped()
|
||||
elif self.animation_type == "double_arc":
|
||||
self._draw_pillow_double_arc_stopped()
|
||||
elif self.animation_type == "counter_arc":
|
||||
self._draw_pillow_counter_arc_stopped()
|
||||
elif self.animation_type == "blink":
|
||||
self._draw_pillow_blink_stopped()
|
||||
|
||||
resized_image = self.image.resize(
|
||||
(self.width, self.height), Image.Resampling.LANCZOS)
|
||||
self.photo_image = ImageTk.PhotoImage(resized_image)
|
||||
self.create_image(0, 0, anchor="nw", image=self.photo_image)
|
||||
|
||||
def _draw_pillow_line_stopped(self):
|
||||
"""Draws the stopped state for the line animation using Pillow."""
|
||||
center_x, center_y = self.width * 2, self.height * 2
|
||||
for i in range(12):
|
||||
angle = i * (pi / 6)
|
||||
start_x = center_x + cos(angle) * (self.width * 0.8)
|
||||
start_y = center_y + sin(angle) * (self.height * 0.8)
|
||||
end_x = center_x + cos(angle) * (self.width * 1.6)
|
||||
end_y = center_y + sin(angle) * (self.height * 1.6)
|
||||
self.draw.line([(start_x, start_y), (end_x, end_y)],
|
||||
fill=self.highlight_color_rgb, width=6, joint="curve")
|
||||
|
||||
def _draw_pillow_double_arc_stopped(self):
|
||||
"""Draws the stopped state for the double arc animation using Pillow."""
|
||||
center_x, center_y = self.width * 2, self.height * 2
|
||||
radius = min(center_x, center_y) * 0.8
|
||||
bbox = (center_x - radius, center_y - radius,
|
||||
center_x + radius, center_y + radius)
|
||||
self.draw.arc(bbox, start=0, end=360,
|
||||
fill=self.highlight_color_rgb, width=5)
|
||||
|
||||
def _draw_pillow_counter_arc_stopped(self):
|
||||
"""Draws the stopped state for the counter arc animation using Pillow."""
|
||||
center_x, center_y = self.width * 2, self.height * 2
|
||||
radius_outer = min(center_x, center_y) * 0.8
|
||||
bbox_outer = (center_x - radius_outer, center_y - radius_outer,
|
||||
center_x + radius_outer, center_y + radius_outer)
|
||||
self.draw.arc(bbox_outer, start=0, end=360,
|
||||
fill=self.highlight_color_rgb, width=7)
|
||||
radius_inner = min(center_x, center_y) * 0.6
|
||||
bbox_inner = (center_x - radius_inner, center_y - radius_inner,
|
||||
center_x + radius_inner, center_y + radius_inner)
|
||||
self.draw.arc(bbox_inner, start=0, end=360,
|
||||
fill=self.color_rgb, width=7)
|
||||
|
||||
def _draw_pillow_blink_stopped(self):
|
||||
"""Draws the stopped state for the blink animation using Pillow."""
|
||||
center_x, center_y = self.width * 2, self.height * 2
|
||||
radius = min(center_x, center_y) * 0.8
|
||||
self.draw.arc((center_x - radius, center_y - radius, center_x + radius,
|
||||
center_y + radius), start=0, end=360, fill=self.highlight_color_rgb, width=10)
|
||||
|
||||
def _animate(self):
|
||||
"""The main animation loop."""
|
||||
if self.running:
|
||||
self.angle += 0.1
|
||||
if self.angle > 2 * pi:
|
||||
self.angle -= 2 * pi
|
||||
self._draw_frame()
|
||||
self.after(30, self._animate)
|
||||
|
||||
def start(self, pulse=False):
|
||||
"""
|
||||
Starts the animation.
|
||||
|
||||
Args:
|
||||
pulse (bool): If True, plays a pulsing animation instead of the main one.
|
||||
"""
|
||||
if not self.running:
|
||||
self.pulse_animation = pulse
|
||||
self.running = True
|
||||
self._animate()
|
||||
|
||||
def stop(self):
|
||||
"""Stops the animation and shows the static 'stopped' frame."""
|
||||
self.running = False
|
||||
self.pulse_animation = False
|
||||
self._draw_stopped_frame()
|
||||
|
||||
def hide(self):
|
||||
"""Stops the animation and clears the canvas."""
|
||||
self.running = False
|
||||
self.pulse_animation = False
|
||||
self.delete("all")
|
||||
|
||||
def show_full_circle(self):
|
||||
"""Shows the static 'stopped' frame without starting the animation."""
|
||||
if not self.winfo_exists():
|
||||
return
|
||||
if not self.running:
|
||||
self._draw_stopped_frame()
|
||||
|
@@ -452,6 +452,12 @@ class Tooltip:
|
||||
if self.state_var:
|
||||
self.state_var.trace_add("write", self.update_bindings)
|
||||
|
||||
# Add bindings to the top-level window to hide the tooltip when the
|
||||
# main window loses focus or is iconified.
|
||||
toplevel = self.widget.winfo_toplevel()
|
||||
toplevel.bind("<FocusOut>", self.leave, add="+")
|
||||
toplevel.bind("<Unmap>", self.leave, add="+")
|
||||
|
||||
def update_bindings(self, *args):
|
||||
"""
|
||||
Updates the event bindings for the widget based on the current state_var.
|
||||
@@ -472,13 +478,12 @@ class Tooltip:
|
||||
Handles the <Enter> event. Schedules the tooltip to be shown after a delay
|
||||
if tooltips are enabled (via state_var).
|
||||
"""
|
||||
try:
|
||||
# Check if a modal dialog has the grab on the entire application
|
||||
if self.winfo_toplevel().grab_current() is not None:
|
||||
return
|
||||
except Exception:
|
||||
# This can happen if no grab is active. We can safely ignore it.
|
||||
pass
|
||||
# Do not show tooltips if a grab is active on a different window.
|
||||
# This prevents tooltips from appearing over other modal dialogs.
|
||||
toplevel = self.widget.winfo_toplevel()
|
||||
grab_widget = toplevel.grab_current()
|
||||
if grab_widget is not None and grab_widget != toplevel:
|
||||
return
|
||||
|
||||
if self.state_var is None or self.state_var.get():
|
||||
self.schedule()
|
||||
@@ -513,15 +518,26 @@ class Tooltip:
|
||||
Displays the tooltip window. The tooltip is a Toplevel window containing a ttk.Label.
|
||||
It is positioned near the widget and styled for readability.
|
||||
"""
|
||||
if self.tooltip_window or not self.text:
|
||||
if self.tooltip_window:
|
||||
return
|
||||
x, y, _, _ = self.widget.bbox("insert")
|
||||
x += self.widget.winfo_rootx() + 25
|
||||
y += self.widget.winfo_rooty() + 20
|
||||
|
||||
text_to_show = self.text() if callable(self.text) else self.text
|
||||
if not text_to_show:
|
||||
return
|
||||
|
||||
try:
|
||||
# Position the tooltip just below the widget.
|
||||
# Using winfo_rootx/y is more reliable than bbox.
|
||||
x = self.widget.winfo_rootx()
|
||||
y = self.widget.winfo_rooty() + self.widget.winfo_height() + 5
|
||||
except tk.TclError:
|
||||
# This can happen if the widget is destroyed while the tooltip is scheduled.
|
||||
return
|
||||
|
||||
self.tooltip_window = tw = tk.Toplevel(self.widget)
|
||||
tw.wm_overrideredirect(True)
|
||||
tw.wm_geometry(f"+" + str(x) + "+" + str(y))
|
||||
label = ttk.Label(tw, text=self.text, justify=tk.LEFT, background="#FFFFE0", foreground="black",
|
||||
label = ttk.Label(tw, text=text_to_show, justify=tk.LEFT, background="#FFFFE0", foreground="black",
|
||||
relief=tk.SOLID, borderwidth=1, wraplength=self.wraplength, padding=(4, 2, 4, 2))
|
||||
label.pack(ipadx=1)
|
||||
|
||||
@@ -617,6 +633,8 @@ class IconManager:
|
||||
'back': '32/arrow-left.png',
|
||||
'forward': '32/arrow-right.png',
|
||||
'up': '32/arrow-up.png',
|
||||
'copy': '32/copy.png',
|
||||
'stair': '32/stair.png',
|
||||
'audio_small': '32/audio.png',
|
||||
'icon_view': '32/carrel.png',
|
||||
'computer_small': '32/computer.png',
|
||||
@@ -675,6 +693,8 @@ class IconManager:
|
||||
'back_large': '48/arrow-left.png',
|
||||
'forward_large': '48/arrow-right.png',
|
||||
'up_large': '48/arrow-up.png',
|
||||
'copy': '48/copy.png',
|
||||
'stair': '48/stair.png',
|
||||
'icon_view_large': '48/carrel.png',
|
||||
'computer_large': '48/computer.png',
|
||||
'device_large': '48/device.png',
|
||||
@@ -722,6 +742,8 @@ class IconManager:
|
||||
'back_extralarge': '64/arrow-left.png',
|
||||
'forward_extralarge': '64/arrow-right.png',
|
||||
'up_extralarge': '64/arrow-up.png',
|
||||
'copy': '64/copy.png',
|
||||
'stair': '64/stair.png',
|
||||
'audio_large': '64/audio.png',
|
||||
'icon_view_extralarge': '64/carrel.png',
|
||||
'computer_extralarge': '64/computer.png',
|
||||
|
@@ -214,14 +214,12 @@ class FileOperationsManager:
|
||||
if not self.dialog.tree.selection():
|
||||
return
|
||||
item_id = self.dialog.tree.selection()[0]
|
||||
item_path = os.path.join(
|
||||
self.dialog.current_dir, self.dialog.tree.item(item_id, "text").strip())
|
||||
self.start_rename(item_id, item_path)
|
||||
self.start_rename(item_id)
|
||||
else: # icon view
|
||||
if item_path and item_frame:
|
||||
self.start_rename(item_frame, item_path)
|
||||
|
||||
def start_rename(self, item_widget: Any, item_path: str) -> None:
|
||||
def start_rename(self, item_widget: Any, item_path: Optional[str] = None) -> None:
|
||||
"""
|
||||
Starts the renaming UI for an item.
|
||||
|
||||
@@ -230,10 +228,12 @@ class FileOperationsManager:
|
||||
Args:
|
||||
item_widget: The widget representing the item (item_id for list view,
|
||||
item_frame for icon view).
|
||||
item_path (str): The full path to the item being renamed.
|
||||
item_path (str, optional): The full path to the item being renamed.
|
||||
Required for icon view.
|
||||
"""
|
||||
if self.dialog.view_mode.get() == "icons":
|
||||
self._start_rename_icon_view(item_widget, item_path)
|
||||
if item_path:
|
||||
self._start_rename_icon_view(item_widget, item_path)
|
||||
else: # list view
|
||||
self._start_rename_list_view(item_widget) # item_widget is item_id
|
||||
|
||||
@@ -258,25 +258,7 @@ class FileOperationsManager:
|
||||
|
||||
def finish_rename(event: tk.Event) -> None:
|
||||
new_name = entry.get()
|
||||
new_path = os.path.join(self.dialog.current_dir, new_name)
|
||||
if new_name and new_path != item_path:
|
||||
if os.path.exists(new_path):
|
||||
self.dialog.widget_manager.search_status_label.config(
|
||||
text=f"'{new_name}' {LocaleStrings.FILE['folder_exists_error']}")
|
||||
self.dialog.view_manager.populate_files(
|
||||
item_to_select=os.path.basename(item_path))
|
||||
return
|
||||
try:
|
||||
os.rename(item_path, new_path)
|
||||
self.dialog.view_manager.populate_files(
|
||||
item_to_select=new_name)
|
||||
except Exception as e:
|
||||
self.dialog.widget_manager.search_status_label.config(
|
||||
text=f"{LocaleStrings.FILE['error_renaming']}: {e}")
|
||||
self.dialog.view_manager.populate_files()
|
||||
else:
|
||||
self.dialog.view_manager.populate_files(
|
||||
item_to_select=os.path.basename(item_path))
|
||||
self._finish_rename_logic(item_path, new_name)
|
||||
|
||||
def cancel_rename(event: tk.Event) -> None:
|
||||
self.dialog.view_manager.populate_files()
|
||||
@@ -312,30 +294,12 @@ class FileOperationsManager:
|
||||
entry.select_range(0, tk.END)
|
||||
entry.focus_set()
|
||||
|
||||
old_path = os.path.join(self.dialog.current_dir, item_text)
|
||||
|
||||
def finish_rename(event: tk.Event) -> None:
|
||||
new_name = entry.get()
|
||||
old_path = os.path.join(self.dialog.current_dir, item_text)
|
||||
new_path = os.path.join(self.dialog.current_dir, new_name)
|
||||
|
||||
if new_name and new_path != old_path:
|
||||
if os.path.exists(new_path):
|
||||
self.dialog.widget_manager.search_status_label.config(
|
||||
text=f"'{new_name}' {LocaleStrings.FILE['folder_exists_error']}")
|
||||
self.dialog.view_manager.populate_files(
|
||||
item_to_select=item_text)
|
||||
else:
|
||||
try:
|
||||
os.rename(old_path, new_path)
|
||||
self.dialog.view_manager.populate_files(
|
||||
item_to_select=new_name)
|
||||
except Exception as e:
|
||||
self.dialog.widget_manager.search_status_label.config(
|
||||
text=f"{LocaleStrings.FILE['error_renaming']}: {e}")
|
||||
self.dialog.view_manager.populate_files()
|
||||
else:
|
||||
self.dialog.view_manager.populate_files(
|
||||
item_to_select=item_text)
|
||||
entry.destroy()
|
||||
self._finish_rename_logic(old_path, new_name)
|
||||
|
||||
def cancel_rename(event: tk.Event) -> None:
|
||||
entry.destroy()
|
||||
@@ -343,3 +307,33 @@ class FileOperationsManager:
|
||||
entry.bind("<Return>", finish_rename)
|
||||
entry.bind("<FocusOut>", finish_rename)
|
||||
entry.bind("<Escape>", cancel_rename)
|
||||
|
||||
def _finish_rename_logic(self, old_path: str, new_name: str) -> None:
|
||||
"""
|
||||
Handles the core logic of renaming a file or folder after the user
|
||||
submits the new name from an Entry widget.
|
||||
|
||||
Args:
|
||||
old_path (str): The original full path of the item.
|
||||
new_name (str): The new name for the item.
|
||||
"""
|
||||
new_path = os.path.join(self.dialog.current_dir, new_name)
|
||||
old_name = os.path.basename(old_path)
|
||||
|
||||
if not new_name or new_path == old_path:
|
||||
self.dialog.view_manager.populate_files(item_to_select=old_name)
|
||||
return
|
||||
|
||||
if os.path.exists(new_path):
|
||||
self.dialog.widget_manager.search_status_label.config(
|
||||
text=f"'{new_name}' {LocaleStrings.FILE['folder_exists_error']}")
|
||||
self.dialog.view_manager.populate_files(item_to_select=old_name)
|
||||
return
|
||||
|
||||
try:
|
||||
os.rename(old_path, new_path)
|
||||
self.dialog.view_manager.populate_files(item_to_select=new_name)
|
||||
except Exception as e:
|
||||
self.dialog.widget_manager.search_status_label.config(
|
||||
text=f"{LocaleStrings.FILE['error_renaming']}: {e}")
|
||||
self.dialog.view_manager.populate_files()
|
||||
|
@@ -90,20 +90,14 @@ class NavigationManager:
|
||||
if self.dialog.history_pos > 0:
|
||||
self.dialog.history_pos -= 1
|
||||
self.dialog.current_dir = self.dialog.history[self.dialog.history_pos]
|
||||
self.dialog.view_manager.populate_files()
|
||||
self.update_nav_buttons()
|
||||
self.dialog.update_status_bar()
|
||||
self.dialog.update_action_buttons_state()
|
||||
self._update_ui_after_navigation()
|
||||
|
||||
def go_forward(self) -> None:
|
||||
"""Navigates to the next directory in the history."""
|
||||
if self.dialog.history_pos < len(self.dialog.history) - 1:
|
||||
self.dialog.history_pos += 1
|
||||
self.dialog.current_dir = self.dialog.history[self.dialog.history_pos]
|
||||
self.dialog.view_manager.populate_files()
|
||||
self.update_nav_buttons()
|
||||
self.dialog.update_status_bar()
|
||||
self.dialog.update_action_buttons_state()
|
||||
self._update_ui_after_navigation()
|
||||
|
||||
def go_up_level(self) -> None:
|
||||
"""Navigates to the parent directory of the current directory."""
|
||||
@@ -111,6 +105,13 @@ class NavigationManager:
|
||||
if new_path != self.dialog.current_dir:
|
||||
self.navigate_to(new_path)
|
||||
|
||||
def _update_ui_after_navigation(self) -> None:
|
||||
"""Updates all necessary UI components after a navigation action."""
|
||||
self.dialog.view_manager.populate_files()
|
||||
self.update_nav_buttons()
|
||||
self.dialog.update_status_bar()
|
||||
self.dialog.update_action_buttons_state()
|
||||
|
||||
def update_nav_buttons(self) -> None:
|
||||
"""Updates the state of the back and forward navigation buttons."""
|
||||
self.dialog.widget_manager.back_button.config(
|
||||
|
@@ -201,8 +201,9 @@ class SearchManager:
|
||||
|
||||
def show_search_results_treeview(self) -> None:
|
||||
"""Displays the search results in a dedicated Treeview."""
|
||||
for widget in self.dialog.widget_manager.file_list_frame.winfo_children():
|
||||
widget.destroy()
|
||||
if self.dialog.widget_manager.file_list_frame.winfo_exists():
|
||||
for widget in self.dialog.widget_manager.file_list_frame.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
tree_frame = ttk.Frame(self.dialog.widget_manager.file_list_frame)
|
||||
tree_frame.pack(fill='both', expand=True)
|
||||
@@ -280,14 +281,25 @@ class SearchManager:
|
||||
|
||||
def on_search_double_click(event: tk.Event) -> None:
|
||||
selection = search_tree.selection()
|
||||
if selection:
|
||||
item = search_tree.item(selection[0])
|
||||
filename = item['text'].strip()
|
||||
directory = item['values'][0]
|
||||
if not selection:
|
||||
return
|
||||
|
||||
item = search_tree.item(selection[0])
|
||||
filename = item['text'].strip()
|
||||
directory = item['values'][0]
|
||||
full_path = os.path.join(directory, filename)
|
||||
|
||||
if not os.path.exists(full_path):
|
||||
return # Item no longer exists
|
||||
|
||||
if os.path.isdir(full_path):
|
||||
# For directories, navigate into them
|
||||
self.hide_search_bar()
|
||||
self.dialog.navigation_manager.navigate_to(
|
||||
directory, file_to_select=filename)
|
||||
self.dialog.navigation_manager.navigate_to(full_path)
|
||||
else:
|
||||
# For files, select it and close the dialog
|
||||
self.dialog.selected_file = full_path
|
||||
self.dialog.destroy()
|
||||
|
||||
search_tree.bind("<Double-1>", on_search_double_click)
|
||||
|
||||
|
@@ -126,7 +126,7 @@ class ViewManager:
|
||||
"""Handles a double click on an icon view item."""
|
||||
item_path = self._get_item_path_from_widget(event.widget)
|
||||
if item_path:
|
||||
self.on_item_double_click(item_path)
|
||||
self._handle_item_double_click(item_path)
|
||||
|
||||
def _handle_icon_context_menu(self, event: tk.Event) -> None:
|
||||
"""Handles a context menu request on an icon view item."""
|
||||
@@ -281,7 +281,7 @@ class ViewManager:
|
||||
|
||||
for widget in [item_frame, icon_label, name_label]:
|
||||
widget.bind("<Double-Button-1>", lambda e,
|
||||
p=path: self.on_item_double_click(p))
|
||||
p=path: self._handle_item_double_click(p))
|
||||
widget.bind("<Button-1>", lambda e, p=path,
|
||||
f=item_frame: self.on_item_select(p, f))
|
||||
widget.bind("<ButtonRelease-3>", lambda e,
|
||||
@@ -485,12 +485,13 @@ class ViewManager:
|
||||
self.dialog.file_op_manager._show_context_menu(event, item_path)
|
||||
return "break"
|
||||
|
||||
def on_item_double_click(self, path: str) -> None:
|
||||
def _handle_item_double_click(self, path: str) -> None:
|
||||
"""
|
||||
Handles a double-click on an icon view item.
|
||||
Handles the logic for a double-click on any item, regardless of view.
|
||||
Navigates into directories or selects files.
|
||||
|
||||
Args:
|
||||
path (str): The path of the double-clicked item.
|
||||
path (str): The full path of the double-clicked item.
|
||||
"""
|
||||
if os.path.isdir(path):
|
||||
self.dialog.navigation_manager.navigate_to(path)
|
||||
@@ -510,15 +511,7 @@ class ViewManager:
|
||||
item_id = self.dialog.tree.selection()[0]
|
||||
item_text = self.dialog.tree.item(item_id, 'text').strip()
|
||||
path = os.path.join(self.dialog.current_dir, item_text)
|
||||
if os.path.isdir(path):
|
||||
self.dialog.navigation_manager.navigate_to(path)
|
||||
elif self.dialog.dialog_mode == "open":
|
||||
self.dialog.selected_file = path
|
||||
self.dialog.destroy()
|
||||
elif self.dialog.dialog_mode == "save":
|
||||
self.dialog.widget_manager.filename_entry.delete(0, tk.END)
|
||||
self.dialog.widget_manager.filename_entry.insert(0, item_text)
|
||||
self.dialog.on_save()
|
||||
self._handle_item_double_click(path)
|
||||
|
||||
def _select_file_in_view(self, filename: str) -> None:
|
||||
"""
|
||||
|
@@ -22,6 +22,15 @@ class CustomFileDialog(tk.Toplevel):
|
||||
directory navigation, search, and file operations.
|
||||
"""
|
||||
|
||||
def _initialize_managers(self) -> None:
|
||||
"""Initializes or re-initializes all the manager classes."""
|
||||
self.style_manager: StyleManager = StyleManager(self)
|
||||
self.file_op_manager: FileOperationsManager = FileOperationsManager(self)
|
||||
self.search_manager: SearchManager = SearchManager(self)
|
||||
self.navigation_manager: NavigationManager = NavigationManager(self)
|
||||
self.view_manager: ViewManager = ViewManager(self)
|
||||
self.widget_manager: WidgetManager = WidgetManager(self, self.settings)
|
||||
|
||||
def __init__(self, parent: tk.Widget, initial_dir: Optional[str] = None,
|
||||
filetypes: Optional[List[Tuple[str, str]]] = None,
|
||||
dialog_mode: str = "open", title: str = LocaleStrings.CFD["title"]):
|
||||
@@ -80,15 +89,7 @@ class CustomFileDialog(tk.Toplevel):
|
||||
self.search_process: Optional[subprocess.Popen] = None
|
||||
|
||||
self.icon_manager: IconManager = IconManager()
|
||||
self.style_manager: StyleManager = StyleManager(self)
|
||||
|
||||
self.file_op_manager: FileOperationsManager = FileOperationsManager(
|
||||
self)
|
||||
self.search_manager: SearchManager = SearchManager(self)
|
||||
self.navigation_manager: NavigationManager = NavigationManager(self)
|
||||
self.view_manager: ViewManager = ViewManager(self)
|
||||
|
||||
self.widget_manager: WidgetManager = WidgetManager(self, self.settings)
|
||||
self._initialize_managers()
|
||||
|
||||
self.widget_manager.filename_entry.bind(
|
||||
"<Return>", self.search_manager.execute_search)
|
||||
@@ -150,12 +151,8 @@ class CustomFileDialog(tk.Toplevel):
|
||||
for widget in self.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
self.style_manager = StyleManager(self)
|
||||
self.file_op_manager = FileOperationsManager(self)
|
||||
self.search_manager = SearchManager(self)
|
||||
self.navigation_manager = NavigationManager(self)
|
||||
self.view_manager = ViewManager(self)
|
||||
self.widget_manager = WidgetManager(self, self.settings)
|
||||
self._initialize_managers()
|
||||
|
||||
self.widget_manager.filename_entry.bind(
|
||||
"<Return>", self.search_manager.execute_search)
|
||||
self.view_manager._update_view_mode_buttons()
|
||||
@@ -198,10 +195,11 @@ class CustomFileDialog(tk.Toplevel):
|
||||
row=0, column=0, sticky='w', padx=(0, 5), pady=(4, 0))
|
||||
self.widget_manager.search_animation.bind(
|
||||
"<Button-1>", lambda e: self.search_manager.activate_search())
|
||||
self.widget_manager.search_animation.bind(
|
||||
"<Enter>", self._show_tooltip)
|
||||
self.widget_manager.search_animation.bind(
|
||||
"<Leave>", self._hide_tooltip)
|
||||
|
||||
self.my_tool_tip = Tooltip(
|
||||
self.widget_manager.search_animation,
|
||||
text=lambda: LocaleStrings.UI["cancel_search"] if self.widget_manager.search_animation.running else LocaleStrings.UI["start_search"]
|
||||
)
|
||||
|
||||
if is_running:
|
||||
self.widget_manager.search_animation.start()
|
||||
@@ -564,33 +562,4 @@ class CustomFileDialog(tk.Toplevel):
|
||||
print(f"Error getting mounted devices: {e}")
|
||||
return devices
|
||||
|
||||
def _show_tooltip(self, event: tk.Event) -> None:
|
||||
"""
|
||||
Displays a tooltip for the search animation icon.
|
||||
|
||||
Args:
|
||||
event: The event object.
|
||||
"""
|
||||
if hasattr(self, 'tooltip_window') and self.tooltip_window.winfo_exists():
|
||||
return
|
||||
|
||||
tooltip_text = LocaleStrings.UI["start_search"] if not self.widget_manager.search_animation.running else LocaleStrings.UI["cancel_search"]
|
||||
|
||||
x = self.widget_manager.search_animation.winfo_rootx() + 25
|
||||
y = self.widget_manager.search_animation.winfo_rooty() + 25
|
||||
self.tooltip_window = tk.Toplevel(self)
|
||||
self.tooltip_window.wm_overrideredirect(True)
|
||||
self.tooltip_window.wm_geometry(f"+{x}+{y}")
|
||||
label = tk.Label(self.tooltip_window, text=tooltip_text,
|
||||
relief="solid", borderwidth=1)
|
||||
label.pack()
|
||||
|
||||
def _hide_tooltip(self, event: tk.Event) -> None:
|
||||
"""
|
||||
Hides the tooltip.
|
||||
|
||||
Args:
|
||||
event: The event object.
|
||||
"""
|
||||
if hasattr(self, 'tooltip_window') and self.tooltip_window.winfo_exists():
|
||||
self.tooltip_window.destroy()
|
||||
|
Reference in New Issue
Block a user