scustomtkinter 0.2.1__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.
Files changed (101) hide show
  1. scustomtkinter/__init__.py +81 -0
  2. scustomtkinter/assets/images/error.png +0 -0
  3. scustomtkinter/assets/images/info.png +0 -0
  4. scustomtkinter/assets/images/warning.png +0 -0
  5. scustomtkinter/assets/sCTkThemes.json +1963 -0
  6. scustomtkinter/license_morecustomtkinterwidgets.txt +22 -0
  7. scustomtkinter/sctk_button_primary.py +366 -0
  8. scustomtkinter/sctk_button_secondary.py +354 -0
  9. scustomtkinter/sctk_button_tertiary.py +375 -0
  10. scustomtkinter/sctk_checkbox.py +270 -0
  11. scustomtkinter/sctk_combobox.py +155 -0
  12. scustomtkinter/sctk_core.py +15 -0
  13. scustomtkinter/sctk_dial.py +1472 -0
  14. scustomtkinter/sctk_dialog.py +701 -0
  15. scustomtkinter/sctk_dialog_toplevel.py +268 -0
  16. scustomtkinter/sctk_entry_primary.py +400 -0
  17. scustomtkinter/sctk_entry_secondary.py +342 -0
  18. scustomtkinter/sctk_file_explorer.py +879 -0
  19. scustomtkinter/sctk_frame.py +210 -0
  20. scustomtkinter/sctk_frame_labeled_primary.py +667 -0
  21. scustomtkinter/sctk_frame_labeled_secondary.py +598 -0
  22. scustomtkinter/sctk_frequency_display.py +636 -0
  23. scustomtkinter/sctk_label_primary.py +283 -0
  24. scustomtkinter/sctk_label_secondary.py +275 -0
  25. scustomtkinter/sctk_label_tertiary.py +278 -0
  26. scustomtkinter/sctk_messagebox.py +275 -0
  27. scustomtkinter/sctk_notebook.py +1103 -0
  28. scustomtkinter/sctk_optionmenu_border_mixin.py +203 -0
  29. scustomtkinter/sctk_optionmenu_primary.py +378 -0
  30. scustomtkinter/sctk_optionmenu_secondary.py +346 -0
  31. scustomtkinter/sctk_path_chooser.py +516 -0
  32. scustomtkinter/sctk_progress_bar.py +274 -0
  33. scustomtkinter/sctk_radiobutton.py +360 -0
  34. scustomtkinter/sctk_scroll_mixin.py +546 -0
  35. scustomtkinter/sctk_scrollable_frame.py +795 -0
  36. scustomtkinter/sctk_scrollbar.py +313 -0
  37. scustomtkinter/sctk_segmentedbutton.py +424 -0
  38. scustomtkinter/sctk_selector.py +581 -0
  39. scustomtkinter/sctk_separator.py +308 -0
  40. scustomtkinter/sctk_slider.py +264 -0
  41. scustomtkinter/sctk_smeter.py +405 -0
  42. scustomtkinter/sctk_smeter_bar.py +557 -0
  43. scustomtkinter/sctk_spinbox.py +664 -0
  44. scustomtkinter/sctk_switch.py +463 -0
  45. scustomtkinter/sctk_tableview.py +869 -0
  46. scustomtkinter/sctk_tabview.py +409 -0
  47. scustomtkinter/sctk_textbox_primary.py +291 -0
  48. scustomtkinter/sctk_textbox_secondary.py +225 -0
  49. scustomtkinter/sctk_toplevel.py +153 -0
  50. scustomtkinter/themeable_widget.py +938 -0
  51. scustomtkinter-0.2.1.dist-info/METADATA +336 -0
  52. scustomtkinter-0.2.1.dist-info/RECORD +101 -0
  53. scustomtkinter-0.2.1.dist-info/WHEEL +5 -0
  54. scustomtkinter-0.2.1.dist-info/entry_points.txt +2 -0
  55. scustomtkinter-0.2.1.dist-info/licenses/LICENSE +21 -0
  56. scustomtkinter-0.2.1.dist-info/licenses/scustomtkinter/license_morecustomtkinterwidgets.txt +22 -0
  57. scustomtkinter-0.2.1.dist-info/top_level.txt +2 -0
  58. scustomtkinter_pygubu/__init__.py +1 -0
  59. scustomtkinter_pygubu/designer/__init__.py +0 -0
  60. scustomtkinter_pygubu/designer/plugin.py +1854 -0
  61. scustomtkinter_pygubu/designer/properties.py +247 -0
  62. scustomtkinter_pygubu/sCTkButtonPrimarybo.py +46 -0
  63. scustomtkinter_pygubu/sCTkButtonSecondarybo.py +45 -0
  64. scustomtkinter_pygubu/sCTkButtonTertiarybo.py +44 -0
  65. scustomtkinter_pygubu/sCTkCheckBoxbo.py +45 -0
  66. scustomtkinter_pygubu/sCTkComboBoxbo.py +45 -0
  67. scustomtkinter_pygubu/sCTkCorebo.py +120 -0
  68. scustomtkinter_pygubu/sCTkDialbo.py +371 -0
  69. scustomtkinter_pygubu/sCTkDialogbo.py +397 -0
  70. scustomtkinter_pygubu/sCTkEntryPrimarybo.py +100 -0
  71. scustomtkinter_pygubu/sCTkEntrySecondarybo.py +98 -0
  72. scustomtkinter_pygubu/sCTkFileExplorerbo.py +189 -0
  73. scustomtkinter_pygubu/sCTkFrameLabeledPrimarybo.py +69 -0
  74. scustomtkinter_pygubu/sCTkFrameLabeledSecondarybo.py +71 -0
  75. scustomtkinter_pygubu/sCTkFramebo.py +46 -0
  76. scustomtkinter_pygubu/sCTkFrequencyDisplaybo.py +175 -0
  77. scustomtkinter_pygubu/sCTkLabelPrimarybo.py +45 -0
  78. scustomtkinter_pygubu/sCTkLabelSecondarybo.py +44 -0
  79. scustomtkinter_pygubu/sCTkLabelTertiarybo.py +45 -0
  80. scustomtkinter_pygubu/sCTkNotebookbo.py +392 -0
  81. scustomtkinter_pygubu/sCTkOptionMenuPrimarybo.py +45 -0
  82. scustomtkinter_pygubu/sCTkOptionMenuSecondarybo.py +39 -0
  83. scustomtkinter_pygubu/sCTkPathChooserbo.py +204 -0
  84. scustomtkinter_pygubu/sCTkProgressBarbo.py +45 -0
  85. scustomtkinter_pygubu/sCTkRadioButtonbo.py +45 -0
  86. scustomtkinter_pygubu/sCTkSMeterBarbo.py +153 -0
  87. scustomtkinter_pygubu/sCTkSMeterbo.py +101 -0
  88. scustomtkinter_pygubu/sCTkScrollableFramebo.py +66 -0
  89. scustomtkinter_pygubu/sCTkScrollbarbo.py +48 -0
  90. scustomtkinter_pygubu/sCTkSegmentedButtonbo.py +47 -0
  91. scustomtkinter_pygubu/sCTkSelectorbo.py +167 -0
  92. scustomtkinter_pygubu/sCTkSeparatorbo.py +141 -0
  93. scustomtkinter_pygubu/sCTkSliderbo.py +45 -0
  94. scustomtkinter_pygubu/sCTkSpinboxbo.py +200 -0
  95. scustomtkinter_pygubu/sCTkSwitchbo.py +45 -0
  96. scustomtkinter_pygubu/sCTkTableviewbo.py +188 -0
  97. scustomtkinter_pygubu/sCTkTabviewbo.py +226 -0
  98. scustomtkinter_pygubu/sCTkTextboxPrimarybo.py +45 -0
  99. scustomtkinter_pygubu/sCTkTextboxSecondarybo.py +45 -0
  100. scustomtkinter_pygubu/sCTkToplevelbo.py +55 -0
  101. scustomtkinter_pygubu/sCTkWidgetSetForPygubuDesigner.py +141 -0
