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,392 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""
|
|
3
|
+
sCTkNotebook
|
|
4
|
+
|
|
5
|
+
Pygubu builder objects for the vertical-tab notebook and for the tabs
|
|
6
|
+
themselves.
|
|
7
|
+
|
|
8
|
+
A TAB IS NOT A WIDGET. It is created by its notebook, through add(), so the
|
|
9
|
+
tab builder object has no class of its own to instantiate -- it calls the
|
|
10
|
+
parent's add() and hands back the sCTkFrame that comes out. Generated code
|
|
11
|
+
reads:
|
|
12
|
+
|
|
13
|
+
tab1 = notebook1.add("Receiver")
|
|
14
|
+
|
|
15
|
+
which is the same arrangement sCTkTabviewbo uses for CTkTabview's tabs, and
|
|
16
|
+
for the same reason.
|
|
17
|
+
"""
|
|
18
|
+
import tkinter as tk
|
|
19
|
+
|
|
20
|
+
from pygubu.api.v1 import (
|
|
21
|
+
BuilderObject,
|
|
22
|
+
register_widget,
|
|
23
|
+
register_custom_property,
|
|
24
|
+
)
|
|
25
|
+
from pygubu.plugins.customtkinter.widgets import CTkFrameBO
|
|
26
|
+
|
|
27
|
+
from scustomtkinter.sctk_notebook import sCTkNotebook
|
|
28
|
+
|
|
29
|
+
widget_namespace = "scustomtkinter.sctk_notebook"
|
|
30
|
+
widget_classname = "sCTkNotebook"
|
|
31
|
+
builder_namespace = "scustomtkinter"
|
|
32
|
+
section_name = "sCustomTkinter"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class sCTkNotebookBO(CTkFrameBO):
|
|
36
|
+
class_ = sCTkNotebook
|
|
37
|
+
|
|
38
|
+
OPTIONS_CUSTOM = (
|
|
39
|
+
"side", "tab_width", "tab_style", "text_orientation",
|
|
40
|
+
"show_page_border", "show_tab_separators", "state",
|
|
41
|
+
)
|
|
42
|
+
# "command" IS IN properties, as well as in command_properties. The
|
|
43
|
+
# Designer lists only what is in `properties`; command_properties does
|
|
44
|
+
# not add to that list, it marks which listed properties are commands, to
|
|
45
|
+
# be connected to a callback rather than set as a value. Declared only in
|
|
46
|
+
# command_properties, it was registered and never shown. Pygubu's own
|
|
47
|
+
# Button declares its command in both.
|
|
48
|
+
#
|
|
49
|
+
# It stays out of OPTIONS_CUSTOM: pygubu connects it after construction,
|
|
50
|
+
# through configure(command=...), so realize() below does not pass it.
|
|
51
|
+
properties = CTkFrameBO.properties + OPTIONS_CUSTOM + ("command",)
|
|
52
|
+
command_properties = ("command",)
|
|
53
|
+
|
|
54
|
+
OPTIONS_CUSTOM_DEFAULTS = {
|
|
55
|
+
"side": "left",
|
|
56
|
+
"tab_width": "34",
|
|
57
|
+
"tab_style": "rounded",
|
|
58
|
+
"text_orientation": "auto",
|
|
59
|
+
"show_page_border": "True",
|
|
60
|
+
"show_tab_separators": "False",
|
|
61
|
+
"state": "normal",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# A container, but only for its own tabs -- see allowed_children below.
|
|
65
|
+
container = True
|
|
66
|
+
container_layout = False
|
|
67
|
+
|
|
68
|
+
def realize(self, parent, extra_init_args: dict = None):
|
|
69
|
+
"""
|
|
70
|
+
Builds the notebook with its own properties as constructor arguments.
|
|
71
|
+
|
|
72
|
+
side and tab_style are validated by the widget and raise on a bad
|
|
73
|
+
value, so they have to arrive as real values rather than being set
|
|
74
|
+
afterwards -- a half-built widget in the Designer is worse than a
|
|
75
|
+
clear error.
|
|
76
|
+
"""
|
|
77
|
+
if extra_init_args is None:
|
|
78
|
+
extra_init_args = {}
|
|
79
|
+
|
|
80
|
+
props = self.wmeta.properties
|
|
81
|
+
init_args = {
|
|
82
|
+
"side": props.get("side", "left"),
|
|
83
|
+
"tab_style": props.get("tab_style", "rounded"),
|
|
84
|
+
"text_orientation": props.get("text_orientation", "auto"),
|
|
85
|
+
"tab_width": int(props.get("tab_width", 34) or 34),
|
|
86
|
+
"show_page_border": self._as_bool(props.get("show_page_border", "True")),
|
|
87
|
+
"show_tab_separators": self._as_bool(
|
|
88
|
+
props.get("show_tab_separators", "False")),
|
|
89
|
+
"state": props.get("state", "normal"),
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for prop in self.OPTIONS_CUSTOM:
|
|
93
|
+
extra_init_args.pop(prop, None)
|
|
94
|
+
init_args.update(extra_init_args)
|
|
95
|
+
|
|
96
|
+
real_master = parent.widget if hasattr(parent, "widget") else parent
|
|
97
|
+
self.widget = self.class_(real_master, **init_args)
|
|
98
|
+
return self.widget
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def _as_bool(value):
|
|
102
|
+
"""
|
|
103
|
+
Parses the Designer's strings.
|
|
104
|
+
|
|
105
|
+
bool("False") is True, which would switch on every option whose
|
|
106
|
+
dropdown said otherwise.
|
|
107
|
+
"""
|
|
108
|
+
if isinstance(value, str):
|
|
109
|
+
return value.strip().lower() in ("true", "1", "yes", "on")
|
|
110
|
+
return bool(value)
|
|
111
|
+
|
|
112
|
+
def _process_property_value(self, pname, value):
|
|
113
|
+
if pname == "tab_width":
|
|
114
|
+
return int(value)
|
|
115
|
+
if pname in ("show_page_border", "show_tab_separators"):
|
|
116
|
+
return self._as_bool(value)
|
|
117
|
+
return super()._process_property_value(pname, value)
|
|
118
|
+
|
|
119
|
+
def _code_set_property(self, targetid, pname, value, code_bag):
|
|
120
|
+
"""Booleans and the width emit as tokens; the rest keep their quotes."""
|
|
121
|
+
if pname in ("show_page_border", "show_tab_separators", "tab_width"):
|
|
122
|
+
code_bag[pname] = str(value).strip("'\"")
|
|
123
|
+
elif pname in ("side", "tab_style", "text_orientation", "state"):
|
|
124
|
+
code_bag[pname] = f"'{str(value).strip(chr(39) + chr(34))}'"
|
|
125
|
+
else:
|
|
126
|
+
super()._code_set_property(targetid, pname, value, code_bag)
|
|
127
|
+
|
|
128
|
+
def _code_define_callback_args(self, cmd_pname, cmd):
|
|
129
|
+
"""
|
|
130
|
+
Declares that `command` receives the tab's name, so the generated
|
|
131
|
+
handler stub has a matching parameter.
|
|
132
|
+
|
|
133
|
+
Without it a handler written from the stub would have no indication
|
|
134
|
+
that the selected tab is being handed to it -- which is the entire
|
|
135
|
+
point of the callback.
|
|
136
|
+
"""
|
|
137
|
+
return ("tab",)
|
|
138
|
+
|
|
139
|
+
def code_imports(self):
|
|
140
|
+
imports = [(widget_namespace, widget_classname)]
|
|
141
|
+
imports.extend(self.code_extra_imports())
|
|
142
|
+
return imports
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
builder_id = f"{builder_namespace}.{widget_classname}"
|
|
146
|
+
register_widget(builder_id, sCTkNotebookBO, widget_classname,
|
|
147
|
+
("ttk", section_name))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# The tab
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
class sCTkNotebookTabBO(BuilderObject):
|
|
154
|
+
"""
|
|
155
|
+
A page in an sCTkNotebook.
|
|
156
|
+
|
|
157
|
+
Has no class_ of its own: a tab is made by the notebook's add(), which
|
|
158
|
+
returns the sCTkFrame that children are then parented to.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
class_ = None
|
|
162
|
+
container = True
|
|
163
|
+
container_layout = True
|
|
164
|
+
layout_required = False
|
|
165
|
+
allow_bindings = False
|
|
166
|
+
properties = ("label",)
|
|
167
|
+
OPTIONS_CUSTOM_DEFAULTS = {"label": "Tab"}
|
|
168
|
+
|
|
169
|
+
def realize(self, parent, extra_init_args: dict = None):
|
|
170
|
+
"""
|
|
171
|
+
Asks the parent notebook for a page.
|
|
172
|
+
|
|
173
|
+
DUPLICATE NAMES ARE RENAMED, NOT REJECTED. The widget's own add()
|
|
174
|
+
raises ValueError on a name already in use, which is right for
|
|
175
|
+
application code -- but inside the Designer that exception surfaces
|
|
176
|
+
only on the console, where nobody sees it, and the tab silently fails
|
|
177
|
+
to appear while the tree and the preview disagree. A numeric suffix is
|
|
178
|
+
appended instead, visibly, in both the tab strip and the property
|
|
179
|
+
field. Same accommodation sCTkTabviewbo makes.
|
|
180
|
+
"""
|
|
181
|
+
master = parent.widget if hasattr(parent, "widget") else parent
|
|
182
|
+
label = self.wmeta.properties.get("label", self.wmeta.identifier)
|
|
183
|
+
|
|
184
|
+
existing = master.tabs()
|
|
185
|
+
if label in existing:
|
|
186
|
+
suffix = 2
|
|
187
|
+
while f"{label}_{suffix}" in existing:
|
|
188
|
+
suffix += 1
|
|
189
|
+
label = f"{label}_{suffix}"
|
|
190
|
+
self.wmeta.properties["label"] = label
|
|
191
|
+
|
|
192
|
+
self._label = label
|
|
193
|
+
self.widget = master.add(label)
|
|
194
|
+
return self.widget
|
|
195
|
+
|
|
196
|
+
def _set_property(self, target_widget, pname, value):
|
|
197
|
+
"""
|
|
198
|
+
Rebuilds the notebook when a tab is renamed.
|
|
199
|
+
|
|
200
|
+
A tab's label is consumed by realize() -- it is the name add() was
|
|
201
|
+
called with -- so changing it afterwards cannot be expressed by
|
|
202
|
+
configuring the existing page. Without a rebuild the strip went on
|
|
203
|
+
showing the old caption until some unrelated edit forced a repaint.
|
|
204
|
+
|
|
205
|
+
recreate_widget() is what the Designer itself does when you add or
|
|
206
|
+
delete a widget, which is why doing that by hand made the change
|
|
207
|
+
appear. It hangs off _set_property, NOT set_property: the Designer
|
|
208
|
+
edits a property through the former, and the latter is never called.
|
|
209
|
+
"""
|
|
210
|
+
if pname == "label":
|
|
211
|
+
# CONSUMED, not delegated, and not acted on here either.
|
|
212
|
+
#
|
|
213
|
+
# The base implementation forwards an unknown property to
|
|
214
|
+
# widget.configure(), and a page is an ordinary sCTkFrame:
|
|
215
|
+
#
|
|
216
|
+
# Failed to set property 'label' on class 'None'.
|
|
217
|
+
# Error: ['label'] are not supported arguments.
|
|
218
|
+
#
|
|
219
|
+
# The rename itself happens in configure(), which is the call the
|
|
220
|
+
# Designer makes after updating the metadata -- see there.
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
super()._set_property(target_widget, pname, value)
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _find_notebook(page):
|
|
227
|
+
"""
|
|
228
|
+
Climbs from a page to the notebook that owns it.
|
|
229
|
+
|
|
230
|
+
A page is parented to the notebook's internal page host, not to the
|
|
231
|
+
notebook, so the immediate parent is the wrong object.
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
top = page.winfo_toplevel()
|
|
235
|
+
widget = page
|
|
236
|
+
for _ in range(4):
|
|
237
|
+
parent_name = widget.winfo_parent()
|
|
238
|
+
if not parent_name:
|
|
239
|
+
return None
|
|
240
|
+
widget = top.nametowidget(parent_name)
|
|
241
|
+
if hasattr(widget, "rename") and hasattr(widget, "tabs"):
|
|
242
|
+
return widget
|
|
243
|
+
except Exception:
|
|
244
|
+
pass
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
def configure(self, target=None):
|
|
248
|
+
"""
|
|
249
|
+
Applies a changed `label` by renaming the live tab.
|
|
250
|
+
|
|
251
|
+
THIS IS WHERE THE RENAME BELONGS, not in _set_property. The Designer
|
|
252
|
+
edits the metadata and then calls configure(); a no-op here left the
|
|
253
|
+
design canvas showing the old caption while the preview, which
|
|
254
|
+
rebuilds from scratch, showed the new one. sCTkTabviewTabBO learned
|
|
255
|
+
the same lesson and its docstring records it.
|
|
256
|
+
|
|
257
|
+
EVERYTHING IS DERIVED FROM self.widget, deliberately. Pygubu calls
|
|
258
|
+
configure() on builder objects whose realize() never ran, so any state
|
|
259
|
+
stashed during realize() -- the notebook, the name it was filed under
|
|
260
|
+
-- may simply not be there. The page is the only reliable anchor: walk
|
|
261
|
+
up from it to the notebook, then find the page in that notebook's
|
|
262
|
+
registry BY IDENTITY to learn what it is currently called.
|
|
263
|
+
"""
|
|
264
|
+
widget = getattr(self, "widget", None)
|
|
265
|
+
if widget is None:
|
|
266
|
+
return
|
|
267
|
+
|
|
268
|
+
notebook = self._find_notebook(widget)
|
|
269
|
+
if notebook is None:
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
# Found by identity rather than by trusting a stored name.
|
|
273
|
+
old_name = None
|
|
274
|
+
for name, page in getattr(notebook, "_pages", {}).items():
|
|
275
|
+
if page is widget:
|
|
276
|
+
old_name = name
|
|
277
|
+
break
|
|
278
|
+
if old_name is None:
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
new_name = str(self.wmeta.properties.get("label", old_name))
|
|
282
|
+
if not new_name or new_name == old_name:
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
existing = [n for n in notebook.tabs() if n != old_name]
|
|
286
|
+
if new_name in existing:
|
|
287
|
+
suffix = 2
|
|
288
|
+
while f"{new_name}_{suffix}" in existing:
|
|
289
|
+
suffix += 1
|
|
290
|
+
new_name = f"{new_name}_{suffix}"
|
|
291
|
+
self.wmeta.properties["label"] = new_name
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
notebook.rename(old_name, new_name)
|
|
295
|
+
self._label = new_name
|
|
296
|
+
except Exception:
|
|
297
|
+
# Torn down mid-edit. The preview rebuild still shows the new
|
|
298
|
+
# label, so this degrades rather than breaks.
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
def layout(self, target=None, *args, **kwargs):
|
|
302
|
+
"""The notebook grids its own pages; a tab takes no layout."""
|
|
303
|
+
pass
|
|
304
|
+
|
|
305
|
+
def code_realize(self, boparent, code_identifier=None):
|
|
306
|
+
identifier = code_identifier or self.code_identifier()
|
|
307
|
+
label = self.wmeta.properties.get("label", "Tab")
|
|
308
|
+
return (f'{identifier} = {boparent.code_child_master()}'
|
|
309
|
+
f'.add("{label}")',)
|
|
310
|
+
|
|
311
|
+
def code_configure(self, targetid=None):
|
|
312
|
+
return tuple()
|
|
313
|
+
|
|
314
|
+
def code_layout(self, targetid=None, parentid=None):
|
|
315
|
+
return tuple()
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# ONE dot in the id, not two.
|
|
319
|
+
#
|
|
320
|
+
# pygubu splits a classname on "." to work out which module to import, so
|
|
321
|
+
# "scustomtkinter.sCTkNotebook.Tab" made that split return a list and the
|
|
322
|
+
# generated code failed on load:
|
|
323
|
+
#
|
|
324
|
+
# AttributeError: 'list' object has no attribute 'startswith'
|
|
325
|
+
#
|
|
326
|
+
# The DISPLAY name still reads "sCTkNotebook.Tab" -- that is what appears in
|
|
327
|
+
# the palette. sCTkTabviewbo uses exactly this pairing.
|
|
328
|
+
tab_builder_id = f"{builder_namespace}.sCTkNotebookTab"
|
|
329
|
+
register_widget(tab_builder_id, sCTkNotebookTabBO,
|
|
330
|
+
f"{widget_classname}.Tab", ("ttk", section_name))
|
|
331
|
+
|
|
332
|
+
# A tab has nowhere else to live, so it is offered only inside a notebook.
|
|
333
|
+
sCTkNotebookTabBO.allowed_parents = (builder_id,)
|
|
334
|
+
sCTkNotebookBO.allowed_children = (tab_builder_id,)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
# ---------------------------------------------------------------------------
|
|
338
|
+
# Inspector properties
|
|
339
|
+
# ---------------------------------------------------------------------------
|
|
340
|
+
register_custom_property(
|
|
341
|
+
builder_id, "side", "choice", values=("left", "right"),
|
|
342
|
+
state="readonly", default_value="left",
|
|
343
|
+
help="Which edge the tab strip sits on.")
|
|
344
|
+
|
|
345
|
+
register_custom_property(
|
|
346
|
+
builder_id, "tab_width", "naturalnumber", default_value="34",
|
|
347
|
+
help="Width of the strip in pixels. Explicit rather than measured, "
|
|
348
|
+
"because the strip takes space from the pages. Horizontal text "
|
|
349
|
+
"needs this wide enough to hold the longest label.")
|
|
350
|
+
|
|
351
|
+
register_custom_property(
|
|
352
|
+
builder_id, "tab_style", "choice", values=("rounded", "angled"),
|
|
353
|
+
state="readonly", default_value="rounded",
|
|
354
|
+
help="Rounded matches the rest of the library. Angled cuts the outer "
|
|
355
|
+
"corners, the shape of a real notebook divider.")
|
|
356
|
+
|
|
357
|
+
register_custom_property(
|
|
358
|
+
builder_id, "text_orientation", "choice",
|
|
359
|
+
values=("auto", "up", "down", "horizontal"),
|
|
360
|
+
state="readonly", default_value="auto",
|
|
361
|
+
help="auto reads outside-in and flips with the side. up and down pin the "
|
|
362
|
+
"direction whichever side the strip is on. horizontal lays the text "
|
|
363
|
+
"flat and needs a wider tab_width.")
|
|
364
|
+
|
|
365
|
+
register_custom_property(
|
|
366
|
+
builder_id, "show_page_border", "choice", values=("True", "False"),
|
|
367
|
+
state="readonly", default_value="True",
|
|
368
|
+
help="Draws the outline around the page, broken where the selected tab "
|
|
369
|
+
"meets it.")
|
|
370
|
+
|
|
371
|
+
register_custom_property(
|
|
372
|
+
builder_id, "show_tab_separators", "choice", values=("True", "False"),
|
|
373
|
+
state="readonly", default_value="False",
|
|
374
|
+
help="A line between adjacent tabs. Earns its keep with horizontal text, "
|
|
375
|
+
"where stacked labels can run together; rarely otherwise.")
|
|
376
|
+
|
|
377
|
+
register_custom_property(
|
|
378
|
+
builder_id, "state", "choice", values=("normal", "disabled"),
|
|
379
|
+
state="readonly", default_value="normal",
|
|
380
|
+
help="Disabled dims the strip and stops tab selection. It does not "
|
|
381
|
+
"cascade to the widgets on a page.")
|
|
382
|
+
|
|
383
|
+
register_custom_property(
|
|
384
|
+
builder_id, "command", "commandentry",
|
|
385
|
+
help="Called with the tab's name when the operator clicks a tab. Not "
|
|
386
|
+
"called when a tab is selected in code with set() -- the "
|
|
387
|
+
"application already knows what it chose.")
|
|
388
|
+
|
|
389
|
+
register_custom_property(
|
|
390
|
+
tab_builder_id, "label", "entry", default_value="Tab",
|
|
391
|
+
help="The tab's caption, and the name add() is called with. A duplicate "
|
|
392
|
+
"is renamed with a numeric suffix rather than rejected.")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""
|
|
3
|
+
sCTkOptionMenuPrimary
|
|
4
|
+
|
|
5
|
+
Tailored version of the standard ctkOptionMenu
|
|
6
|
+
|
|
7
|
+
UI source file: sCTkOptionMenuPrimary.ui
|
|
8
|
+
"""
|
|
9
|
+
import tkinter as tk
|
|
10
|
+
import tkinter.ttk as ttk
|
|
11
|
+
from customtkinter import CTkOptionMenu
|
|
12
|
+
from pygubu.api.v1 import (
|
|
13
|
+
BuilderObject,
|
|
14
|
+
register_widget,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from pygubu.plugins.customtkinter.widgets import CTkOptionMenuBO
|
|
18
|
+
from scustomtkinter.sctk_optionmenu_primary import sCTkOptionMenuPrimary
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
#
|
|
22
|
+
# Builder definition section
|
|
23
|
+
#
|
|
24
|
+
widget_namespace = "scustomtkinter.sctk_optionmenu_primary"
|
|
25
|
+
widget_classname = "sCTkOptionMenuPrimary"
|
|
26
|
+
builder_namespace = "scustomtkinter"
|
|
27
|
+
section_name = "sCustomTkinter"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class sCTkOptionMenuPrimaryBO(CTkOptionMenuBO):
|
|
31
|
+
class_ = sCTkOptionMenuPrimary
|
|
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, sCTkOptionMenuPrimaryBO, widget_classname, ("ttk", section_name)
|
|
44
|
+
)
|
|
45
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
import tkinter as tk
|
|
4
|
+
import tkinter.ttk as ttk
|
|
5
|
+
from customtkinter import CTkOptionMenu
|
|
6
|
+
from pygubu.api.v1 import (
|
|
7
|
+
BuilderObject,
|
|
8
|
+
register_widget,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from pygubu.plugins.customtkinter.widgets import CTkOptionMenuBO
|
|
12
|
+
from scustomtkinter.sctk_optionmenu_secondary import sCTkOptionMenuSecondary
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# Builder definition section
|
|
17
|
+
#
|
|
18
|
+
widget_namespace = "scustomtkinter.sctk_optionmenu_secondary"
|
|
19
|
+
widget_classname = "sCTkOptionMenuSecondary"
|
|
20
|
+
builder_namespace = "scustomtkinter"
|
|
21
|
+
section_name = "sCustomTkinter"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class sCTkOptionMenuSecondaryBO(CTkOptionMenuBO):
|
|
25
|
+
class_ = sCTkOptionMenuSecondary
|
|
26
|
+
|
|
27
|
+
def code_imports(self):
|
|
28
|
+
# should return an iterable of (module, classname/function) to import
|
|
29
|
+
# or None
|
|
30
|
+
imports = [(widget_namespace, widget_classname)]
|
|
31
|
+
imports.extend(self.code_extra_imports())
|
|
32
|
+
return imports
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
builder_id = f"{builder_namespace}.{widget_classname}"
|
|
36
|
+
register_widget(
|
|
37
|
+
builder_id, sCTkOptionMenuSecondaryBO, widget_classname, ("ttk", section_name)
|
|
38
|
+
)
|
|
39
|
+
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""
|
|
3
|
+
sCTkPathChooser
|
|
4
|
+
|
|
5
|
+
Pygubu Builder Object for the compound sCTkPathChooser entry widget row.
|
|
6
|
+
"""
|
|
7
|
+
import pygubu
|
|
8
|
+
|
|
9
|
+
from pygubu.api.v1 import (
|
|
10
|
+
BuilderObject,
|
|
11
|
+
register_widget,
|
|
12
|
+
register_custom_property
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# Import the native custom class
|
|
16
|
+
from scustomtkinter.sctk_path_chooser import sCTkPathChooser
|
|
17
|
+
from pygubu.plugins.customtkinter.widgets import CTkFrameBO
|
|
18
|
+
|
|
19
|
+
#
|
|
20
|
+
# Builder definition section
|
|
21
|
+
#
|
|
22
|
+
widget_namespace = "scustomtkinter.sctk_path_chooser"
|
|
23
|
+
widget_classname = "sCTkPathChooser"
|
|
24
|
+
builder_namespace = "scustomtkinter"
|
|
25
|
+
section_name = "sCustomTkinter"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class sCTkPathChooserBO(BuilderObject):
|
|
29
|
+
class_ = sCTkPathChooser
|
|
30
|
+
|
|
31
|
+
# Expose custom compound parameters alongside theme state configurations
|
|
32
|
+
OPTIONS_CUSTOM = ("width", "height", "type", "title", "initialdir", "initialfile", "filetypes", "state", "command", "btn_width", "btn_height", "btn_text", "entry_height", "browser_width", "browser_height", "justify")
|
|
33
|
+
properties = CTkFrameBO.properties + OPTIONS_CUSTOM
|
|
34
|
+
|
|
35
|
+
command_properties = ("command",)
|
|
36
|
+
|
|
37
|
+
def _process_property_value(self, pname, value):
|
|
38
|
+
"""Passes values directly to allow core widget validations to handle exceptions."""
|
|
39
|
+
return super()._process_property_value(pname, value)
|
|
40
|
+
|
|
41
|
+
def _code_define_callback_args(self, cmd_pname, cmd):
|
|
42
|
+
"""
|
|
43
|
+
Declares what the callback receives, so the generated stub has a
|
|
44
|
+
matching parameter.
|
|
45
|
+
|
|
46
|
+
Without this, pygubu generates a stub taking no arguments and the
|
|
47
|
+
widget calls it with the chosen path:
|
|
48
|
+
|
|
49
|
+
TypeError: on_path() takes 1 positional argument but 2 were given
|
|
50
|
+
"""
|
|
51
|
+
return ("selected_path",)
|
|
52
|
+
|
|
53
|
+
def _code_set_property(self, targetid, pname, value, code_bag):
|
|
54
|
+
"""
|
|
55
|
+
Emits `filetypes` as a real Python list, not a quoted string.
|
|
56
|
+
|
|
57
|
+
The bracketed form contains double quotes, and the default handler
|
|
58
|
+
wraps the whole value in double quotes again:
|
|
59
|
+
|
|
60
|
+
sctkpathchooser1.configure(filetypes="[".py"]", ...)
|
|
61
|
+
^^^^^^^^^
|
|
62
|
+
SyntaxError: invalid syntax
|
|
63
|
+
|
|
64
|
+
Emitting the literal unquoted gives valid Python and a real list. A
|
|
65
|
+
bare comma-separated value has no brackets and no quotes, so it is
|
|
66
|
+
emitted as a quoted string and parsed by the widget as usual.
|
|
67
|
+
"""
|
|
68
|
+
if pname == "filetypes" and value:
|
|
69
|
+
text = str(value).strip()
|
|
70
|
+
if text.startswith("[") and text.endswith("]"):
|
|
71
|
+
code_bag[pname] = text
|
|
72
|
+
return None
|
|
73
|
+
return super()._code_set_property(targetid, pname, value, code_bag)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Register the widget into Pygubu's parsing engine
|
|
77
|
+
builder_id = f"{builder_namespace}.{widget_classname}"
|
|
78
|
+
|
|
79
|
+
register_widget(builder_id, sCTkPathChooserBO, 'sCTkPathChooser', ("ttk", section_name))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Map the 'command' option directly to Pygubu's native callback editor panel
|
|
83
|
+
register_custom_property(
|
|
84
|
+
builder_id,
|
|
85
|
+
"command",
|
|
86
|
+
"commandentry",
|
|
87
|
+
help="Method callback string triggered on path confirmation"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Register custom attribute fields to display inside the Designer properties panel
|
|
92
|
+
|
|
93
|
+
register_custom_property(
|
|
94
|
+
builder_id,
|
|
95
|
+
"width",
|
|
96
|
+
"naturalnumber",
|
|
97
|
+
help="Set total width in pixels of file entry and button. File path width = width - button width"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
register_custom_property(
|
|
101
|
+
builder_id,
|
|
102
|
+
"height",
|
|
103
|
+
"naturalnumber",
|
|
104
|
+
help="Set height in pixels allocated to file path and button frame. Button and file path height set separately."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
register_custom_property(
|
|
108
|
+
builder_id,
|
|
109
|
+
"type",
|
|
110
|
+
"choice",
|
|
111
|
+
values=("", "file", "directory"),
|
|
112
|
+
state="readonly",
|
|
113
|
+
default_value="directory",
|
|
114
|
+
help="What the chooser selects. The widget's own default is 'directory'; "
|
|
115
|
+
"filetypes only applies when this is 'file'."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
register_custom_property(
|
|
119
|
+
builder_id,
|
|
120
|
+
"justify",
|
|
121
|
+
"choice",
|
|
122
|
+
values=("", "left", "right", "center"),
|
|
123
|
+
state="readonly",
|
|
124
|
+
help="Align long path strings to prioritize viewing starting roots or trailing filenames"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
register_custom_property(
|
|
128
|
+
builder_id,
|
|
129
|
+
"title",
|
|
130
|
+
"entry",
|
|
131
|
+
help="Window header title string text"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
register_custom_property(
|
|
135
|
+
builder_id,
|
|
136
|
+
"initialdir",
|
|
137
|
+
"entry",
|
|
138
|
+
help="Starting directory path location string"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
register_custom_property(
|
|
142
|
+
builder_id,
|
|
143
|
+
"initialfile",
|
|
144
|
+
"entry",
|
|
145
|
+
help="Starting target highlight file path string"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
register_custom_property(
|
|
149
|
+
builder_id,
|
|
150
|
+
"filetypes",
|
|
151
|
+
"entry",
|
|
152
|
+
help='Filter by file extension. Preferred: [".py", ".txt"]. Bare comma-separated (.py, .txt) also works, but cannot contain a comma inside a value.'
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
register_custom_property(
|
|
156
|
+
builder_id,
|
|
157
|
+
"entry_height",
|
|
158
|
+
"naturalnumber",
|
|
159
|
+
help="Set height in pixels of the file path field widget"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
register_custom_property(
|
|
163
|
+
builder_id,
|
|
164
|
+
"btn_width",
|
|
165
|
+
"naturalnumber",
|
|
166
|
+
help="Set width in pixels of the button"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
register_custom_property(
|
|
170
|
+
builder_id,
|
|
171
|
+
"btn_height",
|
|
172
|
+
"naturalnumber",
|
|
173
|
+
help="Set height in pixels of the button"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
register_custom_property(
|
|
177
|
+
builder_id,
|
|
178
|
+
"btn_text",
|
|
179
|
+
"entry",
|
|
180
|
+
help="Override default button text (e.g. 'Select', '▶' or '>')"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
register_custom_property(
|
|
184
|
+
builder_id,
|
|
185
|
+
"browser_width",
|
|
186
|
+
"naturalnumber",
|
|
187
|
+
help="Set width of the pop-up file browser window in pixels"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
register_custom_property(
|
|
191
|
+
builder_id,
|
|
192
|
+
"browser_height",
|
|
193
|
+
"naturalnumber",
|
|
194
|
+
help="Set height of the pop-up file browser window in pixels"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
register_custom_property(
|
|
198
|
+
builder_id,
|
|
199
|
+
"state",
|
|
200
|
+
"choice",
|
|
201
|
+
values=("", "normal", "disabled"),
|
|
202
|
+
state="readonly",
|
|
203
|
+
help="Set widget interaction state"
|
|
204
|
+
)
|