refactor(settings): Overhaul Reset and Keyfile UI/UX

This commit introduces a major refactoring of the settings and advanced settings panels to improve user experience and code structure.

Key Changes:

1.  **Reset Functionality Refactored:**
    -   The "Default Settings" logic has been moved from `actions.py` into `settings_frame.py`, where it belongs.
    -   Clicking "Reset" now opens a dedicated view with separate options for a "Default Reset" and a "Hard Reset", each with clear explanations.
    -   A global "Cancel" button was added for this new view, and the layout of the components has been centered and cleaned up.
    -   Error handling for the reset process has been improved to show feedback in the header.

2.  **Keyfile Creation UX Overhauled:**
    -   The "Automation Settings" (Keyfile) view in Advanced Settings is completely redesigned for clarity.
    -   It now explicitly displays the currently selected backup destination, so the user knows which container will be affected.
    -   A detailed description has been added to explain the purpose and prerequisites of creating a keyfile.
    -   The redundant "Apply" button is now hidden in this view.
    -   Feedback for keyfile creation (success or failure) is now shown as a temporary message in the header instead of a blocking dialog.
    -   Error messages are more informative, guiding the user on how to proceed (e.g., by creating an encrypted backup first).

3.  **String Externalization:**
    -   All new UI text for the keyfile and reset features has been added to `pbp_app_config.py` to support translation.
This commit is contained in:
2025-09-15 00:46:47 +02:00
parent 34234d2d14
commit 9406d3f0e2
5 changed files with 179 additions and 119 deletions

View File

