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:
|
def _animate(self) -> None:
|
||||||
"""The main animation loop."""
|
"""The main animation loop."""
|
||||||
if self.running:
|
if not self.winfo_exists() or not self.running:
|
||||||
try:
|
return
|
||||||
# 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
|
|
||||||
|
|
||||||
self.angle += 0.1
|
# Do not animate if a grab is active on a different window.
|
||||||
if self.angle > 2 * pi:
|
toplevel = self.winfo_toplevel()
|
||||||
self.angle -= 2 * pi
|
grab_widget = toplevel.grab_current()
|
||||||
self._draw_frame()
|
if grab_widget is not None and grab_widget != toplevel:
|
||||||
self.after(30, self._animate)
|
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:
|
def start(self, pulse: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -476,6 +476,8 @@ class AnimatedIcon(tk.Canvas):
|
|||||||
Args:
|
Args:
|
||||||
pulse (bool): If True, plays a pulsing animation instead of the main one.
|
pulse (bool): If True, plays a pulsing animation instead of the main one.
|
||||||
"""
|
"""
|
||||||
|
if not self.winfo_exists():
|
||||||
|
return
|
||||||
if not self.running:
|
if not self.running:
|
||||||
self.pulse_animation = pulse
|
self.pulse_animation = pulse
|
||||||
self.running = True
|
self.running = True
|
||||||
@@ -483,497 +485,23 @@ class AnimatedIcon(tk.Canvas):
|
|||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
"""Stops the animation and shows the static 'stopped' frame."""
|
"""Stops the animation and shows the static 'stopped' frame."""
|
||||||
|
if not self.winfo_exists():
|
||||||
|
return
|
||||||
self.running = False
|
self.running = False
|
||||||
self.pulse_animation = False
|
self.pulse_animation = False
|
||||||
self._draw_stopped_frame()
|
self._draw_stopped_frame()
|
||||||
|
|
||||||
def hide(self) -> None:
|
def hide(self) -> None:
|
||||||
"""Stops the animation and clears the canvas."""
|
"""Stops the animation and clears the canvas."""
|
||||||
|
if not self.winfo_exists():
|
||||||
|
return
|
||||||
self.running = False
|
self.running = False
|
||||||
self.pulse_animation = False
|
self.pulse_animation = False
|
||||||
self.delete("all")
|
self.delete("all")
|
||||||
|
|
||||||
def show_full_circle(self) -> None:
|
def show_full_circle(self) -> None:
|
||||||
"""Shows the static 'stopped' frame without starting the animation."""
|
"""Shows the static 'stopped' frame without starting the animation."""
|
||||||
if not self.running:
|
if not self.winfo_exists():
|
||||||
self._draw_stopped_frame()
|
return
|
||||||
|
|
||||||
|
|
||||||
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.running:
|
if not self.running:
|
||||||
self._draw_stopped_frame()
|
self._draw_stopped_frame()
|
||||||
|
@@ -452,6 +452,12 @@ class Tooltip:
|
|||||||
if self.state_var:
|
if self.state_var:
|
||||||
self.state_var.trace_add("write", self.update_bindings)
|
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):
|
def update_bindings(self, *args):
|
||||||
"""
|
"""
|
||||||
Updates the event bindings for the widget based on the current state_var.
|
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
|
Handles the <Enter> event. Schedules the tooltip to be shown after a delay
|
||||||
if tooltips are enabled (via state_var).
|
if tooltips are enabled (via state_var).
|
||||||
"""
|
"""
|
||||||
try:
|
# Do not show tooltips if a grab is active on a different window.
|
||||||
# Check if a modal dialog has the grab on the entire application
|
# This prevents tooltips from appearing over other modal dialogs.
|
||||||
if self.winfo_toplevel().grab_current() is not None:
|
toplevel = self.widget.winfo_toplevel()
|
||||||
return
|
grab_widget = toplevel.grab_current()
|
||||||
except Exception:
|
if grab_widget is not None and grab_widget != toplevel:
|
||||||
# This can happen if no grab is active. We can safely ignore it.
|
return
|
||||||
pass
|
|
||||||
|
|
||||||
if self.state_var is None or self.state_var.get():
|
if self.state_var is None or self.state_var.get():
|
||||||
self.schedule()
|
self.schedule()
|
||||||
@@ -513,15 +518,26 @@ class Tooltip:
|
|||||||
Displays the tooltip window. The tooltip is a Toplevel window containing a ttk.Label.
|
Displays the tooltip window. The tooltip is a Toplevel window containing a ttk.Label.
|
||||||
It is positioned near the widget and styled for readability.
|
It is positioned near the widget and styled for readability.
|
||||||
"""
|
"""
|
||||||
if self.tooltip_window or not self.text:
|
if self.tooltip_window:
|
||||||
return
|
return
|
||||||
x, y, _, _ = self.widget.bbox("insert")
|
|
||||||
x += self.widget.winfo_rootx() + 25
|
text_to_show = self.text() if callable(self.text) else self.text
|
||||||
y += self.widget.winfo_rooty() + 20
|
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)
|
self.tooltip_window = tw = tk.Toplevel(self.widget)
|
||||||
tw.wm_overrideredirect(True)
|
tw.wm_overrideredirect(True)
|
||||||
tw.wm_geometry(f"+" + str(x) + "+" + str(y))
|
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))
|
relief=tk.SOLID, borderwidth=1, wraplength=self.wraplength, padding=(4, 2, 4, 2))
|
||||||
label.pack(ipadx=1)
|
label.pack(ipadx=1)
|
||||||
|
|
||||||
@@ -617,6 +633,8 @@ class IconManager:
|
|||||||
'back': '32/arrow-left.png',
|
'back': '32/arrow-left.png',
|
||||||
'forward': '32/arrow-right.png',
|
'forward': '32/arrow-right.png',
|
||||||
'up': '32/arrow-up.png',
|
'up': '32/arrow-up.png',
|
||||||
|
'copy': '32/copy.png',
|
||||||
|
'stair': '32/stair.png',
|
||||||
'audio_small': '32/audio.png',
|
'audio_small': '32/audio.png',
|
||||||
'icon_view': '32/carrel.png',
|
'icon_view': '32/carrel.png',
|
||||||
'computer_small': '32/computer.png',
|
'computer_small': '32/computer.png',
|
||||||
@@ -675,6 +693,8 @@ class IconManager:
|
|||||||
'back_large': '48/arrow-left.png',
|
'back_large': '48/arrow-left.png',
|
||||||
'forward_large': '48/arrow-right.png',
|
'forward_large': '48/arrow-right.png',
|
||||||
'up_large': '48/arrow-up.png',
|
'up_large': '48/arrow-up.png',
|
||||||
|
'copy': '48/copy.png',
|
||||||
|
'stair': '48/stair.png',
|
||||||
'icon_view_large': '48/carrel.png',
|
'icon_view_large': '48/carrel.png',
|
||||||
'computer_large': '48/computer.png',
|
'computer_large': '48/computer.png',
|
||||||
'device_large': '48/device.png',
|
'device_large': '48/device.png',
|
||||||
@@ -722,6 +742,8 @@ class IconManager:
|
|||||||
'back_extralarge': '64/arrow-left.png',
|
'back_extralarge': '64/arrow-left.png',
|
||||||
'forward_extralarge': '64/arrow-right.png',
|
'forward_extralarge': '64/arrow-right.png',
|
||||||
'up_extralarge': '64/arrow-up.png',
|
'up_extralarge': '64/arrow-up.png',
|
||||||
|
'copy': '64/copy.png',
|
||||||
|
'stair': '64/stair.png',
|
||||||
'audio_large': '64/audio.png',
|
'audio_large': '64/audio.png',
|
||||||
'icon_view_extralarge': '64/carrel.png',
|
'icon_view_extralarge': '64/carrel.png',
|
||||||
'computer_extralarge': '64/computer.png',
|
'computer_extralarge': '64/computer.png',
|
||||||
|
@@ -214,14 +214,12 @@ class FileOperationsManager:
|
|||||||
if not self.dialog.tree.selection():
|
if not self.dialog.tree.selection():
|
||||||
return
|
return
|
||||||
item_id = self.dialog.tree.selection()[0]
|
item_id = self.dialog.tree.selection()[0]
|
||||||
item_path = os.path.join(
|
self.start_rename(item_id)
|
||||||
self.dialog.current_dir, self.dialog.tree.item(item_id, "text").strip())
|
|
||||||
self.start_rename(item_id, item_path)
|
|
||||||
else: # icon view
|
else: # icon view
|
||||||
if item_path and item_frame:
|
if item_path and item_frame:
|
||||||
self.start_rename(item_frame, item_path)
|
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.
|
Starts the renaming UI for an item.
|
||||||
|
|
||||||
@@ -230,10 +228,12 @@ class FileOperationsManager:
|
|||||||
Args:
|
Args:
|
||||||
item_widget: The widget representing the item (item_id for list view,
|
item_widget: The widget representing the item (item_id for list view,
|
||||||
item_frame for icon 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":
|
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
|
else: # list view
|
||||||
self._start_rename_list_view(item_widget) # item_widget is item_id
|
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:
|
def finish_rename(event: tk.Event) -> None:
|
||||||
new_name = entry.get()
|
new_name = entry.get()
|
||||||
new_path = os.path.join(self.dialog.current_dir, new_name)
|
self._finish_rename_logic(item_path, 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))
|
|
||||||
|
|
||||||
def cancel_rename(event: tk.Event) -> None:
|
def cancel_rename(event: tk.Event) -> None:
|
||||||
self.dialog.view_manager.populate_files()
|
self.dialog.view_manager.populate_files()
|
||||||
@@ -312,30 +294,12 @@ class FileOperationsManager:
|
|||||||
entry.select_range(0, tk.END)
|
entry.select_range(0, tk.END)
|
||||||
entry.focus_set()
|
entry.focus_set()
|
||||||
|
|
||||||
|
old_path = os.path.join(self.dialog.current_dir, item_text)
|
||||||
|
|
||||||
def finish_rename(event: tk.Event) -> None:
|
def finish_rename(event: tk.Event) -> None:
|
||||||
new_name = entry.get()
|
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()
|
entry.destroy()
|
||||||
|
self._finish_rename_logic(old_path, new_name)
|
||||||
|
|
||||||
def cancel_rename(event: tk.Event) -> None:
|
def cancel_rename(event: tk.Event) -> None:
|
||||||
entry.destroy()
|
entry.destroy()
|
||||||
@@ -343,3 +307,33 @@ class FileOperationsManager:
|
|||||||
entry.bind("<Return>", finish_rename)
|
entry.bind("<Return>", finish_rename)
|
||||||
entry.bind("<FocusOut>", finish_rename)
|
entry.bind("<FocusOut>", finish_rename)
|
||||||
entry.bind("<Escape>", cancel_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:
|
if self.dialog.history_pos > 0:
|
||||||
self.dialog.history_pos -= 1
|
self.dialog.history_pos -= 1
|
||||||
self.dialog.current_dir = self.dialog.history[self.dialog.history_pos]
|
self.dialog.current_dir = self.dialog.history[self.dialog.history_pos]
|
||||||
self.dialog.view_manager.populate_files()
|
self._update_ui_after_navigation()
|
||||||
self.update_nav_buttons()
|
|
||||||
self.dialog.update_status_bar()
|
|
||||||
self.dialog.update_action_buttons_state()
|
|
||||||
|
|
||||||
def go_forward(self) -> None:
|
def go_forward(self) -> None:
|
||||||
"""Navigates to the next directory in the history."""
|
"""Navigates to the next directory in the history."""
|
||||||
if self.dialog.history_pos < len(self.dialog.history) - 1:
|
if self.dialog.history_pos < len(self.dialog.history) - 1:
|
||||||
self.dialog.history_pos += 1
|
self.dialog.history_pos += 1
|
||||||
self.dialog.current_dir = self.dialog.history[self.dialog.history_pos]
|
self.dialog.current_dir = self.dialog.history[self.dialog.history_pos]
|
||||||
self.dialog.view_manager.populate_files()
|
self._update_ui_after_navigation()
|
||||||
self.update_nav_buttons()
|
|
||||||
self.dialog.update_status_bar()
|
|
||||||
self.dialog.update_action_buttons_state()
|
|
||||||
|
|
||||||
def go_up_level(self) -> None:
|
def go_up_level(self) -> None:
|
||||||
"""Navigates to the parent directory of the current directory."""
|
"""Navigates to the parent directory of the current directory."""
|
||||||
@@ -111,6 +105,13 @@ class NavigationManager:
|
|||||||
if new_path != self.dialog.current_dir:
|
if new_path != self.dialog.current_dir:
|
||||||
self.navigate_to(new_path)
|
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:
|
def update_nav_buttons(self) -> None:
|
||||||
"""Updates the state of the back and forward navigation buttons."""
|
"""Updates the state of the back and forward navigation buttons."""
|
||||||
self.dialog.widget_manager.back_button.config(
|
self.dialog.widget_manager.back_button.config(
|
||||||
|
@@ -201,8 +201,9 @@ class SearchManager:
|
|||||||
|
|
||||||
def show_search_results_treeview(self) -> None:
|
def show_search_results_treeview(self) -> None:
|
||||||
"""Displays the search results in a dedicated Treeview."""
|
"""Displays the search results in a dedicated Treeview."""
|
||||||
for widget in self.dialog.widget_manager.file_list_frame.winfo_children():
|
if self.dialog.widget_manager.file_list_frame.winfo_exists():
|
||||||
widget.destroy()
|
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 = ttk.Frame(self.dialog.widget_manager.file_list_frame)
|
||||||
tree_frame.pack(fill='both', expand=True)
|
tree_frame.pack(fill='both', expand=True)
|
||||||
@@ -280,14 +281,25 @@ class SearchManager:
|
|||||||
|
|
||||||
def on_search_double_click(event: tk.Event) -> None:
|
def on_search_double_click(event: tk.Event) -> None:
|
||||||
selection = search_tree.selection()
|
selection = search_tree.selection()
|
||||||
if selection:
|
if not selection:
|
||||||
item = search_tree.item(selection[0])
|
return
|
||||||
filename = item['text'].strip()
|
|
||||||
directory = item['values'][0]
|
|
||||||
|
|
||||||
|
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.hide_search_bar()
|
||||||
self.dialog.navigation_manager.navigate_to(
|
self.dialog.navigation_manager.navigate_to(full_path)
|
||||||
directory, file_to_select=filename)
|
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)
|
search_tree.bind("<Double-1>", on_search_double_click)
|
||||||
|
|
||||||
|
@@ -126,7 +126,7 @@ class ViewManager:
|
|||||||
"""Handles a double click on an icon view item."""
|
"""Handles a double click on an icon view item."""
|
||||||
item_path = self._get_item_path_from_widget(event.widget)
|
item_path = self._get_item_path_from_widget(event.widget)
|
||||||
if item_path:
|
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:
|
def _handle_icon_context_menu(self, event: tk.Event) -> None:
|
||||||
"""Handles a context menu request on an icon view item."""
|
"""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]:
|
for widget in [item_frame, icon_label, name_label]:
|
||||||
widget.bind("<Double-Button-1>", lambda e,
|
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,
|
widget.bind("<Button-1>", lambda e, p=path,
|
||||||
f=item_frame: self.on_item_select(p, f))
|
f=item_frame: self.on_item_select(p, f))
|
||||||
widget.bind("<ButtonRelease-3>", lambda e,
|
widget.bind("<ButtonRelease-3>", lambda e,
|
||||||
@@ -485,12 +485,13 @@ class ViewManager:
|
|||||||
self.dialog.file_op_manager._show_context_menu(event, item_path)
|
self.dialog.file_op_manager._show_context_menu(event, item_path)
|
||||||
return "break"
|
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:
|
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):
|
if os.path.isdir(path):
|
||||||
self.dialog.navigation_manager.navigate_to(path)
|
self.dialog.navigation_manager.navigate_to(path)
|
||||||
@@ -510,15 +511,7 @@ class ViewManager:
|
|||||||
item_id = self.dialog.tree.selection()[0]
|
item_id = self.dialog.tree.selection()[0]
|
||||||
item_text = self.dialog.tree.item(item_id, 'text').strip()
|
item_text = self.dialog.tree.item(item_id, 'text').strip()
|
||||||
path = os.path.join(self.dialog.current_dir, item_text)
|
path = os.path.join(self.dialog.current_dir, item_text)
|
||||||
if os.path.isdir(path):
|
self._handle_item_double_click(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()
|
|
||||||
|
|
||||||
def _select_file_in_view(self, filename: str) -> None:
|
def _select_file_in_view(self, filename: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
@@ -22,6 +22,15 @@ class CustomFileDialog(tk.Toplevel):
|
|||||||
directory navigation, search, and file operations.
|
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,
|
def __init__(self, parent: tk.Widget, initial_dir: Optional[str] = None,
|
||||||
filetypes: Optional[List[Tuple[str, str]]] = None,
|
filetypes: Optional[List[Tuple[str, str]]] = None,
|
||||||
dialog_mode: str = "open", title: str = LocaleStrings.CFD["title"]):
|
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.search_process: Optional[subprocess.Popen] = None
|
||||||
|
|
||||||
self.icon_manager: IconManager = IconManager()
|
self.icon_manager: IconManager = IconManager()
|
||||||
self.style_manager: StyleManager = StyleManager(self)
|
self._initialize_managers()
|
||||||
|
|
||||||
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.widget_manager.filename_entry.bind(
|
self.widget_manager.filename_entry.bind(
|
||||||
"<Return>", self.search_manager.execute_search)
|
"<Return>", self.search_manager.execute_search)
|
||||||
@@ -150,12 +151,8 @@ class CustomFileDialog(tk.Toplevel):
|
|||||||
for widget in self.winfo_children():
|
for widget in self.winfo_children():
|
||||||
widget.destroy()
|
widget.destroy()
|
||||||
|
|
||||||
self.style_manager = StyleManager(self)
|
self._initialize_managers()
|
||||||
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.widget_manager.filename_entry.bind(
|
self.widget_manager.filename_entry.bind(
|
||||||
"<Return>", self.search_manager.execute_search)
|
"<Return>", self.search_manager.execute_search)
|
||||||
self.view_manager._update_view_mode_buttons()
|
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))
|
row=0, column=0, sticky='w', padx=(0, 5), pady=(4, 0))
|
||||||
self.widget_manager.search_animation.bind(
|
self.widget_manager.search_animation.bind(
|
||||||
"<Button-1>", lambda e: self.search_manager.activate_search())
|
"<Button-1>", lambda e: self.search_manager.activate_search())
|
||||||
self.widget_manager.search_animation.bind(
|
|
||||||
"<Enter>", self._show_tooltip)
|
self.my_tool_tip = Tooltip(
|
||||||
self.widget_manager.search_animation.bind(
|
self.widget_manager.search_animation,
|
||||||
"<Leave>", self._hide_tooltip)
|
text=lambda: LocaleStrings.UI["cancel_search"] if self.widget_manager.search_animation.running else LocaleStrings.UI["start_search"]
|
||||||
|
)
|
||||||
|
|
||||||
if is_running:
|
if is_running:
|
||||||
self.widget_manager.search_animation.start()
|
self.widget_manager.search_animation.start()
|
||||||
@@ -564,33 +562,4 @@ class CustomFileDialog(tk.Toplevel):
|
|||||||
print(f"Error getting mounted devices: {e}")
|
print(f"Error getting mounted devices: {e}")
|
||||||
return devices
|
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