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.
- scustomtkinter/__init__.py +81 -0
- scustomtkinter/assets/images/error.png +0 -0
- scustomtkinter/assets/images/info.png +0 -0
- scustomtkinter/assets/images/warning.png +0 -0
- scustomtkinter/assets/sCTkThemes.json +1963 -0
- scustomtkinter/license_morecustomtkinterwidgets.txt +22 -0
- scustomtkinter/sctk_button_primary.py +366 -0
- scustomtkinter/sctk_button_secondary.py +354 -0
- scustomtkinter/sctk_button_tertiary.py +375 -0
- scustomtkinter/sctk_checkbox.py +270 -0
- scustomtkinter/sctk_combobox.py +155 -0
- scustomtkinter/sctk_core.py +15 -0
- scustomtkinter/sctk_dial.py +1472 -0
- scustomtkinter/sctk_dialog.py +701 -0
- scustomtkinter/sctk_dialog_toplevel.py +268 -0
- scustomtkinter/sctk_entry_primary.py +400 -0
- scustomtkinter/sctk_entry_secondary.py +342 -0
- scustomtkinter/sctk_file_explorer.py +879 -0
- scustomtkinter/sctk_frame.py +210 -0
- scustomtkinter/sctk_frame_labeled_primary.py +667 -0
- scustomtkinter/sctk_frame_labeled_secondary.py +598 -0
- scustomtkinter/sctk_frequency_display.py +636 -0
- scustomtkinter/sctk_label_primary.py +283 -0
- scustomtkinter/sctk_label_secondary.py +275 -0
- scustomtkinter/sctk_label_tertiary.py +278 -0
- scustomtkinter/sctk_messagebox.py +275 -0
- scustomtkinter/sctk_notebook.py +1103 -0
- scustomtkinter/sctk_optionmenu_border_mixin.py +203 -0
- scustomtkinter/sctk_optionmenu_primary.py +378 -0
- scustomtkinter/sctk_optionmenu_secondary.py +346 -0
- scustomtkinter/sctk_path_chooser.py +516 -0
- scustomtkinter/sctk_progress_bar.py +274 -0
- scustomtkinter/sctk_radiobutton.py +360 -0
- scustomtkinter/sctk_scroll_mixin.py +546 -0
- scustomtkinter/sctk_scrollable_frame.py +795 -0
- scustomtkinter/sctk_scrollbar.py +313 -0
- scustomtkinter/sctk_segmentedbutton.py +424 -0
- scustomtkinter/sctk_selector.py +581 -0
- scustomtkinter/sctk_separator.py +308 -0
- scustomtkinter/sctk_slider.py +264 -0
- scustomtkinter/sctk_smeter.py +405 -0
- scustomtkinter/sctk_smeter_bar.py +557 -0
- scustomtkinter/sctk_spinbox.py +664 -0
- scustomtkinter/sctk_switch.py +463 -0
- scustomtkinter/sctk_tableview.py +869 -0
- scustomtkinter/sctk_tabview.py +409 -0
- scustomtkinter/sctk_textbox_primary.py +291 -0
- scustomtkinter/sctk_textbox_secondary.py +225 -0
- scustomtkinter/sctk_toplevel.py +153 -0
- scustomtkinter/themeable_widget.py +938 -0
- scustomtkinter-0.2.1.dist-info/METADATA +336 -0
- scustomtkinter-0.2.1.dist-info/RECORD +101 -0
- scustomtkinter-0.2.1.dist-info/WHEEL +5 -0
- scustomtkinter-0.2.1.dist-info/entry_points.txt +2 -0
- scustomtkinter-0.2.1.dist-info/licenses/LICENSE +21 -0
- scustomtkinter-0.2.1.dist-info/licenses/scustomtkinter/license_morecustomtkinterwidgets.txt +22 -0
- scustomtkinter-0.2.1.dist-info/top_level.txt +2 -0
- scustomtkinter_pygubu/__init__.py +1 -0
- scustomtkinter_pygubu/designer/__init__.py +0 -0
- scustomtkinter_pygubu/designer/plugin.py +1854 -0
- scustomtkinter_pygubu/designer/properties.py +247 -0
- scustomtkinter_pygubu/sCTkButtonPrimarybo.py +46 -0
- scustomtkinter_pygubu/sCTkButtonSecondarybo.py +45 -0
- scustomtkinter_pygubu/sCTkButtonTertiarybo.py +44 -0
- scustomtkinter_pygubu/sCTkCheckBoxbo.py +45 -0
- scustomtkinter_pygubu/sCTkComboBoxbo.py +45 -0
- scustomtkinter_pygubu/sCTkCorebo.py +120 -0
- scustomtkinter_pygubu/sCTkDialbo.py +371 -0
- scustomtkinter_pygubu/sCTkDialogbo.py +397 -0
- scustomtkinter_pygubu/sCTkEntryPrimarybo.py +100 -0
- scustomtkinter_pygubu/sCTkEntrySecondarybo.py +98 -0
- scustomtkinter_pygubu/sCTkFileExplorerbo.py +189 -0
- scustomtkinter_pygubu/sCTkFrameLabeledPrimarybo.py +69 -0
- scustomtkinter_pygubu/sCTkFrameLabeledSecondarybo.py +71 -0
- scustomtkinter_pygubu/sCTkFramebo.py +46 -0
- scustomtkinter_pygubu/sCTkFrequencyDisplaybo.py +175 -0
- scustomtkinter_pygubu/sCTkLabelPrimarybo.py +45 -0
- scustomtkinter_pygubu/sCTkLabelSecondarybo.py +44 -0
- scustomtkinter_pygubu/sCTkLabelTertiarybo.py +45 -0
- scustomtkinter_pygubu/sCTkNotebookbo.py +392 -0
- scustomtkinter_pygubu/sCTkOptionMenuPrimarybo.py +45 -0
- scustomtkinter_pygubu/sCTkOptionMenuSecondarybo.py +39 -0
- scustomtkinter_pygubu/sCTkPathChooserbo.py +204 -0
- scustomtkinter_pygubu/sCTkProgressBarbo.py +45 -0
- scustomtkinter_pygubu/sCTkRadioButtonbo.py +45 -0
- scustomtkinter_pygubu/sCTkSMeterBarbo.py +153 -0
- scustomtkinter_pygubu/sCTkSMeterbo.py +101 -0
- scustomtkinter_pygubu/sCTkScrollableFramebo.py +66 -0
- scustomtkinter_pygubu/sCTkScrollbarbo.py +48 -0
- scustomtkinter_pygubu/sCTkSegmentedButtonbo.py +47 -0
- scustomtkinter_pygubu/sCTkSelectorbo.py +167 -0
- scustomtkinter_pygubu/sCTkSeparatorbo.py +141 -0
- scustomtkinter_pygubu/sCTkSliderbo.py +45 -0
- scustomtkinter_pygubu/sCTkSpinboxbo.py +200 -0
- scustomtkinter_pygubu/sCTkSwitchbo.py +45 -0
- scustomtkinter_pygubu/sCTkTableviewbo.py +188 -0
- scustomtkinter_pygubu/sCTkTabviewbo.py +226 -0
- scustomtkinter_pygubu/sCTkTextboxPrimarybo.py +45 -0
- scustomtkinter_pygubu/sCTkTextboxSecondarybo.py +45 -0
- scustomtkinter_pygubu/sCTkToplevelbo.py +55 -0
- 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)
|