bootstack 0.1.0__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 +157 -0
- bootstack/__main__.py +5 -0
- bootstack/_core/__init__.py +21 -0
- bootstack/_core/capabilities/__init__.py +45 -0
- bootstack/_core/capabilities/after.py +103 -0
- bootstack/_core/capabilities/bind.py +192 -0
- bootstack/_core/capabilities/bindtags.py +112 -0
- bootstack/_core/capabilities/busy.py +72 -0
- bootstack/_core/capabilities/clipboard.py +89 -0
- bootstack/_core/capabilities/focus.py +118 -0
- bootstack/_core/capabilities/grab.py +65 -0
- bootstack/_core/capabilities/grid.py +211 -0
- bootstack/_core/capabilities/localization.py +231 -0
- bootstack/_core/capabilities/pack.py +140 -0
- bootstack/_core/capabilities/place.py +113 -0
- bootstack/_core/capabilities/selection.py +136 -0
- bootstack/_core/capabilities/signals.py +244 -0
- bootstack/_core/capabilities/winfo.py +315 -0
- bootstack/_core/colorutils.py +234 -0
- bootstack/_core/exceptions.py +25 -0
- bootstack/_core/images.py +463 -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/paths.py +49 -0
- bootstack/_core/publisher.py +149 -0
- bootstack/_core/variables.py +62 -0
- bootstack/_runtime/__init__.py +3 -0
- bootstack/_runtime/app.py +930 -0
- bootstack/_runtime/base_window.py +945 -0
- bootstack/_runtime/events.py +399 -0
- bootstack/_runtime/shortcuts.py +496 -0
- bootstack/_runtime/tk_patch.py +43 -0
- bootstack/_runtime/toplevel.py +276 -0
- bootstack/_runtime/utility.py +457 -0
- bootstack/_runtime/visual_focus.py +240 -0
- bootstack/_runtime/window_utilities.py +1123 -0
- bootstack/assets/__init__.py +21 -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/button-compact.png +0 -0
- bootstack/assets/elements/button-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
- bootstack/assets/elements/card.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-addon-compact.png +0 -0
- bootstack/assets/elements/input-addon-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/listrow-compact.png +0 -0
- bootstack/assets/elements/listrow-default.png +0 -0
- bootstack/assets/elements/manifest.toml +361 -0
- bootstack/assets/elements/menu-item.png +0 -0
- bootstack/assets/elements/navitem-compact.png +0 -0
- bootstack/assets/elements/navitem-default.png +0 -0
- bootstack/assets/elements/progressbar-h-compact.png +0 -0
- bootstack/assets/elements/progressbar-h-default.png +0 -0
- bootstack/assets/elements/progressbar-v-compact.png +0 -0
- bootstack/assets/elements/progressbar-v-default.png +0 -0
- bootstack/assets/elements/radiobutton.png +0 -0
- bootstack/assets/elements/scrollbar-horizontal.png +0 -0
- bootstack/assets/elements/scrollbar-vertical.png +0 -0
- bootstack/assets/elements/slider-handle.png +0 -0
- bootstack/assets/elements/slider-track-h.png +0 -0
- bootstack/assets/elements/slider-track-v.png +0 -0
- bootstack/assets/elements/switch-off.png +0 -0
- bootstack/assets/elements/switch-on.png +0 -0
- bootstack/assets/elements/tab-h.png +0 -0
- bootstack/assets/elements/tab-v.png +0 -0
- bootstack/assets/icons/bootstrap.ttf +0 -0
- bootstack/assets/icons/glyphmap.json +2080 -0
- bootstack/assets/icons/icon_metrics.json +12470 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +854 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +917 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +845 -0
- bootstack/cli/__init__.py +133 -0
- bootstack/cli/__main__.py +6 -0
- bootstack/cli/add.py +395 -0
- bootstack/cli/appicon.py +285 -0
- bootstack/cli/build.py +115 -0
- bootstack/cli/config.py +313 -0
- bootstack/cli/demo.py +564 -0
- bootstack/cli/dev.py +153 -0
- bootstack/cli/doctor.py +195 -0
- bootstack/cli/icons.py +98 -0
- bootstack/cli/promote.py +120 -0
- bootstack/cli/pyinstaller.py +268 -0
- bootstack/cli/run.py +95 -0
- bootstack/cli/start.py +117 -0
- bootstack/cli/templates/__init__.py +931 -0
- bootstack/clipboard.py +48 -0
- bootstack/constants.py +318 -0
- bootstack/data/README.md +615 -0
- bootstack/data/__init__.py +78 -0
- bootstack/data/_observable.py +276 -0
- bootstack/data/base.py +780 -0
- bootstack/data/file_source.py +367 -0
- bootstack/data/memory_source.py +450 -0
- bootstack/data/query.py +367 -0
- bootstack/data/readers.py +289 -0
- bootstack/data/sqlite_source.py +869 -0
- bootstack/data/types.py +354 -0
- bootstack/data/writers.py +232 -0
- bootstack/dev/__init__.py +36 -0
- bootstack/dev/_capture.py +141 -0
- bootstack/dev/_env.py +43 -0
- bootstack/dev/_registry.py +140 -0
- bootstack/dev/_reloader.py +351 -0
- bootstack/dev/_reset.py +50 -0
- bootstack/dev/_watcher.py +91 -0
- bootstack/dialogs/__init__.py +948 -0
- bootstack/dialogs/_impl/__init__.py +47 -0
- bootstack/dialogs/_impl/colorchooser.py +588 -0
- bootstack/dialogs/_impl/datedialog.py +450 -0
- bootstack/dialogs/_impl/dialog.py +594 -0
- bootstack/dialogs/_impl/filterdialog.py +358 -0
- bootstack/dialogs/_impl/fontdialog.py +364 -0
- bootstack/dialogs/_impl/formdialog.py +564 -0
- bootstack/dialogs/_impl/message.py +486 -0
- bootstack/dialogs/_impl/query.py +570 -0
- bootstack/errors.py +67 -0
- bootstack/events/__init__.py +111 -0
- bootstack/events/_event.py +135 -0
- bootstack/events/_payloads.py +539 -0
- bootstack/events/_subscription.py +38 -0
- bootstack/i18n/README.md +77 -0
- bootstack/i18n/__init__.py +23 -0
- bootstack/i18n/catalog.py +121 -0
- bootstack/i18n/intl_format.py +584 -0
- bootstack/i18n/msgcat.py +425 -0
- bootstack/i18n/specs.py +156 -0
- bootstack/images.py +563 -0
- bootstack/py.typed +1 -0
- bootstack/scheduling/__init__.py +11 -0
- bootstack/scheduling/_schedule.py +218 -0
- bootstack/shortcuts.py +21 -0
- bootstack/signals/README.md +98 -0
- bootstack/signals/__init__.py +10 -0
- bootstack/signals/integration.py +100 -0
- bootstack/signals/signal.py +353 -0
- bootstack/signals/types.py +6 -0
- bootstack/store.py +286 -0
- bootstack/streams/__init__.py +12 -0
- bootstack/streams/_stream.py +321 -0
- bootstack/style/__init__.py +38 -0
- bootstack/style/builders/__init__.py +51 -0
- bootstack/style/builders/badge.py +46 -0
- bootstack/style/builders/button.py +339 -0
- bootstack/style/builders/buttongroup.py +311 -0
- bootstack/style/builders/calendar.py +271 -0
- bootstack/style/builders/checkbutton.py +110 -0
- bootstack/style/builders/combobox.py +113 -0
- bootstack/style/builders/contextmenu.py +268 -0
- bootstack/style/builders/entry.py +82 -0
- bootstack/style/builders/expander.py +148 -0
- bootstack/style/builders/field.py +335 -0
- bootstack/style/builders/frame.py +50 -0
- bootstack/style/builders/label.py +28 -0
- bootstack/style/builders/labelframe.py +34 -0
- bootstack/style/builders/listview.py +369 -0
- bootstack/style/builders/menubar.py +91 -0
- bootstack/style/builders/menubutton.py +359 -0
- bootstack/style/builders/panedwindow.py +25 -0
- bootstack/style/builders/progressbar.py +67 -0
- bootstack/style/builders/radiobutton.py +99 -0
- bootstack/style/builders/scale.py +64 -0
- bootstack/style/builders/scrollbar.py +225 -0
- bootstack/style/builders/separator.py +49 -0
- bootstack/style/builders/sidenav.py +643 -0
- bootstack/style/builders/sizegrip.py +15 -0
- bootstack/style/builders/spinbox.py +119 -0
- bootstack/style/builders/switch.py +70 -0
- bootstack/style/builders/tabitem.py +204 -0
- bootstack/style/builders/togglegroup.py +294 -0
- bootstack/style/builders/toolbutton.py +275 -0
- bootstack/style/builders/tooltip.py +26 -0
- bootstack/style/builders/treeview.py +193 -0
- bootstack/style/builders/utils.py +455 -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/fonts.py +123 -0
- bootstack/style/style.py +609 -0
- bootstack/style/style_builder_base.py +716 -0
- bootstack/style/style_builder_mixed.py +93 -0
- bootstack/style/style_builder_tk.py +109 -0
- bootstack/style/style_builder_ttk.py +353 -0
- bootstack/style/style_resolver.py +447 -0
- bootstack/style/theme.py +245 -0
- bootstack/style/theme_provider.py +471 -0
- bootstack/style/themes/__init__.py +128 -0
- bootstack/style/tk_patch.py +5 -0
- bootstack/style/token_maps.py +41 -0
- bootstack/style/types.py +32 -0
- bootstack/style/typography.py +523 -0
- bootstack/style/utility.py +746 -0
- bootstack/types.py +39 -0
- bootstack/validation/__init__.py +6 -0
- bootstack/validation/types.py +15 -0
- bootstack/validation/validation_result.py +17 -0
- bootstack/validation/validation_rules.py +205 -0
- bootstack/widgets/__init__.py +74 -0
- bootstack/widgets/_core/__init__.py +31 -0
- bootstack/widgets/_core/app_config.py +266 -0
- bootstack/widgets/_core/base.py +461 -0
- bootstack/widgets/_core/container.py +334 -0
- bootstack/widgets/_core/context.py +35 -0
- bootstack/widgets/_core/events.py +130 -0
- bootstack/widgets/_core/field_mixin.py +353 -0
- bootstack/widgets/_core/icon_image_props.py +72 -0
- bootstack/widgets/_core/image_binding.py +93 -0
- bootstack/widgets/_core/navmodel.py +345 -0
- bootstack/widgets/_core/options.py +210 -0
- bootstack/widgets/_core/selection_group.py +130 -0
- bootstack/widgets/_core/window_controls.py +96 -0
- bootstack/widgets/_core/window_menu.py +246 -0
- bootstack/widgets/_impl/__init__.py +1 -0
- bootstack/widgets/_impl/_internal/__init__.py +0 -0
- bootstack/widgets/_impl/_internal/wrapper_base.py +307 -0
- bootstack/widgets/_impl/_parts/__init__.py +11 -0
- bootstack/widgets/_impl/_parts/numberentry_part.py +385 -0
- bootstack/widgets/_impl/_parts/spinnerentry_part.py +434 -0
- bootstack/widgets/_impl/_parts/textentry_part.py +406 -0
- bootstack/widgets/_impl/composites/__init__.py +33 -0
- bootstack/widgets/_impl/composites/_dateutils.py +33 -0
- bootstack/widgets/_impl/composites/_image_fit.py +105 -0
- bootstack/widgets/_impl/composites/accordion.py +381 -0
- bootstack/widgets/_impl/composites/avatar.py +191 -0
- bootstack/widgets/_impl/composites/buttongroup.py +371 -0
- bootstack/widgets/_impl/composites/calendar.py +972 -0
- bootstack/widgets/_impl/composites/carousel.py +567 -0
- bootstack/widgets/_impl/composites/chart.py +882 -0
- bootstack/widgets/_impl/composites/compositeframe.py +298 -0
- bootstack/widgets/_impl/composites/contextmenu.py +1951 -0
- bootstack/widgets/_impl/composites/dateentry.py +404 -0
- bootstack/widgets/_impl/composites/dropdownbutton.py +325 -0
- bootstack/widgets/_impl/composites/expander.py +515 -0
- bootstack/widgets/_impl/composites/field.py +670 -0
- bootstack/widgets/_impl/composites/form.py +1066 -0
- bootstack/widgets/_impl/composites/gallery.py +551 -0
- bootstack/widgets/_impl/composites/list/__init__.py +15 -0
- bootstack/widgets/_impl/composites/list/listitem.py +802 -0
- bootstack/widgets/_impl/composites/list/listview.py +1433 -0
- bootstack/widgets/_impl/composites/menu/__init__.py +18 -0
- bootstack/widgets/_impl/composites/menu/model.py +358 -0
- bootstack/widgets/_impl/composites/menu/render_native.py +134 -0
- bootstack/widgets/_impl/composites/menu/render_themed.py +134 -0
- bootstack/widgets/_impl/composites/meter.py +860 -0
- bootstack/widgets/_impl/composites/numericentry.py +201 -0
- bootstack/widgets/_impl/composites/pagestack.py +395 -0
- bootstack/widgets/_impl/composites/passwordentry.py +142 -0
- bootstack/widgets/_impl/composites/pathentry.py +168 -0
- bootstack/widgets/_impl/composites/picture.py +289 -0
- bootstack/widgets/_impl/composites/radiogroup.py +511 -0
- bootstack/widgets/_impl/composites/scrolledtext.py +375 -0
- bootstack/widgets/_impl/composites/scrolledtext.pyi +186 -0
- bootstack/widgets/_impl/composites/scrollview.py +764 -0
- bootstack/widgets/_impl/composites/selectbox.py +1026 -0
- bootstack/widgets/_impl/composites/shell/__init__.py +40 -0
- bootstack/widgets/_impl/composites/shell/content_host.py +52 -0
- bootstack/widgets/_impl/composites/shell/layout.py +335 -0
- bootstack/widgets/_impl/composites/shell/nav_panel.py +345 -0
- bootstack/widgets/_impl/composites/shell/providers.py +558 -0
- bootstack/widgets/_impl/composites/shell/rail.py +117 -0
- bootstack/widgets/_impl/composites/shell/shell.py +581 -0
- bootstack/widgets/_impl/composites/shell/workspace.py +273 -0
- bootstack/widgets/_impl/composites/sidenav/__init__.py +16 -0
- bootstack/widgets/_impl/composites/sidenav/header.py +81 -0
- bootstack/widgets/_impl/composites/sidenav/separator.py +44 -0
- bootstack/widgets/_impl/composites/slider/__init__.py +7 -0
- bootstack/widgets/_impl/composites/slider/_shared.py +195 -0
- bootstack/widgets/_impl/composites/slider/rangeslider.py +982 -0
- bootstack/widgets/_impl/composites/slider/slider.py +851 -0
- bootstack/widgets/_impl/composites/spinnerentry.py +185 -0
- bootstack/widgets/_impl/composites/tableview/__init__.py +5 -0
- bootstack/widgets/_impl/composites/tableview/tableview.py +3388 -0
- bootstack/widgets/_impl/composites/tableview/types.py +169 -0
- bootstack/widgets/_impl/composites/tabs/__init__.py +23 -0
- bootstack/widgets/_impl/composites/tabs/tabitem.py +389 -0
- bootstack/widgets/_impl/composites/tabs/tabs.py +974 -0
- bootstack/widgets/_impl/composites/tabs/tabview.py +650 -0
- bootstack/widgets/_impl/composites/textarea/__init__.py +26 -0
- bootstack/widgets/_impl/composites/textarea/change.py +46 -0
- bootstack/widgets/_impl/composites/textarea/codeeditor.py +526 -0
- bootstack/widgets/_impl/composites/textarea/core.py +495 -0
- bootstack/widgets/_impl/composites/textarea/decoration.py +42 -0
- bootstack/widgets/_impl/composites/textarea/diff.py +127 -0
- bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +1 -0
- bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +158 -0
- bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +312 -0
- bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +177 -0
- bootstack/widgets/_impl/composites/textarea/filter.py +171 -0
- bootstack/widgets/_impl/composites/textarea/search_overlay.py +459 -0
- bootstack/widgets/_impl/composites/textarea/sidebar.py +88 -0
- bootstack/widgets/_impl/composites/textarea/style_registry.py +178 -0
- bootstack/widgets/_impl/composites/textarea/textarea.py +606 -0
- bootstack/widgets/_impl/composites/textarea/undo.py +217 -0
- bootstack/widgets/_impl/composites/textentry.py +57 -0
- bootstack/widgets/_impl/composites/timeentry.py +176 -0
- bootstack/widgets/_impl/composites/toast.py +390 -0
- bootstack/widgets/_impl/composites/toast_stack.py +156 -0
- bootstack/widgets/_impl/composites/togglegroup.py +418 -0
- bootstack/widgets/_impl/composites/toolbar.py +608 -0
- bootstack/widgets/_impl/composites/tooltip.py +491 -0
- bootstack/widgets/_impl/composites/tree/__init__.py +7 -0
- bootstack/widgets/_impl/composites/tree/source_binding.py +136 -0
- bootstack/widgets/_impl/composites/tree/treeitem.py +393 -0
- bootstack/widgets/_impl/composites/tree/treenode.py +174 -0
- bootstack/widgets/_impl/composites/tree/treeview.py +841 -0
- bootstack/widgets/_impl/mixins/__init__.py +24 -0
- bootstack/widgets/_impl/mixins/configure_mixin.py +216 -0
- bootstack/widgets/_impl/mixins/entry_mixin.py +134 -0
- bootstack/widgets/_impl/mixins/font_mixin.py +368 -0
- bootstack/widgets/_impl/mixins/icon_mixin.py +61 -0
- bootstack/widgets/_impl/mixins/localization_mixin.py +253 -0
- bootstack/widgets/_impl/mixins/signal_mixin.py +268 -0
- bootstack/widgets/_impl/mixins/validation_mixin.py +226 -0
- bootstack/widgets/_impl/primitives/__init__.py +49 -0
- bootstack/widgets/_impl/primitives/_menubutton.py +107 -0
- bootstack/widgets/_impl/primitives/badge.py +45 -0
- bootstack/widgets/_impl/primitives/button.py +76 -0
- bootstack/widgets/_impl/primitives/card.py +45 -0
- bootstack/widgets/_impl/primitives/checkbutton.py +124 -0
- bootstack/widgets/_impl/primitives/checktoggle.py +62 -0
- bootstack/widgets/_impl/primitives/combobox.py +156 -0
- bootstack/widgets/_impl/primitives/entry.py +87 -0
- bootstack/widgets/_impl/primitives/flexframe.py +448 -0
- bootstack/widgets/_impl/primitives/frame.py +185 -0
- bootstack/widgets/_impl/primitives/gridframe.py +546 -0
- bootstack/widgets/_impl/primitives/label.py +84 -0
- bootstack/widgets/_impl/primitives/labelframe.py +54 -0
- bootstack/widgets/_impl/primitives/optionmenu.py +387 -0
- bootstack/widgets/_impl/primitives/packframe.py +227 -0
- bootstack/widgets/_impl/primitives/panedwindow.py +45 -0
- bootstack/widgets/_impl/primitives/progressbar.py +83 -0
- bootstack/widgets/_impl/primitives/radiobutton.py +115 -0
- bootstack/widgets/_impl/primitives/radiotoggle.py +54 -0
- bootstack/widgets/_impl/primitives/scrollbar.py +42 -0
- bootstack/widgets/_impl/primitives/separator.py +43 -0
- bootstack/widgets/_impl/primitives/sizegrip.py +33 -0
- bootstack/widgets/_impl/primitives/spinbox.py +95 -0
- bootstack/widgets/_impl/primitives/switch.py +44 -0
- bootstack/widgets/_impl/primitives/treeview.py +69 -0
- bootstack/widgets/app.py +371 -0
- bootstack/widgets/appshell.py +1179 -0
- bootstack/widgets/avatar.py +140 -0
- bootstack/widgets/boolean_controls.py +455 -0
- bootstack/widgets/button.py +224 -0
- bootstack/widgets/buttongroup.py +239 -0
- bootstack/widgets/calendar.py +195 -0
- bootstack/widgets/card.py +159 -0
- bootstack/widgets/carousel.py +241 -0
- bootstack/widgets/chart.py +302 -0
- bootstack/widgets/codeeditor.py +675 -0
- bootstack/widgets/contextmenu.py +371 -0
- bootstack/widgets/datatable.py +688 -0
- bootstack/widgets/datefield.py +395 -0
- bootstack/widgets/divider.py +60 -0
- bootstack/widgets/expander.py +579 -0
- bootstack/widgets/form.py +200 -0
- bootstack/widgets/gallery.py +250 -0
- bootstack/widgets/gauge.py +168 -0
- bootstack/widgets/grid.py +121 -0
- bootstack/widgets/groupbox.py +162 -0
- bootstack/widgets/label.py +229 -0
- bootstack/widgets/listview.py +354 -0
- bootstack/widgets/menubutton.py +419 -0
- bootstack/widgets/numberfield.py +431 -0
- bootstack/widgets/pagestack.py +345 -0
- bootstack/widgets/passwordfield.py +383 -0
- bootstack/widgets/pathfield.py +454 -0
- bootstack/widgets/picture.py +251 -0
- bootstack/widgets/progressbar.py +106 -0
- bootstack/widgets/radio_variants.py +271 -0
- bootstack/widgets/radiogroup.py +228 -0
- bootstack/widgets/scrollbar.py +64 -0
- bootstack/widgets/scrollview.py +186 -0
- bootstack/widgets/select.py +302 -0
- bootstack/widgets/selectbutton.py +182 -0
- bootstack/widgets/sidebar_toggle.py +130 -0
- bootstack/widgets/sizegrip.py +42 -0
- bootstack/widgets/slider.py +408 -0
- bootstack/widgets/spinbox.py +147 -0
- bootstack/widgets/spinnerfield.py +413 -0
- bootstack/widgets/splash.py +367 -0
- bootstack/widgets/splitview.py +482 -0
- bootstack/widgets/stacks.py +249 -0
- bootstack/widgets/statusbar.py +189 -0
- bootstack/widgets/tabs.py +425 -0
- bootstack/widgets/textarea.py +459 -0
- bootstack/widgets/textfield.py +410 -0
- bootstack/widgets/theme_toggle.py +92 -0
- bootstack/widgets/timefield.py +340 -0
- bootstack/widgets/toast.py +314 -0
- bootstack/widgets/togglegroup.py +231 -0
- bootstack/widgets/toolbar.py +347 -0
- bootstack/widgets/tooltip.py +94 -0
- bootstack/widgets/tree.py +648 -0
- bootstack/widgets/types.py +305 -0
- bootstack/widgets/window.py +307 -0
- bootstack-0.1.0.dist-info/METADATA +301 -0
- bootstack-0.1.0.dist-info/RECORD +471 -0
- bootstack-0.1.0.dist-info/WHEEL +5 -0
- bootstack-0.1.0.dist-info/entry_points.txt +2 -0
- bootstack-0.1.0.dist-info/licenses/LICENSE +21 -0
- bootstack-0.1.0.dist-info/licenses/NOTICE +10 -0
- bootstack-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
"""Cross-platform keyboard shortcut management.
|
|
2
|
+
|
|
3
|
+
This module provides a service for managing keyboard shortcuts with
|
|
4
|
+
automatic platform detection and modifier key translation.
|
|
5
|
+
|
|
6
|
+
Examples:
|
|
7
|
+
.. code-block:: python
|
|
8
|
+
|
|
9
|
+
import bootstack as bs
|
|
10
|
+
from bootstack.shortcuts import get_shortcuts
|
|
11
|
+
|
|
12
|
+
app = bs.App()
|
|
13
|
+
shortcuts = get_shortcuts()
|
|
14
|
+
|
|
15
|
+
# Register shortcuts
|
|
16
|
+
shortcuts.register("save", "Mod+S", save_file)
|
|
17
|
+
shortcuts.register("open", "Mod+O", open_file)
|
|
18
|
+
shortcuts.register("quit", "Mod+Q", app.quit)
|
|
19
|
+
|
|
20
|
+
# Bind to app window
|
|
21
|
+
shortcuts.bind_to(app)
|
|
22
|
+
|
|
23
|
+
# Menu displays shortcut text automatically
|
|
24
|
+
menu.add_command(text="Save", shortcut="save", command=save_file)
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import sys
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from typing import Callable, Any
|
|
32
|
+
from weakref import WeakSet
|
|
33
|
+
|
|
34
|
+
IS_MAC = sys.platform == 'darwin'
|
|
35
|
+
IS_WIN = sys.platform == 'win32'
|
|
36
|
+
|
|
37
|
+
# Modifier display symbols for Mac
|
|
38
|
+
_MAC_SYMBOLS = {
|
|
39
|
+
'mod': '\u2318', # ⌘ Command
|
|
40
|
+
'command': '\u2318', # ⌘
|
|
41
|
+
'ctrl': '\u2303', # ⌃ Control
|
|
42
|
+
'control': '\u2303', # ⌃
|
|
43
|
+
'alt': '\u2325', # ⌥ Option
|
|
44
|
+
'option': '\u2325', # ⌥
|
|
45
|
+
'shift': '\u21E7', # ⇧
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Modifier display names for Windows/Linux
|
|
49
|
+
_WIN_NAMES = {
|
|
50
|
+
'mod': 'Ctrl',
|
|
51
|
+
'command': 'Ctrl',
|
|
52
|
+
'ctrl': 'Ctrl',
|
|
53
|
+
'control': 'Ctrl',
|
|
54
|
+
'alt': 'Alt',
|
|
55
|
+
'option': 'Alt',
|
|
56
|
+
'shift': 'Shift',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Tkinter binding modifiers
|
|
60
|
+
_BINDING_MODIFIERS = {
|
|
61
|
+
'mod': 'Command' if IS_MAC else 'Control',
|
|
62
|
+
'command': 'Command',
|
|
63
|
+
'ctrl': 'Control',
|
|
64
|
+
'control': 'Control',
|
|
65
|
+
'alt': 'Option' if IS_MAC else 'Alt',
|
|
66
|
+
'option': 'Option',
|
|
67
|
+
'shift': 'Shift',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class Shortcut:
|
|
73
|
+
"""A registered keyboard shortcut."""
|
|
74
|
+
|
|
75
|
+
key: str
|
|
76
|
+
"""Unique identifier for lookup (e.g. `'save'`, `'file.open'`)."""
|
|
77
|
+
|
|
78
|
+
pattern: str
|
|
79
|
+
"""Shortcut pattern (e.g. `'Mod+S'`, `'Shift+Alt+N'`)."""
|
|
80
|
+
|
|
81
|
+
command: Callable
|
|
82
|
+
"""Function to execute when triggered."""
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def display(self) -> str:
|
|
86
|
+
"""Platform-appropriate display string for menus.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
On Mac: "⇧⌘S" (symbols, no separators)
|
|
90
|
+
On Windows/Linux: "Ctrl+Shift+S" (names with + separators)
|
|
91
|
+
"""
|
|
92
|
+
parts = self.pattern.split('+')
|
|
93
|
+
key_part = parts[-1]
|
|
94
|
+
mod_parts = [p.lower() for p in parts[:-1]]
|
|
95
|
+
|
|
96
|
+
if IS_MAC:
|
|
97
|
+
# Mac style: symbols without separators, specific order
|
|
98
|
+
# Standard order: Control, Option, Shift, Command
|
|
99
|
+
ordered_mods = []
|
|
100
|
+
for mod in ['ctrl', 'control', 'alt', 'option', 'shift', 'mod', 'command']:
|
|
101
|
+
if mod in mod_parts:
|
|
102
|
+
symbol = _MAC_SYMBOLS.get(mod, mod)
|
|
103
|
+
if symbol not in ordered_mods:
|
|
104
|
+
ordered_mods.append(symbol)
|
|
105
|
+
return ''.join(ordered_mods) + key_part.upper()
|
|
106
|
+
else:
|
|
107
|
+
# Windows/Linux style: names with + separators
|
|
108
|
+
mod_names = []
|
|
109
|
+
for mod in mod_parts:
|
|
110
|
+
name = _WIN_NAMES.get(mod, mod.capitalize())
|
|
111
|
+
if name not in mod_names:
|
|
112
|
+
mod_names.append(name)
|
|
113
|
+
return '+'.join(mod_names + [key_part.upper()])
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def binding(self) -> str:
|
|
117
|
+
"""Tkinter binding string.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Binding string like "<Control-s>" or "<Command-Shift-s>"
|
|
121
|
+
"""
|
|
122
|
+
parts = self.pattern.split('+')
|
|
123
|
+
key_part = parts[-1].lower()
|
|
124
|
+
mod_parts = [p.lower() for p in parts[:-1]]
|
|
125
|
+
|
|
126
|
+
# Convert to Tkinter modifier names
|
|
127
|
+
tk_mods = []
|
|
128
|
+
for mod in mod_parts:
|
|
129
|
+
tk_mod = _BINDING_MODIFIERS.get(mod, mod.capitalize())
|
|
130
|
+
if tk_mod not in tk_mods:
|
|
131
|
+
tk_mods.append(tk_mod)
|
|
132
|
+
|
|
133
|
+
# Handle function keys - keep uppercase
|
|
134
|
+
if key_part.upper().startswith('F') and key_part[1:].isdigit():
|
|
135
|
+
key_part = key_part.upper()
|
|
136
|
+
|
|
137
|
+
return f"<{'-'.join(tk_mods + [key_part])}>"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class Shortcuts:
|
|
141
|
+
"""Singleton service for managing keyboard shortcuts.
|
|
142
|
+
|
|
143
|
+
Provides cross-platform keyboard shortcut registration and binding.
|
|
144
|
+
The service automatically translates modifier keys for each platform:
|
|
145
|
+
|
|
146
|
+
- `Mod` becomes `Ctrl` on Windows/Linux, `Command` on Mac
|
|
147
|
+
- `Alt` becomes `Alt` on Windows/Linux, `Option` on Mac
|
|
148
|
+
- `Shift` works the same on all platforms
|
|
149
|
+
|
|
150
|
+
Examples:
|
|
151
|
+
.. code-block:: python
|
|
152
|
+
|
|
153
|
+
shortcuts = get_shortcuts()
|
|
154
|
+
|
|
155
|
+
# Register shortcuts
|
|
156
|
+
shortcuts.register("save", "Mod+S", save_file)
|
|
157
|
+
shortcuts.register("undo", "Mod+Z", undo)
|
|
158
|
+
shortcuts.register("find", "Mod+F", find)
|
|
159
|
+
|
|
160
|
+
# Bind all to window
|
|
161
|
+
shortcuts.bind_to(app)
|
|
162
|
+
|
|
163
|
+
# Get display string for menu
|
|
164
|
+
shortcuts.display("save") # "Ctrl+S" on Windows, "⌘S" on Mac
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
_instance: 'Shortcuts | None' = None
|
|
169
|
+
|
|
170
|
+
def __new__(cls):
|
|
171
|
+
if cls._instance is None:
|
|
172
|
+
cls._instance = super().__new__(cls)
|
|
173
|
+
cls._instance._shortcuts: dict[str, Shortcut] = {}
|
|
174
|
+
cls._instance._bound_windows: WeakSet = WeakSet()
|
|
175
|
+
cls._instance._bindings: dict[int, list[tuple[str, str]]] = {}
|
|
176
|
+
return cls._instance
|
|
177
|
+
|
|
178
|
+
def register(self, key: str, pattern: str, command: Callable) -> Shortcut:
|
|
179
|
+
"""Register a keyboard shortcut.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
key: Unique identifier (e.g., "save", "file.open")
|
|
183
|
+
pattern: Shortcut pattern using symbolic modifiers:
|
|
184
|
+
- `Mod+S` - Primary modifier + S
|
|
185
|
+
- `Mod+Shift+S` - Primary modifier + Shift + S
|
|
186
|
+
- `Alt+F4` - Alt + F4
|
|
187
|
+
- `F5` - Function key alone
|
|
188
|
+
command: Function to call when triggered
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
The created Shortcut object.
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
ValueError: If key is already registered.
|
|
195
|
+
|
|
196
|
+
Examples:
|
|
197
|
+
.. code-block:: python
|
|
198
|
+
|
|
199
|
+
shortcuts.register("save", "Mod+S", save_file)
|
|
200
|
+
shortcuts.register("quit", "Mod+Q", app.quit)
|
|
201
|
+
shortcuts.register("refresh", "F5", refresh)
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
if key in self._shortcuts:
|
|
205
|
+
raise ValueError(f"Shortcut key '{key}' is already registered")
|
|
206
|
+
|
|
207
|
+
shortcut = Shortcut(key=key, pattern=pattern, command=command)
|
|
208
|
+
self._shortcuts[key] = shortcut
|
|
209
|
+
|
|
210
|
+
# Bind to any already-bound windows
|
|
211
|
+
for window in self._bound_windows:
|
|
212
|
+
self._bind_shortcut(window, shortcut)
|
|
213
|
+
|
|
214
|
+
return shortcut
|
|
215
|
+
|
|
216
|
+
def unregister(self, key: str) -> None:
|
|
217
|
+
"""Remove a registered shortcut.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
key: The shortcut key to remove.
|
|
221
|
+
|
|
222
|
+
Raises:
|
|
223
|
+
KeyError: If key is not registered.
|
|
224
|
+
"""
|
|
225
|
+
if key not in self._shortcuts:
|
|
226
|
+
raise KeyError(f"Shortcut key '{key}' is not registered")
|
|
227
|
+
|
|
228
|
+
shortcut = self._shortcuts.pop(key)
|
|
229
|
+
|
|
230
|
+
# Unbind from all windows
|
|
231
|
+
for window in self._bound_windows:
|
|
232
|
+
self._unbind_shortcut(window, shortcut)
|
|
233
|
+
|
|
234
|
+
def get(self, key: str) -> Shortcut | None:
|
|
235
|
+
"""Get a shortcut by key.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
key: The shortcut key to look up.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
The Shortcut object, or None if not found.
|
|
242
|
+
"""
|
|
243
|
+
return self._shortcuts.get(key)
|
|
244
|
+
|
|
245
|
+
def display(self, key: str) -> str:
|
|
246
|
+
"""Get display string for a shortcut key.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
key: The shortcut key to look up.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Platform-appropriate display string (e.g., "Ctrl+S" or "⌘S"),
|
|
253
|
+
or empty string if not found.
|
|
254
|
+
"""
|
|
255
|
+
shortcut = self._shortcuts.get(key)
|
|
256
|
+
return shortcut.display if shortcut else ''
|
|
257
|
+
|
|
258
|
+
def binding(self, key: str) -> str:
|
|
259
|
+
"""Get Tkinter binding string for a shortcut key.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
key: The shortcut key to look up.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Tkinter binding string (e.g., "<Control-s>"),
|
|
266
|
+
or empty string if not found.
|
|
267
|
+
"""
|
|
268
|
+
shortcut = self._shortcuts.get(key)
|
|
269
|
+
return shortcut.binding if shortcut else ''
|
|
270
|
+
|
|
271
|
+
def bind_to(self, window: Any) -> None:
|
|
272
|
+
"""Bind all registered shortcuts to a window.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
window: The window (App, Toplevel, Tk) to bind shortcuts to.
|
|
276
|
+
|
|
277
|
+
Note:
|
|
278
|
+
Shortcuts registered after calling bind_to will also be
|
|
279
|
+
automatically bound to this window.
|
|
280
|
+
"""
|
|
281
|
+
if window in self._bound_windows:
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
self._bound_windows.add(window)
|
|
285
|
+
window_id = id(window)
|
|
286
|
+
self._bindings[window_id] = []
|
|
287
|
+
|
|
288
|
+
for shortcut in self._shortcuts.values():
|
|
289
|
+
self._bind_shortcut(window, shortcut)
|
|
290
|
+
|
|
291
|
+
def unbind_from(self, window: Any) -> None:
|
|
292
|
+
"""Remove all shortcut bindings from a window.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
window: The window to unbind shortcuts from.
|
|
296
|
+
"""
|
|
297
|
+
if window not in self._bound_windows:
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
self._bound_windows.discard(window)
|
|
301
|
+
window_id = id(window)
|
|
302
|
+
|
|
303
|
+
# Unbind all shortcuts
|
|
304
|
+
bindings = self._bindings.pop(window_id, [])
|
|
305
|
+
for binding_str, func_id in bindings:
|
|
306
|
+
try:
|
|
307
|
+
window.unbind(binding_str, func_id)
|
|
308
|
+
except Exception:
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
def all(self) -> dict[str, Shortcut]:
|
|
312
|
+
"""Get all registered shortcuts.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Dictionary mapping keys to Shortcut objects.
|
|
316
|
+
"""
|
|
317
|
+
return dict(self._shortcuts)
|
|
318
|
+
|
|
319
|
+
def _bind_shortcut(self, window: Any, shortcut: Shortcut) -> None:
|
|
320
|
+
"""Bind a single shortcut to a window."""
|
|
321
|
+
window_id = id(window)
|
|
322
|
+
binding_str = shortcut.binding
|
|
323
|
+
|
|
324
|
+
def handler(event):
|
|
325
|
+
shortcut.command()
|
|
326
|
+
return 'break'
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
func_id = window.bind(binding_str, handler, add='+')
|
|
330
|
+
if window_id in self._bindings:
|
|
331
|
+
self._bindings[window_id].append((binding_str, func_id))
|
|
332
|
+
except Exception:
|
|
333
|
+
pass
|
|
334
|
+
|
|
335
|
+
def _unbind_shortcut(self, window: Any, shortcut: Shortcut) -> None:
|
|
336
|
+
"""Unbind a single shortcut from a window."""
|
|
337
|
+
window_id = id(window)
|
|
338
|
+
binding_str = shortcut.binding
|
|
339
|
+
|
|
340
|
+
if window_id not in self._bindings:
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
# Find and remove the binding
|
|
344
|
+
bindings = self._bindings[window_id]
|
|
345
|
+
for i, (b_str, func_id) in enumerate(bindings):
|
|
346
|
+
if b_str == binding_str:
|
|
347
|
+
try:
|
|
348
|
+
window.unbind(binding_str, func_id)
|
|
349
|
+
except Exception:
|
|
350
|
+
pass
|
|
351
|
+
bindings.pop(i)
|
|
352
|
+
break
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def get_shortcuts() -> Shortcuts:
|
|
356
|
+
"""Get the global Shortcuts service instance.
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
The singleton Shortcuts instance.
|
|
360
|
+
|
|
361
|
+
Examples:
|
|
362
|
+
.. code-block:: python
|
|
363
|
+
|
|
364
|
+
from bootstack.shortcuts import get_shortcuts
|
|
365
|
+
|
|
366
|
+
shortcuts = get_shortcuts()
|
|
367
|
+
shortcuts.register("save", "Mod+S", save_file)
|
|
368
|
+
shortcuts.bind_to(app)
|
|
369
|
+
|
|
370
|
+
"""
|
|
371
|
+
return Shortcuts()
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def classify_shortcut(spec: str | None) -> str:
|
|
375
|
+
"""Classify a shortcut spec into one of four kinds.
|
|
376
|
+
|
|
377
|
+
Returns one of:
|
|
378
|
+
|
|
379
|
+
- `'empty'` — `spec` is `None` or empty.
|
|
380
|
+
- `'registered'` — `spec` matches a key registered with the
|
|
381
|
+
`Shortcuts` service. The keypress is (or will be) bound there; a
|
|
382
|
+
caller should treat it as display-only.
|
|
383
|
+
- `'pattern'` — a modifier expression like `'Mod+S'` or a bare
|
|
384
|
+
function key like `'F5'`. Not registered; a caller may register and
|
|
385
|
+
bind it themselves.
|
|
386
|
+
- `'literal'` — anything else; a plain display string to show as-is.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
spec: A registered key, modifier pattern, or literal display.
|
|
390
|
+
"""
|
|
391
|
+
if not spec:
|
|
392
|
+
return 'empty'
|
|
393
|
+
if Shortcuts().get(spec) is not None:
|
|
394
|
+
return 'registered'
|
|
395
|
+
looks_like_pattern = '+' in spec or (
|
|
396
|
+
len(spec) >= 2
|
|
397
|
+
and spec[0].upper() == 'F'
|
|
398
|
+
and spec[1:].isdigit()
|
|
399
|
+
)
|
|
400
|
+
return 'pattern' if looks_like_pattern else 'literal'
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def format_shortcut(spec: str | None) -> str:
|
|
404
|
+
"""Resolve a shortcut spec to its platform-appropriate display string.
|
|
405
|
+
|
|
406
|
+
Accepts three forms in order of precedence:
|
|
407
|
+
|
|
408
|
+
1. **Registered key** — the key passed to `Shortcuts.register`
|
|
409
|
+
(e.g. `"save"`, `"file.open"`). Returns the registered
|
|
410
|
+
shortcut's `display`.
|
|
411
|
+
2. **Pattern** — a modifier expression like `"Mod+S"`,
|
|
412
|
+
`"Ctrl+Shift+N"`, or `"F5"`. Treated as a transient pattern;
|
|
413
|
+
returns the pattern's platform display without registering anything.
|
|
414
|
+
3. **Plain text** — anything that doesn't parse as a pattern (no
|
|
415
|
+
modifier separator, not a function key) is returned unchanged so
|
|
416
|
+
callers can still pass a literal display string.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
spec: A registered key, modifier pattern, or literal display.
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
Platform-appropriate display string, or empty string when `spec`
|
|
423
|
+
is empty/None.
|
|
424
|
+
|
|
425
|
+
Examples:
|
|
426
|
+
.. code-block:: python
|
|
427
|
+
|
|
428
|
+
format_shortcut("save") # "⌘S" on Mac, "Ctrl+S" on Win/Linux
|
|
429
|
+
# (assumes 'save' is registered)
|
|
430
|
+
format_shortcut("Mod+Shift+N") # "⇧⌘N" on Mac, "Ctrl+Shift+N" on Win
|
|
431
|
+
format_shortcut("F5") # "F5" everywhere
|
|
432
|
+
format_shortcut("⌘S") # "⌘S" — passes through unchanged
|
|
433
|
+
|
|
434
|
+
"""
|
|
435
|
+
kind = classify_shortcut(spec)
|
|
436
|
+
if kind == 'empty':
|
|
437
|
+
return ''
|
|
438
|
+
if kind == 'registered':
|
|
439
|
+
return Shortcuts().get(spec).display
|
|
440
|
+
if kind == 'pattern':
|
|
441
|
+
# Transient Shortcut for display only — no registration, no binding.
|
|
442
|
+
return Shortcut(key='', pattern=spec, command=lambda: None).display
|
|
443
|
+
# 'literal' — treat as a display string.
|
|
444
|
+
return spec
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
# Tk on Aqua renders these modifier WORDS as the ⌘/⌥/⌃/⇧ glyphs in menu
|
|
448
|
+
# accelerators. It does NOT parse the glyphs themselves, so a native tk.Menu
|
|
449
|
+
# must be given the word form — not `Shortcut.display`.
|
|
450
|
+
_AQUA_ACCEL_WORDS = {
|
|
451
|
+
'mod': 'Command', 'command': 'Command', 'cmd': 'Command',
|
|
452
|
+
'ctrl': 'Control', 'control': 'Control',
|
|
453
|
+
'alt': 'Option', 'option': 'Option', 'opt': 'Option',
|
|
454
|
+
'shift': 'Shift',
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def tk_aqua_accelerator(spec: str | None) -> str:
|
|
459
|
+
"""Accelerator string for a native macOS `tk.Menu` entry.
|
|
460
|
+
|
|
461
|
+
Tk on Aqua converts modifier *words* (`Command`, `Option`, `Control`,
|
|
462
|
+
`Shift`) into the ⌘/⌥/⌃/⇧ glyphs when drawing a menu accelerator, but it
|
|
463
|
+
does not understand a pre-rendered glyph string. So a native menu must be
|
|
464
|
+
given the word form (e.g. `'Command+N'`) rather than `format_shortcut`'s
|
|
465
|
+
display (`'⌘N'`), which would show the modifier but drop the key.
|
|
466
|
+
|
|
467
|
+
Accepts the same three forms as `format_shortcut` — a registered key
|
|
468
|
+
(resolved to its pattern), a pattern, or a literal (returned unchanged).
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
spec: A registered key, modifier pattern, or literal display.
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
A Tk-Aqua accelerator string, or empty string when `spec` is empty.
|
|
475
|
+
"""
|
|
476
|
+
kind = classify_shortcut(spec)
|
|
477
|
+
if kind == 'empty':
|
|
478
|
+
return ''
|
|
479
|
+
if kind == 'literal':
|
|
480
|
+
return spec
|
|
481
|
+
pattern = Shortcuts().get(spec).pattern if kind == 'registered' else spec
|
|
482
|
+
|
|
483
|
+
parts = pattern.split('+')
|
|
484
|
+
key = parts[-1]
|
|
485
|
+
mods: list[str] = []
|
|
486
|
+
for part in parts[:-1]:
|
|
487
|
+
word = _AQUA_ACCEL_WORDS.get(part.lower(), part.capitalize())
|
|
488
|
+
if word not in mods:
|
|
489
|
+
mods.append(word)
|
|
490
|
+
return '+'.join(mods + [key.upper()])
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
__all__ = [
|
|
494
|
+
'Shortcut', 'Shortcuts', 'get_shortcuts', 'format_shortcut',
|
|
495
|
+
'classify_shortcut', 'tk_aqua_accelerator',
|
|
496
|
+
]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Install-time patching for Tk widgets only.
|
|
2
|
+
|
|
3
|
+
This module applies bootstack's Tk autostyle behavior to Tk widgets by
|
|
4
|
+
wrapping their constructors. It does not affect ttk widgets, which are
|
|
5
|
+
provided as explicit subclasses in the bootstack.widgets package.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import tkinter as tk
|
|
11
|
+
|
|
12
|
+
from bootstack.style.style_resolver import StyleResolver
|
|
13
|
+
|
|
14
|
+
# Native Tk widget classes whose constructors get the autostyle wrapper.
|
|
15
|
+
# Limited to the raw Tk widgets the framework actually instantiates or
|
|
16
|
+
# subclasses (everything else is built on ttk). Implementation detail of this
|
|
17
|
+
# patch — intentionally not exported anywhere.
|
|
18
|
+
_TK_WIDGETS = (
|
|
19
|
+
tk.Tk, # application root window
|
|
20
|
+
tk.Toplevel, # secondary windows, dialogs, popups
|
|
21
|
+
tk.Frame, # Slider/RangeSlider/multiline-text container subclasses
|
|
22
|
+
tk.Text, # TextArea / CodeEditor cores
|
|
23
|
+
tk.Canvas, # sliders, scroll view, gauge drawing
|
|
24
|
+
tk.Menu, # context menus and menu bars
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def install_tk_autostyle() -> None:
|
|
29
|
+
"""Patch Tk widgets to enable autostyle via StyleResolver.
|
|
30
|
+
|
|
31
|
+
- Wraps Tk widget `__init__` with surface color inheritance and theme
|
|
32
|
+
background application using registered Tk builders.
|
|
33
|
+
- Leaves ttk widgets untouched; ttk integration is done via wrappers.
|
|
34
|
+
"""
|
|
35
|
+
# Ensure Tk builders are registered
|
|
36
|
+
import bootstack.style.builders_tk # noqa: F401
|
|
37
|
+
|
|
38
|
+
for widget in _TK_WIDGETS:
|
|
39
|
+
_init = StyleResolver.override_tk_widget_constructor(widget.__init__)
|
|
40
|
+
widget.__init__ = _init
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = ["install_tk_autostyle"]
|