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,317 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
import weakref
|
|
3
|
+
from itertools import count
|
|
4
|
+
from typing import Any, Callable, Generic, Type, TypeVar
|
|
5
|
+
|
|
6
|
+
from bootstack.core.signals.types import TraceOperation
|
|
7
|
+
from bootstack.core.variables import SetVar
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
U = TypeVar("U")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _SignalTrace:
|
|
14
|
+
"""
|
|
15
|
+
Internal helper to manage Tcl variable traces using tkinter's Variable API.
|
|
16
|
+
This class encapsulates low-level `trace_add` and `trace_remove` logic.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, tk_var: tk.Variable):
|
|
20
|
+
"""
|
|
21
|
+
Initialize a trace manager for a tkinter variable.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
tk_var: A tkinter.Variable instance (e.g., StringVar, IntVar).
|
|
25
|
+
"""
|
|
26
|
+
self._var = tk_var
|
|
27
|
+
# Map trace id -> (operation, callback)
|
|
28
|
+
self._traces: dict[str, tuple[TraceOperation, Callable[..., Any]]] = {}
|
|
29
|
+
|
|
30
|
+
def callbacks(self) -> tuple[str, ...]:
|
|
31
|
+
"""
|
|
32
|
+
Return all currently active trace IDs.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
A tuple of trace ID strings.
|
|
36
|
+
"""
|
|
37
|
+
return tuple(self._traces.keys())
|
|
38
|
+
|
|
39
|
+
def add(
|
|
40
|
+
self,
|
|
41
|
+
operation: TraceOperation,
|
|
42
|
+
callback: Callable[[T], Any],
|
|
43
|
+
get_value: Callable[[], T],
|
|
44
|
+
) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Add a new trace that calls a callback when the variable is written.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def traced_callback(name: str, index: str, mode: str) -> None:
|
|
50
|
+
callback(get_value())
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
fid = self._var.trace_add(operation, traced_callback)
|
|
54
|
+
except tk.TclError as e:
|
|
55
|
+
raise RuntimeError(f"failed to add trace: {e}") from e
|
|
56
|
+
self._traces[fid] = (operation, traced_callback)
|
|
57
|
+
return fid
|
|
58
|
+
|
|
59
|
+
def remove(self, fid: str) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Remove a trace by ID. Safe if already removed or variable destroyed.
|
|
62
|
+
"""
|
|
63
|
+
op_cb = self._traces.pop(fid, None)
|
|
64
|
+
if op_cb is None:
|
|
65
|
+
return
|
|
66
|
+
operation, _ = op_cb
|
|
67
|
+
try:
|
|
68
|
+
self._var.trace_remove(operation, fid)
|
|
69
|
+
except tk.TclError:
|
|
70
|
+
# Variable may be unset/destroyed; ignore
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Signal(Generic[T]):
|
|
75
|
+
"""
|
|
76
|
+
A reactive signal backed by a tkinter Variable.
|
|
77
|
+
|
|
78
|
+
Supports value access, transformation via `.map()`, and subscription
|
|
79
|
+
to change events via `.subscribe()`.
|
|
80
|
+
|
|
81
|
+
Can be passed to Tkinter widgets using `str(signal)` or `signal.name`.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
_cnt = count(1)
|
|
85
|
+
|
|
86
|
+
def __init__(self, value: T, name: str | None = None, master: tk.Misc | None = None):
|
|
87
|
+
self._name = name or f"SIG{next(self._cnt)}"
|
|
88
|
+
self._type: Type[T] = type(value)
|
|
89
|
+
self._master: tk.Misc | None = master
|
|
90
|
+
self._var = self._create_variable(value)
|
|
91
|
+
self._trace = _SignalTrace(self._var)
|
|
92
|
+
# Map fid -> callback to allow multiple subscriptions of same function
|
|
93
|
+
self._subscribers: dict[str, Callable[[T], Any]] = {}
|
|
94
|
+
# Reverse index: callback -> set of fids
|
|
95
|
+
self._callback_index: dict[Callable[[T], Any], set[str]] = {}
|
|
96
|
+
# Cache last known value for robustness when Tcl variable is torn down
|
|
97
|
+
self._last: T = value
|
|
98
|
+
|
|
99
|
+
def _create_variable(self, value: T) -> tk.Variable:
|
|
100
|
+
if isinstance(value, bool):
|
|
101
|
+
return tk.BooleanVar(master=self._master, name=self._name, value=value)
|
|
102
|
+
elif isinstance(value, int):
|
|
103
|
+
return tk.IntVar(master=self._master, name=self._name, value=value)
|
|
104
|
+
elif isinstance(value, float):
|
|
105
|
+
return tk.DoubleVar(master=self._master, name=self._name, value=value)
|
|
106
|
+
elif isinstance(value, set):
|
|
107
|
+
return SetVar(master=self._master, name=self._name, value=value)
|
|
108
|
+
else:
|
|
109
|
+
return tk.StringVar(master=self._master, name=self._name, value=value)
|
|
110
|
+
|
|
111
|
+
def __call__(self) -> T:
|
|
112
|
+
"""
|
|
113
|
+
Get the current value of the signal.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
The current typed value.
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
value = self._var.get()
|
|
120
|
+
self._last = value # cache last good value
|
|
121
|
+
return value
|
|
122
|
+
except tk.TclError:
|
|
123
|
+
# Return last known value when underlying var is destroyed/unset
|
|
124
|
+
return self._last
|
|
125
|
+
|
|
126
|
+
def get(self) -> T:
|
|
127
|
+
"""Return the current value of the signal.
|
|
128
|
+
|
|
129
|
+
Alias for calling the instance directly (`signal()`). Mirrors
|
|
130
|
+
tkinter's `Variable.get` naming for consistency.
|
|
131
|
+
"""
|
|
132
|
+
return self()
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def from_variable(
|
|
136
|
+
cls,
|
|
137
|
+
tk_var: tk.Variable,
|
|
138
|
+
*,
|
|
139
|
+
name: str | None = None,
|
|
140
|
+
coerce: Type[T] | None = None,
|
|
141
|
+
) -> "Signal[T]":
|
|
142
|
+
"""
|
|
143
|
+
Wrap an existing tkinter Variable as a Signal.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
tk_var: An existing tkinter.Variable instance (StringVar, IntVar, etc.).
|
|
147
|
+
name: Optional override of the Tcl variable name. Defaults to tk_var's name.
|
|
148
|
+
coerce: Optional Python type to treat the signal as (e.g., int/float/bool/str).
|
|
149
|
+
If omitted, the type is inferred from the tk_var subclass.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
A Signal bound to the provided tk_var.
|
|
153
|
+
"""
|
|
154
|
+
# Infer Python type from the tk variable if not explicitly provided
|
|
155
|
+
if coerce is None:
|
|
156
|
+
if isinstance(tk_var, tk.BooleanVar):
|
|
157
|
+
py_type: Type[Any] = bool
|
|
158
|
+
elif isinstance(tk_var, tk.IntVar):
|
|
159
|
+
py_type = int
|
|
160
|
+
elif isinstance(tk_var, tk.DoubleVar):
|
|
161
|
+
py_type = float
|
|
162
|
+
elif isinstance(tk_var, SetVar):
|
|
163
|
+
py_type = set
|
|
164
|
+
else:
|
|
165
|
+
py_type = str
|
|
166
|
+
else:
|
|
167
|
+
py_type = coerce
|
|
168
|
+
|
|
169
|
+
# Construct without creating a new tk.Variable
|
|
170
|
+
self = cls.__new__(cls) # bypass __init__
|
|
171
|
+
self._name = name or getattr(tk_var, "_name", str(tk_var))
|
|
172
|
+
self._type = py_type # type: ignore[assignment]
|
|
173
|
+
self._var = tk_var
|
|
174
|
+
self._trace = _SignalTrace(self._var)
|
|
175
|
+
self._subscribers = {}
|
|
176
|
+
self._callback_index = {}
|
|
177
|
+
# Best-effort capture of master/interpreter for reference
|
|
178
|
+
self._master = getattr(tk_var, "_master", None)
|
|
179
|
+
try:
|
|
180
|
+
current = tk_var.get() # type: ignore[assignment]
|
|
181
|
+
except tk.TclError:
|
|
182
|
+
# Fallback default per inferred type
|
|
183
|
+
if py_type is float: # type: ignore[comparison-overlap]
|
|
184
|
+
current = 0.0
|
|
185
|
+
elif py_type is int: # type: ignore[comparison-overlap]
|
|
186
|
+
current = 0
|
|
187
|
+
elif py_type is bool: # type: ignore[comparison-overlap]
|
|
188
|
+
current = False
|
|
189
|
+
elif py_type is set:
|
|
190
|
+
current = set()
|
|
191
|
+
else:
|
|
192
|
+
current = ""
|
|
193
|
+
self._last = current # type: ignore[assignment]
|
|
194
|
+
return self
|
|
195
|
+
|
|
196
|
+
def set(self, value: T) -> None:
|
|
197
|
+
"""
|
|
198
|
+
Set the signal to a new value and notify subscribers.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
value: The new value. Must match the original type.
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
TypeError: If the value type does not match the original.
|
|
205
|
+
"""
|
|
206
|
+
# Enforce exact type to avoid bool being accepted for int, etc.
|
|
207
|
+
if type(value) is not self._type:
|
|
208
|
+
raise TypeError(f"Expected {self._type.__name__}, got {type(value).__name__}")
|
|
209
|
+
# Reduce redundant updates if value unchanged
|
|
210
|
+
try:
|
|
211
|
+
current = self._var.get()
|
|
212
|
+
if current == value:
|
|
213
|
+
return
|
|
214
|
+
except tk.TclError:
|
|
215
|
+
# If var is gone, proceed to set and let Tcl recreate path if possible
|
|
216
|
+
pass
|
|
217
|
+
self._var.set(value)
|
|
218
|
+
self._last = value
|
|
219
|
+
|
|
220
|
+
def map(self, transform: Callable[[T], U]) -> 'Signal[U]':
|
|
221
|
+
"""
|
|
222
|
+
Create a derived signal that transforms this signal's value.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
transform: A function applied to the current and future values.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
A new Signal[U] that stays updated with the transformed value.
|
|
229
|
+
"""
|
|
230
|
+
derived = Signal(transform(self()))
|
|
231
|
+
|
|
232
|
+
# Use weakref to avoid keeping derived alive solely via subscription
|
|
233
|
+
weak_derived = weakref.ref(derived)
|
|
234
|
+
|
|
235
|
+
def update(value: T) -> None:
|
|
236
|
+
d = weak_derived()
|
|
237
|
+
if d is None:
|
|
238
|
+
# Auto-detach if derived is GC'd
|
|
239
|
+
return
|
|
240
|
+
d.set(transform(value))
|
|
241
|
+
|
|
242
|
+
self.subscribe(update)
|
|
243
|
+
return derived
|
|
244
|
+
|
|
245
|
+
def subscribe(self, callback: Callable[[T], Any], *, immediate: bool = False) -> str:
|
|
246
|
+
"""
|
|
247
|
+
Subscribe to value changes of this signal.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
callback: A function that receives the current value (T) when updated.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
A trace ID that can be used for removal.
|
|
254
|
+
"""
|
|
255
|
+
fid = self._trace.add("write", callback, self)
|
|
256
|
+
self._subscribers[fid] = callback
|
|
257
|
+
self._callback_index.setdefault(callback, set()).add(fid)
|
|
258
|
+
if immediate:
|
|
259
|
+
try:
|
|
260
|
+
callback(self())
|
|
261
|
+
except Exception:
|
|
262
|
+
# Do not fail subscription due to callback error
|
|
263
|
+
pass
|
|
264
|
+
return fid
|
|
265
|
+
|
|
266
|
+
def unsubscribe(self, funcid: str) -> None:
|
|
267
|
+
"""
|
|
268
|
+
Remove a previously registered subscriber.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
funcid: The function id returned from `subscribe()`.
|
|
272
|
+
"""
|
|
273
|
+
self._subscribers.pop(funcid, None)
|
|
274
|
+
self._trace.remove(funcid)
|
|
275
|
+
|
|
276
|
+
def unsubscribe_all(self) -> None:
|
|
277
|
+
"""
|
|
278
|
+
Remove all currently subscribed callbacks.
|
|
279
|
+
"""
|
|
280
|
+
# Copy keys to avoid mutation during iteration
|
|
281
|
+
for fid in list(self._subscribers.keys()):
|
|
282
|
+
self._trace.remove(fid)
|
|
283
|
+
self._subscribers.clear()
|
|
284
|
+
self._callback_index.clear()
|
|
285
|
+
|
|
286
|
+
def __getattr__(self, name: str) -> Any:
|
|
287
|
+
"""
|
|
288
|
+
Proxy access to the underlying tk.Variable instance.
|
|
289
|
+
"""
|
|
290
|
+
return getattr(self._var, name)
|
|
291
|
+
|
|
292
|
+
@property
|
|
293
|
+
def name(self) -> str:
|
|
294
|
+
"""
|
|
295
|
+
Return the Tcl name of the variable (for use in widget `textvariable`).
|
|
296
|
+
"""
|
|
297
|
+
return self._name
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def type(self) -> Type[T]:
|
|
301
|
+
"""
|
|
302
|
+
The original type of the signal value.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
A Python type (e.g., int, str).
|
|
306
|
+
"""
|
|
307
|
+
return self._type
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def var(self) -> tk.Variable:
|
|
311
|
+
return self._var
|
|
312
|
+
|
|
313
|
+
def __str__(self) -> str:
|
|
314
|
+
return self._name
|
|
315
|
+
|
|
316
|
+
def __repr__(self) -> str:
|
|
317
|
+
return self._name
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import Callable, Literal, Optional, TypedDict
|
|
2
|
+
|
|
3
|
+
RuleType = Literal["required", "email", "pattern", "stringLength", "custom", "compare"]
|
|
4
|
+
RuleTriggerType = Literal['key', 'blur', 'always', 'manual']
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ValidationOptions(TypedDict, total=False):
|
|
8
|
+
pattern: str
|
|
9
|
+
message: str
|
|
10
|
+
min: int
|
|
11
|
+
max: int
|
|
12
|
+
trigger: Optional[Literal["key", "blur", "always", "manual"]]
|
|
13
|
+
func: Callable[[str], bool]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class ValidationResult:
|
|
2
|
+
"""The outcome of a single validation check.
|
|
3
|
+
|
|
4
|
+
Attributes:
|
|
5
|
+
is_valid (bool): True if validation passed, False otherwise.
|
|
6
|
+
message (str): Error message when `is_valid` is False; empty string otherwise.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, is_valid: bool, message: str = ""):
|
|
10
|
+
"""Create a validation result.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
is_valid: True if validation passed.
|
|
14
|
+
message: Error message. Defaults to an empty string.
|
|
15
|
+
"""
|
|
16
|
+
self.is_valid = is_valid
|
|
17
|
+
self.message = message
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
from bootstack.core.validation.types import RuleTriggerType, RuleType
|
|
5
|
+
from bootstack.core.validation.validation_result import ValidationResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ValidationRule:
|
|
9
|
+
"""A single validation rule that can be applied to a string value.
|
|
10
|
+
|
|
11
|
+
Supports the built-in rule types `'required'`, `'email'`,
|
|
12
|
+
`'stringLength'`, `'pattern'`, and `'custom'`, and carries a trigger
|
|
13
|
+
policy that controls when the rule is evaluated.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
type (RuleType): The validation rule type.
|
|
17
|
+
message (str): Custom error message; if empty a default is generated.
|
|
18
|
+
trigger (RuleTriggerType): When the rule fires — `'always'`, `'blur'`, or `'manual'`.
|
|
19
|
+
params (dict): Additional parameters specific to the rule type
|
|
20
|
+
(e.g., `min`/`max` for `'stringLength'`, `pattern` for `'pattern'`,
|
|
21
|
+
`func` for `'custom'`).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
rule_type: RuleType,
|
|
27
|
+
message: str = "",
|
|
28
|
+
**kwargs
|
|
29
|
+
):
|
|
30
|
+
"""Create a validation rule.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
rule_type: The type of validation to apply.
|
|
34
|
+
message: Custom error message. If empty, a sensible default is used.
|
|
35
|
+
**kwargs: Rule-specific parameters. Pass `trigger` to override the
|
|
36
|
+
default trigger policy; all other keys are stored in `params`
|
|
37
|
+
(e.g., `min=3, max=20` for `'stringLength'`, `pattern=r'\\d+'`
|
|
38
|
+
for `'pattern'`, `func=callable` for `'custom'`).
|
|
39
|
+
"""
|
|
40
|
+
self.type = rule_type
|
|
41
|
+
self.message = message
|
|
42
|
+
self.trigger = kwargs.pop('trigger', self._default_trigger())
|
|
43
|
+
self.params = kwargs
|
|
44
|
+
|
|
45
|
+
def validate(self, value: str) -> ValidationResult:
|
|
46
|
+
"""Apply this rule to a value and return the result.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
value: The string value to validate.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
A ValidationResult with `is_valid=True` on success or `is_valid=False`
|
|
53
|
+
with an error message on failure.
|
|
54
|
+
"""
|
|
55
|
+
msg = self.message or self._default_message()
|
|
56
|
+
|
|
57
|
+
if self.type == "required":
|
|
58
|
+
if value is None:
|
|
59
|
+
return ValidationResult(False, msg)
|
|
60
|
+
if isinstance(value, str) and not value.strip():
|
|
61
|
+
return ValidationResult(False, msg)
|
|
62
|
+
# Everything else is valid (non-empty string, number, date, etc.)
|
|
63
|
+
return ValidationResult(True, "")
|
|
64
|
+
|
|
65
|
+
elif self.type == "email":
|
|
66
|
+
if not re.match(r"[^@]+@[^@]+\.[^@]+", value):
|
|
67
|
+
return ValidationResult(False, msg)
|
|
68
|
+
elif self.type == "stringLength":
|
|
69
|
+
min_len = self.params.get("min", 0)
|
|
70
|
+
max_len = self.params.get("max", float("inf"))
|
|
71
|
+
if not (min_len <= len(value) <= max_len):
|
|
72
|
+
return ValidationResult(False, msg)
|
|
73
|
+
elif self.type == "pattern":
|
|
74
|
+
pattern = self.params.get("pattern", "")
|
|
75
|
+
if not re.match(pattern, value):
|
|
76
|
+
return ValidationResult(False, msg)
|
|
77
|
+
elif self.type == "custom":
|
|
78
|
+
func: Callable[[str], bool] = self.params.get("func")
|
|
79
|
+
if func and not func(value):
|
|
80
|
+
return ValidationResult(False, msg)
|
|
81
|
+
|
|
82
|
+
return ValidationResult(True)
|
|
83
|
+
|
|
84
|
+
def _default_message(self) -> str:
|
|
85
|
+
"""Return a sensible default error message for this rule type."""
|
|
86
|
+
if self.type == "required":
|
|
87
|
+
return "This field is required."
|
|
88
|
+
elif self.type == "email":
|
|
89
|
+
return "Enter a valid email address."
|
|
90
|
+
elif self.type == "stringLength":
|
|
91
|
+
min_len = self.params.get("min", 0)
|
|
92
|
+
max_len = self.params.get("max", None)
|
|
93
|
+
if max_len is None or max_len == float("inf"):
|
|
94
|
+
return f"Enter at least {min_len} characters."
|
|
95
|
+
return f"Enter between {min_len} and {max_len} characters."
|
|
96
|
+
elif self.type == "pattern":
|
|
97
|
+
return "Value does not match the required pattern."
|
|
98
|
+
elif self.type == "custom":
|
|
99
|
+
return "Invalid value."
|
|
100
|
+
return "Invalid input."
|
|
101
|
+
|
|
102
|
+
def _default_trigger(self) -> RuleTriggerType:
|
|
103
|
+
"""Return the default trigger policy for this rule type."""
|
|
104
|
+
if self.type == "required":
|
|
105
|
+
return "always"
|
|
106
|
+
elif self.type in {"stringLength"}:
|
|
107
|
+
return "blur"
|
|
108
|
+
elif self.type in {"email", "pattern"}:
|
|
109
|
+
return "always"
|
|
110
|
+
elif self.type in {"custom"}:
|
|
111
|
+
return "manual"
|
|
112
|
+
return "blur"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import tkinter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SetVar(tkinter.Variable):
|
|
8
|
+
"""
|
|
9
|
+
A tkinter variable that holds a set of values.
|
|
10
|
+
|
|
11
|
+
This variable serializes a Python set into its string representation
|
|
12
|
+
for storage and deserializes it back into a set on retrieval.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, master=None, value: set | None = None, name: str | None = None):
|
|
16
|
+
"""
|
|
17
|
+
Initialize the SetVar.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
master (Widget, optional): The parent widget. Defaults to None.
|
|
21
|
+
value (set, optional): The initial value. Defaults to an empty set.
|
|
22
|
+
name (str, optional): The name of the variable. Defaults to None.
|
|
23
|
+
"""
|
|
24
|
+
if value is None:
|
|
25
|
+
value = set()
|
|
26
|
+
super().__init__(master, value, name)
|
|
27
|
+
|
|
28
|
+
def set(self, value: set | frozenset):
|
|
29
|
+
"""
|
|
30
|
+
Set the variable to a new value.
|
|
31
|
+
|
|
32
|
+
The value is converted to its string representation for storage.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
value (set | frozenset): The new value. Should be a set or frozenset.
|
|
36
|
+
"""
|
|
37
|
+
if not isinstance(value, (set, frozenset)):
|
|
38
|
+
raise TypeError(f"Expected set or frozenset, got {type(value).__name__}")
|
|
39
|
+
super().set(repr(value))
|
|
40
|
+
|
|
41
|
+
def get(self) -> set:
|
|
42
|
+
"""
|
|
43
|
+
Return the value of the variable as a Python set.
|
|
44
|
+
|
|
45
|
+
The string representation is deserialized back into a set.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
set: The current value of the variable.
|
|
49
|
+
"""
|
|
50
|
+
value_str = super().get()
|
|
51
|
+
if not value_str:
|
|
52
|
+
return set()
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
# Use literal_eval for safe evaluation of the string representation
|
|
56
|
+
deserialized_value = ast.literal_eval(value_str)
|
|
57
|
+
if isinstance(deserialized_value, (set, frozenset)):
|
|
58
|
+
return set(deserialized_value)
|
|
59
|
+
else:
|
|
60
|
+
return set()
|
|
61
|
+
except (ValueError, SyntaxError):
|
|
62
|
+
return set()
|