Added uploads, part renaming, bulk data import acceptance
This commit is contained in:
127
ui/progress_dialog.py
Normal file
127
ui/progress_dialog.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Progress dialog with progress bar, ETA, and cancel button.
|
||||
"""
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import time
|
||||
import threading
|
||||
|
||||
|
||||
class ProgressDialog:
|
||||
"""
|
||||
A modal progress dialog with progress bar, status text, ETA, and cancel button.
|
||||
Thread-safe for updating from background threads.
|
||||
"""
|
||||
|
||||
def __init__(self, parent, title="Progress"):
|
||||
self.parent = parent
|
||||
self.cancelled = False
|
||||
self._start_time = time.time()
|
||||
|
||||
# Create dialog
|
||||
self.dialog = tk.Toplevel(parent)
|
||||
self.dialog.title(title)
|
||||
self.dialog.geometry("500x150")
|
||||
self.dialog.resizable(False, False)
|
||||
|
||||
# Make it modal
|
||||
self.dialog.transient(parent)
|
||||
self.dialog.grab_set()
|
||||
|
||||
# Center on parent
|
||||
self.dialog.update_idletasks()
|
||||
x = parent.winfo_x() + (parent.winfo_width() - 500) // 2
|
||||
y = parent.winfo_y() + (parent.winfo_height() - 150) // 2
|
||||
self.dialog.geometry(f"+{x}+{y}")
|
||||
|
||||
# Status label
|
||||
self.status_var = tk.StringVar(value="Starting...")
|
||||
ttk.Label(self.dialog, textvariable=self.status_var, font=("Segoe UI", 10)).pack(pady=(15, 5))
|
||||
|
||||
# Progress bar
|
||||
self.progress = ttk.Progressbar(self.dialog, length=450, mode='determinate')
|
||||
self.progress.pack(pady=(5, 5), padx=25)
|
||||
|
||||
# ETA label
|
||||
self.eta_var = tk.StringVar(value="Calculating...")
|
||||
ttk.Label(self.dialog, textvariable=self.eta_var, font=("Segoe UI", 9), foreground="#666").pack(pady=(0, 10))
|
||||
|
||||
# Cancel button
|
||||
self.cancel_btn = ttk.Button(self.dialog, text="Cancel", command=self.cancel)
|
||||
self.cancel_btn.pack(pady=(0, 15))
|
||||
|
||||
# Handle window close
|
||||
self.dialog.protocol("WM_DELETE_WINDOW", self.cancel)
|
||||
|
||||
def update(self, current, total, status_text=None):
|
||||
"""
|
||||
Update progress bar and ETA.
|
||||
Thread-safe - can be called from background threads.
|
||||
|
||||
Args:
|
||||
current: Current item number (0-based or 1-based)
|
||||
total: Total number of items
|
||||
status_text: Optional status text to display
|
||||
"""
|
||||
def _update():
|
||||
if self.cancelled:
|
||||
return
|
||||
|
||||
# Update progress bar
|
||||
if total > 0:
|
||||
percentage = (current / total) * 100
|
||||
self.progress['value'] = percentage
|
||||
|
||||
# Update status text
|
||||
if status_text:
|
||||
self.status_var.set(status_text)
|
||||
|
||||
# Calculate and update ETA
|
||||
if current > 0 and total > 0:
|
||||
elapsed = time.time() - self._start_time
|
||||
avg_time_per_item = elapsed / current
|
||||
remaining_items = total - current
|
||||
eta_seconds = remaining_items * avg_time_per_item
|
||||
|
||||
if eta_seconds < 60:
|
||||
eta_text = f"ETA: {int(eta_seconds)}s"
|
||||
elif eta_seconds < 3600:
|
||||
eta_text = f"ETA: {int(eta_seconds / 60)}m {int(eta_seconds % 60)}s"
|
||||
else:
|
||||
hours = int(eta_seconds / 3600)
|
||||
minutes = int((eta_seconds % 3600) / 60)
|
||||
eta_text = f"ETA: {hours}h {minutes}m"
|
||||
|
||||
self.eta_var.set(f"{current}/{total} items - {eta_text}")
|
||||
else:
|
||||
self.eta_var.set(f"{current}/{total} items")
|
||||
|
||||
# Schedule update on main thread
|
||||
if threading.current_thread() == threading.main_thread():
|
||||
_update()
|
||||
else:
|
||||
self.dialog.after(0, _update)
|
||||
|
||||
def cancel(self):
|
||||
"""Mark as cancelled and close dialog."""
|
||||
self.cancelled = True
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
"""Close the dialog."""
|
||||
def _close():
|
||||
try:
|
||||
self.dialog.grab_release()
|
||||
self.dialog.destroy()
|
||||
except:
|
||||
pass
|
||||
|
||||
# Schedule on main thread
|
||||
if threading.current_thread() == threading.main_thread():
|
||||
_close()
|
||||
else:
|
||||
self.dialog.after(0, _close)
|
||||
|
||||
def is_cancelled(self):
|
||||
"""Check if user cancelled the operation."""
|
||||
return self.cancelled
|
||||
Reference in New Issue
Block a user