Skip to content

Commit 2e40c89

Browse files
committed
fix: improve pathchooser filter defaults and demo UI
1 parent 98969c1 commit 2e40c89

File tree

6 files changed

+202
-118
lines changed

6 files changed

+202
-118
lines changed

examples/demo_pathchooser.py

Lines changed: 164 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,10 @@ def __init__(self, root):
3030
self.y_var = tk.StringVar(value="None")
3131
self.unround_var = tk.BooleanVar(value=True)
3232

33-
# File type checkboxes
34-
self.filetype_vars = {
35-
"Text Files": tk.BooleanVar(value=True),
36-
"Python Files": tk.BooleanVar(value=True),
37-
"Image Files": tk.BooleanVar(value=False),
38-
"Document Files": tk.BooleanVar(value=False),
39-
"Audio Files": tk.BooleanVar(value=False),
40-
"Video Files": tk.BooleanVar(value=False)
41-
}
4233

43-
# File type definitions
34+
# File type definitions (including "All files")
4435
self.filetype_definitions = {
36+
"All Files": ("All files", "*.*"),
4537
"Text Files": ("Text files", "*.txt"),
4638
"Python Files": ("Python files", "*.py"),
4739
"Image Files": ("Image files", "*.png *.jpg *.jpeg *.gif *.bmp *.tiff"),
@@ -50,6 +42,14 @@ def __init__(self, root):
5042
"Video Files": ("Video files", "*.mp4 *.avi *.mov *.mkv *.wmv")
5143
}
5244

45+
# File type variables for checkboxes
46+
self.filetype_vars = {}
47+
for filetype in self.filetype_definitions.keys():
48+
self.filetype_vars[filetype] = tk.BooleanVar(value=filetype in ["Text Files", "Python Files"])
49+
50+
# File type order (for drag and drop)
51+
self.filetype_order = list(self.filetype_definitions.keys())
52+
5353
# Main frame
5454
main_frame = tk.Frame(root)
5555
main_frame.pack(expand=True, fill="both", padx=10, pady=10)
@@ -125,81 +125,83 @@ def __init__(self, root):
125125
)
126126
unround_check.pack(side=tk.LEFT)
127127

128-
# Configuration controls - Row 3 (File Types Checkboxes)
128+
# Configuration controls - Row 3 (Integrated File Types Management)
129129
config_row3 = tk.Frame(config_frame)
130130
config_row3.pack(fill="x", pady=(0, 10))
131131

132132
# File types label
133133
ttk.Label(config_row3, text="File Types:").pack(side=tk.LEFT)
134134

135-
# File types checkboxes frame
135+
# File types checkboxes in simple rows
136136
filetypes_frame = tk.Frame(config_row3)
137137
filetypes_frame.pack(side=tk.LEFT, padx=(5, 0), fill="x", expand=True)
138138

139-
# Create checkboxes in two rows
139+
# Create checkboxes in rows
140140
checkbox_row1 = tk.Frame(filetypes_frame)
141141
checkbox_row1.pack(fill="x", pady=(0, 5))
142142

143143
checkbox_row2 = tk.Frame(filetypes_frame)
144144
checkbox_row2.pack(fill="x")
145145

146-
# Row 1 checkboxes (3 items)
147-
self.text_files_check = ttk.Checkbutton(
148-
checkbox_row1,
149-
text="Text Files",
150-
variable=self.filetype_vars["Text Files"],
151-
command=self.update_filetypes_from_checkboxes
152-
)
153-
self.text_files_check.pack(side=tk.LEFT, padx=(0, 15))
146+
# Create checkboxes for all file types
147+
self.filetype_checkboxes = {}
148+
row1_filetypes = self.filetype_order[:4] # First 4 filetypes
149+
row2_filetypes = self.filetype_order[4:] # Remaining filetypes
150+
151+
# Row 1 checkboxes
152+
for i, filetype in enumerate(row1_filetypes):
153+
var = self.filetype_vars[filetype]
154+
checkbox = ttk.Checkbutton(
155+
checkbox_row1,
156+
text=filetype,
157+
variable=var,
158+
command=self.update_filetypes_from_checkboxes
159+
)
160+
checkbox.pack(side=tk.LEFT, padx=(0, 15))
161+
self.filetype_checkboxes[filetype] = checkbox
162+
163+
# Row 2 checkboxes
164+
for i, filetype in enumerate(row2_filetypes):
165+
var = self.filetype_vars[filetype]
166+
checkbox = ttk.Checkbutton(
167+
checkbox_row2,
168+
text=filetype,
169+
variable=var,
170+
command=self.update_filetypes_from_checkboxes
171+
)
172+
checkbox.pack(side=tk.LEFT, padx=(0, 15))
173+
self.filetype_checkboxes[filetype] = checkbox
154174

