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