@@ -0,0 +1,392 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ sCTkNotebook
4
+
5
+ Pygubu builder objects for the vertical-tab notebook and for the tabs
6
+ themselves.
7
+
8
+ A TAB IS NOT A WIDGET. It is created by its notebook, through add(), so the
9
+ tab builder object has no class of its own to instantiate -- it calls the
10
+ parent's add() and hands back the sCTkFrame that comes out. Generated code
11
+ reads:
12
+
13
+ tab1 = notebook1.add("Receiver")
14
+
15
+ which is the same arrangement sCTkTabviewbo uses for CTkTabview's tabs, and
16
+ for the same reason.
17
+ """
18
+ import tkinter as tk
19
+
20
+ from pygubu.api.v1 import (
21
+ BuilderObject,
22
+ register_widget,
23
+ register_custom_property,
24
+ )
25
+ from pygubu.plugins.customtkinter.widgets import CTkFrameBO
26
+
27
+ from scustomtkinter.sctk_notebook import sCTkNotebook
28
+
29
+ widget_namespace = "scustomtkinter.sctk_notebook"
30
+ widget_classname = "sCTkNotebook"
31
+ builder_namespace = "scustomtkinter"
32
+ section_name = "sCustomTkinter"
33
+
34
+
35
+ class sCTkNotebookBO(CTkFrameBO):
36
+ class_ = sCTkNotebook
37
+
38
+ OPTIONS_CUSTOM = (
39
+ "side", "tab_width", "tab_style", "text_orientation",
40
+ "show_page_border", "show_tab_separators", "state",
41
+ )
42
+ # "command" IS IN properties, as well as in command_properties. The
43
+ # Designer lists only what is in `properties`; command_properties does
44
+ # not add to that list, it marks which listed properties are commands, to
45
+ # be connected to a callback rather than set as a value. Declared only in
46
+ # command_properties, it was registered and never shown. Pygubu's own
47
+ # Button declares its command in both.
48
+ #
49
+ # It stays out of OPTIONS_CUSTOM: pygubu connects it after construction,
50
+ # through configure(command=...), so realize() below does not pass it.
51
+ properties = CTkFrameBO.properties + OPTIONS_CUSTOM + ("command",)
52
+ command_properties = ("command",)
53
+
54
+ OPTIONS_CUSTOM_DEFAULTS = {
55
+ "side": "left",
56
+ "tab_width": "34",
57
+ "tab_style": "rounded",
58
+ "text_orientation": "auto",
59
+ "show_page_border": "True",
60
+ "show_tab_separators": "False",
61
+ "state": "normal",
62
+ }
63
+
64
+ # A container, but only for its own tabs -- see allowed_children below.
65
+ container = True
66
+ container_layout = False
67
+
68
+ def realize(self, parent, extra_init_args: dict = None):
69
+ """
70
+ Builds the notebook with its own properties as constructor arguments.
71
+
72
+ side and tab_style are validated by the widget and raise on a bad
73
+ value, so they have to arrive as real values rather than being set
74
+ afterwards -- a half-built widget in the Designer is worse than a
75
+ clear error.
76
+ """
77
+ if extra_init_args is None:
78
+ extra_init_args = {}
79
+
80
+ props = self.wmeta.properties
81
+ init_args = {
82
+ "side": props.get("side", "left"),
83
+ "tab_style": props.get("tab_style", "rounded"),
84
+ "text_orientation": props.get("text_orientation", "auto"),
85
+ "tab_width": int(props.get("tab_width", 34) or 34),
86
+ "show_page_border": self._as_bool(props.get("show_page_border", "True")),
87
+ "show_tab_separators": self._as_bool(
88
+ props.get("show_tab_separators", "False")),
89
+ "state": props.get("state", "normal"),
90
+ }
91
+
92
+ for prop in self.OPTIONS_CUSTOM:
93
+ extra_init_args.pop(prop, None)
94
+ init_args.update(extra_init_args)
95
+
96
+ real_master = parent.widget if hasattr(parent, "widget") else parent
97
+ self.widget = self.class_(real_master, **init_args)
98
+ return self.widget
99
+
100
+ @staticmethod
101
+ def _as_bool(value):
102
+ """
103
+ Parses the Designer's strings.
104
+
105
+ bool("False") is True, which would switch on every option whose
106
+ dropdown said otherwise.
107
+ """
108
+ if isinstance(value, str):
109
+ return value.strip().lower() in ("true", "1", "yes", "on")
110
+ return bool(value)
111
+
112
+ def _process_property_value(self, pname, value):
113
+ if pname == "tab_width":
114
+ return int(value)
115
+ if pname in ("show_page_border", "show_tab_separators"):
116
+ return self._as_bool(value)
117
+ return super()._process_property_value(pname, value)
118
+
119
+ def _code_set_property(self, targetid, pname, value, code_bag):
120
+ """Booleans and the width emit as tokens; the rest keep their quotes."""
121
+ if pname in ("show_page_border", "show_tab_separators", "tab_width"):
122
+ code_bag[pname] = str(value).strip("'\"")
123
+ elif pname in ("side", "tab_style", "text_orientation", "state"):
124
+ code_bag[pname] = f"'{str(value).strip(chr(39) + chr(34))}'"
125
+ else:
126
+ super()._code_set_property(targetid, pname, value, code_bag)
127
+
128
+ def _code_define_callback_args(self, cmd_pname, cmd):
129
+ """
130
+ Declares that `command` receives the tab's name, so the generated
131
+ handler stub has a matching parameter.
132
+
133
+ Without it a handler written from the stub would have no indication
134
+ that the selected tab is being handed to it -- which is the entire
135
+ point of the callback.
136
+ """
137
+ return ("tab",)
138
+
139
+ def code_imports(self):
140
+ imports = [(widget_namespace, widget_classname)]
141
+ imports.extend(self.code_extra_imports())
142
+ return imports
143
+
144
+
145
+ builder_id = f"{builder_namespace}.{widget_classname}"
146
+ register_widget(builder_id, sCTkNotebookBO, widget_classname,
147
+ ("ttk", section_name))
148
+
149
+
150
+ # ---------------------------------------------------------------------------
151
+ # The tab
152
+ # ---------------------------------------------------------------------------
153
+ class sCTkNotebookTabBO(BuilderObject):
154
+ """
155
+ A page in an sCTkNotebook.
156
+
157
+ Has no class_ of its own: a tab is made by the notebook's add(), which
158
+ returns the sCTkFrame that children are then parented to.
159
+ """
160
+
161
+ class_ = None
162
+ container = True
163
+ container_layout = True
164
+ layout_required = False
165
+ allow_bindings = False
166
+ properties = ("label",)
167
+ OPTIONS_CUSTOM_DEFAULTS = {"label": "Tab"}
168
+
169
+ def realize(self, parent, extra_init_args: dict = None):
170
+ """
171
+ Asks the parent notebook for a page.
172
+
173
+ DUPLICATE NAMES ARE RENAMED, NOT REJECTED. The widget's own add()
174
+ raises ValueError on a name already in use, which is right for
175
+ application code -- but inside the Designer that exception surfaces
176
+ only on the console, where nobody sees it, and the tab silently fails
177
+ to appear while the tree and the preview disagree. A numeric suffix is
178
+ appended instead, visibly, in both the tab strip and the property
179
+ field. Same accommodation sCTkTabviewbo makes.
180
+ """
181
+ master = parent.widget if hasattr(parent, "widget") else parent
182
+ label = self.wmeta.properties.get("label", self.wmeta.identifier)
183
+
184
+ existing = master.tabs()
185
+ if label in existing:
186
+ suffix = 2
187
+ while f"{label}_{suffix}" in existing:
188
+ suffix += 1
189
+ label = f"{label}_{suffix}"
190
+ self.wmeta.properties["label"] = label
191
+
192
+ self._label = label
193
+ self.widget = master.add(label)
194
+ return self.widget
195
+
196
+ def _set_property(self, target_widget, pname, value):
197
+ """
198
+ Rebuilds the notebook when a tab is renamed.
199
+
200
+ A tab's label is consumed by realize() -- it is the name add() was
201
+ called with -- so changing it afterwards cannot be expressed by
202
+ configuring the existing page. Without a rebuild the strip went on
203
+ showing the old caption until some unrelated edit forced a repaint.
204
+
205
+ recreate_widget() is what the Designer itself does when you add or
206
+ delete a widget, which is why doing that by hand made the change
207
+ appear. It hangs off _set_property, NOT set_property: the Designer
208
+ edits a property through the former, and the latter is never called.
209
+ """
210
+ if pname == "label":
211
+ # CONSUMED, not delegated, and not acted on here either.
212
+ #
213
+ # The base implementation forwards an unknown property to
214
+ # widget.configure(), and a page is an ordinary sCTkFrame:
215
+ #
216
+ # Failed to set property 'label' on class 'None'.
217
+ # Error: ['label'] are not supported arguments.
218
+ #
219
+ # The rename itself happens in configure(), which is the call the
220
+ # Designer makes after updating the metadata -- see there.
221
+ return
222
+
223
+ super()._set_property(target_widget, pname, value)
224
+
225
+ @staticmethod
226
+ def _find_notebook(page):
227
+ """
228
+ Climbs from a page to the notebook that owns it.
229
+
230
+ A page is parented to the notebook's internal page host, not to the
231
+ notebook, so the immediate parent is the wrong object.
232
+ """
233
+ try:
234
+ top = page.winfo_toplevel()
235
+ widget = page
236
+ for _ in range(4):
237
+ parent_name = widget.winfo_parent()
238
+ if not parent_name:
239
+ return None
240
+ widget = top.nametowidget(parent_name)
241
+ if hasattr(widget, "rename") and hasattr(widget, "tabs"):
242
+ return widget
243
+ except Exception:
244
+ pass
245
+ return None
246
+
247
+ def configure(self, target=None):
248
+ """
249
+ Applies a changed `label` by renaming the live tab.
250
+
251
+ THIS IS WHERE THE RENAME BELONGS, not in _set_property. The Designer
252
+ edits the metadata and then calls configure(); a no-op here left the
253
+ design canvas showing the old caption while the preview, which
254
+ rebuilds from scratch, showed the new one. sCTkTabviewTabBO learned
255
+ the same lesson and its docstring records it.
256
+
257
+ EVERYTHING IS DERIVED FROM self.widget, deliberately. Pygubu calls
258
+ configure() on builder objects whose realize() never ran, so any state
259
+ stashed during realize() -- the notebook, the name it was filed under
260
+ -- may simply not be there. The page is the only reliable anchor: walk
261
+ up from it to the notebook, then find the page in that notebook's
262
+ registry BY IDENTITY to learn what it is currently called.
263
+ """
264
+ widget = getattr(self, "widget", None)
265
+ if widget is None:
266
+ return
267
+
268
+ notebook = self._find_notebook(widget)
269
+ if notebook is None:
270
+ return
271
+
272
+ # Found by identity rather than by trusting a stored name.
273
+ old_name = None
274
+ for name, page in getattr(notebook, "_pages", {}).items():
275
+ if page is widget:
276
+ old_name = name
277
+ break
278
+ if old_name is None:
279
+ return
280
+
281
+ new_name = str(self.wmeta.properties.get("label", old_name))
282
+ if not new_name or new_name == old_name:
283
+ return
284
+
285
+ existing = [n for n in notebook.tabs() if n != old_name]
286
+ if new_name in existing:
287
+ suffix = 2
288
+ while f"{new_name}_{suffix}" in existing:
289
+ suffix += 1
290
+ new_name = f"{new_name}_{suffix}"
291
+ self.wmeta.properties["label"] = new_name
292
+
293
+ try:
294
+ notebook.rename(old_name, new_name)
295
+ self._label = new_name
296
+ except Exception:
297
+ # Torn down mid-edit. The preview rebuild still shows the new
298
+ # label, so this degrades rather than breaks.
299
+ return
300
+
301
+ def layout(self, target=None, *args, **kwargs):
302
+ """The notebook grids its own pages; a tab takes no layout."""
303
+ pass
304
+
305
+ def code_realize(self, boparent, code_identifier=None):
306
+ identifier = code_identifier or self.code_identifier()
307
+ label = self.wmeta.properties.get("label", "Tab")
308
+ return (f'{identifier} = {boparent.code_child_master()}'
309
+ f'.add("{label}")',)
310
+
311
+ def code_configure(self, targetid=None):
312
+ return tuple()
313
+
314
+ def code_layout(self, targetid=None, parentid=None):
315
+ return tuple()
316
+
317
+
318
+ # ONE dot in the id, not two.
319
+ #
320
+ # pygubu splits a classname on "." to work out which module to import, so
321
+ # "scustomtkinter.sCTkNotebook.Tab" made that split return a list and the
322
+ # generated code failed on load:
323
+ #
324
+ # AttributeError: 'list' object has no attribute 'startswith'
325
+ #
326
+ # The DISPLAY name still reads "sCTkNotebook.Tab" -- that is what appears in
327
+ # the palette. sCTkTabviewbo uses exactly this pairing.
328
+ tab_builder_id = f"{builder_namespace}.sCTkNotebookTab"
329
+ register_widget(tab_builder_id, sCTkNotebookTabBO,
330
+ f"{widget_classname}.Tab", ("ttk", section_name))
331
+
332
+ # A tab has nowhere else to live, so it is offered only inside a notebook.
333
+ sCTkNotebookTabBO.allowed_parents = (builder_id,)
334
+ sCTkNotebookBO.allowed_children = (tab_builder_id,)
335
+
336
+
337
+ # ---------------------------------------------------------------------------
338
+ # Inspector properties
339
+ # ---------------------------------------------------------------------------
340
+ register_custom_property(
341
+ builder_id, "side", "choice", values=("left", "right"),
342
+ state="readonly", default_value="left",
343
+ help="Which edge the tab strip sits on.")
344
+
345
+ register_custom_property(
346
+ builder_id, "tab_width", "naturalnumber", default_value="34",
347
+ help="Width of the strip in pixels. Explicit rather than measured, "
348
+ "because the strip takes space from the pages. Horizontal text "
349
+ "needs this wide enough to hold the longest label.")
350
+
351
+ register_custom_property(
352
+ builder_id, "tab_style", "choice", values=("rounded", "angled"),
353
+ state="readonly", default_value="rounded",
354
+ help="Rounded matches the rest of the library. Angled cuts the outer "
355
+ "corners, the shape of a real notebook divider.")
356
+
357
+ register_custom_property(
358
+ builder_id, "text_orientation", "choice",
359
+ values=("auto", "up", "down", "horizontal"),
360
+ state="readonly", default_value="auto",
361
+ help="auto reads outside-in and flips with the side. up and down pin the "
362
+ "direction whichever side the strip is on. horizontal lays the text "
363
+ "flat and needs a wider tab_width.")
364
+
365
+ register_custom_property(
366
+ builder_id, "show_page_border", "choice", values=("True", "False"),
367
+ state="readonly", default_value="True",
368
+ help="Draws the outline around the page, broken where the selected tab "
369
+ "meets it.")
370
+
371
+ register_custom_property(
372
+ builder_id, "show_tab_separators", "choice", values=("True", "False"),
373
+ state="readonly", default_value="False",
374
+ help="A line between adjacent tabs. Earns its keep with horizontal text, "
375
+ "where stacked labels can run together; rarely otherwise.")
376
+
377
+ register_custom_property(
378
+ builder_id, "state", "choice", values=("normal", "disabled"),
379
+ state="readonly", default_value="normal",
380
+ help="Disabled dims the strip and stops tab selection. It does not "
381
+ "cascade to the widgets on a page.")
382
+
383
+ register_custom_property(
384
+ builder_id, "command", "commandentry",
385
+ help="Called with the tab's name when the operator clicks a tab. Not "
386
+ "called when a tab is selected in code with set() -- the "
387
+ "application already knows what it chose.")
388
+
389
+ register_custom_property(
390
+ tab_builder_id, "label", "entry", default_value="Tab",
391
+ help="The tab's caption, and the name add() is called with. A duplicate "
392
+ "is renamed with a numeric suffix rather than rejected.")
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ sCTkOptionMenuPrimary
4
+
5
+ Tailored version of the standard ctkOptionMenu
6
+
7
+ UI source file: sCTkOptionMenuPrimary.ui
8
+ """
9
+ import tkinter as tk
10
+ import tkinter.ttk as ttk
11
+ from customtkinter import CTkOptionMenu
12
+ from pygubu.api.v1 import (
13
+ BuilderObject,
14
+ register_widget,
15
+ )
16
+
17
+ from pygubu.plugins.customtkinter.widgets import CTkOptionMenuBO
18
+ from scustomtkinter.sctk_optionmenu_primary import sCTkOptionMenuPrimary
19
+
20
+
21
+ #
22
+ # Builder definition section
23
+ #
24
+ widget_namespace = "scustomtkinter.sctk_optionmenu_primary"
25
+ widget_classname = "sCTkOptionMenuPrimary"
26
+ builder_namespace = "scustomtkinter"
27
+ section_name = "sCustomTkinter"
28
+
29
+
30
+ class sCTkOptionMenuPrimaryBO(CTkOptionMenuBO):
31
+ class_ = sCTkOptionMenuPrimary
32
+
33
+ def code_imports(self):
34
+ # should return an iterable of (module, classname/function) to import
35
+ # or None
36
+ imports = [(widget_namespace, widget_classname)]
37
+ imports.extend(self.code_extra_imports())
38
+ return imports
39
+
40
+
41
+ builder_id = f"{builder_namespace}.{widget_classname}"
42
+ register_widget(
43
+ builder_id, sCTkOptionMenuPrimaryBO, widget_classname, ("ttk", section_name)
44
+ )
45
+
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/python3
2
+
3
+ import tkinter as tk
4
+ import tkinter.ttk as ttk
5
+ from customtkinter import CTkOptionMenu
6
+ from pygubu.api.v1 import (
7
+ BuilderObject,
8
+ register_widget,
9
+ )
10
+
11
+ from pygubu.plugins.customtkinter.widgets import CTkOptionMenuBO
12
+ from scustomtkinter.sctk_optionmenu_secondary import sCTkOptionMenuSecondary
13
+
14
+
15
+ #
16
+ # Builder definition section
17
+ #
18
+ widget_namespace = "scustomtkinter.sctk_optionmenu_secondary"
19
+ widget_classname = "sCTkOptionMenuSecondary"
20
+ builder_namespace = "scustomtkinter"
21
+ section_name = "sCustomTkinter"
22
+
23
+
24
+ class sCTkOptionMenuSecondaryBO(CTkOptionMenuBO):
25
+ class_ = sCTkOptionMenuSecondary
26
+
27
+ def code_imports(self):
28
+ # should return an iterable of (module, classname/function) to import
29
+ # or None
30
+ imports = [(widget_namespace, widget_classname)]
31
+ imports.extend(self.code_extra_imports())
32
+ return imports
33
+
34
+
35
+ builder_id = f"{builder_namespace}.{widget_classname}"
36
+ register_widget(
37
+ builder_id, sCTkOptionMenuSecondaryBO, widget_classname, ("ttk", section_name)
38
+ )
39
+
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ sCTkPathChooser
4
+
5
+ Pygubu Builder Object for the compound sCTkPathChooser entry widget row.
6
+ """
7
+ import pygubu
8
+
9
+ from pygubu.api.v1 import (
10
+ BuilderObject,
11
+ register_widget,
12
+ register_custom_property
13
+ )
14
+
15
+ # Import the native custom class
16
+ from scustomtkinter.sctk_path_chooser import sCTkPathChooser
17
+ from pygubu.plugins.customtkinter.widgets import CTkFrameBO
18
+
19
+ #
20
+ # Builder definition section
21
+ #
22
+ widget_namespace = "scustomtkinter.sctk_path_chooser"
23
+ widget_classname = "sCTkPathChooser"
24
+ builder_namespace = "scustomtkinter"
25
+ section_name = "sCustomTkinter"
26
+
27
+
28
+ class sCTkPathChooserBO(BuilderObject):
29
+ class_ = sCTkPathChooser
30
+
31
+ # Expose custom compound parameters alongside theme state configurations
32
+ OPTIONS_CUSTOM = ("width", "height", "type", "title", "initialdir", "initialfile", "filetypes", "state", "command", "btn_width", "btn_height", "btn_text", "entry_height", "browser_width", "browser_height", "justify")
33
+ properties = CTkFrameBO.properties + OPTIONS_CUSTOM
34
+
35
+ command_properties = ("command",)
36
+
37
+ def _process_property_value(self, pname, value):
38
+ """Passes values directly to allow core widget validations to handle exceptions."""
39
+ return super()._process_property_value(pname, value)
40
+
41
+ def _code_define_callback_args(self, cmd_pname, cmd):
42
+ """
43
+ Declares what the callback receives, so the generated stub has a
44
+ matching parameter.
45
+
46
+ Without this, pygubu generates a stub taking no arguments and the
47
+ widget calls it with the chosen path:
48
+
49
+ TypeError: on_path() takes 1 positional argument but 2 were given
50
+ """
51
+ return ("selected_path",)
52
+
53
+ def _code_set_property(self, targetid, pname, value, code_bag):
54
+ """
55
+ Emits `filetypes` as a real Python list, not a quoted string.
56
+
57
+ The bracketed form contains double quotes, and the default handler
58
+ wraps the whole value in double quotes again:
59
+
60
+ sctkpathchooser1.configure(filetypes="[".py"]", ...)
61
+ ^^^^^^^^^
62
+ SyntaxError: invalid syntax
63
+
64
+ Emitting the literal unquoted gives valid Python and a real list. A
65
+ bare comma-separated value has no brackets and no quotes, so it is
66
+ emitted as a quoted string and parsed by the widget as usual.
67
+ """
68
+ if pname == "filetypes" and value:
69
+ text = str(value).strip()
70
+ if text.startswith("[") and text.endswith("]"):
71
+ code_bag[pname] = text
72
+ return None
73
+ return super()._code_set_property(targetid, pname, value, code_bag)
74
+
75
+
76
+ # Register the widget into Pygubu's parsing engine
77
+ builder_id = f"{builder_namespace}.{widget_classname}"
78
+
79
+ register_widget(builder_id, sCTkPathChooserBO, 'sCTkPathChooser', ("ttk", section_name))
80
+
81
+
82
+ # Map the 'command' option directly to Pygubu's native callback editor panel
83
+ register_custom_property(
84
+ builder_id,
85
+ "command",
86
+ "commandentry",
87
+ help="Method callback string triggered on path confirmation"
88
+ )
89
+
90
+
91
+ # Register custom attribute fields to display inside the Designer properties panel
92
+
93
+ register_custom_property(
94
+ builder_id,
95
+ "width",
96
+ "naturalnumber",
97
+ help="Set total width in pixels of file entry and button. File path width = width - button width"
98
+ )
99
+
100
+ register_custom_property(
101
+ builder_id,
102
+ "height",
103
+ "naturalnumber",
104
+ help="Set height in pixels allocated to file path and button frame. Button and file path height set separately."
105
+ )
106
+
107
+ register_custom_property(
108
+ builder_id,
109
+ "type",
110
+ "choice",
111
+ values=("", "file", "directory"),
112
+ state="readonly",
113
+ default_value="directory",
114
+ help="What the chooser selects. The widget's own default is 'directory'; "
115
+ "filetypes only applies when this is 'file'."
116
+ )
117
+
118
+ register_custom_property(
119
+ builder_id,
120
+ "justify",
121
+ "choice",
122
+ values=("", "left", "right", "center"),
123
+ state="readonly",
124
+ help="Align long path strings to prioritize viewing starting roots or trailing filenames"
125
+ )
126
+
127
+ register_custom_property(
128
+ builder_id,
129
+ "title",
130
+ "entry",
131
+ help="Window header title string text"
132
+ )
133
+
134
+ register_custom_property(
135
+ builder_id,
136
+ "initialdir",
137
+ "entry",
138
+ help="Starting directory path location string"
139
+ )
140
+
141
+ register_custom_property(
142
+ builder_id,
143
+ "initialfile",
144
+ "entry",
145
+ help="Starting target highlight file path string"
146
+ )
147
+
148
+ register_custom_property(
149
+ builder_id,
150
+ "filetypes",
151
+ "entry",
152
+ help='Filter by file extension. Preferred: [".py", ".txt"]. Bare comma-separated (.py, .txt) also works, but cannot contain a comma inside a value.'
153
+ )
154
+
155
+ register_custom_property(
156
+ builder_id,
157
+ "entry_height",
158
+ "naturalnumber",
159
+ help="Set height in pixels of the file path field widget"
160
+ )
161
+
162
+ register_custom_property(
163
+ builder_id,
164
+ "btn_width",
165
+ "naturalnumber",
166
+ help="Set width in pixels of the button"
167
+ )
168
+
169
+ register_custom_property(
170
+ builder_id,
171
+ "btn_height",
172
+ "naturalnumber",
173
+ help="Set height in pixels of the button"
174
+ )
175
+
176
+ register_custom_property(
177
+ builder_id,
178
+ "btn_text",
179
+ "entry",
180
+ help="Override default button text (e.g. 'Select', '▶' or '>')"
181
+ )
182
+
183
+ register_custom_property(
184
+ builder_id,
185
+ "browser_width",
186
+ "naturalnumber",
187
+ help="Set width of the pop-up file browser window in pixels"
188
+ )
189
+
190
+ register_custom_property(
191
+ builder_id,
192
+ "browser_height",
193
+ "naturalnumber",
194
+ help="Set height of the pop-up file browser window in pixels"
195
+ )
196
+
197
+ register_custom_property(
198
+ builder_id,
199
+ "state",
200
+ "choice",
201
+ values=("", "normal", "disabled"),
202
+ state="readonly",
203
+ help="Set widget interaction state"
204
+ )