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,425 @@
|
|
|
1
|
+
"""Localization bridge for bootstack.
|
|
2
|
+
|
|
3
|
+
This module integrates Python gettext catalogs (compiled with Babel) with
|
|
4
|
+
Tcl/Tk's native msgcat. It preserves bootstack's existing msgcat-facing
|
|
5
|
+
APIs while enabling gettext-powered translations and runtime locale switching.
|
|
6
|
+
|
|
7
|
+
Key behaviors:
|
|
8
|
+
- Prefer gettext (.mo) translations when available, with Python '%' formatting.
|
|
9
|
+
- Fall back to Tcl msgcat for formatting (supports legacy placeholders like
|
|
10
|
+
'%1$s') and for untranslated strings.
|
|
11
|
+
- Keep runtime overrides set via set/set_many in sync with msgcat and
|
|
12
|
+
consult them first during translation.
|
|
13
|
+
- Auto-discover a 'locales/' directory for catalogs unless overridden.
|
|
14
|
+
|
|
15
|
+
Public API (unchanged signatures):
|
|
16
|
+
- MessageCatalog.translate(src, *fmtargs) -> str
|
|
17
|
+
- MessageCatalog.locale(newlocale: Optional[str] = None) -> str
|
|
18
|
+
- MessageCatalog.preferences() -> list[str]
|
|
19
|
+
- MessageCatalog.load(dirname) -> int
|
|
20
|
+
- MessageCatalog.set(locale, src, translated=None) -> None
|
|
21
|
+
- MessageCatalog.set_many(locale, *args) -> int
|
|
22
|
+
- MessageCatalog.max(*src) -> int
|
|
23
|
+
- MessageCatalog.init(root=None, locales_dir=None, domain='messages',
|
|
24
|
+
default_locale='en', strip_ampersands=True) -> None
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import gettext
|
|
30
|
+
from os import PathLike
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from tkinter import Tk
|
|
33
|
+
from typing import Any, Optional, Union
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MessageCatalog:
|
|
38
|
+
"""Facade that unifies gettext and Tcl msgcat for bootstack.
|
|
39
|
+
|
|
40
|
+
Manages the active locale, a gettext translator, and runtime overrides.
|
|
41
|
+
Translation prefers gettext when available and falls back to Tcl msgcat
|
|
42
|
+
for both translation and printf-style formatting.
|
|
43
|
+
"""
|
|
44
|
+
# --- internal state for gettext bridge ---
|
|
45
|
+
_inited: bool = False
|
|
46
|
+
_locales_dir: Path | None = None
|
|
47
|
+
_domain: str = "messages"
|
|
48
|
+
_locale: str = "en"
|
|
49
|
+
_gt: gettext.NullTranslations | None = None
|
|
50
|
+
_strip_amp: bool = True
|
|
51
|
+
_emit_event: bool = True
|
|
52
|
+
_event_name: str = "<<LocaleChanged>>"
|
|
53
|
+
|
|
54
|
+
# runtime overrides (compatible with your existing set/set_many usage)
|
|
55
|
+
_overrides: dict[str, dict[str, str]] = {}
|
|
56
|
+
|
|
57
|
+
# -------------- setup -------------------------------------------------
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def init(
|
|
61
|
+
root: Tk | None = None,
|
|
62
|
+
locales_dir: Union[str, Path, None] = None,
|
|
63
|
+
domain: str = "messages",
|
|
64
|
+
default_locale: str = "en",
|
|
65
|
+
strip_ampersands: bool = True,
|
|
66
|
+
emit_virtual_event: bool = True,
|
|
67
|
+
virtual_event_name: str = "<<LocaleChanged>>",
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Initialize the translation system.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
root: Optional Tk root to ensure msgcat is available.
|
|
73
|
+
locales_dir: Base directory containing gettext catalogs
|
|
74
|
+
(`<lang>/LC_MESSAGES/<domain>.mo`). If `None`, the
|
|
75
|
+
directory is auto-discovered.
|
|
76
|
+
domain: Gettext domain name.
|
|
77
|
+
default_locale: Locale to activate after initialization.
|
|
78
|
+
strip_ampersands: If true, remove mnemonic `&` markers.
|
|
79
|
+
emit_virtual_event: If true, generate a Tk virtual event after
|
|
80
|
+
locale changes so widgets can refresh themselves.
|
|
81
|
+
virtual_event_name: The virtual event name to emit when the
|
|
82
|
+
locale changes (default `"<<LocaleChanged>>"`).
|
|
83
|
+
"""
|
|
84
|
+
# Ensure a Tk exists so msgcat calls work even if root is omitted
|
|
85
|
+
from bootstack.runtime.app import get_default_root
|
|
86
|
+
_ = root or get_default_root()
|
|
87
|
+
|
|
88
|
+
MessageCatalog._domain = domain
|
|
89
|
+
MessageCatalog._locales_dir = Path(locales_dir) if locales_dir else MessageCatalog._discover_locales_dir()
|
|
90
|
+
MessageCatalog._strip_amp = strip_ampersands
|
|
91
|
+
MessageCatalog._emit_event = bool(emit_virtual_event)
|
|
92
|
+
MessageCatalog._event_name = str(virtual_event_name or "<<LocaleChanged>>")
|
|
93
|
+
MessageCatalog._install_gettext(default_locale)
|
|
94
|
+
MessageCatalog._sync_msgcat_locale(default_locale)
|
|
95
|
+
MessageCatalog._inited = True
|
|
96
|
+
|
|
97
|
+
# -------------- core helpers -----------------------------------------
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def _install_gettext(lang: str) -> None:
|
|
101
|
+
"""Install gettext catalogs for the requested language.
|
|
102
|
+
|
|
103
|
+
Prefers an exact match (e.g. `de_DE`) and falls back to base
|
|
104
|
+
language (`de`) if available.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
lang: Requested locale code.
|
|
108
|
+
"""
|
|
109
|
+
MessageCatalog._locale = MessageCatalog._normalize_lang(lang)
|
|
110
|
+
# Try exact match, then base language (e.g., de_DE -> de)
|
|
111
|
+
langs = [MessageCatalog._locale]
|
|
112
|
+
if "_" in MessageCatalog._locale:
|
|
113
|
+
langs.append(MessageCatalog._locale.split("_", 1)[0])
|
|
114
|
+
try:
|
|
115
|
+
MessageCatalog._gt = gettext.translation(
|
|
116
|
+
MessageCatalog._domain,
|
|
117
|
+
localedir=str(MessageCatalog._locales_dir or "locales"),
|
|
118
|
+
languages=langs,
|
|
119
|
+
fallback=True,
|
|
120
|
+
)
|
|
121
|
+
except Exception:
|
|
122
|
+
# fallback=True already returns a NullTranslations if not found
|
|
123
|
+
MessageCatalog._gt = gettext.NullTranslations()
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def _discover_locales_dir() -> Path:
|
|
127
|
+
"""Return a plausible locales directory for this installation.
|
|
128
|
+
|
|
129
|
+
Priority order:
|
|
130
|
+
1) `TTKBOOTSTRAP_LOCALES` environment variable
|
|
131
|
+
2) Package asset `src/bootstack/assets/locales`
|
|
132
|
+
3) Module-local `bootstack/localization/locales`
|
|
133
|
+
4) Package-local `src/bootstack/locales`
|
|
134
|
+
5) Repository root `locales/`
|
|
135
|
+
6) Current working directory `./locales`
|
|
136
|
+
"""
|
|
137
|
+
import os
|
|
138
|
+
env = os.environ.get("TTKBOOTSTRAP_LOCALES")
|
|
139
|
+
if env:
|
|
140
|
+
p = Path(env)
|
|
141
|
+
if p.exists():
|
|
142
|
+
return p
|
|
143
|
+
here = Path(__file__).resolve()
|
|
144
|
+
candidates = [
|
|
145
|
+
here.parents[2] / "assets" / "locales", # package assets: src/bootstack/assets/locales
|
|
146
|
+
here.parent / "locales", # module-local: bootstack/localization/locales
|
|
147
|
+
here.parents[1] / "locales", # package-local: src/bootstack/locales
|
|
148
|
+
here.parents[3] / "locales", # repo root: .../bootstack/locales
|
|
149
|
+
Path.cwd() / "locales", # current working dir
|
|
150
|
+
]
|
|
151
|
+
for c in candidates:
|
|
152
|
+
try:
|
|
153
|
+
if c.exists() and c.is_dir() and any(
|
|
154
|
+
(c / d.name / "LC_MESSAGES").exists() for d in c.iterdir() if d.is_dir()):
|
|
155
|
+
return c
|
|
156
|
+
except Exception:
|
|
157
|
+
pass
|
|
158
|
+
return here.parents[3] / "locales"
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def _sync_msgcat_locale(lang: str) -> None:
|
|
162
|
+
"""Set Tcl msgcat locale to match the Python-side locale.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
lang: Locale code (e.g. `en`, `de_DE`).
|
|
166
|
+
"""
|
|
167
|
+
from bootstack.runtime.app import get_default_root
|
|
168
|
+
root = get_default_root()
|
|
169
|
+
|
|
170
|
+
tcl_lang = MessageCatalog._to_msgcat_locale(lang)
|
|
171
|
+
try:
|
|
172
|
+
root.tk.call("::msgcat::mclocale", tcl_lang)
|
|
173
|
+
except Exception:
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
@staticmethod
|
|
177
|
+
def _normalize_lang(code: str) -> str:
|
|
178
|
+
"""Normalize a locale code to gettext style (`ll` or `ll_RR`).
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
code: Input locale code (e.g. `de-de`, `pt_br`).
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Normalized locale code for gettext use.
|
|
185
|
+
"""
|
|
186
|
+
if not code:
|
|
187
|
+
return "en"
|
|
188
|
+
parts = code.replace("-", "_").split("_")
|
|
189
|
+
return parts[0].lower() if len(parts) == 1 else f"{parts[0].lower()}_{parts[1].upper()}"
|
|
190
|
+
|
|
191
|
+
@staticmethod
|
|
192
|
+
def _to_msgcat_locale(code: str) -> str:
|
|
193
|
+
"""Normalize a locale code to msgcat style (`ll` or `ll_rr`).
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
code: Input locale code (e.g. `de-DE`, `pt_BR`).
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Lowercased region code used by msgcat.
|
|
200
|
+
"""
|
|
201
|
+
parts = code.replace("-", "_").split("_")
|
|
202
|
+
return parts[0].lower() if len(parts) == 1 else f"{parts[0].lower()}_{parts[1].lower()}"
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def __join(*args: Any) -> str:
|
|
206
|
+
"""Join format args for Tcl msgcat formatting.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
*args: Positional values to forward to Tcl 'format'.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
String of brace-wrapped arguments joined by spaces.
|
|
213
|
+
"""
|
|
214
|
+
new_args = []
|
|
215
|
+
for arg in args:
|
|
216
|
+
if isinstance(arg, str):
|
|
217
|
+
stripped = str(arg).strip('"')
|
|
218
|
+
new_args.append("{%s}" % stripped)
|
|
219
|
+
else:
|
|
220
|
+
new_args.append(str(arg))
|
|
221
|
+
return " ".join(new_args)
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def _strip_ampersands(s: str) -> str:
|
|
225
|
+
"""Remove mnemonic ampersands from text.
|
|
226
|
+
|
|
227
|
+
Converts single '&' markers to nothing and turns '&&' into a
|
|
228
|
+
literal '&'. Useful for rendering toolkit-agnostic text.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
s: Input string.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Cleaned string with mnemonic indicators removed.
|
|
235
|
+
"""
|
|
236
|
+
if not s or "&" not in s:
|
|
237
|
+
return s
|
|
238
|
+
out = []
|
|
239
|
+
i = 0
|
|
240
|
+
while i < len(s):
|
|
241
|
+
if s[i] == "&":
|
|
242
|
+
if i + 1 < len(s) and s[i + 1] == "&":
|
|
243
|
+
out.append("&")
|
|
244
|
+
i += 2
|
|
245
|
+
else:
|
|
246
|
+
i += 1 # skip mnemonic marker
|
|
247
|
+
else:
|
|
248
|
+
out.append(s[i])
|
|
249
|
+
i += 1
|
|
250
|
+
return "".join(out)
|
|
251
|
+
|
|
252
|
+
# -------------- public API (unchanged signatures) ---------------------
|
|
253
|
+
|
|
254
|
+
@staticmethod
|
|
255
|
+
def translate(src: str, *fmtargs: Any) -> str:
|
|
256
|
+
"""Translate a message id according to the active locale.
|
|
257
|
+
|
|
258
|
+
Strategy:
|
|
259
|
+
1) If a runtime override exists and formatting args are provided,
|
|
260
|
+
use Tcl msgcat so legacy '%1$s' placeholders work.
|
|
261
|
+
2) Otherwise use overrides (with Python '%' formatting when args
|
|
262
|
+
are given).
|
|
263
|
+
3) Next, try gettext; if Python '%' formatting fails, fall back
|
|
264
|
+
to Tcl formatting.
|
|
265
|
+
4) Finally, fall back entirely to Tcl msgcat.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
src: Message id to translate.
|
|
269
|
+
*fmtargs: Positional formatting values.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Localized and formatted string.
|
|
273
|
+
"""
|
|
274
|
+
from bootstack.runtime.app import get_default_root
|
|
275
|
+
root = get_default_root()
|
|
276
|
+
|
|
277
|
+
# Fast-path: if an override exists for this locale and formatting args were
|
|
278
|
+
# provided, prefer Tcl msgcat formatting so positional specifiers like %1$s
|
|
279
|
+
# work as in legacy behavior.
|
|
280
|
+
cur = MessageCatalog._locale
|
|
281
|
+
if fmtargs and cur in MessageCatalog._overrides and src in MessageCatalog._overrides[cur]:
|
|
282
|
+
command = f"::msgcat::mc {{{src}}} {MessageCatalog.__join(*fmtargs)}"
|
|
283
|
+
out = root.tk.eval(command)
|
|
284
|
+
return MessageCatalog._strip_ampersands(out) if MessageCatalog._strip_amp else out
|
|
285
|
+
|
|
286
|
+
# 1) overrides for current locale win first
|
|
287
|
+
cur = MessageCatalog._locale
|
|
288
|
+
if cur in MessageCatalog._overrides and src in MessageCatalog._overrides[cur]:
|
|
289
|
+
s = MessageCatalog._overrides[cur][src]
|
|
290
|
+
if MessageCatalog._strip_amp:
|
|
291
|
+
s = MessageCatalog._strip_ampersands(s)
|
|
292
|
+
# try Python formatting if args were passed; ignore on failure
|
|
293
|
+
if fmtargs:
|
|
294
|
+
try:
|
|
295
|
+
s = s % fmtargs
|
|
296
|
+
return s
|
|
297
|
+
except Exception:
|
|
298
|
+
pass
|
|
299
|
+
# no args or failed → return override as-is
|
|
300
|
+
return s
|
|
301
|
+
|
|
302
|
+
# 2) gettext translation (if inited)
|
|
303
|
+
if MessageCatalog._inited and MessageCatalog._gt is not None:
|
|
304
|
+
try:
|
|
305
|
+
s = MessageCatalog._gt.gettext(src)
|
|
306
|
+
# If gettext returns src unchanged, and we have no fmtargs,
|
|
307
|
+
# we'll consider falling back to msgcat for consistency.
|
|
308
|
+
if s != src:
|
|
309
|
+
if MessageCatalog._strip_amp:
|
|
310
|
+
s = MessageCatalog._strip_ampersands(s)
|
|
311
|
+
if fmtargs:
|
|
312
|
+
try:
|
|
313
|
+
return s % fmtargs
|
|
314
|
+
except Exception:
|
|
315
|
+
# fall through to Tcl formatting
|
|
316
|
+
pass
|
|
317
|
+
else:
|
|
318
|
+
return s
|
|
319
|
+
except Exception:
|
|
320
|
+
# ignore and fall back to msgcat
|
|
321
|
+
pass
|
|
322
|
+
|
|
323
|
+
# 3) Tcl msgcat fallback (preserves your current behavior exactly)
|
|
324
|
+
command = f"::msgcat::mc {{{src}}}"
|
|
325
|
+
if fmtargs:
|
|
326
|
+
command = f"{command} {MessageCatalog.__join(*fmtargs)}"
|
|
327
|
+
out = root.tk.eval(command)
|
|
328
|
+
return MessageCatalog._strip_ampersands(out) if MessageCatalog._strip_amp else out
|
|
329
|
+
|
|
330
|
+
@staticmethod
|
|
331
|
+
def locale(new_locale: Optional[str] = None) -> str:
|
|
332
|
+
"""Get or set the current locale.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
new_locale: If provided, switch both gettext and msgcat locales.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
The active normalized locale code (or Tcl's current code when
|
|
339
|
+
queried).
|
|
340
|
+
"""
|
|
341
|
+
from bootstack.runtime.app import get_default_root
|
|
342
|
+
root = get_default_root()
|
|
343
|
+
if new_locale:
|
|
344
|
+
# switch gettext + msgcat
|
|
345
|
+
MessageCatalog._install_gettext(new_locale)
|
|
346
|
+
MessageCatalog._sync_msgcat_locale(new_locale)
|
|
347
|
+
# notify listeners (optional)
|
|
348
|
+
try:
|
|
349
|
+
if MessageCatalog._emit_event:
|
|
350
|
+
root.event_generate(MessageCatalog._event_name, data={"locale": new_locale}, when="tail")
|
|
351
|
+
except Exception:
|
|
352
|
+
pass
|
|
353
|
+
return MessageCatalog._locale
|
|
354
|
+
# query Tcl msgcat current locale
|
|
355
|
+
return root.tk.eval("::msgcat::mclocale")
|
|
356
|
+
|
|
357
|
+
@staticmethod
|
|
358
|
+
def preferences() -> list[str]:
|
|
359
|
+
"""Return Tcl msgcat locale preferences (ordered)."""
|
|
360
|
+
from bootstack.runtime.app import get_default_root
|
|
361
|
+
root = get_default_root()
|
|
362
|
+
items = root.tk.eval("::msgcat::mcpreferences").split(" ")
|
|
363
|
+
return items[0:-1] if len(items) > 0 else []
|
|
364
|
+
|
|
365
|
+
@staticmethod
|
|
366
|
+
def load(dirname: Union[str, PathLike[str]]) -> int:
|
|
367
|
+
"""Load Tcl .msg catalogs from a directory.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
dirname: Directory containing msgcat .msg files.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Number of files loaded, as reported by Tcl.
|
|
374
|
+
"""
|
|
375
|
+
msgs = Path(dirname).as_posix()
|
|
376
|
+
from bootstack.runtime.app import get_default_root
|
|
377
|
+
root = get_default_root()
|
|
378
|
+
return int(root.tk.eval(f"::msgcat::mcload [list {msgs}]"))
|
|
379
|
+
|
|
380
|
+
@staticmethod
|
|
381
|
+
def set(locale: str, src: str, translated: Optional[str] = None) -> None:
|
|
382
|
+
"""Define a single runtime translation and mirror it into msgcat.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
locale: Target locale code.
|
|
386
|
+
src: Message id.
|
|
387
|
+
translated: Localized string.
|
|
388
|
+
"""
|
|
389
|
+
loc = MessageCatalog._normalize_lang(locale)
|
|
390
|
+
MessageCatalog._overrides.setdefault(loc, {})[src] = translated or ""
|
|
391
|
+
from bootstack.runtime.app import get_default_root
|
|
392
|
+
root = get_default_root()
|
|
393
|
+
root.tk.eval("::msgcat::mcset %s {%s} {%s}" % (MessageCatalog._to_msgcat_locale(locale), src, translated or ""))
|
|
394
|
+
|
|
395
|
+
@staticmethod
|
|
396
|
+
def set_many(locale: str, *args: str) -> int:
|
|
397
|
+
"""Bulk-define runtime translations and mirror into msgcat.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
locale: Target locale code.
|
|
401
|
+
*args: Alternating message ids and translations.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Number of messages set, as reported by Tcl.
|
|
405
|
+
"""
|
|
406
|
+
loc = MessageCatalog._normalize_lang(locale)
|
|
407
|
+
# update Python overrides
|
|
408
|
+
pairs = list(args)
|
|
409
|
+
for i in range(0, len(pairs), 2):
|
|
410
|
+
k = pairs[i]
|
|
411
|
+
v = pairs[i + 1] if i + 1 < len(pairs) else ""
|
|
412
|
+
MessageCatalog._overrides.setdefault(loc, {})[k] = v
|
|
413
|
+
|
|
414
|
+
# update Tcl msgcat
|
|
415
|
+
from bootstack.runtime.app import get_default_root
|
|
416
|
+
root = get_default_root()
|
|
417
|
+
messages = " ".join(["{%s}" % x for x in args])
|
|
418
|
+
out = f"::msgcat::mcmset {MessageCatalog._to_msgcat_locale(locale)} {{{messages}}}"
|
|
419
|
+
return int(root.tk.eval(out))
|
|
420
|
+
|
|
421
|
+
@staticmethod
|
|
422
|
+
def max(*src: str) -> int:
|
|
423
|
+
from bootstack.runtime.app import get_default_root
|
|
424
|
+
root = get_default_root()
|
|
425
|
+
return int(root.tk.eval(f"::msgcat::mcmax {' '.join(src)}"))
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Mapping, Optional, Tuple, Union
|
|
5
|
+
|
|
6
|
+
from .msgcat import MessageCatalog
|
|
7
|
+
from .intl_format import IntlFormatter, FormatSpec
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LocalizedSpec:
|
|
11
|
+
"""Base class for all localization specifications.
|
|
12
|
+
|
|
13
|
+
Localization specs define how text or values should be localized when
|
|
14
|
+
the locale changes. Subclasses implement specific localization strategies.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
enabled: Whether this spec is currently enabled for localization.
|
|
18
|
+
"""
|
|
19
|
+
enabled: bool = True
|
|
20
|
+
|
|
21
|
+
def resolve(self, locale: str) -> str:
|
|
22
|
+
"""Resolve this spec to a localized string for the given locale.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
locale: The locale code to use for resolution.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
The localized string value.
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
NotImplementedError: Subclasses must implement this method.
|
|
32
|
+
"""
|
|
33
|
+
raise NotImplementedError("Subclasses must implement resolve().")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class LocalizedTextSpec(LocalizedSpec):
|
|
38
|
+
"""Localization spec for translatable text via MessageCatalog.
|
|
39
|
+
|
|
40
|
+
This spec translates text using gettext catalogs and MessageCatalog,
|
|
41
|
+
with optional format arguments for interpolation.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
key: The message ID or semantic key for translation lookup.
|
|
45
|
+
fmtargs: Tuple of formatting arguments for MessageCatalog interpolation.
|
|
46
|
+
original: Fallback literal text if translation fails.
|
|
47
|
+
enabled: Whether this spec is currently enabled for localization.
|
|
48
|
+
"""
|
|
49
|
+
key: str
|
|
50
|
+
fmtargs: Tuple[Any, ...] = ()
|
|
51
|
+
original: Optional[str] = None
|
|
52
|
+
|
|
53
|
+
enabled: bool = True
|
|
54
|
+
|
|
55
|
+
def resolve(self, locale: str) -> str:
|
|
56
|
+
"""Resolve to translated text using MessageCatalog.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
locale: The locale code (unused, MessageCatalog uses its own state).
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The translated string, or the original/key as fallback.
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
return MessageCatalog.translate(self.key, *self.fmtargs)
|
|
66
|
+
except Exception:
|
|
67
|
+
return self.original or self.key
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class LocalizedValueSpec(LocalizedSpec):
|
|
72
|
+
"""Localization spec for locale-aware value formatting.
|
|
73
|
+
|
|
74
|
+
This spec formats numbers, dates, times, and currency values using
|
|
75
|
+
IntlFormatter according to the current locale.
|
|
76
|
+
|
|
77
|
+
Attributes:
|
|
78
|
+
value: The value to format (number, date, datetime, time, etc.).
|
|
79
|
+
format_spec: IntlFormatter spec such as "currency", "decimal", "percent",
|
|
80
|
+
or a dict with formatting options.
|
|
81
|
+
enabled: Whether this spec is currently enabled for localization.
|
|
82
|
+
"""
|
|
83
|
+
value: Any
|
|
84
|
+
format_spec: FormatSpec
|
|
85
|
+
|
|
86
|
+
enabled: bool = True
|
|
87
|
+
|
|
88
|
+
def resolve(self, locale: str) -> str:
|
|
89
|
+
"""Resolve to a locale-formatted string using IntlFormatter.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
locale: The locale code (unused, uses MessageCatalog's current locale).
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
The formatted string, or str(value) as fallback.
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
current_locale = MessageCatalog.locale()
|
|
99
|
+
fmt = IntlFormatter(locale=current_locale)
|
|
100
|
+
return fmt.format(self.value, self.format_spec)
|
|
101
|
+
except Exception:
|
|
102
|
+
return str(self.value)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def L(key: str, *fmtargs: Any) -> LocalizedTextSpec:
|
|
106
|
+
"""Create a LocalizedTextSpec for translatable text.
|
|
107
|
+
|
|
108
|
+
Shorthand constructor that creates a text localization spec with the
|
|
109
|
+
translation key and optional format arguments.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
key: The message ID or semantic key for translation lookup.
|
|
113
|
+
*fmtargs: Optional formatting arguments for string interpolation.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
A LocalizedTextSpec instance.
|
|
117
|
+
|
|
118
|
+
Examples:
|
|
119
|
+
>>> spec = L("greeting", "World")
|
|
120
|
+
>>> # Will translate "greeting" with "World" as format argument
|
|
121
|
+
"""
|
|
122
|
+
return LocalizedTextSpec(key=key, fmtargs=fmtargs, original=key)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def LV(value: Any, format_spec: FormatSpec) -> LocalizedValueSpec:
|
|
126
|
+
"""Create a LocalizedValueSpec for locale-aware value formatting.
|
|
127
|
+
|
|
128
|
+
Shorthand constructor that creates a value formatting spec for numbers,
|
|
129
|
+
dates, times, and currency values.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
value: The value to format (number, date, datetime, time, etc.).
|
|
133
|
+
format_spec: IntlFormatter spec like "currency", "decimal", "percent",
|
|
134
|
+
or a dict with detailed formatting options.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
A LocalizedValueSpec instance.
|
|
138
|
+
|
|
139
|
+
Examples:
|
|
140
|
+
>>> spec = LV(1234.56, "currency")
|
|
141
|
+
>>> # Will format as currency in the current locale
|
|
142
|
+
"""
|
|
143
|
+
return LocalizedValueSpec(value=value, format_spec=format_spec)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Widget capability and TTK state mixins for bootstack widgets."""
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Iterable, Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TtkStateMixin:
|
|
7
|
+
"""ttk-only widget helpers.
|
|
8
|
+
|
|
9
|
+
This mixin contains methods that exist on ttk widgets but not on classic Tk widgets
|
|
10
|
+
(e.g., `tk.Text`, `tk.Canvas`).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def state(self, statespec: str | Iterable[str] | None = None) -> Any:
|
|
14
|
+
"""Get or modify the ttk state of the widget.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
statespec: State specification (e.g. ("disabled",) or ("!disabled",)).
|
|
18
|
+
If None, returns the current state.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
The current state (getter) or an implementation-dependent result.
|
|
22
|
+
"""
|
|
23
|
+
return super().state(statespec) # type: ignore[misc]
|
|
24
|
+
|
|
25
|
+
def instate(self, statespec: str | Iterable[str], callback: Any = None) -> bool:
|
|
26
|
+
"""Test the ttk state of the widget.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
statespec: State specification to test.
|
|
30
|
+
callback: Optional callable invoked if the test succeeds.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
True if the widget matches the state spec; otherwise False.
|
|
34
|
+
"""
|
|
35
|
+
return super().instate(statespec, callback) # type: ignore[misc]
|