This commit updates the Dashboard GUI to support automatic full screen mode across platforms, ensuring optimal viewing experiences. It introduces a responsive layout that dynamically adjusts components during window resizing, improving usability. Additionally, typography has been enhanced with larger fonts for better readability. The README has been updated to reflect these new features, emphasizing the unified dashboard's capabilities and user experience improvements.
549 lines
24 KiB
Python
549 lines
24 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Unified Dashboard GUI for PunimTag features
|
|
Designed with web migration in mind - single window with menu bar and content area
|
|
"""
|
|
|
|
import os
|
|
import threading
|
|
import tkinter as tk
|
|
from tkinter import ttk, messagebox
|
|
from typing import Dict, Optional, Callable
|
|
|
|
from gui_core import GUICore
|
|
from identify_panel import IdentifyPanel
|
|
|
|
|
|
class DashboardGUI:
|
|
"""Unified Dashboard with menu bar and content area for all features.
|
|
|
|
Designed to be web-migration friendly with clear separation between
|
|
navigation (menu bar) and content (panels).
|
|
"""
|
|
|
|
def __init__(self, gui_core: GUICore, db_manager=None, face_processor=None, on_scan=None, on_process=None, on_identify=None):
|
|
self.gui_core = gui_core
|
|
self.db_manager = db_manager
|
|
self.face_processor = face_processor
|
|
self.on_scan = on_scan
|
|
self.on_process = on_process
|
|
self.on_identify = on_identify
|
|
|
|
# Panel management for future web migration
|
|
self.panels: Dict[str, ttk.Frame] = {}
|
|
self.current_panel: Optional[str] = None
|
|
self.root: Optional[tk.Tk] = None
|
|
|
|
def open(self) -> int:
|
|
"""Open the unified dashboard with menu bar and content area"""
|
|
self.root = tk.Tk()
|
|
self.root.title("PunimTag - Unified Dashboard")
|
|
self.root.resizable(True, True)
|
|
self.root.withdraw()
|
|
|
|
# Make window full screen - use geometry instead of state for better compatibility
|
|
try:
|
|
# Try Windows-style maximized state first
|
|
self.root.state('zoomed')
|
|
except tk.TclError:
|
|
try:
|
|
# Try Linux-style maximized attribute
|
|
self.root.attributes('-zoomed', True)
|
|
except tk.TclError:
|
|
# Fallback: set geometry to screen size
|
|
screen_width = self.root.winfo_screenwidth()
|
|
screen_height = self.root.winfo_screenheight()
|
|
self.root.geometry(f"{screen_width}x{screen_height}+0+0")
|
|
|
|
# Get screen dimensions for dynamic sizing
|
|
screen_width = self.root.winfo_screenwidth()
|
|
screen_height = self.root.winfo_screenheight()
|
|
|
|
# Set minimum window size
|
|
self.root.minsize(800, 600)
|
|
|
|
# Create main container with proper grid configuration
|
|
main_container = ttk.Frame(self.root)
|
|
main_container.pack(fill=tk.BOTH, expand=True)
|
|
|
|
# Configure main container grid weights for responsiveness
|
|
main_container.columnconfigure(0, weight=1)
|
|
main_container.rowconfigure(0, weight=0) # Menu bar - fixed height
|
|
main_container.rowconfigure(1, weight=1) # Content area - expandable
|
|
|
|
# Add window resize handler for dynamic responsiveness
|
|
self.root.bind('<Configure>', self._on_window_resize)
|
|
|
|
# Create menu bar
|
|
self._create_menu_bar(main_container)
|
|
|
|
# Create content area
|
|
self._create_content_area(main_container)
|
|
|
|
# Initialize panels
|
|
self._initialize_panels()
|
|
|
|
# Show default panel
|
|
self.show_panel("home")
|
|
|
|
# Show window
|
|
self.root.deiconify()
|
|
self.root.mainloop()
|
|
return 0
|
|
|
|
def _create_menu_bar(self, parent: ttk.Frame):
|
|
"""Create the top menu bar with all functionality buttons"""
|
|
menu_frame = ttk.Frame(parent)
|
|
menu_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=15, pady=10)
|
|
menu_frame.columnconfigure(0, weight=0) # Title - fixed width
|
|
menu_frame.columnconfigure(1, weight=1) # Buttons - expandable
|
|
menu_frame.columnconfigure(2, weight=0) # Status - fixed width
|
|
|
|
# Title with larger font for full screen
|
|
title_label = tk.Label(menu_frame, text="PunimTag", font=("Arial", 20, "bold"))
|
|
title_label.grid(row=0, column=0, padx=(0, 30), sticky=tk.W)
|
|
|
|
# Create buttons frame for better organization
|
|
buttons_frame = ttk.Frame(menu_frame)
|
|
buttons_frame.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=20)
|
|
|
|
# Menu buttons with larger size for full screen
|
|
menu_buttons = [
|
|
("📁 Scan", "scan", "Scan folders and add photos"),
|
|
("🔍 Process", "process", "Detect faces in photos"),
|
|
("👤 Identify", "identify", "Identify faces in photos"),
|
|
("🔗 Auto-Match", "auto_match", "Find and confirm matching faces"),
|
|
("🔎 Search", "search", "Search photos by person name"),
|
|
("✏️ Modify", "modify", "View and modify identified faces"),
|
|
("🏷️ Tags", "tags", "Manage photo tags"),
|
|
]
|
|
|
|
for i, (text, panel_name, tooltip) in enumerate(menu_buttons):
|
|
btn = ttk.Button(
|
|
buttons_frame,
|
|
text=text,
|
|
command=lambda p=panel_name: self.show_panel(p),
|
|
width=12 # Fixed width for consistent layout
|
|
)
|
|
btn.grid(row=0, column=i, padx=3, sticky=tk.W)
|
|
|
|
# Add tooltip functionality
|
|
self._add_tooltip(btn, tooltip)
|
|
|
|
# Status/Info area with better styling
|
|
status_frame = ttk.Frame(menu_frame)
|
|
status_frame.grid(row=0, column=2, sticky=tk.E, padx=(20, 0))
|
|
|
|
self.status_label = tk.Label(status_frame, text="Ready", foreground="#666", font=("Arial", 10))
|
|
self.status_label.pack(side=tk.RIGHT)
|
|
|
|
# Add a subtle separator line below the menu
|
|
separator = ttk.Separator(parent, orient='horizontal')
|
|
separator.grid(row=1, column=0, sticky=(tk.W, tk.E), padx=15, pady=(0, 5))
|
|
|
|
def _create_content_area(self, parent: ttk.Frame):
|
|
"""Create the main content area where panels will be displayed"""
|
|
self.content_frame = ttk.Frame(parent)
|
|
self.content_frame.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=15, pady=(0, 15))
|
|
|
|
# Configure content frame to expand
|
|
self.content_frame.columnconfigure(0, weight=1)
|
|
self.content_frame.rowconfigure(0, weight=1)
|
|
|
|
# Add a subtle border
|
|
self.content_frame.configure(relief='sunken', borderwidth=1)
|
|
|
|
def _initialize_panels(self):
|
|
"""Initialize all panels (currently placeholders)"""
|
|
# Home panel (default)
|
|
self.panels["home"] = self._create_home_panel()
|
|
|
|
# Functional panels (placeholders for now)
|
|
self.panels["scan"] = self._create_scan_panel()
|
|
self.panels["process"] = self._create_process_panel()
|
|
self.panels["identify"] = self._create_identify_panel()
|
|
self.panels["auto_match"] = self._create_auto_match_panel()
|
|
self.panels["search"] = self._create_search_panel()
|
|
self.panels["modify"] = self._create_modify_panel()
|
|
self.panels["tags"] = self._create_tags_panel()
|
|
|
|
def show_panel(self, panel_name: str):
|
|
"""Show the specified panel in the content area"""
|
|
if panel_name not in self.panels:
|
|
messagebox.showerror("Error", f"Panel '{panel_name}' not found", parent=self.root)
|
|
return
|
|
|
|
# Hide current panel
|
|
if self.current_panel:
|
|
self.panels[self.current_panel].grid_remove()
|
|
|
|
# Show new panel
|
|
self.panels[panel_name].grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=15, pady=15)
|
|
self.current_panel = panel_name
|
|
|
|
# Update status
|
|
self.status_label.config(text=f"Viewing: {panel_name.replace('_', ' ').title()}")
|
|
|
|
def _add_tooltip(self, widget, text):
|
|
"""Add a simple tooltip to a widget"""
|
|
def show_tooltip(event):
|
|
tooltip = tk.Toplevel()
|
|
tooltip.wm_overrideredirect(True)
|
|
tooltip.wm_geometry(f"+{event.x_root+10}+{event.y_root+10}")
|
|
label = ttk.Label(tooltip, text=text, background="#ffffe0", relief="solid", borderwidth=1)
|
|
label.pack()
|
|
widget.tooltip = tooltip
|
|
|
|
def hide_tooltip(event):
|
|
if hasattr(widget, 'tooltip'):
|
|
widget.tooltip.destroy()
|
|
del widget.tooltip
|
|
|
|
widget.bind("<Enter>", show_tooltip)
|
|
widget.bind("<Leave>", hide_tooltip)
|
|
|
|
def _on_window_resize(self, event):
|
|
"""Handle window resize events for dynamic responsiveness"""
|
|
# Only handle resize events for the main window, not child widgets
|
|
if event.widget == self.root:
|
|
# Update status with current window size
|
|
width = self.root.winfo_width()
|
|
height = self.root.winfo_height()
|
|
self.status_label.config(text=f"Ready - {width}x{height}")
|
|
|
|
# Force update of all panels to ensure proper resizing
|
|
if hasattr(self, 'identify_panel') and self.identify_panel:
|
|
# Update identify panel layout if it's active
|
|
if self.current_panel == "identify":
|
|
self.identify_panel.update_layout()
|
|
|
|
def _create_home_panel(self) -> ttk.Frame:
|
|
"""Create the home/welcome panel"""
|
|
panel = ttk.Frame(self.content_frame)
|
|
|
|
# Configure panel grid for responsiveness
|
|
panel.columnconfigure(0, weight=1)
|
|
panel.rowconfigure(0, weight=1)
|
|
|
|
# Welcome content
|
|
welcome_frame = ttk.Frame(panel)
|
|
welcome_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=20, pady=20)
|
|
welcome_frame.columnconfigure(0, weight=1)
|
|
welcome_frame.rowconfigure(0, weight=1)
|
|
|
|
# Center the content
|
|
center_frame = ttk.Frame(welcome_frame)
|
|
center_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
center_frame.columnconfigure(0, weight=1)
|
|
|
|
# Title with larger font for full screen
|
|
title_label = tk.Label(center_frame, text="Welcome to PunimTag", font=("Arial", 32, "bold"))
|
|
title_label.grid(row=0, column=0, pady=(0, 30))
|
|
|
|
# Description with larger font
|
|
desc_text = (
|
|
"PunimTag is a powerful photo face recognition and tagging system.\n\n"
|
|
"Use the menu above to access different features:\n\n"
|
|
"• 📁 Scan - Add photos to your collection\n"
|
|
"• 🔍 Process - Detect faces in photos\n"
|
|
"• 👤 Identify - Identify people in photos\n"
|
|
"• 🔗 Auto-Match - Find matching faces automatically\n"
|
|
"• 🔎 Search - Find photos by person name\n"
|
|
"• ✏️ Modify - Edit face identifications\n"
|
|
"• 🏷️ Tags - Manage photo tags\n\n"
|
|
"Select a feature from the menu to get started!"
|
|
)
|
|
|
|
desc_label = tk.Label(center_frame, text=desc_text, font=("Arial", 14), justify=tk.LEFT)
|
|
desc_label.grid(row=1, column=0, pady=(0, 20))
|
|
|
|
return panel
|
|
|
|
def _create_scan_panel(self) -> ttk.Frame:
|
|
"""Create the scan panel (migrated from original dashboard)"""
|
|
panel = ttk.Frame(self.content_frame)
|
|
|
|
# Configure panel grid for responsiveness
|
|
panel.columnconfigure(0, weight=1)
|
|
panel.rowconfigure(1, weight=1)
|
|
|
|
# Title with larger font for full screen
|
|
title_label = tk.Label(panel, text="📁 Scan Photos", font=("Arial", 24, "bold"))
|
|
title_label.grid(row=0, column=0, sticky=tk.W, pady=(0, 20))
|
|
|
|
# Scan form
|
|
form_frame = ttk.LabelFrame(panel, text="Scan Configuration", padding="20")
|
|
form_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 20))
|
|
form_frame.columnconfigure(0, weight=1)
|
|
|
|
# Folder selection
|
|
folder_frame = ttk.Frame(form_frame)
|
|
folder_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 15))
|
|
folder_frame.columnconfigure(0, weight=1)
|
|
|
|
tk.Label(folder_frame, text="Folder to scan:", font=("Arial", 12)).grid(row=0, column=0, sticky=tk.W)
|
|
|
|
folder_input_frame = ttk.Frame(folder_frame)
|
|
folder_input_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(5, 0))
|
|
folder_input_frame.columnconfigure(0, weight=1)
|
|
|
|
self.folder_var = tk.StringVar()
|
|
folder_entry = tk.Entry(folder_input_frame, textvariable=self.folder_var, font=("Arial", 11))
|
|
folder_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
|
|
|
|
def browse_folder():
|
|
from tkinter import filedialog
|
|
folder_path = filedialog.askdirectory(title="Select folder to scan for photos")
|
|
if folder_path:
|
|
self.folder_var.set(folder_path)
|
|
|
|
browse_btn = ttk.Button(folder_input_frame, text="Browse", command=browse_folder)
|
|
browse_btn.grid(row=0, column=1)
|
|
|
|
# Recursive option
|
|
self.recursive_var = tk.BooleanVar(value=True)
|
|
recursive_check = tk.Checkbutton(
|
|
form_frame,
|
|
text="Include photos in sub-folders",
|
|
variable=self.recursive_var,
|
|
font=("Arial", 11)
|
|
)
|
|
recursive_check.grid(row=1, column=0, sticky=tk.W, pady=(15, 0))
|
|
|
|
# Action button
|
|
scan_btn = ttk.Button(form_frame, text="🔍 Start Scan", command=self._run_scan)
|
|
scan_btn.grid(row=2, column=0, sticky=tk.W, pady=(20, 0))
|
|
|
|
return panel
|
|
|
|
def _create_process_panel(self) -> ttk.Frame:
|
|
"""Create the process panel (migrated from original dashboard)"""
|
|
panel = ttk.Frame(self.content_frame)
|
|
|
|
# Configure panel grid for responsiveness
|
|
panel.columnconfigure(0, weight=1)
|
|
panel.rowconfigure(1, weight=1)
|
|
|
|
# Title with larger font for full screen
|
|
title_label = tk.Label(panel, text="🔍 Process Faces", font=("Arial", 24, "bold"))
|
|
title_label.grid(row=0, column=0, sticky=tk.W, pady=(0, 20))
|
|
|
|
# Process form
|
|
form_frame = ttk.LabelFrame(panel, text="Processing Configuration", padding="20")
|
|
form_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 20))
|
|
form_frame.columnconfigure(0, weight=1)
|
|
|
|
# Limit option
|
|
limit_frame = ttk.Frame(form_frame)
|
|
limit_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 15))
|
|
|
|
self.limit_enabled = tk.BooleanVar(value=False)
|
|
limit_check = tk.Checkbutton(limit_frame, text="Limit processing to", variable=self.limit_enabled, font=("Arial", 11))
|
|
limit_check.grid(row=0, column=0, sticky=tk.W)
|
|
|
|
self.limit_var = tk.StringVar(value="50")
|
|
limit_entry = tk.Entry(limit_frame, textvariable=self.limit_var, width=8, font=("Arial", 11))
|
|
limit_entry.grid(row=0, column=1, padx=(10, 5))
|
|
|
|
tk.Label(limit_frame, text="photos", font=("Arial", 11)).grid(row=0, column=2, sticky=tk.W)
|
|
|
|
# Action button
|
|
process_btn = ttk.Button(form_frame, text="🚀 Start Processing", command=self._run_process)
|
|
process_btn.grid(row=1, column=0, sticky=tk.W, pady=(20, 0))
|
|
|
|
return panel
|
|
|
|
def _create_identify_panel(self) -> ttk.Frame:
|
|
"""Create the identify panel with full functionality"""
|
|
panel = ttk.Frame(self.content_frame)
|
|
|
|
# Configure panel grid for responsiveness
|
|
panel.columnconfigure(0, weight=1)
|
|
panel.rowconfigure(1, weight=1)
|
|
|
|
# Title with larger font for full screen
|
|
title_label = tk.Label(panel, text="👤 Identify Faces", font=("Arial", 24, "bold"))
|
|
title_label.grid(row=0, column=0, sticky=tk.W, pady=(0, 20))
|
|
|
|
# Create the identify panel if we have the required dependencies
|
|
if self.db_manager and self.face_processor:
|
|
self.identify_panel = IdentifyPanel(panel, self.db_manager, self.face_processor, self.gui_core)
|
|
identify_frame = self.identify_panel.create_panel()
|
|
identify_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
else:
|
|
# Fallback placeholder if dependencies are not available
|
|
placeholder_frame = ttk.LabelFrame(panel, text="Configuration Required", padding="20")
|
|
placeholder_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
placeholder_frame.columnconfigure(0, weight=1)
|
|
placeholder_frame.rowconfigure(0, weight=1)
|
|
|
|
placeholder_text = (
|
|
"Identify panel requires database and face processor to be configured.\n\n"
|
|
"This will contain the full face identification interface\n"
|
|
"currently available in the separate Identify window.\n\n"
|
|
"Features will include:\n"
|
|
"• Face browsing and identification\n"
|
|
"• Similar face matching\n"
|
|
"• Person management\n"
|
|
"• Batch processing options"
|
|
)
|
|
|
|
placeholder_label = tk.Label(placeholder_frame, text=placeholder_text, font=("Arial", 14), justify=tk.LEFT)
|
|
placeholder_label.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
|
|
return panel
|
|
|
|
def _create_auto_match_panel(self) -> ttk.Frame:
|
|
"""Create the auto-match panel (placeholder)"""
|
|
panel = ttk.Frame(self.content_frame)
|
|
|
|
# Configure panel grid for responsiveness
|
|
panel.columnconfigure(0, weight=1)
|
|
panel.rowconfigure(1, weight=1)
|
|
|
|
title_label = tk.Label(panel, text="🔗 Auto-Match Faces", font=("Arial", 24, "bold"))
|
|
title_label.grid(row=0, column=0, sticky=tk.W, pady=(0, 20))
|
|
|
|
placeholder_frame = ttk.LabelFrame(panel, text="Coming Soon", padding="20")
|
|
placeholder_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
placeholder_frame.columnconfigure(0, weight=1)
|
|
placeholder_frame.rowconfigure(0, weight=1)
|
|
|
|
placeholder_text = "Auto-Match functionality will be integrated here."
|
|
placeholder_label = tk.Label(placeholder_frame, text=placeholder_text, font=("Arial", 14))
|
|
placeholder_label.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
|
|
return panel
|
|
|
|
def _create_search_panel(self) -> ttk.Frame:
|
|
"""Create the search panel (placeholder)"""
|
|
panel = ttk.Frame(self.content_frame)
|
|
|
|
# Configure panel grid for responsiveness
|
|
panel.columnconfigure(0, weight=1)
|
|
panel.rowconfigure(1, weight=1)
|
|
|
|
title_label = tk.Label(panel, text="🔎 Search Photos", font=("Arial", 24, "bold"))
|
|
title_label.grid(row=0, column=0, sticky=tk.W, pady=(0, 20))
|
|
|
|
placeholder_frame = ttk.LabelFrame(panel, text="Coming Soon", padding="20")
|
|
placeholder_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
placeholder_frame.columnconfigure(0, weight=1)
|
|
placeholder_frame.rowconfigure(0, weight=1)
|
|
|
|
placeholder_text = "Search functionality will be integrated here."
|
|
placeholder_label = tk.Label(placeholder_frame, text=placeholder_text, font=("Arial", 14))
|
|
placeholder_label.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
|
|
return panel
|
|
|
|
def _create_modify_panel(self) -> ttk.Frame:
|
|
"""Create the modify panel (placeholder)"""
|
|
panel = ttk.Frame(self.content_frame)
|
|
|
|
# Configure panel grid for responsiveness
|
|
panel.columnconfigure(0, weight=1)
|
|
panel.rowconfigure(1, weight=1)
|
|
|
|
title_label = tk.Label(panel, text="✏️ Modify Identified", font=("Arial", 24, "bold"))
|
|
title_label.grid(row=0, column=0, sticky=tk.W, pady=(0, 20))
|
|
|
|
placeholder_frame = ttk.LabelFrame(panel, text="Coming Soon", padding="20")
|
|
placeholder_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
placeholder_frame.columnconfigure(0, weight=1)
|
|
placeholder_frame.rowconfigure(0, weight=1)
|
|
|
|
placeholder_text = "Modify functionality will be integrated here."
|
|
placeholder_label = tk.Label(placeholder_frame, text=placeholder_text, font=("Arial", 14))
|
|
placeholder_label.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
|
|
return panel
|
|
|
|
def _create_tags_panel(self) -> ttk.Frame:
|
|
"""Create the tags panel (placeholder)"""
|
|
panel = ttk.Frame(self.content_frame)
|
|
|
|
# Configure panel grid for responsiveness
|
|
panel.columnconfigure(0, weight=1)
|
|
panel.rowconfigure(1, weight=1)
|
|
|
|
title_label = tk.Label(panel, text="🏷️ Tag Manager", font=("Arial", 24, "bold"))
|
|
title_label.grid(row=0, column=0, sticky=tk.W, pady=(0, 20))
|
|
|
|
placeholder_frame = ttk.LabelFrame(panel, text="Coming Soon", padding="20")
|
|
placeholder_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
placeholder_frame.columnconfigure(0, weight=1)
|
|
placeholder_frame.rowconfigure(0, weight=1)
|
|
|
|
placeholder_text = "Tag management functionality will be integrated here."
|
|
placeholder_label = tk.Label(placeholder_frame, text=placeholder_text, font=("Arial", 14))
|
|
placeholder_label.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
|
|
return panel
|
|
|
|
def _run_scan(self):
|
|
"""Run the scan operation (migrated from original dashboard)"""
|
|
folder = self.folder_var.get().strip()
|
|
recursive = bool(self.recursive_var.get())
|
|
|
|
if not folder:
|
|
messagebox.showwarning("Scan", "Please enter a folder path.", parent=self.root)
|
|
return
|
|
|
|
# Validate folder path using path utilities
|
|
from path_utils import validate_path_exists, normalize_path
|
|
try:
|
|
folder = normalize_path(folder)
|
|
if not validate_path_exists(folder):
|
|
messagebox.showerror("Scan", f"Folder does not exist or is not accessible: {folder}", parent=self.root)
|
|
return
|
|
except ValueError as e:
|
|
messagebox.showerror("Scan", f"Invalid folder path: {e}", parent=self.root)
|
|
return
|
|
|
|
if not callable(self.on_scan):
|
|
messagebox.showinfo("Scan", "Scan functionality is not wired yet.", parent=self.root)
|
|
return
|
|
|
|
def worker():
|
|
try:
|
|
self.status_label.config(text="Scanning...")
|
|
result = self.on_scan(folder, recursive)
|
|
messagebox.showinfo("Scan", f"Scan completed. Result: {result}", parent=self.root)
|
|
self.status_label.config(text="Ready")
|
|
except Exception as e:
|
|
messagebox.showerror("Scan", f"Error during scan: {e}", parent=self.root)
|
|
self.status_label.config(text="Ready")
|
|
|
|
threading.Thread(target=worker, daemon=True).start()
|
|
|
|
def _run_process(self):
|
|
"""Run the process operation (migrated from original dashboard)"""
|
|
if not callable(self.on_process):
|
|
messagebox.showinfo("Process", "Process functionality is not wired yet.", parent=self.root)
|
|
return
|
|
|
|
limit_value = None
|
|
if self.limit_enabled.get():
|
|
try:
|
|
limit_value = int(self.limit_var.get().strip())
|
|
if limit_value <= 0:
|
|
raise ValueError
|
|
except Exception:
|
|
messagebox.showerror("Process", "Please enter a valid positive integer for limit.", parent=self.root)
|
|
return
|
|
|
|
def worker():
|
|
try:
|
|
self.status_label.config(text="Processing...")
|
|
result = self.on_process(limit_value)
|
|
messagebox.showinfo("Process", f"Processing completed. Result: {result}", parent=self.root)
|
|
self.status_label.config(text="Ready")
|
|
except Exception as e:
|
|
messagebox.showerror("Process", f"Error during processing: {e}", parent=self.root)
|
|
self.status_label.config(text="Ready")
|
|
|
|
threading.Thread(target=worker, daemon=True).start()
|
|
|
|
|