bootstack 0.2.3__tar.gz → 0.3.1__tar.gz
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-0.2.3/src/bootstack.egg-info → bootstack-0.3.1}/PKG-INFO +1 -1
- {bootstack-0.2.3 → bootstack-0.3.1}/pyproject.toml +2 -2
- bootstack-0.3.1/src/bootstack/_core/capture.py +449 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dialogs/__init__.py +6 -1
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dialogs/_impl/datedialog.py +51 -10
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dialogs/_impl/dialog.py +347 -16
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dialogs/_impl/formdialog.py +122 -30
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dialogs/_impl/query.py +41 -29
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/base.py +91 -1
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/container.py +63 -6
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/form.py +17 -3
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/appshell.py +1 -1
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/card.py +1 -1
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/expander.py +2 -2
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/form.py +6 -1
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/grid.py +1 -1
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/groupbox.py +1 -1
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/pagestack.py +1 -1
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/splitview.py +1 -1
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/tabs.py +1 -1
- {bootstack-0.2.3 → bootstack-0.3.1/src/bootstack.egg-info}/PKG-INFO +1 -1
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack.egg-info/SOURCES.txt +1 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/LICENSE +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/MANIFEST.in +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/NOTICE +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/README.md +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/setup.cfg +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/__main__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/after.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/bind.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/bindtags.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/busy.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/clipboard.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/focus.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/grab.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/grid.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/localization.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/pack.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/place.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/selection.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/signals.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/capabilities/winfo.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/colorutils.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/exceptions.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/images.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/mixins/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/mixins/ttk_state.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/mixins/widget.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/paths.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/publisher.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_core/variables.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_runtime/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_runtime/app.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_runtime/base_window.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_runtime/events.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_runtime/shortcuts.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_runtime/tk_patch.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_runtime/toplevel.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_runtime/utility.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_runtime/visual_focus.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_runtime/wheel.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/_runtime/window_utilities.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/bootstack.ico +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/bootstack.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/badge-pill.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/badge-square.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/button-compact.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/button-default.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/card.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/checkbox-checked.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/checkbox-indeterminate.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/checkbox-unchecked.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/field.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/input-addon-compact.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/input-addon-default.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/input-compact.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/input-default.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/list-item-separated.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/list-item.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/listrow-compact.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/listrow-default.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/manifest.toml +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/menu-item.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/navitem-compact.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/navitem-default.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/progressbar-h-compact.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/progressbar-h-default.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/progressbar-v-compact.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/progressbar-v-default.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/radiobutton.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/scrollbar-horizontal.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/scrollbar-vertical.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/slider-handle.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/slider-track-h.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/slider-track-v.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/switch-off.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/switch-on.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/tab-h.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/elements/tab-v.png +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/icons/bootstrap.ttf +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/icons/glyphmap.json +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/icons/icon_metrics.json +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/__main__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/add.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/appicon.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/build.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/config.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/demo.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/dev.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/doctor.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/icons.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/promote.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/pyinstaller.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/run.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/start.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/cli/templates/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/clipboard.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/constants.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/data/README.md +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/data/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/data/_observable.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/data/base.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/data/file_source.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/data/memory_source.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/data/query.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/data/readers.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/data/sqlite_source.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/data/types.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/data/writers.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dev/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dev/_capture.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dev/_env.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dev/_registry.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dev/_reloader.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dev/_reset.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dev/_watcher.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dialogs/_impl/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dialogs/_impl/colorchooser.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dialogs/_impl/filterdialog.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dialogs/_impl/fontdialog.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/dialogs/_impl/message.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/errors.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/events/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/events/_event.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/events/_payloads.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/events/_subscription.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/i18n/README.md +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/i18n/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/i18n/catalog.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/i18n/intl_format.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/i18n/msgcat.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/i18n/specs.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/images.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/py.typed +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/scheduling/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/scheduling/_schedule.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/shortcuts.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/signals/README.md +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/signals/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/signals/integration.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/signals/signal.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/signals/types.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/store.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/streams/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/streams/_stream.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/badge.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/button.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/buttongroup.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/calendar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/checkbutton.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/combobox.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/contextmenu.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/entry.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/expander.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/field.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/frame.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/label.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/labelframe.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/listview.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/menubar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/menubutton.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/panedwindow.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/progressbar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/radiobutton.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/scale.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/scrollbar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/separator.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/sidenav.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/sizegrip.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/spinbox.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/switch.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/tabitem.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/togglegroup.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/toolbutton.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/tooltip.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/treeview.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders/utils.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders_tk/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/builders_tk/defaults.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/element.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/fonts.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/style.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/style_builder_base.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/style_builder_mixed.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/style_builder_tk.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/style_builder_ttk.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/style_resolver.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/theme.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/theme_provider.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/themes/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/tk_patch.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/token_maps.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/types.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/typography.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/style/utility.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/types.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/validation/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/validation/types.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/validation/validation_result.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/validation/validation_rules.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/app_config.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/choices.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/context.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/events.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/field_mixin.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/icon_image_props.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/image_binding.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/kwargs.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/navmodel.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/options.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/selection_group.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/window_controls.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_core/window_menu.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/_internal/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/_internal/wrapper_base.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/_parts/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/_parts/numberentry_part.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/_parts/spinnerentry_part.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/_parts/textentry_part.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/_dateutils.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/_image_fit.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/accordion.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/avatar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/buttongroup.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/calendar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/carousel.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/chart.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/compositeframe.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/contextmenu.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/dateentry.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/dropdownbutton.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/expander.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/field.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/gallery.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/list/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/list/listitem.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/list/listview.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/menu/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/menu/model.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/menu/render_native.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/menu/render_themed.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/meter.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/numericentry.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/pagestack.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/passwordentry.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/pathentry.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/picture.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/radiogroup.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/scrolledtext.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/scrolledtext.pyi +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/scrollview.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/selectbox.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/shell/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/shell/content_host.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/shell/layout.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/shell/nav_panel.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/shell/providers.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/shell/rail.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/shell/shell.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/shell/workspace.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/sidenav/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/sidenav/header.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/sidenav/separator.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/slider/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/slider/_shared.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/slider/rangeslider.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/slider/slider.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/spinnerentry.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/tableview/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/tableview/tableview.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/tableview/types.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/tabs/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/tabs/tabitem.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/tabs/tabs.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/tabs/tabview.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/change.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/codeeditor.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/core.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/decoration.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/diff.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/filter.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/redirector.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/search_overlay.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/sidebar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/style_registry.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/textarea.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textarea/undo.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/textentry.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/timeentry.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/toast.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/toast_stack.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/togglegroup.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/toolbar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/tooltip.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/tree/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/tree/source_binding.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/tree/treeitem.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/tree/treenode.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/composites/tree/treeview.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/mixins/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/mixins/configure_mixin.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/mixins/entry_mixin.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/mixins/font_mixin.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/mixins/icon_mixin.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/mixins/localization_mixin.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/mixins/signal_mixin.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/mixins/validation_mixin.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/__init__.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/_menubutton.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/badge.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/button.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/card.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/checkbutton.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/checktoggle.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/combobox.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/entry.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/flexframe.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/frame.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/gridframe.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/label.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/labelframe.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/optionmenu.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/packframe.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/panedwindow.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/progressbar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/radiobutton.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/radiotoggle.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/scrollbar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/separator.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/sizegrip.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/spinbox.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/switch.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/_impl/primitives/treeview.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/app.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/avatar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/boolean_controls.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/button.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/buttongroup.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/calendar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/carousel.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/chart.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/codeeditor.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/contextmenu.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/datatable.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/datefield.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/divider.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/gallery.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/gauge.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/label.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/listview.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/menubutton.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/numberfield.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/passwordfield.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/pathfield.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/picture.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/progressbar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/radio_variants.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/radiogroup.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/scrollbar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/scrollview.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/select.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/selectbutton.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/sidebar_toggle.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/sizegrip.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/slider.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/spinbox.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/spinnerfield.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/splash.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/stacks.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/statusbar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/textarea.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/textfield.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/theme_toggle.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/timefield.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/toast.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/togglegroup.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/toolbar.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/tooltip.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/tree.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/types.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack/widgets/window.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack.egg-info/dependency_links.txt +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack.egg-info/entry_points.txt +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack.egg-info/requires.txt +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/src/bootstack.egg-info/top_level.txt +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/tests/test_dialog_anchor.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/tests/test_events_doc_coverage.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/tests/test_hot_reload.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/tests/test_images.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/tests/test_images_gui.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/tests/test_intl_format.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/tests/test_navmodel.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/tests/test_on_color_contrast.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/tests/test_public_surface.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/tests/test_store.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/tests/test_theme_from_existing.py +0 -0
- {bootstack-0.2.3 → bootstack-0.3.1}/tests/test_validation_rules.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bootstack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Build desktop GUI apps in pure Python and ship them as a standalone executable — built on Tk, no Tkinter boilerplate.
|
|
5
5
|
Author-email: Israel Dryer <israel.dryer@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "bootstack"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Build desktop GUI apps in pure Python and ship them as a standalone executable — built on Tk, no Tkinter boilerplate."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -114,7 +114,7 @@ where = ["src"]
|
|
|
114
114
|
]
|
|
115
115
|
|
|
116
116
|
[tool.bumpversion]
|
|
117
|
-
current_version = "0.
|
|
117
|
+
current_version = "0.3.1"
|
|
118
118
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)((?P<pre>a|b|rc)(?P<pre_n>\\d+))?"
|
|
119
119
|
serialize = [
|
|
120
120
|
"{major}.{minor}.{patch}{pre}{pre_n}",
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
"""Screen-region capture backing the public `widget.capture()`.
|
|
2
|
+
|
|
3
|
+
A capture reads pixels from the display, so the region has to be visible:
|
|
4
|
+
the widget must be on screen, and anything drawn over it is captured along
|
|
5
|
+
with it. There is no offscreen rendering path — the toolkit does not offer
|
|
6
|
+
one — which is why the public method requires a visible widget.
|
|
7
|
+
|
|
8
|
+
Capture uses Pillow's `ImageGrab`, falling back to a desktop screenshot tool
|
|
9
|
+
on Linux sessions where `ImageGrab` is unavailable (Wayland, or a Pillow
|
|
10
|
+
built without XCB support).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import contextlib
|
|
16
|
+
import os
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import tempfile
|
|
21
|
+
import time
|
|
22
|
+
import tkinter
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
from PIL import Image, ImageGrab
|
|
26
|
+
|
|
27
|
+
from bootstack.errors import BootstackError
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from screeninfo import get_monitors
|
|
31
|
+
HAS_SCREENINFO = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
HAS_SCREENINFO = False
|
|
34
|
+
|
|
35
|
+
# Platforms whose capture backend ships with the operating system. There is
|
|
36
|
+
# nothing to install and nothing to fall back to, so a failure here is a real
|
|
37
|
+
# failure rather than a missing-backend one.
|
|
38
|
+
_BUILTIN_BACKEND_PLATFORMS = ("win32", "darwin")
|
|
39
|
+
|
|
40
|
+
# How long to pause between event-loop turns while settling. Short enough to
|
|
41
|
+
# stay responsive to the repaint we are waiting for, long enough not to spin.
|
|
42
|
+
_SETTLE_TICK = 0.01
|
|
43
|
+
|
|
44
|
+
# Formats that store plain color and nothing else. A capture bound for one of
|
|
45
|
+
# these is converted first, because saving an image that carries transparency
|
|
46
|
+
# — or a color palette — as JPEG fails outright.
|
|
47
|
+
_NO_ALPHA_FORMATS = {".jpg", ".jpeg", ".pdf", ".bmp"}
|
|
48
|
+
|
|
49
|
+
# Image modes those formats accept as they are.
|
|
50
|
+
_PLAIN_COLOR_MODES = ("RGB", "L")
|
|
51
|
+
|
|
52
|
+
# Tried in order, first one installed wins.
|
|
53
|
+
_SUBPROCESS_BACKENDS = (
|
|
54
|
+
("grim", ["grim", "{out}"]), # wlroots, Sway
|
|
55
|
+
("gnome-screenshot", ["gnome-screenshot", "-f", "{out}"]),
|
|
56
|
+
# `-f` asks for the full screen. Without it spectacle repeats whatever
|
|
57
|
+
# capture mode it was last used with, which is remembered in its own
|
|
58
|
+
# config — so the same call photographs the desktop on one machine and a
|
|
59
|
+
# single window on the next.
|
|
60
|
+
("spectacle", ["spectacle", "-b", "-n", "-f", "-o", "{out}"]), # KDE
|
|
61
|
+
("import", ["import", "-window", "root", "{out}"]), # ImageMagick
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Platforms where cutting the region has to stay `ImageGrab`'s job, because it
|
|
65
|
+
# needs detail this module does not have. Windows crops against the origin its
|
|
66
|
+
# own grab reports, which cannot be reconstructed from a rectangle alone; macOS
|
|
67
|
+
# asks `screencapture` for the region and rescales the answer, which is what
|
|
68
|
+
# keeps the result aligned on a Retina display. Cutting the region here would
|
|
69
|
+
# discard both.
|
|
70
|
+
#
|
|
71
|
+
# This says which code owns the crop, not that the crop is checked — neither of
|
|
72
|
+
# those is. Windows pads a region reaching past the grabbed desktop with black,
|
|
73
|
+
# and macOS stretches a region the system clamped. Extending the bounds check to
|
|
74
|
+
# them is a separate change, and one no box available here can verify.
|
|
75
|
+
#
|
|
76
|
+
# Everywhere else — Linux — `ImageGrab` grabs the whole desktop and crops it
|
|
77
|
+
# with no check that it covers the region, padding the difference with black
|
|
78
|
+
# and raising nothing. That is the failure `_crop_desktop` refuses, so there
|
|
79
|
+
# the region is cut here rather than by the library.
|
|
80
|
+
_LIBRARY_REGION_PLATFORMS = ("win32", "darwin")
|
|
81
|
+
_LIBRARY_HANDLES_REGION = sys.platform in _LIBRARY_REGION_PLATFORMS
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def widget_region(tk_widget, inset: int = 0) -> tuple[int, int, int, int]:
|
|
85
|
+
"""Return a widget's on-screen rectangle as `(left, top, right, bottom)`.
|
|
86
|
+
|
|
87
|
+
Coordinates span the whole virtual desktop, so a widget on a monitor
|
|
88
|
+
placed to the left of, or above, the primary one has a negative origin.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
tk_widget: The toolkit widget to measure.
|
|
92
|
+
inset: Pixels to trim from every edge. Zero or more.
|
|
93
|
+
"""
|
|
94
|
+
if inset < 0:
|
|
95
|
+
raise BootstackError(
|
|
96
|
+
f"inset={inset} is negative. An inset trims pixels from every "
|
|
97
|
+
f"edge, so it cannot reach past the widget onto its neighbors."
|
|
98
|
+
)
|
|
99
|
+
x, y = tk_widget.winfo_rootx(), tk_widget.winfo_rooty()
|
|
100
|
+
width, height = tk_widget.winfo_width(), tk_widget.winfo_height()
|
|
101
|
+
left, top = x + inset, y + inset
|
|
102
|
+
right, bottom = x + width - inset, y + height - inset
|
|
103
|
+
if right <= left or bottom <= top:
|
|
104
|
+
raise BootstackError(
|
|
105
|
+
f"inset={inset} trims away the whole capture — the widget is only "
|
|
106
|
+
f"{width}x{height} pixels."
|
|
107
|
+
)
|
|
108
|
+
return (left, top, right, bottom)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def still_exists(tk_widget) -> bool:
|
|
112
|
+
"""Whether a widget is still alive, without leaking a toolkit error.
|
|
113
|
+
|
|
114
|
+
`winfo_exists()` reports 0 for a destroyed child but RAISES for a destroyed
|
|
115
|
+
root — the toolkit has no interpreter left to ask by then. Both mean the
|
|
116
|
+
same thing to a caller, so both answer False here.
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
return bool(tk_widget.winfo_exists())
|
|
120
|
+
except tkinter.TclError:
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def clipped_out_of_view(tk_widget) -> bool:
|
|
125
|
+
"""Whether a widget's rectangle has left its own window entirely.
|
|
126
|
+
|
|
127
|
+
A widget scrolled out of a viewport stays MAPPED — the toolkit reports
|
|
128
|
+
`winfo_ismapped()` and `winfo_viewable()` as 1 for a row that is nowhere
|
|
129
|
+
near the visible area — while `winfo_rooty()` keeps reporting where the row
|
|
130
|
+
would be if the window were tall enough to show it. Measured: a row in a
|
|
131
|
+
scrolled list reported y=273 for a window spanning 390 to 630.
|
|
132
|
+
|
|
133
|
+
Capturing that rectangle photographs whatever else is on screen there and
|
|
134
|
+
saves it without complaint, which is the one outcome this module exists to
|
|
135
|
+
prevent. Only a rectangle that has left the window completely is refused;
|
|
136
|
+
a row half out of view still captures, matching the rule that a capture
|
|
137
|
+
shows what the screen shows.
|
|
138
|
+
"""
|
|
139
|
+
top = tk_widget.winfo_toplevel()
|
|
140
|
+
if tk_widget is top:
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
left, top_y = tk_widget.winfo_rootx(), tk_widget.winfo_rooty()
|
|
144
|
+
right = left + tk_widget.winfo_width()
|
|
145
|
+
bottom = top_y + tk_widget.winfo_height()
|
|
146
|
+
|
|
147
|
+
win_left, win_top = top.winfo_rootx(), top.winfo_rooty()
|
|
148
|
+
win_right = win_left + top.winfo_width()
|
|
149
|
+
win_bottom = win_top + top.winfo_height()
|
|
150
|
+
|
|
151
|
+
return not (
|
|
152
|
+
left < win_right and right > win_left
|
|
153
|
+
and top_y < win_bottom and bottom > win_top
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@contextlib.contextmanager
|
|
158
|
+
def raised(tk_widget):
|
|
159
|
+
"""Hold a widget's window at the front for the duration of a capture.
|
|
160
|
+
|
|
161
|
+
A capture reads whatever is on the display, so a window sitting over the
|
|
162
|
+
target is captured instead of it — silently, and with a plausible-looking
|
|
163
|
+
image to show for it. Raising the window first is what makes the result
|
|
164
|
+
mean what the caller asked for.
|
|
165
|
+
|
|
166
|
+
The previous always-on-top setting is restored on the way out, and left
|
|
167
|
+
alone entirely if it was already set, so a window the application
|
|
168
|
+
deliberately pinned on top does not get un-pinned by taking a picture.
|
|
169
|
+
"""
|
|
170
|
+
top = tk_widget.winfo_toplevel()
|
|
171
|
+
try:
|
|
172
|
+
top.lift()
|
|
173
|
+
except tkinter.TclError:
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
# None means "left alone" — either the setting could not be read, or the
|
|
177
|
+
# window manager does not support it. Some Linux window managers do not,
|
|
178
|
+
# and a capture must not fail just because it cannot pin a window.
|
|
179
|
+
was_topmost = None
|
|
180
|
+
try:
|
|
181
|
+
was_topmost = bool(top.attributes("-topmost"))
|
|
182
|
+
if not was_topmost:
|
|
183
|
+
top.attributes("-topmost", True)
|
|
184
|
+
except tkinter.TclError:
|
|
185
|
+
was_topmost = None
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
yield
|
|
189
|
+
finally:
|
|
190
|
+
# Restore only what this function actually changed.
|
|
191
|
+
if was_topmost is False:
|
|
192
|
+
try:
|
|
193
|
+
top.attributes("-topmost", False)
|
|
194
|
+
except tkinter.TclError:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def settle(tk_widget, seconds: float) -> None:
|
|
199
|
+
"""Let pending redraws finish before the pixels are read.
|
|
200
|
+
|
|
201
|
+
Waits `seconds` while the event loop turns, so the desktop can repaint
|
|
202
|
+
the area behind any window that has just closed — a save dialog, most
|
|
203
|
+
commonly.
|
|
204
|
+
|
|
205
|
+
The wait has to dispatch events. Flushing our own drawing is not enough:
|
|
206
|
+
the area a closing window uncovers is repainted by the desktop, and on
|
|
207
|
+
macOS that repaint does not happen at all unless the loop is turning. A
|
|
208
|
+
capture taken without it photographs the dialog that was just dismissed
|
|
209
|
+
rather than the widget underneath.
|
|
210
|
+
|
|
211
|
+
Dispatching brings its own problem, which is why the window is held busy
|
|
212
|
+
for the duration. Turning the loop runs everything queued, including a
|
|
213
|
+
second click on the button that started the capture: that click would
|
|
214
|
+
re-enter the caller's handler, opening a second save dialog on top of the
|
|
215
|
+
first and starting an overlapping capture, none of which the person
|
|
216
|
+
clicking asked for. `tk busy` routes that click to a blocking window
|
|
217
|
+
instead.
|
|
218
|
+
|
|
219
|
+
⚠ THE HOLD IS BEST-EFFORT, AND ON macOS IT DOES NOTHING. Aqua reports the
|
|
220
|
+
hold as successful and never maps the window it created: `tk busy status`
|
|
221
|
+
returns 1 while the busy window sits at `winfo_ismapped() == 0`, and Tk's
|
|
222
|
+
own hit test at the button's position still resolves to the button. So a
|
|
223
|
+
real click re-enters the handler there — confirmed by hand, since a
|
|
224
|
+
synthesized click cannot test this. Measured on Tk 8.6.17 in plain
|
|
225
|
+
tkinter, so it is the toolkit rather than anything above it. X11 and Win32
|
|
226
|
+
map the window and are expected to honor it;
|
|
227
|
+
`development/probe_429_busy_during_settle.py` reports which a given box
|
|
228
|
+
does.
|
|
229
|
+
|
|
230
|
+
The wait dispatches either way. A correct picture is worth more than a
|
|
231
|
+
stray click: without dispatching, the capture reads pixels that are simply
|
|
232
|
+
wrong, and that is the failure this function exists to prevent.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
tk_widget: Any widget; its root window is the one flushed.
|
|
236
|
+
seconds: How long to wait for. Zero flushes without waiting.
|
|
237
|
+
"""
|
|
238
|
+
root = tk_widget._root()
|
|
239
|
+
root.update_idletasks()
|
|
240
|
+
with _input_blocked(tk_widget):
|
|
241
|
+
deadline = time.perf_counter() + max(seconds, 0.0)
|
|
242
|
+
while time.perf_counter() < deadline:
|
|
243
|
+
root.update()
|
|
244
|
+
# Yield the processor rather than spinning on update() alone.
|
|
245
|
+
time.sleep(_SETTLE_TICK)
|
|
246
|
+
# Once more after the wait, so anything the pause allowed to arrive
|
|
247
|
+
# is drawn before the pixels are read.
|
|
248
|
+
root.update()
|
|
249
|
+
root.update_idletasks()
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@contextlib.contextmanager
|
|
253
|
+
def _input_blocked(tk_widget):
|
|
254
|
+
"""Route input away from a window while the event loop is being turned.
|
|
255
|
+
|
|
256
|
+
Yields either way: a window that cannot be held busy is still captured,
|
|
257
|
+
just without the protection from a stray click.
|
|
258
|
+
|
|
259
|
+
⚠ Yielding `True` means the hold was ACCEPTED, not that input is actually
|
|
260
|
+
blocked — macOS accepts it and does not map the busy window. See `settle`.
|
|
261
|
+
|
|
262
|
+
⚠ The target may already be DEAD by the time this runs — settling flushes
|
|
263
|
+
pending drawing first, and idle work queued before the capture can destroy
|
|
264
|
+
it. Asking a destroyed widget for its toplevel raises a raw `TclError`,
|
|
265
|
+
which would escape a method documented to raise `BootstackError` and
|
|
266
|
+
pre-empt the guard that reports the closure properly. So the lookup is
|
|
267
|
+
guarded too, not just the `tk busy` calls.
|
|
268
|
+
"""
|
|
269
|
+
top = None
|
|
270
|
+
held = False
|
|
271
|
+
try:
|
|
272
|
+
top = tk_widget.winfo_toplevel()
|
|
273
|
+
top.tk.call("tk", "busy", "hold", top._w)
|
|
274
|
+
held = True
|
|
275
|
+
except tkinter.TclError:
|
|
276
|
+
held = False
|
|
277
|
+
try:
|
|
278
|
+
yield held
|
|
279
|
+
finally:
|
|
280
|
+
if held and top is not None:
|
|
281
|
+
try:
|
|
282
|
+
top.tk.call("tk", "busy", "forget", top._w)
|
|
283
|
+
except tkinter.TclError:
|
|
284
|
+
pass
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def capture_region(bbox: tuple[int, int, int, int], path) -> Path:
|
|
288
|
+
"""Grab one screen region and write it to `path`, returning the path."""
|
|
289
|
+
return save(grab(bbox), path)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def grab(bbox: tuple[int, int, int, int] | None = None) -> Image.Image:
|
|
293
|
+
"""Grab a screen region, or the whole primary display when `bbox` is None."""
|
|
294
|
+
try:
|
|
295
|
+
if _LIBRARY_HANDLES_REGION:
|
|
296
|
+
# `all_screens` widens the grab to the whole virtual desktop.
|
|
297
|
+
# Without it, a region on a monitor left of or above the primary
|
|
298
|
+
# one is cropped out of a primary-only grab and comes back
|
|
299
|
+
# silently BLACK — no exception raised and nothing to debug.
|
|
300
|
+
return ImageGrab.grab(bbox=bbox, all_screens=bbox is not None)
|
|
301
|
+
# Grab the whole desktop and cut the region out below, under the
|
|
302
|
+
# bounds check. This platform grabs everything and crops either way,
|
|
303
|
+
# so what changes is where the crop happens, not how much is read.
|
|
304
|
+
image = ImageGrab.grab()
|
|
305
|
+
except (OSError, NotImplementedError) as exc:
|
|
306
|
+
# Diagnosis happens only AFTER a failed grab, never before one, so
|
|
307
|
+
# nothing here can reject a capture that would have worked.
|
|
308
|
+
#
|
|
309
|
+
# The catch is deliberately wide because the failures it has to cover
|
|
310
|
+
# are: no ImageGrab on this build at all, and a grab that ran and came
|
|
311
|
+
# back with nothing usable. Those need opposite answers, and the
|
|
312
|
+
# exception type cannot tell them apart — Pillow reports a region no
|
|
313
|
+
# display covers as `UnidentifiedImageError`, which IS an `OSError`.
|
|
314
|
+
if bbox is not None and _covered_by_a_display(bbox) is False:
|
|
315
|
+
raise BootstackError(
|
|
316
|
+
f"The area being captured — ({bbox[0]}, {bbox[1]}) to "
|
|
317
|
+
f"({bbox[2]}, {bbox[3]}) — is not on any display, so there are "
|
|
318
|
+
f"no pixels to read. A window moved off the edge of the screen, "
|
|
319
|
+
f"or a widget scrolled out of view, is the usual cause."
|
|
320
|
+
) from exc
|
|
321
|
+
|
|
322
|
+
if sys.platform in _BUILTIN_BACKEND_PLATFORMS:
|
|
323
|
+
# Falling through to the subprocess chain here would end in advice
|
|
324
|
+
# to install Linux screenshot tools on a machine that already has a
|
|
325
|
+
# working backend — wrong on its face and no help in fixing this.
|
|
326
|
+
raise BootstackError(
|
|
327
|
+
f"The screen capture failed — {exc}. The window may have been "
|
|
328
|
+
f"hidden or moved while the picture was being taken."
|
|
329
|
+
) from exc
|
|
330
|
+
|
|
331
|
+
# Wayland, or a Pillow built without XCB.
|
|
332
|
+
return _grab_via_subprocess(bbox)
|
|
333
|
+
# Outside the `except` on purpose: a region the grab missed is a capture
|
|
334
|
+
# failure to report, not a reason to try another backend.
|
|
335
|
+
return _crop_desktop(image, bbox) if bbox else image
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _covered_by_a_display(bbox: tuple[int, int, int, int]) -> bool | None:
|
|
339
|
+
"""Whether any monitor covers part of `bbox`.
|
|
340
|
+
|
|
341
|
+
Returns None when the question cannot be answered — no `screeninfo`, or it
|
|
342
|
+
could not read the display layout — so a caller can tell "definitely not on
|
|
343
|
+
a display" apart from "could not tell", and only act on the former.
|
|
344
|
+
|
|
345
|
+
Partial overlap counts as covered. A window hanging off the bottom edge of
|
|
346
|
+
the screen captures perfectly well; only a region no monitor touches at all
|
|
347
|
+
has nothing to read.
|
|
348
|
+
"""
|
|
349
|
+
if not HAS_SCREENINFO:
|
|
350
|
+
return None
|
|
351
|
+
|
|
352
|
+
left, top, right, bottom = bbox
|
|
353
|
+
try:
|
|
354
|
+
monitors = get_monitors()
|
|
355
|
+
except Exception: # noqa: BLE001 - a diagnostic must not raise its own error
|
|
356
|
+
return None
|
|
357
|
+
if not monitors:
|
|
358
|
+
return None
|
|
359
|
+
|
|
360
|
+
return any(
|
|
361
|
+
left < m.x + m.width and right > m.x
|
|
362
|
+
and top < m.y + m.height and bottom > m.y
|
|
363
|
+
for m in monitors
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def save(image: Image.Image, path) -> Path:
|
|
368
|
+
"""Write an image to `path`, taking the format from its extension."""
|
|
369
|
+
path = Path(path)
|
|
370
|
+
# Decided by the target format rather than one source mode: a desktop
|
|
371
|
+
# screenshot tool can hand back a palette or grayscale-with-alpha image,
|
|
372
|
+
# and those fail on the same formats RGBA does.
|
|
373
|
+
if (
|
|
374
|
+
path.suffix.lower() in _NO_ALPHA_FORMATS
|
|
375
|
+
and image.mode not in _PLAIN_COLOR_MODES
|
|
376
|
+
):
|
|
377
|
+
image = image.convert("RGB")
|
|
378
|
+
try:
|
|
379
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
380
|
+
image.save(path)
|
|
381
|
+
except (OSError, ValueError, KeyError) as exc:
|
|
382
|
+
raise BootstackError(
|
|
383
|
+
f"Could not save the capture to '{path}' — {exc}. The file "
|
|
384
|
+
f"extension selects the format; '.png', '.jpg', and '.pdf' are "
|
|
385
|
+
f"the usual choices."
|
|
386
|
+
) from exc
|
|
387
|
+
return path
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _grab_via_subprocess(
|
|
391
|
+
bbox: tuple[int, int, int, int] | None = None,
|
|
392
|
+
) -> Image.Image:
|
|
393
|
+
"""Capture through a desktop screenshot tool, for Linux without ImageGrab."""
|
|
394
|
+
for name, argv in _SUBPROCESS_BACKENDS:
|
|
395
|
+
if not shutil.which(name):
|
|
396
|
+
continue
|
|
397
|
+
# A unique name per call — a fixed one collides when two captures
|
|
398
|
+
# overlap, and the loser silently reads the other's pixels.
|
|
399
|
+
handle, tmp_name = tempfile.mkstemp(
|
|
400
|
+
prefix="bootstack-capture-", suffix=".png"
|
|
401
|
+
)
|
|
402
|
+
os.close(handle)
|
|
403
|
+
tmp = Path(tmp_name)
|
|
404
|
+
try:
|
|
405
|
+
subprocess.run(
|
|
406
|
+
[arg.format(out=tmp) for arg in argv],
|
|
407
|
+
check=True,
|
|
408
|
+
capture_output=True,
|
|
409
|
+
timeout=30,
|
|
410
|
+
)
|
|
411
|
+
with Image.open(tmp) as opened:
|
|
412
|
+
opened.load()
|
|
413
|
+
image = opened.copy()
|
|
414
|
+
except (subprocess.SubprocessError, OSError):
|
|
415
|
+
continue
|
|
416
|
+
finally:
|
|
417
|
+
tmp.unlink(missing_ok=True)
|
|
418
|
+
return _crop_desktop(image, bbox) if bbox else image
|
|
419
|
+
|
|
420
|
+
raise BootstackError(
|
|
421
|
+
"No screen capture backend is available. Install a Pillow build with "
|
|
422
|
+
"XCB support, or one of: grim, gnome-screenshot, spectacle, import."
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _crop_desktop(
|
|
427
|
+
image: Image.Image, bbox: tuple[int, int, int, int]
|
|
428
|
+
) -> Image.Image:
|
|
429
|
+
"""Cut a region out of a whole-desktop grab, refusing a region it misses.
|
|
430
|
+
|
|
431
|
+
A whole-desktop grab returns an image measured from whatever its source
|
|
432
|
+
treats as the origin, while the region is measured across the whole virtual
|
|
433
|
+
desktop. Those agree on an ordinary single-monitor session and can disagree
|
|
434
|
+
otherwise — most often when the window sits on a monitor the grab did not
|
|
435
|
+
cover. Cropping past the edge of an image pads the result with black rather
|
|
436
|
+
than failing, so the caller would get a plausible-looking all-black picture
|
|
437
|
+
and no way to tell why. Raising is the better answer.
|
|
438
|
+
"""
|
|
439
|
+
left, top, right, bottom = bbox
|
|
440
|
+
if left < 0 or top < 0 or right > image.width or bottom > image.height:
|
|
441
|
+
raise BootstackError(
|
|
442
|
+
f"The screen capture returned a {image.width}x{image.height} "
|
|
443
|
+
f"picture, which does not cover the area being captured — "
|
|
444
|
+
f"({left}, {top}) to ({right}, {bottom}). Either the window is "
|
|
445
|
+
f"hanging off the edge of the screen, or it is on a display the "
|
|
446
|
+
f"capture did not reach. Moving it fully onto the main display "
|
|
447
|
+
f"resolves both."
|
|
448
|
+
)
|
|
449
|
+
return image.crop(bbox)
|
|
@@ -436,7 +436,12 @@ class FormDialog:
|
|
|
436
436
|
on_close: Callback fired when the dialog closes by any means.
|
|
437
437
|
width: Explicit form width in pixels.
|
|
438
438
|
height: Explicit form height in pixels.
|
|
439
|
-
buttons: Footer button specs. Defaults to Cancel + OK.
|
|
439
|
+
buttons: Footer button specs. Defaults to Cancel + OK. A button's
|
|
440
|
+
`result=` decides whether it submits the form: `'ok'`, `'submit'`
|
|
441
|
+
and `'save'` return the entered data, and so does a button with no
|
|
442
|
+
`result=` at all. Give an action button — a Delete, say — a result
|
|
443
|
+
token of its own, and it runs without the form having to validate
|
|
444
|
+
and hands that token back instead of the data.
|
|
440
445
|
min_size: Minimum dialog window size `(width, height)`.
|
|
441
446
|
max_size: Maximum dialog window size `(width, height)`.
|
|
442
447
|
resizable: Allow window resizing. Default `False`.
|
|
@@ -16,7 +16,8 @@ from bootstack.widgets._impl.primitives import Frame
|
|
|
16
16
|
from bootstack.constants import BOTH, YES
|
|
17
17
|
from bootstack.events import Subscription
|
|
18
18
|
from bootstack.dialogs._impl.dialog import (
|
|
19
|
-
DIALOG_RESULT, Dialog, DialogButton, emit_dialog_result,
|
|
19
|
+
DIALOG_RESULT, Dialog, DialogButton, capture_grab, emit_dialog_result, restore_grab,
|
|
20
|
+
result_target,
|
|
20
21
|
)
|
|
21
22
|
from bootstack._runtime.window_utilities import AnchorPoint
|
|
22
23
|
from bootstack.widgets._impl.composites.calendar import Calendar
|
|
@@ -78,6 +79,22 @@ class _ChromeDialog(Dialog):
|
|
|
78
79
|
self._build_content()
|
|
79
80
|
self._build_footer()
|
|
80
81
|
|
|
82
|
+
# This override duplicates `Dialog.show()` rather than extending it, so
|
|
83
|
+
# the initial-focus resolution has to be repeated here — see
|
|
84
|
+
# `Dialog._focus_when_mapped` (issue #439).
|
|
85
|
+
#
|
|
86
|
+
# This reaches only RANGE mode. Single-date mode commits the moment a
|
|
87
|
+
# date is clicked and so builds no footer buttons (see `__init__`),
|
|
88
|
+
# leaving `_default_button` None; nothing sets `_focus_target` for the
|
|
89
|
+
# raw calendar content either. So `ask_date()` still opens with focus on
|
|
90
|
+
# the toplevel itself, exactly as before — measured, not assumed. Giving
|
|
91
|
+
# the single-date calendar a real focus target is a separate change,
|
|
92
|
+
# since it would put a focus ring on a day cell and make the arrow keys
|
|
93
|
+
# move the selection.
|
|
94
|
+
focus_target = self._focus_target or self._default_button
|
|
95
|
+
if focus_target is not None:
|
|
96
|
+
self._focus_when_mapped(focus_target)
|
|
97
|
+
|
|
81
98
|
self._position_dialog(
|
|
82
99
|
position=position,
|
|
83
100
|
anchor_to=anchor_to,
|
|
@@ -110,9 +127,17 @@ class _ChromeDialog(Dialog):
|
|
|
110
127
|
if modal:
|
|
111
128
|
# transient() is already called in Dialog._create_toplevel()
|
|
112
129
|
# Don't call it again here as it breaks mica effect on Windows
|
|
130
|
+
previous_grab = None
|
|
113
131
|
if self._mode == "modal":
|
|
132
|
+
# Remember who held the grab, and how, so it can be handed back
|
|
133
|
+
# — a nested modal must not leave its opener non-modal (#440),
|
|
134
|
+
# nor narrow an app-modal window to a local grab.
|
|
135
|
+
previous_grab = capture_grab(self._toplevel)
|
|
114
136
|
self._toplevel.grab_set()
|
|
115
|
-
|
|
137
|
+
try:
|
|
138
|
+
self._master.wait_window(self._toplevel)
|
|
139
|
+
finally:
|
|
140
|
+
restore_grab(previous_grab)
|
|
116
141
|
|
|
117
142
|
def _on_focus_out(self, event: tkinter.Event):
|
|
118
143
|
if self._suppress_focus_out:
|
|
@@ -260,7 +285,6 @@ class DateDialog:
|
|
|
260
285
|
text="button.ok",
|
|
261
286
|
role="primary",
|
|
262
287
|
default=True,
|
|
263
|
-
closes=False,
|
|
264
288
|
command=lambda dlg: self._on_confirm_range(),
|
|
265
289
|
),
|
|
266
290
|
]
|
|
@@ -318,22 +342,39 @@ class DateDialog:
|
|
|
318
342
|
|
|
319
343
|
self._confirm(selected)
|
|
320
344
|
|
|
321
|
-
def _on_confirm_range(self) ->
|
|
345
|
+
def _on_confirm_range(self) -> bool:
|
|
322
346
|
"""Confirm the selected range when OK is clicked.
|
|
323
347
|
|
|
324
|
-
|
|
348
|
+
Refuses the press — leaving the dialog open — until both endpoints are
|
|
349
|
+
selected. The OK button hands that answer back to `Dialog`, which owns
|
|
350
|
+
the close.
|
|
351
|
+
|
|
352
|
+
Unlike `_confirm`, this releases no grab of its own. It does not need
|
|
353
|
+
to: `Dialog` destroys the toplevel, and Tk releases a grab held by a
|
|
354
|
+
window when that window is destroyed (measured — see
|
|
355
|
+
`development/probe_437_review2_fixes.py`). `_confirm` keeps its
|
|
356
|
+
`grab_release()` because it destroys the window itself, where the call
|
|
357
|
+
is at least self-documenting.
|
|
325
358
|
"""
|
|
326
359
|
if not self._picker:
|
|
327
|
-
return
|
|
360
|
+
return False
|
|
328
361
|
start, end = self._picker.get_range()
|
|
329
362
|
if start is None or end is None:
|
|
330
|
-
return
|
|
331
|
-
self.
|
|
363
|
+
return False
|
|
364
|
+
self._record((start, end))
|
|
365
|
+
return True
|
|
332
366
|
|
|
333
|
-
def
|
|
334
|
-
"""Record the result
|
|
367
|
+
def _record(self, result: date | Tuple[date, date]) -> None:
|
|
368
|
+
"""Record the result and notify listeners, without closing."""
|
|
335
369
|
self._dialog.result = result
|
|
336
370
|
self._emit_result(result, confirmed=True)
|
|
371
|
+
|
|
372
|
+
def _confirm(self, result: date | Tuple[date, date]) -> None:
|
|
373
|
+
"""Record the result, emit, and close the dialog.
|
|
374
|
+
|
|
375
|
+
Used by the commit-on-select path, which has no button behind it.
|
|
376
|
+
"""
|
|
377
|
+
self._record(result)
|
|
337
378
|
if self._dialog.toplevel:
|
|
338
379
|
top = self._dialog.toplevel
|
|
339
380
|
try:
|