155-
self.python_files_check = ttk.Checkbutton(
156-
checkbox_row1,
157-
text="Python Files",
158-
variable=self.filetype_vars["Python Files"],
159-
command=self.update_filetypes_from_checkboxes
160-
)
161-
self.python_files_check.pack(side=tk.LEFT, padx=(0, 15))
175+
# Configuration controls - Row 4 (Selected File Types Order)
176+
config_row4 = tk.Frame(config_frame)
177+
config_row4.pack(fill="x", pady=(0, 10))
162178

163-
self.image_files_check = ttk.Checkbutton(
164-
checkbox_row1,
165-
text="Image Files",
166-
variable=self.filetype_vars["Image Files"],
167-
command=self.update_filetypes_from_checkboxes
168-
)
169-
self.image_files_check.pack(side=tk.LEFT, padx=(0, 15))
170-
171-
# Row 2 checkboxes (3 items)
172-
self.document_files_check = ttk.Checkbutton(
173-
checkbox_row2,
174-
text="Document Files",
175-
variable=self.filetype_vars["Document Files"],
176-
command=self.update_filetypes_from_checkboxes
177-
)
178-
self.document_files_check.pack(side=tk.LEFT, padx=(0, 15))
179+
# Selected file types order label
180+
ttk.Label(config_row4, text="Selected File Types Order:").pack(side=tk.LEFT)
179181

180-
self.audio_files_check = ttk.Checkbutton(
181-
checkbox_row2,
182-
text="Audio Files",
183-
variable=self.filetype_vars["Audio Files"],
184-
command=self.update_filetypes_from_checkboxes
185-
)
186-
self.audio_files_check.pack(side=tk.LEFT, padx=(0, 15))
182+
# Selected file types order management frame
183+
selected_order_frame = tk.Frame(config_row4)
184+
selected_order_frame.pack(side=tk.LEFT, padx=(5, 0), fill="x", expand=True)
187185

188-
self.video_files_check = ttk.Checkbutton(
189-
checkbox_row2,
190-
text="Video Files",
191-
variable=self.filetype_vars["Video Files"],
192-
command=self.update_filetypes_from_checkboxes
193-
)
194-
self.video_files_check.pack(side=tk.LEFT, padx=(0, 15))
186+
# Selected file types listbox
187+
self.selected_filetypes_listbox = tk.Listbox(selected_order_frame, height=3, selectmode=tk.SINGLE)
188+
self.selected_filetypes_listbox.pack(side=tk.LEFT, fill="x", expand=True)
195189

196-
# Configuration controls - Row 4 (Initial Directory)
197-
config_row4 = tk.Frame(config_frame)
198-
config_row4.pack(fill="x", pady=(0, 10))
190+
# Selected order control buttons
191+
selected_order_buttons_frame = tk.Frame(selected_order_frame)
192+
selected_order_buttons_frame.pack(side=tk.LEFT, padx=(5, 0))
193+
194+
ttk.Label(selected_order_buttons_frame, text="Order:").pack(side=tk.TOP, pady=(0, 5))
195+
ttk.Button(selected_order_buttons_frame, text="↑", width=4, command=self.move_selected_filetype_up).pack(side=tk.TOP, pady=(0, 2))
196+
ttk.Button(selected_order_buttons_frame, text="↓", width=4, command=self.move_selected_filetype_down).pack(side=tk.TOP, pady=(0, 2))
197+
198+
# Configuration controls - Row 5 (Initial Directory)
199+
config_row5 = tk.Frame(config_frame)
200+
config_row5.pack(fill="x", pady=(0, 10))
199201

200202
# Initial directory entry
201-
ttk.Label(config_row4, text="Initial Dir:").pack(side=tk.LEFT)
202-
initialdir_entry = ttk.Entry(config_row4, textvariable=self.initialdir_var, width=60)
203+
ttk.Label(config_row5, text="Initial Dir:").pack(side=tk.LEFT)
204+
initialdir_entry = ttk.Entry(config_row5, textvariable=self.initialdir_var, width=60)
203205
initialdir_entry.pack(side=tk.LEFT, padx=(5, 0), fill="x", expand=True)
204206
initialdir_entry.bind("<KeyRelease>", lambda e: self.validate_and_generate_code())
205207

