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,183 @@
|
|
|
1
|
+
"""Numeric entry field widget with spin buttons.
|
|
2
|
+
|
|
3
|
+
Provides a specialized entry field for numeric input with increment/decrement
|
|
4
|
+
buttons and keyboard/mouse wheel support.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
from tkinter import TclError
|
|
9
|
+
from typing_extensions import Unpack
|
|
10
|
+
|
|
11
|
+
from bootstack.widgets.primitives.button import Button
|
|
12
|
+
from bootstack.widgets.composites.field import Field, FieldOptions
|
|
13
|
+
from bootstack.widgets.mixins import configure_delegate
|
|
14
|
+
from bootstack.widgets.types import Master
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class NumericEntry(Field):
|
|
18
|
+
"""A numeric entry field widget with increment/decrement spin buttons.
|
|
19
|
+
|
|
20
|
+
Extends Field to provide numeric input with spin buttons, bounds validation,
|
|
21
|
+
keyboard stepping (Up/Down arrows), mouse wheel support, and optional wrapping.
|
|
22
|
+
|
|
23
|
+
!!! note "Events"
|
|
24
|
+
|
|
25
|
+
- `<<Increment>>`: Fired when increment is requested (before step occurs).
|
|
26
|
+
- `<<Decrement>>`: Fired when decrement is requested (before step occurs).
|
|
27
|
+
- `<<Change>>`: Fired when value changes after commit.
|
|
28
|
+
- `<<Input>>`: Fired on each keystroke.
|
|
29
|
+
- `<<Valid>>`: Fired when validation passes.
|
|
30
|
+
- `<<Invalid>>`: Fired when validation fails.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
entry_widget (NumberEntryPart): The underlying entry widget.
|
|
34
|
+
label_widget (Label): The label widget.
|
|
35
|
+
message_widget (Label): The message label widget.
|
|
36
|
+
addons (dict[str, Widget]): Dictionary of inserted addon widgets by name.
|
|
37
|
+
variable (Variable): Tkinter Variable linked to entry text.
|
|
38
|
+
signal (Signal): Signal object for reactive updates.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
master: Master = None,
|
|
44
|
+
value: int | float = 0,
|
|
45
|
+
label: str = None,
|
|
46
|
+
message: str = None,
|
|
47
|
+
show_spin_buttons: bool = True,
|
|
48
|
+
minvalue: int | float | None = None,
|
|
49
|
+
maxvalue: int | float | None = None,
|
|
50
|
+
increment: int | float = 1,
|
|
51
|
+
**kwargs: Unpack[FieldOptions]
|
|
52
|
+
):
|
|
53
|
+
"""Initialize a NumericEntry widget.
|
|
54
|
+
|
|
55
|
+
Creates a numeric entry field with optional label, validation, bounds
|
|
56
|
+
constraints, and increment/decrement spin buttons. The widget supports
|
|
57
|
+
keyboard stepping (Up/Down arrows), mouse wheel interaction, and optional
|
|
58
|
+
value wrapping at boundaries.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
master: Parent widget. If None, uses the default root window.
|
|
62
|
+
value (int | float): Initial numeric value to display.
|
|
63
|
+
label (str): Optional label text to display above the entry field.
|
|
64
|
+
If `required=True`, an asterisk (*) is automatically appended.
|
|
65
|
+
message (str): Optional message text to display below the entry field.
|
|
66
|
+
Used for hints or help text. Replaced by validation errors when
|
|
67
|
+
validation fails.
|
|
68
|
+
show_spin_buttons (bool): If True, displays increment/decrement buttons
|
|
69
|
+
(plus and minus icons) to the right of the entry.
|
|
70
|
+
minvalue (int | float): Minimum allowed value (inclusive). Values below
|
|
71
|
+
this will be clamped or wrapped depending on the wrap setting.
|
|
72
|
+
maxvalue (int | float): Maximum allowed value (inclusive). Values above
|
|
73
|
+
this will be clamped or wrapped depending on the wrap setting.
|
|
74
|
+
increment (int | float): Step size for increment/decrement operations.
|
|
75
|
+
|
|
76
|
+
Other Parameters:
|
|
77
|
+
wrap (bool): If True, values wrap around at min/max boundaries.
|
|
78
|
+
value_format (str): Number format specification for IntlFormatter.
|
|
79
|
+
Examples: `'decimal'`, `'percent'`, `'currency'`, `'#,##0.00'`.
|
|
80
|
+
locale (str): Locale identifier for number formatting (e.g., `'en_US'`).
|
|
81
|
+
required (bool): If True, field cannot be empty.
|
|
82
|
+
accent (str): Accent token for the focus ring and active border.
|
|
83
|
+
bootstyle (str): DEPRECATED - Use `accent` instead.
|
|
84
|
+
allow_blank (bool): If True, empty input is allowed (sets value to None).
|
|
85
|
+
cursor (str): Cursor style when hovering.
|
|
86
|
+
exportselection (bool): Export selection to clipboard.
|
|
87
|
+
font (str): Font for text display.
|
|
88
|
+
foreground (str): Text color.
|
|
89
|
+
initial_focus (bool): If True, widget receives focus on creation.
|
|
90
|
+
justify (str): Text alignment (`'left'`, `'center'`, `'right'`).
|
|
91
|
+
show_message (bool): If True, displays message area.
|
|
92
|
+
padding (int | tuple): Padding around entry widget.
|
|
93
|
+
takefocus (bool): If True, widget accepts Tab focus.
|
|
94
|
+
textvariable (Variable): Tkinter Variable to link with text.
|
|
95
|
+
textsignal (Signal): Signal object for reactive updates.
|
|
96
|
+
width (int): Width in characters.
|
|
97
|
+
xscrollcommand (Callable): Callback for horizontal scrolling.
|
|
98
|
+
"""
|
|
99
|
+
super().__init__(
|
|
100
|
+
master, value=value, label=label, message=message, minvalue=minvalue, maxvalue=maxvalue, kind="numeric",
|
|
101
|
+
increment=increment, **kwargs)
|
|
102
|
+
|
|
103
|
+
self._show_spin_buttons = show_spin_buttons
|
|
104
|
+
|
|
105
|
+
# passthrough methods
|
|
106
|
+
self.on_increment = self.entry_widget.on_increment
|
|
107
|
+
self.off_increment = self.entry_widget.off_increment
|
|
108
|
+
self.on_decrement = self.entry_widget.on_decrement
|
|
109
|
+
self.off_decrement = self.entry_widget.off_decrement
|
|
110
|
+
self.step = self.entry_widget.step
|
|
111
|
+
|
|
112
|
+
# pack info
|
|
113
|
+
self._increment_pack_info = {}
|
|
114
|
+
self._decrement_pack_info = {}
|
|
115
|
+
|
|
116
|
+
# buttons
|
|
117
|
+
self.insert_addon(
|
|
118
|
+
Button, position="after", name="decrement", icon="dash", command=self.decrement, icon_only=True)
|
|
119
|
+
self.insert_addon(
|
|
120
|
+
Button, position="after", name="increment", icon="plus", command=self.increment, icon_only=True)
|
|
121
|
+
self._delegate_show_spin_buttons(show_spin_buttons)
|
|
122
|
+
|
|
123
|
+
self.entry_widget.bind('<<StateChanged>>', self._on_entry_state_changed, add=True)
|
|
124
|
+
self._update_spin_button_states()
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def increment_widget(self):
|
|
128
|
+
"""Get the increment spin button widget."""
|
|
129
|
+
return self.addons['increment']
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def decrement_widget(self):
|
|
133
|
+
"""Get the decrement spin button widget."""
|
|
134
|
+
return self.addons['decrement']
|
|
135
|
+
|
|
136
|
+
def increment(self):
|
|
137
|
+
"""Increment the numeric value by one step."""
|
|
138
|
+
if not self._entry_is_interactive():
|
|
139
|
+
return
|
|
140
|
+
self.entry_widget.event_generate("<<Increment>>")
|
|
141
|
+
|
|
142
|
+
def decrement(self):
|
|
143
|
+
"""Decrement the numeric value by one step."""
|
|
144
|
+
if not self._entry_is_interactive():
|
|
145
|
+
return
|
|
146
|
+
self.entry_widget.event_generate("<<Decrement>>")
|
|
147
|
+
|
|
148
|
+
def _entry_is_interactive(self) -> bool:
|
|
149
|
+
"""Return True if the entry widget is not disabled or readonly."""
|
|
150
|
+
state = self.entry_widget.state()
|
|
151
|
+
return 'disabled' not in state and 'readonly' not in state
|
|
152
|
+
|
|
153
|
+
def _on_entry_state_changed(self, _: Any = None):
|
|
154
|
+
"""Sync spin buttons whenever the entry state mutates."""
|
|
155
|
+
self._update_spin_button_states()
|
|
156
|
+
|
|
157
|
+
def _update_spin_button_states(self):
|
|
158
|
+
"""Enable or disable spin buttons based on entry interactivity."""
|
|
159
|
+
state_value = 'disabled' if not self._entry_is_interactive() else '!disabled'
|
|
160
|
+
for control in (self.increment_widget, self.decrement_widget):
|
|
161
|
+
try:
|
|
162
|
+
control.configure(state=state_value)
|
|
163
|
+
except TclError:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
@configure_delegate('show_spin_buttons')
|
|
167
|
+
def _delegate_show_spin_buttons(self, value: bool = None):
|
|
168
|
+
"""Get or set the visibility of spin buttons."""
|
|
169
|
+
if value is None:
|
|
170
|
+
return self._show_spin_buttons
|
|
171
|
+
else:
|
|
172
|
+
self._show_spin_buttons = value
|
|
173
|
+
if value:
|
|
174
|
+
if not self.increment_widget.winfo_ismapped():
|
|
175
|
+
self.increment_widget.pack(**self._increment_pack_info)
|
|
176
|
+
if not self.decrement_widget.winfo_ismapped():
|
|
177
|
+
self.decrement_widget.pack(**self._decrement_pack_info)
|
|
178
|
+
else:
|
|
179
|
+
self._increment_pack_info = self.increment_widget.pack_info()
|
|
180
|
+
self._decrement_pack_info = self.decrement_widget.pack_info()
|
|
181
|
+
self.increment_widget.pack_forget()
|
|
182
|
+
self.decrement_widget.pack_forget()
|
|
183
|
+
return None
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import tkinter
|
|
2
|
+
from collections.abc import ValuesView
|
|
3
|
+
from typing import Any, Callable
|
|
4
|
+
|
|
5
|
+
from typing_extensions import TypedDict, Unpack
|
|
6
|
+
|
|
7
|
+
from bootstack.widgets.primitives import Frame
|
|
8
|
+
from bootstack.widgets.types import Master
|
|
9
|
+
from bootstack.core import NavigationError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PageOptions(TypedDict, total=False):
|
|
13
|
+
padding: Any
|
|
14
|
+
width: int
|
|
15
|
+
height: int
|
|
16
|
+
style: str
|
|
17
|
+
cursor: str
|
|
18
|
+
show_border: bool
|
|
19
|
+
accent: str
|
|
20
|
+
variant: str
|
|
21
|
+
surface: str
|
|
22
|
+
style_options: dict[str, Any]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PageStackKwargs(TypedDict, total=False):
|
|
26
|
+
takefocus: bool
|
|
27
|
+
width: int
|
|
28
|
+
height: int
|
|
29
|
+
padding: Any
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PageStack(Frame):
|
|
33
|
+
"""A navigation container widget for managing multiple pages with history support.
|
|
34
|
+
|
|
35
|
+
PageStack provides a stack-based navigation system where only one page is
|
|
36
|
+
visible at a time. It maintains a navigation history, allowing users to move
|
|
37
|
+
backward and forward through pages similar to a web browser.
|
|
38
|
+
|
|
39
|
+
!!! note "Events"
|
|
40
|
+
|
|
41
|
+
- `<<PageUnmount>>`: Triggered when the current page is hidden.
|
|
42
|
+
- `<<PageWillMount>>`: Triggered before a new page is displayed.
|
|
43
|
+
- `<<PageMount>>`: Triggered after a new page is displayed.
|
|
44
|
+
- `<<PageChange>>`: Triggered after page navigation completes.
|
|
45
|
+
|
|
46
|
+
All events provide `event.data` with keys: `page`, `prev_page`, `prev_data`,
|
|
47
|
+
`nav` ('push', 'back', 'forward'), `index`, `length`, `can_back`,
|
|
48
|
+
`can_forward`.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, master: Master = None, **kwargs: Unpack[PageStackKwargs]):
|
|
52
|
+
"""Initialize a new PageStack instance.
|
|
53
|
+
|
|
54
|
+
Creates an empty PageStack with no pages and no navigation history.
|
|
55
|
+
The widget inherits from Frame and supports all standard Frame
|
|
56
|
+
configuration options.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
master: Parent widget. If None, uses the default root window.
|
|
60
|
+
|
|
61
|
+
Other Parameters:
|
|
62
|
+
takefocus (bool): If True, the widget can receive keyboard focus.
|
|
63
|
+
width (int): Width of the PageStack in pixels.
|
|
64
|
+
height (int): Height of the PageStack in pixels.
|
|
65
|
+
padding (int | tuple): Padding around the PageStack (can be a single value
|
|
66
|
+
or tuple of (left, top, right, bottom)).
|
|
67
|
+
|
|
68
|
+
Note:
|
|
69
|
+
Pages must be added using add() before navigation can occur.
|
|
70
|
+
The PageStack starts with an empty history and no current page.
|
|
71
|
+
"""
|
|
72
|
+
super().__init__(master, **kwargs)
|
|
73
|
+
self._pages: dict[str, tkinter.Widget] = {}
|
|
74
|
+
self._current: str | None = None
|
|
75
|
+
self._history: list[tuple[str, dict]] = []
|
|
76
|
+
self._index: int = -1
|
|
77
|
+
|
|
78
|
+
def add(self, key: str, page: tkinter.Widget = None, **kwargs) -> tkinter.Widget:
|
|
79
|
+
"""Add a page to the stack, optionally creating a Frame.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
key (str): Unique identifier for the page (required for navigation).
|
|
83
|
+
page (Widget | None): The widget to add. If None, creates a Frame.
|
|
84
|
+
**kwargs: When page is None, these are passed to Frame (e.g., padding, color, variant).
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Widget: The page widget (passed or created Frame).
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
NavigationError: If a page with the given key already exists.
|
|
91
|
+
ValueError: If key is an empty string.
|
|
92
|
+
"""
|
|
93
|
+
if not key:
|
|
94
|
+
raise ValueError("Page key cannot be an empty string")
|
|
95
|
+
if key in self._pages:
|
|
96
|
+
raise NavigationError(f"Page {key} already exists")
|
|
97
|
+
|
|
98
|
+
if page is None:
|
|
99
|
+
page = Frame(self, **kwargs)
|
|
100
|
+
|
|
101
|
+
self._pages[key] = page
|
|
102
|
+
page.pack(fill='both', expand=True)
|
|
103
|
+
page.pack_forget()
|
|
104
|
+
return page
|
|
105
|
+
|
|
106
|
+
def remove(self, key: str) -> None:
|
|
107
|
+
"""Remove a page from the stack and destroy its widget.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
key: The identifier of the page to remove
|
|
111
|
+
|
|
112
|
+
Note:
|
|
113
|
+
If the removed page is currently displayed, the current page
|
|
114
|
+
will be set to None without navigating to another page.
|
|
115
|
+
"""
|
|
116
|
+
if key in self._pages:
|
|
117
|
+
page = self._pages.pop(key)
|
|
118
|
+
page.destroy()
|
|
119
|
+
if self._current == key:
|
|
120
|
+
self._current = None
|
|
121
|
+
|
|
122
|
+
def navigate(
|
|
123
|
+
self,
|
|
124
|
+
key: str,
|
|
125
|
+
data: dict | None = None,
|
|
126
|
+
replace: bool = False,
|
|
127
|
+
_nav: str = 'push',
|
|
128
|
+
_prev: tuple[str, dict] | None = None
|
|
129
|
+
) -> None:
|
|
130
|
+
"""Navigate to the page with the given key.
|
|
131
|
+
|
|
132
|
+
This method handles page transitions, manages navigation history,
|
|
133
|
+
and triggers lifecycle events (`<<PageUnmount>>`, `<<PageWillMount>>`,
|
|
134
|
+
`<<PageMount>>`, `<<PageChange>>`).
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
key: The identifier of the page to navigate to
|
|
138
|
+
data: Optional data to pass to the page and include in event payloads
|
|
139
|
+
replace: If True, replace the current history entry instead of adding a new one
|
|
140
|
+
_nav: Internal parameter indicating navigation type ('push', 'back', 'forward')
|
|
141
|
+
_prev: Internal parameter for preserving previous page state
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
NavigationError: If the page with the given key does not exist
|
|
145
|
+
ValueError: If key is an empty string
|
|
146
|
+
|
|
147
|
+
Note:
|
|
148
|
+
Event payloads include: page, prev_page, prev_data, nav, index,
|
|
149
|
+
length, can_back, and can_forward.
|
|
150
|
+
"""
|
|
151
|
+
if not key:
|
|
152
|
+
raise ValueError("Page key cannot be an empty string")
|
|
153
|
+
if key not in self._pages:
|
|
154
|
+
raise NavigationError(f"Page {key} does not exist")
|
|
155
|
+
|
|
156
|
+
# Normalize data to empty dict if None
|
|
157
|
+
if data is None:
|
|
158
|
+
data = {}
|
|
159
|
+
|
|
160
|
+
# Snapshot "previous" BEFORE mutating history
|
|
161
|
+
if _prev is None:
|
|
162
|
+
prev_key = self._current
|
|
163
|
+
prev_data = self._history[self._index][1] if self._index >= 0 else {}
|
|
164
|
+
else:
|
|
165
|
+
prev_key, prev_data = _prev
|
|
166
|
+
|
|
167
|
+
# Mutate history
|
|
168
|
+
if replace and 0 <= self._index < len(self._history):
|
|
169
|
+
self._history[self._index] = (key, data)
|
|
170
|
+
else:
|
|
171
|
+
if self._index < len(self._history) - 1:
|
|
172
|
+
self._history = self._history[:self._index + 1]
|
|
173
|
+
self._history.append((key, data))
|
|
174
|
+
self._index += 1
|
|
175
|
+
|
|
176
|
+
# Unmount previous page
|
|
177
|
+
if self._current is not None:
|
|
178
|
+
self._pages[self._current].event_generate('<<PageUnmount>>', when="tail")
|
|
179
|
+
self._pages[self._current].pack_forget()
|
|
180
|
+
|
|
181
|
+
# Normalized payload (self-contained snapshot)
|
|
182
|
+
payload = dict(data)
|
|
183
|
+
payload.update(
|
|
184
|
+
{
|
|
185
|
+
"page": key,
|
|
186
|
+
"prev_page": prev_key,
|
|
187
|
+
"prev_data": prev_data,
|
|
188
|
+
"nav": _nav,
|
|
189
|
+
"index": self._index,
|
|
190
|
+
"length": len(self._history),
|
|
191
|
+
"can_back": self._index > 0,
|
|
192
|
+
"can_forward": self._index < len(self._history) - 1,
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Mount and notify
|
|
197
|
+
page = self._pages[key]
|
|
198
|
+
page.event_generate('<<PageWillMount>>', data=payload, when="tail")
|
|
199
|
+
page.pack(fill='both', expand=True)
|
|
200
|
+
self._current = key
|
|
201
|
+
self.event_generate('<<PageMount>>', data=payload, when="tail")
|
|
202
|
+
self.event_generate('<<PageChange>>', data=payload, when="tail")
|
|
203
|
+
|
|
204
|
+
def back(self) -> None:
|
|
205
|
+
"""Navigate to the previous page in the navigation history.
|
|
206
|
+
|
|
207
|
+
This method moves backward in the history stack if possible.
|
|
208
|
+
Does nothing if already at the first page.
|
|
209
|
+
"""
|
|
210
|
+
if self._index > 0:
|
|
211
|
+
# Snapshot BEFORE changing index
|
|
212
|
+
prev = (self._current, self._history[self._index][1] if self._index >= 0 else {})
|
|
213
|
+
self._index -= 1
|
|
214
|
+
key, data = self._history[self._index]
|
|
215
|
+
# use replace=True to avoid pushing a new entry
|
|
216
|
+
# pass _prev to preserve the correct 'previous' snapshot
|
|
217
|
+
self.navigate(key, data=data, replace=True, _nav='back', _prev=prev)
|
|
218
|
+
|
|
219
|
+
def forward(self) -> None:
|
|
220
|
+
"""Navigate to the next page in the navigation history.
|
|
221
|
+
|
|
222
|
+
This method moves forward in the history stack if possible.
|
|
223
|
+
Does nothing if already at the most recent page.
|
|
224
|
+
"""
|
|
225
|
+
if self._index < len(self._history) - 1:
|
|
226
|
+
prev = (self._current, self._history[self._index][1] if self._index >= 0 else {})
|
|
227
|
+
self._index += 1
|
|
228
|
+
key, data = self._history[self._index]
|
|
229
|
+
self.navigate(key, data=data, replace=True, _nav='forward', _prev=prev)
|
|
230
|
+
|
|
231
|
+
def can_back(self) -> bool:
|
|
232
|
+
"""Check if backward navigation is possible.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
True if there is a previous page in the history to navigate back to,
|
|
236
|
+
False otherwise.
|
|
237
|
+
"""
|
|
238
|
+
return self._index > 0
|
|
239
|
+
|
|
240
|
+
def can_forward(self) -> bool:
|
|
241
|
+
"""Check if forward navigation is possible.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
True if there is a next page in the history to navigate forward to,
|
|
245
|
+
False otherwise.
|
|
246
|
+
"""
|
|
247
|
+
return self._index < len(self._history) - 1
|
|
248
|
+
|
|
249
|
+
def current(self) -> tuple[str, dict] | None:
|
|
250
|
+
"""Return the current page key and its navigation data.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
A tuple of (page_key, data_dict) if a page is currently displayed,
|
|
254
|
+
None if no page is currently displayed.
|
|
255
|
+
"""
|
|
256
|
+
if self._current is None:
|
|
257
|
+
return None
|
|
258
|
+
return self._current, (self._history[self._index][1] if self._index >= 0 else {})
|
|
259
|
+
|
|
260
|
+
def item(self, key: str) -> tkinter.Widget:
|
|
261
|
+
"""Get a page by its key.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
key: The identifier of the page to retrieve.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
The page widget.
|
|
268
|
+
|
|
269
|
+
Raises:
|
|
270
|
+
KeyError: If no page with the given key exists.
|
|
271
|
+
ValueError: If key is an empty string.
|
|
272
|
+
"""
|
|
273
|
+
if not key:
|
|
274
|
+
raise ValueError("Page key cannot be an empty string")
|
|
275
|
+
if key not in self._pages:
|
|
276
|
+
raise KeyError(f"No page with key '{key}'")
|
|
277
|
+
return self._pages[key]
|
|
278
|
+
|
|
279
|
+
def items(self) -> tuple[tkinter.Widget, ...]:
|
|
280
|
+
"""Get all page widgets in the stack.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
A tuple of all page widgets managed by this PageStack.
|
|
284
|
+
"""
|
|
285
|
+
return tuple(self._pages.values())
|
|
286
|
+
|
|
287
|
+
def keys(self) -> tuple[str, ...]:
|
|
288
|
+
"""Get all page keys.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
A tuple of all page keys in the stack.
|
|
292
|
+
"""
|
|
293
|
+
return tuple(self._pages.keys())
|
|
294
|
+
|
|
295
|
+
def configure_item(self, key: str, option: Any = None, **kwargs: Any) -> Any:
|
|
296
|
+
"""Query or configure the page configuration.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
key: The identifier of the page to configure.
|
|
300
|
+
option: Optional configuration option to query.
|
|
301
|
+
**kwargs: Configuration options to set on the page widget.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
If option is provided, returns the value of that option.
|
|
305
|
+
Otherwise returns the result of configure() if kwargs are provided.
|
|
306
|
+
|
|
307
|
+
Raises:
|
|
308
|
+
KeyError: If no page with the given key exists.
|
|
309
|
+
ValueError: If key is an empty string.
|
|
310
|
+
"""
|
|
311
|
+
if not key:
|
|
312
|
+
raise ValueError("Page key cannot be an empty string")
|
|
313
|
+
if key not in self._pages:
|
|
314
|
+
raise KeyError(f"No page with key '{key}'")
|
|
315
|
+
if option is not None:
|
|
316
|
+
return self._pages[key].cget(option)
|
|
317
|
+
else:
|
|
318
|
+
return self._pages[key].configure(**kwargs)
|
|
319
|
+
|
|
320
|
+
def on_page_changed(self, callback: Callable) -> str:
|
|
321
|
+
"""Bind to `<<PageChange>>`. Callback receives `event.data` with navigation info.
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Binding identifier for use with off_page_changed().
|
|
325
|
+
"""
|
|
326
|
+
return self.bind('<<PageChange>>', callback, add="+")
|
|
327
|
+
|
|
328
|
+
def off_page_changed(self, bind_id: str | None = None) -> None:
|
|
329
|
+
"""Unbind from `<<PageChange>>`."""
|
|
330
|
+
self.unbind("<<PageChange>>", bind_id)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Password entry field widget with visibility toggle.
|
|
2
|
+
|
|
3
|
+
Provides a specialized text entry field for password input with masked characters
|
|
4
|
+
and an optional visibility toggle button.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from bootstack.widgets.primitives.button import Button
|
|
8
|
+
from bootstack.widgets.composites.field import Field, FieldOptions
|
|
9
|
+
from bootstack.widgets.mixins.configure_mixin import configure_delegate
|
|
10
|
+
from bootstack.widgets.types import Master
|
|
11
|
+
from typing_extensions import Unpack
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PasswordEntry(Field):
|
|
15
|
+
"""A password entry field widget with masked input and visibility toggle.
|
|
16
|
+
|
|
17
|
+
PasswordEntry extends the Field widget to provide password-specific functionality,
|
|
18
|
+
including character masking and a toggle button to temporarily reveal the password.
|
|
19
|
+
The widget automatically inserts a visibility toggle button (eye icon) that shows
|
|
20
|
+
the password while pressed and hides it when released.
|
|
21
|
+
|
|
22
|
+
!!! note "Events"
|
|
23
|
+
|
|
24
|
+
- `<<Input>>`: Triggered on each keystroke.
|
|
25
|
+
- `<<Change>>`: Triggered when value changes after commit.
|
|
26
|
+
- `<<Valid>>`: Triggered when validation passes.
|
|
27
|
+
- `<<Invalid>>`: Triggered when validation fails.
|
|
28
|
+
- `<<Validate>>`: Triggered after any validation.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
entry_widget (TextEntryPart): The underlying text entry widget.
|
|
32
|
+
label_widget (Label): The label widget above the entry.
|
|
33
|
+
message_widget (Label): The message label widget below the entry.
|
|
34
|
+
addons (dict[str, Widget]): Dictionary of inserted addon widgets by name.
|
|
35
|
+
variable (Variable): Tkinter Variable linked to entry text.
|
|
36
|
+
signal (Signal): Signal object for reactive updates.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
master: Master = None,
|
|
42
|
+
value: str = None,
|
|
43
|
+
label: str = None,
|
|
44
|
+
message: str = None,
|
|
45
|
+
show_visibility_toggle: bool = True,
|
|
46
|
+
**kwargs: Unpack[FieldOptions]):
|
|
47
|
+
"""Initialize a PasswordEntry widget.
|
|
48
|
+
|
|
49
|
+
Creates a password entry field with character masking and an optional
|
|
50
|
+
visibility toggle button. The widget automatically masks input characters
|
|
51
|
+
(default: '•') and provides a button to temporarily reveal the password
|
|
52
|
+
while pressed.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
master: Parent widget. If None, uses the default root window.
|
|
56
|
+
value: Initial password value to display (masked). Default is None.
|
|
57
|
+
label: Optional label text to display above the entry field.
|
|
58
|
+
If required=True, an asterisk (*) is automatically appended.
|
|
59
|
+
message: Optional message text to display below the entry field.
|
|
60
|
+
Used for hints or help text. Replaced by validation errors when
|
|
61
|
+
validation fails.
|
|
62
|
+
show_visibility_toggle: If True, displays the visibility toggle button
|
|
63
|
+
(eye icon) that reveals the password while pressed. Default is True.
|
|
64
|
+
Can be changed at runtime via `configure(show_visibility_toggle=...)`.
|
|
65
|
+
|
|
66
|
+
Other Parameters:
|
|
67
|
+
show (str): Character to mask password input. Default is '•'.
|
|
68
|
+
required (bool): If True, field cannot be empty.
|
|
69
|
+
accent (str): Accent token for the focus ring and active border.
|
|
70
|
+
bootstyle (str): DEPRECATED - Use `accent` instead.
|
|
71
|
+
allow_blank (bool): Allow empty input.
|
|
72
|
+
cursor (str): Cursor style when hovering.
|
|
73
|
+
font (str): Font for text display.
|
|
74
|
+
foreground (str): Text color.
|
|
75
|
+
initial_focus (bool): If True, widget receives focus on creation.
|
|
76
|
+
justify (str): Text alignment.
|
|
77
|
+
show_message (bool): If True, displays message area.
|
|
78
|
+
padding (str): Padding around entry widget.
|
|
79
|
+
takefocus (bool): If True, widget accepts Tab focus.
|
|
80
|
+
textvariable (Variable): Tkinter Variable to link with text.
|
|
81
|
+
textsignal (Signal): Signal object for reactive updates.
|
|
82
|
+
width (int): Width in characters.
|
|
83
|
+
|
|
84
|
+
Note:
|
|
85
|
+
The visibility toggle button uses a press-and-hold interaction.
|
|
86
|
+
The password is only visible while the button is actively pressed,
|
|
87
|
+
providing a secure way to verify input without leaving it exposed.
|
|
88
|
+
"""
|
|
89
|
+
# set default mask if not provided
|
|
90
|
+
self._show_indicator = kwargs.get('show', '•')
|
|
91
|
+
kwargs.setdefault('show', self._show_indicator)
|
|
92
|
+
super().__init__(master, value=value, label=label, message=message, **kwargs)
|
|
93
|
+
|
|
94
|
+
# configuration
|
|
95
|
+
self._show_visibility_toggle = show_visibility_toggle
|
|
96
|
+
self._show_visibility_pack = {}
|
|
97
|
+
|
|
98
|
+
self.insert_addon(
|
|
99
|
+
Button,
|
|
100
|
+
position="after",
|
|
101
|
+
name="visibility",
|
|
102
|
+
icon={"name": "eye", "state": [("pressed", "eye-slash")]},
|
|
103
|
+
compound="image",
|
|
104
|
+
icon_only=True
|
|
105
|
+
)
|
|
106
|
+
addon = self.addons['visibility']
|
|
107
|
+
addon.bind('<ButtonPress>', self._show_password, add=True)
|
|
108
|
+
addon.bind('<ButtonRelease>', self._hide_password, add=True)
|
|
109
|
+
|
|
110
|
+
# Apply initial visibility setting
|
|
111
|
+
if not show_visibility_toggle:
|
|
112
|
+
self._apply_visibility_toggle(False)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def _visibility_toggle(self):
|
|
116
|
+
"""Get the visibility toggle button widget."""
|
|
117
|
+
return self.addons['visibility']
|
|
118
|
+
|
|
119
|
+
def _show_password(self, _):
|
|
120
|
+
"""Reveal the password by removing character masking."""
|
|
121
|
+
self.entry_widget['show'] = ''
|
|
122
|
+
|
|
123
|
+
def _hide_password(self, _):
|
|
124
|
+
"""Hide the password by restoring character masking."""
|
|
125
|
+
self.entry_widget['show'] = self._show_indicator
|
|
126
|
+
|
|
127
|
+
# ------ Configuration Delegates ------
|
|
128
|
+
|
|
129
|
+
@configure_delegate('show_visibility_toggle')
|
|
130
|
+
def _delegate_show_visibility_toggle(self, value=None):
|
|
131
|
+
if value is None:
|
|
132
|
+
return self._show_visibility_toggle
|
|
133
|
+
else:
|
|
134
|
+
self._apply_visibility_toggle(value)
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
def _apply_visibility_toggle(self, value: bool):
|
|
138
|
+
"""Show or hide the visibility toggle button.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
value: If True, show the toggle button. If False, hide it.
|
|
142
|
+
"""
|
|
143
|
+
self._show_visibility_toggle = value
|
|
144
|
+
if value and not self._visibility_toggle.winfo_ismapped():
|
|
145
|
+
self._visibility_toggle.pack(**self._show_visibility_pack)
|
|
146
|
+
elif not value and self._visibility_toggle.winfo_ismapped():
|
|
147
|
+
self._show_visibility_pack = self._visibility_toggle.pack_info()
|
|
148
|
+
self._visibility_toggle.pack_forget()
|
|
149
|
+
|