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,358 @@
|
|
|
1
|
+
"""FilterDialog - A dialog for filtering and selecting multiple items from a list.
|
|
2
|
+
|
|
3
|
+
This module provides a multi-select dialog with optional search and select-all
|
|
4
|
+
functionality. Items can be displayed with checkboxes, and the dialog supports
|
|
5
|
+
both standard and frameless (borderless) display modes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from tkinter import Widget
|
|
9
|
+
from typing import Any, Callable, Literal, Optional, Tuple, Union
|
|
10
|
+
from types import SimpleNamespace
|
|
11
|
+
|
|
12
|
+
from bootstack.widgets.primitives import CheckButton, Frame, Label, Separator
|
|
13
|
+
from bootstack.widgets.types import Master
|
|
14
|
+
from bootstack.widgets.composites.textentry import TextEntry
|
|
15
|
+
from bootstack.runtime.app import Window
|
|
16
|
+
from bootstack.dialogs import Dialog, DialogButton
|
|
17
|
+
from bootstack.widgets.composites.scrollview import ScrollView
|
|
18
|
+
from bootstack.runtime.window_utilities import AnchorPoint
|
|
19
|
+
|
|
20
|
+
ttk = SimpleNamespace(
|
|
21
|
+
Checkbutton=CheckButton,
|
|
22
|
+
Frame=Frame,
|
|
23
|
+
Label=Label,
|
|
24
|
+
Separator=Separator,
|
|
25
|
+
TextEntry=TextEntry,
|
|
26
|
+
Window=Window,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FilterDialogContent(ttk.Frame):
|
|
31
|
+
"""Internal content frame for FilterDialog.
|
|
32
|
+
|
|
33
|
+
This frame contains the search box, select-all checkbox, and scrollable
|
|
34
|
+
list of checkboxes for filtering items.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
master: Master = None,
|
|
40
|
+
enable_search: bool = False,
|
|
41
|
+
enable_select_all: bool = False,
|
|
42
|
+
items: list[str | dict[str, Any]] = None
|
|
43
|
+
):
|
|
44
|
+
"""Initialize the FilterDialogContent frame.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
master: Parent widget. If None, uses the default root window.
|
|
48
|
+
enable_search: If True, includes a search box to filter items by text.
|
|
49
|
+
enable_select_all: If True, includes a "Select All" checkbox.
|
|
50
|
+
items: List of items to display. Can be strings or dicts with keys:
|
|
51
|
+
- text (str): Display text (required for dict items)
|
|
52
|
+
- value (Any): Value to return when selected (defaults to text)
|
|
53
|
+
- selected (bool): Initial selection state (defaults to False)
|
|
54
|
+
"""
|
|
55
|
+
super().__init__(master)
|
|
56
|
+
self._enable_search = enable_search
|
|
57
|
+
self._enable_select_all = enable_select_all
|
|
58
|
+
self._selected_items = []
|
|
59
|
+
self._items = self._normalize_items(items)
|
|
60
|
+
self._filter: str | None = None
|
|
61
|
+
self._check_buttons: dict[str, ttk.Checkbutton] = {}
|
|
62
|
+
self._select_all_cb: ttk.Checkbutton | None = None
|
|
63
|
+
self._scroll_container: ttk.Frame | None = None
|
|
64
|
+
self._build_content()
|
|
65
|
+
|
|
66
|
+
def _normalize_items(self, items: list[str | dict[str, Any]] | None) -> list[dict[str, Any]]:
|
|
67
|
+
"""Normalize items to a consistent dict format.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
items: List of strings or dicts representing items.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
List of dicts with 'text', 'value', and 'selected' keys.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
ValueError: If a dict item is missing the 'text' key.
|
|
77
|
+
"""
|
|
78
|
+
if items is None:
|
|
79
|
+
return []
|
|
80
|
+
|
|
81
|
+
out = []
|
|
82
|
+
for item in items:
|
|
83
|
+
if isinstance(item, str):
|
|
84
|
+
out.append(dict(text=item, value=item, selected=False))
|
|
85
|
+
else:
|
|
86
|
+
if 'text' not in item:
|
|
87
|
+
raise ValueError('Item must have "text" key')
|
|
88
|
+
|
|
89
|
+
if 'selected' not in item:
|
|
90
|
+
item['selected'] = False
|
|
91
|
+
item.setdefault('value', item['text'])
|
|
92
|
+
if item['selected']:
|
|
93
|
+
self._selected_items.append(item['value'])
|
|
94
|
+
out.append(item)
|
|
95
|
+
return out
|
|
96
|
+
|
|
97
|
+
def _build_content(self):
|
|
98
|
+
"""Build and layout all UI components."""
|
|
99
|
+
# Search box
|
|
100
|
+
if self._enable_search:
|
|
101
|
+
search_entry = ttk.TextEntry(self)
|
|
102
|
+
search_entry.insert_addon(ttk.Label, 'before', icon='search')
|
|
103
|
+
search_entry.pack(fill='x')
|
|
104
|
+
search_entry.on_input(self._on_search_input)
|
|
105
|
+
|
|
106
|
+
# Select all checkbox
|
|
107
|
+
if self._enable_select_all:
|
|
108
|
+
self._select_all_cb = ttk.Checkbutton(self, text='edit.select_all')
|
|
109
|
+
self._select_all_cb.invoke()
|
|
110
|
+
self._select_all_cb.invoke()
|
|
111
|
+
self._select_all_cb['command'] = self._handle_select_all
|
|
112
|
+
self._select_all_cb.pack(fill='x', padx=8, pady=(12, 8))
|
|
113
|
+
ttk.Separator(self).pack(fill='x')
|
|
114
|
+
|
|
115
|
+
# Scrollable container for checkboxes
|
|
116
|
+
scroll_view = ScrollView(
|
|
117
|
+
self,
|
|
118
|
+
scroll_direction='vertical',
|
|
119
|
+
scrollbar_visibility='always'
|
|
120
|
+
)
|
|
121
|
+
scroll_view.pack(fill='both', expand=False, pady=(8, 0))
|
|
122
|
+
scroll_view.configure(height=230)
|
|
123
|
+
|
|
124
|
+
self._scroll_container = ttk.Frame(scroll_view.canvas)
|
|
125
|
+
scroll_view.add(self._scroll_container)
|
|
126
|
+
|
|
127
|
+
# Stretch container frame to canvas width for full-width checkboxes
|
|
128
|
+
def _configure_scroll_width(event):
|
|
129
|
+
scroll_view.canvas.itemconfig(scroll_view._window_id, width=event.width)
|
|
130
|
+
scroll_view.canvas.bind('<Configure>', _configure_scroll_width)
|
|
131
|
+
|
|
132
|
+
# Add item checkboxes
|
|
133
|
+
for item in self._items:
|
|
134
|
+
cb = ttk.Checkbutton(self._scroll_container, text=item['text'])
|
|
135
|
+
cb.invoke()
|
|
136
|
+
if not item['selected']:
|
|
137
|
+
cb.invoke()
|
|
138
|
+
cb['command'] = lambda value=item['value']: self._on_item_clicked(value)
|
|
139
|
+
cb.pack(fill='x', padx=8, pady=8)
|
|
140
|
+
self._check_buttons[item['text']] = cb
|
|
141
|
+
|
|
142
|
+
def _handle_select_all(self):
|
|
143
|
+
"""Toggle selection state of all checkboxes."""
|
|
144
|
+
if 'selected' in self._select_all_cb.state():
|
|
145
|
+
for item in self._check_buttons.values():
|
|
146
|
+
item.instate(['!selected'], item.invoke)
|
|
147
|
+
else:
|
|
148
|
+
for item in self._check_buttons.values():
|
|
149
|
+
item.instate(['selected'], item.invoke)
|
|
150
|
+
|
|
151
|
+
def _on_search_input(self, event):
|
|
152
|
+
"""Filter visible checkboxes based on search text."""
|
|
153
|
+
self._filter = (event.data['text'] or '').lower()
|
|
154
|
+
|
|
155
|
+
for key, cb in self._check_buttons.items():
|
|
156
|
+
if self._filter in key.lower():
|
|
157
|
+
cb.pack(fill='x', padx=8, pady=8)
|
|
158
|
+
else:
|
|
159
|
+
cb.pack_forget()
|
|
160
|
+
|
|
161
|
+
def _on_item_clicked(self, value):
|
|
162
|
+
"""Update selected items list when a checkbox is clicked."""
|
|
163
|
+
if value in self._selected_items:
|
|
164
|
+
self._selected_items.remove(value)
|
|
165
|
+
else:
|
|
166
|
+
self._selected_items.append(value)
|
|
167
|
+
|
|
168
|
+
def get_selected_items(self):
|
|
169
|
+
"""Return the list of selected item values."""
|
|
170
|
+
return self._selected_items
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class FilterDialog(ttk.Frame):
|
|
174
|
+
"""A dialog for filtering and selecting multiple items from a list.
|
|
175
|
+
|
|
176
|
+
This dialog displays a list of checkboxes with optional search and select-all
|
|
177
|
+
functionality. When the user clicks OK, the selected values are stored in
|
|
178
|
+
the result property.
|
|
179
|
+
|
|
180
|
+
!!! note "Events"
|
|
181
|
+
|
|
182
|
+
`<<SelectionChange>>`: Triggered when OK is clicked and selections are confirmed.
|
|
183
|
+
Provides `event.data` with key: `selected` (list[Any]).
|
|
184
|
+
|
|
185
|
+
Attributes:
|
|
186
|
+
result (list[Any] | None): List of selected values after dialog is closed,
|
|
187
|
+
or None if canceled.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
def __init__(
|
|
191
|
+
self,
|
|
192
|
+
master: Master = None,
|
|
193
|
+
title: str = "Filter",
|
|
194
|
+
items: list[str | dict[str, Any]] = None,
|
|
195
|
+
enable_search: bool = False,
|
|
196
|
+
enable_select_all: bool = False,
|
|
197
|
+
frameless: bool = False
|
|
198
|
+
):
|
|
199
|
+
"""Initialize the FilterDialog.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
master: Parent widget. If None, uses the default root window.
|
|
203
|
+
title: Dialog window title.
|
|
204
|
+
items: List of items to display. Can be strings or dicts with keys:
|
|
205
|
+
- text (str): Display text (required for dict items)
|
|
206
|
+
- value (Any): Value to return when selected (defaults to text)
|
|
207
|
+
- selected (bool): Initial selection state (defaults to False)
|
|
208
|
+
enable_search: If True, includes a search box to filter items by text.
|
|
209
|
+
enable_select_all: If True, includes a "Select All" checkbox.
|
|
210
|
+
frameless: If True, removes window decorations and displays with a
|
|
211
|
+
border frame. Enables dismiss-on-outside-click behavior.
|
|
212
|
+
"""
|
|
213
|
+
super().__init__(master)
|
|
214
|
+
self._master = master if master else self.master
|
|
215
|
+
self._title = title
|
|
216
|
+
self._items = items or []
|
|
217
|
+
self._enable_search = enable_search
|
|
218
|
+
self._enable_select_all = enable_select_all
|
|
219
|
+
self._frameless = frameless
|
|
220
|
+
self._content: FilterDialogContent | None = None
|
|
221
|
+
self._dialog: Dialog | None = None
|
|
222
|
+
self.result: list[Any] | None = None
|
|
223
|
+
|
|
224
|
+
def _build_content(self, parent):
|
|
225
|
+
"""Build the dialog content with FilterDialogContent."""
|
|
226
|
+
self._content = FilterDialogContent(
|
|
227
|
+
master=parent,
|
|
228
|
+
enable_search=self._enable_search,
|
|
229
|
+
enable_select_all=self._enable_select_all,
|
|
230
|
+
items=self._items
|
|
231
|
+
)
|
|
232
|
+
padding = 0 if self._frameless else 10
|
|
233
|
+
self._content.pack(fill='both', expand=True, padx=padding, pady=padding)
|
|
234
|
+
|
|
235
|
+
# Setup dismiss-on-click for frameless after dialog is built
|
|
236
|
+
if self._frameless:
|
|
237
|
+
parent.after(10, self._setup_frameless_dismiss)
|
|
238
|
+
|
|
239
|
+
def _on_ok(self, dialog: Dialog):
|
|
240
|
+
"""Save selected items to result when OK button is clicked."""
|
|
241
|
+
if self._content:
|
|
242
|
+
self.result = self._content.get_selected_items()
|
|
243
|
+
# Generate selection changed event on confirmation
|
|
244
|
+
if self.result is not None:
|
|
245
|
+
self.event_generate('<<SelectionChange>>', data={"selected": self.result.copy()})
|
|
246
|
+
|
|
247
|
+
def on_selection_changed(self, callback: Callable) -> str:
|
|
248
|
+
"""Bind to `<<SelectionChange>>`. Callback receives `event.data = {"selected": list[Any]}`.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Binding identifier for use with off_selection_changed().
|
|
252
|
+
"""
|
|
253
|
+
return self.bind('<<SelectionChange>>', callback, add=True)
|
|
254
|
+
|
|
255
|
+
def off_selection_changed(self, bind_id: str = None):
|
|
256
|
+
"""Unbind callback from <<SelectionChange>> event.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
bind_id: Binding identifier from on_selection_changed().
|
|
260
|
+
"""
|
|
261
|
+
self.unbind('<<SelectionChange>>', bind_id)
|
|
262
|
+
|
|
263
|
+
def _on_click(self, event):
|
|
264
|
+
"""Check if click is outside dialog and dismiss if so."""
|
|
265
|
+
if not self._dialog or not self._dialog.toplevel:
|
|
266
|
+
return
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
# Get dialog window bounds
|
|
270
|
+
x = self._dialog.toplevel.winfo_rootx()
|
|
271
|
+
y = self._dialog.toplevel.winfo_rooty()
|
|
272
|
+
w = self._dialog.toplevel.winfo_width()
|
|
273
|
+
h = self._dialog.toplevel.winfo_height()
|
|
274
|
+
|
|
275
|
+
# Check if click is outside dialog
|
|
276
|
+
if not (x <= event.x_root <= x + w and y <= event.y_root <= y + h):
|
|
277
|
+
self._dialog.toplevel.destroy()
|
|
278
|
+
except:
|
|
279
|
+
pass
|
|
280
|
+
|
|
281
|
+
def _setup_frameless_dismiss(self):
|
|
282
|
+
"""Setup dismiss-on-outside-click for frameless dialogs."""
|
|
283
|
+
if self._frameless and self._dialog and self._dialog.toplevel:
|
|
284
|
+
# Bind to root window to catch all clicks
|
|
285
|
+
root = self._dialog.toplevel.winfo_toplevel()
|
|
286
|
+
while root.master:
|
|
287
|
+
root = root.master.winfo_toplevel()
|
|
288
|
+
root.bind('<Button-1>', self._on_click, add='+')
|
|
289
|
+
|
|
290
|
+
def show(
|
|
291
|
+
self,
|
|
292
|
+
position: Optional[Tuple[int, int]] = None,
|
|
293
|
+
modal: Optional[bool] = None,
|
|
294
|
+
*,
|
|
295
|
+
anchor_to: Optional[Union[Widget, Literal["screen", "cursor", "parent"]]] = None,
|
|
296
|
+
anchor_point: AnchorPoint = 'center',
|
|
297
|
+
window_point: AnchorPoint = 'center',
|
|
298
|
+
offset: Tuple[int, int] = (0, 0),
|
|
299
|
+
auto_flip: Union[bool, Literal['vertical', 'horizontal']] = False
|
|
300
|
+
) -> Optional[list[Any]]:
|
|
301
|
+
"""Show the dialog and return the selected items.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
position: Optional (x, y) coordinates to position the dialog.
|
|
305
|
+
If provided, takes precedence over anchor-based positioning.
|
|
306
|
+
modal: Override the mode's default modality.
|
|
307
|
+
If None, uses True (modal mode).
|
|
308
|
+
anchor_to: Positioning target. Can be:
|
|
309
|
+
- Widget: Anchor to a specific widget
|
|
310
|
+
- "screen": Anchor to screen edges/corners
|
|
311
|
+
- "cursor": Anchor to mouse cursor location
|
|
312
|
+
- "parent": Anchor to parent window (same as widget)
|
|
313
|
+
- None: Centers on parent (default)
|
|
314
|
+
anchor_point: Point on the anchor target (n, s, e, w, ne, nw, se, sw, center).
|
|
315
|
+
Default 'center'.
|
|
316
|
+
window_point: Point on the dialog window (n, s, e, w, ne, nw, se, sw, center).
|
|
317
|
+
Default 'center'.
|
|
318
|
+
offset: Additional (x, y) offset in pixels from the anchor position.
|
|
319
|
+
auto_flip: Smart positioning to keep window on screen.
|
|
320
|
+
- False: No flipping (default)
|
|
321
|
+
- True: Flip both vertically and horizontally as needed
|
|
322
|
+
- 'vertical': Only flip up/down
|
|
323
|
+
- 'horizontal': Only flip left/right
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
List of selected item values, or None if canceled.
|
|
327
|
+
"""
|
|
328
|
+
self._dialog: Dialog = Dialog(
|
|
329
|
+
master=self._master,
|
|
330
|
+
title=self._title,
|
|
331
|
+
content_builder=self._build_content,
|
|
332
|
+
buttons=[
|
|
333
|
+
DialogButton(text="button.cancel", role="cancel", result=None),
|
|
334
|
+
DialogButton(
|
|
335
|
+
text="button.ok",
|
|
336
|
+
role="primary",
|
|
337
|
+
result=True,
|
|
338
|
+
default=True,
|
|
339
|
+
command=self._on_ok
|
|
340
|
+
)
|
|
341
|
+
],
|
|
342
|
+
minsize=(250, 200),
|
|
343
|
+
maxsize=(250, 380),
|
|
344
|
+
resizable=(False, True),
|
|
345
|
+
mode="modal",
|
|
346
|
+
frameless=self._frameless
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
self._dialog.show(
|
|
350
|
+
position=position,
|
|
351
|
+
modal=modal,
|
|
352
|
+
anchor_to=anchor_to,
|
|
353
|
+
anchor_point=anchor_point,
|
|
354
|
+
window_point=window_point,
|
|
355
|
+
offset=offset,
|
|
356
|
+
auto_flip=auto_flip
|
|
357
|
+
)
|
|
358
|
+
return self.result
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"""FontDialog implementation for selecting and previewing fonts."""
|
|
2
|
+
|
|
3
|
+
import tkinter
|
|
4
|
+
from tkinter import Text, Variable, font
|
|
5
|
+
from types import SimpleNamespace
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
from typing_extensions import Unpack
|
|
9
|
+
|
|
10
|
+
from bootstack.constants import *
|
|
11
|
+
from bootstack.core.localization import MessageCatalog
|
|
12
|
+
from bootstack.dialogs.dialog import Dialog, DialogButton, ShowOptions
|
|
13
|
+
from bootstack.runtime.app import Window
|
|
14
|
+
from bootstack.runtime.utility import scale_size
|
|
15
|
+
from bootstack.style.style import get_style
|
|
16
|
+
from bootstack.widgets.primitives import (
|
|
17
|
+
CheckButton,
|
|
18
|
+
Frame,
|
|
19
|
+
Label,
|
|
20
|
+
LabelFrame,
|
|
21
|
+
RadioButton,
|
|
22
|
+
Scrollbar,
|
|
23
|
+
TreeView,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
ttk = SimpleNamespace(
|
|
27
|
+
Checkbutton=CheckButton,
|
|
28
|
+
Frame=Frame,
|
|
29
|
+
Label=Label,
|
|
30
|
+
Labelframe=LabelFrame,
|
|
31
|
+
Radiobutton=RadioButton,
|
|
32
|
+
Scrollbar=Scrollbar,
|
|
33
|
+
Treeview=TreeView,
|
|
34
|
+
Text=Text,
|
|
35
|
+
Variable=Variable,
|
|
36
|
+
Window=Window,
|
|
37
|
+
use_style=get_style,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class FontDialog:
|
|
42
|
+
"""A dialog for selecting and previewing fonts.
|
|
43
|
+
|
|
44
|
+
This dialog provides a comprehensive interface for selecting fonts,
|
|
45
|
+
including family, size, weight, slant, and effects (underline, overstrike).
|
|
46
|
+
The selected font is returned as a `tkinter.font.Font` object when OK is
|
|
47
|
+
pressed, or None if canceled.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
result (font.Font | None): The selected font, or None if canceled.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
title: str = "font.selector", # Use semantic key as default
|
|
56
|
+
master: Optional[tkinter.Misc] = None,
|
|
57
|
+
default_font: str = "TkDefaultFont"
|
|
58
|
+
):
|
|
59
|
+
"""Create a font selection dialog.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
title: The dialog window title. Will be localized automatically.
|
|
63
|
+
Defaults to semantic key 'font.selector'.
|
|
64
|
+
master: Parent widget. The dialog will be modal and centered on this widget.
|
|
65
|
+
default_font: Name of the initial font to display. Can be any valid tkinter
|
|
66
|
+
font name (e.g., 'TkDefaultFont', 'TkFixedFont', 'TkTextFont',
|
|
67
|
+
'TkHeadingFont', etc.) or a custom font name.
|
|
68
|
+
"""
|
|
69
|
+
# Title is now automatically localized by BaseWindow._setup_window
|
|
70
|
+
self._style = ttk.use_style()
|
|
71
|
+
self._default = font.nametofont(default_font)
|
|
72
|
+
self._actual = self._default.actual()
|
|
73
|
+
self._size = ttk.Variable(value=self._actual["size"])
|
|
74
|
+
self._family = ttk.Variable(value=self._actual["family"])
|
|
75
|
+
self._slant = ttk.Variable(value=self._actual["slant"])
|
|
76
|
+
self._weight = ttk.Variable(value=self._actual["weight"])
|
|
77
|
+
self._overstrike = ttk.Variable(value=self._actual["overstrike"])
|
|
78
|
+
self._underline = ttk.Variable(value=self._actual["underline"])
|
|
79
|
+
self._preview_font = font.Font()
|
|
80
|
+
self._preview_text: Optional[ttk.Text] = None
|
|
81
|
+
|
|
82
|
+
self._slant.trace_add("write", self._update_font_preview)
|
|
83
|
+
self._weight.trace_add("write", self._update_font_preview)
|
|
84
|
+
self._overstrike.trace_add("write", self._update_font_preview)
|
|
85
|
+
self._underline.trace_add("write", self._update_font_preview)
|
|
86
|
+
|
|
87
|
+
self._update_font_preview()
|
|
88
|
+
self._families = {self._family.get()}
|
|
89
|
+
for f in font.families():
|
|
90
|
+
if all([f, not f.startswith("@"), "emoji" not in f.lower()]):
|
|
91
|
+
self._families.add(f)
|
|
92
|
+
|
|
93
|
+
# Create the underlying dialog
|
|
94
|
+
self._dialog = Dialog(
|
|
95
|
+
master=master,
|
|
96
|
+
title=title,
|
|
97
|
+
content_builder=self._create_content,
|
|
98
|
+
buttons=[
|
|
99
|
+
DialogButton(
|
|
100
|
+
text="button.cancel",
|
|
101
|
+
role="cancel",
|
|
102
|
+
result=None,
|
|
103
|
+
),
|
|
104
|
+
DialogButton(
|
|
105
|
+
text="button.ok",
|
|
106
|
+
role="primary",
|
|
107
|
+
default=True,
|
|
108
|
+
command=lambda dlg: self._on_submit(),
|
|
109
|
+
result=self._preview_font,
|
|
110
|
+
),
|
|
111
|
+
],
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def _create_content(self, master: tkinter.Widget) -> None:
|
|
115
|
+
"""Create the dialog body with font selection controls."""
|
|
116
|
+
# Set dialog size
|
|
117
|
+
width = scale_size(master, 800)
|
|
118
|
+
height = scale_size(master, 600)
|
|
119
|
+
if self._dialog.toplevel:
|
|
120
|
+
self._dialog.toplevel.geometry(f"{width}x{height}")
|
|
121
|
+
|
|
122
|
+
family_size_frame = ttk.Frame(master, padding=10)
|
|
123
|
+
family_size_frame.pack(fill=X, anchor=N)
|
|
124
|
+
initial_focus = self._font_families_selector(family_size_frame)
|
|
125
|
+
self._font_size_selector(family_size_frame)
|
|
126
|
+
self._font_options_selectors(master, padding=10)
|
|
127
|
+
self._font_preview(master, padding=10)
|
|
128
|
+
|
|
129
|
+
# Set initial focus
|
|
130
|
+
if initial_focus and self._dialog.toplevel:
|
|
131
|
+
self._dialog.toplevel.after(100, initial_focus.focus_set)
|
|
132
|
+
|
|
133
|
+
def _font_families_selector(self, master: tkinter.Misc) -> ttk.Treeview:
|
|
134
|
+
"""Create and populate the font family selection list."""
|
|
135
|
+
container = ttk.Frame(master)
|
|
136
|
+
container.pack(fill=BOTH, expand=YES, side=LEFT)
|
|
137
|
+
|
|
138
|
+
header = ttk.Label(
|
|
139
|
+
container,
|
|
140
|
+
text="font.family",
|
|
141
|
+
font="TkHeadingFont",
|
|
142
|
+
)
|
|
143
|
+
header.pack(fill=X, pady=(0, 2), anchor=N)
|
|
144
|
+
|
|
145
|
+
listbox = ttk.Treeview(
|
|
146
|
+
master=container,
|
|
147
|
+
height=5,
|
|
148
|
+
show="",
|
|
149
|
+
columns=[0],
|
|
150
|
+
)
|
|
151
|
+
listbox.column(0, width=scale_size(listbox, 250))
|
|
152
|
+
listbox.pack(side=LEFT, fill=BOTH, expand=YES)
|
|
153
|
+
|
|
154
|
+
listbox_vbar = ttk.Scrollbar(
|
|
155
|
+
container,
|
|
156
|
+
command=listbox.yview,
|
|
157
|
+
orient=VERTICAL,
|
|
158
|
+
variant="round",
|
|
159
|
+
)
|
|
160
|
+
listbox_vbar.pack(side=RIGHT, fill=Y)
|
|
161
|
+
listbox.configure(yscrollcommand=listbox_vbar.set)
|
|
162
|
+
|
|
163
|
+
for f in sorted(self._families):
|
|
164
|
+
listbox.insert("", iid=f, index='end', tags=[f], values=[f])
|
|
165
|
+
listbox.tag_configure(f, font=(f, self._size.get()))
|
|
166
|
+
|
|
167
|
+
iid = self._family.get()
|
|
168
|
+
listbox.selection_set(iid) # select default value
|
|
169
|
+
listbox.see(iid) # ensure default is visible
|
|
170
|
+
listbox.bind("<<TreeviewSelect>>", lambda e: self._on_select_font_family(e))
|
|
171
|
+
return listbox
|
|
172
|
+
|
|
173
|
+
def _font_size_selector(self, master: tkinter.Misc) -> None:
|
|
174
|
+
"""Create and populate the font size selection list."""
|
|
175
|
+
container = ttk.Frame(master)
|
|
176
|
+
container.pack(side=LEFT, fill=Y, padx=(10, 0))
|
|
177
|
+
|
|
178
|
+
header = ttk.Label(
|
|
179
|
+
container,
|
|
180
|
+
text="font.size",
|
|
181
|
+
font="TkHeadingFont",
|
|
182
|
+
)
|
|
183
|
+
header.pack(fill=X, pady=(0, 2), anchor=N)
|
|
184
|
+
|
|
185
|
+
sizes_listbox = ttk.Treeview(container, height=7, columns=[0], show="")
|
|
186
|
+
sizes_listbox.column(0, width=scale_size(sizes_listbox, 48))
|
|
187
|
+
|
|
188
|
+
sizes = [*range(8, 13), *range(13, 30, 2), 36, 48, 72]
|
|
189
|
+
for s in sizes:
|
|
190
|
+
sizes_listbox.insert("", iid=s, index='end', values=[s])
|
|
191
|
+
|
|
192
|
+
iid = self._size.get()
|
|
193
|
+
sizes_listbox.selection_set(iid)
|
|
194
|
+
sizes_listbox.see(iid)
|
|
195
|
+
sizes_listbox.bind("<<TreeviewSelect>>", lambda e: self._on_select_font_size(e))
|
|
196
|
+
|
|
197
|
+
sizes_listbox_vbar = ttk.Scrollbar(
|
|
198
|
+
master=container,
|
|
199
|
+
orient=VERTICAL,
|
|
200
|
+
command=sizes_listbox.yview,
|
|
201
|
+
variant="round",
|
|
202
|
+
)
|
|
203
|
+
sizes_listbox.configure(yscrollcommand=sizes_listbox_vbar.set)
|
|
204
|
+
sizes_listbox.pack(side=LEFT, fill=Y, expand=YES, anchor=N)
|
|
205
|
+
sizes_listbox_vbar.pack(side=LEFT, fill=Y, expand=YES)
|
|
206
|
+
|
|
207
|
+
def _font_options_selectors(self, master: tkinter.Misc, padding: int) -> None:
|
|
208
|
+
"""Create font option controls (weight, slant, effects)."""
|
|
209
|
+
container = ttk.Frame(master, padding=padding)
|
|
210
|
+
container.pack(fill=X, padx=2, pady=2, anchor=N)
|
|
211
|
+
|
|
212
|
+
weight_lframe = ttk.Labelframe(container, text="font.weight", padding=5) # Use semantic key
|
|
213
|
+
weight_lframe.pack(side=LEFT, fill=X, expand=YES)
|
|
214
|
+
opt_normal = ttk.Radiobutton(
|
|
215
|
+
master=weight_lframe,
|
|
216
|
+
text="font.weight.normal",
|
|
217
|
+
value="normal",
|
|
218
|
+
variable=self._weight,
|
|
219
|
+
)
|
|
220
|
+
opt_normal.invoke()
|
|
221
|
+
opt_normal.pack(side=LEFT, padx=5, pady=5)
|
|
222
|
+
opt_bold = ttk.Radiobutton(
|
|
223
|
+
master=weight_lframe,
|
|
224
|
+
text="font.weight.bold",
|
|
225
|
+
value="bold",
|
|
226
|
+
variable=self._weight,
|
|
227
|
+
)
|
|
228
|
+
opt_bold.pack(side=LEFT, padx=5, pady=5)
|
|
229
|
+
|
|
230
|
+
slant_lframe = ttk.Labelframe(container, text="font.slant", padding=5) # Use semantic key
|
|
231
|
+
slant_lframe.pack(side=LEFT, fill=X, padx=10, expand=YES)
|
|
232
|
+
opt_roman = ttk.Radiobutton(
|
|
233
|
+
master=slant_lframe,
|
|
234
|
+
text="font.slant.roman",
|
|
235
|
+
value="roman",
|
|
236
|
+
variable=self._slant,
|
|
237
|
+
)
|
|
238
|
+
opt_roman.invoke()
|
|
239
|
+
opt_roman.pack(side=LEFT, padx=5, pady=5)
|
|
240
|
+
opt_italic = ttk.Radiobutton(
|
|
241
|
+
master=slant_lframe,
|
|
242
|
+
text="font.slant.italic",
|
|
243
|
+
value="italic",
|
|
244
|
+
variable=self._slant,
|
|
245
|
+
)
|
|
246
|
+
opt_italic.pack(side=LEFT, padx=5, pady=5)
|
|
247
|
+
|
|
248
|
+
effects_lframe = ttk.Labelframe(container, text="font.effects", padding=5) # Use semantic key
|
|
249
|
+
effects_lframe.pack(side=LEFT, padx=(2, 0), fill=X, expand=YES)
|
|
250
|
+
opt_underline = ttk.Checkbutton(
|
|
251
|
+
master=effects_lframe,
|
|
252
|
+
text="font.effects.underline",
|
|
253
|
+
variable=self._underline,
|
|
254
|
+
)
|
|
255
|
+
opt_underline.pack(side=LEFT, padx=5, pady=5)
|
|
256
|
+
opt_overstrike = ttk.Checkbutton(
|
|
257
|
+
master=effects_lframe,
|
|
258
|
+
text="font.effects.overstrike",
|
|
259
|
+
variable=self._overstrike,
|
|
260
|
+
)
|
|
261
|
+
opt_overstrike.pack(side=LEFT, padx=5, pady=5)
|
|
262
|
+
|
|
263
|
+
def _font_preview(self, master: tkinter.Misc, padding: int) -> None:
|
|
264
|
+
"""Create the font preview text widget."""
|
|
265
|
+
container = ttk.Frame(master, padding=padding, height=scale_size(master, 150))
|
|
266
|
+
container.pack(fill=BOTH, expand=YES, anchor=N)
|
|
267
|
+
container.pack_propagate(False)
|
|
268
|
+
|
|
269
|
+
header = ttk.Label(
|
|
270
|
+
container,
|
|
271
|
+
text="font.preview",
|
|
272
|
+
font="TkHeadingFont",
|
|
273
|
+
)
|
|
274
|
+
header.pack(fill=X, pady=2, anchor=N)
|
|
275
|
+
|
|
276
|
+
content = MessageCatalog.translate("font.preview_text")
|
|
277
|
+
self._preview_text = ttk.Text(
|
|
278
|
+
master=container,
|
|
279
|
+
height=3,
|
|
280
|
+
font=self._preview_font,
|
|
281
|
+
)
|
|
282
|
+
self._preview_text.insert(END, content)
|
|
283
|
+
self._preview_text.pack(fill=BOTH, expand=YES)
|
|
284
|
+
|
|
285
|
+
def _on_select_font_family(self, e: tkinter.Event) -> None:
|
|
286
|
+
"""Handle font family selection event."""
|
|
287
|
+
if not self._dialog.toplevel:
|
|
288
|
+
return
|
|
289
|
+
tree: ttk.Treeview = self._dialog.toplevel.nametowidget(e.widget)
|
|
290
|
+
font_family = tree.selection()[0]
|
|
291
|
+
self._family.set(value=font_family)
|
|
292
|
+
self._update_font_preview()
|
|
293
|
+
|
|
294
|
+
def _on_select_font_size(self, e: tkinter.Event) -> None:
|
|
295
|
+
"""Handle font size selection event."""
|
|
296
|
+
if not self._dialog.toplevel:
|
|
297
|
+
return
|
|
298
|
+
tree: ttk.Treeview = self._dialog.toplevel.nametowidget(e.widget)
|
|
299
|
+
fontsize = tree.selection()[0]
|
|
300
|
+
self._size.set(value=fontsize)
|
|
301
|
+
self._update_font_preview()
|
|
302
|
+
|
|
303
|
+
def _on_submit(self) -> None:
|
|
304
|
+
"""Handle OK button - update result with current font."""
|
|
305
|
+
self._dialog.result = self._preview_font
|
|
306
|
+
|
|
307
|
+
def _update_font_preview(self, *_: Any) -> None:
|
|
308
|
+
"""Update the preview font based on current selections."""
|
|
309
|
+
family = self._family.get()
|
|
310
|
+
size = self._size.get()
|
|
311
|
+
slant = self._slant.get()
|
|
312
|
+
overstrike = self._overstrike.get()
|
|
313
|
+
underline = self._underline.get()
|
|
314
|
+
weight = self._weight.get()
|
|
315
|
+
|
|
316
|
+
self._preview_font.config(
|
|
317
|
+
family=family,
|
|
318
|
+
size=size,
|
|
319
|
+
slant=slant,
|
|
320
|
+
overstrike=overstrike,
|
|
321
|
+
underline=underline,
|
|
322
|
+
weight=weight,
|
|
323
|
+
)
|
|
324
|
+
if self._preview_text:
|
|
325
|
+
try:
|
|
326
|
+
self._preview_text.configure(font=self._preview_font)
|
|
327
|
+
except Exception:
|
|
328
|
+
pass
|
|
329
|
+
|
|
330
|
+
def show(self, **kwargs: Unpack[ShowOptions]) -> None:
|
|
331
|
+
"""Show the dialog."""
|
|
332
|
+
kwargs.setdefault('modal', True)
|
|
333
|
+
kwargs.setdefault('anchor_to', 'screen')
|
|
334
|
+
self._dialog.show(**kwargs)
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
def result(self) -> Optional[font.Font]:
|
|
338
|
+
"""The selected font, or None if canceled."""
|
|
339
|
+
return self._dialog.result
|