@@ -249,6 +251,9 @@ def __init__(self, root):
249251
self.result_text.pack(side=tk.LEFT, fill="both", expand=True)
250252
scrollbar.pack(side=tk.RIGHT, fill="y")
251253

254+
# Initialize selected filetypes listbox
255+
self.update_selected_filetypes_listbox()
256+
252257
# Initialize dialog type settings and generate initial code
253258
self.update_dialog_type()
254259

@@ -355,9 +360,10 @@ def test_current_settings(self):
355360
try:
356361
# Parse filetypes if provided
357362
filetypes = None
358-
if self.filetypes_var.get():
363+
filetypes_value = self.filetypes_var.get()
364+
if filetypes_value and filetypes_value != "None":
359365
try:
360-
filetypes = eval(self.filetypes_var.get())
366+
filetypes = eval(filetypes_value)
361367
except (SyntaxError, NameError, TypeError, ValueError):
362368
# If evaluation fails, use default
363369
filetypes = None
@@ -503,34 +509,97 @@ def update_dialog_type(self):
503509

504510
def _set_filetype_checkboxes_state(self, state):
505511
"""Set the state of all file type checkboxes."""
506-
checkboxes = [
507-
self.text_files_check,
508-
self.python_files_check,
509-
self.image_files_check,
510-
self.document_files_check,
511-
self.audio_files_check,
512-
self.video_files_check
513-
]
514-
515-
for checkbox in checkboxes:
512+
for checkbox in self.filetype_checkboxes.values():
516513
checkbox.config(state=state)
517514

518515
def update_filetypes_from_checkboxes(self):
519516
"""Update file types from checkbox selections."""
520517
selected_types = []
521518

522-
for filetype, var in self.filetype_vars.items():
523-
if var.get():
519+
# Use the order from filetype_order
520+
for filetype in self.filetype_order:
521+
if filetype in self.filetype_vars and self.filetype_vars[filetype].get():
524522
description, pattern = self.filetype_definitions[filetype]
525523
selected_types.append((description, pattern))
526524

527-
# "All files" is automatically added by PathBrowser when filetypes is None or empty
525+
# Update selected filetypes listbox
526+
self.update_selected_filetypes_listbox()
528527

529528
# Convert to string format
530-
filetypes_str = str(selected_types)
529+
filetypes_str = str(selected_types) if selected_types else "None"
531530
self.filetypes_var.set(filetypes_str)
531+
532532
self.generate_code()
533533

534+
535+
def update_selected_filetypes_listbox(self):
536+
"""Update the selected filetypes listbox display."""
537+
self.selected_filetypes_listbox.delete(0, tk.END)
538+
539+
# Get selected filetypes in order
540+
selected_filetypes = []
541+
for filetype in self.filetype_order:
542+
if filetype in self.filetype_vars and self.filetype_vars[filetype].get():
543+
selected_filetypes.append(filetype)
544+
545+
# Add to listbox
546+
for filetype in selected_filetypes:
547+
self.selected_filetypes_listbox.insert(tk.END, filetype)
548+
549+
def move_selected_filetype_up(self):
550+
"""Move selected filetype up in the selected list."""
551+
selection = self.selected_filetypes_listbox.curselection()
552+
if selection and selection[0] > 0:
553+
index = selection[0]
554+
# Get the selected filetype from the listbox
555+
selected_filetype = self.selected_filetypes_listbox.get(index)
556+
557+
# Find its position in the main order
558+
main_index = self.filetype_order.index(selected_filetype)
559+
prev_index = None
560+
561+
# Find the previous selected filetype in main order
562+
for i in range(main_index - 1, -1, -1):
563+
if self.filetype_order[i] in self.filetype_vars and self.filetype_vars[self.filetype_order[i]].get():
564+
prev_index = i
565+
break
566+
567+
if prev_index is not None:
568+
# Swap in main order
569+
self.filetype_order[main_index], self.filetype_order[prev_index] = \
570+
self.filetype_order[prev_index], self.filetype_order[main_index]
571+
self.update_selected_filetypes_listbox()
572+
# Keep the same item selected
573+
self.selected_filetypes_listbox.selection_set(index - 1)
574+
self.update_filetypes_from_checkboxes()
575+
576+
def move_selected_filetype_down(self):
577+
"""Move selected filetype down in the selected list."""
578+
selection = self.selected_filetypes_listbox.curselection()
579+
if selection and selection[0] < self.selected_filetypes_listbox.size() - 1:
580+
index = selection[0]
581+
# Get the selected filetype from the listbox
582+
selected_filetype = self.selected_filetypes_listbox.get(index)
583+
584+
# Find its position in the main order
585+
main_index = self.filetype_order.index(selected_filetype)
586+
next_index = None
587+
588+
# Find the next selected filetype in main order
589+
for i in range(main_index + 1, len(self.filetype_order)):
590+
if self.filetype_order[i] in self.filetype_vars and self.filetype_vars[self.filetype_order[i]].get():
591+
next_index = i
592+
break
593+
594+
if next_index is not None:
595+
# Swap in main order
596+
self.filetype_order[main_index], self.filetype_order[next_index] = \
597+
self.filetype_order[next_index], self.filetype_order[main_index]
598+
self.update_selected_filetypes_listbox()
599+
# Keep the same item selected
600+
self.selected_filetypes_listbox.selection_set(index + 1)
601+
self.update_filetypes_from_checkboxes()
602+
534603
def generate_code(self):
535604
"""Generate Python code for the current pathchooser configuration."""
536605
dialog_type = self.dialog_type_var.get()
@@ -561,17 +630,22 @@ def generate_code(self):
561630

