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,276 @@
|
|
|
1
|
+
"""Change broadcasting for data sources — the reactive engine behind
|
|
2
|
+
`DataSource.on_change` and `DataSource.observe`.
|
|
3
|
+
|
|
4
|
+
A `_ChangeHub` is composed into every `BaseDataSource` (as `self._hub`). It is
|
|
5
|
+
plain Python — no Tkinter dependency of its own — so the data layer stays
|
|
6
|
+
testable headless. The hub does three things:
|
|
7
|
+
|
|
8
|
+
1. **Broadcast** — fan a `DataChangeEvent` out to coarse listeners
|
|
9
|
+
(`on_change`) and to live result-set queries (`observe`).
|
|
10
|
+
2. **Marshal** — when a mutation happens on a background thread (a web feed,
|
|
11
|
+
a file load), defer delivery to the Tk main thread so bound widgets update
|
|
12
|
+
safely. With no active App it degrades to synchronous, in-thread delivery.
|
|
13
|
+
3. **Coalesce** — collapse a burst of mutations in one event-loop turn into a
|
|
14
|
+
single recompute/notification (invalidate-and-refetch, à la TanStack Query).
|
|
15
|
+
|
|
16
|
+
`silence()` suppresses emission for the duration of a `with` block. Widgets use
|
|
17
|
+
it to wrap mutations they make to their *own* bound source, so a widget does not
|
|
18
|
+
broadcast a change back to itself.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import contextlib
|
|
24
|
+
import logging
|
|
25
|
+
import threading
|
|
26
|
+
import warnings
|
|
27
|
+
from typing import TYPE_CHECKING, Any, Callable, List
|
|
28
|
+
|
|
29
|
+
from bootstack.events import DataChangeEvent
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from bootstack.data.query import Condition, SortKey
|
|
33
|
+
from bootstack.data.types import DataSourceProtocol, Record
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
# Safety net: if a change handler mutates the source on every flush it would
|
|
38
|
+
# loop forever. Drain at most this many ticks per flush, then bail with a warning.
|
|
39
|
+
_MAX_FLUSH_ITERATIONS = 100
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class _ObservedQuery:
|
|
43
|
+
"""A live `where`/`order` slice registered with a `_ChangeHub`.
|
|
44
|
+
|
|
45
|
+
Holds its own filter/sort (independent of the source's pagination view
|
|
46
|
+
state) and re-runs against the source on each relevant change, emitting a
|
|
47
|
+
fresh result-set to its handler only when the rows actually change.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
__slots__ = ("_source", "_condition", "_sort_keys", "_emit", "_signature", "_last")
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
source: "DataSourceProtocol",
|
|
55
|
+
condition: "Condition | None",
|
|
56
|
+
sort_keys: "List[SortKey]",
|
|
57
|
+
emit: Callable[[list], Any],
|
|
58
|
+
) -> None:
|
|
59
|
+
self._source = source
|
|
60
|
+
self._condition = condition
|
|
61
|
+
self._sort_keys = sort_keys
|
|
62
|
+
self._emit = emit
|
|
63
|
+
self._signature: Any = None
|
|
64
|
+
self._last: "List[Record] | None" = None
|
|
65
|
+
|
|
66
|
+
def current(self) -> "List[Record]":
|
|
67
|
+
"""Run the query against the source and return the result-set."""
|
|
68
|
+
return self._source._query(self._condition, self._sort_keys)
|
|
69
|
+
|
|
70
|
+
def start(self) -> "List[Record]":
|
|
71
|
+
"""Compute, cache, and return the initial result-set (on subscribe)."""
|
|
72
|
+
rows = self.current()
|
|
73
|
+
self._signature = self._signature_of(rows)
|
|
74
|
+
self._last = rows
|
|
75
|
+
return rows
|
|
76
|
+
|
|
77
|
+
def recompute(self) -> None:
|
|
78
|
+
"""Re-run the query; emit to the handler only if the result changed."""
|
|
79
|
+
rows = self.current()
|
|
80
|
+
sig = self._signature_of(rows)
|
|
81
|
+
if sig == self._signature and rows == self._last:
|
|
82
|
+
return
|
|
83
|
+
self._signature = sig
|
|
84
|
+
self._last = rows
|
|
85
|
+
self._emit(rows)
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _signature_of(rows: "List[Record]") -> Any:
|
|
89
|
+
"""A cheap identity for fast change detection (len + id tuple)."""
|
|
90
|
+
return (len(rows), tuple(r.get("id") for r in rows))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class _ChangeHub:
|
|
94
|
+
"""Per-source pub/sub with thread marshaling and coalescing.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
source: The data source that owns this hub. Used to run observed
|
|
98
|
+
queries via its `_query` method.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
__slots__ = (
|
|
102
|
+
"_source",
|
|
103
|
+
"_listeners",
|
|
104
|
+
"_queries",
|
|
105
|
+
"_pending",
|
|
106
|
+
"_flush_scheduled",
|
|
107
|
+
"_silence",
|
|
108
|
+
"_in_flush",
|
|
109
|
+
"_lock",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def __init__(self, source: "DataSourceProtocol") -> None:
|
|
113
|
+
self._source = source
|
|
114
|
+
self._listeners: List[Callable[[DataChangeEvent], Any]] = []
|
|
115
|
+
self._queries: List[_ObservedQuery] = []
|
|
116
|
+
self._pending: List[DataChangeEvent] = []
|
|
117
|
+
self._flush_scheduled = False
|
|
118
|
+
# Silencing is thread-local: a widget silencing its own main-thread write
|
|
119
|
+
# must not suppress a concurrent background-thread mutation (which is a
|
|
120
|
+
# genuinely external change that bound widgets need to see).
|
|
121
|
+
self._silence = threading.local()
|
|
122
|
+
self._in_flush = False
|
|
123
|
+
self._lock = threading.Lock()
|
|
124
|
+
|
|
125
|
+
def _silence_depth(self) -> int:
|
|
126
|
+
return getattr(self._silence, "depth", 0)
|
|
127
|
+
|
|
128
|
+
# ----- registration ------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
def add_listener(self, fn: Callable[[DataChangeEvent], Any]) -> Callable[[], None]:
|
|
131
|
+
"""Register a coarse change listener; returns an unregister callable."""
|
|
132
|
+
self._listeners.append(fn)
|
|
133
|
+
|
|
134
|
+
def _remove() -> None:
|
|
135
|
+
try:
|
|
136
|
+
self._listeners.remove(fn)
|
|
137
|
+
except ValueError:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
return _remove
|
|
141
|
+
|
|
142
|
+
def add_query(self, query: _ObservedQuery) -> Callable[[], None]:
|
|
143
|
+
"""Register a live observed query; returns an unregister callable."""
|
|
144
|
+
self._queries.append(query)
|
|
145
|
+
|
|
146
|
+
def _remove() -> None:
|
|
147
|
+
try:
|
|
148
|
+
self._queries.remove(query)
|
|
149
|
+
except ValueError:
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
return _remove
|
|
153
|
+
|
|
154
|
+
# ----- silencing ---------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
@contextlib.contextmanager
|
|
157
|
+
def silence(self):
|
|
158
|
+
"""Suppress emission on the current thread for the block (nestable)."""
|
|
159
|
+
self._silence.depth = self._silence_depth() + 1
|
|
160
|
+
try:
|
|
161
|
+
yield
|
|
162
|
+
finally:
|
|
163
|
+
self._silence.depth = self._silence_depth() - 1
|
|
164
|
+
|
|
165
|
+
# ----- emission ----------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
def emit(self, event: DataChangeEvent) -> None:
|
|
168
|
+
"""Broadcast a change, marshaling to the main thread and coalescing.
|
|
169
|
+
|
|
170
|
+
A no-op while `silence()` is active. With no active App, delivers
|
|
171
|
+
synchronously on the calling thread (deterministic for headless use).
|
|
172
|
+
"""
|
|
173
|
+
if self._silence_depth() > 0:
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
# Lazy import to avoid a load-time cycle with the runtime/app module.
|
|
177
|
+
from bootstack._runtime.app import get_current_app, has_current_app
|
|
178
|
+
|
|
179
|
+
app = None
|
|
180
|
+
schedule = False
|
|
181
|
+
flush_now = False
|
|
182
|
+
with self._lock:
|
|
183
|
+
self._pending.append(event)
|
|
184
|
+
if not has_current_app():
|
|
185
|
+
flush_now = True
|
|
186
|
+
elif not self._flush_scheduled:
|
|
187
|
+
self._flush_scheduled = True
|
|
188
|
+
schedule = True
|
|
189
|
+
app = get_current_app()
|
|
190
|
+
# else: a flush is already queued — this event rides along (coalesce).
|
|
191
|
+
|
|
192
|
+
if flush_now:
|
|
193
|
+
self._flush()
|
|
194
|
+
elif schedule:
|
|
195
|
+
on_main = threading.current_thread() is threading.main_thread()
|
|
196
|
+
try:
|
|
197
|
+
# after_idle coalesces naturally on the main thread; from a
|
|
198
|
+
# worker thread hop onto the loop with after(0, ...).
|
|
199
|
+
if on_main:
|
|
200
|
+
app.after_idle(self._flush)
|
|
201
|
+
else:
|
|
202
|
+
app.after(0, self._flush)
|
|
203
|
+
except Exception:
|
|
204
|
+
# App is tearing down or the loop is gone — drop the schedule.
|
|
205
|
+
with self._lock:
|
|
206
|
+
self._flush_scheduled = False
|
|
207
|
+
|
|
208
|
+
def _flush(self) -> None:
|
|
209
|
+
"""Drain pending events: recompute queries, then notify listeners once.
|
|
210
|
+
|
|
211
|
+
Runs on the Tk main thread (scheduled via the App) whenever an App is
|
|
212
|
+
active; only the headless path runs it on the emitting thread. The
|
|
213
|
+
`_in_flush` / `_pending`-swap bookkeeping outside the lock relies on that
|
|
214
|
+
single-threaded-under-an-App invariant.
|
|
215
|
+
"""
|
|
216
|
+
if self._in_flush:
|
|
217
|
+
# Re-entrant call (a handler mutated the source) — the running loop
|
|
218
|
+
# will pick up the newly queued events.
|
|
219
|
+
return
|
|
220
|
+
self._in_flush = True
|
|
221
|
+
try:
|
|
222
|
+
iterations = 0
|
|
223
|
+
while True:
|
|
224
|
+
with self._lock:
|
|
225
|
+
if not self._pending:
|
|
226
|
+
self._flush_scheduled = False
|
|
227
|
+
break
|
|
228
|
+
events = self._pending
|
|
229
|
+
self._pending = []
|
|
230
|
+
iterations += 1
|
|
231
|
+
if iterations > _MAX_FLUSH_ITERATIONS:
|
|
232
|
+
with self._lock:
|
|
233
|
+
self._pending.clear()
|
|
234
|
+
self._flush_scheduled = False
|
|
235
|
+
warnings.warn(
|
|
236
|
+
"DataSource change flush exceeded its iteration limit; "
|
|
237
|
+
"a change handler may be mutating the source in a loop.",
|
|
238
|
+
RuntimeWarning,
|
|
239
|
+
stacklevel=2,
|
|
240
|
+
)
|
|
241
|
+
break
|
|
242
|
+
self._dispatch(events)
|
|
243
|
+
finally:
|
|
244
|
+
self._in_flush = False
|
|
245
|
+
|
|
246
|
+
def _dispatch(self, events: List[DataChangeEvent]) -> None:
|
|
247
|
+
"""Recompute observed queries, then fire coarse listeners once."""
|
|
248
|
+
kinds = {e.kind for e in events}
|
|
249
|
+
row_changed = bool(kinds - {"select"}) # selection is not a row-set change
|
|
250
|
+
|
|
251
|
+
if self._queries and row_changed:
|
|
252
|
+
for query in list(self._queries):
|
|
253
|
+
try:
|
|
254
|
+
query.recompute()
|
|
255
|
+
except Exception:
|
|
256
|
+
logger.exception("observe() query handler raised; continuing")
|
|
257
|
+
|
|
258
|
+
if self._listeners:
|
|
259
|
+
rep = self._representative(events)
|
|
260
|
+
for fn in list(self._listeners):
|
|
261
|
+
try:
|
|
262
|
+
fn(rep)
|
|
263
|
+
except Exception:
|
|
264
|
+
logger.exception("on_change() handler raised; continuing")
|
|
265
|
+
|
|
266
|
+
@staticmethod
|
|
267
|
+
def _representative(events: List[DataChangeEvent]) -> DataChangeEvent:
|
|
268
|
+
"""Pick one event to stand for the batch.
|
|
269
|
+
|
|
270
|
+
A single coalesced kind keeps its last event; a mix collapses to a
|
|
271
|
+
coarse `reload`.
|
|
272
|
+
"""
|
|
273
|
+
kinds = {e.kind for e in events}
|
|
274
|
+
if len(kinds) == 1:
|
|
275
|
+
return events[-1]
|
|
276
|
+
return DataChangeEvent(kind="reload")
|