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,450 @@
|
|
|
1
|
+
"""Dialog wrapper around the Calendar widget.
|
|
2
|
+
|
|
3
|
+
Exposes a chrome-less, popover-capable date picker dialog that can close on
|
|
4
|
+
outside clicks and forwards Calendar options (disabled dates, bounds, etc.).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import tkinter
|
|
10
|
+
from datetime import date, datetime
|
|
11
|
+
from types import SimpleNamespace
|
|
12
|
+
from typing import Any, Callable, Iterable, Literal, Optional, Tuple, Union
|
|
13
|
+
from tkinter import Widget
|
|
14
|
+
|
|
15
|
+
from bootstack.widgets._impl.primitives import Frame
|
|
16
|
+
from bootstack.constants import BOTH, YES
|
|
17
|
+
from bootstack.dialogs._impl.dialog import Dialog, DialogButton
|
|
18
|
+
from bootstack._runtime.window_utilities import AnchorPoint
|
|
19
|
+
from bootstack.widgets._impl.composites.calendar import Calendar
|
|
20
|
+
|
|
21
|
+
ttk = SimpleNamespace(Frame=Frame)
|
|
22
|
+
|
|
23
|
+
__all__ = ["DateDialog"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _ChromeDialog(Dialog):
|
|
27
|
+
"""Dialog that can optionally hide window chrome via override-redirect."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, *args: Any, hide_window_chrome: bool = False, **kwargs: Any) -> None:
|
|
30
|
+
self._hide_window_chrome = hide_window_chrome
|
|
31
|
+
self._suppress_focus_out = False
|
|
32
|
+
self._outside_click_binding: str | None = None
|
|
33
|
+
super().__init__(*args, **kwargs)
|
|
34
|
+
|
|
35
|
+
def _create_toplevel(self):
|
|
36
|
+
super()._create_toplevel()
|
|
37
|
+
if self._hide_window_chrome and self._toplevel:
|
|
38
|
+
# Note: overrideredirect is automatically disabled on macOS
|
|
39
|
+
# in BaseWindow.overrideredirect() due to Tk/Cocoa issues
|
|
40
|
+
try:
|
|
41
|
+
self._toplevel.overrideredirect(True)
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
def show(
|
|
46
|
+
self,
|
|
47
|
+
*,
|
|
48
|
+
position: Optional[Tuple[int, int]] = None,
|
|
49
|
+
modal: Optional[bool] = None,
|
|
50
|
+
anchor_to: Optional[Union[Widget, Literal["screen", "cursor", "parent"]]] = None,
|
|
51
|
+
anchor_point: AnchorPoint = 'center',
|
|
52
|
+
window_point: AnchorPoint = 'center',
|
|
53
|
+
offset: Tuple[int, int] = (0, 0),
|
|
54
|
+
auto_flip: Union[bool, Literal['vertical', 'horizontal']] = False
|
|
55
|
+
):
|
|
56
|
+
"""Override show to position before deiconify, avoiding placement flash.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
position: Optional (x, y) coordinates to position the dialog.
|
|
60
|
+
modal: Override the mode's default modality.
|
|
61
|
+
anchor_to: Positioning target (Widget, "screen", "cursor", "parent", or None).
|
|
62
|
+
anchor_point: Point on the anchor target (n, s, e, w, ne, nw, se, sw, center).
|
|
63
|
+
window_point: Point on the dialog window (n, s, e, w, ne, nw, se, sw, center).
|
|
64
|
+
offset: Additional (x, y) offset in pixels from the anchor position.
|
|
65
|
+
auto_flip: Smart positioning to keep window on screen.
|
|
66
|
+
"""
|
|
67
|
+
if modal is None:
|
|
68
|
+
modal = (self._mode == "modal")
|
|
69
|
+
|
|
70
|
+
self.result = None
|
|
71
|
+
self._create_toplevel()
|
|
72
|
+
if self._hide_window_chrome and self._toplevel:
|
|
73
|
+
self._toplevel.withdraw()
|
|
74
|
+
|
|
75
|
+
self._build_content()
|
|
76
|
+
self._build_footer()
|
|
77
|
+
|
|
78
|
+
self._position_dialog(
|
|
79
|
+
position=position,
|
|
80
|
+
anchor_to=anchor_to,
|
|
81
|
+
anchor_point=anchor_point,
|
|
82
|
+
window_point=window_point,
|
|
83
|
+
offset=offset,
|
|
84
|
+
auto_flip=auto_flip
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if self._hide_window_chrome and self._toplevel:
|
|
88
|
+
self._toplevel.deiconify()
|
|
89
|
+
try:
|
|
90
|
+
self._toplevel.lift()
|
|
91
|
+
self._toplevel.focus_force()
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
if self._alert:
|
|
96
|
+
self._toplevel.bell()
|
|
97
|
+
|
|
98
|
+
if self._mode == "popover":
|
|
99
|
+
self._suppress_focus_out = True
|
|
100
|
+
self._toplevel.bind("<FocusOut>", self._on_focus_out, add="+")
|
|
101
|
+
try:
|
|
102
|
+
self._toplevel.after(50, lambda: setattr(self, "_suppress_focus_out", False))
|
|
103
|
+
except Exception:
|
|
104
|
+
self._suppress_focus_out = False
|
|
105
|
+
self._bind_outside_click()
|
|
106
|
+
|
|
107
|
+
if modal:
|
|
108
|
+
# transient() is already called in Dialog._create_toplevel()
|
|
109
|
+
# Don't call it again here as it breaks mica effect on Windows
|
|
110
|
+
if self._mode == "modal":
|
|
111
|
+
self._toplevel.grab_set()
|
|
112
|
+
self._master.wait_window(self._toplevel)
|
|
113
|
+
|
|
114
|
+
def _on_focus_out(self, event: tkinter.Event):
|
|
115
|
+
if self._suppress_focus_out:
|
|
116
|
+
return
|
|
117
|
+
return super()._on_focus_out(event)
|
|
118
|
+
|
|
119
|
+
def _bind_outside_click(self) -> None:
|
|
120
|
+
"""Close popover when clicking outside the toplevel."""
|
|
121
|
+
if not self._toplevel:
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
def handler(event: tkinter.Event) -> None:
|
|
125
|
+
widget = getattr(event, "widget", None)
|
|
126
|
+
if widget is None:
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
# Walk up the widget hierarchy to check if any parent is the toplevel
|
|
130
|
+
current = widget
|
|
131
|
+
toplevel_str = str(self._toplevel)
|
|
132
|
+
while current:
|
|
133
|
+
current_str = str(current)
|
|
134
|
+
if current_str == toplevel_str or current_str.startswith(toplevel_str + "."):
|
|
135
|
+
return
|
|
136
|
+
try:
|
|
137
|
+
current = current.master
|
|
138
|
+
except AttributeError:
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
# Schedule destroy for after current event processing completes
|
|
142
|
+
# This allows button commands inside the dialog to execute first
|
|
143
|
+
try:
|
|
144
|
+
if self._toplevel.winfo_exists():
|
|
145
|
+
self._toplevel.after_idle(lambda: self._destroy_if_exists())
|
|
146
|
+
except Exception:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
self._outside_click_binding = self._toplevel.bind_all("<ButtonPress-1>", handler, add="+")
|
|
151
|
+
self._toplevel.bind("<Destroy>", lambda e: self._unbind_outside_click(), add="+")
|
|
152
|
+
except Exception:
|
|
153
|
+
self._outside_click_binding = None
|
|
154
|
+
|
|
155
|
+
def _destroy_if_exists(self) -> None:
|
|
156
|
+
"""Destroy the toplevel if it still exists."""
|
|
157
|
+
try:
|
|
158
|
+
if self._toplevel and self._toplevel.winfo_exists():
|
|
159
|
+
self._toplevel.destroy()
|
|
160
|
+
except Exception:
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
def _unbind_outside_click(self) -> None:
|
|
164
|
+
if self._toplevel and self._outside_click_binding:
|
|
165
|
+
try:
|
|
166
|
+
self._toplevel.unbind_all("<ButtonPress-1>", self._outside_click_binding)
|
|
167
|
+
except Exception:
|
|
168
|
+
pass
|
|
169
|
+
self._outside_click_binding = None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class _DialogCalendar(Calendar):
|
|
173
|
+
"""Calendar variant that records why a selection event fired."""
|
|
174
|
+
|
|
175
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
176
|
+
super().__init__(*args, **kwargs)
|
|
177
|
+
self._last_trigger_reason: str | None = None
|
|
178
|
+
|
|
179
|
+
def _on_reset_date(self, *args: Any) -> None:
|
|
180
|
+
self._last_trigger_reason = "reset"
|
|
181
|
+
super()._on_reset_date(*args)
|
|
182
|
+
|
|
183
|
+
def _on_date_selected_by_date(self, target: date) -> None:
|
|
184
|
+
self._last_trigger_reason = "select"
|
|
185
|
+
super()._on_date_selected_by_date(target)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class DateDialog:
|
|
189
|
+
"""Modal dialog that displays a Calendar"""
|
|
190
|
+
|
|
191
|
+
def __init__(
|
|
192
|
+
self,
|
|
193
|
+
master: Optional[tkinter.Misc] = None,
|
|
194
|
+
title: str = " ",
|
|
195
|
+
initial_date: Optional[date] = None,
|
|
196
|
+
first_weekday: int = 6,
|
|
197
|
+
accent: str = None,
|
|
198
|
+
disabled_dates: Optional[Iterable[date | datetime | str]] = None,
|
|
199
|
+
min_date: Optional[date | datetime | str] = None,
|
|
200
|
+
max_date: Optional[date | datetime | str] = None,
|
|
201
|
+
show_outside_days: Optional[bool] = None,
|
|
202
|
+
show_week_numbers: bool = False,
|
|
203
|
+
hide_window_chrome: bool = False,
|
|
204
|
+
close_on_click_outside: bool = False,
|
|
205
|
+
selection_mode: Literal["single", "range"] = "single",
|
|
206
|
+
start_date: Optional[date | datetime | str] = None,
|
|
207
|
+
end_date: Optional[date | datetime | str] = None,
|
|
208
|
+
) -> None:
|
|
209
|
+
"""Create a date selection dialog.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
master: Parent widget; positions dialog relative to it when set.
|
|
213
|
+
title: Dialog window title text.
|
|
214
|
+
initial_date: Initial date shown in single mode; defaults to `date.today()`.
|
|
215
|
+
first_weekday: First weekday index (0=Monday, 6=Sunday).
|
|
216
|
+
accent: Calendar accent token (e.g., `primary`, `secondary`).
|
|
217
|
+
disabled_dates: Iterable of dates to disable selection.
|
|
218
|
+
min_date: Lower bound for selectable dates.
|
|
219
|
+
max_date: Upper bound for selectable dates.
|
|
220
|
+
show_outside_days: Whether to show outside-month days. Defaults to
|
|
221
|
+
the Calendar behavior (True for single month).
|
|
222
|
+
show_week_numbers: Display ISO week numbers beside each row.
|
|
223
|
+
hide_window_chrome: When True, displays the dialog with no window
|
|
224
|
+
decorations using override-redirect.
|
|
225
|
+
close_on_click_outside: When True, closes the dialog when focus
|
|
226
|
+
moves outside (popover mode).
|
|
227
|
+
selection_mode: `'single'` for a single date (default) or `'range'`
|
|
228
|
+
for a start/end date range. In range mode `result` is a
|
|
229
|
+
`tuple[date, date]` rather than a `date`.
|
|
230
|
+
start_date: Initial range start date (range mode only).
|
|
231
|
+
end_date: Initial range end date (range mode only).
|
|
232
|
+
"""
|
|
233
|
+
self._master = master
|
|
234
|
+
self._first_weekday = first_weekday
|
|
235
|
+
self._initial_date = initial_date or datetime.today().date()
|
|
236
|
+
self._accent = accent or 'primary'
|
|
237
|
+
self._disabled_dates = disabled_dates
|
|
238
|
+
self._min_date = min_date
|
|
239
|
+
self._max_date = max_date
|
|
240
|
+
self._show_outside_days = show_outside_days
|
|
241
|
+
self._show_week_numbers = show_week_numbers
|
|
242
|
+
self._hide_window_chrome = hide_window_chrome
|
|
243
|
+
self._close_on_click_outside = close_on_click_outside
|
|
244
|
+
self._selection_mode = selection_mode
|
|
245
|
+
self._start_date = start_date
|
|
246
|
+
self._end_date = end_date
|
|
247
|
+
|
|
248
|
+
self._picker: Optional[_DialogCalendar] = None
|
|
249
|
+
|
|
250
|
+
# Range mode uses an explicit OK/Cancel footer instead of committing the
|
|
251
|
+
# moment the second date is clicked; single mode keeps commit-on-select.
|
|
252
|
+
buttons: list[DialogButton] = []
|
|
253
|
+
if self._selection_mode == "range":
|
|
254
|
+
buttons = [
|
|
255
|
+
DialogButton(text="button.cancel", role="cancel", result=None),
|
|
256
|
+
DialogButton(
|
|
257
|
+
text="button.ok",
|
|
258
|
+
role="primary",
|
|
259
|
+
default=True,
|
|
260
|
+
closes=False,
|
|
261
|
+
command=lambda dlg: self._on_confirm_range(),
|
|
262
|
+
),
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
self._dialog = _ChromeDialog(
|
|
266
|
+
title=title,
|
|
267
|
+
content_builder=self._create_content,
|
|
268
|
+
_raw_content=True,
|
|
269
|
+
buttons=buttons,
|
|
270
|
+
footer_builder=None,
|
|
271
|
+
hide_window_chrome=self._hide_window_chrome,
|
|
272
|
+
mode="popover" if self._close_on_click_outside else "modal",
|
|
273
|
+
parent=master,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
def _create_content(self, master: tkinter.Widget) -> None:
|
|
277
|
+
"""Build the Calendar content inside the dialog."""
|
|
278
|
+
container = ttk.Frame(master, padding=2, show_border=True)
|
|
279
|
+
container.pack(fill=BOTH, expand=YES)
|
|
280
|
+
|
|
281
|
+
self._picker = _DialogCalendar(
|
|
282
|
+
master=container,
|
|
283
|
+
selection_mode=self._selection_mode,
|
|
284
|
+
start_date=self._start_date if self._selection_mode == "range" else self._initial_date,
|
|
285
|
+
end_date=self._end_date if self._selection_mode == "range" else None,
|
|
286
|
+
first_weekday=self._first_weekday,
|
|
287
|
+
accent=self._accent,
|
|
288
|
+
disabled_dates=self._disabled_dates,
|
|
289
|
+
min_date=self._min_date,
|
|
290
|
+
max_date=self._max_date,
|
|
291
|
+
show_outside_days=self._show_outside_days,
|
|
292
|
+
show_week_numbers=self._show_week_numbers,
|
|
293
|
+
padding=0,
|
|
294
|
+
)
|
|
295
|
+
self._picker.pack(fill=BOTH, expand=YES)
|
|
296
|
+
self._picker.on_date_selected(self._on_date_selected)
|
|
297
|
+
|
|
298
|
+
def _on_date_selected(self, event: tkinter.Event) -> None:
|
|
299
|
+
"""Handle <<DateSelect>> from the embedded Calendar (single mode only).
|
|
300
|
+
|
|
301
|
+
Range mode does not commit on selection — the user confirms the range
|
|
302
|
+
with the OK button (see `_on_confirm_range`).
|
|
303
|
+
"""
|
|
304
|
+
if not self._picker or self._selection_mode == "range":
|
|
305
|
+
return
|
|
306
|
+
trigger_reason = getattr(self._picker, "_last_trigger_reason", None)
|
|
307
|
+
if trigger_reason != "select":
|
|
308
|
+
return
|
|
309
|
+
|
|
310
|
+
payload = getattr(event, "data", None)
|
|
311
|
+
selected = getattr(payload, "date", None)
|
|
312
|
+
selected = selected or self._picker.get()
|
|
313
|
+
if selected is None:
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
self._confirm(selected)
|
|
317
|
+
|
|
318
|
+
def _on_confirm_range(self) -> None:
|
|
319
|
+
"""Confirm the selected range when OK is clicked.
|
|
320
|
+
|
|
321
|
+
No-op (dialog stays open) until both endpoints are selected.
|
|
322
|
+
"""
|
|
323
|
+
if not self._picker:
|
|
324
|
+
return
|
|
325
|
+
start, end = self._picker.get_range()
|
|
326
|
+
if start is None or end is None:
|
|
327
|
+
return
|
|
328
|
+
self._confirm((start, end))
|
|
329
|
+
|
|
330
|
+
def _confirm(self, result: date | Tuple[date, date]) -> None:
|
|
331
|
+
"""Record the result, emit, and close the dialog."""
|
|
332
|
+
self._dialog.result = result
|
|
333
|
+
self._emit_result(result, confirmed=True)
|
|
334
|
+
if self._dialog.toplevel:
|
|
335
|
+
top = self._dialog.toplevel
|
|
336
|
+
try:
|
|
337
|
+
top.grab_release()
|
|
338
|
+
except Exception:
|
|
339
|
+
pass
|
|
340
|
+
top.destroy()
|
|
341
|
+
|
|
342
|
+
def show(
|
|
343
|
+
self,
|
|
344
|
+
*,
|
|
345
|
+
position: Optional[Tuple[int, int]] = None,
|
|
346
|
+
modal: Optional[bool] = None,
|
|
347
|
+
anchor_to: Optional[Union[Widget, Literal["screen", "cursor", "parent"]]] = None,
|
|
348
|
+
anchor_point: AnchorPoint = 'center',
|
|
349
|
+
window_point: AnchorPoint = 'center',
|
|
350
|
+
offset: Tuple[int, int] = (0, 0),
|
|
351
|
+
auto_flip: Union[bool, Literal['vertical', 'horizontal']] = False
|
|
352
|
+
) -> None:
|
|
353
|
+
"""Display the dialog and block until closed.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
position: Optional (x, y) coordinates to position the dialog.
|
|
357
|
+
If provided, takes precedence over anchor-based positioning.
|
|
358
|
+
If omitted and anchor_to is not provided, positions at the parent's
|
|
359
|
+
bottom-right when available, otherwise centers.
|
|
360
|
+
modal: Override the mode's default modality.
|
|
361
|
+
If None, uses True for modal mode dialogs.
|
|
362
|
+
anchor_to: Positioning target. Can be:
|
|
363
|
+
- Widget: Anchor to a specific widget
|
|
364
|
+
- "screen": Anchor to screen edges/corners
|
|
365
|
+
- "cursor": Anchor to mouse cursor location
|
|
366
|
+
- "parent": Anchor to parent window (same as widget)
|
|
367
|
+
- None: Uses default positioning behavior
|
|
368
|
+
anchor_point: Point on the anchor target (n, s, e, w, ne, nw, se, sw, center).
|
|
369
|
+
Default 'center'.
|
|
370
|
+
window_point: Point on the dialog window (n, s, e, w, ne, nw, se, sw, center).
|
|
371
|
+
Default 'center'.
|
|
372
|
+
offset: Additional (x, y) offset in pixels from the anchor position.
|
|
373
|
+
auto_flip: Smart positioning to keep window on screen.
|
|
374
|
+
- False: No flipping (default)
|
|
375
|
+
- True: Flip both vertically and horizontally as needed
|
|
376
|
+
- 'vertical': Only flip up/down
|
|
377
|
+
- 'horizontal': Only flip left/right
|
|
378
|
+
"""
|
|
379
|
+
# Default positioning: bottom-right of parent if no positioning options provided
|
|
380
|
+
if position is None and anchor_to is None and self._master:
|
|
381
|
+
try:
|
|
382
|
+
x = self._master.winfo_rootx() + self._master.winfo_width()
|
|
383
|
+
y = self._master.winfo_rooty() + self._master.winfo_height()
|
|
384
|
+
position = (x, y)
|
|
385
|
+
except Exception:
|
|
386
|
+
pass
|
|
387
|
+
|
|
388
|
+
# Default modal to True if not specified
|
|
389
|
+
if modal is None:
|
|
390
|
+
modal = True
|
|
391
|
+
|
|
392
|
+
self._dialog.show(
|
|
393
|
+
position=position,
|
|
394
|
+
modal=modal,
|
|
395
|
+
anchor_to=anchor_to,
|
|
396
|
+
anchor_point=anchor_point,
|
|
397
|
+
window_point=window_point,
|
|
398
|
+
offset=offset,
|
|
399
|
+
auto_flip=auto_flip
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
@property
|
|
403
|
+
def result(self) -> Optional[Union[date, Tuple[date, date]]]:
|
|
404
|
+
"""The selected date (single mode) or `(start, end)` tuple (range mode), or None if cancelled."""
|
|
405
|
+
return self._dialog.result
|
|
406
|
+
|
|
407
|
+
def on_result(self, callback: Callable[[date], None]) -> Optional[str]:
|
|
408
|
+
"""Bind a callback fired when a result is produced.
|
|
409
|
+
|
|
410
|
+
The callback receives `event.data["result"]` (a `datetime.date`).
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
callback: Callable that receives the selected `datetime.date`.
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
The Tk binding identifier, which can be used with `off_result`.
|
|
417
|
+
"""
|
|
418
|
+
target = self._dialog.toplevel or self._master
|
|
419
|
+
if target is None:
|
|
420
|
+
return None
|
|
421
|
+
|
|
422
|
+
def handler(event: tkinter.Event) -> None:
|
|
423
|
+
callback(getattr(event, "data", None))
|
|
424
|
+
|
|
425
|
+
return target.bind("<<DialogResult>>", handler, add="+")
|
|
426
|
+
|
|
427
|
+
def off_result(self, funcid: str) -> None:
|
|
428
|
+
"""Unbind a previously bound `on_result` callback.
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
funcid: Binding identifier returned by `on_result`.
|
|
432
|
+
"""
|
|
433
|
+
target = self._dialog.toplevel or self._master
|
|
434
|
+
if target is None:
|
|
435
|
+
return
|
|
436
|
+
target.unbind("<<DialogResult>>", funcid)
|
|
437
|
+
|
|
438
|
+
def _emit_result(self, value: date, confirmed: bool) -> None:
|
|
439
|
+
"""Emit a virtual Tk event with the dialog result."""
|
|
440
|
+
target = self._dialog.toplevel or self._master
|
|
441
|
+
if not target:
|
|
442
|
+
return
|
|
443
|
+
payload = {"result": value, "confirmed": confirmed}
|
|
444
|
+
try:
|
|
445
|
+
target.event_generate("<<DialogResult>>", data=payload)
|
|
446
|
+
except Exception:
|
|
447
|
+
try:
|
|
448
|
+
target.event_generate("<<DialogResult>>")
|
|
449
|
+
except Exception:
|
|
450
|
+
pass
|