562631
# Add filetypes for appropriate dialogs (not for asksavefile with fixed filename)
563632
if dialog_type in ["askopenfile", "askopenfiles", "askpath"] and self.filetypes_var.get():
564-
try:
565-
# Try to evaluate the filetypes string
566-
filetypes = eval(self.filetypes_var.get())
567-
if filetypes:
568-
code_lines.append(" filetypes=[")
569-
for filetype in filetypes:
570-
code_lines.append(f" {filetype},")
571-
code_lines.append(" ],")
572-
except (SyntaxError, NameError, TypeError, ValueError):
573-
# If evaluation fails, add as string
574-
code_lines.append(f" filetypes={self.filetypes_var.get()},")
633+
filetypes_value = self.filetypes_var.get()
634+
if filetypes_value == "None":
635+
# Don't add filetypes parameter when None (will use default "All files")
636+
pass
637+
else:
638+
try:
639+
# Try to evaluate the filetypes string
640+
filetypes = eval(filetypes_value)
641+
if filetypes:
642+
code_lines.append(" filetypes=[")
643+
for filetype in filetypes:
644+
code_lines.append(f" {filetype},")
645+
code_lines.append(" ],")
646+
except (SyntaxError, NameError, TypeError, ValueError):
647+
# If evaluation fails, add as string
648+
code_lines.append(f" filetypes={filetypes_value},")
575649

576650
# Add select parameter for askpath
577651
if dialog_type == "askpath" and self.select_var.get() != "file":

tests/test_pathbrowser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def test_pathbrowser_config(self):
5252
assert config.select == "file"
5353
assert config.multiple is False
5454
assert config.initialdir is None
55-
assert config.filetypes == [("All files", "*.*")]
55+
assert config.filetypes is None
5656
assert config.ok_label == "ok"
5757
assert config.cancel_label == "cancel"
5858
assert config.max_cache_size == 1000

tests/test_pathbrowser_core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,8 +1484,8 @@ def test_update_filter_options_no_all_files(self, root):
14841484
# Mock lang.get to return localized text
14851485
with patch("tkface.widget.pathbrowser.core.lang.get", return_value="All files"):
14861486
browser._update_filter_options()
1487-
# Should set to "All files" as default
1488-
browser.filter_combo.set.assert_called_with("All files")
1487+
# Should set to first filetype as default
1488+
browser.filter_combo.set.assert_called_with("Text files (*.txt)")
14891489

14901490
def test_go_up_no_parent(self, root):
14911491
"""Test _go_up method with no parent directory."""

tests/test_pathbrowser_manager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ def test_update_filter_options_fallback_to_first(self, root):
5050
with patch("tkface.widget.pathbrowser.core.lang.get", return_value="All files"):
5151
browser._update_filter_options()
5252

53-
# Should set "All files" as default (since lang.get returns "All files")
54-
browser.filter_combo.set.assert_called_with("All files")
53+
# Should set first filetype as default
54+
browser.filter_combo.set.assert_called_with("Text files (*.txt)")
5555

5656

5757
class TestFileInfoManagerCoverage:

0 commit comments

Comments
 (0)