bootstack 0.1.0a1__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.
- bootstack/__init__.py +249 -0
- bootstack/__main__.py +5 -0
- bootstack/api/__init__.py +127 -0
- bootstack/api/app.py +30 -0
- bootstack/api/constants.py +3 -0
- bootstack/api/data.py +23 -0
- bootstack/api/dialogs.py +44 -0
- bootstack/api/i18n.py +17 -0
- bootstack/api/localization.py +16 -0
- bootstack/api/menu.py +7 -0
- bootstack/api/style.py +23 -0
- bootstack/api/utils.py +24 -0
- bootstack/api/widgets.py +137 -0
- bootstack/assets/__init__.py +24 -0
- bootstack/assets/bootstack-transparent.png +0 -0
- bootstack/assets/bootstack.ico +0 -0
- bootstack/assets/bootstack.png +0 -0
- bootstack/assets/elements/__init__.py +0 -0
- bootstack/assets/elements/badge-pill.png +0 -0
- bootstack/assets/elements/badge-square.png +0 -0
- bootstack/assets/elements/border.png +0 -0
- bootstack/assets/elements/button-compact.png +0 -0
- bootstack/assets/elements/button-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-after-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-after-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-before-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-before-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-center-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-center-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-after-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-after-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-before-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-before-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-center-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-center-default.png +0 -0
- bootstack/assets/elements/checkbox-checked.png +0 -0
- bootstack/assets/elements/checkbox-indeterminate.png +0 -0
- bootstack/assets/elements/checkbox-unchecked.png +0 -0
- bootstack/assets/elements/field.png +0 -0
- bootstack/assets/elements/input-after-compact.png +0 -0
- bootstack/assets/elements/input-after-default.png +0 -0
- bootstack/assets/elements/input-before-compact.png +0 -0
- bootstack/assets/elements/input-before-default.png +0 -0
- bootstack/assets/elements/input-compact.png +0 -0
- bootstack/assets/elements/input-default.png +0 -0
- bootstack/assets/elements/list-item-separated.png +0 -0
- bootstack/assets/elements/list-item.png +0 -0
- bootstack/assets/elements/manifest.toml +480 -0
- bootstack/assets/elements/menu-item.png +0 -0
- bootstack/assets/elements/nav-button-compact.png +0 -0
- bootstack/assets/elements/nav-button-default.png +0 -0
- bootstack/assets/elements/nav-icon-button-compact.png +0 -0
- bootstack/assets/elements/nav-icon-button-default.png +0 -0
- bootstack/assets/elements/notebook-client-border.png +0 -0
- bootstack/assets/elements/notebook-tab-active.png +0 -0
- bootstack/assets/elements/notebook-tab-bar.png +0 -0
- bootstack/assets/elements/notebook-tab-normal.png +0 -0
- bootstack/assets/elements/notebook-tab-pill.png +0 -0
- bootstack/assets/elements/progress-bar-horizontal-striped.png +0 -0
- bootstack/assets/elements/progress-bar-solid.png +0 -0
- bootstack/assets/elements/progress-bar-thin.png +0 -0
- bootstack/assets/elements/progress-bar-vertical-striped.png +0 -0
- bootstack/assets/elements/radio-selected.png +0 -0
- bootstack/assets/elements/radio-unselected.png +0 -0
- bootstack/assets/elements/scrollbar-horizontal.png +0 -0
- bootstack/assets/elements/scrollbar-vertical.png +0 -0
- bootstack/assets/elements/slider-handle-focus.png +0 -0
- bootstack/assets/elements/slider-handle.png +0 -0
- bootstack/assets/elements/slider-track-horizontal.png +0 -0
- bootstack/assets/elements/slider-track-vertical.png +0 -0
- bootstack/assets/elements/switch-off.png +0 -0
- bootstack/assets/elements/switch-on.png +0 -0
- bootstack/assets/elements/tabs-bar-horizontal.png +0 -0
- bootstack/assets/elements/tabs-bar-vertical.png +0 -0
- bootstack/assets/elements/tabs-pill.png +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +875 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +875 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +851 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +914 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/themes/__init__.py +0 -0
- bootstack/assets/themes/amber-dark.json +32 -0
- bootstack/assets/themes/amber-light.json +32 -0
- bootstack/assets/themes/aurora-dark.json +32 -0
- bootstack/assets/themes/aurora-light.json +32 -0
- bootstack/assets/themes/bootstrap-dark.json +32 -0
- bootstack/assets/themes/bootstrap-light.json +32 -0
- bootstack/assets/themes/classic-dark.json +32 -0
- bootstack/assets/themes/classic-light.json +32 -0
- bootstack/assets/themes/docs-dark.json +32 -0
- bootstack/assets/themes/docs-light.json +32 -0
- bootstack/assets/themes/forest-dark.json +32 -0
- bootstack/assets/themes/forest-light.json +32 -0
- bootstack/assets/themes/ocean-dark.json +32 -0
- bootstack/assets/themes/ocean-light.json +32 -0
- bootstack/assets/themes/rose-dark.json +32 -0
- bootstack/assets/themes/rose-light.json +32 -0
- bootstack/assets/widgets/__init__.py +0 -0
- bootstack/assets/widgets/badge-default.png +0 -0
- bootstack/assets/widgets/badge-pill.png +0 -0
- bootstack/assets/widgets/border.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-after.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-before.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-center.png +0 -0
- bootstack/assets/widgets/button-group-vertical-after.png +0 -0
- bootstack/assets/widgets/button-group-vertical-before.png +0 -0
- bootstack/assets/widgets/button-group-vertical-center.png +0 -0
- bootstack/assets/widgets/button.png +0 -0
- bootstack/assets/widgets/checkbox-checked.png +0 -0
- bootstack/assets/widgets/checkbox-indeterminate.png +0 -0
- bootstack/assets/widgets/checkbox-unchecked.png +0 -0
- bootstack/assets/widgets/field.png +0 -0
- bootstack/assets/widgets/icon-button.png +0 -0
- bootstack/assets/widgets/input-inner.png +0 -0
- bootstack/assets/widgets/input-prefix.png +0 -0
- bootstack/assets/widgets/input-suffix.png +0 -0
- bootstack/assets/widgets/input.png +0 -0
- bootstack/assets/widgets/list-item-focus.png +0 -0
- bootstack/assets/widgets/list-item-separated.png +0 -0
- bootstack/assets/widgets/menu-item-separated.png +0 -0
- bootstack/assets/widgets/notebook-client-border.png +0 -0
- bootstack/assets/widgets/notebook-pill-active.png +0 -0
- bootstack/assets/widgets/notebook-pill-inactive.png +0 -0
- bootstack/assets/widgets/notebook-tab-active.png +0 -0
- bootstack/assets/widgets/notebook-tab-border.png +0 -0
- bootstack/assets/widgets/notebook-tab-normal.png +0 -0
- bootstack/assets/widgets/notebook-underline.png +0 -0
- bootstack/assets/widgets/progress-bar-horizontal-default.png +0 -0
- bootstack/assets/widgets/progress-bar-horizontal-striped.png +0 -0
- bootstack/assets/widgets/progress-bar-vertical-default.png +0 -0
- bootstack/assets/widgets/progress-bar-vertical-striped.png +0 -0
- bootstack/assets/widgets/progress-trough-horizontal.png +0 -0
- bootstack/assets/widgets/progress-trough-vertical.png +0 -0
- bootstack/assets/widgets/radio-selected.png +0 -0
- bootstack/assets/widgets/radio-unselected.png +0 -0
- bootstack/assets/widgets/scrollbar-horizontal-rounded.png +0 -0
- bootstack/assets/widgets/scrollbar-vertical-rounded.png +0 -0
- bootstack/assets/widgets/separator-horizontal.png +0 -0
- bootstack/assets/widgets/separator-vertical.png +0 -0
- bootstack/assets/widgets/slider-handle-focus.png +0 -0
- bootstack/assets/widgets/slider-handle.png +0 -0
- bootstack/assets/widgets/slider-track-horizontal.png +0 -0
- bootstack/assets/widgets/slider-track-vertical.png +0 -0
- bootstack/assets/widgets/switch-off.png +0 -0
- bootstack/assets/widgets/switch-on.png +0 -0
- bootstack/assets/widgets/tabs-bar-horizontal.png +0 -0
- bootstack/assets/widgets/tabs-bar-vertical.png +0 -0
- bootstack/assets/widgets/tabs-pill.png +0 -0
- bootstack/cli/__init__.py +124 -0
- bootstack/cli/__main__.py +6 -0
- bootstack/cli/add.py +439 -0
- bootstack/cli/build.py +115 -0
- bootstack/cli/config.py +287 -0
- bootstack/cli/demo.py +1267 -0
- bootstack/cli/doctor.py +195 -0
- bootstack/cli/list_cmd.py +71 -0
- bootstack/cli/promote.py +120 -0
- bootstack/cli/pyinstaller.py +246 -0
- bootstack/cli/run.py +99 -0
- bootstack/cli/start.py +105 -0
- bootstack/cli/templates/__init__.py +861 -0
- bootstack/constants.py +325 -0
- bootstack/core/__init__.py +34 -0
- bootstack/core/capabilities/__init__.py +45 -0
- bootstack/core/capabilities/after.py +103 -0
- bootstack/core/capabilities/bind.py +154 -0
- bootstack/core/capabilities/bindtags.py +112 -0
- bootstack/core/capabilities/busy.py +61 -0
- bootstack/core/capabilities/clipboard.py +88 -0
- bootstack/core/capabilities/focus.py +118 -0
- bootstack/core/capabilities/grab.py +65 -0
- bootstack/core/capabilities/grid.py +188 -0
- bootstack/core/capabilities/localization.py +231 -0
- bootstack/core/capabilities/pack.py +119 -0
- bootstack/core/capabilities/place.py +92 -0
- bootstack/core/capabilities/selection.py +136 -0
- bootstack/core/capabilities/signals.py +242 -0
- bootstack/core/capabilities/winfo.py +315 -0
- bootstack/core/colorutils.py +234 -0
- bootstack/core/exceptions.py +95 -0
- bootstack/core/images.py +283 -0
- bootstack/core/localization/README.md +90 -0
- bootstack/core/localization/__init__.py +13 -0
- bootstack/core/localization/intl_format.py +580 -0
- bootstack/core/localization/msgcat.py +425 -0
- bootstack/core/localization/specs.py +143 -0
- bootstack/core/mixins/__init__.py +1 -0
- bootstack/core/mixins/ttk_state.py +35 -0
- bootstack/core/mixins/widget.py +132 -0
- bootstack/core/publisher.py +147 -0
- bootstack/core/signals/README.md +112 -0
- bootstack/core/signals/__init__.py +8 -0
- bootstack/core/signals/integration.py +100 -0
- bootstack/core/signals/signal.py +317 -0
- bootstack/core/signals/types.py +4 -0
- bootstack/core/validation/__init__.py +5 -0
- bootstack/core/validation/types.py +13 -0
- bootstack/core/validation/validation_result.py +17 -0
- bootstack/core/validation/validation_rules.py +112 -0
- bootstack/core/variables.py +62 -0
- bootstack/datasource/README.md +607 -0
- bootstack/datasource/__init__.py +51 -0
- bootstack/datasource/base.py +474 -0
- bootstack/datasource/file_source.py +541 -0
- bootstack/datasource/memory_source.py +482 -0
- bootstack/datasource/sqlite_source.py +453 -0
- bootstack/datasource/types.py +259 -0
- bootstack/dialogs/__init__.py +56 -0
- bootstack/dialogs/colorchooser.py +674 -0
- bootstack/dialogs/colordropper.py +257 -0
- bootstack/dialogs/datedialog.py +404 -0
- bootstack/dialogs/dialog.py +514 -0
- bootstack/dialogs/filterdialog.py +358 -0
- bootstack/dialogs/fontdialog.py +339 -0
- bootstack/dialogs/formdialog.py +541 -0
- bootstack/dialogs/message.py +489 -0
- bootstack/dialogs/query.py +561 -0
- bootstack/py.typed +1 -0
- bootstack/runtime/__init__.py +3 -0
- bootstack/runtime/app.py +879 -0
- bootstack/runtime/base_window.py +786 -0
- bootstack/runtime/events.py +399 -0
- bootstack/runtime/menu.py +510 -0
- bootstack/runtime/shortcuts.py +423 -0
- bootstack/runtime/tk_patch.py +31 -0
- bootstack/runtime/toplevel.py +131 -0
- bootstack/runtime/utility.py +371 -0
- bootstack/runtime/visual_focus.py +228 -0
- bootstack/runtime/window_utilities.py +1043 -0
- bootstack/style/__init__.py +5498 -0
- bootstack/style/bootstyle.py +507 -0
- bootstack/style/bootstyle_builder_base.py +752 -0
- bootstack/style/bootstyle_builder_mixed.py +93 -0
- bootstack/style/bootstyle_builder_tk.py +109 -0
- bootstack/style/bootstyle_builder_ttk.py +354 -0
- bootstack/style/builders/__init__.py +51 -0
- bootstack/style/builders/badge.py +44 -0
- bootstack/style/builders/button.py +453 -0
- bootstack/style/builders/buttongroup.py +344 -0
- bootstack/style/builders/calendar.py +271 -0
- bootstack/style/builders/checkbutton.py +95 -0
- bootstack/style/builders/combobox.py +112 -0
- bootstack/style/builders/contextmenu.py +268 -0
- bootstack/style/builders/entry.py +83 -0
- bootstack/style/builders/expander.py +171 -0
- bootstack/style/builders/field.py +312 -0
- bootstack/style/builders/frame.py +27 -0
- bootstack/style/builders/label.py +28 -0
- bootstack/style/builders/labelframe.py +41 -0
- bootstack/style/builders/listview.py +267 -0
- bootstack/style/builders/menubar.py +74 -0
- bootstack/style/builders/menubutton.py +408 -0
- bootstack/style/builders/notebook.py +316 -0
- bootstack/style/builders/panedwindow.py +25 -0
- bootstack/style/builders/progressbar.py +71 -0
- bootstack/style/builders/radiobutton.py +68 -0
- bootstack/style/builders/scale.py +66 -0
- bootstack/style/builders/scrollbar.py +360 -0
- bootstack/style/builders/separator.py +45 -0
- bootstack/style/builders/sidenav.py +313 -0
- bootstack/style/builders/sizegrip.py +15 -0
- bootstack/style/builders/spinbox.py +119 -0
- bootstack/style/builders/switch.py +67 -0
- bootstack/style/builders/tabitem.py +205 -0
- bootstack/style/builders/toolbutton.py +260 -0
- bootstack/style/builders/tooltip.py +26 -0
- bootstack/style/builders/treeview.py +269 -0
- bootstack/style/builders/utils.py +404 -0
- bootstack/style/builders_tk/__init__.py +16 -0
- bootstack/style/builders_tk/defaults.py +229 -0
- bootstack/style/element.py +173 -0
- bootstack/style/style.py +499 -0
- bootstack/style/theme_provider.py +449 -0
- bootstack/style/tk_patch.py +5 -0
- bootstack/style/token_maps.py +42 -0
- bootstack/style/types.py +32 -0
- bootstack/style/typography.py +527 -0
- bootstack/style/utility.py +696 -0
- bootstack/themes/__init__.py +12 -0
- bootstack/themes/standard.py +415 -0
- bootstack/themes/user.py +45 -0
- bootstack/widgets/__init__.py +53 -0
- bootstack/widgets/composites/__init__.py +38 -0
- bootstack/widgets/composites/accordion.py +385 -0
- bootstack/widgets/composites/appshell.py +445 -0
- bootstack/widgets/composites/buttongroup.py +391 -0
- bootstack/widgets/composites/calendar.py +914 -0
- bootstack/widgets/composites/compositeframe.py +282 -0
- bootstack/widgets/composites/contextmenu.py +1754 -0
- bootstack/widgets/composites/dateentry.py +261 -0
- bootstack/widgets/composites/dropdownbutton.py +190 -0
- bootstack/widgets/composites/expander.py +508 -0
- bootstack/widgets/composites/field.py +448 -0
- bootstack/widgets/composites/floodgauge.py +434 -0
- bootstack/widgets/composites/form.py +983 -0
- bootstack/widgets/composites/labeledscale.py +209 -0
- bootstack/widgets/composites/list/__init__.py +15 -0
- bootstack/widgets/composites/list/listitem.py +733 -0
- bootstack/widgets/composites/list/listview.py +1507 -0
- bootstack/widgets/composites/menubar.py +303 -0
- bootstack/widgets/composites/meter.py +882 -0
- bootstack/widgets/composites/numericentry.py +183 -0
- bootstack/widgets/composites/pagestack.py +330 -0
- bootstack/widgets/composites/passwordentry.py +149 -0
- bootstack/widgets/composites/pathentry.py +223 -0
- bootstack/widgets/composites/radiogroup.py +466 -0
- bootstack/widgets/composites/scrolledtext.py +388 -0
- bootstack/widgets/composites/scrolledtext.pyi +186 -0
- bootstack/widgets/composites/scrollview.py +675 -0
- bootstack/widgets/composites/selectbox.py +544 -0
- bootstack/widgets/composites/sidenav/__init__.py +24 -0
- bootstack/widgets/composites/sidenav/group.py +485 -0
- bootstack/widgets/composites/sidenav/header.py +83 -0
- bootstack/widgets/composites/sidenav/item.py +413 -0
- bootstack/widgets/composites/sidenav/separator.py +51 -0
- bootstack/widgets/composites/sidenav/view.py +919 -0
- bootstack/widgets/composites/spinnerentry.py +232 -0
- bootstack/widgets/composites/tableview/__init__.py +5 -0
- bootstack/widgets/composites/tableview/tableview.py +2254 -0
- bootstack/widgets/composites/tableview/types.py +169 -0
- bootstack/widgets/composites/tabs/__init__.py +6 -0
- bootstack/widgets/composites/tabs/tabitem.py +372 -0
- bootstack/widgets/composites/tabs/tabs.py +478 -0
- bootstack/widgets/composites/tabs/tabview.py +352 -0
- bootstack/widgets/composites/textentry.py +90 -0
- bootstack/widgets/composites/timeentry.py +189 -0
- bootstack/widgets/composites/toast.py +364 -0
- bootstack/widgets/composites/togglegroup.py +382 -0
- bootstack/widgets/composites/toolbar.py +393 -0
- bootstack/widgets/composites/tooltip.py +404 -0
- bootstack/widgets/internal/__init__.py +0 -0
- bootstack/widgets/internal/wrapper_base.py +304 -0
- bootstack/widgets/mixins/__init__.py +25 -0
- bootstack/widgets/mixins/configure_mixin.py +186 -0
- bootstack/widgets/mixins/entry_mixin.py +70 -0
- bootstack/widgets/mixins/font_mixin.py +346 -0
- bootstack/widgets/mixins/icon_mixin.py +38 -0
- bootstack/widgets/mixins/localization_mixin.py +255 -0
- bootstack/widgets/mixins/signal_mixin.py +272 -0
- bootstack/widgets/mixins/validation_mixin.py +204 -0
- bootstack/widgets/parts/__init__.py +11 -0
- bootstack/widgets/parts/numberentry_part.py +345 -0
- bootstack/widgets/parts/spinnerentry_part.py +394 -0
- bootstack/widgets/parts/textentry_part.py +344 -0
- bootstack/widgets/primitives/__init__.py +55 -0
- bootstack/widgets/primitives/badge.py +44 -0
- bootstack/widgets/primitives/button.py +89 -0
- bootstack/widgets/primitives/card.py +66 -0
- bootstack/widgets/primitives/checkbutton.py +124 -0
- bootstack/widgets/primitives/checktoggle.py +53 -0
- bootstack/widgets/primitives/combobox.py +165 -0
- bootstack/widgets/primitives/entry.py +98 -0
- bootstack/widgets/primitives/frame.py +206 -0
- bootstack/widgets/primitives/gridframe.py +479 -0
- bootstack/widgets/primitives/label.py +95 -0
- bootstack/widgets/primitives/labelframe.py +63 -0
- bootstack/widgets/primitives/menubutton.py +118 -0
- bootstack/widgets/primitives/notebook.py +551 -0
- bootstack/widgets/primitives/optionmenu.py +248 -0
- bootstack/widgets/primitives/packframe.py +228 -0
- bootstack/widgets/primitives/panedwindow.py +58 -0
- bootstack/widgets/primitives/progressbar.py +95 -0
- bootstack/widgets/primitives/radiobutton.py +115 -0
- bootstack/widgets/primitives/radiotoggle.py +50 -0
- bootstack/widgets/primitives/scale.py +85 -0
- bootstack/widgets/primitives/scrollbar.py +56 -0
- bootstack/widgets/primitives/separator.py +56 -0
- bootstack/widgets/primitives/sizegrip.py +47 -0
- bootstack/widgets/primitives/spinbox.py +91 -0
- bootstack/widgets/primitives/switch.py +41 -0
- bootstack/widgets/primitives/treeview.py +77 -0
- bootstack/widgets/types.py +20 -0
- bootstack-0.1.0a1.dist-info/METADATA +196 -0
- bootstack-0.1.0a1.dist-info/RECORD +419 -0
- bootstack-0.1.0a1.dist-info/WHEEL +5 -0
- bootstack-0.1.0a1.dist-info/entry_points.txt +2 -0
- bootstack-0.1.0a1.dist-info/licenses/LICENSE +22 -0
- bootstack-0.1.0a1.dist-info/licenses/NOTICE +10 -0
- bootstack-0.1.0a1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Signal integration mixins for bootstack widgets.
|
|
2
|
+
|
|
3
|
+
Provides seamless integration between tkinter Variables and reactive Signals,
|
|
4
|
+
exposing both as properties on widgets that support `textvariable` and `variable`
|
|
5
|
+
options. The mixins maintain bidirectional synchronization between Variables and
|
|
6
|
+
Signals automatically.
|
|
7
|
+
|
|
8
|
+
These mixins are thin glue layers that delegate to the core signal capability
|
|
9
|
+
module for normalization and binding logic.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import tkinter as tk
|
|
15
|
+
from typing import Any, TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from bootstack.core.signals import Signal
|
|
19
|
+
|
|
20
|
+
from bootstack.core.capabilities.signals import (
|
|
21
|
+
is_signal,
|
|
22
|
+
is_variable,
|
|
23
|
+
normalize_signal,
|
|
24
|
+
create_signal,
|
|
25
|
+
infer_default_value_for_widget,
|
|
26
|
+
query_binding,
|
|
27
|
+
)
|
|
28
|
+
from bootstack.widgets.mixins.configure_mixin import configure_delegate
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TextSignalMixin:
|
|
32
|
+
"""Mixin providing `.textvariable` and `.textsignal` properties for text-based widgets.
|
|
33
|
+
|
|
34
|
+
For widgets that support the `textvariable` option (Entry, Label, Button, etc.),
|
|
35
|
+
this mixin exposes both the underlying tk.Variable and a reactive Signal as properties,
|
|
36
|
+
maintaining bidirectional synchronization between them.
|
|
37
|
+
|
|
38
|
+
This mixin delegates normalization and binding logic to the core signals capability.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
textvariable (Variable): The underlying tk.Variable (usually StringVar).
|
|
42
|
+
textsignal (Signal): The reactive Signal wrapper with subscribe/map capabilities.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, *args, **kwargs):
|
|
46
|
+
"""Initialize mixin and extract textsignal/textvariable parameters before tkinter sees them."""
|
|
47
|
+
# Extract textsignal and textvariable before passing kwargs to tkinter
|
|
48
|
+
textsignal_value = kwargs.pop('textsignal', None)
|
|
49
|
+
textvariable_value = kwargs.pop('textvariable', None)
|
|
50
|
+
|
|
51
|
+
# Call parent __init__
|
|
52
|
+
super().__init__(*args, **kwargs)
|
|
53
|
+
|
|
54
|
+
# Apply textsignal or textvariable after widget construction
|
|
55
|
+
# Prefer textsignal if both are provided
|
|
56
|
+
if textsignal_value is not None:
|
|
57
|
+
self._config_delegate_set('textsignal', textsignal_value)
|
|
58
|
+
elif textvariable_value is not None:
|
|
59
|
+
self._config_delegate_set('textvariable', textvariable_value)
|
|
60
|
+
|
|
61
|
+
@configure_delegate("textvariable", "textsignal")
|
|
62
|
+
def _delegate_textsignal(self, value: Any = None):
|
|
63
|
+
"""Handle textvariable and textsignal configuration.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
value: Signal, tk.Variable, or None for query.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Current value for query path, None for set path.
|
|
70
|
+
"""
|
|
71
|
+
# Query path - return stored value
|
|
72
|
+
if value is None:
|
|
73
|
+
return query_binding(
|
|
74
|
+
getattr(self, '_textsignal', None),
|
|
75
|
+
getattr(self, '_textvariable', None),
|
|
76
|
+
self._ttk_base, # type: ignore[misc]
|
|
77
|
+
self,
|
|
78
|
+
'textvariable',
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Set path - normalize and apply
|
|
82
|
+
binding = normalize_signal(value, default_value="")
|
|
83
|
+
if binding is not None:
|
|
84
|
+
self._textsignal = binding.signal
|
|
85
|
+
self._textvariable = binding.variable
|
|
86
|
+
var_to_set = binding.tk_value
|
|
87
|
+
else:
|
|
88
|
+
# String (Tcl variable name) or other - pass through
|
|
89
|
+
var_to_set = value
|
|
90
|
+
if value:
|
|
91
|
+
self._textvariable = value
|
|
92
|
+
|
|
93
|
+
# Apply via base ttk widget to avoid recursion
|
|
94
|
+
return self._ttk_base.configure(self, textvariable=var_to_set) # type: ignore[misc]
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def textsignal(self) -> 'Signal[str]':
|
|
98
|
+
"""Get or lazily create the textsignal.
|
|
99
|
+
|
|
100
|
+
If no textvariable has been set, creates a new Signal with empty string.
|
|
101
|
+
If textvariable exists, wraps it in a Signal.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
The reactive Signal for this widget's text.
|
|
105
|
+
"""
|
|
106
|
+
if not hasattr(self, '_textsignal'):
|
|
107
|
+
# Create fresh signal
|
|
108
|
+
binding = create_signal("")
|
|
109
|
+
self._textsignal = binding.signal
|
|
110
|
+
self._textvariable = binding.variable
|
|
111
|
+
try:
|
|
112
|
+
self._ttk_base.configure(self, textvariable=binding.tk_value) # type: ignore[misc]
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
return self._textsignal
|
|
116
|
+
|
|
117
|
+
@textsignal.setter
|
|
118
|
+
def textsignal(self, value: 'Signal[str]') -> None:
|
|
119
|
+
"""Set the textsignal, extracting and configuring its underlying variable.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
value: Signal to use for this widget's text.
|
|
123
|
+
"""
|
|
124
|
+
self._delegate_textsignal(value)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def textvariable(self) -> tk.Variable:
|
|
128
|
+
"""Get the underlying tk.Variable.
|
|
129
|
+
|
|
130
|
+
If not yet created, accessing this property will trigger lazy creation
|
|
131
|
+
via the textsignal property.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
The tk.Variable (usually StringVar) for this widget's text.
|
|
135
|
+
"""
|
|
136
|
+
if not hasattr(self, '_textvariable'):
|
|
137
|
+
# Trigger lazy creation via textsignal
|
|
138
|
+
_ = self.textsignal
|
|
139
|
+
return self._textvariable
|
|
140
|
+
|
|
141
|
+
@textvariable.setter
|
|
142
|
+
def textvariable(self, value: tk.Variable) -> None:
|
|
143
|
+
"""Set the textvariable, creating a synced Signal automatically.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
value: tk.Variable to use for this widget's text.
|
|
147
|
+
"""
|
|
148
|
+
self._delegate_textsignal(value)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class SignalMixin:
|
|
152
|
+
"""Mixin providing `.variable` and `.signal` properties for value-based widgets.
|
|
153
|
+
|
|
154
|
+
For widgets that support the `variable` option (Checkbutton, Radiobutton, Scale, etc.),
|
|
155
|
+
this mixin exposes both the underlying tk.Variable and a reactive Signal as properties,
|
|
156
|
+
maintaining bidirectional synchronization between them.
|
|
157
|
+
|
|
158
|
+
This mixin delegates normalization and binding logic to the core signals capability.
|
|
159
|
+
|
|
160
|
+
Attributes:
|
|
161
|
+
variable (Variable): The underlying tk.Variable (IntVar, DoubleVar, BooleanVar, etc.).
|
|
162
|
+
signal (Signal): The reactive Signal wrapper with subscribe/map capabilities.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def __init__(self, *args, **kwargs):
|
|
166
|
+
"""Initialize mixin and extract signal/variable parameters before tkinter sees them."""
|
|
167
|
+
# Extract signal and variable before passing kwargs to tkinter
|
|
168
|
+
signal_value = kwargs.pop('signal', None)
|
|
169
|
+
variable_value = kwargs.pop('variable', None)
|
|
170
|
+
|
|
171
|
+
# Call parent __init__
|
|
172
|
+
super().__init__(*args, **kwargs)
|
|
173
|
+
|
|
174
|
+
# Apply signal or variable after widget construction
|
|
175
|
+
# Prefer signal if both are provided
|
|
176
|
+
if signal_value is not None:
|
|
177
|
+
self._config_delegate_set('signal', signal_value)
|
|
178
|
+
elif variable_value is not None:
|
|
179
|
+
self._config_delegate_set('variable', variable_value)
|
|
180
|
+
|
|
181
|
+
@configure_delegate("variable", "signal")
|
|
182
|
+
def _delegate_signal(self, value: Any = None):
|
|
183
|
+
"""Handle variable and signal configuration.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
value: Signal, tk.Variable, or None for query.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Current value for query path, None for set path.
|
|
190
|
+
"""
|
|
191
|
+
# Query path - return stored value
|
|
192
|
+
if value is None:
|
|
193
|
+
return query_binding(
|
|
194
|
+
getattr(self, '_signal', None),
|
|
195
|
+
getattr(self, '_variable', None),
|
|
196
|
+
self._ttk_base, # type: ignore[misc]
|
|
197
|
+
self,
|
|
198
|
+
'variable',
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Set path - normalize and apply
|
|
202
|
+
binding = normalize_signal(value)
|
|
203
|
+
if binding is not None:
|
|
204
|
+
self._signal = binding.signal
|
|
205
|
+
self._variable = binding.variable
|
|
206
|
+
var_to_set = binding.tk_value
|
|
207
|
+
else:
|
|
208
|
+
# String (Tcl variable name) or other - pass through
|
|
209
|
+
var_to_set = value
|
|
210
|
+
if value:
|
|
211
|
+
self._variable = value
|
|
212
|
+
|
|
213
|
+
# Apply via base ttk widget to avoid recursion
|
|
214
|
+
return self._ttk_base.configure(self, variable=var_to_set) # type: ignore[misc]
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def signal(self) -> 'Signal[Any]':
|
|
218
|
+
"""Get or lazily create the signal.
|
|
219
|
+
|
|
220
|
+
If no variable has been set, creates a new Signal with appropriate default
|
|
221
|
+
(False for Checkbutton, 0 for Scale, etc.). If variable exists, wraps it in a Signal.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
The reactive Signal for this widget's value.
|
|
225
|
+
"""
|
|
226
|
+
if not hasattr(self, '_signal'):
|
|
227
|
+
# Infer default value based on widget type
|
|
228
|
+
default_value = infer_default_value_for_widget(self.winfo_class())
|
|
229
|
+
binding = create_signal(default_value)
|
|
230
|
+
self._signal = binding.signal
|
|
231
|
+
self._variable = binding.variable
|
|
232
|
+
try:
|
|
233
|
+
self._ttk_base.configure(self, variable=binding.tk_value) # type: ignore[misc]
|
|
234
|
+
except Exception:
|
|
235
|
+
pass
|
|
236
|
+
return self._signal
|
|
237
|
+
|
|
238
|
+
@signal.setter
|
|
239
|
+
def signal(self, value: 'Signal[Any]') -> None:
|
|
240
|
+
"""Set the signal, extracting and configuring its underlying variable.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
value: Signal to use for this widget's value.
|
|
244
|
+
"""
|
|
245
|
+
self._delegate_signal(value)
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def variable(self) -> tk.Variable:
|
|
249
|
+
"""Get the underlying tk.Variable.
|
|
250
|
+
|
|
251
|
+
If not yet created, accessing this property will trigger lazy creation
|
|
252
|
+
via the signal property.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
The tk.Variable (IntVar, BooleanVar, DoubleVar, etc.) for this widget's value.
|
|
256
|
+
"""
|
|
257
|
+
if not hasattr(self, '_variable'):
|
|
258
|
+
# Trigger lazy creation via signal
|
|
259
|
+
_ = self.signal
|
|
260
|
+
return self._variable
|
|
261
|
+
|
|
262
|
+
@variable.setter
|
|
263
|
+
def variable(self, value: tk.Variable) -> None:
|
|
264
|
+
"""Set the variable, creating a synced Signal automatically.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
value: tk.Variable to use for this widget's value.
|
|
268
|
+
"""
|
|
269
|
+
self._delegate_signal(value)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
__all__ = ["TextSignalMixin", "SignalMixin"]
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Validation mixin for widgets with enhanced event system.
|
|
2
|
+
|
|
3
|
+
This mixin provides validation functionality using the enhanced event system
|
|
4
|
+
that allows passing data directly through virtual events.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from tkinter import TclError
|
|
10
|
+
from tkinter.ttk import Widget
|
|
11
|
+
from typing import Any, Callable, Optional
|
|
12
|
+
|
|
13
|
+
from bootstack.core.validation import ValidationRule
|
|
14
|
+
from bootstack.core.validation.types import RuleTriggerType, RuleType, ValidationOptions
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ValidationMixin(Widget):
|
|
18
|
+
"""Pure-Tkinter validation mixin for bound widgets.
|
|
19
|
+
|
|
20
|
+
Provides debounced auto-validation on key/blur with virtual event emission.
|
|
21
|
+
Event data is accessible via `event.data` in handlers.
|
|
22
|
+
|
|
23
|
+
!!! note "Events"
|
|
24
|
+
|
|
25
|
+
- `<<Valid>>`: Fired when validation passes.
|
|
26
|
+
Provides `event.data` with keys: `value`, `is_valid` (True), `message`.
|
|
27
|
+
|
|
28
|
+
- `<<Invalid>>`: Fired when validation fails.
|
|
29
|
+
Provides `event.data` with keys: `value`, `is_valid` (False), `message`.
|
|
30
|
+
|
|
31
|
+
- `<<Validate>>`: Fired after any validation.
|
|
32
|
+
Provides `event.data` with keys: `value`, `is_valid` (bool), `message`.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
EVENT_VALID = '<<Valid>>'
|
|
36
|
+
EVENT_INVALID = '<<Invalid>>'
|
|
37
|
+
EVENT_VALIDATED = '<<Validate>>'
|
|
38
|
+
SEQ_KEYUP = '<KeyRelease>'
|
|
39
|
+
SEQ_BLUR = '<FocusOut>'
|
|
40
|
+
|
|
41
|
+
def __init__(self, *args, **kwargs):
|
|
42
|
+
"""Initialize validation mixin."""
|
|
43
|
+
# Validation rules
|
|
44
|
+
self._rules: list[ValidationRule] = []
|
|
45
|
+
|
|
46
|
+
# Debounce tracking
|
|
47
|
+
self._debounce_ids: dict[str, str] = {}
|
|
48
|
+
|
|
49
|
+
# Optional convenience callbacks
|
|
50
|
+
self._on_invalid_command: Optional[Callable[[dict[str, Any]], None]] = None
|
|
51
|
+
self._on_valid_command: Optional[Callable[[dict[str, Any]], None]] = None
|
|
52
|
+
self._on_validated_command: Optional[Callable[[dict[str, Any]], None]] = None
|
|
53
|
+
|
|
54
|
+
super().__init__(*args, **kwargs) # next in MRO must be a Tk/ttk widget
|
|
55
|
+
self._setup_validation_binds()
|
|
56
|
+
|
|
57
|
+
# ---------------- Public API ----------------
|
|
58
|
+
|
|
59
|
+
def value(self) -> Any:
|
|
60
|
+
"""Default value accessor; override for non-Entry widgets.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Current widget value
|
|
64
|
+
"""
|
|
65
|
+
if hasattr(self, 'get'):
|
|
66
|
+
try:
|
|
67
|
+
return self.get()
|
|
68
|
+
except AttributeError:
|
|
69
|
+
return None
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
def add_validation_rule(self, rule_type: RuleType, **kwargs: ValidationOptions) -> None:
|
|
73
|
+
"""Add a single validation rule.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
rule_type: Type of validation rule (e.g., 'required', 'min_length')
|
|
77
|
+
**kwargs: Rule-specific options (e.g., min_length=5, message='...')
|
|
78
|
+
"""
|
|
79
|
+
self._rules.append(ValidationRule(rule_type, **kwargs))
|
|
80
|
+
|
|
81
|
+
def add_validation_rules(self, rules: list[ValidationRule]) -> None:
|
|
82
|
+
"""Replace all validation rules.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
rules: List of ValidationRule objects
|
|
86
|
+
"""
|
|
87
|
+
self._rules = list(rules)
|
|
88
|
+
|
|
89
|
+
def validate(self, value: Any, trigger: RuleTriggerType = "manual") -> bool:
|
|
90
|
+
"""Run validation rules against a value.
|
|
91
|
+
|
|
92
|
+
Emits `<<Valid>>`, `<<Invalid>>`, and `<<Validate>>` events
|
|
93
|
+
with data payload containing validation results.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
value: Value to validate
|
|
97
|
+
trigger: Trigger type ('manual', 'key', 'blur', or 'always')
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if validation was performed (regardless of result)
|
|
101
|
+
"""
|
|
102
|
+
ran_rule = False
|
|
103
|
+
payload: dict[str, Any] = {"value": value, "is_valid": True, "message": ""}
|
|
104
|
+
|
|
105
|
+
for rule in self._rules:
|
|
106
|
+
if trigger != "manual" and rule.trigger not in ("always", trigger):
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
ran_rule = True
|
|
110
|
+
result = rule.validate(value)
|
|
111
|
+
payload.update(is_valid=result.is_valid, message=result.message)
|
|
112
|
+
|
|
113
|
+
if not result.is_valid:
|
|
114
|
+
# Emit invalid and validated events with data
|
|
115
|
+
self.event_generate(self.EVENT_INVALID, data=payload)
|
|
116
|
+
self.event_generate(self.EVENT_VALIDATED, data=payload)
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
if ran_rule:
|
|
120
|
+
# Emit valid and validated events with data
|
|
121
|
+
self.event_generate(self.EVENT_VALID, data=payload)
|
|
122
|
+
self.event_generate(self.EVENT_VALIDATED, data=payload)
|
|
123
|
+
|
|
124
|
+
return ran_rule
|
|
125
|
+
|
|
126
|
+
# Optional: ergonomic callback registration
|
|
127
|
+
def on_invalid(self, func: Callable[[dict[str, Any]], None]) -> None:
|
|
128
|
+
"""Register callback for invalid validation."""
|
|
129
|
+
self._on_invalid_command = func
|
|
130
|
+
|
|
131
|
+
def off_invalid(self, bind_id: str | None = None):
|
|
132
|
+
"""Remove the callback for the <<Invalid>> event"""
|
|
133
|
+
self.unbind('<<Invalid>>', bind_id)
|
|
134
|
+
|
|
135
|
+
def on_valid(self, func: Callable[[dict[str, Any]], None]) -> None:
|
|
136
|
+
"""Register callback for valid validation."""
|
|
137
|
+
self._on_valid_command = func
|
|
138
|
+
|
|
139
|
+
def off_valid(self, bind_id: str | None = None):
|
|
140
|
+
"""Remove the callback for the <<Valid>> event"""
|
|
141
|
+
self.unbind('<<Valid>>', bind_id)
|
|
142
|
+
|
|
143
|
+
def on_validated(self, func: Callable[[dict[str, Any]], None]) -> None:
|
|
144
|
+
"""Register callback for any validation (valid or invalid)."""
|
|
145
|
+
self._on_validated_command = func
|
|
146
|
+
|
|
147
|
+
def off_validated(self, bind_id: str | None = None):
|
|
148
|
+
"""Remove the callback for validated event"""
|
|
149
|
+
self.unbind('<<Validate>>', bind_id)
|
|
150
|
+
|
|
151
|
+
# ---------------- Internals ----------------
|
|
152
|
+
|
|
153
|
+
def _get_validation_value(self) -> Any:
|
|
154
|
+
"""Get the current value for validation purposes.
|
|
155
|
+
|
|
156
|
+
For entry widgets, this should return the current text being typed,
|
|
157
|
+
not the committed value. Prefers get() method over value() method.
|
|
158
|
+
"""
|
|
159
|
+
if hasattr(self, 'get'):
|
|
160
|
+
try:
|
|
161
|
+
return self.get()
|
|
162
|
+
except (AttributeError, TclError):
|
|
163
|
+
pass
|
|
164
|
+
return self.value()
|
|
165
|
+
|
|
166
|
+
def _setup_validation_binds(self, keyup_delay_ms: int = 50, blur_delay_ms: int = 50) -> None:
|
|
167
|
+
"""Set up automatic validation bindings with debouncing."""
|
|
168
|
+
# Auto-validate (debounced)
|
|
169
|
+
self.bind(self.SEQ_KEYUP, lambda e: self._debounced("key", keyup_delay_ms), add=True)
|
|
170
|
+
self.bind(self.SEQ_BLUR, lambda e: self._debounced("blur", blur_delay_ms), add=True)
|
|
171
|
+
|
|
172
|
+
# Wire optional convenience callbacks
|
|
173
|
+
self.bind(self.EVENT_VALIDATED, self._dispatch_validated, add=True)
|
|
174
|
+
self.bind(self.EVENT_VALID, self._dispatch_valid, add=True)
|
|
175
|
+
self.bind(self.EVENT_INVALID, self._dispatch_invalid, add=True)
|
|
176
|
+
|
|
177
|
+
def _debounced(self, trigger: RuleTriggerType, ms: int) -> None:
|
|
178
|
+
"""Debounce validation to avoid excessive checks during typing."""
|
|
179
|
+
key = f"debounce:{trigger}"
|
|
180
|
+
aid: Optional[str] = self._debounce_ids.get(key)
|
|
181
|
+
if aid:
|
|
182
|
+
try:
|
|
183
|
+
self.after_cancel(aid)
|
|
184
|
+
except TclError:
|
|
185
|
+
pass
|
|
186
|
+
# defer reading value until the timer fires
|
|
187
|
+
self._debounce_ids[key] = self.after(ms, lambda: self.validate(self._get_validation_value(), trigger))
|
|
188
|
+
|
|
189
|
+
# ----- optional dispatchers for on_* convenience -----
|
|
190
|
+
|
|
191
|
+
def _dispatch_validated(self, event) -> None:
|
|
192
|
+
"""Dispatch validated event to registered callback."""
|
|
193
|
+
if self._on_validated_command:
|
|
194
|
+
self._on_validated_command(event.data)
|
|
195
|
+
|
|
196
|
+
def _dispatch_valid(self, event) -> None:
|
|
197
|
+
"""Dispatch valid event to registered callback."""
|
|
198
|
+
if self._on_valid_command:
|
|
199
|
+
self._on_valid_command(event.data)
|
|
200
|
+
|
|
201
|
+
def _dispatch_invalid(self, event) -> None:
|
|
202
|
+
"""Dispatch invalid event to registered callback."""
|
|
203
|
+
if self._on_invalid_command:
|
|
204
|
+
self._on_invalid_command(event.data)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Widget parts module for composite widgets."""
|
|
2
|
+
|
|
3
|
+
from bootstack.widgets.parts.textentry_part import TextEntryPart
|
|
4
|
+
from bootstack.widgets.parts.numberentry_part import NumberEntryPart
|
|
5
|
+
from bootstack.widgets.parts.spinnerentry_part import SpinnerEntryPart
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'TextEntryPart',
|
|
9
|
+
'NumberEntryPart',
|
|
10
|
+
'SpinnerEntryPart',
|
|
11
|
+
]
|