Abilita selezione multipla nella coda
This commit is contained in:
@@ -77,8 +77,8 @@ La GUI di configurazione deve permettere di:
|
|||||||
- aprire i log giornalieri;
|
- aprire i log giornalieri;
|
||||||
- eventualmente eseguire un test di raggiungibilita' del server;
|
- eventualmente eseguire un test di raggiungibilita' del server;
|
||||||
- visualizzare l'elenco dei file in coda per il backup;
|
- visualizzare l'elenco dei file in coda per il backup;
|
||||||
- rimuovere singoli file dalla coda;
|
- rimuovere singoli file o selezioni multiple dalla coda;
|
||||||
- proporre, quando si rimuove un file dalla coda, se rimuovere solo il file o escludere dal backup la cartella che lo contiene.
|
- proporre, quando si rimuovono file dalla coda, se rimuovere solo i file selezionati o escludere dal backup le cartelle che li contengono.
|
||||||
|
|
||||||
Se un computer viene spento senza usare `Backup e spegni`, i file gia' registrati come `pending` devono restare nel registro cambiamenti e accumularsi con quelli del giorno successivo, in modo da poter essere sincronizzati al backup successivo.
|
Se un computer viene spento senza usare `Backup e spegni`, i file gia' registrati come `pending` devono restare nel registro cambiamenti e accumularsi con quelli del giorno successivo, in modo da poter essere sincronizzati al backup successivo.
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ class BakRestConfigApp(ctk.CTk):
|
|||||||
refresh_button = ctk.CTkButton(toolbar, text="Aggiorna", command=self._refresh_queue)
|
refresh_button = ctk.CTkButton(toolbar, text="Aggiorna", command=self._refresh_queue)
|
||||||
refresh_button.grid(row=0, column=1, padx=4)
|
refresh_button.grid(row=0, column=1, padx=4)
|
||||||
|
|
||||||
remove_button = ctk.CTkButton(toolbar, text="Rimuovi selezionato", command=self._remove_selected_pending)
|
remove_button = ctk.CTkButton(toolbar, text="Rimuovi selezionati", command=self._remove_selected_pending)
|
||||||
remove_button.grid(row=0, column=2, padx=4)
|
remove_button.grid(row=0, column=2, padx=4)
|
||||||
|
|
||||||
list_frame = ctk.CTkFrame(self.queue_tab)
|
list_frame = ctk.CTkFrame(self.queue_tab)
|
||||||
@@ -163,7 +163,12 @@ class BakRestConfigApp(ctk.CTk):
|
|||||||
list_frame.grid_columnconfigure(0, weight=1)
|
list_frame.grid_columnconfigure(0, weight=1)
|
||||||
list_frame.grid_rowconfigure(0, weight=1)
|
list_frame.grid_rowconfigure(0, weight=1)
|
||||||
|
|
||||||
self.queue_list = tk.Listbox(list_frame, activestyle="dotbox", exportselection=False)
|
self.queue_list = tk.Listbox(
|
||||||
|
list_frame,
|
||||||
|
activestyle="dotbox",
|
||||||
|
exportselection=False,
|
||||||
|
selectmode=tk.EXTENDED,
|
||||||
|
)
|
||||||
self.queue_list.grid(row=0, column=0, sticky="nsew", padx=(8, 0), pady=8)
|
self.queue_list.grid(row=0, column=0, sticky="nsew", padx=(8, 0), pady=8)
|
||||||
|
|
||||||
scrollbar = ctk.CTkScrollbar(list_frame, command=self.queue_list.yview)
|
scrollbar = ctk.CTkScrollbar(list_frame, command=self.queue_list.yview)
|
||||||
@@ -230,18 +235,21 @@ class BakRestConfigApp(ctk.CTk):
|
|||||||
def _remove_selected_pending(self) -> None:
|
def _remove_selected_pending(self) -> None:
|
||||||
selection = self.queue_list.curselection()
|
selection = self.queue_list.curselection()
|
||||||
if not selection:
|
if not selection:
|
||||||
messagebox.showinfo("Bak&Rest", "Seleziona un file dalla coda.")
|
messagebox.showinfo("Bak&Rest", "Seleziona uno o piu' file dalla coda.")
|
||||||
return
|
return
|
||||||
|
|
||||||
event = self.pending_events[selection[0]]
|
selected_events = [self.pending_events[index] for index in selection]
|
||||||
dialog = RemovePendingDialog(self, event.path)
|
dialog = RemovePendingDialog(self, [event.path for event in selected_events])
|
||||||
self.wait_window(dialog)
|
self.wait_window(dialog)
|
||||||
if dialog.choice == "file":
|
if dialog.choice == "file":
|
||||||
self.registry.mark_ignored(event.path)
|
self.registry.mark_status([event.path for event in selected_events], "ignored")
|
||||||
elif dialog.choice == "folder":
|
elif dialog.choice == "folder":
|
||||||
folder = str(Path(event.path).parent)
|
folders = sorted({str(Path(event.path).parent) for event in selected_events})
|
||||||
self._add_exclude_dir(folder)
|
paths_to_ignore: set[str] = set()
|
||||||
self.registry.mark_status(self._pending_paths_inside(folder), "ignored")
|
for folder in folders:
|
||||||
|
self._add_exclude_dir(folder)
|
||||||
|
paths_to_ignore.update(self._pending_paths_inside(folder))
|
||||||
|
self.registry.mark_status(sorted(paths_to_ignore), "ignored")
|
||||||
save_config_data(self.data, self.config_path)
|
save_config_data(self.data, self.config_path)
|
||||||
self.exclude_dirs.set_items(self.data.setdefault("watch", {}).get("exclude_dirs", []))
|
self.exclude_dirs.set_items(self.data.setdefault("watch", {}).get("exclude_dirs", []))
|
||||||
else:
|
else:
|
||||||
@@ -268,25 +276,28 @@ class BakRestConfigApp(ctk.CTk):
|
|||||||
|
|
||||||
|
|
||||||
class RemovePendingDialog(ctk.CTkToplevel):
|
class RemovePendingDialog(ctk.CTkToplevel):
|
||||||
def __init__(self, parent: ctk.CTk, file_path: str) -> None:
|
def __init__(self, parent: ctk.CTk, file_paths: list[str]) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.choice: str | None = None
|
self.choice: str | None = None
|
||||||
self.title("Rimuovi dalla coda")
|
self.title("Rimuovi dalla coda")
|
||||||
self.geometry("520x190")
|
self.geometry("560x240")
|
||||||
self.resizable(False, False)
|
self.resizable(False, False)
|
||||||
self.transient(parent)
|
self.transient(parent)
|
||||||
self.grab_set()
|
self.grab_set()
|
||||||
|
|
||||||
self.grid_columnconfigure(0, weight=1)
|
self.grid_columnconfigure(0, weight=1)
|
||||||
|
count = len(file_paths)
|
||||||
|
subject = "il file selezionato" if count == 1 else f"i {count} file selezionati"
|
||||||
label = ctk.CTkLabel(
|
label = ctk.CTkLabel(
|
||||||
self,
|
self,
|
||||||
text="Vuoi rimuovere solo il file o isolare dal backup la cartella che lo contiene?",
|
text=f"Vuoi rimuovere solo {subject} o isolare dal backup le cartelle che li contengono?",
|
||||||
wraplength=470,
|
wraplength=510,
|
||||||
justify="left",
|
justify="left",
|
||||||
)
|
)
|
||||||
label.grid(row=0, column=0, sticky="ew", padx=18, pady=(18, 8))
|
label.grid(row=0, column=0, sticky="ew", padx=18, pady=(18, 8))
|
||||||
|
|
||||||
path_label = ctk.CTkLabel(self, text=file_path, wraplength=470, justify="left")
|
preview = _selection_preview(file_paths)
|
||||||
|
path_label = ctk.CTkLabel(self, text=preview, wraplength=510, justify="left")
|
||||||
path_label.grid(row=1, column=0, sticky="ew", padx=18, pady=(0, 16))
|
path_label.grid(row=1, column=0, sticky="ew", padx=18, pady=(0, 16))
|
||||||
|
|
||||||
buttons = ctk.CTkFrame(self, fg_color="transparent")
|
buttons = ctk.CTkFrame(self, fg_color="transparent")
|
||||||
@@ -301,6 +312,13 @@ class RemovePendingDialog(ctk.CTkToplevel):
|
|||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
def _selection_preview(file_paths: list[str]) -> str:
|
||||||
|
if len(file_paths) <= 3:
|
||||||
|
return "\n".join(file_paths)
|
||||||
|
shown = "\n".join(file_paths[:3])
|
||||||
|
return f"{shown}\n... altri {len(file_paths) - 3} file"
|
||||||
|
|
||||||
|
|
||||||
class _ListEditor(ctk.CTkFrame):
|
class _ListEditor(ctk.CTkFrame):
|
||||||
def __init__(self, parent: ctk.CTkBaseClass, title: str, browse_dirs: bool = False) -> None:
|
def __init__(self, parent: ctk.CTkBaseClass, title: str, browse_dirs: bool = False) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|||||||
Reference in New Issue
Block a user