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,675 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import tkinter
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from typing import overload, Any, Callable, Iterator, Literal, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from bootstack.widgets._impl.composites.textarea.codeeditor import CodeEditor as _InternalCodeEditor
|
|
8
|
+
from bootstack.widgets._impl.composites.textarea.filter import EditFilter
|
|
9
|
+
from bootstack.widgets._core.base import PublicWidgetBase, adapt_handler
|
|
10
|
+
from bootstack.widgets._core.events import resolve_event, register_widget_events
|
|
11
|
+
from bootstack.events import ChangeEvent, InputEvent, Subscription, TextModifiedEvent, ValidationEvent
|
|
12
|
+
from bootstack.streams import Stream
|
|
13
|
+
from bootstack.validation import RuleType
|
|
14
|
+
from bootstack.widgets.types import AccentToken, Event
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from bootstack.signals import Signal
|
|
18
|
+
|
|
19
|
+
_CODEEDITOR_EVENTS: dict[str, str] = {
|
|
20
|
+
"change": "<<BsChange>>",
|
|
21
|
+
"input": "<<BsInput>>",
|
|
22
|
+
"valid": "<<Valid>>",
|
|
23
|
+
"invalid": "<<Invalid>>",
|
|
24
|
+
"validate": "<<Validate>>",
|
|
25
|
+
"modified": "<<TextModified>>",
|
|
26
|
+
"undo": "<<TextUndo>>",
|
|
27
|
+
"redo": "<<TextRedo>>",
|
|
28
|
+
"cursor_move": "<<CursorMove>>",
|
|
29
|
+
"focus": "<FocusIn>",
|
|
30
|
+
"blur": "<FocusOut>",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Sequences emitted on the inner text widget (bound there by `on()`). The typed
|
|
34
|
+
# `<<BsChange>>`/`<<BsInput>>` are re-emitted on `self._internal`, so they are
|
|
35
|
+
# NOT listed here.
|
|
36
|
+
_INNER_SEQUENCES = frozenset({
|
|
37
|
+
"<<TextModified>>", "<<TextUndo>>", "<<TextRedo>>",
|
|
38
|
+
"<<CursorMove>>", "<FocusIn>", "<FocusOut>",
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CodeEditor(PublicWidgetBase):
|
|
43
|
+
"""A full-featured code editor with line numbers, bracket matching, and syntax highlighting.
|
|
44
|
+
|
|
45
|
+
Not Field-wrapped — use `TextArea` for labeled form inputs.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
value: Initial text content.
|
|
49
|
+
textsignal: Reactive `Signal[str]` bound to the editor content.
|
|
50
|
+
language: Pygments lexer name for syntax highlighting (e.g. `'python'`,
|
|
51
|
+
`'json'`, `'sql'`). Any Pygments lexer name works; unknown names
|
|
52
|
+
fall back to plain text. `None` disables highlighting. See the full
|
|
53
|
+
list at https://pygments.org/languages/.
|
|
54
|
+
theme: Pygments color scheme. `'auto'` (default) switches between
|
|
55
|
+
`light_theme` and `dark_theme` when the bootstack theme changes.
|
|
56
|
+
Pass an explicit Pygments style name (e.g. `'monokai'`, `'dracula'`)
|
|
57
|
+
to pin the scheme. See the full list at https://pygments.org/styles/.
|
|
58
|
+
light_theme: Pygments style used when `theme='auto'` and the active
|
|
59
|
+
bootstack theme is light. Default `'default'`.
|
|
60
|
+
dark_theme: Pygments style used when `theme='auto'` and the active
|
|
61
|
+
bootstack theme is dark. Default `'monokai'`.
|
|
62
|
+
read_only: If True, content is visible but not editable.
|
|
63
|
+
wrap: If True, long lines wrap. Default `False` (horizontal scroll).
|
|
64
|
+
tab_width: Number of spaces per tab stop. Default `4`.
|
|
65
|
+
insert_spaces: If True, Tab inserts spaces instead of a tab character.
|
|
66
|
+
auto_indent: If True, Return replicates the current line's indentation.
|
|
67
|
+
show_line_numbers: If True (default), shows a line-number gutter.
|
|
68
|
+
show_indent_guides: If True, draws subtle vertical guide marks at
|
|
69
|
+
each indent stop.
|
|
70
|
+
height: Visible row count. Default `20`.
|
|
71
|
+
scrollbars: Scrollbar visibility. Default `'both'`. Horizontal
|
|
72
|
+
scrolling requires `wrap=False`.
|
|
73
|
+
font: Semantic font token for the editor. Default `'code'` (the
|
|
74
|
+
monospace token). See :doc:`/reference/typography`.
|
|
75
|
+
show_border: If True (default), styles the editor frame as a themed
|
|
76
|
+
border with a focus ring.
|
|
77
|
+
accent: Accent token applied to the focus border. Default `'primary'`.
|
|
78
|
+
extensions: Additional `EditFilter` instances to install on top of
|
|
79
|
+
the built-in set.
|
|
80
|
+
parent: Override the context-stack parent.
|
|
81
|
+
**kwargs: Layout placement options applied by the parent container —
|
|
82
|
+
`fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
|
|
83
|
+
See :doc:`/tasks/layout`.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
value: str = "",
|
|
89
|
+
*,
|
|
90
|
+
textsignal: "Signal[str] | None" = None,
|
|
91
|
+
language: str | None = None,
|
|
92
|
+
theme: str = "auto",
|
|
93
|
+
light_theme: str = "default",
|
|
94
|
+
dark_theme: str = "monokai",
|
|
95
|
+
read_only: bool = False,
|
|
96
|
+
wrap: bool = False,
|
|
97
|
+
tab_width: int = 4,
|
|
98
|
+
insert_spaces: bool = True,
|
|
99
|
+
auto_indent: bool = True,
|
|
100
|
+
show_line_numbers: bool = True,
|
|
101
|
+
show_indent_guides: bool = False,
|
|
102
|
+
height: int = 20,
|
|
103
|
+
scrollbars: Literal["both", "auto", "vertical", "none"] = "both",
|
|
104
|
+
font: str = "code",
|
|
105
|
+
show_border: bool = True,
|
|
106
|
+
accent: AccentToken | str = "primary",
|
|
107
|
+
extensions: list[EditFilter] | None = None,
|
|
108
|
+
parent: Any = None,
|
|
109
|
+
**kwargs: Any,
|
|
110
|
+
) -> None:
|
|
111
|
+
self._parent = self._resolve_parent(parent)
|
|
112
|
+
layout_kw = self._split_layout_kwargs(kwargs)
|
|
113
|
+
|
|
114
|
+
tk_master = self._parent._child_master() if self._parent else None
|
|
115
|
+
|
|
116
|
+
internal_kwargs: dict[str, Any] = {
|
|
117
|
+
"value": value,
|
|
118
|
+
"language": language,
|
|
119
|
+
"pygments_style": theme,
|
|
120
|
+
"light_theme": light_theme,
|
|
121
|
+
"dark_theme": dark_theme,
|
|
122
|
+
"read_only": read_only,
|
|
123
|
+
"wrap": wrap,
|
|
124
|
+
"tab_width": tab_width,
|
|
125
|
+
"insert_spaces": insert_spaces,
|
|
126
|
+
"auto_indent": auto_indent,
|
|
127
|
+
"show_line_numbers": show_line_numbers,
|
|
128
|
+
"show_indent_guides": show_indent_guides,
|
|
129
|
+
"height": height,
|
|
130
|
+
"scrollbars": scrollbars,
|
|
131
|
+
"font": font,
|
|
132
|
+
"show_border": show_border,
|
|
133
|
+
"accent": accent,
|
|
134
|
+
}
|
|
135
|
+
if textsignal is not None:
|
|
136
|
+
internal_kwargs["textsignal"] = textsignal
|
|
137
|
+
if extensions is not None:
|
|
138
|
+
internal_kwargs["extensions"] = extensions
|
|
139
|
+
|
|
140
|
+
self._internal = _InternalCodeEditor(tk_master, **internal_kwargs)
|
|
141
|
+
|
|
142
|
+
# Generate <<CursorMove>> so on_cursor_move() subscribers always work.
|
|
143
|
+
t = self._internal.core.text
|
|
144
|
+
t.bind("<KeyRelease>", lambda e: t.event_generate("<<CursorMove>>"), add="+")
|
|
145
|
+
t.bind("<ButtonRelease-1>", lambda e: t.event_generate("<<CursorMove>>"), add="+")
|
|
146
|
+
|
|
147
|
+
# Re-emit the core's raw <<Change>> (which carries a low-level
|
|
148
|
+
# {"op", "index"} dict) as typed payloads on the public widget, so
|
|
149
|
+
# on_change()/on_input() deliver the editor text — like TextArea.
|
|
150
|
+
def _emit_typed_change(_e: Any = None) -> None:
|
|
151
|
+
text = self._internal.value
|
|
152
|
+
self._internal.event_generate("<<BsChange>>", data=ChangeEvent(value=text, text=text))
|
|
153
|
+
self._internal.event_generate("<<BsInput>>", data=InputEvent(text=text))
|
|
154
|
+
t.bind("<<Change>>", _emit_typed_change, add="+")
|
|
155
|
+
|
|
156
|
+
self._attach_to_parent(layout_kw)
|
|
157
|
+
|
|
158
|
+
# ----- Event routing -----
|
|
159
|
+
|
|
160
|
+
def _text_widget(self) -> tkinter.Misc:
|
|
161
|
+
return self._internal.core.text
|
|
162
|
+
|
|
163
|
+
@overload
|
|
164
|
+
def on(self, event: str) -> Stream: ...
|
|
165
|
+
@overload
|
|
166
|
+
def on(self, event: str, handler: Callable[[Event], Any]) -> Subscription: ...
|
|
167
|
+
def on(self, event: str, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
|
|
168
|
+
"""Register a callback for an event by name.
|
|
169
|
+
|
|
170
|
+
A generic, string-keyed escape hatch — prefer the typed `on_*`
|
|
171
|
+
shorthands (e.g. `on_change`), which carry the precise payload type.
|
|
172
|
+
Called with no handler, returns a composable `Stream`; with a handler,
|
|
173
|
+
binds it and returns a `Subscription`.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
event: Event name (for example `'change'` or `'focus'`).
|
|
177
|
+
handler: Called with the event payload. Omit to get a composable
|
|
178
|
+
:class:`~bootstack.streams.Stream` instead.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
182
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
183
|
+
"""
|
|
184
|
+
sequence = resolve_event(self, str(event))
|
|
185
|
+
target = self._text_widget() if sequence in _INNER_SEQUENCES else self._internal
|
|
186
|
+
if handler is None:
|
|
187
|
+
def _source(h):
|
|
188
|
+
t = self._text_widget() if sequence in _INNER_SEQUENCES else self._internal
|
|
189
|
+
bid = t.bind(sequence, adapt_handler(h), add="+")
|
|
190
|
+
return Subscription(t, sequence, bid)
|
|
191
|
+
return Stream(self._internal, _source=_source)
|
|
192
|
+
bid = target.bind(sequence, adapt_handler(handler), add="+")
|
|
193
|
+
return Subscription(target, sequence, bid)
|
|
194
|
+
|
|
195
|
+
# ----- Properties -----
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def value(self) -> str:
|
|
199
|
+
"""Full text content (trailing newline stripped)."""
|
|
200
|
+
return self._internal.value
|
|
201
|
+
|
|
202
|
+
@value.setter
|
|
203
|
+
def value(self, v: str) -> None:
|
|
204
|
+
self._internal.value = v
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def text(self) -> str:
|
|
208
|
+
"""The current display text. Same as `value` for a code editor (no
|
|
209
|
+
formatting layer). Read-only — assign to `value` to change it."""
|
|
210
|
+
return self._internal.value
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def signal(self) -> "Signal[str] | None":
|
|
214
|
+
"""The reactive `Signal` bound to this editor, or `None`."""
|
|
215
|
+
return getattr(self._internal.core, "signal", None)
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def valid(self) -> "Signal[bool]":
|
|
219
|
+
"""Reactive `Signal[bool]` — `True` when all validation rules pass.
|
|
220
|
+
|
|
221
|
+
Starts `True`. Updates after every validation run (blur or manual
|
|
222
|
+
`validate()` call). Subscribe a named handler to react immediately when
|
|
223
|
+
validity changes (a `lambda` cannot assign, so define a function):
|
|
224
|
+
`def on_valid(ok): btn.disabled = not ok` then
|
|
225
|
+
`editor.valid.subscribe(on_valid)`.
|
|
226
|
+
"""
|
|
227
|
+
return self._internal._valid_signal
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def error(self) -> "Signal[str]":
|
|
231
|
+
"""Reactive `Signal[str]` — current validation error message.
|
|
232
|
+
|
|
233
|
+
Empty string when the editor is valid. Updates in lockstep with `valid`.
|
|
234
|
+
"""
|
|
235
|
+
return self._internal._error_signal
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def is_dirty(self) -> bool:
|
|
239
|
+
"""True if content has changed since the last `mark_saved()` call."""
|
|
240
|
+
return self._internal.is_dirty
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def cursor_position(self) -> tuple[int, int]:
|
|
244
|
+
"""The insertion cursor as a 1-indexed `(line, col)` tuple.
|
|
245
|
+
|
|
246
|
+
`(1, 1)` is the start of the content — the same coordinates `insert()`
|
|
247
|
+
and `goto()` accept. Read it in an `on_cursor_move` handler to drive a
|
|
248
|
+
status-bar readout. Read-only — use `goto()` to move the cursor.
|
|
249
|
+
"""
|
|
250
|
+
idx = self._internal.core.text.index("insert")
|
|
251
|
+
line, col = idx.split(".")
|
|
252
|
+
return int(line), int(col) + 1
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def selection(self) -> "tuple[tuple[int, int], tuple[int, int]] | None":
|
|
256
|
+
"""The current text selection as `((start_line, start_col), (end_line, end_col))`.
|
|
257
|
+
|
|
258
|
+
Both lines and columns are 1-indexed — the same coordinates `cursor_position`
|
|
259
|
+
reports and `goto()` / `insert()` accept. Returns `None` when no text is
|
|
260
|
+
selected. Set to a pair of `(line, col)` tuples to select a range, or
|
|
261
|
+
set to `None` to clear the selection.
|
|
262
|
+
"""
|
|
263
|
+
tw = self._internal.core.text
|
|
264
|
+
ranges = tw.tag_ranges("sel")
|
|
265
|
+
if not ranges:
|
|
266
|
+
return None
|
|
267
|
+
sl, sc = str(ranges[0]).split(".")
|
|
268
|
+
el, ec = str(ranges[1]).split(".")
|
|
269
|
+
return (int(sl), int(sc) + 1), (int(el), int(ec) + 1)
|
|
270
|
+
|
|
271
|
+
@selection.setter
|
|
272
|
+
def selection(self, v: "tuple[tuple[int, int], tuple[int, int]] | None") -> None:
|
|
273
|
+
tw = self._internal.core.text
|
|
274
|
+
tw.tag_remove("sel", "1.0", "end")
|
|
275
|
+
if v is not None:
|
|
276
|
+
(sl, sc), (el, ec) = v
|
|
277
|
+
tw.tag_add("sel", f"{sl}.{sc - 1}", f"{el}.{ec - 1}")
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def selected_text(self) -> str:
|
|
281
|
+
"""The currently selected text, or an empty string when nothing is selected."""
|
|
282
|
+
sel = self.selection
|
|
283
|
+
if sel is None:
|
|
284
|
+
return ""
|
|
285
|
+
(sl, sc), (el, ec) = sel
|
|
286
|
+
return self._internal.core.text.get(f"{sl}.{sc - 1}", f"{el}.{ec - 1}")
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def language(self) -> str | None:
|
|
290
|
+
"""Active syntax highlighting language, or `None` if disabled."""
|
|
291
|
+
return self._internal.language
|
|
292
|
+
|
|
293
|
+
@language.setter
|
|
294
|
+
def language(self, v: str | None) -> None:
|
|
295
|
+
self._internal.set_language(v)
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def theme(self) -> str:
|
|
299
|
+
"""Active Pygments color scheme, or `'auto'` if tracking the bootstack theme."""
|
|
300
|
+
return self._internal.theme
|
|
301
|
+
|
|
302
|
+
@theme.setter
|
|
303
|
+
def theme(self, v: str) -> None:
|
|
304
|
+
self._internal.set_theme(v)
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def read_only(self) -> bool:
|
|
308
|
+
"""Whether the editor content is visible but not editable."""
|
|
309
|
+
return self._internal.core.read_only
|
|
310
|
+
|
|
311
|
+
@read_only.setter
|
|
312
|
+
def read_only(self, v: bool) -> None:
|
|
313
|
+
# Route through the core property so the `_read_only` flag and the Tk
|
|
314
|
+
# widget state stay in lockstep (the value/insert setters consult the
|
|
315
|
+
# flag to lift the disabled state for programmatic writes).
|
|
316
|
+
self._internal.core.read_only = v
|
|
317
|
+
|
|
318
|
+
# ----- Methods -----
|
|
319
|
+
|
|
320
|
+
def validate(self) -> bool:
|
|
321
|
+
"""Run validation rules against the current content.
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
`True` if all rules pass, `False` otherwise.
|
|
325
|
+
"""
|
|
326
|
+
return self._internal.validate()
|
|
327
|
+
|
|
328
|
+
def add_validation_rule(self, rule_type: RuleType, **kwargs: Any) -> None:
|
|
329
|
+
"""Attach a validation rule to this editor.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
rule_type: The kind of validation rule to apply.
|
|
333
|
+
**kwargs: Rule options such as `message=`, `min=`, `max=`,
|
|
334
|
+
`trigger=`.
|
|
335
|
+
"""
|
|
336
|
+
self._internal.add_validation_rule(rule_type, **kwargs)
|
|
337
|
+
|
|
338
|
+
def clear(self) -> None:
|
|
339
|
+
"""Clear all text content."""
|
|
340
|
+
self._internal.value = ""
|
|
341
|
+
|
|
342
|
+
def insert(self, text: str, line: int | None = None, col: int | None = None) -> None:
|
|
343
|
+
"""Insert `text` at a position, defaulting to the cursor.
|
|
344
|
+
|
|
345
|
+
With no `line`/`col`, inserts at the current cursor. Pass `line` to
|
|
346
|
+
insert at the start of that line, or `line` and `col` together for an
|
|
347
|
+
exact position. Both are 1-indexed — `(1, 1)` is the start of the
|
|
348
|
+
content, the same coordinates `cursor_position` reports and `goto()`
|
|
349
|
+
accepts.
|
|
350
|
+
|
|
351
|
+
Read-only blocks *user* edits, not programmatic ones: this applies even
|
|
352
|
+
when `read_only=True`, leaving the editor read-only afterward.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
text: Text to insert.
|
|
356
|
+
line: 1-indexed line, or `None` to use the cursor's line. Defaults
|
|
357
|
+
to `None`.
|
|
358
|
+
col: 1-indexed column, or `None` for the start of the line.
|
|
359
|
+
Defaults to `None`.
|
|
360
|
+
"""
|
|
361
|
+
if line is None:
|
|
362
|
+
index = "insert"
|
|
363
|
+
else:
|
|
364
|
+
index = f"{line}.{(col or 1) - 1}"
|
|
365
|
+
self._internal.core.insert(index, text)
|
|
366
|
+
|
|
367
|
+
def append(self, text: str) -> None:
|
|
368
|
+
"""Append `text` to the end of the content.
|
|
369
|
+
|
|
370
|
+
Applies even when `read_only=True`, leaving the editor read-only
|
|
371
|
+
afterward.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
text: Text to add at the end of the content.
|
|
375
|
+
"""
|
|
376
|
+
self._internal.core.insert("end-1c", text)
|
|
377
|
+
|
|
378
|
+
def select_all(self) -> None:
|
|
379
|
+
"""Select all text content."""
|
|
380
|
+
tw = self._internal.core.text
|
|
381
|
+
tw.focus_set()
|
|
382
|
+
tw.tag_add("sel", "1.0", "end-1c")
|
|
383
|
+
tw.mark_set("insert", "end-1c")
|
|
384
|
+
|
|
385
|
+
def focus(self) -> None:
|
|
386
|
+
"""Move keyboard focus to the editor."""
|
|
387
|
+
self._internal.focus_set()
|
|
388
|
+
|
|
389
|
+
def goto(self, line: int, col: int = 1) -> None:
|
|
390
|
+
"""Move the cursor to a position and scroll it into view.
|
|
391
|
+
|
|
392
|
+
Both are 1-indexed — `(1, 1)` is the start of the content, the same
|
|
393
|
+
coordinates `cursor_position` reports and `insert()` accepts. Omit
|
|
394
|
+
`col` to land at the start of the line.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
line: 1-indexed line number.
|
|
398
|
+
col: 1-indexed column, or omit for the start of the line. Defaults
|
|
399
|
+
to 1.
|
|
400
|
+
"""
|
|
401
|
+
self._internal.focus_set()
|
|
402
|
+
self._internal.goto(line, col)
|
|
403
|
+
|
|
404
|
+
def undo(self) -> None:
|
|
405
|
+
"""Undo the last edit."""
|
|
406
|
+
self._internal.undo()
|
|
407
|
+
|
|
408
|
+
def redo(self) -> None:
|
|
409
|
+
"""Redo the last undone edit."""
|
|
410
|
+
self._internal.redo()
|
|
411
|
+
|
|
412
|
+
@contextmanager
|
|
413
|
+
def undo_block(self) -> Iterator[None]:
|
|
414
|
+
"""Context manager that groups edits into a single undo step.
|
|
415
|
+
|
|
416
|
+
Usage::
|
|
417
|
+
|
|
418
|
+
with editor.undo_block():
|
|
419
|
+
editor.insert("# header\\n", 1, 1)
|
|
420
|
+
editor.value = new_content
|
|
421
|
+
"""
|
|
422
|
+
self._internal.undo_block_start()
|
|
423
|
+
try:
|
|
424
|
+
yield
|
|
425
|
+
finally:
|
|
426
|
+
self._internal.undo_block_stop()
|
|
427
|
+
|
|
428
|
+
def mark_saved(self) -> None:
|
|
429
|
+
"""Mark the current state as the saved baseline (clears `is_dirty`)."""
|
|
430
|
+
self._internal.mark_saved()
|
|
431
|
+
|
|
432
|
+
def install(self, f: EditFilter) -> None:
|
|
433
|
+
"""Install a custom filter extension.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
f: The `EditFilter` to add.
|
|
437
|
+
"""
|
|
438
|
+
self._internal.install(f)
|
|
439
|
+
|
|
440
|
+
def uninstall(self, f: EditFilter) -> None:
|
|
441
|
+
"""Remove a previously installed filter extension.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
f: The `EditFilter` to remove.
|
|
445
|
+
"""
|
|
446
|
+
self._internal.uninstall(f)
|
|
447
|
+
|
|
448
|
+
def show_search(self) -> None:
|
|
449
|
+
"""Show the find bar and focus the search input."""
|
|
450
|
+
self._internal.show_search()
|
|
451
|
+
|
|
452
|
+
def show_replace(self) -> None:
|
|
453
|
+
"""Show the find/replace bar with the replace row visible."""
|
|
454
|
+
self._internal.show_replace()
|
|
455
|
+
|
|
456
|
+
def hide_search(self) -> None:
|
|
457
|
+
"""Hide the find/replace bar."""
|
|
458
|
+
self._internal.hide_search()
|
|
459
|
+
|
|
460
|
+
def indent(self) -> None:
|
|
461
|
+
"""Indent selected lines by one tab stop, or insert a tab stop at the cursor.
|
|
462
|
+
|
|
463
|
+
With a selection, every selected line is indented by one tab stop (spaces
|
|
464
|
+
or a tab character, depending on the editor's `insert_spaces` setting).
|
|
465
|
+
The affected lines stay selected afterward (a partial-line selection is
|
|
466
|
+
re-selected to span the whole lines).
|
|
467
|
+
|
|
468
|
+
Without a selection, inserts spaces to the next tab stop at the cursor
|
|
469
|
+
(same as pressing Tab with no selection).
|
|
470
|
+
"""
|
|
471
|
+
self._internal.indent()
|
|
472
|
+
|
|
473
|
+
def dedent(self) -> None:
|
|
474
|
+
"""Dedent selected lines (or the current line) by one tab stop.
|
|
475
|
+
|
|
476
|
+
Removes up to one tab stop of leading whitespace from each line in the
|
|
477
|
+
selection. With no selection, dedents the line the cursor is on. The
|
|
478
|
+
affected lines stay selected afterward (a partial-line selection is
|
|
479
|
+
re-selected to span the whole lines).
|
|
480
|
+
"""
|
|
481
|
+
self._internal.dedent()
|
|
482
|
+
|
|
483
|
+
# ----- Event shorthands -----
|
|
484
|
+
|
|
485
|
+
@overload
|
|
486
|
+
def on_change(self) -> Stream: ...
|
|
487
|
+
@overload
|
|
488
|
+
def on_change(self, handler: Callable[[ChangeEvent], Any]) -> Subscription: ...
|
|
489
|
+
def on_change(self, handler: Callable[[ChangeEvent], Any] | None = None) -> Stream | Subscription:
|
|
490
|
+
"""Register a callback fired on every edit.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
handler: Called with a :class:`~bootstack.events.ChangeEvent` whose
|
|
494
|
+
`value` is the editor text. Omit to get a composable
|
|
495
|
+
:class:`~bootstack.streams.Stream`.
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
499
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
500
|
+
"""
|
|
501
|
+
return self.on("change", handler)
|
|
502
|
+
|
|
503
|
+
@overload
|
|
504
|
+
def on_input(self) -> Stream: ...
|
|
505
|
+
@overload
|
|
506
|
+
def on_input(self, handler: Callable[[InputEvent], Any]) -> Subscription: ...
|
|
507
|
+
def on_input(self, handler: Callable[[InputEvent], Any] | None = None) -> Stream | Subscription:
|
|
508
|
+
"""Register a callback fired on every edit, before commit.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
handler: Called with an :class:`~bootstack.events.InputEvent` whose
|
|
512
|
+
`text` is the current content. Omit to get a composable
|
|
513
|
+
:class:`~bootstack.streams.Stream`.
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
517
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
518
|
+
"""
|
|
519
|
+
return self.on("input", handler)
|
|
520
|
+
|
|
521
|
+
@overload
|
|
522
|
+
def on_valid(self) -> Stream: ...
|
|
523
|
+
@overload
|
|
524
|
+
def on_valid(self, handler: Callable[[ValidationEvent], Any]) -> Subscription: ...
|
|
525
|
+
def on_valid(self, handler: Callable[[ValidationEvent], Any] | None = None) -> Stream | Subscription:
|
|
526
|
+
"""Register a callback fired when validation passes.
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
handler: Called with a :class:`~bootstack.events.ValidationEvent`. Omit to
|
|
530
|
+
get a composable :class:`~bootstack.streams.Stream` instead.
|
|
531
|
+
|
|
532
|
+
Returns:
|
|
533
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
534
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
535
|
+
"""
|
|
536
|
+
return self.on("valid", handler)
|
|
537
|
+
|
|
538
|
+
@overload
|
|
539
|
+
def on_invalid(self) -> Stream: ...
|
|
540
|
+
@overload
|
|
541
|
+
def on_invalid(self, handler: Callable[[ValidationEvent], Any]) -> Subscription: ...
|
|
542
|
+
def on_invalid(self, handler: Callable[[ValidationEvent], Any] | None = None) -> Stream | Subscription:
|
|
543
|
+
"""Register a callback fired when validation fails.
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
handler: Called with a :class:`~bootstack.events.ValidationEvent`. Omit to
|
|
547
|
+
get a composable :class:`~bootstack.streams.Stream` instead.
|
|
548
|
+
|
|
549
|
+
Returns:
|
|
550
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
551
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
552
|
+
"""
|
|
553
|
+
return self.on("invalid", handler)
|
|
554
|
+
|
|
555
|
+
@overload
|
|
556
|
+
def on_validate(self) -> Stream: ...
|
|
557
|
+
@overload
|
|
558
|
+
def on_validate(self, handler: Callable[[ValidationEvent], Any]) -> Subscription: ...
|
|
559
|
+
def on_validate(self, handler: Callable[[ValidationEvent], Any] | None = None) -> Stream | Subscription:
|
|
560
|
+
"""Register a callback fired after any validation run.
|
|
561
|
+
|
|
562
|
+
Args:
|
|
563
|
+
handler: Called with a :class:`~bootstack.events.ValidationEvent`. Omit to
|
|
564
|
+
get a composable :class:`~bootstack.streams.Stream` instead.
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
568
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
569
|
+
"""
|
|
570
|
+
return self.on("validate", handler)
|
|
571
|
+
|
|
572
|
+
@overload
|
|
573
|
+
def on_modified(self) -> Stream: ...
|
|
574
|
+
@overload
|
|
575
|
+
def on_modified(self, handler: Callable[[TextModifiedEvent], Any]) -> Subscription: ...
|
|
576
|
+
def on_modified(self, handler: Callable[[TextModifiedEvent], Any] | None = None) -> Stream | Subscription:
|
|
577
|
+
"""Register a callback fired when `is_dirty` changes.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
handler: Called with a :class:`~bootstack.events.TextModifiedEvent`. Omit to
|
|
581
|
+
get a composable :class:`~bootstack.streams.Stream` instead.
|
|
582
|
+
|
|
583
|
+
Returns:
|
|
584
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
585
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
586
|
+
"""
|
|
587
|
+
return self.on("modified", handler)
|
|
588
|
+
|
|
589
|
+
@overload
|
|
590
|
+
def on_undo(self) -> Stream: ...
|
|
591
|
+
@overload
|
|
592
|
+
def on_undo(self, handler: Callable[[InputEvent], Any]) -> Subscription: ...
|
|
593
|
+
def on_undo(self, handler: Callable[[InputEvent], Any] | None = None) -> Stream | Subscription:
|
|
594
|
+
"""Register a callback fired after an undo operation.
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
handler: Called with an :class:`~bootstack.events.InputEvent`. Omit to
|
|
598
|
+
get a composable :class:`~bootstack.streams.Stream` instead.
|
|
599
|
+
|
|
600
|
+
Returns:
|
|
601
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
602
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
603
|
+
"""
|
|
604
|
+
return self.on("undo", handler)
|
|
605
|
+
|
|
606
|
+
@overload
|
|
607
|
+
def on_redo(self) -> Stream: ...
|
|
608
|
+
@overload
|
|
609
|
+
def on_redo(self, handler: Callable[[InputEvent], Any]) -> Subscription: ...
|
|
610
|
+
def on_redo(self, handler: Callable[[InputEvent], Any] | None = None) -> Stream | Subscription:
|
|
611
|
+
"""Register a callback fired after a redo operation.
|
|
612
|
+
|
|
613
|
+
Args:
|
|
614
|
+
handler: Called with an :class:`~bootstack.events.InputEvent`. Omit to
|
|
615
|
+
get a composable :class:`~bootstack.streams.Stream` instead.
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
619
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
620
|
+
"""
|
|
621
|
+
return self.on("redo", handler)
|
|
622
|
+
|
|
623
|
+
@overload
|
|
624
|
+
def on_cursor_move(self) -> Stream: ...
|
|
625
|
+
@overload
|
|
626
|
+
def on_cursor_move(self, handler: Callable[[Event], Any]) -> Subscription: ...
|
|
627
|
+
def on_cursor_move(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
|
|
628
|
+
"""Register a callback fired when the cursor position changes.
|
|
629
|
+
|
|
630
|
+
Args:
|
|
631
|
+
handler: Called with an :class:`~bootstack.events.Event`. Omit to
|
|
632
|
+
get a composable :class:`~bootstack.streams.Stream` instead.
|
|
633
|
+
|
|
634
|
+
Returns:
|
|
635
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
636
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
637
|
+
"""
|
|
638
|
+
return self.on("cursor_move", handler)
|
|
639
|
+
|
|
640
|
+
@overload
|
|
641
|
+
def on_focus(self) -> Stream: ...
|
|
642
|
+
@overload
|
|
643
|
+
def on_focus(self, handler: Callable[[Event], Any]) -> Subscription: ...
|
|
644
|
+
def on_focus(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
|
|
645
|
+
"""Register a callback fired when the editor gains focus.
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
handler: Called with an :class:`~bootstack.events.Event`. Omit to
|
|
649
|
+
get a composable :class:`~bootstack.streams.Stream` instead.
|
|
650
|
+
|
|
651
|
+
Returns:
|
|
652
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
653
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
654
|
+
"""
|
|
655
|
+
return self.on("focus", handler)
|
|
656
|
+
|
|
657
|
+
@overload
|
|
658
|
+
def on_blur(self) -> Stream: ...
|
|
659
|
+
@overload
|
|
660
|
+
def on_blur(self, handler: Callable[[Event], Any]) -> Subscription: ...
|
|
661
|
+
def on_blur(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
|
|
662
|
+
"""Register a callback fired when the editor loses focus.
|
|
663
|
+
|
|
664
|
+
Args:
|
|
665
|
+
handler: Called with an :class:`~bootstack.events.Event`. Omit to
|
|
666
|
+
get a composable :class:`~bootstack.streams.Stream` instead.
|
|
667
|
+
|
|
668
|
+
Returns:
|
|
669
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
670
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
671
|
+
"""
|
|
672
|
+
return self.on("blur", handler)
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
register_widget_events(CodeEditor, _CODEEDITOR_EVENTS)
|