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,346 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ sCTkOptionMenuSecondary
4
+
5
+ The quiet variant of the dropdown option-selection menu (see also
6
+ sCTkOptionMenuPrimary, the emphasised one). Inherits directly from
7
+ ctk.CTkOptionMenu so CustomTkinter handles native rendering and dropdown
8
+ behaviour; this class layers theme resolution, a disabled state, and a border
9
+ on top.
10
+
11
+ FORMERLY A COMPOSITE. This widget used to be a ctk.CTkFrame wrapping an inner
12
+ ctk.CTkOptionMenu, because native CTkOptionMenu has no border option and this
13
+ variant needs one -- its fill is deliberately close to the background it sits
14
+ on, so without an outline the control is hard to see at rest.
15
+
16
+ That structure cost more than it bought:
17
+
18
+ - values, command and variable lived on the inner menu, so cget("values")
19
+ returned None here while returning a list on Primary. Two widgets meant to
20
+ be interchangeable, disagreeing.
21
+ - Every option CustomTkinter adds to CTkOptionMenu had to be forwarded by
22
+ hand, forever.
23
+ - The outline did not line up. A frame draws its own rounded rectangle
24
+ around a widget that draws another, and at the corners the two diverged
25
+ visibly.
26
+ - The widget's SHAPE was dictated by one theme's colour choices. Swap the
27
+ Primary and Secondary palettes and the structure is wrong.
28
+
29
+ sCTkOptionMenuBorderMixin now supplies the border, so this is a plain subclass
30
+ like Primary. get(), set(), values, command and variable are all native again.
31
+
32
+ Base class order matters. The mixin comes FIRST so its _draw() replaces the
33
+ native one; ctk.CTkOptionMenu comes next, so every other super() call in this
34
+ file resolves to it -- never to ThemeableWidget, whose own
35
+ configure()/cget()/_set_appearance_mode() overrides were removed for this
36
+ reason (see themeable_widget.py).
37
+ """
38
+ from typing import Any, Optional
39
+ import customtkinter as ctk
40
+ from .themeable_widget import ThemeableWidget
41
+ from .sctk_optionmenu_border_mixin import sCTkOptionMenuBorderMixin
42
+
43
+
44
+ class sCTkOptionMenuSecondary(sCTkOptionMenuBorderMixin, ctk.CTkOptionMenu,
45
+ ThemeableWidget):
46
+ """Themeable dropdown option-selection menu, quiet variant.
47
+
48
+ Differs from sCTkOptionMenuPrimary in two ways, both from the theme file
49
+ rather than from anything structural:
50
+
51
+ - A border. border_width and border_color come from this widget's theme
52
+ block via sCTkOptionMenuBorderMixin. Primary supports the same keys and
53
+ simply sets border_width to 0.
54
+ - No distinct arrow button. button_color is set from fg_color, so the
55
+ arrow blends into the control rather than standing out as a separate
56
+ block of colour. Primary gives it its own colour.
57
+
58
+ fg_color and text_color are REQUIRED in whichever map is active -- top
59
+ level for the normal state, disabled_map when disabled. Missing either
60
+ raises rather than substituting a guess, following the library-wide
61
+ fail-loud rule.
62
+ """
63
+
64
+ def __init__(self, master: Optional[Any] = None, width: int = 160,
65
+ height: int = 28, **kw: Any) -> None:
66
+ """
67
+ Args:
68
+ master: Parent container.
69
+ width / height: Widget dimensions in pixels.
70
+ **kw: `values` (list[str]), `command` (callable) and `variable`
71
+ (tkinter.StringVar) are pulled out and applied after the native
72
+ widget exists. Everything else is either a native
73
+ CTkOptionMenu argument or a theme-key override (see the
74
+ "sCTkOptionMenuSecondary" block in sCTkThemes.json).
75
+ """
76
+ # 1. Capture widget-specific attributes before the theme pass, so they
77
+ # are not merged into final_kw and treated as theme properties.
78
+ values = kw.pop("values", None)
79
+ command = kw.pop("command", None)
80
+ variable = kw.pop("variable", None)
81
+
82
+ kw.setdefault("width", width)
83
+ kw.setdefault("height", height)
84
+
85
+ # 2. Shared theme logic: resolves final_kw and the disabled map.
86
+ ThemeableWidget.__init__(self, kw)
87
+
88
+ # 3. Deep-copy onto this instance, so later changes never leak back
89
+ # into the shared theme registry.
90
+ self._local_defaults = dict(self.final_kw)
91
+ self._custom_disabled_map = dict(self._widget_disabled_map)
92
+
93
+ # 4. Initialize CustomTkinter natively.
94
+ #
95
+ # border_width and border_color are REMOVED first. They are this
96
+ # library's own additions -- native CTkOptionMenu has no border option
97
+ # and rejects any keyword it does not recognise, so leaving them in
98
+ # final_kw would raise at construction. _local_defaults above already
99
+ # holds copies for _init_border() to read.
100
+ native_kw = {k: v for k, v in self.final_kw.items()
101
+ if k not in ("border_width", "border_color")}
102
+ super().__init__(master, **native_kw)
103
+
104
+ # 5. Apply the extracted attributes now the native widget exists.
105
+ if values is not None:
106
+ super().configure(values=values)
107
+ if command is not None:
108
+ super().configure(command=command)
109
+ if variable is not None:
110
+ super().configure(variable=variable)
111
+
112
+ # 6. Border, after the native widget exists: the first redraw needs a
113
+ # canvas.
114
+ self._init_border(
115
+ border_width=self._local_defaults.get("border_width", 0),
116
+ border_color=self._local_defaults.get("border_color"),
117
+ )
118
+
119
+ self._custom_current_state = "normal"
120
+ self._update_current_visual_state()
121
+
122
+ # 7. Lifecycle handshake, notifying Pygubu-style consumers that
123
+ # construction is complete.
124
+ self._finalize_themeable_lifecycle()
125
+
126
+ def configure(self, *args: Any, **kwargs: Any) -> Any:
127
+ """
128
+ Standard widget configuration, with Pygubu/positional-argument handling.
129
+
130
+ Args:
131
+ *args: At most one positional argument is meaningful:
132
+ - a dict: merged into kwargs and processed normally below.
133
+ - "state": returns a Tkinter-style
134
+ (name, name, name, default, current) tuple.
135
+ - "border_width" or "border_color": the same, from the mixin.
136
+ - "fg_color"/"button_color"/"button_hover_color"/"text_color":
137
+ the same, with `current` reflecting the disabled or normal
138
+ value as appropriate.
139
+ - anything else: handed to _configure_query(), which builds a
140
+ proper tuple rather than forwarding the name to the native
141
+ configure() -- see themeable_widget.py.
142
+ **kwargs: Standard CTkOptionMenu options, plus `border_width` and
143
+ `border_color`; `values`/`command`/`variable` are routed
144
+ through super().configure() individually, and `state=...` goes
145
+ through self.state().
146
+
147
+ Returns:
148
+ The query tuple for the single-argument case, otherwise whatever
149
+ super().configure() returns.
150
+ """
151
+ if len(args) == 1:
152
+ if isinstance(args[0], dict):
153
+ kwargs = {**args[0], **kwargs}
154
+ else:
155
+ pname = args[0]
156
+ if pname == "state":
157
+ return ("state", "state", "state", "normal", str(self.state()))
158
+
159
+ border_value = self._cget_border(pname)
160
+ if border_value is not self._NOT_A_BORDER_PROPERTY:
161
+ return (pname, pname, pname,
162
+ self._query_value(self._theme_default(pname)),
163
+ self._query_value(border_value))
164
+
165
+ if pname in ["fg_color", "button_color", "button_hover_color",
166
+ "text_color"]:
167
+ is_disabled = str(self.state()).lower() == "disabled"
168
+ source = (self._custom_disabled_map if is_disabled
169
+ else self._local_defaults)
170
+ return (pname, pname, pname,
171
+ self._query_value(self._theme_default(pname)),
172
+ self._query_value(source.get(pname)))
173
+
174
+ return self._configure_query(pname)
175
+
176
+ # Consumed before the native call below, which rejects both as unknown
177
+ # options.
178
+ self._configure_border(kwargs)
179
+
180
+ if "values" in kwargs:
181
+ super().configure(values=kwargs.pop("values"))
182
+ if "command" in kwargs:
183
+ super().configure(command=kwargs.pop("command"))
184
+ if "variable" in kwargs:
185
+ super().configure(variable=kwargs.pop("variable"))
186
+
187
+ # Runtime overrides have to reach the map a repaint reads, or the
188
+ # repaint puts the theme value straight back -- see
189
+ # ThemeableWidget._record_theme_overrides().
190
+ self._record_theme_overrides(kwargs)
191
+
192
+ if "state" in kwargs:
193
+ self.state(str(kwargs.pop("state")).lower())
194
+
195
+ self._drop_cleared(kwargs)
196
+
197
+ if kwargs:
198
+ return super().configure(**kwargs)
199
+ return None
200
+
201
+ # Tkinter/CTk convention binds .config to .configure as a SEPARATE class
202
+ # attribute -- it does not track whichever configure() a subclass defines.
203
+ # Without this line, .config(...) would skip this override entirely and
204
+ # land on the native widget, bypassing theming and state handling.
205
+ config = configure
206
+
207
+ def cget(self, attribute_name: str) -> Any:
208
+ """
209
+ Standard cget(), extended to this widget's own border properties.
210
+
211
+ border_width and border_color are not native CTkOptionMenu options, so
212
+ without this they reach CTkBaseClass.cget() and raise.
213
+ """
214
+ border_value = self._cget_border(attribute_name)
215
+ if border_value is not self._NOT_A_BORDER_PROPERTY:
216
+ return border_value
217
+ return super().cget(attribute_name)
218
+
219
+ def _set_appearance_mode(self, mode_string: str) -> None:
220
+ """
221
+ Forwards CustomTkinter's light/dark change to the native widget.
222
+
223
+ No manual re-application is needed: colours are passed through as raw
224
+ (light, dark) tuples, so CTk's own appearance tracking repaints them.
225
+ The border follows because _draw() re-resolves it on every redraw.
226
+ """
227
+ if hasattr(super(), "_set_appearance_mode"):
228
+ try:
229
+ super()._set_appearance_mode(mode_string)
230
+ except Exception:
231
+ pass
232
+
233
+ def get_state(self) -> str:
234
+ """Equivalent to calling state() with no argument."""
235
+ return self.state()
236
+
237
+ def state(self, mode: Optional[str] = None) -> str:
238
+ """
239
+ Gets or sets the widget's enabled/disabled visual state.
240
+
241
+ Args:
242
+ mode: None returns the current state unchanged. Otherwise only
243
+ "disabled" (case-insensitive) disables; anything in
244
+ ("normal", "enabled", "active") enables. Any other value
245
+ matches neither branch, though the repaint still runs.
246
+
247
+ Returns:
248
+ The resulting state, lowercase.
249
+ """
250
+ if mode is None:
251
+ return str(getattr(self, "_custom_current_state", "normal")).lower()
252
+
253
+ mode = mode.lower()
254
+ if mode in ("normal", "enabled", "active"):
255
+ self._custom_current_state = "normal"
256
+ elif mode == "disabled":
257
+ self._custom_current_state = "disabled"
258
+
259
+ self._update_current_visual_state()
260
+ return self._custom_current_state
261
+
262
+ def _update_current_visual_state(self) -> None:
263
+ """
264
+ Recomputes and applies this widget's colours from the theme, based on
265
+ the current state, then sets the native interactive lock.
266
+
267
+ Colours are passed through as raw (light, dark) tuples so CTk's own
268
+ appearance tracking handles light/dark repaints.
269
+
270
+ button_color is set FROM fg_color rather than read as its own key.
271
+ That is what makes this the quiet variant: the arrow blends into the
272
+ control instead of standing out as a separate block, and the theme
273
+ block accordingly has no button_color of its own. Primary gives the
274
+ arrow its own colour.
275
+ """
276
+ is_disabled = getattr(self, "_custom_current_state", "normal") == "disabled"
277
+ target_map = self._custom_disabled_map if is_disabled else self._local_defaults
278
+
279
+ # fg_color and text_color are required in whichever map is active.
280
+ # Substituting a guess would leave a broken theme block looking merely
281
+ # slightly wrong -- see the library-wide fail-loud rule.
282
+ section = "disabled_map" if is_disabled else "top-level"
283
+ for required_key in ("fg_color", "text_color"):
284
+ if target_map.get(required_key) is None:
285
+ name = (getattr(self, "_THEME_BLOCK_NAME", None)
286
+ or self.__class__.__name__)
287
+ raise KeyError(
288
+ f"'{name}' theme block is missing '{required_key}' in its "
289
+ f"{section} section of sCTkThemes.json."
290
+ )
291
+
292
+ payload = {}
293
+ for key in ("font", "dropdown_font", "dropdown_fg_color",
294
+ "dropdown_text_color", "dropdown_hover_color",
295
+ "button_hover_color"):
296
+ val = self._local_defaults.get(key)
297
+ if val is not None:
298
+ payload[key] = val
299
+
300
+ fill = target_map.get("fg_color")
301
+ payload.update({
302
+ "fg_color": fill,
303
+ "button_color": fill,
304
+ # text_color stays the NORMAL value and the disabled one goes to
305
+ # CTk's native text_color_disabled, because the state below puts
306
+ # the widget into CTk's own disabled mode -- where its draw code
307
+ # uses that option and ignores text_color entirely. Assigning the
308
+ # disabled colour here was discarded at draw time, so the theme key
309
+ # had no visible effect.
310
+ "text_color": self._local_defaults.get("text_color"),
311
+ "text_color_disabled": self._custom_disabled_map.get("text_color"),
312
+ "state": "disabled" if is_disabled else "normal",
313
+ })
314
+ super().configure(**payload)
315
+
316
+ # The border is not a native option, so it cannot ride along above.
317
+ # Its disabled colour comes from disabled_map like any other, falling
318
+ # back to the normal one when unspecified.
319
+ border_colour = target_map.get("border_color")
320
+ if border_colour is None:
321
+ border_colour = self._local_defaults.get("border_color")
322
+ if border_colour is not None:
323
+ self._border_color = border_colour
324
+ self._redraw_border()
325
+
326
+ def update_list(self, new_values: list, default_index: int = 0) -> None:
327
+ """
328
+ Replaces the dropdown's options and resets the visible selection.
329
+
330
+ Args:
331
+ new_values: The new options. An empty list sets a single blank
332
+ option and clears the display.
333
+ default_index: Which option to select afterwards. Out of range
334
+ falls back to index 0 rather than raising.
335
+ """
336
+ if not new_values:
337
+ self.configure(values=[""])
338
+ self.set("")
339
+ return
340
+
341
+ self.configure(values=new_values)
342
+
343
+ if default_index < len(new_values):
344
+ self.set(new_values[default_index])
345
+ else:
346
+ self.set(new_values[0])