reorganized file and folder structure to reflect responsibilities

This commit is contained in:
2025-09-23 06:01:53 +08:00
committed by Jason Ross
parent 6d08df1807
commit f8a942bf51
20 changed files with 58 additions and 158 deletions
+319
View File
@@ -0,0 +1,319 @@
"""
UI Components Module
Contains reusable UI components for the Android file transfer application.
"""
import tkinter as tk
from tkinter import messagebox
from typing import Callable, Optional
try:
from ..dialogs.license_agreement import LicenseAgreementFrame, check_license_agreement
except ImportError:
from gui.dialogs.license_agreement import LicenseAgreementFrame, check_license_agreement
class PathSelectorFrame:
"""Frame component for path selection with browse button."""
def __init__(self, parent: tk.Widget, label_text: str, browse_command: Callable):
"""Initialize the path selector frame.
Args:
parent: Parent widget
label_text: Text for the label
browse_command: Command to execute when browse button is clicked
"""
self.frame = tk.Frame(parent)
self.label = tk.Label(self.frame, text=label_text)
self.label.pack(anchor="w")
# Path frame with label and button
path_frame = tk.Frame(self.frame)
path_frame.pack(fill="x", pady=(0, 10))
self.path_var = tk.StringVar(value="Please select file or folder ->")
self.path_display = tk.Label(
path_frame,
textvariable=self.path_var,
anchor="w"
)
self.path_display.pack(side="left", fill="x", expand=True)
self.browse_btn = tk.Button(
path_frame,
text="Browse...",
command=browse_command
)
self.browse_btn.pack(side="right", padx=(5, 0))
def pack(self, **kwargs):
"""Pack the frame."""
self.frame.pack(**kwargs)
def pack_forget(self):
"""Remove the frame from packing."""
self.frame.pack_forget()
def set_path(self, path: str) -> None:
"""Set the displayed path.
Args:
path: Path to display
"""
self.path_var.set(path)
def get_path(self) -> str:
"""Get the current path.
Returns:
Current path string
"""
return self.path_var.get().strip()
def is_path_selected(self) -> bool:
"""Check if a valid path is selected.
Returns:
True if path is selected and not the default placeholder
"""
path = self.get_path()
return path and path != "Please select file or folder ->"
def clear_path(self) -> None:
"""Clear the path selection."""
self.path_var.set("Please select file or folder ->")
def enable_browse(self) -> None:
"""Enable the browse button."""
self.browse_btn.config(state="normal")
def disable_browse(self) -> None:
"""Disable the browse button."""
self.browse_btn.config(state="disabled")
class DirectionSelector:
"""Component for selecting transfer direction (pull/push)."""
def __init__(self, parent: tk.Widget, on_change_command: Optional[Callable] = None):
"""Initialize the direction selector.
Args:
parent: Parent widget
on_change_command: Command to execute when direction changes
"""
self.direction_var = tk.StringVar(value="pull")
self.frame = tk.Frame(parent)
self.frame.pack(anchor="w", padx=10, pady=(10, 0))
tk.Radiobutton(
self.frame,
text="Pull (Android → Computer)",
variable=self.direction_var,
value="pull",
command=on_change_command,
).pack(side="left")
tk.Radiobutton(
self.frame,
text="Push (Computer → Android)",
variable=self.direction_var,
value="push",
command=on_change_command,
).pack(side="left", padx=(20, 0))
def get_direction(self) -> str:
"""Get the current direction.
Returns:
Current direction ('pull' or 'push')
"""
return self.direction_var.get()
class StatusLabel:
"""Responsive status label with word wrapping."""
def __init__(self, parent: tk.Widget, initial_text: str = "Status: Idle"):
"""Initialize the status label.
Args:
parent: Parent widget
initial_text: Initial status text
"""
self.label = tk.Label(
parent,
text=initial_text,
wraplength=0, # Will be set dynamically
justify="center",
anchor="center"
)
self.label.pack(padx=10, fill="x", pady=(20, 5))
# Bind parent window resize to update wrapping
parent.bind("<Configure>", self._on_window_configure)
def set_text(self, text: str) -> None:
"""Set the status text.
Args:
text: Text to display
"""
self.label.config(text=text)
def get_text(self) -> str:
"""Get the current status text.
Returns:
Current status text
"""
return self.label.cget("text")
def _on_window_configure(self, event) -> None:
"""Handle window resize events to update label wrapping.
Args:
event: Configure event
"""
# Only handle configure events for the main window, not child widgets
if hasattr(event.widget, 'winfo_toplevel') and event.widget == event.widget.winfo_toplevel():
# Calculate available width for the status label
# Account for padding (10px on each side) and some margin
available_width = event.widget.winfo_width() - 40
if available_width > 100: # Minimum reasonable width
self.label.config(wraplength=available_width)
class TransferButton:
"""Multi-mode transfer button that changes text and behavior based on state."""
def __init__(self, parent: tk.Widget):
"""Initialize the transfer button.
Args:
parent: Parent widget
"""
self.button = tk.Button(
parent,
text="Start Transfer",
state="disabled"
)
self.button.pack(pady=10)
self.current_mode = "transfer" # transfer, recheck, cancel
def set_transfer_mode(self, command: Callable, enabled: bool = True) -> None:
"""Set button to transfer mode.
Args:
command: Command to execute on button click
enabled: Whether button should be enabled
"""
self.current_mode = "transfer"
self.button.config(
text="Start Transfer",
command=command,
state="normal" if enabled else "disabled"
)
def set_recheck_mode(self, command: Callable) -> None:
"""Set button to recheck device mode.
Args:
command: Command to execute on button click
"""
self.current_mode = "recheck"
self.button.config(
text="Recheck for connected Android device",
command=command,
state="normal"
)
def set_cancel_mode(self, command: Callable) -> None:
"""Set button to cancel transfer mode.
Args:
command: Command to execute on button click
"""
self.current_mode = "cancel"
self.button.config(
text="Cancel Transfer",
command=command,
state="normal"
)
def set_checking_mode(self) -> None:
"""Set button to temporary checking state."""
self.button.config(
text="Checking...",
state="disabled"
)
def enable(self) -> None:
"""Enable the button."""
self.button.config(state="normal")
def disable(self) -> None:
"""Disable the button."""
self.button.config(state="disabled")
def get_mode(self) -> str:
"""Get the current button mode.
Returns:
Current mode string ('transfer', 'recheck', 'cancel')
"""
return self.current_mode
class LicenseManager:
"""Manages license agreement display and main interface switching."""
def __init__(self, parent_window: tk.Tk):
"""Initialize the license manager.
Args:
parent_window: The main window instance
"""
self.parent = parent_window
self.license_agreed = check_license_agreement()
self.license_frame = None
self.on_agreed_callback: Optional[Callable] = None
def needs_license_agreement(self) -> bool:
"""Check if license agreement is needed.
Returns:
True if license agreement needs to be shown, False otherwise
"""
return not self.license_agreed
def show_license_agreement(self, on_agreed_callback: Callable) -> None:
"""Show the license agreement interface.
Args:
on_agreed_callback: Callback to execute when license is agreed
"""
self.on_agreed_callback = on_agreed_callback
# Adjust window size for license agreement
self.parent.geometry("700x600")
self.parent.minsize(700, 600)
# Create license agreement frame
self.license_frame = LicenseAgreementFrame(self.parent, self._on_license_agreed)
def _on_license_agreed(self) -> None:
"""Handle when user agrees to license."""
self.license_agreed = True
# Remove license frame
if self.license_frame:
self.license_frame.destroy()
self.license_frame = None
# Execute callback
if self.on_agreed_callback:
self.on_agreed_callback()