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,606 @@
|
|
|
1
|
+
"""TextArea — labeled multi-line text input with validation support."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import tkinter as tk
|
|
5
|
+
from typing import Callable
|
|
6
|
+
|
|
7
|
+
from bootstack.events import ChangeEvent, InputEvent, ValidationEvent
|
|
8
|
+
from bootstack.widgets._impl.primitives.frame import Frame
|
|
9
|
+
from bootstack.widgets._impl.primitives.gridframe import GridFrame
|
|
10
|
+
from bootstack.widgets._impl.primitives.label import Label
|
|
11
|
+
from bootstack.widgets._impl.composites.textarea.core import _MultilineCore, ScrollbarMode
|
|
12
|
+
from bootstack.widgets.types import Master, AccentToken
|
|
13
|
+
from bootstack._runtime.utility import scale_padding_floor
|
|
14
|
+
|
|
15
|
+
from bootstack.signals import Signal
|
|
16
|
+
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from bootstack.validation.validation_rules import ValidationRule
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TextArea(GridFrame):
|
|
23
|
+
"""A labeled, scrollable multi-line text input with validation support.
|
|
24
|
+
|
|
25
|
+
`TextArea` is the multi-line counterpart to `TextEntry`. It provides the
|
|
26
|
+
same field chrome (label, message, focus border, validation) while wrapping
|
|
27
|
+
a multi-line `tk.Text` widget with undo/redo, scrollbars, and reactive
|
|
28
|
+
signal binding.
|
|
29
|
+
|
|
30
|
+
Use this for comment boxes, note fields, and any other multi-line prose
|
|
31
|
+
input in a form context. For code editing see `CodeEditor`.
|
|
32
|
+
|
|
33
|
+
Replaces `bs.ScrolledText`.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
master: Master = None,
|
|
39
|
+
*,
|
|
40
|
+
value: str = "",
|
|
41
|
+
textsignal: Signal | None = None,
|
|
42
|
+
label: str | None = None,
|
|
43
|
+
message: str | None = None,
|
|
44
|
+
show_message: bool = False,
|
|
45
|
+
required: bool = False,
|
|
46
|
+
placeholder: str | None = None,
|
|
47
|
+
max_length: int | None = None,
|
|
48
|
+
read_only: bool = False,
|
|
49
|
+
wrap: bool = True,
|
|
50
|
+
height: int = 4,
|
|
51
|
+
width: int | None = None,
|
|
52
|
+
scrollbars: ScrollbarMode = "auto",
|
|
53
|
+
font: str = "body",
|
|
54
|
+
accent: AccentToken | str = "primary",
|
|
55
|
+
show_border: bool = True,
|
|
56
|
+
on_input: Callable | None = None,
|
|
57
|
+
on_changed: Callable | None = None,
|
|
58
|
+
on_blur: Callable | None = None,
|
|
59
|
+
on_valid: Callable | None = None,
|
|
60
|
+
on_invalid: Callable | None = None,
|
|
61
|
+
on_validated: Callable | None = None,
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Create a TextArea widget.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
master: Parent widget. If None, uses the default root window.
|
|
67
|
+
value: Initial text content.
|
|
68
|
+
textsignal: Reactive `Signal[str]` — the TextArea displays and
|
|
69
|
+
tracks the signal's value.
|
|
70
|
+
label: Optional label displayed above the text area.
|
|
71
|
+
message: Optional hint text shown below the text area. Replaced
|
|
72
|
+
by validation error messages when validation fails.
|
|
73
|
+
show_message: If True, the message area is always visible. Auto-
|
|
74
|
+
enabled when `message=` or `required=True` is set.
|
|
75
|
+
required: If True, marks the field as required and adds a
|
|
76
|
+
validation rule that rejects empty input.
|
|
77
|
+
placeholder: Placeholder text shown when the widget is empty.
|
|
78
|
+
Cleared on first focus.
|
|
79
|
+
max_length: If set, inserts beyond this character count are blocked.
|
|
80
|
+
read_only: If True, the text is not editable.
|
|
81
|
+
wrap: If True (default), long lines wrap at the widget boundary.
|
|
82
|
+
If False, horizontal scrolling is used.
|
|
83
|
+
height: Visible row count. Default is 4.
|
|
84
|
+
scrollbars: Scrollbar visibility mode — `"auto"` (shown when
|
|
85
|
+
needed), `"vertical"`, `"both"`, or `"none"`.
|
|
86
|
+
font: Font token. Defaults to `"body"`.
|
|
87
|
+
accent: Accent token for the focus border. Defaults to `"primary"`.
|
|
88
|
+
show_border: If True (default), wraps the text area in a themed
|
|
89
|
+
border that gains a focus ring on interaction.
|
|
90
|
+
on_input: Callback invoked on every edit. Receives `event.data`
|
|
91
|
+
of type `InputEvent`.
|
|
92
|
+
on_changed: Callback invoked when the widget loses focus. Receives
|
|
93
|
+
`event.data` of type `InputEvent`.
|
|
94
|
+
on_blur: Callback invoked when the widget loses focus (alias for
|
|
95
|
+
`on_changed` — use either, not both).
|
|
96
|
+
on_valid: Callback invoked after successful validation. Receives
|
|
97
|
+
`event.data` of type `ValidationEvent`.
|
|
98
|
+
on_invalid: Callback invoked after failed validation. Receives
|
|
99
|
+
`event.data` of type `ValidationEvent`.
|
|
100
|
+
on_validated: Callback invoked after any validation, pass or fail.
|
|
101
|
+
Receives `event.data` of type `ValidationEvent`.
|
|
102
|
+
"""
|
|
103
|
+
# GridFrame rows: auto (label) | 1 (field, expands) | auto (message)
|
|
104
|
+
super().__init__(master, rows=["auto", 1, "auto"], columns=[1],
|
|
105
|
+
auto_flow="none", gap=(0, 2))
|
|
106
|
+
|
|
107
|
+
self._accent = accent
|
|
108
|
+
self._placeholder_text = placeholder
|
|
109
|
+
self._max_length = max_length
|
|
110
|
+
self._showing_placeholder = False
|
|
111
|
+
self._original_message = message or ""
|
|
112
|
+
self._rules: list[ValidationRule] = []
|
|
113
|
+
self._message_showing = False
|
|
114
|
+
self._valid_signal: Signal = Signal(True)
|
|
115
|
+
self._error_signal: Signal = Signal("")
|
|
116
|
+
|
|
117
|
+
# ── label (row 0) ─────────────────────────────────────────────────
|
|
118
|
+
if label:
|
|
119
|
+
_lbl_text = f"{label} *" if required else label
|
|
120
|
+
Label(self, text=_lbl_text, font="caption").grid(
|
|
121
|
+
row=0, column=0, sticky="w", padx=(4, 0),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# ── core (row 1) ──────────────────────────────────────────────────
|
|
125
|
+
if show_border:
|
|
126
|
+
# Padding must grow with the DPI-scaled TField border slice, else the
|
|
127
|
+
# text core overpaints the border at high DPI (#90, same as Field).
|
|
128
|
+
self._field_frame = Frame(
|
|
129
|
+
self, accent=accent, padding=scale_padding_floor(5), ttk_class="TField",
|
|
130
|
+
)
|
|
131
|
+
self._field_frame.grid(row=1, column=0, sticky="nsew")
|
|
132
|
+
core_parent = self._field_frame
|
|
133
|
+
else:
|
|
134
|
+
self._field_frame = None
|
|
135
|
+
core_parent = self
|
|
136
|
+
|
|
137
|
+
core_kwargs: dict = {}
|
|
138
|
+
if width is not None:
|
|
139
|
+
core_kwargs["width"] = width
|
|
140
|
+
self._core = _MultilineCore(
|
|
141
|
+
core_parent,
|
|
142
|
+
value=value,
|
|
143
|
+
wrap=wrap,
|
|
144
|
+
height=height,
|
|
145
|
+
scrollbars=scrollbars,
|
|
146
|
+
font=font,
|
|
147
|
+
read_only=read_only,
|
|
148
|
+
**core_kwargs,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if show_border:
|
|
152
|
+
self._core.pack(fill="both", expand=True)
|
|
153
|
+
self._core.text.bind(
|
|
154
|
+
"<FocusIn>",
|
|
155
|
+
lambda _: self._field_frame.state(["focus"]),
|
|
156
|
+
add="+",
|
|
157
|
+
)
|
|
158
|
+
self._core.text.bind(
|
|
159
|
+
"<FocusOut>",
|
|
160
|
+
lambda _: self._field_frame.state(["!focus"]),
|
|
161
|
+
add="+",
|
|
162
|
+
)
|
|
163
|
+
else:
|
|
164
|
+
self._core.grid(row=1, column=0, sticky="nsew")
|
|
165
|
+
|
|
166
|
+
# ── Tab moves focus (not a keyboard trap) ─────────────────────────
|
|
167
|
+
# A multi-line tk.Text inserts a literal tab on <Tab> and keeps focus,
|
|
168
|
+
# which traps keyboard users in a form field. Rebind Tab/Shift-Tab to
|
|
169
|
+
# move focus like every other field. (CodeEditor keeps Tab for indent —
|
|
170
|
+
# it is a separate composite and is unaffected.)
|
|
171
|
+
self._core.text.bind("<Tab>", self._focus_next, add="+")
|
|
172
|
+
self._core.text.bind("<Shift-Tab>", self._focus_prev, add="+")
|
|
173
|
+
self._core.text.bind("<ISO_Left_Tab>", self._focus_prev, add="+")
|
|
174
|
+
|
|
175
|
+
# ── message label (row 2) ─────────────────────────────────────────
|
|
176
|
+
self._message_lbl = Label(self, text=message or "", font="caption",
|
|
177
|
+
accent="secondary")
|
|
178
|
+
self._message_lbl.grid(row=2, column=0, sticky="w")
|
|
179
|
+
self._message_lbl.grid_remove()
|
|
180
|
+
|
|
181
|
+
if show_message or message or required:
|
|
182
|
+
self._show_message_area()
|
|
183
|
+
|
|
184
|
+
# ── signal binding ────────────────────────────────────────────────
|
|
185
|
+
if textsignal is not None:
|
|
186
|
+
self._core.bind_signal(textsignal)
|
|
187
|
+
|
|
188
|
+
# ── placeholder ───────────────────────────────────────────────────
|
|
189
|
+
self._default_fg = self._core.text.cget("foreground")
|
|
190
|
+
if placeholder and not value and textsignal is None:
|
|
191
|
+
self._show_placeholder()
|
|
192
|
+
if placeholder:
|
|
193
|
+
self._core.text.bind("<FocusIn>", self._on_focus_in_placeholder, add="+")
|
|
194
|
+
|
|
195
|
+
# ── max_length filter ─────────────────────────────────────────────
|
|
196
|
+
if max_length is not None:
|
|
197
|
+
from bootstack.widgets._impl.composites.textarea.filter import EditFilter
|
|
198
|
+
|
|
199
|
+
_limit = max_length
|
|
200
|
+
|
|
201
|
+
class _MaxLenFilter(EditFilter):
|
|
202
|
+
def insert(self, index, chars, tags=None):
|
|
203
|
+
current = len(self.get("1.0", "end-1c"))
|
|
204
|
+
allowed = max(0, _limit - current)
|
|
205
|
+
if allowed > 0:
|
|
206
|
+
self.delegate.insert(index, chars[:allowed], tags)
|
|
207
|
+
|
|
208
|
+
self._core.add_filter(_MaxLenFilter())
|
|
209
|
+
|
|
210
|
+
# ── required rule ─────────────────────────────────────────────────
|
|
211
|
+
if required:
|
|
212
|
+
self.add_validation_rule("required")
|
|
213
|
+
|
|
214
|
+
# ── internal event wiring ─────────────────────────────────────────
|
|
215
|
+
# Fire <<Input>> on every edit, <<Changed>> + validation on blur.
|
|
216
|
+
self._core.text.bind("<<Change>>", self._on_core_change, add="+")
|
|
217
|
+
self._core.text.bind("<FocusOut>", self._on_focus_out, add="+")
|
|
218
|
+
|
|
219
|
+
# ── constructor callbacks ─────────────────────────────────────────
|
|
220
|
+
if on_input is not None:
|
|
221
|
+
self.on_input(on_input)
|
|
222
|
+
if on_changed is not None:
|
|
223
|
+
self.on_changed(on_changed)
|
|
224
|
+
if on_blur is not None:
|
|
225
|
+
self.on_changed(on_blur)
|
|
226
|
+
if on_valid is not None:
|
|
227
|
+
self.on_valid(on_valid)
|
|
228
|
+
if on_invalid is not None:
|
|
229
|
+
self.on_invalid(on_invalid)
|
|
230
|
+
if on_validated is not None:
|
|
231
|
+
self.on_validated(on_validated)
|
|
232
|
+
|
|
233
|
+
# ── placeholder ───────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
def _show_placeholder(self) -> None:
|
|
236
|
+
self._showing_placeholder = True
|
|
237
|
+
was_read_only = self._core._read_only
|
|
238
|
+
self._core.text.configure(state=tk.NORMAL)
|
|
239
|
+
self._core.text.insert("1.0", self._placeholder_text)
|
|
240
|
+
self._core.text.configure(foreground="grey")
|
|
241
|
+
# Restore read-only state — showing a placeholder must not silently make
|
|
242
|
+
# a read-only field editable.
|
|
243
|
+
if was_read_only:
|
|
244
|
+
self._core.text.configure(state=tk.DISABLED)
|
|
245
|
+
|
|
246
|
+
def _hide_placeholder(self) -> None:
|
|
247
|
+
self._showing_placeholder = False
|
|
248
|
+
was_read_only = self._core._read_only
|
|
249
|
+
if was_read_only:
|
|
250
|
+
self._core.text.configure(state=tk.NORMAL)
|
|
251
|
+
self._core.text.delete("1.0", tk.END)
|
|
252
|
+
self._core.text.configure(foreground=self._default_fg)
|
|
253
|
+
if was_read_only:
|
|
254
|
+
self._core.text.configure(state=tk.DISABLED)
|
|
255
|
+
|
|
256
|
+
def _on_focus_in_placeholder(self, _event: tk.Event) -> None:
|
|
257
|
+
if self._showing_placeholder:
|
|
258
|
+
self._hide_placeholder()
|
|
259
|
+
|
|
260
|
+
# ── focus traversal ───────────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
def _focus_next(self, event: tk.Event) -> str:
|
|
263
|
+
event.widget.tk_focusNext().focus_set()
|
|
264
|
+
return "break"
|
|
265
|
+
|
|
266
|
+
def _focus_prev(self, event: tk.Event) -> str:
|
|
267
|
+
event.widget.tk_focusPrev().focus_set()
|
|
268
|
+
return "break"
|
|
269
|
+
|
|
270
|
+
# ── message / error area ──────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
def _show_message_area(self) -> None:
|
|
273
|
+
if not self._message_showing:
|
|
274
|
+
self._message_lbl.grid()
|
|
275
|
+
self._message_showing = True
|
|
276
|
+
|
|
277
|
+
def _show_error(self, message: str) -> None:
|
|
278
|
+
self._show_message_area()
|
|
279
|
+
self._message_lbl.configure(text=message, accent="danger")
|
|
280
|
+
|
|
281
|
+
def _clear_error(self) -> None:
|
|
282
|
+
self._message_lbl.configure(text=self._original_message, accent="secondary")
|
|
283
|
+
|
|
284
|
+
# ── validation ────────────────────────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
def add_validation_rule(
|
|
287
|
+
self,
|
|
288
|
+
rule_type: str,
|
|
289
|
+
message: str | None = None,
|
|
290
|
+
**kwargs,
|
|
291
|
+
) -> None:
|
|
292
|
+
"""Add a validation rule to this field.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
rule_type: Rule name — `"required"`, `"pattern"`, `"stringLength"`,
|
|
296
|
+
`"email"`, `"custom"`, etc.
|
|
297
|
+
message: Custom error message. If None, the rule's default is used.
|
|
298
|
+
**kwargs: Rule-specific parameters (e.g. `minLength=`, `pattern=`,
|
|
299
|
+
`func=` for custom rules).
|
|
300
|
+
"""
|
|
301
|
+
from bootstack.validation.validation_rules import ValidationRule
|
|
302
|
+
self._rules.append(ValidationRule(rule_type, message=message, **kwargs))
|
|
303
|
+
self._show_message_area()
|
|
304
|
+
|
|
305
|
+
def validate(self) -> bool:
|
|
306
|
+
"""Run all validation rules immediately and return True if valid.
|
|
307
|
+
|
|
308
|
+
Triggers the same `<<Valid>>` / `<<Invalid>>` / `<<Validate>>` events
|
|
309
|
+
as blur-triggered validation.
|
|
310
|
+
"""
|
|
311
|
+
return self._run_validation(trigger="manual")
|
|
312
|
+
|
|
313
|
+
def _set_validity(self, is_valid: bool, message: str) -> None:
|
|
314
|
+
self._error_signal.set("" if is_valid else message)
|
|
315
|
+
self._valid_signal.set(is_valid)
|
|
316
|
+
|
|
317
|
+
def _run_validation(self, trigger: str = "blur") -> bool:
|
|
318
|
+
value = self.value
|
|
319
|
+
for rule in self._rules:
|
|
320
|
+
if trigger != "manual" and rule.trigger not in ("always", trigger):
|
|
321
|
+
continue
|
|
322
|
+
result = rule.validate(value)
|
|
323
|
+
if not result.is_valid:
|
|
324
|
+
self._set_validity(False, result.message)
|
|
325
|
+
self._show_error(result.message)
|
|
326
|
+
data = ValidationEvent(
|
|
327
|
+
value=value, is_valid=False, message=result.message,
|
|
328
|
+
)
|
|
329
|
+
self.event_generate("<<Invalid>>", data=data, when="tail")
|
|
330
|
+
self.event_generate("<<Validate>>", data=data, when="tail")
|
|
331
|
+
return False
|
|
332
|
+
self._set_validity(True, "")
|
|
333
|
+
self._clear_error()
|
|
334
|
+
data = ValidationEvent(value=value, is_valid=True, message="")
|
|
335
|
+
self.event_generate("<<Valid>>", data=data, when="tail")
|
|
336
|
+
self.event_generate("<<Validate>>", data=data, when="tail")
|
|
337
|
+
return True
|
|
338
|
+
|
|
339
|
+
# ── internal event dispatch ───────────────────────────────────────────
|
|
340
|
+
|
|
341
|
+
def _on_core_change(self, _event: tk.Event) -> None:
|
|
342
|
+
if not self._showing_placeholder:
|
|
343
|
+
self.event_generate(
|
|
344
|
+
"<<Input>>",
|
|
345
|
+
data=InputEvent(text=self.value),
|
|
346
|
+
when="tail",
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
def _on_focus_out(self, _event: tk.Event) -> None:
|
|
350
|
+
if self._placeholder_text and not self._core.value:
|
|
351
|
+
self._show_placeholder()
|
|
352
|
+
if not self._showing_placeholder:
|
|
353
|
+
self.event_generate(
|
|
354
|
+
"<<Changed>>",
|
|
355
|
+
data=ChangeEvent(value=self.value, text=self.value),
|
|
356
|
+
when="tail",
|
|
357
|
+
)
|
|
358
|
+
if self._rules:
|
|
359
|
+
self._run_validation(trigger="blur")
|
|
360
|
+
|
|
361
|
+
# ── public value API ──────────────────────────────────────────────────
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def value(self) -> str:
|
|
365
|
+
"""Full text content (trailing newline stripped)."""
|
|
366
|
+
if self._showing_placeholder:
|
|
367
|
+
return ""
|
|
368
|
+
return self._core.value
|
|
369
|
+
|
|
370
|
+
@value.setter
|
|
371
|
+
def value(self, text: str) -> None:
|
|
372
|
+
if self._showing_placeholder:
|
|
373
|
+
self._hide_placeholder()
|
|
374
|
+
self._core.value = text
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def is_dirty(self) -> bool:
|
|
378
|
+
"""True if the content has changed since last save."""
|
|
379
|
+
return self._core.is_dirty
|
|
380
|
+
|
|
381
|
+
def mark_saved(self) -> None:
|
|
382
|
+
"""Mark the current state as the saved baseline."""
|
|
383
|
+
self._core.mark_saved()
|
|
384
|
+
|
|
385
|
+
def undo(self) -> None:
|
|
386
|
+
"""Undo the last edit."""
|
|
387
|
+
self._core.undo()
|
|
388
|
+
|
|
389
|
+
def redo(self) -> None:
|
|
390
|
+
"""Redo the last undone edit."""
|
|
391
|
+
self._core.redo()
|
|
392
|
+
|
|
393
|
+
def undo_block_start(self) -> None:
|
|
394
|
+
"""Begin a compound undo block."""
|
|
395
|
+
self._core.undo_block_start()
|
|
396
|
+
|
|
397
|
+
def undo_block_stop(self) -> None:
|
|
398
|
+
"""End a compound undo block."""
|
|
399
|
+
self._core.undo_block_stop()
|
|
400
|
+
|
|
401
|
+
def focus_set(self, *args, **kwargs) -> None:
|
|
402
|
+
"""Set focus to the text area."""
|
|
403
|
+
self._core.focus_set()
|
|
404
|
+
|
|
405
|
+
@property
|
|
406
|
+
def core(self) -> _MultilineCore:
|
|
407
|
+
"""The internal `_MultilineCore` — for advanced filter/decoration use."""
|
|
408
|
+
return self._core
|
|
409
|
+
|
|
410
|
+
# ── event subscriptions ───────────────────────────────────────────────
|
|
411
|
+
|
|
412
|
+
def on_input(self, callback: Callable[[InputEvent], None]) -> str:
|
|
413
|
+
"""Register a callback for `<<Input>>` events.
|
|
414
|
+
|
|
415
|
+
Fires on every edit. `event.data.text` is the current text.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
callback: Receives the Tk event. `event.data` is
|
|
419
|
+
`InputEvent`.
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
Bind ID — pass to `off_input()` to unsubscribe.
|
|
423
|
+
"""
|
|
424
|
+
return self.bind("<<Input>>", callback, add="+")
|
|
425
|
+
|
|
426
|
+
def off_input(self, bind_id: str | None = None) -> None:
|
|
427
|
+
"""Unsubscribe from `<<Input>>`.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
bind_id: ID returned by `on_input()`. If None, removes all.
|
|
431
|
+
"""
|
|
432
|
+
self.unbind("<<Input>>", bind_id)
|
|
433
|
+
|
|
434
|
+
def on_changed(self, callback: Callable[[InputEvent], None]) -> str:
|
|
435
|
+
"""Register a callback for `<<Changed>>` events.
|
|
436
|
+
|
|
437
|
+
Fires when the widget loses focus. `event.data.value` is the
|
|
438
|
+
current text.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
callback: Receives the Tk event. `event.data` is
|
|
442
|
+
`InputEvent`.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
Bind ID — pass to `off_changed()` to unsubscribe.
|
|
446
|
+
"""
|
|
447
|
+
return self.bind("<<Changed>>", callback, add="+")
|
|
448
|
+
|
|
449
|
+
def off_changed(self, bind_id: str | None = None) -> None:
|
|
450
|
+
"""Unsubscribe from `<<Changed>>`.
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
bind_id: ID returned by `on_changed()`. If None, removes all.
|
|
454
|
+
"""
|
|
455
|
+
self.unbind("<<Changed>>", bind_id)
|
|
456
|
+
|
|
457
|
+
def on_blur(self, callback: Callable) -> str:
|
|
458
|
+
"""Register a callback for focus-out events.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
callback: Called when the text area loses focus.
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
Bind ID — pass to `off_blur()` to unsubscribe.
|
|
465
|
+
"""
|
|
466
|
+
return self._core.text.bind("<FocusOut>", callback, add="+")
|
|
467
|
+
|
|
468
|
+
def off_blur(self, bind_id: str | None = None) -> None:
|
|
469
|
+
"""Unsubscribe from focus-out events.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
bind_id: ID returned by `on_blur()`. If None, removes all.
|
|
473
|
+
"""
|
|
474
|
+
self._core.text.unbind("<FocusOut>", bind_id)
|
|
475
|
+
|
|
476
|
+
def on_valid(self, callback: Callable) -> str:
|
|
477
|
+
"""Register a callback for `<<Valid>>` events.
|
|
478
|
+
|
|
479
|
+
Fires after all validation rules pass.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
callback: Receives the Tk event. `event.data` is
|
|
483
|
+
`ValidationEvent`.
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
Bind ID — pass to `off_valid()` to unsubscribe.
|
|
487
|
+
"""
|
|
488
|
+
return self.bind("<<Valid>>", callback, add="+")
|
|
489
|
+
|
|
490
|
+
def off_valid(self, bind_id: str | None = None) -> None:
|
|
491
|
+
"""Unsubscribe from `<<Valid>>`.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
bind_id: ID returned by `on_valid()`. If None, removes all.
|
|
495
|
+
"""
|
|
496
|
+
self.unbind("<<Valid>>", bind_id)
|
|
497
|
+
|
|
498
|
+
def on_invalid(self, callback: Callable) -> str:
|
|
499
|
+
"""Register a callback for `<<Invalid>>` events.
|
|
500
|
+
|
|
501
|
+
Fires when any validation rule fails.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
callback: Receives the Tk event. `event.data` is
|
|
505
|
+
`ValidationEvent`.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
Bind ID — pass to `off_invalid()` to unsubscribe.
|
|
509
|
+
"""
|
|
510
|
+
return self.bind("<<Invalid>>", callback, add="+")
|
|
511
|
+
|
|
512
|
+
def off_invalid(self, bind_id: str | None = None) -> None:
|
|
513
|
+
"""Unsubscribe from `<<Invalid>>`.
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
bind_id: ID returned by `on_invalid()`. If None, removes all.
|
|
517
|
+
"""
|
|
518
|
+
self.unbind("<<Invalid>>", bind_id)
|
|
519
|
+
|
|
520
|
+
def on_validated(self, callback: Callable) -> str:
|
|
521
|
+
"""Register a callback for `<<Validate>>` events.
|
|
522
|
+
|
|
523
|
+
Fires after any validation pass, whether the result is valid or not.
|
|
524
|
+
|
|
525
|
+
Args:
|
|
526
|
+
callback: Receives the Tk event. `event.data` is
|
|
527
|
+
`ValidationEvent`.
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
Bind ID — pass to `off_validated()` to unsubscribe.
|
|
531
|
+
"""
|
|
532
|
+
return self.bind("<<Validate>>", callback, add="+")
|
|
533
|
+
|
|
534
|
+
def off_validated(self, bind_id: str | None = None) -> None:
|
|
535
|
+
"""Unsubscribe from `<<Validate>>`.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
bind_id: ID returned by `on_validated()`. If None, removes all.
|
|
539
|
+
"""
|
|
540
|
+
self.unbind("<<Validate>>", bind_id)
|
|
541
|
+
|
|
542
|
+
def on_modified(self, callback: Callable) -> str:
|
|
543
|
+
"""Register a callback for `<<TextModified>>` events.
|
|
544
|
+
|
|
545
|
+
Fires when the dirty state changes. `event.data.is_dirty` is the
|
|
546
|
+
new state.
|
|
547
|
+
|
|
548
|
+
Args:
|
|
549
|
+
callback: Receives the Tk event.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
Bind ID — pass to `off_modified()` to unsubscribe.
|
|
553
|
+
"""
|
|
554
|
+
return self._core.on_modified(callback)
|
|
555
|
+
|
|
556
|
+
def off_modified(self, bind_id: str | None = None) -> None:
|
|
557
|
+
"""Unsubscribe from `<<TextModified>>`.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
bind_id: ID returned by `on_modified()`. If None, removes all.
|
|
561
|
+
"""
|
|
562
|
+
self._core.off_modified(bind_id)
|
|
563
|
+
|
|
564
|
+
def on_undo(self, callback: Callable) -> str:
|
|
565
|
+
"""Register a callback for `<<TextUndo>>` events.
|
|
566
|
+
|
|
567
|
+
Fires after an undo completes. `event.data.text` is the text
|
|
568
|
+
content after the undo.
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
callback: Receives the Tk event.
|
|
572
|
+
|
|
573
|
+
Returns:
|
|
574
|
+
Bind ID — pass to `off_undo()` to unsubscribe.
|
|
575
|
+
"""
|
|
576
|
+
return self._core.on_undo(callback)
|
|
577
|
+
|
|
578
|
+
def off_undo(self, bind_id: str | None = None) -> None:
|
|
579
|
+
"""Unsubscribe from `<<TextUndo>>`.
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
bind_id: ID returned by `on_undo()`. If None, removes all.
|
|
583
|
+
"""
|
|
584
|
+
self._core.off_undo(bind_id)
|
|
585
|
+
|
|
586
|
+
def on_redo(self, callback: Callable) -> str:
|
|
587
|
+
"""Register a callback for `<<TextRedo>>` events.
|
|
588
|
+
|
|
589
|
+
Fires after a redo completes. `event.data.text` is the text
|
|
590
|
+
content after the redo.
|
|
591
|
+
|
|
592
|
+
Args:
|
|
593
|
+
callback: Receives the Tk event.
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
Bind ID — pass to `off_redo()` to unsubscribe.
|
|
597
|
+
"""
|
|
598
|
+
return self._core.on_redo(callback)
|
|
599
|
+
|
|
600
|
+
def off_redo(self, bind_id: str | None = None) -> None:
|
|
601
|
+
"""Unsubscribe from `<<TextRedo>>`.
|
|
602
|
+
|
|
603
|
+
Args:
|
|
604
|
+
bind_id: ID returned by `on_redo()`. If None, removes all.
|
|
605
|
+
"""
|
|
606
|
+
self._core.off_redo(bind_id)
|