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,217 @@
|
|
|
1
|
+
"""UndoManager filter — word-level undo via character-class grouping.
|
|
2
|
+
|
|
3
|
+
Adaptation of IDLE's UndoDelegator (idlelib/undo.py). Key differences:
|
|
4
|
+
- Renamed UndoManager / _Command for clarity.
|
|
5
|
+
- Character-class merging gives word-level undo without needing Tk's
|
|
6
|
+
built-in undo stack (undo=False on the Text widget).
|
|
7
|
+
- Canonical line.col indices recorded before forwarding so undo positions
|
|
8
|
+
stay correct after the cursor moves.
|
|
9
|
+
- Adjacency check uses line/col arithmetic rather than Tk mark resolution.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
from bootstack.events import InputEvent
|
|
16
|
+
from bootstack.widgets._impl.composites.textarea.filter import EditFilter
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from bootstack.widgets._impl.composites.textarea.core import _MultilineCore
|
|
20
|
+
|
|
21
|
+
# Character classes for merging adjacent typed characters into one undo step
|
|
22
|
+
_ALPHANUMERIC = frozenset(
|
|
23
|
+
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _char_class(ch: str) -> str:
|
|
28
|
+
if ch in _ALPHANUMERIC:
|
|
29
|
+
return 'alphanum'
|
|
30
|
+
if ch == '\n':
|
|
31
|
+
return 'newline'
|
|
32
|
+
return 'punct'
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class _Command:
|
|
36
|
+
"""One reversible edit operation."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, op: str, index: str, chars: str, tags=None) -> None:
|
|
39
|
+
self.op = op # 'insert' or 'delete'
|
|
40
|
+
self.index = index # canonical line.col (e.g. "1.5")
|
|
41
|
+
self.chars = chars
|
|
42
|
+
self.tags = tags
|
|
43
|
+
|
|
44
|
+
def do(self, core: _MultilineCore) -> None:
|
|
45
|
+
"""Re-apply this operation (used by redo)."""
|
|
46
|
+
if self.op == 'insert':
|
|
47
|
+
core.text.insert(self.index, self.chars, self.tags or ())
|
|
48
|
+
else:
|
|
49
|
+
end = core.text.index(f'{self.index} +{len(self.chars)}c')
|
|
50
|
+
core.text.delete(self.index, end)
|
|
51
|
+
|
|
52
|
+
def undo(self, core: _MultilineCore) -> None:
|
|
53
|
+
"""Reverse this operation."""
|
|
54
|
+
if self.op == 'insert':
|
|
55
|
+
end = core.text.index(f'{self.index} +{len(self.chars)}c')
|
|
56
|
+
core.text.delete(self.index, end)
|
|
57
|
+
else:
|
|
58
|
+
core.text.insert(self.index, self.chars, self.tags or ())
|
|
59
|
+
|
|
60
|
+
def merge(self, other: '_Command') -> bool:
|
|
61
|
+
"""Try to merge *other* into this command (in-place). Returns True if merged.
|
|
62
|
+
|
|
63
|
+
Two single-character inserts merge when they are:
|
|
64
|
+
- Both inserts
|
|
65
|
+
- Adjacent (other starts where self ends, same row)
|
|
66
|
+
- Same character class
|
|
67
|
+
- Neither contains a newline (newlines always start a new block)
|
|
68
|
+
"""
|
|
69
|
+
if self.op != 'insert' or other.op != 'insert':
|
|
70
|
+
return False
|
|
71
|
+
if len(other.chars) != 1 or '\n' in other.chars:
|
|
72
|
+
return False
|
|
73
|
+
if not self.chars or '\n' in self.chars:
|
|
74
|
+
return False
|
|
75
|
+
if _char_class(self.chars[-1]) != _char_class(other.chars[0]):
|
|
76
|
+
return False
|
|
77
|
+
# Adjacency: other must start exactly where self ends (same row)
|
|
78
|
+
try:
|
|
79
|
+
row1, col1 = map(int, self.index.split('.'))
|
|
80
|
+
row2, col2 = map(int, other.index.split('.'))
|
|
81
|
+
if row1 == row2 and col1 + len(self.chars) == col2:
|
|
82
|
+
self.chars += other.chars
|
|
83
|
+
return True
|
|
84
|
+
except (ValueError, AttributeError):
|
|
85
|
+
pass
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class UndoManager(EditFilter):
|
|
90
|
+
"""Undo/redo filter with character-class word-level grouping.
|
|
91
|
+
|
|
92
|
+
Intercepts every insert/delete, records canonical operations, and groups
|
|
93
|
+
consecutive same-class single-character inserts into one undo block.
|
|
94
|
+
Provides `undo()`, `redo()`, `undo_block_start()`,
|
|
95
|
+
`undo_block_stop()`, and `reset_undo()` via the owning core.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
MAX_UNDO = 1000
|
|
99
|
+
|
|
100
|
+
def __init__(self) -> None:
|
|
101
|
+
super().__init__()
|
|
102
|
+
self._stack: list[list[_Command]] = []
|
|
103
|
+
self._redo_stack: list[list[_Command]] = []
|
|
104
|
+
self._current_block: list[_Command] | None = None
|
|
105
|
+
self._block_depth: int = 0
|
|
106
|
+
self._saved_index: int = 0 # stack length at last save
|
|
107
|
+
self._core: _MultilineCore | None = None
|
|
108
|
+
self._undoing: bool = False
|
|
109
|
+
|
|
110
|
+
# ── lifecycle ──────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
def attach(self, core: _MultilineCore) -> None:
|
|
113
|
+
self._core = core
|
|
114
|
+
|
|
115
|
+
def detach(self, core: _MultilineCore) -> None:
|
|
116
|
+
self._core = None
|
|
117
|
+
|
|
118
|
+
# ── filter chain ───────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
def insert(self, index: str, chars: str, tags=None) -> None:
|
|
121
|
+
if not self._undoing:
|
|
122
|
+
canonical = self.index(index)
|
|
123
|
+
self.delegate.insert(index, chars, tags)
|
|
124
|
+
self._record(_Command('insert', canonical, chars, tags))
|
|
125
|
+
else:
|
|
126
|
+
self.delegate.insert(index, chars, tags)
|
|
127
|
+
|
|
128
|
+
def delete(self, index1: str, index2: str | None = None) -> None:
|
|
129
|
+
if not self._undoing:
|
|
130
|
+
canonical = self.index(index1)
|
|
131
|
+
end = index2 if index2 else self.index(f'{index1} +1c')
|
|
132
|
+
chars = self.get(canonical, end)
|
|
133
|
+
self.delegate.delete(index1, index2)
|
|
134
|
+
self._record(_Command('delete', canonical, chars))
|
|
135
|
+
else:
|
|
136
|
+
self.delegate.delete(index1, index2)
|
|
137
|
+
|
|
138
|
+
# ── recording ─────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
def _record(self, cmd: _Command) -> None:
|
|
141
|
+
self._redo_stack.clear()
|
|
142
|
+
if self._current_block is not None:
|
|
143
|
+
self._current_block.append(cmd)
|
|
144
|
+
return
|
|
145
|
+
# Try to merge with the last single-command block
|
|
146
|
+
if self._stack and len(self._stack[-1]) == 1:
|
|
147
|
+
if self._stack[-1][0].merge(cmd):
|
|
148
|
+
return
|
|
149
|
+
self._stack.append([cmd])
|
|
150
|
+
if len(self._stack) > self.MAX_UNDO:
|
|
151
|
+
self._stack.pop(0)
|
|
152
|
+
if self._saved_index > 0:
|
|
153
|
+
self._saved_index -= 1
|
|
154
|
+
|
|
155
|
+
# ── public API ────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
def undo_block_start(self) -> None:
|
|
158
|
+
"""Begin a compound undo block. Nested calls are safe."""
|
|
159
|
+
if self._block_depth == 0:
|
|
160
|
+
self._current_block = []
|
|
161
|
+
self._block_depth += 1
|
|
162
|
+
|
|
163
|
+
def undo_block_stop(self) -> None:
|
|
164
|
+
"""End a compound undo block. Commits when depth reaches zero."""
|
|
165
|
+
self._block_depth -= 1
|
|
166
|
+
if self._block_depth == 0 and self._current_block is not None:
|
|
167
|
+
if self._current_block:
|
|
168
|
+
self._stack.append(self._current_block)
|
|
169
|
+
self._current_block = None
|
|
170
|
+
|
|
171
|
+
def undo(self) -> None:
|
|
172
|
+
"""Undo the last edit group."""
|
|
173
|
+
if not self._stack or self._core is None:
|
|
174
|
+
return
|
|
175
|
+
block = self._stack.pop()
|
|
176
|
+
self._redo_stack.append(block)
|
|
177
|
+
self._undoing = True
|
|
178
|
+
try:
|
|
179
|
+
for cmd in reversed(block):
|
|
180
|
+
cmd.undo(self._core)
|
|
181
|
+
finally:
|
|
182
|
+
self._undoing = False
|
|
183
|
+
self._core.text.event_generate(
|
|
184
|
+
'<<TextUndo>>', data=InputEvent(text=self._core.value), when='tail'
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def redo(self) -> None:
|
|
188
|
+
"""Redo the last undone edit group."""
|
|
189
|
+
if not self._redo_stack or self._core is None:
|
|
190
|
+
return
|
|
191
|
+
block = self._redo_stack.pop()
|
|
192
|
+
self._stack.append(block)
|
|
193
|
+
self._undoing = True
|
|
194
|
+
try:
|
|
195
|
+
for cmd in block:
|
|
196
|
+
cmd.do(self._core)
|
|
197
|
+
finally:
|
|
198
|
+
self._undoing = False
|
|
199
|
+
self._core.text.event_generate(
|
|
200
|
+
'<<TextRedo>>', data=InputEvent(text=self._core.value), when='tail'
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def reset_undo(self) -> None:
|
|
204
|
+
"""Clear undo/redo history and mark current state as saved."""
|
|
205
|
+
self._stack.clear()
|
|
206
|
+
self._redo_stack.clear()
|
|
207
|
+
self._redo_stack.clear()
|
|
208
|
+
self._saved_index = 0
|
|
209
|
+
|
|
210
|
+
def mark_saved(self) -> None:
|
|
211
|
+
"""Mark the current stack depth as the saved baseline."""
|
|
212
|
+
self._saved_index = len(self._stack)
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def is_dirty(self) -> bool:
|
|
216
|
+
"""True if the stack depth differs from the saved baseline."""
|
|
217
|
+
return len(self._stack) != self._saved_index
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from typing_extensions import Unpack
|
|
4
|
+
|
|
5
|
+
from bootstack.widgets._impl.composites.field import Field, FieldOptions
|
|
6
|
+
from bootstack.widgets.types import Master
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TextEntry(Field):
|
|
10
|
+
"""A text entry field widget with label, validation, and formatting support.
|
|
11
|
+
|
|
12
|
+
TextEntry is a composite widget that combines a label, text entry input, and
|
|
13
|
+
message area into a single component. It provides internationalization-aware
|
|
14
|
+
text input with deferred parsing, validation support, and visual feedback.
|
|
15
|
+
|
|
16
|
+
The widget separates user input (display text) from the committed/parsed value,
|
|
17
|
+
only parsing and formatting when the user commits via `<FocusOut>` or `<Return>`.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self, master: Master = None, value: Any = None, label: str = None, message: str = None,
|
|
22
|
+
**kwargs: Unpack[FieldOptions]):
|
|
23
|
+
"""Args:
|
|
24
|
+
master: Parent widget. If None, uses the default root window.
|
|
25
|
+
value: Initial value to display. Default is None.
|
|
26
|
+
label: Optional label text to display above the entry field.
|
|
27
|
+
If required=True, an asterisk (*) is automatically appended.
|
|
28
|
+
message: Optional message text to display below the entry field.
|
|
29
|
+
This is replaced by validation error messages when validation fails.
|
|
30
|
+
|
|
31
|
+
Other Parameters:
|
|
32
|
+
accent: Accent token for the focus ring and active border.
|
|
33
|
+
density: Widget density — 'default' or 'compact'.
|
|
34
|
+
state: Initial widget state — 'normal', 'disabled', or 'readonly'.
|
|
35
|
+
allow_blank: Allow empty input. Default is True.
|
|
36
|
+
cursor: Cursor style when hovering.
|
|
37
|
+
value_format: ICU format pattern for parsing/formatting.
|
|
38
|
+
exportselection: Export selection to clipboard.
|
|
39
|
+
font: Font for text display.
|
|
40
|
+
foreground: Text color.
|
|
41
|
+
initial_focus: If True, widget receives focus on creation.
|
|
42
|
+
justify: Text alignment ('left', 'center', 'right').
|
|
43
|
+
show_message: If True, displays message area. Defaults to False,
|
|
44
|
+
but auto-enables when `message=` or `required=True` is set.
|
|
45
|
+
padding: Padding around entry widget.
|
|
46
|
+
show: Character to mask input (e.g., '*' for passwords).
|
|
47
|
+
takefocus: If True, widget accepts Tab focus.
|
|
48
|
+
textvariable: Tkinter Variable to link with text.
|
|
49
|
+
See [tkinter Variables](https://docs.python.org/3/library/tkinter.html#tkinter-variables).
|
|
50
|
+
textsignal: Signal object for reactive updates.
|
|
51
|
+
width: Width in characters.
|
|
52
|
+
required: If True, field cannot be empty. Adds 'required'
|
|
53
|
+
validation rule and appends '*' to label.
|
|
54
|
+
xscrollcommand: Callback for horizontal scrolling.
|
|
55
|
+
"""
|
|
56
|
+
super().__init__(master, value=value, label=label, message=message, **kwargs)
|
|
57
|
+
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Time entry field widget with dropdown list of time intervals.
|
|
2
|
+
|
|
3
|
+
Provides a specialized entry field for time input with a searchable dropdown
|
|
4
|
+
list of time values at specified intervals.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import datetime
|
|
10
|
+
from bootstack.i18n.intl_format import DateFormatSpec
|
|
11
|
+
|
|
12
|
+
from typing_extensions import Unpack
|
|
13
|
+
|
|
14
|
+
from bootstack._runtime.app import get_app_settings
|
|
15
|
+
from bootstack.i18n import IntlFormatter
|
|
16
|
+
from bootstack.widgets._impl.composites.field import FieldOptions
|
|
17
|
+
from bootstack.widgets._impl.composites.selectbox import SelectBox
|
|
18
|
+
from bootstack.widgets.types import Master
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TimeEntry(SelectBox):
|
|
22
|
+
"""Time entry field with dropdown list of time intervals.
|
|
23
|
+
|
|
24
|
+
TimeEntry extends SelectBox to provide specialized time input with
|
|
25
|
+
locale-aware formatting and a searchable dropdown of time intervals.
|
|
26
|
+
The widget supports various time format presets and custom time patterns.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
master: Master = None,
|
|
32
|
+
value: datetime.time | str = None,
|
|
33
|
+
value_format: DateFormatSpec = 'shortTime',
|
|
34
|
+
interval: int = 30,
|
|
35
|
+
min_time: datetime.time | str = None,
|
|
36
|
+
max_time: datetime.time | str = None,
|
|
37
|
+
label: str = None,
|
|
38
|
+
message: str = None,
|
|
39
|
+
**kwargs: Unpack[FieldOptions]
|
|
40
|
+
):
|
|
41
|
+
"""Args:
|
|
42
|
+
master: Parent widget. If None, uses the default root window.
|
|
43
|
+
value: Initial time value to display. Can be a time object or string.
|
|
44
|
+
Empty by default.
|
|
45
|
+
value_format: Time format pattern for parsing and displaying times.
|
|
46
|
+
Default is "shortTime" (e.g., "3:30 PM"). Common formats:
|
|
47
|
+
"shortTime" (3:30 PM), "longTime" (3:30:45 PM PST),
|
|
48
|
+
"mediumTime" (3:30:45 PM), "HH:mm" (15:30), "h:mm a" (3:30 PM).
|
|
49
|
+
interval: Time interval in minutes for dropdown items (e.g., 15, 30, 60).
|
|
50
|
+
Default is 30 minutes.
|
|
51
|
+
min_time: Minimum time value for the dropdown list. Can be a time object
|
|
52
|
+
or string (e.g., "09:00" or "9:00 AM"). Default is midnight (00:00).
|
|
53
|
+
max_time: Maximum time value for the dropdown list. Can be a time object
|
|
54
|
+
or string (e.g., "17:00" or "5:00 PM"). Default is 11:59 PM (23:59).
|
|
55
|
+
label: Optional label text to display above the entry field.
|
|
56
|
+
If required=True, an asterisk (*) is automatically appended.
|
|
57
|
+
message: Optional message text to display below the entry field.
|
|
58
|
+
Used for hints or help text. Replaced by validation errors when
|
|
59
|
+
validation fails.
|
|
60
|
+
|
|
61
|
+
Other Parameters:
|
|
62
|
+
locale: Locale identifier for time formatting (e.g., 'en_US').
|
|
63
|
+
required: If True, field cannot be empty.
|
|
64
|
+
accent: Accent token for the focus ring and active border.
|
|
65
|
+
allow_blank: Allow empty input.
|
|
66
|
+
width: Width in characters.
|
|
67
|
+
textvariable: Tkinter Variable to link with text.
|
|
68
|
+
See [tkinter Variables](https://docs.python.org/3/library/tkinter.html#tkinter-variables).
|
|
69
|
+
textsignal: Signal object for reactive updates.
|
|
70
|
+
"""
|
|
71
|
+
self._interval = interval
|
|
72
|
+
self._value_format = value_format
|
|
73
|
+
self._locale = kwargs.get('locale') or get_app_settings().locale
|
|
74
|
+
|
|
75
|
+
# Parse and store time range
|
|
76
|
+
self._min_time = self._parse_time(min_time) if min_time else datetime.time(0, 0)
|
|
77
|
+
self._max_time = self._parse_time(max_time) if max_time else datetime.time(23, 59)
|
|
78
|
+
|
|
79
|
+
# An empty field stays empty — consistent with the other field widgets,
|
|
80
|
+
# so `required=` meaningfully demands input (was: defaulted to the current
|
|
81
|
+
# time, which silently pre-filled and defeated `required`). Pass an
|
|
82
|
+
# explicit `value=` to seed a starting time.
|
|
83
|
+
if value is None or value == "":
|
|
84
|
+
formatted_value = ""
|
|
85
|
+
else:
|
|
86
|
+
# Normalize value to a time object using our own parser (avoids
|
|
87
|
+
# dateparser mis-reading "08:30" as "August 30"), then format it with
|
|
88
|
+
# the same IntlFormatter used to build items so the strings compare
|
|
89
|
+
# equal.
|
|
90
|
+
formatter = IntlFormatter(locale=self._locale)
|
|
91
|
+
time_val = self._parse_time(value) if isinstance(value, str) else value
|
|
92
|
+
formatted_value = formatter.format(time_val, value_format)
|
|
93
|
+
|
|
94
|
+
# Generate time intervals for dropdown
|
|
95
|
+
items = self._generate_time_intervals()
|
|
96
|
+
|
|
97
|
+
# Initialize SelectBox with time-specific configuration
|
|
98
|
+
super().__init__(
|
|
99
|
+
master=master,
|
|
100
|
+
value=formatted_value,
|
|
101
|
+
value_format=value_format,
|
|
102
|
+
items=items,
|
|
103
|
+
allow_custom_values=True,
|
|
104
|
+
enable_search=True,
|
|
105
|
+
dropdown_button_icon='clock',
|
|
106
|
+
message=message,
|
|
107
|
+
label=label,
|
|
108
|
+
**kwargs
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def _parse_time(self, time_value: datetime.time | str) -> datetime.time:
|
|
112
|
+
"""Parse time value from various input formats.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
time_value: Time value as time object or string
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Parsed time object, or midnight (00:00) if parsing fails
|
|
119
|
+
"""
|
|
120
|
+
if isinstance(time_value, datetime.time):
|
|
121
|
+
return time_value
|
|
122
|
+
|
|
123
|
+
if isinstance(time_value, str):
|
|
124
|
+
# Try 24-hour format (HH:MM)
|
|
125
|
+
try:
|
|
126
|
+
dt = datetime.datetime.strptime(time_value, '%H:%M')
|
|
127
|
+
return dt.time()
|
|
128
|
+
except ValueError:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
# Try 12-hour format (h:MM AM/PM)
|
|
132
|
+
try:
|
|
133
|
+
dt = datetime.datetime.strptime(time_value, '%I:%M %p')
|
|
134
|
+
return dt.time()
|
|
135
|
+
except ValueError:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
# Fallback to midnight
|
|
139
|
+
return datetime.time(0, 0)
|
|
140
|
+
|
|
141
|
+
def _generate_time_intervals(self) -> list[str]:
|
|
142
|
+
"""Generate list of formatted time strings at specified intervals.
|
|
143
|
+
|
|
144
|
+
Creates a list of time values from min_time to max_time at the specified
|
|
145
|
+
interval, formatted according to value_format. Uses IntlFormatter for
|
|
146
|
+
consistent locale-aware formatting with Field.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of formatted time strings
|
|
150
|
+
"""
|
|
151
|
+
times = []
|
|
152
|
+
formatter = IntlFormatter(locale=self._locale)
|
|
153
|
+
|
|
154
|
+
# Convert time objects to datetime for iteration
|
|
155
|
+
current = datetime.datetime.combine(datetime.date.today(), self._min_time)
|
|
156
|
+
end = datetime.datetime.combine(datetime.date.today(), self._max_time)
|
|
157
|
+
|
|
158
|
+
# Handle midnight crossing (e.g., min_time=22:00, max_time=02:00)
|
|
159
|
+
if end < current:
|
|
160
|
+
end += datetime.timedelta(days=1)
|
|
161
|
+
|
|
162
|
+
# Generate intervals
|
|
163
|
+
while current <= end:
|
|
164
|
+
# Format using IntlFormatter (consistent with TextEntryPart)
|
|
165
|
+
# Note: Pass datetime (not time) as IntlFormatter expects it for Babel
|
|
166
|
+
try:
|
|
167
|
+
formatted_time = formatter.format(current, self._value_format)
|
|
168
|
+
except:
|
|
169
|
+
# Fallback to 24-hour format if formatting fails
|
|
170
|
+
formatted_time = current.strftime('%H:%M')
|
|
171
|
+
|
|
172
|
+
times.append(formatted_time)
|
|
173
|
+
current += datetime.timedelta(minutes=self._interval)
|
|
174
|
+
|
|
175
|
+
return times
|
|
176
|
+
|