310 lines
9.2 KiB
Python
310 lines
9.2 KiB
Python
"""
|
|
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()
|