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,346 @@
|
|
|
1
|
+
"""Font modifier syntax for inline font customization in bootstack widgets.
|
|
2
|
+
|
|
3
|
+
This module provides a mixin that enables concise font modification syntax for all
|
|
4
|
+
bootstack widgets. The syntax uses bracket notation similar to bootstyle modifiers,
|
|
5
|
+
allowing inline font customization without creating custom Font objects.
|
|
6
|
+
|
|
7
|
+
## Syntax
|
|
8
|
+
|
|
9
|
+
The full modifier syntax follows the pattern: `family[size][weight][style]`
|
|
10
|
+
|
|
11
|
+
All components are optional and can be mixed in any combination. When components are
|
|
12
|
+
omitted, the widget's current font values are preserved.
|
|
13
|
+
|
|
14
|
+
Components:
|
|
15
|
+
- family: Font family name or typography token (e.g., 'helvetica', 'body', 'heading-lg')
|
|
16
|
+
- size: Point size (e.g., '16'), pixel size (e.g., '16px'), or size token (e.g., 'sm', 'lg')
|
|
17
|
+
- weight: 'bold' or 'normal'
|
|
18
|
+
- style: 'italic', 'roman', 'underline', 'overstrike' (comma-separated for multiple)
|
|
19
|
+
|
|
20
|
+
Size Tokens:
|
|
21
|
+
xs=8pt, sm=10pt, md=12pt, lg=14pt, xl=16pt, xxl=18pt
|
|
22
|
+
|
|
23
|
+
Font Tokens:
|
|
24
|
+
body, label, heading-md, heading-lg, heading-xl, display-lg, display-xl,
|
|
25
|
+
code, hyperlink, caption, body-sm, body-lg, body-xl
|
|
26
|
+
|
|
27
|
+
## Behavior
|
|
28
|
+
|
|
29
|
+
- **At widget creation**: Modifiers are applied to the widget's default style font
|
|
30
|
+
- **At runtime**: Modifiers are applied to the widget's current font
|
|
31
|
+
- **Missing family**: Uses widget's current font family (or 'body' token if none)
|
|
32
|
+
- **Missing size**: Uses widget's current font size (or 'body' token size if none)
|
|
33
|
+
|
|
34
|
+
## Examples
|
|
35
|
+
|
|
36
|
+
Basic modifications:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# Use body token, make it bold
|
|
40
|
+
Label(root, text="Title", font="body[bold]")
|
|
41
|
+
|
|
42
|
+
# Change current font to 16pt (preserves family)
|
|
43
|
+
label.configure(font="[16]")
|
|
44
|
+
|
|
45
|
+
# Make current font bold and italic (preserves family and size)
|
|
46
|
+
label.configure(font="[bold,italic]")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Custom font families:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
# Helvetica, 16pt, bold
|
|
53
|
+
Button(root, text="Click", font="helvetica[16][bold]")
|
|
54
|
+
|
|
55
|
+
# Arial, 14 pixels (negative in Tk), bold and italic
|
|
56
|
+
Label(root, text="Text", font="arial[14px][bold,italic]")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Size tokens:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
# Small size (10pt) with bold
|
|
63
|
+
Entry(root, font="[sm][bold]")
|
|
64
|
+
|
|
65
|
+
# Large size (14pt)
|
|
66
|
+
Label(root, font="[lg]")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Font tokens with modifiers:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
# Heading-lg token with italic style
|
|
73
|
+
Label(root, text="Heading", font="heading-lg[italic]")
|
|
74
|
+
|
|
75
|
+
# Label token at custom size
|
|
76
|
+
Button(root, text="Button", font="label[16]")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Multiple style modifiers:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# Bold, italic, and underlined
|
|
83
|
+
Label(root, text="Emphasis", font="[16][bold,italic,underline]")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Integration
|
|
87
|
+
|
|
88
|
+
FontMixin is automatically integrated into all bootstack widgets via TTKWrapperBase.
|
|
89
|
+
No additional setup is required - all widgets supporting the 'font' argument automatically
|
|
90
|
+
gain modifier syntax support.
|
|
91
|
+
|
|
92
|
+
The mixin uses the @configure_delegate pattern to intercept font configuration, parse
|
|
93
|
+
the modifier syntax, and apply the resolved font specification to the underlying ttk widget.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
from __future__ import annotations
|
|
97
|
+
|
|
98
|
+
import re
|
|
99
|
+
from typing import Any, Literal, TYPE_CHECKING
|
|
100
|
+
|
|
101
|
+
if TYPE_CHECKING:
|
|
102
|
+
from bootstack.style.typography import Typography, FontTokenNames
|
|
103
|
+
|
|
104
|
+
from bootstack.widgets.mixins.configure_mixin import configure_delegate
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# Size tokens mapping (shortcuts for common sizes)
|
|
108
|
+
SIZE_TOKENS = {
|
|
109
|
+
'xs': 8,
|
|
110
|
+
'sm': 10,
|
|
111
|
+
'md': 12,
|
|
112
|
+
'lg': 14,
|
|
113
|
+
'xl': 16,
|
|
114
|
+
'xxl': 18,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _get_font_token_names() -> set[str]:
|
|
119
|
+
"""Get all valid font token names (lazy to avoid import issues)."""
|
|
120
|
+
from bootstack.style.typography import FontTokenNames
|
|
121
|
+
return {
|
|
122
|
+
name.replace('_', '-')
|
|
123
|
+
for name in dir(FontTokenNames)
|
|
124
|
+
if not name.startswith('_')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def parse_font_modifier(font_spec: str) -> dict[str, Any]:
|
|
129
|
+
"""Parse font modifier syntax string into configuration dict.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
font_spec: Font specification string (e.g., "helvetica[16][bold]")
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dict with keys: family, size, weight, slant, underline, overstrike
|
|
136
|
+
"""
|
|
137
|
+
if not font_spec or not font_spec.strip():
|
|
138
|
+
return {}
|
|
139
|
+
|
|
140
|
+
result = {}
|
|
141
|
+
|
|
142
|
+
# Find all bracketed parts: [content]
|
|
143
|
+
bracket_pattern = r'\[([^\]]+)\]'
|
|
144
|
+
parts = re.findall(bracket_pattern, font_spec)
|
|
145
|
+
|
|
146
|
+
# Get family/token (everything before first bracket, or whole string if no brackets)
|
|
147
|
+
if '[' in font_spec:
|
|
148
|
+
family_part = font_spec[:font_spec.index('[')].strip()
|
|
149
|
+
else:
|
|
150
|
+
family_part = font_spec.strip()
|
|
151
|
+
|
|
152
|
+
if family_part:
|
|
153
|
+
result['family'] = family_part
|
|
154
|
+
|
|
155
|
+
# Process each bracketed part
|
|
156
|
+
for part in parts:
|
|
157
|
+
part = part.strip()
|
|
158
|
+
if not part:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
# Check if it's a pixel size (e.g., "16px")
|
|
162
|
+
if part.endswith('px'):
|
|
163
|
+
try:
|
|
164
|
+
# Pixel sizes are negative in Tk
|
|
165
|
+
result['size'] = -int(part[:-2])
|
|
166
|
+
continue
|
|
167
|
+
except ValueError:
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
# Check if it's a point size (e.g., "16")
|
|
171
|
+
if part.isdigit():
|
|
172
|
+
result['size'] = int(part)
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
# Check if it's a size token (e.g., "sm", "lg")
|
|
176
|
+
if part in SIZE_TOKENS:
|
|
177
|
+
result['size'] = SIZE_TOKENS[part]
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
# Otherwise, treat as comma-separated modifiers
|
|
181
|
+
modifiers = [m.strip().lower() for m in part.split(',')]
|
|
182
|
+
for modifier in modifiers:
|
|
183
|
+
if not modifier:
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
# Weight modifiers
|
|
187
|
+
if modifier in ('bold', 'normal'):
|
|
188
|
+
result['weight'] = modifier
|
|
189
|
+
# Slant modifiers
|
|
190
|
+
elif modifier in ('italic', 'roman'):
|
|
191
|
+
result['slant'] = modifier
|
|
192
|
+
# Boolean modifiers
|
|
193
|
+
elif modifier == 'underline':
|
|
194
|
+
result['underline'] = True
|
|
195
|
+
elif modifier == 'overstrike':
|
|
196
|
+
result['overstrike'] = True
|
|
197
|
+
|
|
198
|
+
return result
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def build_font_from_modifier(font_spec: str, base_font: Any = None) -> tuple | str:
|
|
202
|
+
"""Build Tk-compatible font tuple from modifier syntax, using base_font for missing values.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
font_spec: Font specification with modifier syntax (e.g., "[16][bold]")
|
|
206
|
+
base_font: Base font to extend (token name or tuple); defaults to 'body' token
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Font tuple like ('Helvetica', 16, 'bold italic') or token name
|
|
210
|
+
"""
|
|
211
|
+
from bootstack.style.typography import Typography
|
|
212
|
+
|
|
213
|
+
parsed = parse_font_modifier(font_spec)
|
|
214
|
+
if not parsed:
|
|
215
|
+
return base_font or 'body'
|
|
216
|
+
|
|
217
|
+
# Start with base font configuration
|
|
218
|
+
config = {}
|
|
219
|
+
family_is_token = False
|
|
220
|
+
font_token_names = _get_font_token_names()
|
|
221
|
+
|
|
222
|
+
# Check if family is a known token
|
|
223
|
+
if 'family' in parsed:
|
|
224
|
+
family_name = parsed['family']
|
|
225
|
+
if family_name in font_token_names:
|
|
226
|
+
# It's a font token - get its spec
|
|
227
|
+
token_spec = Typography.get_token(family_name)
|
|
228
|
+
config['family'] = token_spec.font
|
|
229
|
+
config['size'] = token_spec.size
|
|
230
|
+
config['weight'] = token_spec.weight
|
|
231
|
+
if token_spec.underline:
|
|
232
|
+
config['underline'] = token_spec.underline
|
|
233
|
+
family_is_token = True
|
|
234
|
+
else:
|
|
235
|
+
# It's a font family name
|
|
236
|
+
config['family'] = family_name
|
|
237
|
+
elif base_font:
|
|
238
|
+
# Use base font if provided
|
|
239
|
+
if isinstance(base_font, str) and base_font in font_token_names:
|
|
240
|
+
token_spec = Typography.get_token(base_font)
|
|
241
|
+
config['family'] = token_spec.font
|
|
242
|
+
config['size'] = token_spec.size
|
|
243
|
+
config['weight'] = token_spec.weight
|
|
244
|
+
if token_spec.underline:
|
|
245
|
+
config['underline'] = token_spec.underline
|
|
246
|
+
elif isinstance(base_font, tuple) and len(base_font) >= 2:
|
|
247
|
+
config['family'] = base_font[0]
|
|
248
|
+
config['size'] = base_font[1]
|
|
249
|
+
if len(base_font) >= 3:
|
|
250
|
+
# Parse weight/slant from tuple
|
|
251
|
+
styles = base_font[2].split()
|
|
252
|
+
for style in styles:
|
|
253
|
+
if style in ('bold', 'normal'):
|
|
254
|
+
config['weight'] = style
|
|
255
|
+
elif style in ('italic', 'roman'):
|
|
256
|
+
config['slant'] = style
|
|
257
|
+
|
|
258
|
+
# Apply parsed modifiers (override base)
|
|
259
|
+
if 'size' in parsed:
|
|
260
|
+
config['size'] = parsed['size']
|
|
261
|
+
if 'weight' in parsed:
|
|
262
|
+
config['weight'] = parsed['weight']
|
|
263
|
+
if 'slant' in parsed:
|
|
264
|
+
config['slant'] = parsed['slant']
|
|
265
|
+
if 'underline' in parsed:
|
|
266
|
+
config['underline'] = parsed['underline']
|
|
267
|
+
if 'overstrike' in parsed:
|
|
268
|
+
config['overstrike'] = parsed['overstrike']
|
|
269
|
+
|
|
270
|
+
# Ensure we have family and size - use body token as fallback
|
|
271
|
+
if 'family' not in config or 'size' not in config:
|
|
272
|
+
body_spec = Typography.get_token('body')
|
|
273
|
+
if 'family' not in config:
|
|
274
|
+
config['family'] = body_spec.font
|
|
275
|
+
if 'size' not in config:
|
|
276
|
+
config['size'] = body_spec.size
|
|
277
|
+
|
|
278
|
+
# Build Tk font specification as tuple (family, size, modifiers_string)
|
|
279
|
+
family = config['family']
|
|
280
|
+
size = config['size']
|
|
281
|
+
modifiers = []
|
|
282
|
+
|
|
283
|
+
if config.get('weight') == 'bold':
|
|
284
|
+
modifiers.append('bold')
|
|
285
|
+
if config.get('slant') == 'italic':
|
|
286
|
+
modifiers.append('italic')
|
|
287
|
+
if config.get('underline'):
|
|
288
|
+
modifiers.append('underline')
|
|
289
|
+
if config.get('overstrike'):
|
|
290
|
+
modifiers.append('overstrike')
|
|
291
|
+
|
|
292
|
+
if modifiers:
|
|
293
|
+
return (family, size, ' '.join(modifiers))
|
|
294
|
+
else:
|
|
295
|
+
return (family, size)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class FontMixin:
|
|
299
|
+
"""
|
|
300
|
+
Constructor + runtime font modifier support.
|
|
301
|
+
|
|
302
|
+
Contract expected by WrapperBase:
|
|
303
|
+
- _init_font_mixin(kwargs) -> returns processed font value (or None)
|
|
304
|
+
- _delegate_font(value) -> applies/handles delegated configure
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
def _init_font_mixin(self, kwargs: dict[str, Any]) -> Any:
|
|
308
|
+
if "font" not in kwargs:
|
|
309
|
+
return None
|
|
310
|
+
|
|
311
|
+
value = kwargs.pop("font")
|
|
312
|
+
|
|
313
|
+
if isinstance(value, str):
|
|
314
|
+
font_token_names = _get_font_token_names()
|
|
315
|
+
|
|
316
|
+
if value in font_token_names and "[" not in value:
|
|
317
|
+
return value
|
|
318
|
+
|
|
319
|
+
if "[" in value:
|
|
320
|
+
# Constructor-time: no widget yet, so use a stable base
|
|
321
|
+
return build_font_from_modifier(value, base_font="body")
|
|
322
|
+
|
|
323
|
+
return value
|
|
324
|
+
|
|
325
|
+
@configure_delegate("font")
|
|
326
|
+
def _delegate_font(self, value: Any = None):
|
|
327
|
+
if value is None:
|
|
328
|
+
return self._ttk_base.cget(self, "font") # type: ignore[misc]
|
|
329
|
+
|
|
330
|
+
if isinstance(value, str):
|
|
331
|
+
font_token_names = _get_font_token_names()
|
|
332
|
+
|
|
333
|
+
if value in font_token_names and "[" not in value:
|
|
334
|
+
font_value = value
|
|
335
|
+
elif "[" in value:
|
|
336
|
+
try:
|
|
337
|
+
current_font = self._ttk_base.cget(self, "font") # type: ignore[misc]
|
|
338
|
+
except Exception:
|
|
339
|
+
current_font = "body"
|
|
340
|
+
font_value = build_font_from_modifier(value, base_font=current_font)
|
|
341
|
+
else:
|
|
342
|
+
font_value = value
|
|
343
|
+
else:
|
|
344
|
+
font_value = value
|
|
345
|
+
|
|
346
|
+
return self._ttk_base.configure(self, font=font_value) # type: ignore[misc]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Icon mixin for widgets that support theme-aware icons.
|
|
2
|
+
|
|
3
|
+
Provides a `@configure_delegate('icon')` handler that applies an icon through
|
|
4
|
+
the style system and preserves current bootstyle tokens, orientation, and
|
|
5
|
+
surface color where applicable.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Callable
|
|
11
|
+
|
|
12
|
+
from bootstack.widgets.mixins.configure_mixin import configure_delegate
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class IconMixin:
|
|
16
|
+
"""Adds `icon` configuration support via the style engine."""
|
|
17
|
+
|
|
18
|
+
configure_style_options: Callable
|
|
19
|
+
rebuild_style: Callable
|
|
20
|
+
|
|
21
|
+
@configure_delegate("icon")
|
|
22
|
+
def _delegate_icon(self, value: Any = None):
|
|
23
|
+
if value is None:
|
|
24
|
+
return self.configure_style_options("icon")
|
|
25
|
+
else:
|
|
26
|
+
self.configure_style_options(icon=value)
|
|
27
|
+
return self.rebuild_style()
|
|
28
|
+
|
|
29
|
+
@configure_delegate("icon_only")
|
|
30
|
+
def _delegate_icon_only(self, value: Any = None):
|
|
31
|
+
if value is None:
|
|
32
|
+
return self.configure_style_options("icon_only")
|
|
33
|
+
else:
|
|
34
|
+
self.configure_style_options(icon_only=value)
|
|
35
|
+
return self.rebuild_style()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__all__ = ["IconMixin"]
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""Localization mixin for bootstack widgets.
|
|
2
|
+
|
|
3
|
+
Provides automatic text translation and value formatting based on the current
|
|
4
|
+
locale. Widgets using this mixin will automatically update when the locale
|
|
5
|
+
changes.
|
|
6
|
+
|
|
7
|
+
This mixin is a thin glue layer that delegates resolution logic to the core
|
|
8
|
+
localization capability module.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any, Dict
|
|
14
|
+
from tkinter import StringVar, Misc
|
|
15
|
+
|
|
16
|
+
from bootstack.core.capabilities.localization import (
|
|
17
|
+
resolve_text,
|
|
18
|
+
resolve_variable_text,
|
|
19
|
+
apply_spec,
|
|
20
|
+
get_current_locale,
|
|
21
|
+
create_formatted_signal,
|
|
22
|
+
)
|
|
23
|
+
from bootstack.core.localization.specs import (
|
|
24
|
+
LocalizedSpec,
|
|
25
|
+
LocalizedTextSpec,
|
|
26
|
+
LocalizedValueSpec,
|
|
27
|
+
)
|
|
28
|
+
from bootstack.runtime.app import get_app_settings
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LocalizationMixin(Misc):
|
|
32
|
+
"""Mixin for widgets that support automatic text and value localization.
|
|
33
|
+
|
|
34
|
+
This mixin enables widgets to automatically localize text and format values
|
|
35
|
+
according to the current locale. It listens for locale change events and
|
|
36
|
+
updates all registered fields accordingly.
|
|
37
|
+
|
|
38
|
+
This mixin delegates resolution logic to the core localization capability.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
localize: Controls whether literals are auto-wrapped into localization specs.
|
|
42
|
+
Can be True, False, or "auto".
|
|
43
|
+
value_format: Default IntlFormatter spec for non-string values (e.g., 'currency',
|
|
44
|
+
'decimal', 'percent').
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, *args, **kwargs):
|
|
48
|
+
"""Initialize the localizable widget mixin.
|
|
49
|
+
|
|
50
|
+
This mixin intercepts localization-related kwargs. It inspects 'text'
|
|
51
|
+
for localization without consuming it, so it can be passed to the
|
|
52
|
+
underlying widget. It consumes 'localize' and 'value_format' as they
|
|
53
|
+
are not standard ttk options.
|
|
54
|
+
"""
|
|
55
|
+
# Determine the localization mode. A widget-specific 'localize' argument
|
|
56
|
+
# overrides the global app setting. This argument is consumed from kwargs.
|
|
57
|
+
localize = kwargs.pop('localize', get_app_settings().localize_mode)
|
|
58
|
+
value_format = kwargs.pop('value_format', None)
|
|
59
|
+
|
|
60
|
+
# Get 'text' for localization without removing it from kwargs.
|
|
61
|
+
text_to_localize = kwargs.get('text')
|
|
62
|
+
|
|
63
|
+
# Call the next class in the MRO with the remaining kwargs.
|
|
64
|
+
# 'text' is still in kwargs for the underlying widget to use.
|
|
65
|
+
super().__init__(*args, **kwargs)
|
|
66
|
+
|
|
67
|
+
# --- Post-init setup ---
|
|
68
|
+
self._localized_fields: Dict[str, LocalizedSpec] = {}
|
|
69
|
+
self._localize_mode = localize
|
|
70
|
+
self._default_value_format = value_format
|
|
71
|
+
|
|
72
|
+
root = self.winfo_toplevel() # event is generated on root
|
|
73
|
+
root.bind("<<LocaleChanged>>", self._on_locale_changed, add="+")
|
|
74
|
+
|
|
75
|
+
# Check if widget has a textsignal or textvariable with value_format
|
|
76
|
+
if value_format and self._has_signal_or_variable():
|
|
77
|
+
self._setup_signal_formatting(value_format)
|
|
78
|
+
else:
|
|
79
|
+
# Register the text field for static localization
|
|
80
|
+
self.register_localized_field('text', text_to_localize, value_format=value_format)
|
|
81
|
+
|
|
82
|
+
def _has_signal_or_variable(self) -> bool:
|
|
83
|
+
"""Check if the widget has a textsignal, textvariable, or configured textvariable.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
True if any signal/variable binding exists.
|
|
87
|
+
"""
|
|
88
|
+
if hasattr(self, '_textsignal') or hasattr(self, '_textvariable'):
|
|
89
|
+
return True
|
|
90
|
+
try:
|
|
91
|
+
textvariable_name = self.cget('textvariable')
|
|
92
|
+
return bool(textvariable_name)
|
|
93
|
+
except Exception:
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
def register_localized_field(
|
|
97
|
+
self,
|
|
98
|
+
field_name: str,
|
|
99
|
+
value: Any,
|
|
100
|
+
*,
|
|
101
|
+
value_format: str | None = None,
|
|
102
|
+
localize: bool | str | None = None,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Register a widget field for automatic localization.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
field_name: The widget field name (e.g., "text", "textvariable").
|
|
108
|
+
value: The value to localize. Can be a literal string/number or a LocalizedSpec.
|
|
109
|
+
value_format: Optional IntlFormatter spec for non-string values (e.g., 'currency').
|
|
110
|
+
localize: Override the widget's default localization mode for this field.
|
|
111
|
+
"""
|
|
112
|
+
if value is None:
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
if isinstance(value, str) and value == "":
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
localize_mode = self._localize_mode if localize is None else localize
|
|
119
|
+
|
|
120
|
+
# If already a spec, use it directly
|
|
121
|
+
if isinstance(value, LocalizedSpec):
|
|
122
|
+
self._localized_fields[field_name] = value
|
|
123
|
+
self._apply_spec_now(field_name, value)
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
if localize_mode is False:
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
# Resolve the value to a spec using core capability
|
|
130
|
+
if isinstance(value, str):
|
|
131
|
+
spec = resolve_text(value, localize_mode=localize_mode)
|
|
132
|
+
else:
|
|
133
|
+
spec = resolve_variable_text(
|
|
134
|
+
value,
|
|
135
|
+
value_format=value_format,
|
|
136
|
+
default_format=self._default_value_format or "decimal",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if spec is not None:
|
|
140
|
+
self._localized_fields[field_name] = spec
|
|
141
|
+
self._apply_spec_now(field_name, spec)
|
|
142
|
+
|
|
143
|
+
def _apply_spec_now(self, field_name: str, spec: LocalizedSpec) -> None:
|
|
144
|
+
"""Resolve a localization spec using the current locale and apply immediately.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
field_name: The widget field name to update.
|
|
148
|
+
spec: The LocalizedSpec to resolve and apply.
|
|
149
|
+
"""
|
|
150
|
+
if not spec.enabled:
|
|
151
|
+
return
|
|
152
|
+
value = apply_spec(spec)
|
|
153
|
+
self._apply_localized_value(field_name, value)
|
|
154
|
+
|
|
155
|
+
def _on_locale_changed(self, event=None):
|
|
156
|
+
"""Handle locale change events by refreshing all localized fields.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
event: The Tkinter event object (unused).
|
|
160
|
+
"""
|
|
161
|
+
self._refresh_localized_fields()
|
|
162
|
+
|
|
163
|
+
# If we have signal formatting, trigger a re-format with new locale
|
|
164
|
+
if hasattr(self, '_signal_formatter'):
|
|
165
|
+
value_format, formatter, source_signal = self._signal_formatter
|
|
166
|
+
# Re-format current value from source signal with new locale
|
|
167
|
+
formatter(source_signal.get())
|
|
168
|
+
|
|
169
|
+
def _refresh_localized_fields(self) -> None:
|
|
170
|
+
"""Refresh all registered localized fields with the current locale."""
|
|
171
|
+
for field_name, spec in self._localized_fields.items():
|
|
172
|
+
if not spec.enabled:
|
|
173
|
+
continue
|
|
174
|
+
value = apply_spec(spec)
|
|
175
|
+
self._apply_localized_value(field_name, value)
|
|
176
|
+
|
|
177
|
+
def _setup_signal_formatting(self, value_format: str) -> None:
|
|
178
|
+
"""Subscribe to textsignal and format its values.
|
|
179
|
+
|
|
180
|
+
When formatting is enabled, this creates a private textvariable for this widget
|
|
181
|
+
and subscribes to the source signal to format and display values independently.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
value_format: The format spec (e.g., 'currency', 'decimal').
|
|
185
|
+
"""
|
|
186
|
+
# Ensure textsignal exists (triggers lazy creation if needed)
|
|
187
|
+
if not hasattr(self, '_textsignal'):
|
|
188
|
+
_ = self.textsignal
|
|
189
|
+
|
|
190
|
+
# Save reference to the source signal (the one we're subscribing to)
|
|
191
|
+
source_signal = self._textsignal
|
|
192
|
+
|
|
193
|
+
# Create formatted signal using core capability
|
|
194
|
+
formatted_signal, formatter = create_formatted_signal(source_signal, value_format)
|
|
195
|
+
|
|
196
|
+
# Update our internal references to use the new private signal/variable
|
|
197
|
+
self._textsignal = formatted_signal
|
|
198
|
+
self._textvariable = formatted_signal.var
|
|
199
|
+
|
|
200
|
+
# Configure widget to use the new private variable
|
|
201
|
+
try:
|
|
202
|
+
self._ttk_base.configure(self, textvariable=self._textvariable) # type: ignore[misc]
|
|
203
|
+
except Exception:
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
# Store references for locale changes
|
|
207
|
+
self._signal_formatter = (value_format, formatter, source_signal)
|
|
208
|
+
|
|
209
|
+
def _apply_localized_value(self, field_name: str, value: str) -> None:
|
|
210
|
+
"""Apply a localized value to the widget field.
|
|
211
|
+
|
|
212
|
+
Checks for associated textvariable/variable first to ensure proper
|
|
213
|
+
integration with textsignal/signal reactive bindings. Falls back to
|
|
214
|
+
direct widget configuration if no variable is found.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
field_name: The widget field name to update.
|
|
218
|
+
value: The localized value to apply.
|
|
219
|
+
"""
|
|
220
|
+
# Check if field has a direct attribute (less common)
|
|
221
|
+
var = getattr(self, field_name, None)
|
|
222
|
+
if isinstance(var, StringVar):
|
|
223
|
+
var.set(value)
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
# Check for textvariable (for 'text' field)
|
|
227
|
+
if field_name == 'text':
|
|
228
|
+
# Try cached _textvariable first
|
|
229
|
+
if hasattr(self, '_textvariable') and self._textvariable:
|
|
230
|
+
self._textvariable.set(value)
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
# Try to get textvariable from widget configuration
|
|
234
|
+
try:
|
|
235
|
+
textvariable_name = self.cget('textvariable')
|
|
236
|
+
if textvariable_name:
|
|
237
|
+
# Get the actual variable object via the property
|
|
238
|
+
self.textvariable.set(value)
|
|
239
|
+
return
|
|
240
|
+
except Exception:
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
# Check for variable (for 'value' or other fields)
|
|
244
|
+
if hasattr(self, '_variable') and self._variable and field_name != 'text':
|
|
245
|
+
self._variable.set(value)
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
# Fallback: configure the widget option directly
|
|
249
|
+
try:
|
|
250
|
+
self.configure({field_name: value})
|
|
251
|
+
except Exception:
|
|
252
|
+
pass
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
__all__ = ["LocalizationMixin"]
|