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,141 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ sCTkSeparatorBuilder
4
+
5
+ Pygubu Builder Object for the custom themeable sCTkSeparator widget line.
6
+ Inherits from CTkCanvasBO to secure flawless layout and parenting geometry management.
7
+ """
8
+ from pygubu.api.v1 import (
9
+ BuilderObject,
10
+ register_widget,
11
+ register_custom_property
12
+ )
13
+ from pygubu.plugins.customtkinter.widgets import CTkCanvasBO
14
+ from scustomtkinter.themeable_widget import GLOBAL_THEME_REGISTRY as THEME_DEFAULTS
15
+
16
+ # Import the native custom class
17
+ from scustomtkinter.sctk_separator import sCTkSeparator
18
+
19
+ widget_namespace = "scustomtkinter.sctk_separator"
20
+ widget_classname = "sCTkSeparator"
21
+ builder_namespace = "scustomtkinter"
22
+ section_name = "sCustomTkinter"
23
+
24
+
25
+ class sCTkSeparatorBuilder(CTkCanvasBO):
26
+ class_ = sCTkSeparator
27
+
28
+ OPTIONS_CUSTOM = ("length", "width", "corner_radius", "orientation", "text", "dash")
29
+ properties = OPTIONS_CUSTOM
30
+ code_properties = OPTIONS_CUSTOM
31
+
32
+ def _get_init_args(self, extra_init_args: dict = None):
33
+ """Force orientation to be processed first during initialization maps."""
34
+ args = super()._get_init_args(extra_init_args)
35
+ ordered_args = {
36
+ "orientation": args.get("orientation", "vertical") or "vertical",
37
+ "length": args.get("length"),
38
+ "width": args.get("width"),
39
+ "corner_radius": args.get("corner_radius"),
40
+ "text": args.get("text", ""),
41
+ "dash": args.get("dash", None)
42
+ }
43
+ for k, v in args.items():
44
+ if k not in ordered_args:
45
+ ordered_args[k] = v
46
+ return ordered_args
47
+
48
+ def realize(self, parent, extra_init_args: dict = None):
49
+ """Constructs our custom class safely using extracted parameter inputs."""
50
+ args = self._get_init_args(extra_init_args)
51
+
52
+ length = int(args.get("length")) if args.get("length") else 100
53
+ width = int(args.get("width")) if args.get("width") else 4
54
+
55
+ default_radius = THEME_DEFAULTS.get("sCTkSeparator", {}).get("corner_radius", 6)
56
+ corner_radius = int(args.get("corner_radius")) if args.get("corner_radius") else default_radius
57
+
58
+ orientation = args.get("orientation")
59
+ text = args.get("text", "")
60
+
61
+ dash_val = args.get("dash", None)
62
+ dash = None
63
+ if dash_val and str(dash_val).strip().lower() != "none":
64
+ dash = tuple(
65
+ int(x.strip()) for x in str(dash_val).replace("(", "").replace(")", "").split(",") if x.strip())
66
+
67
+ self.widget = sCTkSeparator(
68
+ master=parent.widget,
69
+ length=length,
70
+ width=width,
71
+ corner_radius=corner_radius,
72
+ orientation=orientation,
73
+ text=text,
74
+ dash=dash
75
+ )
76
+ return self.widget
77
+
78
+ def _set_property(self, target, pname, value):
79
+ """Captures live setting adjustments directly from Pygubu UI panel modifications."""
80
+ if pname in self.OPTIONS_CUSTOM:
81
+ if hasattr(self, "widget") and self.widget:
82
+ if value == "" or value is None:
83
+ if pname == "width":
84
+ fallback = 4
85
+ elif pname == "length":
86
+ fallback = 100
87
+ elif pname == "corner_radius":
88
+ fallback = THEME_DEFAULTS.get("sCTkSeparator", {}).get("corner_radius", 6)
89
+ elif pname == "text":
90
+ fallback = ""
91
+ elif pname == "dash":
92
+ fallback = None
93
+ else:
94
+ fallback = "vertical"
95
+ self.widget.configure(**{pname: fallback}, require_redraw=True)
96
+ else:
97
+ if pname in ("length", "width", "corner_radius"):
98
+ self.widget.configure(**{pname: int(value)}, require_redraw=True)
99
+ else:
100
+ self.widget.configure(**{pname: value}, require_redraw=True)
101
+ else:
102
+ super()._set_property(target, pname, value)
103
+
104
+ def _process_property_value(self, pname, value):
105
+ if pname in ("length", "width", "corner_radius"):
106
+ if value:
107
+ try:
108
+ return int(value)
109
+ except ValueError:
110
+ return None
111
+ return super()._process_property_value(pname, value)
112
+
113
+ def code_imports(self):
114
+ return [(widget_namespace, widget_classname)]
115
+
116
+ def _code_set_properties(self, targetid, code_creator):
117
+ super()._code_set_properties(targetid, code_creator)
118
+ for pname in self.OPTIONS_CUSTOM:
119
+ val = self.wproperties.get(pname, None)
120
+ if val is not None and val != "":
121
+ if pname in ("orientation", "text"):
122
+ code_creator.add_configure_line(targetid, pname, f"'{val}'")
123
+ elif pname == "dash":
124
+ code_creator.add_configure_line(targetid, pname, str(val))
125
+ else:
126
+ code_creator.add_configure_line(targetid, pname, str(val))
127
+
128
+
129
+ # Register the widget into Pygubu's parsing engine
130
+ builder_id = f"{builder_namespace}.{widget_classname}"
131
+ register_widget(builder_id, sCTkSeparatorBuilder, 'sCTkSeparator', ("ttk", section_name))
132
+
133
+ # Register properties
134
+ # Register properties panel layout help labels
135
+ register_custom_property(builder_id, "length", "naturalnumber", help="Set total span length of the separator line in pixels")
136
+ register_custom_property(builder_id, "width", "naturalnumber", help="Set visual thickness profile width of the separator line in pixels")
137
+ register_custom_property(builder_id, "corner_radius", "naturalnumber", help="Define roundness sharpness limit token value for divider tips")
138
+ register_custom_property(builder_id, "orientation", "choice", values=("vertical", "horizontal"), help="Select spatial directional positioning alignment")
139
+ register_custom_property(builder_id, "text", "entry", help="Add centered section header string text directly inside the line track split")
140
+ register_custom_property(builder_id, "dash", "entry", help="Enter integer dash sequence mapping tuple format (e.g. 5,5 or 2,8)")
141
+
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ sCTkSlider
4
+
5
+ derived from slider
6
+
7
+ UI source file: sCTkSlider.ui
8
+ """
9
+ import tkinter as tk
10
+ import tkinter.ttk as ttk
11
+ from customtkinter import CTkSlider
12
+ from pygubu.api.v1 import (
13
+ BuilderObject,
14
+ register_widget,
15
+ )
16
+
17
+ from pygubu.plugins.customtkinter.widgets import CTkSliderBO
18
+ from scustomtkinter.sctk_slider import sCTkSlider
19
+
20
+
21
+ #
22
+ # Builder definition section
23
+ #
24
+ widget_namespace = "scustomtkinter.sctk_slider"
25
+ widget_classname = "sCTkSlider"
26
+ builder_namespace = "scustomtkinter"
27
+ section_name = "sCustomTkinter"
28
+
29
+
30
+ class sCTkSliderBO(CTkSliderBO):
31
+ class_ = sCTkSlider
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, sCTkSliderBO, widget_classname, ("ttk", section_name)
44
+ )
45
+
@@ -0,0 +1,200 @@
1
+ import pygubu
2
+ from pygubu.api.v1 import (
3
+ BuilderObject,
4
+ register_widget,
5
+ register_custom_property
6
+ )
7
+ from scustomtkinter.sctk_spinbox import sCTkSpinbox
8
+ from pygubu.plugins.customtkinter.widgets import CTkFrameBO
9
+
10
+ widget_namespace = "scustomtkinter.sctk_spinbox"
11
+ widget_classname = "sCTkSpinbox"
12
+ builder_namespace = "scustomtkinter"
13
+ section_name = "sCustomTkinter"
14
+
15
+
16
+ class sCTkSpinboxBO(BuilderObject):
17
+ class_ = sCTkSpinbox
18
+
19
+ # 📐 STREAMLINED PROPERTY MATRIX: Removed textvariable entirely to isolate programmatic get/set paths!
20
+ # Centralized typography assets and custom configuration keys match your widget core exactly.
21
+ OPTIONS_CUSTOM = ('from_', 'to', 'step_size', 'format', 'button_width',
22
+ 'button_height', 'button_side', 'orientation',
23
+ 'arrow_font_size', 'arrow_up_char',
24
+ 'arrow_down_char', 'arrow_right_char', 'arrow_left_char',
25
+ 'state', 'justify', 'placeholder_text', 'values', 'wrap',
26
+ 'command')
27
+
28
+ # FIX: 'command' was registered with register_custom_property() and listed
29
+ # in command_properties, but never appeared in OPTIONS_CUSTOM -- and
30
+ # `properties` is built from CTkFrameBO.properties + OPTIONS_CUSTOM, which
31
+ # does not contain it either. So the callback field was absent from the
32
+ # inspector entirely and no value-change handler could be wired.
33
+ #
34
+ # The loops in _get_init_args() and code_get_init_args() already `continue`
35
+ # on 'command', which shows it was meant to be in this tuple: they were
36
+ # written to skip it, and it was never there to skip.
37
+
38
+ properties = CTkFrameBO.properties + OPTIONS_CUSTOM
39
+ command_properties = ("command",)
40
+
41
+ container = False
42
+
43
+ @classmethod
44
+ def setup_properties(cls):
45
+ super().setup_properties()
46
+ for prop in cls.OPTIONS_CUSTOM:
47
+ if prop not in cls.properties:
48
+ cls.properties = cls.properties + (prop,)
49
+
50
+ def _process_property_value(self, name, value):
51
+ """Sanitizes property input types, protecting string specifiers from truncation."""
52
+ if value is None or str(value).strip() == "":
53
+ return None
54
+ if name in ('button_width', 'button_height', 'arrow_font_size'):
55
+ return int(str(value).strip())
56
+ if name in ('from_', 'to', 'step_size'):
57
+ return float(str(value).strip())
58
+ if name == 'wrap':
59
+ return str(value).lower() in ("true", "1", "yes")
60
+ # Custom characters, values strings, and format masks pass completely un-mangled
61
+ return str(value).strip()
62
+
63
+ def _get_init_args(self, extra_init_args=None):
64
+ """Assembles early configuration properties sequentially into initialization pools."""
65
+ args = super()._get_init_args(extra_init_args)
66
+ w_props = self.wmeta.properties if (hasattr(self, 'wmeta') and hasattr(self.wmeta, 'properties')) else {}
67
+
68
+ for prop in self.OPTIONS_CUSTOM:
69
+ if prop == 'command':
70
+ continue
71
+ val = w_props.get(prop)
72
+ if val is not None and str(val).strip() != "":
73
+ args[prop] = self._process_property_value(prop, val)
74
+
75
+ if 'from_' not in args or args['from_'] is None:
76
+ args['from_'] = 0.0
77
+
78
+ return args
79
+
80
+ def set_property(self, name, value):
81
+ """Routes dynamic inspector panel property adjustments straight into the live instance."""
82
+ if hasattr(self, 'wmeta') and hasattr(self.wmeta, 'properties'):
83
+ self.wmeta.properties[name] = value
84
+
85
+ if hasattr(self, 'widget') and self.widget:
86
+ if name == 'command':
87
+ processed = self.builder.get_callback(value)
88
+ else:
89
+ processed = self._process_property_value(name, value)
90
+
91
+ if processed is not None or name == 'command':
92
+ self.widget.configure(**{name: processed})
93
+
94
+ def _code_define_callback_args(self, cmd_pname, cmd):
95
+ """
96
+ Declares that `command` receives the new value, so the generated stub
97
+ has a matching parameter.
98
+
99
+ The widget already tolerates either shape -- it calls the callback with
100
+ a value and falls back to calling it with none on TypeError -- so a
101
+ stub with no arguments *worked*, which is why this went unnoticed. It
102
+ also meant a user writing their own handler had no indication that a
103
+ value was available.
104
+ """
105
+ return ("value",)
106
+
107
+ def _code_set_property(self, targetid, pname, value, code_bag):
108
+ """
109
+ Emits `values` as a real Python list, not a quoted string.
110
+
111
+ FIX: code_get_init_args() was hardened first, but that is the wrong
112
+ path -- pygubu emits these properties through configure(), so the
113
+ default handler wrapped the bracketed value in double quotes it already
114
+ contained:
115
+
116
+ sctkspinbox1.configure(values="["VW","Porsche"]", wrap=True)
117
+ ^
118
+ SyntaxError: invalid decimal literal
119
+
120
+ Emitting the literal unquoted gives valid Python and a real list, so
121
+ the widget receives one directly rather than parsing a string back out
122
+ of it. Same approach sCTkSelectorbo uses for `items` and sCTkDialbo for
123
+ `labels`.
124
+
125
+ A bare comma-separated value has no brackets and no quotes, so it is
126
+ emitted as a quoted string and parsed by the widget as usual.
127
+ """
128
+ if pname == "values" and value:
129
+ text = str(value).strip()
130
+ if text.startswith("[") and text.endswith("]"):
131
+ code_bag[pname] = text
132
+ return None
133
+ return super()._code_set_property(targetid, pname, value, code_bag)
134
+
135
+ def code_get_configure_properties(self, code_identifier, entry):
136
+ """Instructs Pygubu's compiler to pipe callbacks through native channels."""
137
+ return ['command']
138
+
139
+ def code_get_init_args(self, code_identifier, entry):
140
+ """Emits pristine syntax definitions for compilation into final exported Python scripts."""
141
+ init_args = {}
142
+ w_props = self.wmeta.properties if (hasattr(self, 'wmeta') and hasattr(self.wmeta, 'properties')) else {}
143
+ for prop in self.OPTIONS_CUSTOM:
144
+ if prop == 'command':
145
+ continue
146
+ value = w_props.get(prop)
147
+ if value is not None and value != '':
148
+ processed = self._process_property_value(prop, value)
149
+ if processed is not None:
150
+ # FIX: was f'"{processed}"' for the string properties --
151
+ # manual double-quoting, which breaks the moment the value
152
+ # itself contains a double quote. The bracketed list form
153
+ # this widget's own help text recommends does exactly that:
154
+ #
155
+ # values="["A", "B"]"
156
+ # ^^^^^^^^^^^
157
+ # SyntaxError: invalid syntax
158
+ #
159
+ # repr() quotes correctly for every value, choosing the
160
+ # delimiter that does not clash. It was already used for
161
+ # everything else in this method.
162
+ init_args[prop] = repr(processed)
163
+ return init_args
164
+
165
+
166
+ # =============================================================================
167
+ # 🎨 INSPECTOR PANEL PROPERTY REGISTRATION (ORDER ENFORCED)
168
+ # =============================================================================
169
+ # Ensure this section sits firmly flush against the left wall margin!
170
+ builder_id = f"{builder_namespace}.{widget_classname}"
171
+
172
+ register_custom_property(builder_id, 'format', 'entry',
173
+ help='Number format. Accepts "{:.2f}" (full brace form), ":.2f" '
174
+ '(braces added for you), or "%.2f" (printf style). '
175
+ 'Leave blank for no formatting.'
176
+ )
177
+ register_custom_property(builder_id, 'from_', 'entry')
178
+ register_custom_property(builder_id, 'to', 'entry')
179
+ register_custom_property(builder_id, 'step_size', 'entry')
180
+ register_custom_property(builder_id, 'values', 'entry',
181
+ help='Preferred: ["A", "B", "C"]. Bare comma-separated (A, B, C) also works, but cannot contain a comma inside a value.')
182
+ register_custom_property(builder_id, 'wrap', 'choice', values=('True', 'False'))
183
+ register_custom_property(builder_id, 'button_width', 'naturalnumber')
184
+ register_custom_property(builder_id, 'button_height', 'naturalnumber')
185
+ register_custom_property(builder_id, 'button_side', 'choice', values=('right', 'left', 'split'))
186
+ register_custom_property(builder_id, 'orientation', 'choice', values=('vertical', 'horizontal'))
187
+ register_custom_property(builder_id, 'arrow_font_size', 'naturalnumber')
188
+
189
+ register_custom_property(builder_id, 'arrow_up_char', 'entry')
190
+ register_custom_property(builder_id, 'arrow_down_char', 'entry')
191
+ register_custom_property(builder_id, 'arrow_right_char', 'entry')
192
+ register_custom_property(builder_id, 'arrow_left_char', 'entry')
193
+
194
+ register_custom_property(builder_id, 'state', 'choice', values=('normal', 'disabled'))
195
+ register_custom_property(builder_id, 'justify', 'choice', values=('left', 'center', 'right'))
196
+ register_custom_property(builder_id, 'placeholder_text', 'entry')
197
+ register_custom_property(builder_id, 'command', 'commandentry')
198
+
199
+ # FLUSH MARGIN LEFT SCOPE EXECUTION FOR PYGUBU DISCOVERY
200
+ register_widget(builder_id, sCTkSpinboxBO, 'sCTkSpinbox', ("ttk", section_name))
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ sCTKSwitch
4
+
5
+ derived from ctk switch
6
+
7
+ UI source file: sCTkSwitch.ui
8
+ """
9
+ import tkinter as tk
10
+ import tkinter.ttk as ttk
11
+ from customtkinter import CTkSwitch
12
+ from pygubu.api.v1 import (
13
+ BuilderObject,
14
+ register_widget,
15
+ )
16
+
17
+ from pygubu.plugins.customtkinter.widgets import CTkSwitchBO
18
+ from scustomtkinter.sctk_switch import sCTkSwitch
19
+
20
+
21
+ #
22
+ # Builder definition section
23
+ #
24
+ widget_namespace = "scustomtkinter.sctk_switch"
25
+ widget_classname = "sCTkSwitch"
26
+ builder_namespace = "scustomtkinter"
27
+ section_name = "sCustomTkinter"
28
+
29
+
30
+ class sCTkSwitchBO(CTkSwitchBO):
31
+ class_ = sCTkSwitch
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, sCTkSwitchBO, widget_classname, ("ttk", section_name)
44
+ )
45
+
@@ -0,0 +1,188 @@
1
+ import pygubu
2
+ from pygubu.api.v1 import (
3
+ BuilderObject,
4
+ register_widget,
5
+ register_custom_property
6
+ )
7
+ from scustomtkinter.sctk_tableview import sCTkTableview
8
+ from scustomtkinter.sctk_tableview import sCTkTableview
9
+ from scustomtkinter.themeable_widget import parse_list_property
10
+
11
+ widget_namespace = "scustomtkinter.sctk_tableview"
12
+ widget_classname = "sCTkTableview"
13
+ builder_namespace = "scustomtkinter"
14
+ section_name = "sCustomTkinter"
15
+
16
+
17
+ class sCTkTableviewBO(BuilderObject):
18
+ """
19
+ Pygubu Designer visual studio object builder registration helper subclass.
20
+ 🚀 PROPERTY SEQUENCE PATCH: Rearranged so that structural grid counts
21
+ load and evaluate BEFORE the string column parser executes!
22
+ """
23
+ class_ = sCTkTableview
24
+
25
+ # 📐 Structural options declared first, layout payload definitions placed dead last!
26
+ OPTIONS_CUSTOM = ('num_columns', 'num_rows', 'grid_mode', 'show_headers', 'header_line_width', 'outline_width',
27
+ 'outline_radius', 'state', 'cell_bg_color', 'cell_alt_bg_color',
28
+ 'editable_columns', 'edit_trigger', 'select_rows', 'columns')
29
+ properties = OPTIONS_CUSTOM
30
+
31
+ container = False
32
+
33
+ @classmethod
34
+ def setup_properties(cls):
35
+ super().setup_properties()
36
+ for prop in cls.OPTIONS_CUSTOM:
37
+ if prop not in cls.properties:
38
+ cls.properties = cls.properties + (prop,)
39
+
40
+ def get_layout_properties(self):
41
+ props = super().get_layout_properties() if hasattr(super(), 'get_layout_properties') else {}
42
+ for custom_prop in self.OPTIONS_CUSTOM:
43
+ props.pop(custom_prop, None)
44
+ return props
45
+
46
+ def layout(self, target=None, *args, **kwargs):
47
+ """
48
+ Lays the table out, minus any of its own properties.
49
+
50
+ ACCEPTS AND PASSES ON what pygubu sends. This was `layout(self)`,
51
+ with no parameters, while pygubu's own is
52
+ `layout(self, target=None, *, forget=False)` -- and it is not always
53
+ called bare. Called with a target, as when the Designer builds a
54
+ preview, the override raised TypeError before laying anything out.
55
+ Pygubu catches errors while building a preview, so the result was an
56
+ empty preview window and nothing on the console. sCTkNotebookbo's
57
+ tab takes `target` for the same reason.
58
+ """
59
+ if hasattr(self, 'wmeta') and hasattr(self.wmeta, 'layout_properties'):
60
+ clean_properties = dict(self.wmeta.layout_properties)
61
+ for custom_prop in self.OPTIONS_CUSTOM:
62
+ clean_properties.pop(custom_prop, None)
63
+ self.wmeta.layout_properties = clean_properties
64
+ super().layout(target, *args, **kwargs)
65
+
66
+ def _process_property_value(self, name, value):
67
+ if name == 'columns':
68
+ # Shared parser: accepts ["A", "B"], "A, B" or a real list, so this
69
+ # property behaves identically to sCTkSelector's items and
70
+ # sCTkDialSelector's labels. The previous comma-only split turned
71
+ # the quoted-list form into garbage -- ['["A"', '"B"]'].
72
+ return parse_list_property(value)
73
+
74
+ if value is None or str(value).strip() == "":
75
+ return None
76
+ if name in ('num_columns', 'num_rows', 'header_line_width', 'outline_radius'):
77
+ return int(str(value).strip())
78
+ if name in ('show_headers', 'select_rows'):
79
+ return str(value).lower() in ("true", "1", "yes")
80
+ # editable_columns stays a string -- "1, 2" -- and the widget parses
81
+ # it, the same way it parses `columns`. Blank already returned None
82
+ # above, which is the widget's "every column" default.
83
+ return str(value)
84
+
85
+ def _get_init_args(self, extra_init_args=None):
86
+ args = super()._get_init_args(extra_init_args)
87
+ w_props = self.wmeta.properties if (hasattr(self, 'wmeta') and hasattr(self.wmeta, 'properties')) else {}
88
+
89
+ args['num_columns'] = int(w_props.get('num_columns') if w_props.get('num_columns') else 3)
90
+ args['num_rows'] = int(w_props.get('num_rows') if w_props.get('num_rows') else 1)
91
+ args['grid_mode'] = str(w_props.get('grid_mode') if w_props.get('grid_mode') else "zebra")
92
+
93
+ # FIXED: Pass state down during rebuild instantiation passes!
94
+ args['state'] = str(w_props.get('state') if w_props.get('state') else "normal")
95
+
96
+ raw_cols = w_props.get('columns')
97
+ if raw_cols:
98
+ args['columns'] = self._process_property_value('columns', raw_cols)
99
+ args['num_columns'] = len(args['columns'])
100
+
101
+ args['show_headers'] = self._process_property_value('show_headers', w_props.get('show_headers') or True)
102
+
103
+ # Editing and selection. Absent, each keeps the widget's default --
104
+ # every column editable, by double-click, no row highlighted -- so a
105
+ # table already in a .ui file is unchanged.
106
+ args['editable_columns'] = self._process_property_value(
107
+ 'editable_columns', w_props.get('editable_columns'))
108
+ args['edit_trigger'] = str(w_props.get('edit_trigger') or "double")
109
+ args['select_rows'] = bool(self._process_property_value(
110
+ 'select_rows', w_props.get('select_rows') or "False"))
111
+ return args
112
+
113
+ def _set_property(self, backend_widget, pname, value):
114
+ """Intercepts and forces live visual canvas property redraw updates instantly."""
115
+ if pname in self.OPTIONS_CUSTOM:
116
+ processed_val = self._process_property_value(pname, value)
117
+ backend_widget.configure(**{pname: processed_val})
118
+ else:
119
+ super()._set_property(backend_widget, pname, value)
120
+
121
+ def set_property(self, name, value):
122
+ # 1. Update the runtime property backing variables
123
+ if hasattr(self, 'wmeta') and hasattr(self.wmeta, 'properties'):
124
+ self.wmeta.properties[name] = value
125
+
126
+ # 2. Call our custom _set_property to handle real-time color repainting on the active canvas object
127
+ if hasattr(self, 'widget') and self.widget:
128
+ self._set_property(self.widget, name, value)
129
+
130
+ # 3. FIXED: Force full visual object recreation on layout structural edits OR state swaps
131
+ # to ensure Pygubu-Designer forces a total clean redrawing window sync pass!
132
+ if name in ('num_columns', 'num_rows', 'columns', 'show_headers', 'state', 'grid_mode'):
133
+ if hasattr(self, "builder") and hasattr(self.builder, "recreate_widget"):
134
+ try:
135
+ self.builder.recreate_widget(self)
136
+ except Exception:
137
+ pass
138
+
139
+
140
+ def code_get_configure_properties(self, code_identifier, entry):
141
+ return []
142
+
143
+ def code_get_init_args(self, code_identifier, entry):
144
+ init_args = {}
145
+ w_props = self.wmeta.properties if (hasattr(self, 'wmeta') and hasattr(self.wmeta, 'properties')) else {}
146
+ for prop in self.OPTIONS_CUSTOM:
147
+ value = w_props.get(prop)
148
+ if value is not None and value != '':
149
+ processed = self._process_property_value(prop, value)
150
+ if processed is not None:
151
+ init_args[prop] = repr(processed)
152
+ return init_args
153
+
154
+
155
+ # =============================================================================
156
+ # 🎨 INSPECTOR PANEL PROPERTY REGISTRATION (ORDER ENFORCED)
157
+ # =============================================================================
158
+ builder_id = f"{builder_namespace}.{widget_classname}"
159
+
160
+ register_custom_property(builder_id, 'num_columns', 'naturalnumber')
161
+ register_custom_property(builder_id, 'num_rows', 'naturalnumber')
162
+ register_custom_property(builder_id, 'grid_mode', 'choice', values=('zebra', 'grid', 'none'))
163
+ register_custom_property(builder_id, 'show_headers', 'choice', values=('True', 'False'))
164
+ register_custom_property(builder_id, 'header_line_width', 'naturalnumber')
165
+ register_custom_property(builder_id, 'outline_width', 'integernumber')
166
+ register_custom_property(builder_id, 'outline_radius', 'naturalnumber')
167
+ register_custom_property(builder_id, 'state', 'choice', values=('normal', 'disabled'))
168
+ register_custom_property(builder_id, 'cell_bg_color', 'entry')
169
+ register_custom_property(builder_id, 'cell_alt_bg_color', 'entry')
170
+
171
+ register_custom_property(
172
+ builder_id, 'editable_columns', 'entry',
173
+ help="Which columns can be edited, by index -- e.g. 1, 2. Blank means "
174
+ "every column. The rest are read-only.")
175
+ register_custom_property(
176
+ builder_id, 'edit_trigger', 'choice', values=('double', 'select'),
177
+ help="double: double-click a cell to edit it. select: click a cell in the "
178
+ "row that is already selected, which leaves double-click free for "
179
+ "bind_activate_callback.")
180
+ register_custom_property(
181
+ builder_id, 'select_rows', 'choice', values=('True', 'False'),
182
+ help="Highlight the row last clicked. Always on with edit_trigger "
183
+ "'select', which needs a selection to work.")
184
+
185
+ # 👑 Columns text registration placed dead last!
186
+ register_custom_property(builder_id, 'columns', 'entry', "","Enter list of columns e.g., ['One','Two','Three']")
187
+
188
+ register_widget(builder_id, sCTkTableviewBO, 'sCTkTableview', ("ttk", section_name))