@@ -257,7 +257,7 @@ class Msg:
"log": _("Log"),
"full_backup": _("Full backup"),
"incremental": _("Incremental"),
"incremental_backup": _("Incremental backup"), # New
"incremental_backup": _("Incremental backup"),
"test_run": _("Test run"),
"start": _("Start"),
"cancel_backup": _("Cancel"),
@@ -280,8 +280,8 @@ class Msg:
"system_excludes": _("System Excludes"),
"manual_excludes": _("Manual Excludes"),
"manual_excludes_info": _("Here, manually add files or folders to be excluded from the backup. Each entry should be on a new line."),
"sudoers_info_text": _(f"To run automated backups, an administrator must create a file in /etc/sudoers.d/\n"
f"with the following content (replace 'user' with the correct username):\n"
"sudoers_info_text": _(f"To run automated backups, an administrator must create a file in /etc/sudoers.d/\n"
f"with the following content (replace 'user' with the correct username):\n"
f"user ALL=(ALL) NOPASSWD: /path/to/pybackup-cli.py"),
# Menus
@@ -353,9 +353,9 @@ class Msg:
"header_subtitle": _("Simple GUI for rsync"),
"encrypted_backup_content": _("Encrypted Backups"),
"compressed": _("Compressed"),
"compression": _("Compression"), # New
"compression": _("Compression"),
"encrypted": _("Encrypted"),
"encryption": _("Encryption"), # New
"encryption": _("Encryption"),
"bypass_security": _("Bypass security"),
"refresh_log": _("Refresh log"),
"comment": _("Kommentar"),
@@ -370,17 +370,38 @@ class Msg:
"sync_mode_trash_bin": _("Sync Mode: Archive. Files deleted from the source will be moved to a trash folder in the backup."),
"sync_mode_no_delete": _("Sync Mode: Additive. Files deleted from the source will be kept in the backup."),
"encryption_note_system_backup": _("Note: For system backups, encryption only applies to files directly within the /home directory. Folders are not automatically encrypted unless explicitly included in the backup."),
"keyfile_settings": _("Keyfile Settings"), # New
"backup_defaults_title": _("Backup Defaults"), # New
"automation_settings_title": _("Automation Settings"), # New
"create_add_key_file": _("Create/Add Key File"), # New
"key_file_not_created": _("Key file not created."), # New
"backup_options": _("Backup Options"), # New
"hard_reset": _("Hard reset"),
# Advanced Settings - Keyfile
"keyfile_settings": _("Keyfile Settings"),
"backup_defaults_title": _("Backup Defaults"),
"automation_settings_title": _("Automation Settings"),
"create_add_key_file": _("Create/Add Key File"),
"key_file_not_created": _("Key file not created."),
"keyfile_automation_info": _("A keyfile provides an alternative way to unlock an encrypted backup without entering a password, which is useful for automation.\n\n**Prerequisite:** You must first create at least one encrypted backup for a profile. This process will then add a keyfile to that existing encrypted container."),
"err_no_encrypted_container": _("No encrypted container found at the destination. Please create an encrypted backup for this profile first."),
"enter_existing_password_title": _("Enter Existing Password"),
"keyfile_creation_success": _("Keyfile created successfully!"),
"keyfile_creation_failed": _("Failed to create keyfile. See log for details."),
"keyfile_status_unknown": _("Keyfile status unknown (no destination set)."),
"keyfile_exists": _("Keyfile exists: {key_file_path}"),
"keyfile_not_found_for_dest": _("Keyfile has not been created for this destination."),
"select_profile_first": _("Please select at least one profile from the list."),
"current_destination": _("Current Target Destination"),
"no_destination_selected": _("None. Please select a destination in the main backup view."),
"sudoers_info_text": _(f"To run automated backups, an administrator must create a file in /etc/sudoers.d/\n"
f"with the following content (replace 'user' with the correct username):\n"
f"user ALL=(ALL) NOPASSWD: /path/to/pybackup-cli.py"),
# General Settings
"backup_options": _("Backup Options"),
"reset": _("Reset"),
"hard_reset_success": _("Hard reset successful.\nRestarting..."),
"hard_reset_info_manual_restart": _("The application must be restarted as drives are still mounted."),
"default_reset_success": _("Default reset successful"),
"default_reset_failed": _("Default reset failed\nPlease click Delete now on Full_delete_config_settings..."),
"default_reset_info": _("Click to restore all settings to their original defaults. Your existing backup schedules and any manually added paths in the exclusion list will be preserved."),
"hard_reset_warning": _("This will reset the application to its initial state, as if it were opened for the first time. This can be useful if you are experiencing problems with the app. Clicking 'Delete now' will delete the '.config/py_backup' folder in your home directory without an additional dialog. The application will then automatically restart."),
"delete_now": _("Delete now"),
"default_config_settings": _("Default config settings"),
"full_delete_config_settings": _("Full delete config settings"),
"password_required": _("Password Required"),
"enter_password_prompt": _("Please enter the password for the encrypted backup:"),

View File

@@ -456,8 +456,8 @@ class MainApplication(tk.Tk):
self.scheduler_frame.grid_remove()
def _setup_settings_frame(self):
self.settings_frame = SettingsFrame(
self.content_frame, self.navigation, self.actions, self.backup_manager.encryption_manager, self.image_manager, self.config_manager, padding=(0, 10))
self.settings_frame = SettingsFrame(self.content_frame, self.navigation, self.actions,
self.backup_manager.encryption_manager, self.image_manager, self.config_manager, padding=(0, 10))
self.settings_frame.grid(row=2, column=0, sticky="nsew")
self.settings_frame.grid_remove()

View File

@@ -77,7 +77,7 @@ class Actions:
self.app.compressed_cb.config(state="normal")
self.app.encrypted_cb.config(state="normal")
self.app.test_run_cb.config(state="normal")
# Apply mutual exclusion rules for Option A
if self.app.compressed_var.get():
self.app.incremental_var.set(False)
@@ -85,7 +85,7 @@ class Actions:
self.app.incremental_cb.config(state="disabled")
self.app.encrypted_var.set(False)
self.app.encrypted_cb.config(state="disabled")
if self.app.incremental_var.get() or self.app.encrypted_var.get():
self.app.compressed_var.set(False)
self.app.compressed_cb.config(state="disabled")
@@ -397,47 +397,6 @@ class Actions:
MessageDialog(message_type="error",
title=Msg.STR["error"], text=Msg.STR["path_not_found"].format(path=path)).show()
def reset_to_default_settings(self):
self.app.config_manager.set_setting("backup_destination_path", None)
self.app.config_manager.set_setting("restore_source_path", None)
self.app.config_manager.set_setting("restore_destination_path", None)
self.app.config_manager.remove_setting("backup_animation_type")
self.app.config_manager.remove_setting("calculation_animation_type")
self.app.config_manager.remove_setting("force_full_backup")
self.app.config_manager.remove_setting("force_incremental_backup")
self.app.config_manager.remove_setting("force_compression")
self.app.config_manager.remove_setting("force_encryption")
self.app.update_backup_options_from_config()
AppConfig.generate_and_write_final_exclude_list()
app_logger.log("Settings have been reset to default values.")
settings_frame = self.app.settings_frame
if settings_frame:
settings_frame.load_and_display_excludes()
settings_frame._load_hidden_files()
self.app.destination_path = None
self.app.start_cancel_button.config(state="disabled")
self.app.backup_left_canvas_data.clear()
self.app.backup_right_canvas_data.clear()
self.app.restore_left_canvas_data.clear()
self.app.restore_right_canvas_data.clear()
current_source = self.app.left_canvas_data.get('folder')
if current_source:
self.on_sidebar_button_click(current_source)
self.app.backup_content_frame.system_backups_frame._load_backup_content()
self.app.backup_content_frame.user_backups_frame._load_backup_content()
with message_box_animation(self.app.animated_icon):
MessageDialog(message_type="info",
title=Msg.STR["settings_reset_title"], text=Msg.STR["settings_reset_text"])
def _parse_size_string_to_bytes(self, size_str: str) -> int:
if not size_str or size_str == Msg.STR["calculating_size"]:
return 0
@@ -524,7 +483,8 @@ class Actions:
self.app.task_progress.stop()
self.app.task_progress.config(mode="determinate", value=0)
self.app._update_info_label(Msg.STR["incremental_calc_cancelled"], color="#E8740C")
self.app._update_info_label(
Msg.STR["incremental_calc_cancelled"], color="#E8740C")
self.app.start_cancel_button.config(text=Msg.STR["start"])
self._set_ui_state(True)
return
@@ -541,12 +501,14 @@ class Actions:
delete_path)
app_logger.log(
Msg.STR["backup_cancelled_and_deleted_msg"])
self.app._update_info_label(Msg.STR["backup_cancelled_and_deleted_msg"])
self.app._update_info_label(
Msg.STR["backup_cancelled_and_deleted_msg"])
else:
self.app.backup_manager.cancel_backup()
app_logger.log(
"Backup cancelled, but directory could not be deleted (path unknown).")
self.app._update_info_label("Backup cancelled, but directory could not be deleted (path unknown).")
self.app._update_info_label(
"Backup cancelled, but directory could not be deleted (path unknown).")
else:
self.app.backup_manager.cancel_backup()
if delete_path:
@@ -557,14 +519,17 @@ class Actions:
shutil.rmtree(delete_path)
app_logger.log(
Msg.STR["backup_cancelled_and_deleted_msg"])
self.app._update_info_label(Msg.STR["backup_cancelled_and_deleted_msg"])
self.app._update_info_label(
Msg.STR["backup_cancelled_and_deleted_msg"])
except Exception as e:
app_logger.log(f"Error deleting backup directory: {e}")
self.app._update_info_label(f"Error deleting backup directory: {e}")
self.app._update_info_label(
f"Error deleting backup directory: {e}")
else:
app_logger.log(
"Backup cancelled, but no path found to delete.")
self.app._update_info_label("Backup cancelled, but no path found to delete.")
self.app._update_info_label(
"Backup cancelled, but no path found to delete.")
if hasattr(self.app, 'current_backup_path'):
self.app.current_backup_path = None

