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,352 @@
|
|
|
1
|
+
"""TabView widget - a tabbed container combining Tabs with PageStack."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
__all__ = ['TabView']
|
|
5
|
+
|
|
6
|
+
import tkinter as tk
|
|
7
|
+
from typing import Callable, Literal, Union
|
|
8
|
+
|
|
9
|
+
from bootstack.widgets.primitives.frame import Frame
|
|
10
|
+
from bootstack.widgets.composites.tabs.tabs import Tabs
|
|
11
|
+
from bootstack.widgets.composites.tabs.tabitem import TabItem
|
|
12
|
+
from bootstack.widgets.composites.pagestack import PageStack
|
|
13
|
+
from bootstack.widgets.types import Master
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TabView(Frame):
|
|
17
|
+
"""A tabbed container that combines Tabs with a PageStack.
|
|
18
|
+
|
|
19
|
+
TabView provides a complete tabbed interface where each tab corresponds
|
|
20
|
+
to a page in the page stack. Selecting a tab navigates to the associated
|
|
21
|
+
page.
|
|
22
|
+
|
|
23
|
+
!!! note "Events"
|
|
24
|
+
- `<<TabSelect>>`: Fired when a tab is selected.
|
|
25
|
+
- `<<TabClose>>`: Fired when a tab's close button is clicked.
|
|
26
|
+
- `<<TabAdd>>`: Fired when the add button is clicked (if enable_adding=True).
|
|
27
|
+
- `<<PageChange>>`: Fired when the page changes (from PageStack).
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
master: Master = None,
|
|
33
|
+
orient: Literal['horizontal', 'vertical'] = 'horizontal',
|
|
34
|
+
variant: Literal['pill', 'bar'] = 'bar',
|
|
35
|
+
show_divider: bool = None,
|
|
36
|
+
compound: Literal['left', 'right', 'top', 'bottom', 'center', 'none'] = 'left',
|
|
37
|
+
tab_width: Union[None, int, Literal['stretch']] = None,
|
|
38
|
+
tab_padding: tuple = (12, 8),
|
|
39
|
+
tab_anchor: str = None,
|
|
40
|
+
enable_closing: Union[bool, Literal['hover']] = False,
|
|
41
|
+
enable_adding: bool = False,
|
|
42
|
+
accent: str = None,
|
|
43
|
+
**kwargs
|
|
44
|
+
):
|
|
45
|
+
"""Create a TabView widget.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
master: Parent widget. If None, uses the default root window.
|
|
49
|
+
orient: Orientation of the tab bar. 'horizontal' places tabs above
|
|
50
|
+
the content, 'vertical' places tabs to the left. Default is 'horizontal'.
|
|
51
|
+
variant: Visual style variant ('pill' or 'bar').
|
|
52
|
+
show_divider: Whether to show a divider between tabs and content.
|
|
53
|
+
compound: How to position icon relative to text in tabs.
|
|
54
|
+
tab_width: Width of tabs (None, integer, or 'stretch').
|
|
55
|
+
tab_padding: Padding for all tabs as (horizontal, vertical).
|
|
56
|
+
tab_anchor: Anchor for tab text/icon alignment. If None, defaults
|
|
57
|
+
to 'w' for vertical orientation, 'center' for horizontal.
|
|
58
|
+
enable_closing: Default close button visibility for all tabs.
|
|
59
|
+
True=always visible, False=hidden, 'hover'=visible on hover.
|
|
60
|
+
Can be overridden per-tab via `closable` in add().
|
|
61
|
+
enable_adding: If True, shows an "add" button that fires `<<TabAdd>>`.
|
|
62
|
+
accent: Accent token for styling.
|
|
63
|
+
**kwargs: Additional arguments passed to Frame.
|
|
64
|
+
"""
|
|
65
|
+
super().__init__(master, **kwargs)
|
|
66
|
+
|
|
67
|
+
self._orient = orient
|
|
68
|
+
self._variant = variant
|
|
69
|
+
self._accent = accent
|
|
70
|
+
|
|
71
|
+
# Create internal variable for tab selection
|
|
72
|
+
self._tab_variable = tk.StringVar()
|
|
73
|
+
|
|
74
|
+
# Create tabs container
|
|
75
|
+
self._tabs = Tabs(
|
|
76
|
+
self,
|
|
77
|
+
orient=orient,
|
|
78
|
+
variant=variant,
|
|
79
|
+
show_divider=show_divider,
|
|
80
|
+
compound=compound,
|
|
81
|
+
tab_width=tab_width,
|
|
82
|
+
tab_padding=tab_padding,
|
|
83
|
+
tab_anchor=tab_anchor,
|
|
84
|
+
enable_closing=enable_closing,
|
|
85
|
+
enable_adding=enable_adding,
|
|
86
|
+
variable=self._tab_variable,
|
|
87
|
+
accent=accent,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Create page stack
|
|
91
|
+
self._page_stack = PageStack(self)
|
|
92
|
+
|
|
93
|
+
# Layout based on orientation
|
|
94
|
+
if orient == 'horizontal':
|
|
95
|
+
self._tabs.pack(side='top', fill='x')
|
|
96
|
+
self._page_stack.pack(side='top', fill='both', expand=True)
|
|
97
|
+
else:
|
|
98
|
+
self._tabs.pack(side='left', fill='y')
|
|
99
|
+
self._page_stack.pack(side='left', fill='both', expand=True)
|
|
100
|
+
|
|
101
|
+
# Bind variable trace to navigate pages
|
|
102
|
+
self._trace_id = self._tab_variable.trace_add('write', self._on_tab_selected)
|
|
103
|
+
|
|
104
|
+
# Track tabs by key for removal
|
|
105
|
+
self._tab_map: dict[str, TabItem] = {}
|
|
106
|
+
|
|
107
|
+
# Clean up trace on destroy
|
|
108
|
+
self.bind('<Destroy>', self._on_destroy, add='+')
|
|
109
|
+
|
|
110
|
+
def _on_destroy(self, event=None):
|
|
111
|
+
"""Clean up variable trace when widget is destroyed."""
|
|
112
|
+
if event.widget is not self:
|
|
113
|
+
return
|
|
114
|
+
if self._trace_id is not None:
|
|
115
|
+
try:
|
|
116
|
+
self._tab_variable.trace_remove('write', self._trace_id)
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
self._trace_id = None
|
|
120
|
+
|
|
121
|
+
def _on_tab_selected(self, *args):
|
|
122
|
+
"""Handle tab selection to navigate page stack."""
|
|
123
|
+
key = self._tab_variable.get()
|
|
124
|
+
if key and key in self._tab_map:
|
|
125
|
+
# Only navigate if not already on this page
|
|
126
|
+
current = self._page_stack.current()
|
|
127
|
+
if current is None or current[0] != key:
|
|
128
|
+
self._page_stack.navigate(key)
|
|
129
|
+
|
|
130
|
+
def add(
|
|
131
|
+
self,
|
|
132
|
+
key: str,
|
|
133
|
+
text: str = "",
|
|
134
|
+
icon: str | dict = None,
|
|
135
|
+
page: tk.Widget = None,
|
|
136
|
+
closable: Union[bool, Literal['hover'], None] = None,
|
|
137
|
+
close_command: Callable = None,
|
|
138
|
+
command: Callable = None,
|
|
139
|
+
**kwargs
|
|
140
|
+
) -> tk.Widget:
|
|
141
|
+
"""Add a tab and its associated page.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
key: Unique identifier for the tab/page.
|
|
145
|
+
text: Text to display on the tab.
|
|
146
|
+
icon: Icon to display on the tab.
|
|
147
|
+
page: The page widget. If None, creates a Frame.
|
|
148
|
+
closable: Close button visibility (True, False, 'hover', or None).
|
|
149
|
+
If None, uses the widget's `enable_closing` setting.
|
|
150
|
+
close_command: Callback when close button is clicked.
|
|
151
|
+
If not provided and closable is enabled, removes the tab/page.
|
|
152
|
+
command: Callback when tab is selected.
|
|
153
|
+
**kwargs: Additional arguments passed to page Frame (if created).
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
The page widget.
|
|
157
|
+
"""
|
|
158
|
+
# Determine effective closable value
|
|
159
|
+
effective_closable = closable if closable is not None else self._tabs._enable_closing
|
|
160
|
+
|
|
161
|
+
# Create close command wrapper if closable but no command provided
|
|
162
|
+
if effective_closable and close_command is None:
|
|
163
|
+
close_command = lambda k=key: self.remove(k)
|
|
164
|
+
|
|
165
|
+
# Add tab
|
|
166
|
+
tab = self._tabs.add(
|
|
167
|
+
text=text,
|
|
168
|
+
icon=icon,
|
|
169
|
+
value=key,
|
|
170
|
+
closable=closable,
|
|
171
|
+
close_command=close_command,
|
|
172
|
+
command=command,
|
|
173
|
+
)
|
|
174
|
+
self._tab_map[key] = tab
|
|
175
|
+
|
|
176
|
+
# Add page
|
|
177
|
+
page_widget = self._page_stack.add(key, page, **kwargs)
|
|
178
|
+
|
|
179
|
+
# If this is the first tab, select it
|
|
180
|
+
if len(self._tab_map) == 1:
|
|
181
|
+
self._tab_variable.set(key)
|
|
182
|
+
|
|
183
|
+
return page_widget
|
|
184
|
+
|
|
185
|
+
def remove(self, key: str) -> None:
|
|
186
|
+
"""Remove a tab and its associated page.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
key: The identifier of the tab/page to remove.
|
|
190
|
+
"""
|
|
191
|
+
# Remove tab
|
|
192
|
+
if key in self._tab_map:
|
|
193
|
+
tab = self._tab_map.pop(key)
|
|
194
|
+
self._tabs.remove(tab)
|
|
195
|
+
tab.destroy()
|
|
196
|
+
|
|
197
|
+
# Remove page
|
|
198
|
+
self._page_stack.remove(key)
|
|
199
|
+
|
|
200
|
+
# If the removed tab was selected, select another
|
|
201
|
+
if self._tab_variable.get() == key:
|
|
202
|
+
if self._tab_map:
|
|
203
|
+
# Select the first available tab
|
|
204
|
+
first_key = next(iter(self._tab_map))
|
|
205
|
+
self._tab_variable.set(first_key)
|
|
206
|
+
else:
|
|
207
|
+
self._tab_variable.set('')
|
|
208
|
+
|
|
209
|
+
def select(self, key: str) -> None:
|
|
210
|
+
"""Select a tab by its key.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
key: The identifier of the tab to select.
|
|
214
|
+
"""
|
|
215
|
+
if key in self._tab_map:
|
|
216
|
+
self._tab_variable.set(key)
|
|
217
|
+
|
|
218
|
+
def navigate(self, key: str, data: dict = None) -> None:
|
|
219
|
+
"""Navigate to a tab/page with optional data.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
key: The identifier of the tab/page to navigate to.
|
|
223
|
+
data: Optional data to pass to the page.
|
|
224
|
+
"""
|
|
225
|
+
if key in self._tab_map:
|
|
226
|
+
self._tab_variable.set(key)
|
|
227
|
+
if data:
|
|
228
|
+
self._page_stack.navigate(key, data=data)
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def tabs_widget(self) -> Tabs:
|
|
232
|
+
"""Get the internal Tabs widget."""
|
|
233
|
+
return self._tabs
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def page_stack_widget(self) -> PageStack:
|
|
237
|
+
"""Get the internal PageStack widget."""
|
|
238
|
+
return self._page_stack
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def current(self) -> str | None:
|
|
242
|
+
"""Get the currently selected tab key."""
|
|
243
|
+
key = self._tab_variable.get()
|
|
244
|
+
return key if key else None
|
|
245
|
+
|
|
246
|
+
def page(self, key: str) -> tk.Widget:
|
|
247
|
+
"""Get a page widget by its key.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
key: The identifier of the page.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
The page widget.
|
|
254
|
+
|
|
255
|
+
Raises:
|
|
256
|
+
KeyError: If no page with the given key exists.
|
|
257
|
+
"""
|
|
258
|
+
return self._page_stack.item(key)
|
|
259
|
+
|
|
260
|
+
def pages(self) -> tuple[tk.Widget, ...]:
|
|
261
|
+
"""Get all page widgets.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
A tuple of all page widgets.
|
|
265
|
+
"""
|
|
266
|
+
return self._page_stack.items()
|
|
267
|
+
|
|
268
|
+
def page_keys(self) -> tuple[str, ...]:
|
|
269
|
+
"""Get all page keys.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
A tuple of all page keys.
|
|
273
|
+
"""
|
|
274
|
+
return self._page_stack.keys()
|
|
275
|
+
|
|
276
|
+
def tab(self, key: str) -> TabItem:
|
|
277
|
+
"""Get a tab widget by its key.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
key: The identifier of the tab.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
The TabItem widget.
|
|
284
|
+
|
|
285
|
+
Raises:
|
|
286
|
+
KeyError: If no tab with the given key exists.
|
|
287
|
+
"""
|
|
288
|
+
if key not in self._tab_map:
|
|
289
|
+
raise KeyError(f"No tab with key '{key}'")
|
|
290
|
+
return self._tab_map[key]
|
|
291
|
+
|
|
292
|
+
def tabs(self) -> tuple[TabItem, ...]:
|
|
293
|
+
"""Get all tab widgets.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
A tuple of all TabItem widgets.
|
|
297
|
+
"""
|
|
298
|
+
return tuple(self._tab_map.values())
|
|
299
|
+
|
|
300
|
+
def tab_keys(self) -> tuple[str, ...]:
|
|
301
|
+
"""Get all tab keys.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
A tuple of all tab keys.
|
|
305
|
+
"""
|
|
306
|
+
return tuple(self._tab_map.keys())
|
|
307
|
+
|
|
308
|
+
def configure_tab(self, key: str, option: str = None, **kwargs):
|
|
309
|
+
"""Configure a specific tab by its key.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
key: The key of the tab to configure.
|
|
313
|
+
option: If provided, return the value of this option.
|
|
314
|
+
**kwargs: Configuration options to apply to the tab.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
If option is provided, returns the value of that option.
|
|
318
|
+
"""
|
|
319
|
+
tab = self.tab(key)
|
|
320
|
+
if option is not None:
|
|
321
|
+
return tab.cget(option)
|
|
322
|
+
tab.configure(**kwargs)
|
|
323
|
+
|
|
324
|
+
def on_page_changed(self, callback: Callable) -> str:
|
|
325
|
+
"""Bind to `<<PageChange>>` event.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
callback: Function to call when page changes.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Binding identifier.
|
|
332
|
+
"""
|
|
333
|
+
return self._page_stack.on_page_changed(callback)
|
|
334
|
+
|
|
335
|
+
def off_page_changed(self, bind_id: str | None = None) -> None:
|
|
336
|
+
"""Unbind from `<<PageChange>>` event."""
|
|
337
|
+
self._page_stack.off_page_changed(bind_id)
|
|
338
|
+
|
|
339
|
+
def on_tab_added(self, callback: Callable) -> str:
|
|
340
|
+
"""Bind to `<<TabAdd>>` event (when add button is clicked).
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
callback: Function to call when add button is clicked.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Binding identifier for use with off_tab_added().
|
|
347
|
+
"""
|
|
348
|
+
return self._tabs.on_tab_added(callback)
|
|
349
|
+
|
|
350
|
+
def off_tab_added(self, bind_id: str | None = None) -> None:
|
|
351
|
+
"""Unbind from `<<TabAdd>>` event."""
|
|
352
|
+
self._tabs.off_tab_added(bind_id)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from typing_extensions import Unpack
|
|
4
|
+
|
|
5
|
+
from bootstack.widgets.composites.field import Field, FieldOptions
|
|
6
|
+
from bootstack.widgets.types import Master
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TextEntry(Field):
|
|
10
|
+
"""A text entry field widget with label, validation, and formatting support.
|
|
11
|
+
|
|
12
|
+
TextEntry is a composite widget that combines a label, text entry input, and
|
|
13
|
+
message area into a single component. It provides internationalization-aware
|
|
14
|
+
text input with deferred parsing, validation support, and visual feedback.
|
|
15
|
+
|
|
16
|
+
The widget separates user input (display text) from the committed/parsed value,
|
|
17
|
+
only parsing and formatting when the user commits via `<FocusOut>` or `<Return>`.
|
|
18
|
+
|
|
19
|
+
!!! note "Events"
|
|
20
|
+
|
|
21
|
+
- `<<Input>>`: Triggered on each keystroke.
|
|
22
|
+
Provides `event.data` with keys: `text`.
|
|
23
|
+
|
|
24
|
+
- `<<Change>>`: Triggered when value changes after commit.
|
|
25
|
+
Provides `event.data` with keys: `value`, `prev_value`, `text`.
|
|
26
|
+
|
|
27
|
+
- `<<Valid>>`: Triggered when validation passes.
|
|
28
|
+
Provides `event.data` with keys: `value`, `is_valid` (True), `message`.
|
|
29
|
+
|
|
30
|
+
- `<<Invalid>>`: Triggered when validation fails.
|
|
31
|
+
Provides `event.data` with keys: `value`, `is_valid` (False), `message`.
|
|
32
|
+
|
|
33
|
+
- `<<Validate>>`: Triggered after any validation.
|
|
34
|
+
Provides `event.data` with keys: `value`, `is_valid` (bool), `message`.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
entry_widget (TextEntryPart): The underlying text entry widget.
|
|
38
|
+
label_widget (Label): The label widget above the entry.
|
|
39
|
+
message_widget (Label): The message label widget below the entry.
|
|
40
|
+
addons (dict[str, Widget]): Dictionary of inserted addon widgets by name.
|
|
41
|
+
variable (Variable): Tkinter Variable linked to entry text.
|
|
42
|
+
signal (Signal): Signal object for reactive updates.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self, master: Master = None, value: Any = None, label: str = None, message: str = None,
|
|
47
|
+
**kwargs: Unpack[FieldOptions]):
|
|
48
|
+
"""Initialize a TextEntry widget.
|
|
49
|
+
|
|
50
|
+
Creates a composite text entry field with optional label, validation,
|
|
51
|
+
and formatting support. The widget includes a label area, entry input,
|
|
52
|
+
and message area for displaying hints or validation errors.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
master: Parent widget. If None, uses the default root window.
|
|
56
|
+
value: Initial value to display. 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
|
+
This is replaced by validation error messages when validation fails.
|
|
61
|
+
|
|
62
|
+
Other Parameters:
|
|
63
|
+
allow_blank (bool): Allow empty input. Default is True.
|
|
64
|
+
color (str): Color token for the focus ring and active border.
|
|
65
|
+
bootstyle (str): DEPRECATED - Use `color` instead.
|
|
66
|
+
cursor (str): Cursor style when hovering.
|
|
67
|
+
value_format (str): ICU format pattern for parsing/formatting.
|
|
68
|
+
exportselection (bool): Export selection to clipboard.
|
|
69
|
+
font (str): Font for text display.
|
|
70
|
+
foreground (str): Text color.
|
|
71
|
+
initial_focus (bool): If True, widget receives focus on creation.
|
|
72
|
+
justify (str): Text alignment ('left', 'center', 'right').
|
|
73
|
+
show_message (bool): If True, displays message area. Default is True.
|
|
74
|
+
padding: Padding around entry widget.
|
|
75
|
+
show (str): Character to mask input (e.g., '*' for passwords).
|
|
76
|
+
takefocus (bool): If True, widget accepts Tab focus.
|
|
77
|
+
textvariable (Variable): Tkinter Variable to link with text.
|
|
78
|
+
textsignal (Signal): Signal object for reactive updates.
|
|
79
|
+
width (int): Width in characters.
|
|
80
|
+
required (bool): If True, field cannot be empty. Adds 'required'
|
|
81
|
+
validation rule and appends '*' to label.
|
|
82
|
+
xscrollcommand: Callback for horizontal scrolling.
|
|
83
|
+
|
|
84
|
+
Note:
|
|
85
|
+
The widget automatically sets up event handlers for focus, validation,
|
|
86
|
+
and value commits. Use on_input(), on_changed(), on_valid(), and
|
|
87
|
+
on_invalid() methods to bind callbacks to widget events.
|
|
88
|
+
"""
|
|
89
|
+
super().__init__(master, value=value, label=label, message=message, **kwargs)
|
|
90
|
+
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Time entry field widget with dropdown list of time intervals.
|
|
2
|
+
|
|
3
|
+
Provides a specialized entry field for time input with a searchable dropdown
|
|
4
|
+
list of time values at specified intervals.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import datetime
|
|
8
|
+
from typing import Union
|
|
9
|
+
|
|
10
|
+
from typing_extensions import Unpack
|
|
11
|
+
|
|
12
|
+
from bootstack.runtime.app import get_app_settings
|
|
13
|
+
from bootstack.core.localization import IntlFormatter
|
|
14
|
+
from bootstack.widgets.composites.field import FieldOptions
|
|
15
|
+
from bootstack.widgets.composites.selectbox import SelectBox
|
|
16
|
+
from bootstack.widgets.types import Master
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TimeEntry(SelectBox):
|
|
20
|
+
"""Time entry field with dropdown list of time intervals.
|
|
21
|
+
|
|
22
|
+
TimeEntry extends SelectBox to provide specialized time input with
|
|
23
|
+
locale-aware formatting and a searchable dropdown of time intervals.
|
|
24
|
+
The widget supports various time format presets and custom time patterns.
|
|
25
|
+
|
|
26
|
+
!!! note "Events"
|
|
27
|
+
|
|
28
|
+
- `<<Change>>`: Fired when time value changes after commit.
|
|
29
|
+
- `<<Input>>`: Fired on each keystroke.
|
|
30
|
+
- `<<Valid>>`: Fired when validation passes.
|
|
31
|
+
- `<<Invalid>>`: Fired when validation fails.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
entry_widget (TextEntryPart): The underlying text entry widget.
|
|
35
|
+
label_widget (Label): The label widget above the entry.
|
|
36
|
+
message_widget (Label): The message label widget below the entry.
|
|
37
|
+
addons (dict[str, Widget]): Dictionary of inserted addon widgets by name.
|
|
38
|
+
variable (Variable): Tkinter Variable linked to entry text.
|
|
39
|
+
signal (Signal): Signal object for reactive updates.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
master: Master = None,
|
|
45
|
+
value: Union[datetime.time, str] = None,
|
|
46
|
+
value_format: str = 'shortTime',
|
|
47
|
+
interval: int = 30,
|
|
48
|
+
min_time: Union[datetime.time, str] = None,
|
|
49
|
+
max_time: Union[datetime.time, str] = None,
|
|
50
|
+
label: str = None,
|
|
51
|
+
message: str = None,
|
|
52
|
+
**kwargs: Unpack[FieldOptions]
|
|
53
|
+
):
|
|
54
|
+
"""Initialize a TimeEntry widget.
|
|
55
|
+
|
|
56
|
+
Creates a time entry field with locale-aware formatting and a searchable
|
|
57
|
+
dropdown of time intervals. The widget accepts time input as time objects
|
|
58
|
+
or strings, and formats them according to the specified value_format pattern.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
master: Parent widget. If None, uses the default root window.
|
|
62
|
+
value: Initial time value to display. Can be a time object or string.
|
|
63
|
+
Default is current time.
|
|
64
|
+
value_format: Time format pattern for parsing and displaying times.
|
|
65
|
+
Default is "shortTime" (e.g., "3:30 PM"). Common formats:
|
|
66
|
+
"shortTime" (3:30 PM), "longTime" (3:30:45 PM PST),
|
|
67
|
+
"mediumTime" (3:30:45 PM), "HH:mm" (15:30), "h:mm a" (3:30 PM).
|
|
68
|
+
interval: Time interval in minutes for dropdown items (e.g., 15, 30, 60).
|
|
69
|
+
Default is 30 minutes.
|
|
70
|
+
min_time: Minimum time value for the dropdown list. Can be a time object
|
|
71
|
+
or string (e.g., "09:00" or "9:00 AM"). Default is midnight (00:00).
|
|
72
|
+
max_time: Maximum time value for the dropdown list. Can be a time object
|
|
73
|
+
or string (e.g., "17:00" or "5:00 PM"). Default is 11:59 PM (23:59).
|
|
74
|
+
label: Optional label text to display above the entry field.
|
|
75
|
+
If required=True, an asterisk (*) is automatically appended.
|
|
76
|
+
message: Optional message text to display below the entry field.
|
|
77
|
+
Used for hints or help text. Replaced by validation errors when
|
|
78
|
+
validation fails.
|
|
79
|
+
|
|
80
|
+
Other Parameters:
|
|
81
|
+
locale (str): Locale identifier for time formatting (e.g., 'en_US').
|
|
82
|
+
required (bool): If True, field cannot be empty.
|
|
83
|
+
color (str): Color token for the focus ring and active border.
|
|
84
|
+
bootstyle (str): DEPRECATED - Use `color` instead.
|
|
85
|
+
allow_blank (bool): Allow empty input.
|
|
86
|
+
width (int): Width in characters.
|
|
87
|
+
textvariable (Variable): Tkinter Variable to link with text.
|
|
88
|
+
textsignal (Signal): Signal object for reactive updates.
|
|
89
|
+
|
|
90
|
+
Note:
|
|
91
|
+
The widget uses IntlFormatter for locale-aware time formatting.
|
|
92
|
+
The dropdown is searchable - type to filter time values.
|
|
93
|
+
Custom time values can be entered directly in the field.
|
|
94
|
+
"""
|
|
95
|
+
self._interval = interval
|
|
96
|
+
self._value_format = value_format
|
|
97
|
+
self._locale = kwargs.get('locale') or get_app_settings().locale
|
|
98
|
+
|
|
99
|
+
# Parse and store time range
|
|
100
|
+
self._min_time = self._parse_time(min_time) if min_time else datetime.time(0, 0)
|
|
101
|
+
self._max_time = self._parse_time(max_time) if max_time else datetime.time(23, 59)
|
|
102
|
+
|
|
103
|
+
# Default to current time if not provided
|
|
104
|
+
if value is None:
|
|
105
|
+
value = datetime.datetime.now().time()
|
|
106
|
+
|
|
107
|
+
# Generate time intervals for dropdown
|
|
108
|
+
items = self._generate_time_intervals()
|
|
109
|
+
|
|
110
|
+
# Initialize SelectBox with time-specific configuration
|
|
111
|
+
super().__init__(
|
|
112
|
+
master=master,
|
|
113
|
+
value=value,
|
|
114
|
+
value_format=value_format,
|
|
115
|
+
items=items,
|
|
116
|
+
allow_custom_values=True,
|
|
117
|
+
enable_search=True,
|
|
118
|
+
dropdown_button_icon='clock',
|
|
119
|
+
message=message,
|
|
120
|
+
label=label,
|
|
121
|
+
**kwargs
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def _parse_time(self, time_value: Union[datetime.time, str]) -> datetime.time:
|
|
125
|
+
"""Parse time value from various input formats.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
time_value: Time value as time object or string
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Parsed time object, or midnight (00:00) if parsing fails
|
|
132
|
+
"""
|
|
133
|
+
if isinstance(time_value, datetime.time):
|
|
134
|
+
return time_value
|
|
135
|
+
|
|
136
|
+
if isinstance(time_value, str):
|
|
137
|
+
# Try 24-hour format (HH:MM)
|
|
138
|
+
try:
|
|
139
|
+
dt = datetime.datetime.strptime(time_value, '%H:%M')
|
|
140
|
+
return dt.time()
|
|
141
|
+
except ValueError:
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
# Try 12-hour format (h:MM AM/PM)
|
|
145
|
+
try:
|
|
146
|
+
dt = datetime.datetime.strptime(time_value, '%I:%M %p')
|
|
147
|
+
return dt.time()
|
|
148
|
+
except ValueError:
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
# Fallback to midnight
|
|
152
|
+
return datetime.time(0, 0)
|
|
153
|
+
|
|
154
|
+
def _generate_time_intervals(self) -> list[str]:
|
|
155
|
+
"""Generate list of formatted time strings at specified intervals.
|
|
156
|
+
|
|
157
|
+
Creates a list of time values from min_time to max_time at the specified
|
|
158
|
+
interval, formatted according to value_format. Uses IntlFormatter for
|
|
159
|
+
consistent locale-aware formatting with Field.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
List of formatted time strings
|
|
163
|
+
"""
|
|
164
|
+
times = []
|
|
165
|
+
formatter = IntlFormatter(locale=self._locale)
|
|
166
|
+
|
|
167
|
+
# Convert time objects to datetime for iteration
|
|
168
|
+
current = datetime.datetime.combine(datetime.date.today(), self._min_time)
|
|
169
|
+
end = datetime.datetime.combine(datetime.date.today(), self._max_time)
|
|
170
|
+
|
|
171
|
+
# Handle midnight crossing (e.g., min_time=22:00, max_time=02:00)
|
|
172
|
+
if end < current:
|
|
173
|
+
end += datetime.timedelta(days=1)
|
|
174
|
+
|
|
175
|
+
# Generate intervals
|
|
176
|
+
while current <= end:
|
|
177
|
+
# Format using IntlFormatter (consistent with TextEntryPart)
|
|
178
|
+
# Note: Pass datetime (not time) as IntlFormatter expects it for Babel
|
|
179
|
+
try:
|
|
180
|
+
formatted_time = formatter.format(current, self._value_format)
|
|
181
|
+
except (ValueError, TypeError):
|
|
182
|
+
# Fallback to 24-hour format if formatting fails
|
|
183
|
+
formatted_time = current.strftime('%H:%M')
|
|
184
|
+
|
|
185
|
+
times.append(formatted_time)
|
|
186
|
+
current += datetime.timedelta(minutes=self._interval)
|
|
187
|
+
|
|
188
|
+
return times
|
|
189
|
+
|