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,342 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ sCTkEntrySecondary
4
+
5
+ A theme-compliant, lower-emphasis single-line text entry widget (see also
6
+ sCTkEntryPrimary). Inherits directly from ctk.CTkEntry so CustomTkinter
7
+ handles native rendering and text editing; this class layers automatic
8
+ light/dark theme resolution and a distinct enabled/disabled visual state on
9
+ top.
10
+
11
+ Base class order matters here: `class sCTkEntrySecondary(ctk.CTkEntry,
12
+ ThemeableWidget)` puts the native CTk class first, so every `super()` call in
13
+ this file's own methods resolves to ctk.CTkEntry -- and, beneath it,
14
+ tkinter.Misc -- never to ThemeableWidget. ThemeableWidget's own
15
+ configure()/cget()/_set_appearance_mode() overrides have been removed entirely
16
+ for this reason (see themeable_widget.py's docstring); this widget owns all of
17
+ its own runtime color-swapping logic.
18
+
19
+ IMPORTANT: disabling uses CTk's native state="disabled" -- confirmed correct
20
+ by direct testing, matching every other widget in this library. See
21
+ sCTkEntryPrimary's module docstring for the fuller history (an earlier version
22
+ used native "readonly" on an unverified claim; tested and found unnecessary
23
+ for the disabled/normal distinction).
24
+
25
+ Now supports a genuine three-state model: normal / readonly / disabled, same
26
+ as sCTkEntryPrimary -- see that widget's module docstring for the full
27
+ rationale and the readonly-specific caveat (state="readonly" is not
28
+ independently confirmed by direct testing the way normal/disabled were).
29
+ """
30
+ from typing import Any, Optional
31
+ import customtkinter as ctk
32
+ from .themeable_widget import ThemeableWidget, defer
33
+
34
+
35
+ class sCTkEntrySecondary(ctk.CTkEntry, ThemeableWidget):
36
+ """Themeable, lower-emphasis text entry field.
37
+
38
+ Adds to native ctk.CTkEntry:
39
+ - Automatic light/dark theme resolution from sCTkThemes.json (via
40
+ ThemeableWidget.__init__ -- see that class's docstring for what it does,
41
+ and just as importantly, what it no longer does).
42
+ - A genuine three-state visual model: normal / readonly / disabled --
43
+ see sCTkEntryPrimary's docstring for the full rationale.
44
+ - Pygubu Designer property introspection for `state`, `fg_color`,
45
+ `text_color`, `border_color`, and `placeholder_text_color` via a
46
+ single-argument configure() call.
47
+
48
+ Colors are passed through to configure() as raw (light, dark) tuples rather
49
+ than pre-resolved to a single value, so CustomTkinter's own appearance-mode
50
+ tracking repaints them automatically on a light/dark switch -- the same
51
+ approach validated on sCTkComboBox, sCTkSegmentedButton, and the button
52
+ family. Not separately re-confirmed for this specific widget.
53
+
54
+ placeholder_text_color is a real, themed key (not a fallback to
55
+ text_color) -- see sCTkEntryPrimary's docstring for the full rationale.
56
+ CTkEntry has no separate font for placeholder text vs. typed text -- both
57
+ always share the single `font` property; that's a ceiling in the
58
+ underlying widget, not a gap in this theme file.
59
+ """
60
+
61
+ def __init__(self, master: Optional[Any] = None, **kw: Any) -> None:
62
+ """
63
+ Args:
64
+ master: Parent container.
65
+ **kw: Any native CTkEntry argument (e.g. `placeholder_text`,
66
+ `width`), or a theme-key override (see the "sCTkEntrySecondary"
67
+ block in sCTkThemes.json, including its disabled_map).
68
+ """
69
+ # 1. Fire our shared theme logic first. This resolves final_kw
70
+ # (construction-time properties) and the disabled color map. See
71
+ # ThemeableWidget.__init__ for what actually happens here.
72
+ ThemeableWidget.__init__(self, kw)
73
+
74
+ # 2. Deep-copy the resolved map onto this instance, so later changes
75
+ # here never leak back into the shared theme registry.
76
+ self._local_defaults = dict(self.final_kw)
77
+ self._custom_disabled_map = dict(self._widget_disabled_map)
78
+ # readonly_map: see sCTkEntryPrimary's module docstring for the
79
+ # three-state model this supports. Required only when readonly is
80
+ # actually requested -- see _update_current_visual_state()'s validation.
81
+ self._custom_readonly_map = dict(self._widget_readonly_map)
82
+
83
+ # Extract "state" from final_kw after ThemeableWidget's merge -- an
84
+ # explicit state= kwarg correctly overrides any "state" the theme
85
+ # JSON might define at the top level.
86
+ state_init = self.final_kw.pop("state", "normal")
87
+
88
+ # 3. Initialize CustomTkinter natively with the clean final kwargs array.
89
+ super().__init__(master, **self.final_kw)
90
+
91
+ # 4. Apply the requested initial state now that the native widget exists.
92
+ self._custom_current_state = "normal"
93
+ self.state(state_init)
94
+
95
+ # 5. Register lifecycle handshake hook, notifying Pygubu-style consumers
96
+ # that construction is complete.
97
+ self._finalize_themeable_lifecycle()
98
+
99
+ def configure(self, *args: Any, **kwargs: Any) -> Any:
100
+ """
101
+ Standard widget configuration, with Pygubu/positional-argument handling.
102
+
103
+ Args:
104
+ *args: At most one positional argument is meaningful:
105
+ - a dict: merged into kwargs and processed normally below.
106
+ - the literal string "state": returns a Tkinter-style
107
+ (name, name, name, default, current) tuple.
108
+ - one of "fg_color"/"text_color"/"border_color"/
109
+ "placeholder_text_color": returns the same style of tuple,
110
+ with `current` reflecting the disabled or normal value as
111
+ appropriate. Note the returned value is str(value), where
112
+ value may itself be a (light, dark) tuple rather than a
113
+ single resolved color -- a known limitation shared with the
114
+ wider Pygubu-query investigation set aside elsewhere in this
115
+ project, not fixed here.
116
+ - anything else: forwarded directly to the native widget's
117
+ configure(), which does not support single-argument property
118
+ queries for arbitrary properties (same limitation).
119
+ **kwargs: Standard CTkEntry configuration options, plus: passing
120
+ `state=...` routes through self.state() rather than being
121
+ forwarded as-is.
122
+
123
+ Returns:
124
+ The query tuple described above for the single-argument case, or
125
+ whatever super().configure() returns for the keyword-argument case
126
+ (typically None).
127
+ """
128
+ # args is always a tuple -- args[0] is the actual value passed, whether
129
+ # that's a string or a dict. An earlier version of this method compared
130
+ # the wrapped tuple directly (`pname = args`), so the query branches
131
+ # below never matched anything. Don't reintroduce that.
132
+ if len(args) == 1:
133
+ if isinstance(args[0], dict):
134
+ kwargs = {**args[0], **kwargs}
135
+ else:
136
+ pname = args[0]
137
+ if pname == "state":
138
+ return ("state", "state", "state", "normal", str(self.state()))
139
+
140
+ if pname in ["fg_color", "text_color", "border_color", "placeholder_text_color"]:
141
+ current = self._custom_current_state
142
+ if current == "disabled":
143
+ val = self._custom_disabled_map.get(pname)
144
+ elif current == "readonly":
145
+ val = self._custom_readonly_map.get(pname)
146
+ else:
147
+ val = self._local_defaults.get(pname)
148
+ return (pname, pname, pname, self._query_value(self._theme_default(pname)), self._query_value(val))
149
+
150
+ return self._configure_query(pname)
151
+
152
+ # Runtime overrides have to reach the map a repaint reads, or the
153
+ # repaint puts the theme value straight back -- see
154
+ # ThemeableWidget._record_theme_overrides().
155
+ self._record_theme_overrides(kwargs)
156
+
157
+ if "state" in kwargs:
158
+ self.state(kwargs.pop("state"))
159
+
160
+ self._drop_cleared(kwargs)
161
+
162
+ if kwargs:
163
+ return super().configure(**kwargs)
164
+ return None
165
+
166
+ # Tkinter/CTk convention binds .config to .configure as a SEPARATE class
167
+ # attribute -- it does not automatically track whichever configure() a
168
+ # subclass defines. Without this line, calling .config(...) on an instance
169
+ # would silently skip this entire override and land on the native widget's
170
+ # configure() directly, bypassing theming and state handling entirely.
171
+ config = configure
172
+
173
+ def _set_appearance_mode(self, mode_string: str) -> None:
174
+ """
175
+ Forwards CustomTkinter's internal light/dark mode change notification to
176
+ the native widget.
177
+
178
+ No longer manually re-triggers _update_current_visual_state(). That
179
+ method now passes raw (light, dark) tuples straight through to
180
+ configure() instead of pre-resolving to a single color, so CTk's own
181
+ appearance-mode tracking should repaint correctly on its own -- the
182
+ same approach validated on sCTkComboBox, sCTkSegmentedButton, and the
183
+ button family.
184
+
185
+ Args:
186
+ mode_string: The new appearance mode ("Light" or "Dark"), as passed
187
+ by CustomTkinter's internal appearance-mode change machinery.
188
+ """
189
+ if hasattr(super(), "_set_appearance_mode"):
190
+ try:
191
+ super()._set_appearance_mode(mode_string)
192
+ except Exception:
193
+ pass
194
+
195
+ def _write_through_state(self, action, *args, **kwargs):
196
+ """
197
+ Runs a text-modifying call even when the field is disabled.
198
+
199
+ A disabled Tk entry silently ignores insert() and delete() -- no error,
200
+ no text. That bites in generated code, because pygubu emits the state
201
+ before the content:
202
+
203
+ sctkentryprimary1.configure(state="disabled")
204
+ sctkentryprimary1.delete(0, "end")
205
+ sctkentryprimary1.insert(0, 'mark')
206
+
207
+ which produced a permanently empty disabled field. Setting text on a
208
+ disabled widget is a normal thing for an application to do -- the state
209
+ is about what the USER may edit, not about what the program may write.
210
+
211
+ The native state is lifted for the write and restored afterwards, which
212
+ is the same approach sCTkPathChooser.set() already uses. The widget's
213
+ own _custom_current_state is untouched, so the colours do not flicker.
214
+
215
+ Args:
216
+ action: The bound native method to call.
217
+ *args, **kwargs: Passed through to it.
218
+
219
+ Returns:
220
+ Whatever the native method returns.
221
+ """
222
+ was_disabled = self._custom_current_state == "disabled"
223
+ if was_disabled:
224
+ try:
225
+ super().configure(state="normal")
226
+ except Exception:
227
+ was_disabled = False
228
+ try:
229
+ return action(*args, **kwargs)
230
+ finally:
231
+ if was_disabled:
232
+ try:
233
+ super().configure(state="disabled")
234
+ except Exception:
235
+ pass
236
+
237
+ def insert(self, *args, **kwargs):
238
+ """Inserts text, even while disabled -- see _write_through_state()."""
239
+ return self._write_through_state(super().insert, *args, **kwargs)
240
+
241
+ def delete(self, *args, **kwargs):
242
+ """Deletes text, even while disabled -- see _write_through_state()."""
243
+ return self._write_through_state(super().delete, *args, **kwargs)
244
+
245
+ def get_state(self) -> str:
246
+ """Equivalent to calling state() with no argument."""
247
+ return self.state()
248
+
249
+ def state(self, state_string: Optional[str] = None) -> str:
250
+ """
251
+ Gets or sets the widget's normal/readonly/disabled visual state.
252
+
253
+ Args:
254
+ state_string: If None, returns the current state without changing
255
+ anything. Otherwise: "normal"/"enabled"/"active" all map to
256
+ "normal"; "readonly" (case-insensitive) maps to "readonly";
257
+ "disabled" (case-insensitive) maps to "disabled". Any other
258
+ value leaves the internal state flag unchanged, though
259
+ _update_current_visual_state() still runs.
260
+
261
+ Returns:
262
+ The resulting state ("normal", "readonly", or "disabled", lowercase).
263
+ """
264
+ if state_string is None:
265
+ return getattr(self, "_custom_current_state", "normal")
266
+
267
+ mode = str(state_string).lower()
268
+ if mode in ("normal", "enabled", "active"):
269
+ self._custom_current_state = "normal"
270
+ elif mode == "readonly":
271
+ self._custom_current_state = "readonly"
272
+ elif mode == "disabled":
273
+ self._custom_current_state = "disabled"
274
+
275
+ self._update_current_visual_state()
276
+ return self._custom_current_state
277
+
278
+ def _update_current_visual_state(self) -> None:
279
+ """
280
+ Recomputes and applies this widget's colors from the theme file, based
281
+ on the current state, then sets the native interactive lock.
282
+
283
+ Called after construction and on every state() change.
284
+
285
+ Passes raw (light, dark) tuples straight through to configure() instead
286
+ of resolving to a single color first, so CTk's native tracking can
287
+ handle appearance-mode repaints without help from _set_appearance_mode.
288
+
289
+ fg_color, border_color, text_color, and placeholder_text_color are
290
+ required to be present in readonly_map specifically when readonly is
291
+ the current state -- see sCTkEntryPrimary's identical method for the
292
+ full reasoning. This check only runs when readonly is actually
293
+ requested.
294
+ """
295
+ current = self._custom_current_state
296
+
297
+ if current == "disabled":
298
+ target_map = self._custom_disabled_map
299
+ elif current == "readonly":
300
+ target_map = self._custom_readonly_map
301
+ for required_key in ("fg_color", "border_color", "text_color", "placeholder_text_color"):
302
+ if target_map.get(required_key) is None:
303
+ raise KeyError(
304
+ f"'{self.__class__.__name__}' theme block is missing '{required_key}' "
305
+ f"in readonly_map -- required because state 'readonly' was requested."
306
+ )
307
+ else:
308
+ target_map = self._local_defaults
309
+
310
+ config_payload = {}
311
+ for key in ("fg_color", "border_color", "text_color", "placeholder_text_color"):
312
+ val = target_map.get(key)
313
+ if val is not None:
314
+ config_payload[key] = val
315
+
316
+ if config_payload:
317
+ super().configure(**config_payload)
318
+
319
+ # Confirmed correct by direct testing: native "disabled" properly
320
+ # blocks interaction. state="readonly" is not independently confirmed
321
+ # here -- see sCTkEntryPrimary's module docstring.
322
+ if current == "disabled":
323
+ super().configure(state="disabled")
324
+ else:
325
+ super().configure(state=current)
326
+ defer(self, "idle", self._reset_cursor_if_showing_placeholder)
327
+
328
+ def _reset_cursor_if_showing_placeholder(self) -> None:
329
+ """
330
+ Resets the cursor to position 0, but only if the field is currently
331
+ showing its placeholder text -- never touches the cursor if the field
332
+ holds real user-typed content. See sCTkEntryPrimary's identical
333
+ method for the full reasoning, including why this deliberately avoids
334
+ CTkEntry's private internal placeholder-tracking attributes in favor
335
+ of comparing self.get() against self.cget("placeholder_text") via
336
+ public API only, and the caveat that the readonly transition case is
337
+ a precaution, not independently confirmed the way the disabled<->normal
338
+ transition was.
339
+ """
340
+ placeholder = self.cget("placeholder_text")
341
+ if placeholder and self.get() == placeholder:
342
+ self.icursor(0)