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,46 @@
|
|
|
1
|
+
"""ChangeNotifier filter — fires <<Change>> after every edit."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
from bootstack.widgets._impl.composites.textarea.filter import EditFilter
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from bootstack.widgets._impl.composites.textarea.core import _MultilineCore
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ChangeNotifier(EditFilter):
|
|
12
|
+
"""Fires `<<Change>>` on the core's Text widget after every edit.
|
|
13
|
+
|
|
14
|
+
The event payload (`event.data`) is a dict with keys:
|
|
15
|
+
- `op`: `"insert"` or `"delete"`
|
|
16
|
+
- `index`: The position where the edit occurred (Tk index string).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
super().__init__()
|
|
21
|
+
self._core: _MultilineCore | None = None
|
|
22
|
+
|
|
23
|
+
def attach(self, core: _MultilineCore) -> None:
|
|
24
|
+
self._core = core
|
|
25
|
+
|
|
26
|
+
def detach(self, core: _MultilineCore) -> None:
|
|
27
|
+
self._core = None
|
|
28
|
+
|
|
29
|
+
def insert(self, index: str, chars: str, tags=None) -> None:
|
|
30
|
+
self.delegate.insert(index, chars, tags)
|
|
31
|
+
self._notify("insert", index)
|
|
32
|
+
|
|
33
|
+
def delete(self, index1: str, index2: str | None = None) -> None:
|
|
34
|
+
self.delegate.delete(index1, index2)
|
|
35
|
+
self._notify("delete", index1)
|
|
36
|
+
|
|
37
|
+
def _notify(self, op: str, index: str) -> None:
|
|
38
|
+
if self._core is not None:
|
|
39
|
+
try:
|
|
40
|
+
self._core.text.event_generate(
|
|
41
|
+
"<<Change>>",
|
|
42
|
+
data={"op": op, "index": index},
|
|
43
|
+
when="tail",
|
|
44
|
+
)
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
"""CodeEditor — a full-featured code editing widget."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import tkinter as tk
|
|
5
|
+
from typing import Callable, Literal
|
|
6
|
+
|
|
7
|
+
from bootstack.widgets._impl.primitives.frame import Frame
|
|
8
|
+
from bootstack.widgets._impl.composites.textarea.core import _MultilineCore, ScrollbarMode
|
|
9
|
+
from bootstack.widgets._impl.composites.textarea.filter import EditFilter
|
|
10
|
+
from bootstack.widgets._impl.composites.textarea.extensions.line_numbers import LineNumbers
|
|
11
|
+
from bootstack.widgets._impl.composites.textarea.extensions.bracket_matcher import BracketMatcher
|
|
12
|
+
from bootstack.widgets._impl.composites.textarea.extensions.smart_indent import SmartIndent
|
|
13
|
+
from bootstack.widgets._impl.composites.textarea.extensions.indent_guides import IndentGuides
|
|
14
|
+
from bootstack.widgets._impl.composites.textarea.search_overlay import SearchOverlay
|
|
15
|
+
from bootstack.widgets.types import Master
|
|
16
|
+
|
|
17
|
+
from bootstack.events import ValidationEvent
|
|
18
|
+
from bootstack.signals import Signal
|
|
19
|
+
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CodeEditor(Frame):
|
|
24
|
+
"""A code editor widget with line numbers, bracket matching, and smart indent.
|
|
25
|
+
|
|
26
|
+
CodeEditor wraps `_MultilineCore` with editor-grade defaults and
|
|
27
|
+
pre-installed extensions. It is not Field-wrapped — use `TextArea`
|
|
28
|
+
for labeled form inputs.
|
|
29
|
+
|
|
30
|
+
Pre-installed extensions:
|
|
31
|
+
- `LineNumbers` sidebar (when `show_line_numbers=True`)
|
|
32
|
+
- `BracketMatcher` — highlights matching bracket pairs
|
|
33
|
+
- `SmartIndent` — auto-indent on Return, Tab-to-spaces
|
|
34
|
+
|
|
35
|
+
Add your own extensions with `editor.install(filter)` and remove
|
|
36
|
+
them with `editor.uninstall(filter)`.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
master: Master = None,
|
|
42
|
+
*,
|
|
43
|
+
value: str = "",
|
|
44
|
+
textsignal: Signal | None = None,
|
|
45
|
+
language: str | None = None,
|
|
46
|
+
pygments_style: str = "auto",
|
|
47
|
+
light_theme: str = "default",
|
|
48
|
+
dark_theme: str = "monokai",
|
|
49
|
+
read_only: bool = False,
|
|
50
|
+
wrap: bool = False,
|
|
51
|
+
tab_width: int = 4,
|
|
52
|
+
insert_spaces: bool = True,
|
|
53
|
+
auto_indent: bool = True,
|
|
54
|
+
show_line_numbers: bool = True,
|
|
55
|
+
show_indent_guides: bool = False,
|
|
56
|
+
height: int = 20,
|
|
57
|
+
scrollbars: ScrollbarMode = "both",
|
|
58
|
+
font: str = "TkFixedFont",
|
|
59
|
+
show_border: bool = True,
|
|
60
|
+
accent: str = "primary",
|
|
61
|
+
extensions: list[EditFilter] | None = None,
|
|
62
|
+
on_change: Callable | None = None,
|
|
63
|
+
on_cursor_move: Callable | None = None,
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Create a CodeEditor widget.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
master: Parent widget. If None, uses the default root window.
|
|
69
|
+
value: Initial text content.
|
|
70
|
+
textsignal: Reactive `Signal[str]` bound to the editor content.
|
|
71
|
+
language: Pygments lexer name for syntax highlighting (e.g.
|
|
72
|
+
`'python'`, `'sql'`). Pass `None` to disable highlighting.
|
|
73
|
+
pygments_style: Pygments style name for syntax colors (e.g.
|
|
74
|
+
`'default'`, `'monokai'`, `'dracula'`). Defaults to
|
|
75
|
+
`'default'`.
|
|
76
|
+
read_only: If True, the editor is not editable.
|
|
77
|
+
wrap: If True, long lines wrap. Defaults to False (horizontal
|
|
78
|
+
scroll) which is the typical code editor behavior.
|
|
79
|
+
tab_width: Number of spaces per tab stop. Default is 4.
|
|
80
|
+
insert_spaces: If True, Tab inserts spaces. Default is True.
|
|
81
|
+
auto_indent: If True, Return replicates the current line's
|
|
82
|
+
indentation. Default is True.
|
|
83
|
+
show_line_numbers: If True (default), shows a line number
|
|
84
|
+
gutter to the left of the text.
|
|
85
|
+
show_indent_guides: If True, draws subtle vertical guide marks
|
|
86
|
+
at each indent stop. Defaults to False.
|
|
87
|
+
scrollbars: Scrollbar visibility mode. Defaults to `"both"`
|
|
88
|
+
(always show both axes).
|
|
89
|
+
font: Font for the editor. Defaults to the system fixed-width
|
|
90
|
+
font (`"TkFixedFont"`).
|
|
91
|
+
extensions: Additional `EditFilter` instances to install
|
|
92
|
+
on top of the built-in set.
|
|
93
|
+
show_border: If True (default), styles the editor frame as a
|
|
94
|
+
themed border that gains a focus ring on interaction.
|
|
95
|
+
accent: Accent token for the focus border. Defaults to `"primary"`.
|
|
96
|
+
on_change: Callback for `<<Change>>` events.
|
|
97
|
+
on_cursor_move: Callback for cursor movement events.
|
|
98
|
+
"""
|
|
99
|
+
frame_kw = (
|
|
100
|
+
{"ttk_class": "TField", "accent": accent, "padding": 5}
|
|
101
|
+
if show_border else {}
|
|
102
|
+
)
|
|
103
|
+
super().__init__(master, **frame_kw)
|
|
104
|
+
|
|
105
|
+
self._language = language
|
|
106
|
+
self._pygments_style = pygments_style
|
|
107
|
+
self._light_theme = light_theme
|
|
108
|
+
self._dark_theme = dark_theme
|
|
109
|
+
self._show_line_numbers = show_line_numbers
|
|
110
|
+
self._tab_width = tab_width
|
|
111
|
+
self._insert_spaces = insert_spaces
|
|
112
|
+
self._highlighter = None
|
|
113
|
+
self._rules: list = []
|
|
114
|
+
self._valid_signal: Signal = Signal(True)
|
|
115
|
+
self._error_signal: Signal = Signal("")
|
|
116
|
+
|
|
117
|
+
# ── core ──────────────────────────────────────────────────────────
|
|
118
|
+
self._core = _MultilineCore(
|
|
119
|
+
self,
|
|
120
|
+
value=value,
|
|
121
|
+
wrap=wrap,
|
|
122
|
+
height=height,
|
|
123
|
+
scrollbars=scrollbars,
|
|
124
|
+
font=font,
|
|
125
|
+
read_only=read_only,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if show_border:
|
|
129
|
+
self._core.text.bind(
|
|
130
|
+
"<FocusIn>", lambda _: self.state(["focus"]), add="+",
|
|
131
|
+
)
|
|
132
|
+
self._core.text.bind(
|
|
133
|
+
"<FocusOut>", lambda _: self.state(["!focus"]), add="+",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# ── search overlay ────────────────────────────────────────────────
|
|
137
|
+
self._search = SearchOverlay(self, self._core)
|
|
138
|
+
|
|
139
|
+
# _core fills the frame; _search is permanently placed at the bottom
|
|
140
|
+
# and rendered at all times — hidden by keeping _core on top (lift).
|
|
141
|
+
# show_search() just lifts _search; hide_search() lifts _core back.
|
|
142
|
+
# This means there is never a first-render delay on open.
|
|
143
|
+
self._core.pack(fill="both", expand=True)
|
|
144
|
+
self._search.place(
|
|
145
|
+
x=0, rely=1.0, relwidth=1.0,
|
|
146
|
+
height=self._search._height, anchor="sw",
|
|
147
|
+
)
|
|
148
|
+
self._core.lift()
|
|
149
|
+
|
|
150
|
+
# ── signal binding ────────────────────────────────────────────────
|
|
151
|
+
if textsignal is not None:
|
|
152
|
+
self._core.bind_signal(textsignal)
|
|
153
|
+
|
|
154
|
+
# ── pre-installed extensions ──────────────────────────────────────
|
|
155
|
+
if show_line_numbers:
|
|
156
|
+
self._line_numbers = LineNumbers()
|
|
157
|
+
self._core.add_sidebar(self._line_numbers)
|
|
158
|
+
else:
|
|
159
|
+
self._line_numbers = None
|
|
160
|
+
|
|
161
|
+
self._bracket_matcher = BracketMatcher()
|
|
162
|
+
self._core.add_filter(self._bracket_matcher)
|
|
163
|
+
|
|
164
|
+
if auto_indent:
|
|
165
|
+
self._smart_indent = SmartIndent(
|
|
166
|
+
tab_width=tab_width,
|
|
167
|
+
insert_spaces=insert_spaces,
|
|
168
|
+
)
|
|
169
|
+
self._core.add_filter(self._smart_indent)
|
|
170
|
+
else:
|
|
171
|
+
self._smart_indent = None
|
|
172
|
+
|
|
173
|
+
# ── indent guides ─────────────────────────────────────────────────
|
|
174
|
+
if show_indent_guides:
|
|
175
|
+
self._indent_guides = IndentGuides(tab_width=tab_width)
|
|
176
|
+
self._core.add_filter(self._indent_guides)
|
|
177
|
+
else:
|
|
178
|
+
self._indent_guides = None
|
|
179
|
+
|
|
180
|
+
# ── user-supplied extensions ──────────────────────────────────────
|
|
181
|
+
if extensions:
|
|
182
|
+
for ext in extensions:
|
|
183
|
+
self._core.add_filter(ext)
|
|
184
|
+
|
|
185
|
+
# ── syntax highlighting ───────────────────────────────────────────
|
|
186
|
+
if language is not None:
|
|
187
|
+
self._try_install_highlighter(language)
|
|
188
|
+
|
|
189
|
+
# ── callbacks ─────────────────────────────────────────────────────
|
|
190
|
+
if on_change is not None:
|
|
191
|
+
self._core.on_change(on_change)
|
|
192
|
+
|
|
193
|
+
if on_cursor_move is not None:
|
|
194
|
+
self._core.text.bind("<<CursorMove>>", on_cursor_move, add="+")
|
|
195
|
+
self._core.text.bind("<KeyRelease>", on_cursor_move, add="+")
|
|
196
|
+
self._core.text.bind("<ButtonRelease-1>", on_cursor_move, add="+")
|
|
197
|
+
|
|
198
|
+
# ── validation: auto-run on blur ──────────────────────────────────
|
|
199
|
+
self._core.text.bind("<FocusOut>", self._on_focus_out_validate, add="+")
|
|
200
|
+
|
|
201
|
+
# ── public API ────────────────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def value(self) -> str:
|
|
205
|
+
"""Full text content (trailing newline stripped)."""
|
|
206
|
+
return self._core.value
|
|
207
|
+
|
|
208
|
+
@value.setter
|
|
209
|
+
def value(self, text: str) -> None:
|
|
210
|
+
self._core.value = text
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def is_dirty(self) -> bool:
|
|
214
|
+
"""True if the content has changed since last save."""
|
|
215
|
+
return self._core.is_dirty
|
|
216
|
+
|
|
217
|
+
def mark_saved(self) -> None:
|
|
218
|
+
"""Mark the current state as the saved baseline."""
|
|
219
|
+
self._core.mark_saved()
|
|
220
|
+
|
|
221
|
+
def undo(self) -> None:
|
|
222
|
+
"""Undo the last edit."""
|
|
223
|
+
self._core.undo()
|
|
224
|
+
|
|
225
|
+
def redo(self) -> None:
|
|
226
|
+
"""Redo the last undone edit."""
|
|
227
|
+
self._core.redo()
|
|
228
|
+
|
|
229
|
+
def undo_block_start(self) -> None:
|
|
230
|
+
"""Begin a compound undo block."""
|
|
231
|
+
self._core.undo_block_start()
|
|
232
|
+
|
|
233
|
+
def undo_block_stop(self) -> None:
|
|
234
|
+
"""End a compound undo block."""
|
|
235
|
+
self._core.undo_block_stop()
|
|
236
|
+
|
|
237
|
+
def goto(self, line: int, col: int = 1) -> None:
|
|
238
|
+
"""Move the cursor to a 1-indexed *(line, col)* and scroll it into view.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
line: 1-indexed line number.
|
|
242
|
+
col: 1-indexed column. Defaults to 1 (the start of the line).
|
|
243
|
+
"""
|
|
244
|
+
idx = f"{line}.{col - 1}"
|
|
245
|
+
try:
|
|
246
|
+
self._core.text.mark_set("insert", idx)
|
|
247
|
+
self._core.text.see(idx)
|
|
248
|
+
except tk.TclError:
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
def set_language(self, language: str | None) -> None:
|
|
252
|
+
"""Set or change the syntax highlighting language.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
language: Pygments lexer name (e.g. `'python'`), or `None`
|
|
256
|
+
to disable syntax highlighting.
|
|
257
|
+
"""
|
|
258
|
+
self._language = language
|
|
259
|
+
if self._highlighter is not None:
|
|
260
|
+
self._core.remove_filter(self._highlighter)
|
|
261
|
+
self._highlighter = None
|
|
262
|
+
if language is not None:
|
|
263
|
+
self._try_install_highlighter(language)
|
|
264
|
+
|
|
265
|
+
def install(self, f: EditFilter) -> None:
|
|
266
|
+
"""Install a custom filter extension.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
f: The `EditFilter` to add.
|
|
270
|
+
"""
|
|
271
|
+
self._core.add_filter(f)
|
|
272
|
+
|
|
273
|
+
def uninstall(self, f: EditFilter) -> None:
|
|
274
|
+
"""Remove a previously installed filter extension.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
f: The `EditFilter` to remove.
|
|
278
|
+
"""
|
|
279
|
+
self._core.remove_filter(f)
|
|
280
|
+
|
|
281
|
+
def focus_set(self) -> None:
|
|
282
|
+
"""Set focus to the editor."""
|
|
283
|
+
self._core.focus_set()
|
|
284
|
+
|
|
285
|
+
def on_change(self, callback: Callable) -> str:
|
|
286
|
+
"""Register a callback for `<<Change>>` events.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
callback: Receives the Tk event.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Bind ID — pass to `off_change()` to unsubscribe.
|
|
293
|
+
"""
|
|
294
|
+
return self._core.on_change(callback)
|
|
295
|
+
|
|
296
|
+
def off_change(self, bind_id: str | None = None) -> None:
|
|
297
|
+
"""Unsubscribe from `<<Change>>`.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
bind_id: ID returned by `on_change()`. If None, removes all.
|
|
301
|
+
"""
|
|
302
|
+
self._core.off_change(bind_id)
|
|
303
|
+
|
|
304
|
+
def on_modified(self, callback: Callable) -> str:
|
|
305
|
+
"""Register a callback for `<<TextModified>>` events.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
callback: Receives event with `event.data = {"is_dirty": bool}`.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Bind ID — pass to `off_modified()` to unsubscribe.
|
|
312
|
+
"""
|
|
313
|
+
return self._core.on_modified(callback)
|
|
314
|
+
|
|
315
|
+
def off_modified(self, bind_id: str | None = None) -> None:
|
|
316
|
+
"""Unsubscribe from `<<TextModified>>`.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
bind_id: ID returned by `on_modified()`. If None, removes all.
|
|
320
|
+
"""
|
|
321
|
+
self._core.off_modified(bind_id)
|
|
322
|
+
|
|
323
|
+
def on_undo(self, callback: Callable) -> str:
|
|
324
|
+
"""Register a callback for `<<TextUndo>>` events.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
callback: Receives event with `event.data = {"value": str}`.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Bind ID — pass to `off_undo()` to unsubscribe.
|
|
331
|
+
"""
|
|
332
|
+
return self._core.on_undo(callback)
|
|
333
|
+
|
|
334
|
+
def off_undo(self, bind_id: str | None = None) -> None:
|
|
335
|
+
"""Unsubscribe from `<<TextUndo>>`.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
bind_id: ID returned by `on_undo()`. If None, removes all.
|
|
339
|
+
"""
|
|
340
|
+
self._core.off_undo(bind_id)
|
|
341
|
+
|
|
342
|
+
def on_redo(self, callback: Callable) -> str:
|
|
343
|
+
"""Register a callback for `<<TextRedo>>` events.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
callback: Receives event with `event.data = {"value": str}`.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
Bind ID — pass to `off_redo()` to unsubscribe.
|
|
350
|
+
"""
|
|
351
|
+
return self._core.on_redo(callback)
|
|
352
|
+
|
|
353
|
+
def off_redo(self, bind_id: str | None = None) -> None:
|
|
354
|
+
"""Unsubscribe from `<<TextRedo>>`.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
bind_id: ID returned by `on_redo()`. If None, removes all.
|
|
358
|
+
"""
|
|
359
|
+
self._core.off_redo(bind_id)
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def core(self) -> _MultilineCore:
|
|
363
|
+
"""The internal `_MultilineCore` — for advanced extension use."""
|
|
364
|
+
return self._core
|
|
365
|
+
|
|
366
|
+
@property
|
|
367
|
+
def language(self) -> str | None:
|
|
368
|
+
"""Active syntax highlighting language, or `None` if disabled."""
|
|
369
|
+
return self._language
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def theme(self) -> str:
|
|
373
|
+
"""Active Pygments color scheme, or `'auto'` if tracking the bootstack theme."""
|
|
374
|
+
return self._pygments_style
|
|
375
|
+
|
|
376
|
+
def set_theme(self, v: str) -> None:
|
|
377
|
+
"""Change the active Pygments color scheme.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
v: Pygments style name, or `'auto'` to track the bootstack theme.
|
|
381
|
+
"""
|
|
382
|
+
self._pygments_style = v
|
|
383
|
+
if self._highlighter is not None:
|
|
384
|
+
self._highlighter._auto = (v == "auto")
|
|
385
|
+
if v != "auto":
|
|
386
|
+
self._highlighter._load_style(v)
|
|
387
|
+
for sname, color in self._highlighter._style_colors.items():
|
|
388
|
+
self._core.define_style(sname, foreground=color)
|
|
389
|
+
self._highlighter._apply_widget_colors(self._core)
|
|
390
|
+
self._highlighter._schedule()
|
|
391
|
+
|
|
392
|
+
def show_search(self) -> None:
|
|
393
|
+
"""Show the find bar and focus the search input."""
|
|
394
|
+
self._search.show(replace=False)
|
|
395
|
+
|
|
396
|
+
def show_replace(self) -> None:
|
|
397
|
+
"""Show the find/replace bar with the replace row visible."""
|
|
398
|
+
self._search.show(replace=True)
|
|
399
|
+
|
|
400
|
+
def hide_search(self) -> None:
|
|
401
|
+
"""Hide the find/replace bar and clear search highlights."""
|
|
402
|
+
self._search.hide()
|
|
403
|
+
|
|
404
|
+
# ── indent/dedent ─────────────────────────────────────────────────────
|
|
405
|
+
|
|
406
|
+
def indent(self) -> None:
|
|
407
|
+
"""Indent selected lines by one tab stop, or insert at cursor."""
|
|
408
|
+
if self._smart_indent is not None:
|
|
409
|
+
self._smart_indent.indent()
|
|
410
|
+
else:
|
|
411
|
+
self._indent_or_dedent(indent=True)
|
|
412
|
+
|
|
413
|
+
def dedent(self) -> None:
|
|
414
|
+
"""Dedent selected lines (or current line) by one tab stop."""
|
|
415
|
+
if self._smart_indent is not None:
|
|
416
|
+
self._smart_indent.dedent()
|
|
417
|
+
else:
|
|
418
|
+
self._indent_or_dedent(indent=False)
|
|
419
|
+
|
|
420
|
+
def _indent_or_dedent(self, indent: bool) -> None:
|
|
421
|
+
from bootstack.widgets._impl.composites.textarea.extensions.smart_indent import _sel_line_range
|
|
422
|
+
tw = self._core.text
|
|
423
|
+
ranges = tw.tag_ranges("sel")
|
|
424
|
+
if indent:
|
|
425
|
+
prefix = " " * self._tab_width if self._insert_spaces else "\t"
|
|
426
|
+
if not ranges:
|
|
427
|
+
col = int(tw.index("insert").split(".")[1])
|
|
428
|
+
spaces = self._tab_width - (col % self._tab_width)
|
|
429
|
+
tw.insert("insert", " " * spaces if self._insert_spaces else "\t")
|
|
430
|
+
return
|
|
431
|
+
start_line, end_line = _sel_line_range(ranges)
|
|
432
|
+
self._core.undo_block_start()
|
|
433
|
+
try:
|
|
434
|
+
for ln in range(start_line, end_line + 1):
|
|
435
|
+
if tw.get(f"{ln}.0", f"{ln}.end"):
|
|
436
|
+
tw.insert(f"{ln}.0", prefix)
|
|
437
|
+
finally:
|
|
438
|
+
self._core.undo_block_stop()
|
|
439
|
+
tw.tag_add("sel", f"{start_line}.0", f"{end_line}.end")
|
|
440
|
+
else:
|
|
441
|
+
if ranges:
|
|
442
|
+
start_line, end_line = _sel_line_range(ranges)
|
|
443
|
+
else:
|
|
444
|
+
start_line = end_line = int(tw.index("insert").split(".")[0])
|
|
445
|
+
self._core.undo_block_start()
|
|
446
|
+
try:
|
|
447
|
+
for ln in range(start_line, end_line + 1):
|
|
448
|
+
line_text = tw.get(f"{ln}.0", f"{ln}.end")
|
|
449
|
+
if self._insert_spaces:
|
|
450
|
+
n = min(self._tab_width, len(line_text) - len(line_text.lstrip(" ")))
|
|
451
|
+
if n:
|
|
452
|
+
tw.delete(f"{ln}.0", f"{ln}.{n}")
|
|
453
|
+
else:
|
|
454
|
+
if line_text.startswith("\t"):
|
|
455
|
+
tw.delete(f"{ln}.0", f"{ln}.1")
|
|
456
|
+
finally:
|
|
457
|
+
self._core.undo_block_stop()
|
|
458
|
+
if ranges:
|
|
459
|
+
tw.tag_add("sel", f"{start_line}.0", f"{end_line}.end")
|
|
460
|
+
|
|
461
|
+
# ── validation ────────────────────────────────────────────────────────
|
|
462
|
+
|
|
463
|
+
def add_validation_rule(
|
|
464
|
+
self,
|
|
465
|
+
rule_type: str,
|
|
466
|
+
message: str | None = None,
|
|
467
|
+
**kwargs,
|
|
468
|
+
) -> None:
|
|
469
|
+
"""Add a validation rule to this editor.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
rule_type: Rule name — `"required"`, `"pattern"`, `"stringLength"`, etc.
|
|
473
|
+
message: Custom error message. If None, the rule's default is used.
|
|
474
|
+
**kwargs: Rule-specific parameters (e.g. `minLength=`, `func=`).
|
|
475
|
+
"""
|
|
476
|
+
from bootstack.validation.validation_rules import ValidationRule
|
|
477
|
+
self._rules.append(ValidationRule(rule_type, message=message, **kwargs))
|
|
478
|
+
|
|
479
|
+
def validate(self) -> bool:
|
|
480
|
+
"""Run all validation rules immediately and return True if valid.
|
|
481
|
+
|
|
482
|
+
Triggers `<<Valid>>` / `<<Invalid>>` / `<<Validate>>` events and
|
|
483
|
+
updates the `_valid_signal` and `_error_signal`.
|
|
484
|
+
"""
|
|
485
|
+
return self._run_validation(trigger="manual")
|
|
486
|
+
|
|
487
|
+
def _set_validity(self, is_valid: bool, message: str) -> None:
|
|
488
|
+
self._error_signal.set("" if is_valid else message)
|
|
489
|
+
self._valid_signal.set(is_valid)
|
|
490
|
+
|
|
491
|
+
def _run_validation(self, trigger: str = "blur") -> bool:
|
|
492
|
+
value = self.value
|
|
493
|
+
for rule in self._rules:
|
|
494
|
+
if trigger != "manual" and rule.trigger not in ("always", trigger):
|
|
495
|
+
continue
|
|
496
|
+
result = rule.validate(value)
|
|
497
|
+
if not result.is_valid:
|
|
498
|
+
self._set_validity(False, result.message)
|
|
499
|
+
data = ValidationEvent(value=value, is_valid=False, message=result.message)
|
|
500
|
+
self.event_generate("<<Invalid>>", data=data, when="tail")
|
|
501
|
+
self.event_generate("<<Validate>>", data=data, when="tail")
|
|
502
|
+
return False
|
|
503
|
+
self._set_validity(True, "")
|
|
504
|
+
data = ValidationEvent(value=value, is_valid=True, message="")
|
|
505
|
+
self.event_generate("<<Valid>>", data=data, when="tail")
|
|
506
|
+
self.event_generate("<<Validate>>", data=data, when="tail")
|
|
507
|
+
return True
|
|
508
|
+
|
|
509
|
+
def _on_focus_out_validate(self, _event: tk.Event) -> None:
|
|
510
|
+
if self._rules:
|
|
511
|
+
self._run_validation(trigger="blur")
|
|
512
|
+
|
|
513
|
+
# ── internal ─────────────────────────────────────────────────────────
|
|
514
|
+
|
|
515
|
+
def _try_install_highlighter(self, language: str) -> None:
|
|
516
|
+
from bootstack.widgets._impl.composites.textarea.extensions.pygments_highlighter import (
|
|
517
|
+
PygmentsHighlighter,
|
|
518
|
+
)
|
|
519
|
+
h = PygmentsHighlighter(
|
|
520
|
+
language,
|
|
521
|
+
pygments_style=self._pygments_style,
|
|
522
|
+
light_style=self._light_theme,
|
|
523
|
+
dark_style=self._dark_theme,
|
|
524
|
+
)
|
|
525
|
+
self._core.add_filter(h)
|
|
526
|
+
self._highlighter = h
|