View File

@@ -166,28 +166,44 @@ class AdvancedSettingsFrame(ttk.Frame):
self.backup_defaults_frame, text=Msg.STR["encryption_note_system_backup"], wraplength=750, justify="left")
encryption_note.pack(anchor=tk.W, pady=5)
# --- Keyfile Settings Frame ---
self.keyfile_settings_frame = ttk.LabelFrame(
view_container, text=Msg.STR["automation_settings_title"], padding=10)
keyfile_info_label = ttk.Label(
self.keyfile_settings_frame, text=Msg.STR["keyfile_automation_info"], justify="left", wraplength=750)
keyfile_info_label.grid(
row=0, column=0, columnspan=3, sticky="w", padx=5, pady=(5, 15))
# Display for current destination
dest_frame = ttk.Frame(self.keyfile_settings_frame)
dest_frame.grid(row=1, column=0, columnspan=3, sticky="ew", padx=5, pady=5)
dest_frame.columnconfigure(1, weight=1)
ttk.Label(dest_frame, text=f"{Msg.STR['current_destination']}:", font=("Ubuntu", 11, "bold")).grid(row=0, column=0, sticky="w")
self.keyfile_dest_path_label = ttk.Label(dest_frame, text=Msg.STR["no_destination_selected"], foreground="gray")
self.keyfile_dest_path_label.grid(row=0, column=1, sticky="w", padx=5)
# Action Button
key_file_button = ttk.Button(
self.keyfile_settings_frame, text=Msg.STR["create_add_key_file"], command=self._create_key_file)
key_file_button.grid(row=0, column=0, padx=5, pady=5)
key_file_button.grid(row=2, column=0, padx=5, pady=10)
self.key_file_status_var = tk.StringVar(
value=Msg.STR["key_file_not_created"])
# Status Label
self.key_file_status_var = tk.StringVar(value="")
key_file_status_label = ttk.Label(
self.keyfile_settings_frame, textvariable=self.key_file_status_var, foreground="gray")
key_file_status_label.grid(row=0, column=1, padx=5, pady=5, sticky="w")
key_file_status_label.grid(
row=2, column=1, padx=5, pady=10, sticky="w")
sudoers_info_text = Msg.STR["sudoers_info_text"]
# Sudoers Info
sudoers_info_label = ttk.Label(
self.keyfile_settings_frame, text=sudoers_info_text, justify="left")
self.keyfile_settings_frame, text=Msg.STR["sudoers_info_text"], justify="left")
sudoers_info_label.grid(
row=1, column=0, columnspan=2, sticky="w", padx=5, pady=5)
row=3, column=0, columnspan=3, sticky="w", padx=5, pady=10)
self.keyfile_settings_frame.columnconfigure(1, weight=1)
# --- Bottom Buttons (for most views) ---
self.bottom_button_frame = ttk.Frame(self)
self.bottom_button_frame.pack(pady=10)
@@ -244,60 +260,57 @@ class AdvancedSettingsFrame(ttk.Frame):
self.config_manager.set_setting("no_trash_bin", no_trash)
def _create_key_file(self):
if not self.app_instance.destination_path:
MessageDialog(message_type="error", title="Error",
text="Please select a backup destination first.")
header = self.app_instance.header_frame
destination = self.app_instance.destination_path
if not destination:
MessageDialog(message_type="error", title=Msg.STR["error"],
text=Msg.STR["select_destination_first"]).show()
return
pybackup_dir = os.path.join(
self.app_instance.destination_path, "pybackup")
pybackup_dir = os.path.join(destination, "pybackup")
container_path = os.path.join(pybackup_dir, "pybackup_encrypted.luks")
if not os.path.exists(container_path):
MessageDialog(message_type="error", title="Error",
text="No encrypted container found at the destination.")
MessageDialog(message_type="error", title=Msg.STR["error"],
text=Msg.STR["err_no_encrypted_container"]).show()
return
password_dialog = PasswordDialog(
self, title="Enter Existing Password", confirm=False, translations=Msg.STR)
self, title=Msg.STR["enter_existing_password_title"], confirm=False, translations=Msg.STR)
password, _ = password_dialog.get_password()
if not password:
return
key_file_path = self.app_instance.backup_manager.encryption_manager.create_and_add_key_file(
self.app_instance.destination_path, password)
destination, password)
if key_file_path:
MessageDialog(message_type="info", title="Success",
text=f"Key file created and added successfully!\nPath: {key_file_path}")
header.show_temporary_message(Msg.STR["keyfile_creation_success"])
else:
MessageDialog(message_type="error", title="Error",
text="Failed to create or add key file. See log for details.")
header.show_temporary_message(Msg.STR["keyfile_creation_failed"])
self._update_key_file_status()
def _update_key_file_status(self):
if not self.app_instance.destination_path:
self.key_file_status_var.set(
"Key file status unknown (no destination set).")
destination = self.app_instance.destination_path
if not destination:
self.keyfile_dest_path_label.config(text=Msg.STR["no_destination_selected"], foreground="gray")
self.key_file_status_var.set(Msg.STR["keyfile_status_unknown"])
return
# Determine the profile_name based on the current source folder
source_name = self.app_instance.left_canvas_data.get('folder')
if not source_name:
# Fallback or handle case where no source is selected, perhaps default to "system" or log an error
# For now, let's assume "system" if no specific folder is selected for keyfile status
profile_name = "system"
else:
profile_name = "system" if source_name == "Computer" else source_name
self.keyfile_dest_path_label.config(text=destination, foreground="#FFFFFF") # Adapt color to theme
source_name = self.app_instance.left_canvas_data.get('folder') or "system"
profile_name = "system" if source_name == "Computer" else source_name
key_file_path = self.app_instance.backup_manager.encryption_manager.get_key_file_path(
self.app_instance.destination_path, profile_name)
destination, profile_name)
if os.path.exists(key_file_path):
self.key_file_status_var.set(f"Key file exists: {key_file_path}")
self.key_file_status_var.set(Msg.STR["keyfile_exists"].format(key_file_path=key_file_path))
else:
self.key_file_status_var.set(
"Key file has not been created for this destination.")
self.key_file_status_var.set(Msg.STR["keyfile_not_found_for_dest"])
def _switch_view(self, index):
self.current_view_index = index
@@ -310,7 +323,7 @@ class AdvancedSettingsFrame(ttk.Frame):
self.backup_defaults_frame.pack_forget()
# Show/hide the main action buttons based on the view
if index == 1: # Manual Excludes view
if index in [1, 2]: # Manual Excludes and Keyfile Settings views
self.bottom_button_frame.pack_forget()
else:
self.bottom_button_frame.pack(pady=10)

