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,397 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ sCTkDialog
4
+
5
+ A consistent popup dialog: heading, content area, action button row.
6
+
7
+ NOTE there is deliberately no builder object for sCTkDialogToplevel. That
8
+ class is created BY sCTkDialog and is never placed by a user, so
9
+ registering it would only offer something that cannot sensibly be dropped
10
+ anywhere. The class remains; its Designer registration does not.
11
+ """
12
+ import tkinter as tk
13
+ import tkinter.ttk as ttk
14
+
15
+ from pygubu.api.v1 import (
16
+ BuilderObject,
17
+ register_widget,
18
+ register_custom_property,
19
+ )
20
+
21
+ from scustomtkinter.themeable_widget import parse_font_property
22
+ from scustomtkinter.sctk_dialog import sCTkDialog
23
+
24
+
25
+ #
26
+ # Builder definition section
27
+ #
28
+ widget_namespace = "scustomtkinter.sctk_dialog"
29
+ widget_classname = "sCTkDialog"
30
+ builder_namespace = "scustomtkinter"
31
+ section_name = "sCustomTkinter"
32
+
33
+
34
+ class sCTkDialogBO(BuilderObject):
35
+ class_ = sCTkDialog
36
+ container = True
37
+
38
+ # Window properties. All are keyword-only constructor arguments on the
39
+ # widget, not configure() options, so realize() and code_realize() below
40
+ # pass them at construction rather than leaving BuilderObject to apply
41
+ # them afterwards.
42
+ OPTIONS_CUSTOM = ("title", "width", "height", "modal", "transient",
43
+ "offset_x", "offset_y", "heading", "heading_anchor",
44
+ "heading_font", "heading_color", "buttons",
45
+ "apply_text", "cancel_text", "reset_text")
46
+ properties = OPTIONS_CUSTOM + ("apply_command", "cancel_command",
47
+ "reset_command")
48
+
49
+ # Declared so pygubu routes these through its callback handling rather
50
+ # than stringifying the property metadata into the generated code.
51
+ command_properties = ("apply_command", "cancel_command", "reset_command")
52
+
53
+ def get_child_master(self):
54
+ """
55
+ Children go into the content area, not into the dialog frame itself.
56
+
57
+ This is what makes the dialog pleasant to lay out: drop widgets onto
58
+ it in the Designer and they land between the heading and the button
59
+ row, where they belong.
60
+ """
61
+ return self.widget.contentFrame
62
+
63
+ def code_child_master(self):
64
+ return f"{self.code_identifier()}.contentFrame"
65
+
66
+ def _dialog_init_args(self):
67
+ """
68
+ Collects the window properties from the widget metadata, converted to
69
+ the types the constructor expects.
70
+
71
+ Omitted properties are left out entirely rather than passed as None,
72
+ so the widget's own defaults apply.
73
+ """
74
+ props = getattr(getattr(self, "wmeta", None), "properties", {}) or {}
75
+ args = {}
76
+
77
+ title = props.get("title")
78
+ if title:
79
+ args["title"] = str(title)
80
+
81
+ for name in ("width", "height", "offset_x", "offset_y"):
82
+ raw = props.get(name)
83
+ if raw not in (None, ""):
84
+ try:
85
+ args[name] = int(raw)
86
+ except (TypeError, ValueError):
87
+ pass
88
+
89
+ for flag in ("modal", "transient"):
90
+ raw = props.get(flag)
91
+ if raw not in (None, ""):
92
+ args[flag] = str(raw).lower() in ("true", "1", "yes")
93
+
94
+ for prop in ("heading", "heading_anchor"):
95
+ value = props.get(prop)
96
+ if value:
97
+ args[prop] = str(value)
98
+
99
+ font = parse_font_property(props.get("heading_font"))
100
+ if font is not None:
101
+ args["heading_font"] = font
102
+
103
+ colour = props.get("heading_color")
104
+ if colour:
105
+ args["heading_color"] = str(colour)
106
+
107
+ buttons = props.get("buttons")
108
+ if buttons not in (None, ""):
109
+ try:
110
+ args["buttons"] = int(buttons)
111
+ except (TypeError, ValueError):
112
+ pass
113
+
114
+ # Only labels for buttons this dialog actually has. With buttons=2 a
115
+ # reset_text argument is inert -- it lands in _button_text where
116
+ # nothing reads it -- but it is noise in the generated file, and noise
117
+ # that reads like a bug: a label for a button that does not exist.
118
+ #
119
+ # Reads args["buttons"], so this must stay AFTER the buttons parsing
120
+ # above.
121
+ #
122
+ # The COMMANDS are deliberately not filtered the same way. Pygubu emits
123
+ # those through its own path, and each one generates a callback stub in
124
+ # the user's file; dropping a command because the button count was
125
+ # temporarily reduced would take that stub with it.
126
+ count = args.get("buttons", 3)
127
+ for prop in ("apply_text", "cancel_text", "reset_text")[:count]:
128
+ value = props.get(prop)
129
+ if value:
130
+ args[prop] = str(value)
131
+
132
+ return args
133
+
134
+ def realize(self, parent, extra_init_args: dict = None):
135
+ """Builds the dialog, passing the window properties at construction."""
136
+ init_args = self._dialog_init_args()
137
+ if extra_init_args:
138
+ init_args.update(extra_init_args)
139
+ master = parent.get_child_master() if hasattr(parent, "get_child_master") else parent
140
+ self.widget = self.class_(master, **init_args)
141
+ return self.widget
142
+
143
+ def code_realize(self, boparent, code_identifier=None):
144
+ """Emits the construction line with the window properties inline."""
145
+ if code_identifier is not None:
146
+ self._code_identifier = code_identifier
147
+ master = boparent.code_child_master()
148
+ args = self._dialog_init_args()
149
+
150
+ bag = [f"{master}"]
151
+ for name, value in args.items():
152
+ bag.append(f"{name}={value!r}")
153
+
154
+ return [f"{self.code_identifier()} = {self._code_class_name()}({', '.join(bag)})"]
155
+
156
+ # Property name -> the widget setter that applies a button command.
157
+ _COMMAND_TARGETS = {
158
+ "apply_command": "set_apply_button",
159
+ "cancel_command": "set_cancel_button",
160
+ "reset_command": "set_reset_button",
161
+ }
162
+
163
+ # Properties that can be applied to a LIVE widget. Everything else in
164
+ # OPTIONS_CUSTOM affects the window or the set of buttons, and can only be
165
+ # honoured by rebuilding -- the Designer does that on its own when the
166
+ # design is redrawn.
167
+ _LIVE_TEXT = {"apply_text": "applyText_VAR",
168
+ "cancel_text": "cancelText_VAR",
169
+ "reset_text": "resetText_VAR"}
170
+
171
+ # The widget's own defaults. Clearing a field in the inspector must restore
172
+ # these, not blank the button: an empty label looks like a broken widget,
173
+ # and the generated code correctly falls back to them because the property
174
+ # is simply omitted from the constructor call.
175
+ _TEXT_DEFAULTS = {"apply_text": "Apply", "cancel_text": "Cancel",
176
+ "reset_text": "Reset", "heading": "Heading Title"}
177
+
178
+ def _set_property(self, target_widget, pname, value):
179
+ """
180
+ Applies a property to the live preview.
181
+
182
+ None of this widget's own properties are configure() options --
183
+ sCTkDialog takes them as constructor arguments -- so letting any of
184
+ them reach sCTkFrame.configure() would raise. They are handled here or
185
+ deliberately ignored.
186
+
187
+ Button labels ARE applied live, by setting the StringVar the button
188
+ reads. Without this, editing a label in the inspector changed nothing
189
+ on the canvas until the design was redrawn for some other reason.
190
+ """
191
+ if pname in self._LIVE_TEXT:
192
+ # FIX: an empty value used to blank the button. Clearing a field
193
+ # means "use the default", which is what the generated code does --
194
+ # the property is omitted and the constructor default applies. The
195
+ # design view now agrees with it.
196
+ # The widget restores its own default when given an empty value,
197
+ # and knows what that default is -- see sCTkDialog.configure() and
198
+ # _PROPERTY_DEFAULTS. Keeping a second copy of the same defaults
199
+ # here would be a place for them to drift apart.
200
+ target_widget.configure(**{pname: value})
201
+ return None
202
+
203
+ if pname == "heading_font":
204
+ target_widget.set_heading_font(
205
+ parse_font_property(value)
206
+ or target_widget.final_kw.get("heading_font"))
207
+ return None
208
+
209
+ if pname == "heading_color":
210
+ # Empty restores the theme's text_color, matching what the
211
+ # generated code does: the property is omitted and the theme
212
+ # applies.
213
+ target_widget.set_heading_color(value or None)
214
+ return None
215
+
216
+ if pname == "buttons":
217
+ # FIX: this used to rely on builder.recreate_widget(), which did
218
+ # not reach us -- the canvas kept the old button count while the
219
+ # preview and generated code showed the new one. The widget rebuilds
220
+ # its own button row instead, which depends on nothing outside it.
221
+ target_widget.set_buttons(value)
222
+ return None
223
+
224
+ if pname == "heading":
225
+ target_widget.configure(heading=value)
226
+ return None
227
+
228
+ if pname == "heading_anchor":
229
+ target_widget.set_heading(anchor=value or "center")
230
+ return None
231
+
232
+ if pname == "title":
233
+ target_widget.set_title(value or "")
234
+ return None
235
+
236
+ if pname in self._COMMAND_TARGETS:
237
+ # A command aimed at a button this dialog does not have is ignored
238
+ # rather than raising: reducing `buttons` while a command is still
239
+ # set on a removed button is an ordinary inspector edit.
240
+ if value:
241
+ getattr(target_widget, self._COMMAND_TARGETS[pname])(
242
+ button_command=value)
243
+ return None
244
+
245
+ if pname in ("width", "height"):
246
+ # Applied live: these size the WINDOW, and the widget routes them
247
+ # there. See sCTkDialog._OWN_PROPERTIES for why they are not the
248
+ # frame's own width and height.
249
+ target_widget.configure(**{pname: value})
250
+ return None
251
+
252
+ if pname in self.OPTIONS_CUSTOM:
253
+ # Placement, modality and transient are construction-time.
254
+ # realize() passes them; nothing to do here.
255
+ return None
256
+
257
+ return super()._set_property(target_widget, pname, value)
258
+
259
+ def set_property(self, name, value):
260
+ """
261
+ Records a property change in the widget metadata, then applies it live.
262
+
263
+ FIX: without the metadata write, a change made in the inspector did not
264
+ reach code generation until the project was saved and reopened --
265
+ editing `buttons` from 3 to 2 updated the canvas but still generated
266
+ buttons=3. The live widget and the metadata had diverged.
267
+
268
+ An earlier version of this method also called
269
+ builder.recreate_widget(); that has been dropped. It never reached us,
270
+ and the widget rebuilds its own button row now -- see
271
+ sCTkDialog.set_buttons().
272
+ """
273
+ if hasattr(self, "wmeta") and hasattr(self.wmeta, "properties"):
274
+ self.wmeta.properties[name] = value
275
+
276
+ if getattr(self, "widget", None) is not None:
277
+ self._set_property(self.widget, name, value)
278
+
279
+ def _code_set_property(self, targetid, pname, value, code_bag):
280
+ """
281
+ Keeps this widget's own properties OUT of the generated configure()
282
+ call.
283
+
284
+ FIX: without this they were emitted as configure() arguments --
285
+
286
+ sctkdialog1.configure(apply_text=""Apply it"", buttons=3, ...)
287
+
288
+ which is wrong twice over. The doubled quotes are a syntax error, and
289
+ even correctly quoted the call would raise at runtime, because
290
+ sCTkFrame.configure() does not accept these names. They belong in the
291
+ constructor, and code_realize() already puts them there.
292
+
293
+ The button commands are emitted as their own statements instead, since
294
+ they are applied through setter methods rather than configure().
295
+ """
296
+ # The button commands are deliberately NOT intercepted here. Pygubu
297
+ # emits command properties through its own code path, which this
298
+ # override is not consulted for -- the earlier attempt to redirect them
299
+ # produced generated code that still called
300
+ # configure(apply_command=...) and raised ValueError at the widget.
301
+ #
302
+ # sCTkDialog.configure() now accepts them instead, so the ordinary
303
+ # emission works and there is nothing to redirect.
304
+ if pname in self.OPTIONS_CUSTOM:
305
+ # Passed at construction by code_realize(); keeping them out of the
306
+ # configure() call avoids setting the same thing twice.
307
+ return None
308
+
309
+ return super()._code_set_property(targetid, pname, value, code_bag)
310
+
311
+ def code_imports(self):
312
+ # should return an iterable of (module, classname/function) to import
313
+ # or None
314
+ return [(widget_namespace, widget_classname)]
315
+
316
+
317
+ builder_id = f"{builder_namespace}.{widget_classname}"
318
+ register_widget(
319
+ builder_id, sCTkDialogBO, widget_classname, ("ttk", section_name)
320
+ )
321
+
322
+ register_custom_property(
323
+ builder_id, "title", "entry",
324
+ help="Window title bar text."
325
+ )
326
+ register_custom_property(
327
+ builder_id, "width", "naturalnumber",
328
+ help="Window width in pixels. Leave blank to size to content."
329
+ )
330
+ register_custom_property(
331
+ builder_id, "height", "naturalnumber",
332
+ help="Window height in pixels. Leave blank to size to content."
333
+ )
334
+ register_custom_property(
335
+ builder_id, "modal", "choice", values=("False", "True"), state="readonly",
336
+ help="True blocks interaction with the rest of the application while the "
337
+ "dialog is open. Call run_and_wait() instead to also block the "
338
+ "calling code until it closes."
339
+ )
340
+ register_custom_property(
341
+ builder_id, "offset_x", "integernumber",
342
+ help="Pixels right of the parent window's top-left corner. Default 40."
343
+ )
344
+ register_custom_property(
345
+ builder_id, "offset_y", "integernumber",
346
+ help="Pixels below the parent window's top-left corner. Default 40."
347
+ )
348
+ register_custom_property(
349
+ builder_id, "transient", "choice", values=("True", "False"),
350
+ state="readonly",
351
+ help="True ties the window to its parent -- above it, minimises with it, "
352
+ "usually not in the taskbar. False gives an independent window."
353
+ )
354
+ register_custom_property(
355
+ builder_id, "heading", "entry",
356
+ help="Text shown above the content area. Clear to restore the default."
357
+ )
358
+ register_custom_property(
359
+ builder_id, "heading_anchor", "choice",
360
+ values=("center", "w", "e"), state="readonly",
361
+ help="Heading alignment."
362
+ )
363
+ register_custom_property(
364
+ builder_id, "heading_font", "fontentry",
365
+ help="Heading font. Leave blank to use the theme's heading_font."
366
+ )
367
+ register_custom_property(
368
+ builder_id, "heading_color", "colorentry",
369
+ help="Heading colour. Leave blank to use the theme's text_color."
370
+ )
371
+ register_custom_property(
372
+ builder_id, "buttons", "choice", values=("3", "2", "1"), state="readonly",
373
+ help="3 = Apply, Cancel, Reset. 2 = Apply, Cancel. 1 = Apply only. "
374
+ "Apply is always present."
375
+ )
376
+ register_custom_property(
377
+ builder_id, "apply_text", "entry", help="Apply button label. Default 'Apply'."
378
+ )
379
+ register_custom_property(
380
+ builder_id, "cancel_text", "entry",
381
+ help="Cancel button label. Default 'Cancel'. Ignored when buttons is 1."
382
+ )
383
+ register_custom_property(
384
+ builder_id, "reset_text", "entry",
385
+ help="Reset button label. Default 'Reset'. Ignored unless buttons is 3."
386
+ )
387
+ register_custom_property(
388
+ builder_id, "apply_command", "commandentry", help="Called when Apply is clicked."
389
+ )
390
+ register_custom_property(
391
+ builder_id, "cancel_command", "commandentry",
392
+ help="Called when Cancel is clicked. Ignored when buttons is 1."
393
+ )
394
+ register_custom_property(
395
+ builder_id, "reset_command", "commandentry",
396
+ help="Called when Reset is clicked. Ignored unless buttons is 3."
397
+ )
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ sCTkEntryPrimary
4
+
5
+ Customized ctk Entry field. - Primary version
6
+
7
+ UI source file: sCTkEntryPrimary.ui
8
+ """
9
+ import tkinter as tk
10
+ import tkinter.ttk as ttk
11
+ from customtkinter import CTkEntry
12
+ from pygubu.api.v1 import (
13
+ BuilderObject,
14
+ register_widget,
15
+ )
16
+
17
+ from pygubu.plugins.customtkinter import nsctk
18
+ from pygubu.plugins.customtkinter.widgets import CTkEntryBO
19
+
20
+ from scustomtkinter.sctk_entry_primary import sCTkEntryPrimary
21
+
22
+
23
+ #
24
+ # Builder definition section
25
+ #
26
+ widget_namespace = "scustomtkinter.sctk_entry_primary"
27
+ widget_classname = "sCTkEntryPrimary"
28
+ builder_namespace = "scustomtkinter"
29
+ section_name = "sCustomTkinter"
30
+
31
+
32
+ class sCTkEntryPrimaryBO(CTkEntryBO):
33
+ class_ = sCTkEntryPrimary
34
+
35
+ # --- Workarounds for CTkEntryBO, both reported upstream -----------------
36
+ #
37
+ # 1. invalidcommand is REMOVED from the inspector. CTkEntry does not accept
38
+ # it -- the name is absent from the widget's own attribute whitelist, so
39
+ # configure() raises:
40
+ #
41
+ # ['invalidcommand'] are not supported arguments.
42
+ #
43
+ # CTkEntryBO offers it anyway. Since sCTkEntry{Primary,Secondary} pass
44
+ # keywords straight through to native CTkEntry, the property could never
45
+ # work at any layer. Better not to offer it than to offer a field that
46
+ # errors when used.
47
+ #
48
+ # Subtracted from the inherited tuple rather than restated, so any
49
+ # property CustomTkinter adds later is picked up automatically.
50
+ properties = tuple(p for p in CTkEntryBO.properties if p != "invalidcommand")
51
+
52
+ # 2. validatecommand and xscrollcommand ARE accepted by CTkEntry, but
53
+ # CTkEntryBO never declares them as command properties -- so pygubu does
54
+ # not route them through its callback handling and instead stringifies
55
+ # the raw property metadata into the generated code:
56
+ #
57
+ # validatecommand="{"name": "validatecommand", "type": "command", ...}"
58
+ #
59
+ # Note the unescaped inner quotes: that is not merely wrong, it is a
60
+ # syntax error, and the whole generated module fails to import.
61
+ #
62
+ # invalidcommand is deliberately NOT listed here even though it is a
63
+ # command property, because the widget rejects it -- see above.
64
+ command_properties = tuple(getattr(CTkEntryBO, "command_properties", ())) + (
65
+ "validatecommand",
66
+ "xscrollcommand",
67
+ )
68
+
69
+ def _code_define_callback_args(self, cmd_pname, cmd):
70
+ """
71
+ Declares what each command callback receives.
72
+
73
+ `xscrollcommand` is called by Tk with TWO arguments -- the first and
74
+ last visible fractions, the same pair a scrollbar's set() takes. A stub
75
+ generated with no parameters raises the moment the entry scrolls:
76
+
77
+ TypeError: on_scroll() takes 1 positional argument but 3 were given
78
+
79
+ `validatecommand` is called with NO arguments when it is a plain
80
+ callable, because Tk's %-substitutions are only passed when the command
81
+ is registered with them and pygubu does not do that. The base class
82
+ already returns nothing, so it is left alone rather than restated.
83
+ """
84
+ if cmd_pname == "xscrollcommand":
85
+ return ("first", "last")
86
+ return super()._code_define_callback_args(cmd_pname, cmd)
87
+
88
+ def code_imports(self):
89
+ # should return an iterable of (module, classname/function) to import
90
+ # or None
91
+ imports = [(widget_namespace, widget_classname)]
92
+ imports.extend(self.code_extra_imports())
93
+ return imports
94
+
95
+
96
+ builder_id = f"{builder_namespace}.{widget_classname}"
97
+ register_widget(
98
+ builder_id, sCTkEntryPrimaryBO, widget_classname, ("ttk", section_name)
99
+ )
100
+
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ sCTkEntrySecondary
4
+
5
+ Customized ctk Entry field. - Secondary version
6
+
7
+ UI source file: sCTkEntrySecondary.ui
8
+ """
9
+ import tkinter as tk
10
+ import tkinter.ttk as ttk
11
+ from customtkinter import CTkEntry
12
+ from pygubu.api.v1 import (
13
+ BuilderObject,
14
+ register_widget,
15
+ )
16
+
17
+ from pygubu.plugins.customtkinter.widgets import CTkEntryBO
18
+ from scustomtkinter.sctk_entry_secondary import sCTkEntrySecondary
19
+
20
+
21
+ #
22
+ # Builder definition section
23
+ #
24
+ widget_namespace = "scustomtkinter.sctk_entry_secondary"
25
+ widget_classname = "sCTkEntrySecondary"
26
+ builder_namespace = "scustomtkinter"
27
+ section_name = "sCustomTkinter"
28
+
29
+
30
+ class sCTkEntrySecondaryBO(CTkEntryBO):
31
+ class_ = sCTkEntrySecondary
32
+
33
+ # --- Workarounds for CTkEntryBO, both reported upstream -----------------
34
+ #
35
+ # 1. invalidcommand is REMOVED from the inspector. CTkEntry does not accept
36
+ # it -- the name is absent from the widget's own attribute whitelist, so
37
+ # configure() raises:
38
+ #
39
+ # ['invalidcommand'] are not supported arguments.
40
+ #
41
+ # CTkEntryBO offers it anyway. Since sCTkEntry{Primary,Secondary} pass
42
+ # keywords straight through to native CTkEntry, the property could never
43
+ # work at any layer. Better not to offer it than to offer a field that
44
+ # errors when used.
45
+ #
46
+ # Subtracted from the inherited tuple rather than restated, so any
47
+ # property CustomTkinter adds later is picked up automatically.
48
+ properties = tuple(p for p in CTkEntryBO.properties if p != "invalidcommand")
49
+
50
+ # 2. validatecommand and xscrollcommand ARE accepted by CTkEntry, but
51
+ # CTkEntryBO never declares them as command properties -- so pygubu does
52
+ # not route them through its callback handling and instead stringifies
53
+ # the raw property metadata into the generated code:
54
+ #
55
+ # validatecommand="{"name": "validatecommand", "type": "command", ...}"
56
+ #
57
+ # Note the unescaped inner quotes: that is not merely wrong, it is a
58
+ # syntax error, and the whole generated module fails to import.
59
+ #
60
+ # invalidcommand is deliberately NOT listed here even though it is a
61
+ # command property, because the widget rejects it -- see above.
62
+ command_properties = tuple(getattr(CTkEntryBO, "command_properties", ())) + (
63
+ "validatecommand",
64
+ "xscrollcommand",
65
+ )
66
+
67
+ def _code_define_callback_args(self, cmd_pname, cmd):
68
+ """
69
+ Declares what each command callback receives.
70
+
71
+ `xscrollcommand` is called by Tk with TWO arguments -- the first and
72
+ last visible fractions, the same pair a scrollbar's set() takes. A stub
73
+ generated with no parameters raises the moment the entry scrolls:
74
+
75
+ TypeError: on_scroll() takes 1 positional argument but 3 were given
76
+
77
+ `validatecommand` is called with NO arguments when it is a plain
78
+ callable, because Tk's %-substitutions are only passed when the command
79
+ is registered with them and pygubu does not do that. The base class
80
+ already returns nothing, so it is left alone rather than restated.
81
+ """
82
+ if cmd_pname == "xscrollcommand":
83
+ return ("first", "last")
84
+ return super()._code_define_callback_args(cmd_pname, cmd)
85
+
86
+ def code_imports(self):
87
+ # should return an iterable of (module, classname/function) to import
88
+ # or None
89
+ imports = [(widget_namespace, widget_classname)]
90
+ imports.extend(self.code_extra_imports())
91
+ return imports
92
+
93
+
94
+ builder_id = f"{builder_namespace}.{widget_classname}"
95
+ register_widget(
96
+ builder_id, sCTkEntrySecondaryBO, widget_classname, ("ttk", section_name)
97
+ )
98
+