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,511 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from tkinter import StringVar
|
|
4
|
+
from typing import Any, Callable, Literal, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from typing_extensions import TypedDict, Unpack
|
|
7
|
+
|
|
8
|
+
from bootstack.widgets._impl.primitives.radiobutton import RadioButton
|
|
9
|
+
from bootstack.widgets._impl.primitives.label import Label
|
|
10
|
+
from bootstack.widgets._impl.mixins.configure_mixin import configure_delegate
|
|
11
|
+
from bootstack.widgets._impl.primitives import Frame
|
|
12
|
+
from bootstack.widgets.types import Master, AccentToken, Orient, SurfaceToken, WidgetState
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from bootstack.signals import Signal
|
|
16
|
+
import tkinter as tk
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RadioGroupKwargs(TypedDict, total=False):
|
|
20
|
+
variable: Any
|
|
21
|
+
signal: Signal[Any]
|
|
22
|
+
value: str
|
|
23
|
+
orient: Orient
|
|
24
|
+
accent: AccentToken | str
|
|
25
|
+
text: str
|
|
26
|
+
labelanchor: Literal['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw']
|
|
27
|
+
state: WidgetState
|
|
28
|
+
show_indicator: bool
|
|
29
|
+
show_border: bool
|
|
30
|
+
surface: SurfaceToken | str
|
|
31
|
+
localize: bool | Literal['auto']
|
|
32
|
+
style_options: dict[str, Any]
|
|
33
|
+
# Frame options
|
|
34
|
+
padding: Any
|
|
35
|
+
width: int
|
|
36
|
+
height: int
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RadioGroup(Frame):
|
|
40
|
+
"""A group of radio buttons with automatic state tracking and optional label.
|
|
41
|
+
|
|
42
|
+
The RadioGroup widget provides a convenient way to create groups of radio
|
|
43
|
+
buttons with automatic state management. It supports both horizontal and
|
|
44
|
+
vertical orientations, and can display an optional label in various positions.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, master: Master = None, **kwargs: Unpack[RadioGroupKwargs]):
|
|
48
|
+
"""Initialize the RadioGroup.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
master: Parent widget. If None, uses the default root window.
|
|
52
|
+
|
|
53
|
+
Other Parameters:
|
|
54
|
+
orient: Layout orientation - 'horizontal' (default) or 'vertical'.
|
|
55
|
+
accent: Accent token for styling (e.g., 'primary', 'success', 'danger').
|
|
56
|
+
Defaults to 'primary'.
|
|
57
|
+
text: Optional label text to display.
|
|
58
|
+
labelanchor: Label position - 'n' (top, default), 's' (bottom),
|
|
59
|
+
'e' (right), 'w' (left), or combinations like 'nw', 'ne', etc.
|
|
60
|
+
variable: Optional tk.StringVar for controlling the selected value.
|
|
61
|
+
See [tkinter Variables](https://docs.python.org/3/library/tkinter.html#tkinter-variables).
|
|
62
|
+
signal: Optional Signal instance for reactive programming.
|
|
63
|
+
value: Initial selected value.
|
|
64
|
+
state: Initial state for all buttons - 'normal' (default) or 'disabled'.
|
|
65
|
+
show_indicator: Whether child buttons show the standard radio indicator.
|
|
66
|
+
Defaults to True. Set to False for icon-only or custom-styled groups.
|
|
67
|
+
Can be overridden per-button in `add()`.
|
|
68
|
+
show_border: If True, draws a border around the group.
|
|
69
|
+
surface: Optional surface token; otherwise inherited.
|
|
70
|
+
style_options: Additional style options passed to child buttons.
|
|
71
|
+
padding: Frame padding. Defaults to 1.
|
|
72
|
+
width: Requested width in pixels.
|
|
73
|
+
height: Requested height in pixels.
|
|
74
|
+
"""
|
|
75
|
+
# Extract RadioGroup-specific options before super().__init__
|
|
76
|
+
self._orientation = kwargs.pop('orient', 'horizontal')
|
|
77
|
+
accent_value = kwargs.pop('accent', None) or 'primary'
|
|
78
|
+
self._labeltext = kwargs.pop('text', None)
|
|
79
|
+
self._labelanchor = kwargs.pop('labelanchor', 'n')
|
|
80
|
+
self._state = kwargs.pop('state', 'normal')
|
|
81
|
+
# Localization mode for the group label (items are localized by the
|
|
82
|
+
# public wrapper per-button). None means inherit the app default.
|
|
83
|
+
self._localize = kwargs.pop('localize', None)
|
|
84
|
+
self._show_indicator = kwargs.pop('show_indicator', True)
|
|
85
|
+
|
|
86
|
+
# Handle signal/variable/value
|
|
87
|
+
initial_value = kwargs.pop('value', None)
|
|
88
|
+
style_options = kwargs.pop('style_options', {})
|
|
89
|
+
|
|
90
|
+
# Initialize internal state
|
|
91
|
+
self._buttons: dict[str, RadioButton] = {}
|
|
92
|
+
self._label: Label | None = None
|
|
93
|
+
self._button_container: Frame | None = None
|
|
94
|
+
|
|
95
|
+
# Extract signal/variable before Frame init
|
|
96
|
+
signal_value = kwargs.pop('signal', None)
|
|
97
|
+
variable_value = kwargs.pop('variable', None)
|
|
98
|
+
|
|
99
|
+
# Handle padding default
|
|
100
|
+
if 'padding' not in kwargs:
|
|
101
|
+
kwargs['padding'] = 1
|
|
102
|
+
|
|
103
|
+
# Call super().__init__() - just Frame now
|
|
104
|
+
super().__init__(master, style_options=style_options, **kwargs)
|
|
105
|
+
|
|
106
|
+
# TTKWrapperBase.__init__wrapper overwrites self._accent (it pops accent
|
|
107
|
+
# from kwargs, but accent was already popped above, so it sees None).
|
|
108
|
+
# Restore the correct value after super().__init__ completes.
|
|
109
|
+
self._accent = accent_value
|
|
110
|
+
|
|
111
|
+
# Handle variable/signal setup manually
|
|
112
|
+
if signal_value is not None:
|
|
113
|
+
# Signal provided - extract its variable
|
|
114
|
+
self._signal = signal_value
|
|
115
|
+
self._variable = signal_value.var
|
|
116
|
+
elif variable_value is not None:
|
|
117
|
+
# Variable provided - wrap in Signal
|
|
118
|
+
from bootstack.signals import Signal
|
|
119
|
+
self._variable = variable_value
|
|
120
|
+
self._signal = Signal.from_variable(variable_value)
|
|
121
|
+
# Set initial value if provided
|
|
122
|
+
if initial_value is not None:
|
|
123
|
+
self._variable.set(initial_value)
|
|
124
|
+
else:
|
|
125
|
+
# Neither provided - create internal variable
|
|
126
|
+
from bootstack.signals import Signal
|
|
127
|
+
internal_var = StringVar(value=initial_value or '')
|
|
128
|
+
self._variable = internal_var
|
|
129
|
+
self._signal = Signal.from_variable(internal_var)
|
|
130
|
+
|
|
131
|
+
# Build the UI
|
|
132
|
+
self._build_ui()
|
|
133
|
+
|
|
134
|
+
def _label_localize_kwargs(self) -> dict:
|
|
135
|
+
"""Forward the group's localize mode to the title Label when set."""
|
|
136
|
+
return {} if self._localize is None else {"localize": self._localize}
|
|
137
|
+
|
|
138
|
+
def _build_ui(self):
|
|
139
|
+
"""Construct the widget layout."""
|
|
140
|
+
# Create button container
|
|
141
|
+
self._button_container = Frame(self)
|
|
142
|
+
|
|
143
|
+
# Create label if text provided
|
|
144
|
+
if self._labeltext:
|
|
145
|
+
self._label = Label(self, text=self._labeltext, **self._label_localize_kwargs())
|
|
146
|
+
|
|
147
|
+
# Position based on labelanchor
|
|
148
|
+
self._update_layout()
|
|
149
|
+
|
|
150
|
+
def _update_layout(self):
|
|
151
|
+
"""Update grid layout based on labelanchor."""
|
|
152
|
+
# Clear existing layout
|
|
153
|
+
for widget in self.winfo_children():
|
|
154
|
+
widget.grid_forget()
|
|
155
|
+
|
|
156
|
+
# Determine anchor direction from labelanchor
|
|
157
|
+
# n/s = top/bottom, e/w = right/left
|
|
158
|
+
anchor = self._labelanchor.lower()
|
|
159
|
+
|
|
160
|
+
# Normalize compound anchors to primary direction
|
|
161
|
+
# Priority: n > s > w > e (vertical placement preferred over horizontal)
|
|
162
|
+
if 'n' in anchor:
|
|
163
|
+
primary = 'n'
|
|
164
|
+
elif 's' in anchor:
|
|
165
|
+
primary = 's'
|
|
166
|
+
elif 'w' in anchor:
|
|
167
|
+
primary = 'w'
|
|
168
|
+
elif 'e' in anchor:
|
|
169
|
+
primary = 'e'
|
|
170
|
+
else:
|
|
171
|
+
# Default to north
|
|
172
|
+
primary = 'n'
|
|
173
|
+
|
|
174
|
+
# Layout based on primary anchor
|
|
175
|
+
if primary == 'n':
|
|
176
|
+
# Label on top
|
|
177
|
+
if self._label:
|
|
178
|
+
self._label.grid(row=0, column=0, sticky='w', pady=(0, 4))
|
|
179
|
+
self._button_container.grid(row=1, column=0, sticky='ew')
|
|
180
|
+
self.grid_columnconfigure(0, weight=1)
|
|
181
|
+
elif primary == 's':
|
|
182
|
+
# Label on bottom
|
|
183
|
+
self._button_container.grid(row=0, column=0, sticky='ew')
|
|
184
|
+
if self._label:
|
|
185
|
+
self._label.grid(row=1, column=0, sticky='w', pady=(4, 0))
|
|
186
|
+
self.grid_columnconfigure(0, weight=1)
|
|
187
|
+
elif primary == 'w':
|
|
188
|
+
# Label on left
|
|
189
|
+
if self._label:
|
|
190
|
+
self._label.grid(row=0, column=0, sticky='w', padx=(0, 8))
|
|
191
|
+
self._button_container.grid(row=0, column=1, sticky='ew')
|
|
192
|
+
self.grid_columnconfigure(1, weight=1)
|
|
193
|
+
else: # 'e'
|
|
194
|
+
# Label on right
|
|
195
|
+
self._button_container.grid(row=0, column=0, sticky='ew')
|
|
196
|
+
if self._label:
|
|
197
|
+
self._label.grid(row=0, column=1, sticky='w', padx=(8, 0))
|
|
198
|
+
self.grid_columnconfigure(0, weight=1)
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def variable(self) -> 'tk.Variable':
|
|
202
|
+
"""Get the underlying tk.Variable.
|
|
203
|
+
|
|
204
|
+
See [tkinter Variables](https://docs.python.org/3/library/tkinter.html#tkinter-variables).
|
|
205
|
+
"""
|
|
206
|
+
return self._variable
|
|
207
|
+
|
|
208
|
+
@variable.setter
|
|
209
|
+
def variable(self, value: 'tk.Variable') -> None:
|
|
210
|
+
"""Set the variable."""
|
|
211
|
+
from bootstack.signals import Signal
|
|
212
|
+
self._variable = value
|
|
213
|
+
self._signal = Signal.from_variable(value)
|
|
214
|
+
# Update all buttons to use new variable
|
|
215
|
+
for button in self._buttons.values():
|
|
216
|
+
button.configure(variable=value)
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def signal(self) -> 'Signal[Any]':
|
|
220
|
+
"""Get the signal."""
|
|
221
|
+
return self._signal
|
|
222
|
+
|
|
223
|
+
@signal.setter
|
|
224
|
+
def signal(self, value: 'Signal[Any]') -> None:
|
|
225
|
+
"""Set the signal."""
|
|
226
|
+
self._signal = value
|
|
227
|
+
self._variable = value.var
|
|
228
|
+
# Update all buttons to use new variable
|
|
229
|
+
for button in self._buttons.values():
|
|
230
|
+
button.configure(variable=value.var)
|
|
231
|
+
|
|
232
|
+
def add(self, text: str = None, value: Any = None, key: str | None = None, **kwargs: Any) -> RadioButton:
|
|
233
|
+
"""Add a radio button to the group.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
text: Text to display on the button.
|
|
237
|
+
value: Value this button represents (required).
|
|
238
|
+
key: Unique identifier. Defaults to value.
|
|
239
|
+
**kwargs: Additional arguments passed to RadioButton.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
The created RadioButton widget.
|
|
243
|
+
|
|
244
|
+
Raises:
|
|
245
|
+
ValueError: If value is None or if a button with the same key already exists.
|
|
246
|
+
"""
|
|
247
|
+
if value is None:
|
|
248
|
+
raise ValueError("The 'value' argument is required.")
|
|
249
|
+
|
|
250
|
+
key = key or value
|
|
251
|
+
if key in self._buttons:
|
|
252
|
+
raise ValueError(f"A button with the key '{key}' already exists.")
|
|
253
|
+
|
|
254
|
+
btn_kwargs = kwargs.copy()
|
|
255
|
+
# Use accent for button styling
|
|
256
|
+
if 'accent' not in btn_kwargs:
|
|
257
|
+
btn_kwargs['accent'] = self._accent
|
|
258
|
+
# Apply current state if not explicitly provided
|
|
259
|
+
if 'state' not in btn_kwargs:
|
|
260
|
+
btn_kwargs['state'] = self._state
|
|
261
|
+
# Forward group-level show_indicator unless overridden per-button
|
|
262
|
+
if 'show_indicator' not in btn_kwargs:
|
|
263
|
+
btn_kwargs['show_indicator'] = self._show_indicator
|
|
264
|
+
|
|
265
|
+
button = RadioButton(
|
|
266
|
+
self._button_container,
|
|
267
|
+
text=text,
|
|
268
|
+
value=value,
|
|
269
|
+
variable=self.variable,
|
|
270
|
+
**btn_kwargs
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if self._orientation == 'horizontal':
|
|
274
|
+
button.pack(side='left', padx=6)
|
|
275
|
+
else: # vertical
|
|
276
|
+
button.pack(side='top', anchor='w', pady=4)
|
|
277
|
+
|
|
278
|
+
self._buttons[key] = button
|
|
279
|
+
return button
|
|
280
|
+
|
|
281
|
+
def get(self) -> str:
|
|
282
|
+
"""Return the currently selected value."""
|
|
283
|
+
return self.variable.get()
|
|
284
|
+
|
|
285
|
+
def set(self, value: str) -> None:
|
|
286
|
+
"""Set the selected value.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
value: The value to select (must match a button's value).
|
|
290
|
+
|
|
291
|
+
Raises:
|
|
292
|
+
TypeError: If value is not a string.
|
|
293
|
+
ValueError: If value doesn't exist in the group.
|
|
294
|
+
"""
|
|
295
|
+
if not isinstance(value, str):
|
|
296
|
+
raise TypeError(f"RadioGroup requires a string value, got {type(value).__name__}")
|
|
297
|
+
|
|
298
|
+
# Allow empty string (deselection)
|
|
299
|
+
if value and value not in self._buttons:
|
|
300
|
+
valid_values = list(self._buttons.keys())
|
|
301
|
+
raise ValueError(
|
|
302
|
+
f"Value '{value}' not found in group. "
|
|
303
|
+
f"Valid values: {valid_values}"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
self.variable.set(value)
|
|
307
|
+
|
|
308
|
+
@property
|
|
309
|
+
def value(self) -> str:
|
|
310
|
+
"""Get or set the selected value."""
|
|
311
|
+
return self.get()
|
|
312
|
+
|
|
313
|
+
@value.setter
|
|
314
|
+
def value(self, value: str) -> None:
|
|
315
|
+
self.set(value)
|
|
316
|
+
|
|
317
|
+
def text_for(self, value: Any) -> str | None:
|
|
318
|
+
"""Return the display text of the option with the given value, or None.
|
|
319
|
+
|
|
320
|
+
The selection variable coerces values to strings, so match on the
|
|
321
|
+
stringified key when an exact lookup misses.
|
|
322
|
+
"""
|
|
323
|
+
if value is None or value == "":
|
|
324
|
+
return None
|
|
325
|
+
button = self._buttons.get(value)
|
|
326
|
+
if button is None:
|
|
327
|
+
button = next((b for k, b in self._buttons.items() if str(k) == str(value)), None)
|
|
328
|
+
return button.cget('text') if button is not None else None
|
|
329
|
+
|
|
330
|
+
def remove(self, key: str):
|
|
331
|
+
"""Remove a button by its key.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
key: The key of the button to remove.
|
|
335
|
+
|
|
336
|
+
Raises:
|
|
337
|
+
KeyError: If no button with the given key exists.
|
|
338
|
+
"""
|
|
339
|
+
if key not in self._buttons:
|
|
340
|
+
raise KeyError(f"No button with key '{key}'")
|
|
341
|
+
button = self._buttons.pop(key)
|
|
342
|
+
button.destroy()
|
|
343
|
+
|
|
344
|
+
def items(self) -> tuple[RadioButton, ...]:
|
|
345
|
+
"""Get all button widgets in the group.
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
A tuple of all RadioButton instances in the group.
|
|
349
|
+
"""
|
|
350
|
+
return tuple(self._buttons.values())
|
|
351
|
+
|
|
352
|
+
def item(self, key: str) -> RadioButton:
|
|
353
|
+
"""Get a button by its key.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
key: The key of the button to retrieve.
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
The RadioButton instance.
|
|
360
|
+
|
|
361
|
+
Raises:
|
|
362
|
+
KeyError: If no button with the given key exists.
|
|
363
|
+
"""
|
|
364
|
+
if key not in self._buttons:
|
|
365
|
+
raise KeyError(f"No button with key '{key}'")
|
|
366
|
+
return self._buttons[key]
|
|
367
|
+
|
|
368
|
+
def configure_item(self, key: str, option: str = None, **kwargs: Any):
|
|
369
|
+
"""Configure a specific button by its key.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
key: The key of the button to configure.
|
|
373
|
+
option: If provided, return the value of this option.
|
|
374
|
+
**kwargs: Configuration options to apply to the button.
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
If option is provided, returns the value of that option.
|
|
378
|
+
"""
|
|
379
|
+
button = self.item(key)
|
|
380
|
+
if option is not None:
|
|
381
|
+
return button.cget(option)
|
|
382
|
+
button.configure(**kwargs)
|
|
383
|
+
|
|
384
|
+
def on_changed(self, callback: Callable) -> Any:
|
|
385
|
+
"""Subscribe to value changes.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
callback: Called when the selected value changes. Receives
|
|
389
|
+
`new_value: str` directly (not a Tk event object).
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Subscription ID — pass to `off_changed()` to unsubscribe.
|
|
393
|
+
"""
|
|
394
|
+
return self._signal.subscribe(callback)
|
|
395
|
+
|
|
396
|
+
def off_changed(self, bind_id: Any) -> None:
|
|
397
|
+
"""Cancel a subscription created by `on_changed()`.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
bind_id: The handle returned by `on_changed()`.
|
|
401
|
+
"""
|
|
402
|
+
if bind_id is not None:
|
|
403
|
+
bind_id.cancel()
|
|
404
|
+
|
|
405
|
+
@configure_delegate('accent')
|
|
406
|
+
def _delegate_accent(self, value=None):
|
|
407
|
+
"""Get or set the accent. Updates all buttons when changed."""
|
|
408
|
+
if value is None:
|
|
409
|
+
return self._accent
|
|
410
|
+
|
|
411
|
+
self._accent = value
|
|
412
|
+
# Update all buttons with new accent
|
|
413
|
+
for button in self._buttons.values():
|
|
414
|
+
button.configure(accent=value)
|
|
415
|
+
|
|
416
|
+
@configure_delegate('orient')
|
|
417
|
+
def _delegate_orient(self, value=None):
|
|
418
|
+
"""Get or set orientation ('horizontal' or 'vertical'). Repacks buttons when changed."""
|
|
419
|
+
if value is None:
|
|
420
|
+
return self._orientation
|
|
421
|
+
|
|
422
|
+
if value not in ('horizontal', 'vertical'):
|
|
423
|
+
raise ValueError("orient must be 'horizontal' or 'vertical'")
|
|
424
|
+
|
|
425
|
+
self._orientation = value
|
|
426
|
+
|
|
427
|
+
# Repack all buttons in new orientation
|
|
428
|
+
for button in self._buttons.values():
|
|
429
|
+
button.pack_forget()
|
|
430
|
+
if self._orientation == 'horizontal':
|
|
431
|
+
button.pack(side='left', padx=6)
|
|
432
|
+
else:
|
|
433
|
+
button.pack(side='top', anchor='w', pady=4)
|
|
434
|
+
|
|
435
|
+
@configure_delegate('labelanchor')
|
|
436
|
+
def _delegate_labelanchor(self, value=None):
|
|
437
|
+
"""Get or set label anchor position. Updates layout when changed."""
|
|
438
|
+
if value is None:
|
|
439
|
+
return self._labelanchor
|
|
440
|
+
|
|
441
|
+
valid_anchors = ('n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw')
|
|
442
|
+
if value not in valid_anchors:
|
|
443
|
+
raise ValueError(f"labelanchor must be one of {valid_anchors}")
|
|
444
|
+
|
|
445
|
+
self._labelanchor = value
|
|
446
|
+
self._update_layout()
|
|
447
|
+
|
|
448
|
+
@configure_delegate('text')
|
|
449
|
+
def _delegate_text(self, value=None):
|
|
450
|
+
"""Get or set label text. Creates or removes label as needed."""
|
|
451
|
+
if value is None:
|
|
452
|
+
return self._labeltext if self._labeltext else ''
|
|
453
|
+
|
|
454
|
+
self._labeltext = value
|
|
455
|
+
if value and not self._label:
|
|
456
|
+
# Create label if it doesn't exist
|
|
457
|
+
self._label = Label(self, text=value, **self._label_localize_kwargs())
|
|
458
|
+
self._update_layout()
|
|
459
|
+
elif self._label:
|
|
460
|
+
if value:
|
|
461
|
+
self._label.configure(text=value)
|
|
462
|
+
else:
|
|
463
|
+
# Remove label if text is empty
|
|
464
|
+
self._label.destroy()
|
|
465
|
+
self._label = None
|
|
466
|
+
self._update_layout()
|
|
467
|
+
|
|
468
|
+
@configure_delegate('value')
|
|
469
|
+
def _delegate_value(self, value=None):
|
|
470
|
+
"""Get or set the selected value."""
|
|
471
|
+
if value is None:
|
|
472
|
+
return self.get()
|
|
473
|
+
self.set(value)
|
|
474
|
+
|
|
475
|
+
@configure_delegate('state')
|
|
476
|
+
def _delegate_state(self, value=None):
|
|
477
|
+
"""Get or set state for all buttons ('normal' or 'disabled')."""
|
|
478
|
+
if value is None:
|
|
479
|
+
return self._state
|
|
480
|
+
|
|
481
|
+
if value not in ('normal', 'disabled'):
|
|
482
|
+
raise ValueError("state must be 'normal' or 'disabled'")
|
|
483
|
+
|
|
484
|
+
self._state = value
|
|
485
|
+
# Update all buttons with new state
|
|
486
|
+
for button in self._buttons.values():
|
|
487
|
+
button.configure(state=value)
|
|
488
|
+
|
|
489
|
+
def keys(self) -> tuple[str, ...]:
|
|
490
|
+
"""Get all button keys.
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
A tuple of all button keys in the group.
|
|
494
|
+
"""
|
|
495
|
+
return tuple(self._buttons.keys())
|
|
496
|
+
|
|
497
|
+
def values(self) -> tuple[str, ...]:
|
|
498
|
+
"""Get all possible values.
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
A tuple of all values that can be selected.
|
|
502
|
+
"""
|
|
503
|
+
return tuple(self._buttons.keys())
|
|
504
|
+
|
|
505
|
+
def __len__(self) -> int:
|
|
506
|
+
"""Return the number of buttons in the group."""
|
|
507
|
+
return len(self._buttons)
|
|
508
|
+
|
|
509
|
+
def __contains__(self, key: str) -> bool:
|
|
510
|
+
"""Check if a button with the given key exists in the group."""
|
|
511
|
+
return key in self._buttons
|