View File

@@ -6,7 +6,6 @@ import sys
from pathlib import Path
from core.pbp_app_config import AppConfig, Msg
from core.backup_manager import BackupManager
from pyimage_ui.advanced_settings_frame import AdvancedSettingsFrame
from shared_libs.custom_file_dialog import CustomFileDialog
from shared_libs.message import MessageDialog, PasswordDialog
@@ -47,19 +46,12 @@ class SettingsFrame(ttk.Frame):
self.button_frame, text=Msg.STR["add_to_exclude_list"], command=self._add_to_exclude_list, style="Gray.Toolbutton")
add_to_exclude_button.pack(side=tk.LEFT, padx=5)
ttk.Separator(self.button_frame, orient=tk.VERTICAL).pack(
side=tk.LEFT, ipady=15, padx=5)
reset_button = ttk.Button(
self.button_frame, text=Msg.STR["default_settings"], command=self.actions.reset_to_default_settings, style="Gray.Toolbutton")
reset_button.pack(side=tk.LEFT)
ttk.Separator(self.button_frame, orient=tk.VERTICAL).pack(
side=tk.LEFT, ipady=15, padx=5)
# Right-aligned buttons
hard_reset_button = ttk.Button(
self.button_frame, text=Msg.STR["hard_reset"], command=self._toggle_hard_reset_view, style="Gray.Toolbutton")
self.button_frame, text=Msg.STR["reset"], command=self._toggle_hard_reset_view, style="Gray.Toolbutton")
hard_reset_button.pack(side=tk.LEFT, padx=5)
ttk.Separator(self.button_frame, orient=tk.VERTICAL).pack(
@@ -119,30 +111,93 @@ class SettingsFrame(ttk.Frame):
self.hidden_tree.bind("<Button-1>", self._toggle_include_status_hidden)
self.hidden_tree_frame.pack_forget() # Initially hidden
# --- Default Reset Frame (initially hidden) ---
self.default_reset_frame = ttk.LabelFrame(
self, text=Msg.STR["default_config_settings"], padding=10)
default_reset_label = ttk.Label(
self.default_reset_frame, text=Msg.STR["default_reset_info"], wraplength=400, justify=tk.CENTER)
default_reset_label.pack(pady=10, expand=True)
# Frame to center the button
default_reset_button_frame = ttk.Frame(self.default_reset_frame)
default_reset_button_frame.pack(pady=5)
reset_button = ttk.Button(
default_reset_button_frame, text=Msg.STR["default_settings"], command=self._reset_to_default_settings)
reset_button.pack() # Centered by default in its own frame
# --- Hard Reset Frame (initially hidden) ---
self.hard_reset_frame = ttk.LabelFrame(
self, text=Msg.STR["full_delete_config_settings"], padding=10)
hard_reset_label = ttk.Label(
self.hard_reset_frame, text=Msg.STR["hard_reset_warning"], wraplength=400, justify=tk.LEFT)
hard_reset_label.pack(pady=10)
self.hard_reset_frame, text=Msg.STR["hard_reset_warning"], wraplength=400, justify=tk.CENTER)
hard_reset_label.pack(pady=10, expand=True)
hard_reset_button_frame = ttk.Frame(self.hard_reset_frame)
hard_reset_button_frame.pack(pady=10)
hard_reset_button_frame.pack(pady=5)
self.delete_now_button = ttk.Button(
hard_reset_button_frame, text=Msg.STR["delete_now"], command=self._perform_hard_reset)
self.delete_now_button.pack(side=tk.LEFT, padx=5)
self.delete_now_button.pack() # Centered by default
self.cancel_hard_reset_button = ttk.Button(
hard_reset_button_frame, text=Msg.STR["cancel"], command=self._toggle_hard_reset_view)
self.cancel_hard_reset_button.pack(side=tk.LEFT, padx=5)
# --- Bottom Cancel Button for Reset View (initially hidden) ---
self.reset_view_cancel_frame = ttk.Frame(self)
self.reset_view_cancel_button = ttk.Button(
self.reset_view_cancel_frame, text=Msg.STR["cancel"], command=self._toggle_hard_reset_view)
self.reset_view_cancel_button.pack(pady=10)
self.hidden_files_visible = False
self.hard_reset_visible = False
# To hold the instance of AdvancedSettingsFrame
self.advanced_settings_frame_instance = None
def _reset_to_default_settings(self):
app_instance = self.master.master.master
header = app_instance.header_frame
try:
self.config_manager.set_setting("backup_destination_path", None)
self.config_manager.set_setting("restore_source_path", None)
self.config_manager.set_setting("restore_destination_path", None)
self.config_manager.remove_setting("backup_animation_type")
self.config_manager.remove_setting("calculation_animation_type")
self.config_manager.remove_setting("force_full_backup")
self.config_manager.remove_setting("force_incremental_backup")
self.config_manager.remove_setting("force_compression")
self.config_manager.remove_setting("force_encryption")
app_instance.update_backup_options_from_config()
AppConfig.generate_and_write_final_exclude_list()
app_logger.log("Settings have been reset to default values.")
self.load_and_display_excludes()
if self.hidden_files_visible:
self._load_hidden_files()
app_instance.destination_path = None
app_instance.start_cancel_button.config(state="disabled")
app_instance.backup_left_canvas_data.clear()
app_instance.backup_right_canvas_data.clear()
app_instance.restore_left_canvas_data.clear()
app_instance.restore_right_canvas_data.clear()
current_source = app_instance.left_canvas_data.get('folder')
if current_source:
self.actions.on_sidebar_button_click(current_source)
app_instance.backup_content_frame.system_backups_frame._load_backup_content()
app_instance.backup_content_frame.user_backups_frame._load_backup_content()
header.show_temporary_message(Msg.STR["default_reset_success"])
except Exception as e:
app_logger.log(f"Error resetting settings: {e}")
header.show_temporary_message(Msg.STR["default_reset_failed"])
def _perform_hard_reset(self):
try:
# First, always attempt to unmount all encrypted drives
@@ -153,7 +208,7 @@ class SettingsFrame(ttk.Frame):
app_logger.log(
"WARNING: Not all encrypted drives could be unmounted. Preventing application closure.")
MessageDialog(
message_type="error", title=Msg.STR["unmount_failed_title"], text=Msg.STR["unmount_failed_message"]).show()
message_type="error", title=Msg.STR["unmount_failed_title"], text=Msg.STR["unmount_failed_message"])
return # Prevent application from closing
# Try to delete the config directory without elevated rights
@@ -179,10 +234,16 @@ class SettingsFrame(ttk.Frame):
self.trees_container.pack_forget()
self.button_frame.pack_forget()
self.bottom_button_frame.pack_forget()
self.default_reset_frame.pack(
fill=tk.X, expand=True, padx=10, pady=5)
self.hard_reset_frame.pack(
fill=tk.BOTH, expand=True, padx=10, pady=5)
fill=tk.X, expand=True, padx=10, pady=5)
self.reset_view_cancel_frame.pack(fill=tk.X, side=tk.BOTTOM)
else:
self.default_reset_frame.pack_forget()
self.hard_reset_frame.pack_forget()
self.reset_view_cancel_frame.pack_forget()
self.button_frame.pack(fill=tk.X, padx=10)
self.trees_container.pack(
fill=tk.BOTH, expand=True, padx=10, pady=5)