Files
shared_libs/custom_file_dialog/cfd_view_manager.py

727 lines
31 KiB
Python

import os
import tkinter as tk
from tkinter import ttk
from datetime import datetime
from typing import Optional, List, Tuple, Callable, Any, Dict
# To avoid circular import with custom_file_dialog.py
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from custom_file_dialog import CustomFileDialog
from shared_libs.common_tools import Tooltip
from shared_libs.message import MessageDialog
from .cfd_app_config import CfdConfigManager, LocaleStrings
class ViewManager:
"""Manages the display of files and folders in list and icon views."""
def __init__(self, dialog: 'CustomFileDialog'):
"""
Initializes the ViewManager.
Args:
dialog: The main CustomFileDialog instance.
"""
self.dialog = dialog
def _get_file_info_list(self) -> Tuple[List[Dict], Optional[str], Optional[str]]:
"""
Gets a sorted list of file information dictionaries from the current source.
"""
if self.dialog.current_fs_type == "sftp":
items, error = self.dialog.sftp_manager.list_directory(self.dialog.current_dir)
if error:
return [], error, None
file_info_list = []
import stat
for item in items:
if item.filename in ['.', '..']:
continue
is_dir = stat.S_ISDIR(item.st_mode)
# Manually construct SFTP path to ensure forward slashes
path = f"{self.dialog.current_dir}/{item.filename}".replace("//", "/")
file_info_list.append({
'name': item.filename,
'path': path,
'is_dir': is_dir,
'size': item.st_size,
'modified': item.st_mtime
})
return file_info_list, None, None
else:
try:
items = list(os.scandir(self.dialog.current_dir))
num_items = len(items)
warning_message = None
if num_items > CfdConfigManager.MAX_ITEMS_TO_DISPLAY:
warning_message = f"{LocaleStrings.CFD['showing']} {CfdConfigManager.MAX_ITEMS_TO_DISPLAY} {LocaleStrings.CFD['of']} {num_items} {LocaleStrings.CFD['entries']}."
items = items[:CfdConfigManager.MAX_ITEMS_TO_DISPLAY]
items.sort(key=lambda e: (not e.is_dir(), e.name.lower()))
file_info_list = []
for item in items:
try:
stat_result = item.stat()
file_info_list.append({
'name': item.name,
'path': item.path,
'is_dir': item.is_dir(),
'size': stat_result.st_size,
'modified': stat_result.st_mtime
})
except (FileNotFoundError, PermissionError):
continue
return file_info_list, None, warning_message
except PermissionError:
return ([], LocaleStrings.CFD["access_denied"], None)
except FileNotFoundError:
return ([], LocaleStrings.CFD["directory_not_found"], None)
def populate_files(self, item_to_rename: Optional[str] = None, item_to_select: Optional[str] = None) -> None:
"""
Populates the main file display area.
"""
self._unbind_mouse_wheel_events()
for widget in self.dialog.widget_manager.file_list_frame.winfo_children():
widget.destroy()
self.dialog.widget_manager.path_entry.delete(0, tk.END)
self.dialog.widget_manager.path_entry.insert(
0, self.dialog.current_dir)
self.dialog.result = None
self.dialog.selected_item_frames.clear()
self.dialog.update_selection_info()
if self.dialog.view_mode.get() == "list":
self.populate_list_view(item_to_rename, item_to_select)
else:
self.populate_icon_view(item_to_rename, item_to_select)
def _get_folder_content_count(self, folder_path: str) -> Optional[int]:
"""
Counts the number of items in a given folder, supporting both local and SFTP.
"""
try:
if self.dialog.current_fs_type == "sftp":
if not self.dialog.sftp_manager.path_is_dir(folder_path):
return None
items, error = self.dialog.sftp_manager.list_directory(folder_path)
if error:
return None
else:
if not os.path.isdir(folder_path) or not os.access(folder_path, os.R_OK):
return None
items = os.listdir(folder_path)
if not self.dialog.show_hidden_files.get():
# For SFTP, items are attrs, for local they are strings
if self.dialog.current_fs_type == "sftp":
items = [item for item in items if not item.filename.startswith('.')]
else:
items = [item for item in items if not item.startswith('.')]
return len(items)
except (PermissionError, FileNotFoundError):
return None
def _get_item_path_from_widget(self, widget: tk.Widget) -> Optional[str]:
"""
Traverses up the widget hierarchy to find the item_path attribute.
"""
while widget and not hasattr(widget, 'item_path'):
widget = widget.master
return getattr(widget, 'item_path', None)
def _is_dir(self, path: str) -> bool:
"""Checks if a given path is a directory, supporting both local and SFTP."""
if self.dialog.current_fs_type == 'sftp':
for item in self.dialog.all_items:
if item['path'] == path:
return item['is_dir']
return False
else:
return os.path.isdir(path)
def _handle_icon_click(self, event: tk.Event) -> None:
"""Handles a single click on an icon view item."""
item_path = self._get_item_path_from_widget(event.widget)
if item_path:
item_frame = event.widget
while not hasattr(item_frame, 'item_path'):
item_frame = item_frame.master
self.on_item_select(item_path, item_frame, event)
def _handle_icon_double_click(self, event: tk.Event) -> None:
"""Handles a double click on an icon view item."""
item_path = self._get_item_path_from_widget(event.widget)
if item_path:
self._handle_item_double_click(item_path)
def _handle_icon_context_menu(self, event: tk.Event) -> None:
"""Handles a context menu request on an icon view item."""
item_path = self._get_item_path_from_widget(event.widget)
if item_path:
self.dialog.file_op_manager._show_context_menu(event, item_path)
def _handle_icon_rename_request(self, event: tk.Event) -> None:
"""Handles a rename request on an icon view item."""
item_path = self._get_item_path_from_widget(event.widget)
if item_path:
item_frame = event.widget
while not hasattr(item_frame, 'item_path'):
item_frame = item_frame.master
self.dialog.file_op_manager.on_rename_request(
event, item_path, item_frame)
def populate_icon_view(self, item_to_rename: Optional[str] = None, item_to_select: Optional[str] = None) -> None:
"""
Populates the file display with items in an icon grid layout.
"""
self.dialog.all_items, error, warning = self._get_file_info_list()
self.dialog.currently_loaded_count = 0
self.dialog.icon_canvas = tk.Canvas(self.dialog.widget_manager.file_list_frame,
highlightthickness=0, bg=self.dialog.style_manager.icon_bg_color)
v_scrollbar = ttk.Scrollbar(
self.dialog.widget_manager.file_list_frame, orient="vertical", command=self.dialog.icon_canvas.yview)
self.dialog.icon_canvas.pack(side="left", fill="both", expand=True)
self.dialog.icon_canvas.focus_set()
v_scrollbar.pack(side="right", fill="y")
container_frame = ttk.Frame(
self.dialog.icon_canvas, style="Content.TFrame")
self.dialog.icon_canvas.create_window(
(0, 0), window=container_frame, anchor="nw")
container_frame.bind("<Configure>", lambda e: self.dialog.icon_canvas.configure(
scrollregion=self.dialog.icon_canvas.bbox("all")))
def _on_mouse_wheel(event: tk.Event) -> None:
if event.num == 4:
delta = -1
elif event.num == 5:
delta = 1
else:
delta = -1 * int(event.delta / 120)
self.dialog.icon_canvas.yview_scroll(delta, "units")
if self.dialog.currently_loaded_count < len(self.dialog.all_items) and self.dialog.icon_canvas.yview()[1] > 0.9:
self._load_more_items_icon_view(
container_frame, _on_mouse_wheel)
for widget in [self.dialog.icon_canvas, container_frame]:
widget.bind("<MouseWheel>", _on_mouse_wheel)
widget.bind("<Button-4>", _on_mouse_wheel)
widget.bind("<Button-5>", _on_mouse_wheel)
if warning:
self.dialog.widget_manager.search_status_label.config(text=warning)
if error:
ttk.Label(container_frame, text=error).pack(pady=20)
return
widget_to_focus = None
while self.dialog.currently_loaded_count < len(self.dialog.all_items):
widget_to_focus = self._load_more_items_icon_view(
container_frame, _on_mouse_wheel, item_to_rename, item_to_select)
if widget_to_focus:
break
if not (item_to_rename or item_to_select):
break
if widget_to_focus:
def scroll_to_widget() -> None:
self.dialog.update_idletasks()
if not widget_to_focus.winfo_exists():
return
y = widget_to_focus.winfo_y()
canvas_height = self.dialog.icon_canvas.winfo_height()
scroll_region = self.dialog.icon_canvas.bbox("all")
if not scroll_region:
return
scroll_height = scroll_region[3]
if scroll_height > canvas_height:
fraction = y / scroll_height
self.dialog.icon_canvas.yview_moveto(fraction)
self.dialog.after(100, scroll_to_widget)
def _load_more_items_icon_view(self, container: ttk.Frame, scroll_handler: Callable, item_to_rename: Optional[str] = None, item_to_select: Optional[str] = None) -> Optional[ttk.Frame]:
"""
Loads a batch of items into the icon view.
"""
start_index = self.dialog.currently_loaded_count
end_index = min(len(self.dialog.all_items), start_index +
self.dialog.items_to_load_per_batch)
if start_index >= end_index:
return None
item_width, item_height = 125, 100
frame_width = self.dialog.widget_manager.file_list_frame.winfo_width()
col_count = max(1, frame_width // item_width - 1)
row = start_index // col_count if col_count > 0 else 0
col = start_index % col_count if col_count > 0 else 0
widget_to_focus = None
for i in range(start_index, end_index):
file_info = self.dialog.all_items[i]
name = file_info['name']
path = file_info['path']
is_dir = file_info['is_dir']
if not self.dialog.show_hidden_files.get() and name.startswith('.'):
continue
if not is_dir and not self.dialog._matches_filetype(name):
continue
item_frame = ttk.Frame(
container, width=item_width, height=item_height, style="Item.TFrame")
item_frame.grid(row=row, column=col, padx=5, ipadx=25, pady=5)
item_frame.grid_propagate(False)
item_frame.item_path = path
if name == item_to_rename:
self.dialog.file_op_manager.start_rename(item_frame, path)
widget_to_focus = item_frame
else:
icon = self.dialog.icon_manager.get_icon(
'folder_large') if is_dir else self.dialog.get_file_icon(name, 'large')
icon_label = ttk.Label(
item_frame, image=icon, style="Icon.TLabel")
icon_label.pack(pady=(10, 5))
name_label = ttk.Label(item_frame, text=self.dialog.shorten_text(
name, 14), anchor="center", style="Item.TLabel")
name_label.pack(fill="x", expand=True)
Tooltip(item_frame, name)
for widget in [item_frame, icon_label, name_label]:
widget.bind("<Double-Button-1>", lambda e,
p=path: self._handle_item_double_click(p))
widget.bind("<Button-1>", lambda e, p=path,
f=item_frame: self.on_item_select(p, f, e))
widget.bind("<ButtonRelease-3>", lambda e,
p=path: self.dialog.file_op_manager._show_context_menu(e, p))
widget.bind("<F2>", lambda e, p=path,
f=item_frame: self.dialog.file_op_manager.on_rename_request(e, p, f))
widget.bind("<MouseWheel>", scroll_handler)
widget.bind("<Button-4>", scroll_handler)
widget.bind("<Button-5>", scroll_handler)
if name == item_to_select:
self.on_item_select(path, item_frame)
widget_to_focus = item_frame
if col_count > 0:
col = (col + 1) % col_count
if col == 0:
row += 1
else:
row += 1
self.dialog.currently_loaded_count = end_index
return widget_to_focus
def populate_list_view(self, item_to_rename: Optional[str] = None, item_to_select: Optional[str] = None) -> None:
"""
Populates the file display with items in a list (Treeview) layout.
"""
self.dialog.all_items, error, warning = self._get_file_info_list()
self.dialog.currently_loaded_count = 0
tree_frame = ttk.Frame(self.dialog.widget_manager.file_list_frame)
tree_frame.pack(fill='both', expand=True)
tree_frame.grid_rowconfigure(0, weight=1)
tree_frame.grid_columnconfigure(0, weight=1)
columns = ("size", "type", "modified")
self.dialog.tree = ttk.Treeview(
tree_frame, columns=columns, show="tree headings")
if self.dialog.dialog_mode == 'multi':
self.dialog.tree.config(selectmode="extended")
self.dialog.tree.heading(
"#0", text=LocaleStrings.VIEW["name"], anchor="w")
self.dialog.tree.column("#0", anchor="w", width=250, stretch=True)
self.dialog.tree.heading(
"size", text=LocaleStrings.VIEW["size"], anchor="e")
self.dialog.tree.column("size", anchor="e", width=120, stretch=False)
self.dialog.tree.heading(
"type", text=LocaleStrings.VIEW["type"], anchor="w")
self.dialog.tree.column("type", anchor="w", width=120, stretch=False)
self.dialog.tree.heading(
"modified", text=LocaleStrings.VIEW["date_modified"], anchor="w")
self.dialog.tree.column("modified", anchor="w",
width=160, stretch=False)
v_scrollbar = ttk.Scrollbar(
tree_frame, orient="vertical", command=self.dialog.tree.yview)
h_scrollbar = ttk.Scrollbar(
tree_frame, orient="horizontal", command=self.dialog.tree.xview)
self.dialog.tree.configure(yscrollcommand=v_scrollbar.set,
xscrollcommand=h_scrollbar.set)
self.dialog.tree.grid(row=0, column=0, sticky='nsew')
self.dialog.tree.focus_set()
v_scrollbar.grid(row=0, column=1, sticky='ns')
h_scrollbar.grid(row=1, column=0, sticky='ew')
def _on_scroll(*args: Any) -> None:
if self.dialog.currently_loaded_count < len(self.dialog.all_items) and self.dialog.tree.yview()[1] > 0.9:
self._load_more_items_list_view()
v_scrollbar.set(*args)
self.dialog.tree.configure(yscrollcommand=_on_scroll)
self.dialog.tree.bind("<Double-1>", self.on_list_double_click)
self.dialog.tree.bind("<<TreeviewSelect>>", self.on_list_select)
self.dialog.tree.bind(
"<F2>", self.dialog.file_op_manager.on_rename_request)
self.dialog.tree.bind("<ButtonRelease-3>", self.on_list_context_menu)
if warning:
self.dialog.widget_manager.search_status_label.config(text=warning)
if error:
self.dialog.tree.insert("", "end", text=error, values=())
return
while self.dialog.currently_loaded_count < len(self.dialog.all_items):
item_found = self._load_more_items_list_view(
item_to_rename, item_to_select)
if item_found:
break
if not (item_to_rename or item_to_select):
break
def _load_more_items_list_view(self, item_to_rename: Optional[str] = None, item_to_select: Optional[str] = None) -> bool:
"""
Loads a batch of items into the list view.
"""
start_index = self.dialog.currently_loaded_count
end_index = min(len(self.dialog.all_items), start_index +
self.dialog.items_to_load_per_batch)
if start_index >= end_index:
return False
item_found = False
for i in range(start_index, end_index):
file_info = self.dialog.all_items[i]
name = file_info['name']
path = file_info['path']
is_dir = file_info['is_dir']
if not self.dialog.show_hidden_files.get() and name.startswith('.'):
continue
if not is_dir and not self.dialog._matches_filetype(name):
continue
try:
modified_time = datetime.fromtimestamp(
file_info['modified']).strftime('%d.%m.%Y %H:%M')
if is_dir:
icon, file_type, size = self.dialog.icon_manager.get_icon(
'folder_small'), LocaleStrings.FILE["folder"], ""
else:
icon, file_type, size = self.dialog.get_file_icon(
name, 'small'), LocaleStrings.FILE["file"], self.dialog._format_size(file_info['size'])
item_id = self.dialog.tree.insert("", "end", text=f" {name}", image=icon, values=(
size, file_type, modified_time))
self.dialog.item_path_map[item_id] = path # Store path for later retrieval
if name == item_to_rename:
self.dialog.tree.selection_set(item_id)
self.dialog.tree.focus(item_id)
self.dialog.tree.see(item_id)
self.dialog.file_op_manager.start_rename(item_id, path)
item_found = True
elif name == item_to_select:
self.dialog.tree.selection_set(item_id)
self.dialog.tree.focus(item_id)
self.dialog.tree.see(item_id)
item_found = True
except (FileNotFoundError, PermissionError):
continue
self.dialog.currently_loaded_count = end_index
return item_found
def on_item_select(self, path: str, item_frame: ttk.Frame, event: Optional[tk.Event] = None) -> None:
"""
Handles the selection of an item in the icon view.
"""
if self.dialog.dialog_mode == 'dir' and not self._is_dir(path):
return
if self.dialog.dialog_mode == 'multi':
ctrl_pressed = (event.state & 0x4) != 0 if event else False
if ctrl_pressed:
if item_frame in self.dialog.selected_item_frames:
item_frame.state(['!selected'])
for child in item_frame.winfo_children():
if isinstance(child, ttk.Label):
child.state(['!selected'])
self.dialog.selected_item_frames.remove(item_frame)
else:
item_frame.state(['selected'])
for child in item_frame.winfo_children():
if isinstance(child, ttk.Label):
child.state(['selected'])
self.dialog.selected_item_frames.append(item_frame)
else:
for f in self.dialog.selected_item_frames:
f.state(['!selected'])
for child in f.winfo_children():
if isinstance(child, ttk.Label):
child.state(['!selected'])
self.dialog.selected_item_frames.clear()
item_frame.state(['selected'])
for child in item_frame.winfo_children():
if isinstance(child, ttk.Label):
child.state(['selected'])
self.dialog.selected_item_frames.append(item_frame)
selected_paths = [frame.item_path for frame in self.dialog.selected_item_frames]
self.dialog.result = selected_paths
self.dialog.update_selection_info()
else: # Single selection mode
if hasattr(self.dialog, 'selected_item_frame') and self.dialog.selected_item_frame.winfo_exists():
self.dialog.selected_item_frame.state(['!selected'])
for child in self.dialog.selected_item_frame.winfo_children():
if isinstance(child, ttk.Label):
child.state(['!selected'])
item_frame.state(['selected'])
for child in item_frame.winfo_children():
if isinstance(child, ttk.Label):
child.state(['selected'])
self.dialog.selected_item_frame = item_frame
self.dialog.update_selection_info(path)
self.dialog.search_manager.show_search_ready()
def on_list_select(self, event: tk.Event) -> None:
"""
Handles the selection of an item in the list view.
"""
selections = self.dialog.tree.selection()
if not selections:
self.dialog.result = [] if self.dialog.dialog_mode == 'multi' else None
self.dialog.update_selection_info()
return
if self.dialog.dialog_mode == 'multi':
selected_paths = []
for item_id in selections:
path = self.dialog.item_path_map.get(item_id)
if path:
selected_paths.append(path)
self.dialog.result = selected_paths
self.dialog.update_selection_info()
else:
item_id = selections[0]
path = self.dialog.item_path_map.get(item_id)
if not path:
return
if self.dialog.dialog_mode == 'dir' and not self._is_dir(path):
self.dialog.result = None
self.dialog.tree.selection_remove(item_id)
self.dialog.update_selection_info()
return
self.dialog.update_selection_info(path)
def on_list_context_menu(self, event: tk.Event) -> str:
"""
Shows the context menu for a list view item.
"""
iid = self.dialog.tree.identify_row(event.y)
if not iid:
return "break"
self.dialog.tree.selection_set(iid)
path = self.dialog.item_path_map.get(iid)
if path:
self.dialog.file_op_manager._show_context_menu(event, path)
return "break"
def _handle_item_double_click(self, path: str) -> None:
"""
Handles the logic for a double-click on any item, regardless of view.
"""
if self._is_dir(path):
if self.dialog.dialog_mode == 'dir':
has_subdirs = False
try:
if self.dialog.current_fs_type == "sftp":
import stat
items, _ = self.dialog.sftp_manager.list_directory(path)
for item in items:
if item.filename not in ['.', '..'] and stat.S_ISDIR(item.st_mode):
has_subdirs = True
break
else:
for item in os.listdir(path):
if os.path.isdir(os.path.join(path, item)) and not item.startswith('.'):
has_subdirs = True
break
except OSError:
self.dialog.navigation_manager.navigate_to(path)
return
if has_subdirs:
self.dialog.navigation_manager.navigate_to(path)
else:
dialog = MessageDialog(
master=self.dialog,
message_type="ask",
title=LocaleStrings.CFD["select_or_enter_title"],
text=LocaleStrings.CFD["select_or_enter_prompt"].format(folder_name=os.path.basename(path)),
buttons=[
LocaleStrings.CFD["select_button"],
LocaleStrings.CFD["enter_button"],
LocaleStrings.CFD["cancel_button"],
]
)
choice = dialog.show()
if choice is True:
self.dialog.result = path
self.dialog.on_open()
elif choice is False:
self.dialog.navigation_manager.navigate_to(path)
else:
self.dialog.navigation_manager.navigate_to(path)
elif self.dialog.dialog_mode in ["open", "multi"]:
self.dialog.result = path
self.dialog.on_open()
elif self.dialog.dialog_mode == "save":
self.dialog.widget_manager.filename_entry.delete(0, tk.END)
self.dialog.widget_manager.filename_entry.insert(
0, os.path.basename(path))
self.dialog.on_save()
def on_list_double_click(self, event: tk.Event) -> None:
"""
Handles a double-click on a list view item.
"""
selection = self.dialog.tree.selection()
if not selection:
return
item_id = selection[0]
path = self.dialog.item_path_map.get(item_id)
if path:
self._handle_item_double_click(path)
def _select_file_in_view(self, filename: str) -> None:
"""
Programmatically selects a file in the current view.
"""
is_sftp = self.dialog.current_fs_type == "sftp"
if self.dialog.view_mode.get() == "list":
for item_id, path in self.dialog.item_path_map.items():
basename = path.split('/')[-1] if is_sftp else os.path.basename(path)
if basename == filename:
self.dialog.tree.selection_set(item_id)
self.dialog.tree.focus(item_id)
self.dialog.tree.see(item_id)
break
elif self.dialog.view_mode.get() == "icons":
if not hasattr(self.dialog, 'icon_canvas') or not self.dialog.icon_canvas.winfo_exists():
return
container_frame = self.dialog.icon_canvas.winfo_children()[0]
if is_sftp:
# Ensure forward slashes for SFTP paths
target_path = f"{self.dialog.current_dir}/{filename}".replace("//", "/")
else:
target_path = os.path.join(self.dialog.current_dir, filename)
for widget in container_frame.winfo_children():
if hasattr(widget, 'item_path') and widget.item_path == target_path:
self.on_item_select(widget.item_path, widget)
def scroll_to_widget() -> None:
self.dialog.update_idletasks()
if not widget.winfo_exists():
return
y = widget.winfo_y()
canvas_height = self.dialog.icon_canvas.winfo_height()
scroll_region = self.dialog.icon_canvas.bbox("all")
if not scroll_region:
return
scroll_height = scroll_region[3]
if scroll_height > canvas_height:
fraction = y / scroll_height
self.dialog.icon_canvas.yview_moveto(fraction)
self.dialog.after(100, scroll_to_widget)
break
def _update_view_mode_buttons(self) -> None:
"""Updates the visual state of the view mode toggle buttons."""
if self.dialog.view_mode.get() == "icons":
self.dialog.widget_manager.icon_view_button.configure(
style="Header.TButton.Active.Round")
self.dialog.widget_manager.list_view_button.configure(
style="Header.TButton.Borderless.Round")
else:
self.dialog.widget_manager.list_view_button.configure(
style="Header.TButton.Active.Round")
self.dialog.widget_manager.icon_view_button.configure(
style="Header.TButton.Borderless.Round")
def set_icon_view(self) -> None:
"""Switches to icon view and repopulates the files."""
self.dialog.view_mode.set("icons")
self._update_view_mode_buttons()
self.populate_files()
def set_list_view(self) -> None:
"""Switches to list view and repopulates the files."""
self.dialog.view_mode.set("list")
self._update_view_mode_buttons()
self.populate_files()
def toggle_hidden_files(self) -> None:
"""Toggles the visibility of hidden files and refreshes the view."""
self.dialog.show_hidden_files.set(
not self.dialog.show_hidden_files.get())
if self.dialog.show_hidden_files.get():
self.dialog.widget_manager.hidden_files_button.config(
image=self.dialog.icon_manager.get_icon('unhide'))
Tooltip(self.dialog.widget_manager.hidden_files_button,
LocaleStrings.UI["hide_hidden_files"])
else:
self.dialog.widget_manager.hidden_files_button.config(
image=self.dialog.icon_manager.get_icon('hide'))
Tooltip(self.dialog.widget_manager.hidden_files_button,
LocaleStrings.UI["show_hidden_files"])
self.populate_files()
def on_filter_change(self, event: tk.Event) -> None:
"""Handles a change in the file type filter combobox."""
selected_desc = self.dialog.widget_manager.filter_combobox.get()
for desc, pattern in self.dialog.filetypes:
if desc == selected_desc:
self.dialog.current_filter_pattern = pattern
break
self.populate_files()
def _unbind_mouse_wheel_events(self) -> None:
"""Unbinds all mouse wheel events from the dialog."""
self.dialog.unbind_all("<MouseWheel>")
self.dialog.unbind_all("<Button-4>")
self.dialog.unbind_all("<Button-5>")