CTkScrollableDropdownPP 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ """
2
+ CustomTkinter Scrollable Dropdown Menu
3
+ Original Author: Akash Bora (https://github.com/Akascape)
4
+ Modified by: PLauncher-Team (https://github.com/PLauncher-Team) with pagination, search, and grouping features.
5
+
6
+ License: MIT
7
+ Homepage: https://github.com/ваш-репозиторий/CTkScrollableDropdownPP
8
+ """
9
+
10
+ __version__ = '2.0'
11
+
12
+ from .ctk_scrollable_dropdown import CTkScrollableDropdown
@@ -0,0 +1,556 @@
1
+ import customtkinter
2
+ import sys
3
+ import re
4
+
5
+ class CTkScrollableDropdown(customtkinter.CTkToplevel):
6
+
7
+ def __init__(self, attach, x=None, y=None, button_color=None, height: int = 55, width: int = None,
8
+ fg_color=None, button_height: int = 20, justify="center", scrollbar_button_color=None,
9
+ scrollbar=True, scrollbar_button_hover_color=None, frame_border_width=0, values=[],
10
+ command=None, image_values=[], alpha: float = 0.95, frame_corner_radius=20, double_click=False,
11
+ resize=False, frame_border_color=None, text_color=None, autocomplete=False,
12
+ hover_color=None, pagination: bool = True, items_per_page: int = 50,
13
+ groups=None, **button_kwargs):
14
+ super().__init__(master=attach.winfo_toplevel(), takefocus=1)
15
+
16
+ self.group_patterns = None
17
+ self.grouped_values = None
18
+ self.y_pos = None
19
+ self.width_new = None
20
+ self.x_pos = None
21
+ self.focus()
22
+ self.lift()
23
+ self.alpha = alpha
24
+ self.attach = attach
25
+ self.corner = frame_corner_radius
26
+ self.padding = 0
27
+ self.focus_something = False
28
+ self.disable = False
29
+ self.update()
30
+ self.old_kwargs = None
31
+ self.widgets = {}
32
+ self.all_values = values.copy()
33
+ self.values = values.copy()
34
+ self.hide_flag = True
35
+ self.pagination = pagination
36
+ self.items_per_page = items_per_page
37
+ self.current_page = 0
38
+ self.filtered_values = None
39
+ self.current_group = 0
40
+ self.groups = []
41
+ if groups is not None:
42
+ for g in groups:
43
+ if isinstance(g, (list, tuple)) and len(g) >= 2:
44
+ self.groups.append({"name": g[0], "pattern": g[1]})
45
+ else:
46
+ raise ValueError(f"groups must be a list of [name, pattern], got {g!r}")
47
+
48
+ self.group_names = [g["name"] for g in self.groups]
49
+ self.group_patterns = []
50
+ included_values = set()
51
+
52
+ for g in self.groups:
53
+ pattern = g["pattern"]
54
+ if pattern == "__OTHERS__":
55
+ self.group_patterns.append("__OTHERS__")
56
+ else:
57
+ compiled = re.compile(pattern)
58
+ self.group_patterns.append(compiled)
59
+ matched = [v for v in self.all_values if compiled.search(v)]
60
+ included_values.update(matched)
61
+
62
+ self.grouped_values = {}
63
+ for i, pat in enumerate(self.group_patterns):
64
+ if pat == "__OTHERS__":
65
+ self.grouped_values[i] = [v for v in self.all_values if v not in included_values]
66
+ else:
67
+ self.grouped_values[i] = [v for v in self.all_values if pat.search(v)]
68
+
69
+ if sys.platform.startswith("win"):
70
+ self.after(100, lambda: self.overrideredirect(True))
71
+ self.transparent_color = self._apply_appearance_mode(self._fg_color) if hasattr(self, '_fg_color') else "#FFFFFF"
72
+ self.attributes("-transparentcolor", self.transparent_color)
73
+ elif sys.platform.startswith("darwin"):
74
+ self.overrideredirect(True)
75
+ self.transparent_color = 'systemTransparent'
76
+ self.attributes("-transparent", True)
77
+ self.focus_something = True
78
+ else:
79
+ self.overrideredirect(True)
80
+ self.transparent_color = '#000001'
81
+ self.corner = 0
82
+ self.padding = 18
83
+ self.withdraw()
84
+ self.hide_flag = True
85
+ self.attach.bind('<Configure>', lambda e: self._withdraw() if not self.disable else None, add="+")
86
+ self.attach.winfo_toplevel().bind('<Configure>', lambda e: self._withdraw() if not self.disable else None, add="+")
87
+ self.attach.winfo_toplevel().bind("<ButtonPress>", lambda e: self._withdraw() if not self.disable else None, add="+")
88
+ self.bind("<Escape>", lambda e: self._withdraw() if not self.disable else None, add="+")
89
+ self.attributes('-alpha', 0)
90
+ self.fg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if fg_color is None else fg_color
91
+ self.scroll_button_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_color"] if scrollbar_button_color is None else scrollbar_button_color
92
+ self.scroll_hover_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_hover_color"] if scrollbar_button_hover_color is None else scrollbar_button_hover_color
93
+ self.frame_border_color = customtkinter.ThemeManager.theme["CTkFrame"]["border_color"] if frame_border_color is None else frame_border_color
94
+ self.button_color = customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"] if button_color is None else button_color
95
+ self.text_color = customtkinter.ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else text_color
96
+ self.hover_color = customtkinter.ThemeManager.theme["CTkButton"]["hover_color"] if hover_color is None else hover_color
97
+ if not scrollbar:
98
+ self.scroll_button_color = self.fg_color
99
+ self.scroll_hover_color = self.fg_color
100
+ if self.pagination:
101
+ self.search_var = customtkinter.StringVar()
102
+ self.search_var.trace_add('write', lambda *args: self.live_update(self.search_var.get()))
103
+ self.search_entry = customtkinter.CTkEntry(self, textvariable=self.search_var)
104
+ self.search_entry.pack(fill="x")
105
+ if self.groups:
106
+ self.group_frame = customtkinter.CTkFrame(self, fg_color=self.fg_color)
107
+ self.group_frame.pack(fill="x", padx=5)
108
+ self.group_buttons = []
109
+ for idx, name in enumerate(self.group_names):
110
+ btn = customtkinter.CTkButton(
111
+ self.group_frame,
112
+ text=name,
113
+ height=button_height,
114
+ fg_color=self.button_color,
115
+ text_color=self.text_color,
116
+ hover_color=self.hover_color,
117
+ command=lambda i=idx: self.switch_group(i),
118
+ )
119
+ self.group_buttons.append(btn)
120
+ self.group_frame.bind("<Configure>", self._on_group_frame_configure, add="+")
121
+ self.group_button_colors = [btn.cget("fg_color") for btn in self.group_buttons]
122
+ self.frame = customtkinter.CTkScrollableFrame(
123
+ self,
124
+ bg_color=self.transparent_color,
125
+ fg_color=self.fg_color,
126
+ scrollbar_button_hover_color=self.scroll_hover_color,
127
+ corner_radius=self.corner,
128
+ border_width=frame_border_width,
129
+ scrollbar_button_color=self.scroll_button_color,
130
+ border_color=self.frame_border_color
131
+ )
132
+ self.frame._scrollbar.grid_configure(padx=3)
133
+ self.frame.pack(expand=True, fill="both")
134
+ if self.pagination:
135
+ self.button_container = customtkinter.CTkFrame(self.frame, fg_color=self.fg_color)
136
+ self.button_container.pack(expand=True, fill="both")
137
+ self.pagination_frame = customtkinter.CTkFrame(self.frame, fg_color=self.fg_color)
138
+ self.pagination_frame.pack(fill="x", side="bottom")
139
+ else:
140
+ self.button_container = self.frame
141
+ self.dummy_entry = customtkinter.CTkEntry(self.frame, fg_color="transparent", border_width=0, height=1, width=1)
142
+ self.height = height
143
+ self.height_new = height
144
+ self.width = width
145
+ self.command = command
146
+ self.fade = False
147
+ self.resize = resize
148
+ self.autocomplete = autocomplete
149
+ self.var_update = customtkinter.StringVar()
150
+ self.appear = True
151
+ if justify.lower() == "left":
152
+ self.justify = "w"
153
+ elif justify.lower() == "right":
154
+ self.justify = "e"
155
+ else:
156
+ self.justify = "c"
157
+ self.button_height = button_height
158
+ self.image_values = None if len(image_values) != len(self.values) else image_values
159
+ self.resizable(width=False, height=False)
160
+ self.transient(self.master)
161
+ if double_click or isinstance(self.attach, customtkinter.CTkEntry) or isinstance(self.attach, customtkinter.CTkComboBox):
162
+ self.attach.bind('<Double-Button-1>', lambda e: self._iconify(), add="+")
163
+ else:
164
+ self.attach.bind('<Button-1>', lambda e: self._iconify(), add="+")
165
+ if isinstance(self.attach, customtkinter.CTkComboBox):
166
+ self.attach._canvas.tag_bind("right_parts", "<Button-1>", lambda e: self._iconify())
167
+ self.attach._canvas.tag_bind("dropdown_arrow", "<Button-1>", lambda e: self._iconify())
168
+ if self.command is None:
169
+ self.command = self.attach.set
170
+ if isinstance(self.attach, customtkinter.CTkOptionMenu):
171
+ self.attach._canvas.bind("<Button-1>", lambda e: self._iconify())
172
+ self.attach._text_label.bind("<Button-1>", lambda e: self._iconify())
173
+ if self.command is None:
174
+ self.command = self.attach.set
175
+ self.attach.bind("<Destroy>", lambda _: self._destroy(), add="+")
176
+ self.update_idletasks()
177
+ self.x = x
178
+ self.y = y
179
+ if self.autocomplete:
180
+ self.bind_autocomplete()
181
+ self.withdraw()
182
+ self.attributes("-alpha", self.alpha)
183
+ if self.groups:
184
+ self.switch_group(0)
185
+ self._init_buttons()
186
+
187
+ def _on_group_frame_configure(self, event):
188
+ min_btn_width = 100
189
+ cols = max(1, event.width // min_btn_width)
190
+
191
+ for child in self.group_frame.grid_slaves():
192
+ child.grid_forget()
193
+
194
+ for idx, btn in enumerate(self.group_buttons):
195
+ row = idx // cols
196
+ col = idx % cols
197
+ btn.grid(row=row, column=col, sticky="ew", padx=2, pady=2)
198
+
199
+ for c in range(cols):
200
+ self.group_frame.grid_columnconfigure(c, weight=1)
201
+
202
+ def switch_group(self, idx):
203
+ if idx == self.current_group:
204
+ self.values = self.all_values.copy()
205
+ self.current_group = None
206
+ else:
207
+ self.current_group = idx
208
+ self.values = self.grouped_values[idx].copy()
209
+ for i, btn in enumerate(self.group_buttons):
210
+ if i == self.current_group:
211
+ btn.configure(fg_color=self.hover_color)
212
+ else:
213
+ btn.configure(fg_color=self.group_button_colors[i])
214
+ self.filtered_values = None
215
+ self.current_page = 0
216
+ self._init_buttons()
217
+
218
+ def update_buttons(self, values_list):
219
+ for i, value in enumerate(values_list):
220
+ if i in self.widgets:
221
+ btn, _ = self.widgets[i]
222
+ btn.configure(text=value, command=lambda v=value: self._attach_key_press(v))
223
+ btn.pack(fill="x", pady=2, padx=(self.padding, 0))
224
+ self.widgets[i][1] = True
225
+ else:
226
+ btn = customtkinter.CTkButton(
227
+ self.button_container,
228
+ text=value,
229
+ font=("Segoe UI", 12),
230
+ command=lambda v=value: self._attach_key_press(v),
231
+ height=self.button_height,
232
+ fg_color=self.button_color,
233
+ text_color=self.text_color,
234
+ anchor=self.justify,
235
+ hover_color=self.hover_color
236
+ )
237
+ btn.pack(fill="x", pady=2, padx=(self.padding, 0))
238
+ self.widgets[i] = [btn, True]
239
+ i = len(values_list)
240
+ while i in self.widgets:
241
+ btn, visible = self.widgets[i]
242
+ if visible:
243
+ btn.pack_forget()
244
+ self.widgets[i][1] = False
245
+ i += 1
246
+
247
+ def _destroy(self):
248
+ self.after(500, self.destroy_popup)
249
+
250
+ def _withdraw(self):
251
+ if not self.winfo_exists():
252
+ return
253
+ if self.winfo_viewable() and self.hide_flag:
254
+ self.withdraw()
255
+ self.event_generate("<<Closed>>")
256
+ self.hide_flag = True
257
+
258
+ def _update(self, a, b, c):
259
+ self.live_update(self.attach._entry.get())
260
+
261
+ def bind_autocomplete(self):
262
+ def appear(x):
263
+ self.appear = True
264
+ if isinstance(self.attach, customtkinter.CTkComboBox):
265
+ self.attach._entry.configure(textvariable=self.var_update)
266
+ self.attach._entry.bind("<Key>", appear)
267
+ if self.values:
268
+ self.attach.set(self.values[0])
269
+ self.var_update.trace_add('write', self._update)
270
+ if isinstance(self.attach, customtkinter.CTkEntry):
271
+ self.attach.configure(textvariable=self.var_update)
272
+ self.attach.bind("<Key>", appear)
273
+ self.var_update.trace_add('write', self._update)
274
+
275
+ def _init_buttons(self):
276
+ if self.pagination:
277
+ values_to_show = self.values[self.current_page * self.items_per_page:
278
+ (self.current_page + 1) * self.items_per_page]
279
+ self._update_pagination_buttons()
280
+ else:
281
+ values_to_show = self.values
282
+ self.update_buttons(values_to_show)
283
+ self.frame._parent_canvas.yview_moveto(0)
284
+
285
+ def _update_pagination_buttons(self, filtered=False):
286
+ if not self.pagination:
287
+ return
288
+ for child in self.pagination_frame.winfo_children():
289
+ child.destroy()
290
+ values_list = self.filtered_values if (filtered and self.filtered_values is not None) else self.values
291
+ total_pages = (len(values_list) + self.items_per_page - 1) // self.items_per_page
292
+ button_width = 30
293
+ button_padding = 4
294
+ available_width = self.pagination_frame.winfo_width() if self.pagination_frame.winfo_ismapped() else self.width_new
295
+ if available_width < 1:
296
+ available_width = self.attach.winfo_width() if self.width is None else self.width
297
+ max_buttons_per_row = max(1, available_width // (button_width + button_padding))
298
+ total_rows = (total_pages + max_buttons_per_row - 1) // max_buttons_per_row
299
+ for row in range(total_rows):
300
+ for col in range(max_buttons_per_row):
301
+ page_index = row * max_buttons_per_row + col
302
+ if page_index >= total_pages:
303
+ break
304
+ state = "disabled" if page_index == self.current_page else "normal"
305
+ btn = customtkinter.CTkButton(
306
+ self.pagination_frame,
307
+ text=str(page_index + 1),
308
+ width=button_width,
309
+ height=30,
310
+ fg_color=self.button_color,
311
+ text_color=self.text_color,
312
+ command=lambda p=page_index: self._change_page(p),
313
+ state=state
314
+ )
315
+ btn.grid(row=row, column=col, padx=2, pady=2, sticky="ew")
316
+ for col in range(max_buttons_per_row):
317
+ self.pagination_frame.grid_columnconfigure(col, weight=1)
318
+
319
+ def _change_page(self, page_index):
320
+ self.current_page = page_index
321
+ if self.filtered_values is not None:
322
+ values_to_show = self.filtered_values[self.current_page * self.items_per_page:
323
+ (self.current_page + 1) * self.items_per_page]
324
+ else:
325
+ values_to_show = self.values[self.current_page * self.items_per_page:
326
+ (self.current_page + 1) * self.items_per_page]
327
+ self.update_buttons(values_to_show)
328
+ self._update_pagination_buttons(filtered=bool(self.filtered_values))
329
+ self.frame.update_idletasks()
330
+ self.frame._parent_canvas.yview_moveto(len(values_to_show) / self.items_per_page)
331
+
332
+ def destroy_popup(self):
333
+ self.destroy()
334
+ self.disable = True
335
+
336
+ def place_dropdown(self):
337
+ if not self.winfo_exists():
338
+ return
339
+ current_width = self.winfo_width()
340
+ current_height = self.winfo_height()
341
+ current_x_pos = self.winfo_x()
342
+ current_y_pos = self.winfo_y()
343
+ self.x_pos = self.attach.winfo_rootx() if self.x is None else self.x + self.attach.winfo_rootx()
344
+ self.width_new = self.attach.winfo_width() if self.width is None else self.width
345
+ if self.resize:
346
+ displayed_items = len(self.filtered_values) if self.filtered_values is not None else len(self.values)
347
+ if self.pagination:
348
+ displayed_items = min(displayed_items, self.items_per_page)
349
+ base_height = 50 if self.pagination else 35
350
+ items_height = displayed_items * self.button_height
351
+ self.height_new = items_height + base_height
352
+ screen_height = self.winfo_screenheight()
353
+ dropdown_bottom = self.attach.winfo_rooty() + self.height_new
354
+ if dropdown_bottom > screen_height:
355
+ self.y_pos = max(0, self.attach.winfo_rooty() - self.height_new)
356
+ else:
357
+ self.y_pos = self.attach.winfo_rooty() + self.attach.winfo_height()
358
+ if (current_width != self.width_new
359
+ or current_height != self.height_new
360
+ or current_x_pos != self.x_pos
361
+ or current_y_pos != self.y_pos):
362
+ self.geometry(f"{self.width_new}x{self.height_new}+{self.x_pos}+{self.y_pos}")
363
+ self.attributes('-alpha', self.alpha)
364
+ self.update_idletasks()
365
+ if self.pagination:
366
+ self._update_pagination_buttons(filtered=bool(self.filtered_values))
367
+
368
+ def _iconify(self):
369
+ if self.attach.cget("state") == "disabled":
370
+ return
371
+ if self.disable:
372
+ return
373
+ if self.winfo_ismapped():
374
+ self.hide_flag = False
375
+ if self.hide_flag and self.all_values:
376
+ self.event_generate("<<Opened>>")
377
+ self.focus()
378
+ self.hide_flag = False
379
+ self.place_dropdown()
380
+ self._deiconify()
381
+ else:
382
+ self.withdraw()
383
+ self.hide_flag = True
384
+
385
+ def _attach_key_press(self, k):
386
+ if hasattr(self, "search_var"):
387
+ self.search_var.set("")
388
+ self.event_generate("<<Selected>>")
389
+ self.fade = True
390
+ if self.command:
391
+ self.command(k)
392
+ if hasattr(self, "search_var") and self.search_var.get().strip() != "":
393
+ self.filtered_values = None
394
+ self.current_page = 0
395
+ self._init_buttons()
396
+ if self.pagination:
397
+ self.pagination_frame.pack(fill="x", side="bottom")
398
+ self.fade = False
399
+ self.withdraw()
400
+ self.hide_flag = True
401
+
402
+ def live_update(self, string=None):
403
+ if self.disable or self.fade:
404
+ return
405
+ self.frame._parent_canvas.yview_moveto(0)
406
+ if string and string.strip() != "":
407
+ string = string.lower()
408
+ filtered = [val for val in self.values if string in val.lower()]
409
+ self.filtered_values = filtered
410
+ if self.pagination:
411
+ total_pages = (len(filtered) + self.items_per_page - 1) // self.items_per_page
412
+ if self.current_page >= total_pages:
413
+ self.current_page = 0
414
+ self._update_pagination_buttons(filtered=True)
415
+ values_to_show = filtered[self.current_page * self.items_per_page:(self.current_page + 1) * self.items_per_page]
416
+ else:
417
+ values_to_show = filtered
418
+ self.update_buttons(values_to_show)
419
+ if self.pagination:
420
+ self.pagination_frame.pack(fill="x", side="bottom")
421
+ self.place_dropdown()
422
+ else:
423
+ if self.pagination:
424
+ self.pagination_frame.pack(fill="x", side="bottom")
425
+ self.filtered_values = None
426
+ self.current_page = 0
427
+ self._init_buttons()
428
+ self.place_dropdown()
429
+ self.appear = False
430
+
431
+ def insert(self, value, **kwargs):
432
+ index = len(self.values)
433
+ if index in self.widgets:
434
+ btn, _ = self.widgets[index]
435
+ btn.configure(text=value, command=lambda v=value: self._attach_key_press(v))
436
+ btn.pack(fill="x", pady=2, padx=(self.padding, 0))
437
+ self.widgets[index][1] = True
438
+ else:
439
+ btn = customtkinter.CTkButton(self.button_container,
440
+ text=value,
441
+ height=self.button_height,
442
+ fg_color=self.button_color,
443
+ text_color=self.text_color,
444
+ hover_color=self.hover_color,
445
+ anchor=self.justify,
446
+ command=lambda v=value: self._attach_key_press(v),
447
+ width=0
448
+ **kwargs)
449
+ btn.pack(fill="x", pady=2, padx=(self.padding, 0))
450
+ self.widgets[index] = [btn, True]
451
+ self.values.append(value)
452
+ self.all_values.append(value)
453
+ if self.groups:
454
+ self.grouped_values[self.current_group].append(value)
455
+ if self.pagination:
456
+ self._update_pagination_buttons()
457
+
458
+ def _deiconify(self):
459
+ if self.all_values:
460
+ self.deiconify()
461
+
462
+ def popup(self, x=None, y=None):
463
+ self.x = x
464
+ self.y = y
465
+ self.hide_flag = True
466
+ self._iconify()
467
+
468
+ def hide(self):
469
+ self._withdraw()
470
+
471
+ def configure(self, **kwargs):
472
+ if self.old_kwargs == kwargs:
473
+ return
474
+ if "height" in kwargs:
475
+ self.height = kwargs.pop("height")
476
+ self.height_new = self.height
477
+ if "alpha" in kwargs:
478
+ self.alpha = kwargs.pop("alpha")
479
+ if "width" in kwargs:
480
+ self.width = kwargs.pop("width")
481
+ if "fg_color" in kwargs:
482
+ self.frame.configure(fg_color=kwargs.pop("fg_color"))
483
+ if "values" in kwargs:
484
+ self.all_values = kwargs.pop("values")
485
+ self.values = self.all_values.copy()
486
+ self.image_values = None
487
+ self.current_page = 0
488
+ self._init_buttons()
489
+ if hasattr(self, "search_var"):
490
+ self.search_var.set("")
491
+ if getattr(self, "groups", None):
492
+ self.group_patterns = []
493
+ included_values = set()
494
+ for g in self.groups:
495
+ pattern = g["pattern"]
496
+ if pattern == "__OTHERS__":
497
+ self.group_patterns.append("__OTHERS__")
498
+ else:
499
+ compiled = re.compile(pattern)
500
+ self.group_patterns.append(compiled)
501
+ matched = [v for v in self.all_values if compiled.search(v)]
502
+ included_values.update(matched)
503
+ self.grouped_values = {}
504
+ for i, pat in enumerate(self.group_patterns):
505
+ if pat == "__OTHERS__":
506
+ self.grouped_values[i] = [v for v in self.all_values if v not in included_values]
507
+ else:
508
+ self.grouped_values[i] = [v for v in self.all_values if pat.search(v)]
509
+ self.switch_group(self.current_group)
510
+ if "groups" in kwargs:
511
+ raw_groups = kwargs.pop("groups")
512
+ self.groups = []
513
+ for g in raw_groups:
514
+ if isinstance(g, (list, tuple)) and len(g) >= 2:
515
+ self.groups.append({"name": g[0], "pattern": g[1]})
516
+ else:
517
+ raise ValueError(f"groups must be list of [name, pattern], got {g!r}")
518
+
519
+ self.group_names = [g["name"] for g in self.groups]
520
+ self.group_patterns = []
521
+ included_values = set()
522
+
523
+ for g in self.groups:
524
+ pattern = g["pattern"]
525
+ if pattern == "__OTHERS__":
526
+ self.group_patterns.append("__OTHERS__")
527
+ else:
528
+ compiled = re.compile(pattern)
529
+ self.group_patterns.append(compiled)
530
+ matched = [v for v in self.all_values if compiled.search(v)]
531
+ included_values.update(matched)
532
+
533
+ self.grouped_values = {}
534
+ for i, pat in enumerate(self.group_patterns):
535
+ if pat == "__OTHERS__":
536
+ self.grouped_values[i] = [v for v in self.all_values if v not in included_values]
537
+ else:
538
+ self.grouped_values[i] = [v for v in self.all_values if pat.search(v)]
539
+
540
+ self.current_group = 0
541
+ self.switch_group(self.current_group)
542
+ if "image_values" in kwargs:
543
+ self.image_values = kwargs.pop("image_values")
544
+ self.image_values = None if len(self.image_values) != len(self.values) else self.image_values
545
+ if self.image_values is not None:
546
+ for i in range(len(self.values)):
547
+ self.widgets[i][0].configure(image=self.image_values[i])
548
+ if "button_color" in kwargs:
549
+ bc = kwargs.pop("button_color")
550
+ for key in self.widgets:
551
+ self.widgets[key][0].configure(fg_color=bc)
552
+ if self.old_kwargs != kwargs:
553
+ for key in self.widgets:
554
+ self.widgets[key][0].configure(**kwargs)
555
+ self.old_kwargs = kwargs
556
+ self.master.update()
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: CTkScrollableDropdownPP
3
+ Version: 2.0.0
4
+ Summary: Enhanced CTkScrollableDropdown with pagination, search and groups support
5
+ Author: PLauncher-Team, Akash Bora
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/PLauncher-Team/CTkScrollableDropdownPP
8
+ Project-URL: Original, https://github.com/Akascape/CTkScrollableDropdown
9
+ Keywords: customtkinter,dropdown,pagination,search,groups
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: customtkinter>=5.2.0
17
+ Dynamic: license-file
18
+
19
+ # CTkScrollableDropdownPP
20
+ Enhanced CTkScrollableDropdown - A modern, feature-packed upgrade of the original dropdown widget for CustomTkinter with pagination, search, and grouping support.
@@ -0,0 +1,7 @@
1
+ CTkScrollableDropdownPP/__init__.py,sha256=kuJtduu1RVbhreeFYbDwZuG2rErYlT_3uL4hkBnTYVI,405
2
+ CTkScrollableDropdownPP/ctk_scrollable_dropdown.py,sha256=wCq4BGgL1efYke6OZU9k5vKr3AKiejCUAPt22n4p-nE,26123
3
+ ctkscrollabledropdownpp-2.0.0.dist-info/licenses/LICENSE,sha256=zOctrfs4mJPWlhOxsnCedEE498wdCYSbac3fsJK2Ia8,1092
4
+ ctkscrollabledropdownpp-2.0.0.dist-info/METADATA,sha256=qiQayUcPRixgmyLGvKAPEuXElnHUJJDqcPCQ-bKmS_E,909
5
+ ctkscrollabledropdownpp-2.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ ctkscrollabledropdownpp-2.0.0.dist-info/top_level.txt,sha256=lVkeP2JzTs8y9WtPRSuerQlnH_bKp0jiZrEw5q9sNs0,24
7
+ ctkscrollabledropdownpp-2.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 PLauncher Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ CTkScrollableDropdownPP