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.
Files changed (471) hide show
  1. bootstack/__init__.py +157 -0
  2. bootstack/__main__.py +5 -0
  3. bootstack/_core/__init__.py +21 -0
  4. bootstack/_core/capabilities/__init__.py +45 -0
  5. bootstack/_core/capabilities/after.py +103 -0
  6. bootstack/_core/capabilities/bind.py +192 -0
  7. bootstack/_core/capabilities/bindtags.py +112 -0
  8. bootstack/_core/capabilities/busy.py +72 -0
  9. bootstack/_core/capabilities/clipboard.py +89 -0
  10. bootstack/_core/capabilities/focus.py +118 -0
  11. bootstack/_core/capabilities/grab.py +65 -0
  12. bootstack/_core/capabilities/grid.py +211 -0
  13. bootstack/_core/capabilities/localization.py +231 -0
  14. bootstack/_core/capabilities/pack.py +140 -0
  15. bootstack/_core/capabilities/place.py +113 -0
  16. bootstack/_core/capabilities/selection.py +136 -0
  17. bootstack/_core/capabilities/signals.py +244 -0
  18. bootstack/_core/capabilities/winfo.py +315 -0
  19. bootstack/_core/colorutils.py +234 -0
  20. bootstack/_core/exceptions.py +25 -0
  21. bootstack/_core/images.py +463 -0
  22. bootstack/_core/mixins/__init__.py +1 -0
  23. bootstack/_core/mixins/ttk_state.py +35 -0
  24. bootstack/_core/mixins/widget.py +132 -0
  25. bootstack/_core/paths.py +49 -0
  26. bootstack/_core/publisher.py +149 -0
  27. bootstack/_core/variables.py +62 -0
  28. bootstack/_runtime/__init__.py +3 -0
  29. bootstack/_runtime/app.py +930 -0
  30. bootstack/_runtime/base_window.py +945 -0
  31. bootstack/_runtime/events.py +399 -0
  32. bootstack/_runtime/shortcuts.py +496 -0
  33. bootstack/_runtime/tk_patch.py +43 -0
  34. bootstack/_runtime/toplevel.py +276 -0
  35. bootstack/_runtime/utility.py +457 -0
  36. bootstack/_runtime/visual_focus.py +240 -0
  37. bootstack/_runtime/window_utilities.py +1123 -0
  38. bootstack/assets/__init__.py +21 -0
  39. bootstack/assets/bootstack.ico +0 -0
  40. bootstack/assets/bootstack.png +0 -0
  41. bootstack/assets/elements/__init__.py +0 -0
  42. bootstack/assets/elements/badge-pill.png +0 -0
  43. bootstack/assets/elements/badge-square.png +0 -0
  44. bootstack/assets/elements/button-compact.png +0 -0
  45. bootstack/assets/elements/button-default.png +0 -0
  46. bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
  47. bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
  48. bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
  49. bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
  50. bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
  51. bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
  52. bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
  53. bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
  54. bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
  55. bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
  56. bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
  57. bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
  58. bootstack/assets/elements/card.png +0 -0
  59. bootstack/assets/elements/checkbox-checked.png +0 -0
  60. bootstack/assets/elements/checkbox-indeterminate.png +0 -0
  61. bootstack/assets/elements/checkbox-unchecked.png +0 -0
  62. bootstack/assets/elements/field.png +0 -0
  63. bootstack/assets/elements/input-addon-compact.png +0 -0
  64. bootstack/assets/elements/input-addon-default.png +0 -0
  65. bootstack/assets/elements/input-compact.png +0 -0
  66. bootstack/assets/elements/input-default.png +0 -0
  67. bootstack/assets/elements/list-item-separated.png +0 -0
  68. bootstack/assets/elements/list-item.png +0 -0
  69. bootstack/assets/elements/listrow-compact.png +0 -0
  70. bootstack/assets/elements/listrow-default.png +0 -0
  71. bootstack/assets/elements/manifest.toml +361 -0
  72. bootstack/assets/elements/menu-item.png +0 -0
  73. bootstack/assets/elements/navitem-compact.png +0 -0
  74. bootstack/assets/elements/navitem-default.png +0 -0
  75. bootstack/assets/elements/progressbar-h-compact.png +0 -0
  76. bootstack/assets/elements/progressbar-h-default.png +0 -0
  77. bootstack/assets/elements/progressbar-v-compact.png +0 -0
  78. bootstack/assets/elements/progressbar-v-default.png +0 -0
  79. bootstack/assets/elements/radiobutton.png +0 -0
  80. bootstack/assets/elements/scrollbar-horizontal.png +0 -0
  81. bootstack/assets/elements/scrollbar-vertical.png +0 -0
  82. bootstack/assets/elements/slider-handle.png +0 -0
  83. bootstack/assets/elements/slider-track-h.png +0 -0
  84. bootstack/assets/elements/slider-track-v.png +0 -0
  85. bootstack/assets/elements/switch-off.png +0 -0
  86. bootstack/assets/elements/switch-on.png +0 -0
  87. bootstack/assets/elements/tab-h.png +0 -0
  88. bootstack/assets/elements/tab-v.png +0 -0
  89. bootstack/assets/icons/bootstrap.ttf +0 -0
  90. bootstack/assets/icons/glyphmap.json +2080 -0
  91. bootstack/assets/icons/icon_metrics.json +12470 -0
  92. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
  93. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +856 -0
  94. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
  95. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +878 -0
  96. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
  97. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +856 -0
  98. bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
  99. bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +856 -0
  100. bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
  101. bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +856 -0
  102. bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
  103. bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +878 -0
  104. bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
  105. bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +856 -0
  106. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
  107. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +856 -0
  108. bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
  109. bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +854 -0
  110. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
  111. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +845 -0
  112. bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
  113. bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +844 -0
  114. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
  115. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +917 -0
  116. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
  117. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +845 -0
  118. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
  119. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +844 -0
  120. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
  121. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +844 -0
  122. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
  123. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +845 -0
  124. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
  125. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +845 -0
  126. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
  127. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +845 -0
  128. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
  129. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +845 -0
  130. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
  131. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +845 -0
  132. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
  133. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +845 -0
  134. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
  135. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +845 -0
  136. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
  137. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +845 -0
  138. bootstack/cli/__init__.py +133 -0
  139. bootstack/cli/__main__.py +6 -0
  140. bootstack/cli/add.py +395 -0
  141. bootstack/cli/appicon.py +285 -0
  142. bootstack/cli/build.py +115 -0
  143. bootstack/cli/config.py +313 -0
  144. bootstack/cli/demo.py +564 -0
  145. bootstack/cli/dev.py +153 -0
  146. bootstack/cli/doctor.py +195 -0
  147. bootstack/cli/icons.py +98 -0
  148. bootstack/cli/promote.py +120 -0
  149. bootstack/cli/pyinstaller.py +268 -0
  150. bootstack/cli/run.py +95 -0
  151. bootstack/cli/start.py +117 -0
  152. bootstack/cli/templates/__init__.py +931 -0
  153. bootstack/clipboard.py +48 -0
  154. bootstack/constants.py +318 -0
  155. bootstack/data/README.md +615 -0
  156. bootstack/data/__init__.py +78 -0
  157. bootstack/data/_observable.py +276 -0
  158. bootstack/data/base.py +780 -0
  159. bootstack/data/file_source.py +367 -0
  160. bootstack/data/memory_source.py +450 -0
  161. bootstack/data/query.py +367 -0
  162. bootstack/data/readers.py +289 -0
  163. bootstack/data/sqlite_source.py +869 -0
  164. bootstack/data/types.py +354 -0
  165. bootstack/data/writers.py +232 -0
  166. bootstack/dev/__init__.py +36 -0
  167. bootstack/dev/_capture.py +141 -0
  168. bootstack/dev/_env.py +43 -0
  169. bootstack/dev/_registry.py +140 -0
  170. bootstack/dev/_reloader.py +351 -0
  171. bootstack/dev/_reset.py +50 -0
  172. bootstack/dev/_watcher.py +91 -0
  173. bootstack/dialogs/__init__.py +948 -0
  174. bootstack/dialogs/_impl/__init__.py +47 -0
  175. bootstack/dialogs/_impl/colorchooser.py +588 -0
  176. bootstack/dialogs/_impl/datedialog.py +450 -0
  177. bootstack/dialogs/_impl/dialog.py +594 -0
  178. bootstack/dialogs/_impl/filterdialog.py +358 -0
  179. bootstack/dialogs/_impl/fontdialog.py +364 -0
  180. bootstack/dialogs/_impl/formdialog.py +564 -0
  181. bootstack/dialogs/_impl/message.py +486 -0
  182. bootstack/dialogs/_impl/query.py +570 -0
  183. bootstack/errors.py +67 -0
  184. bootstack/events/__init__.py +111 -0
  185. bootstack/events/_event.py +135 -0
  186. bootstack/events/_payloads.py +539 -0
  187. bootstack/events/_subscription.py +38 -0
  188. bootstack/i18n/README.md +77 -0
  189. bootstack/i18n/__init__.py +23 -0
  190. bootstack/i18n/catalog.py +121 -0
  191. bootstack/i18n/intl_format.py +584 -0
  192. bootstack/i18n/msgcat.py +425 -0
  193. bootstack/i18n/specs.py +156 -0
  194. bootstack/images.py +563 -0
  195. bootstack/py.typed +1 -0
  196. bootstack/scheduling/__init__.py +11 -0
  197. bootstack/scheduling/_schedule.py +218 -0
  198. bootstack/shortcuts.py +21 -0
  199. bootstack/signals/README.md +98 -0
  200. bootstack/signals/__init__.py +10 -0
  201. bootstack/signals/integration.py +100 -0
  202. bootstack/signals/signal.py +353 -0
  203. bootstack/signals/types.py +6 -0
  204. bootstack/store.py +286 -0
  205. bootstack/streams/__init__.py +12 -0
  206. bootstack/streams/_stream.py +321 -0
  207. bootstack/style/__init__.py +38 -0
  208. bootstack/style/builders/__init__.py +51 -0
  209. bootstack/style/builders/badge.py +46 -0
  210. bootstack/style/builders/button.py +339 -0
  211. bootstack/style/builders/buttongroup.py +311 -0
  212. bootstack/style/builders/calendar.py +271 -0
  213. bootstack/style/builders/checkbutton.py +110 -0
  214. bootstack/style/builders/combobox.py +113 -0
  215. bootstack/style/builders/contextmenu.py +268 -0
  216. bootstack/style/builders/entry.py +82 -0
  217. bootstack/style/builders/expander.py +148 -0
  218. bootstack/style/builders/field.py +335 -0
  219. bootstack/style/builders/frame.py +50 -0
  220. bootstack/style/builders/label.py +28 -0
  221. bootstack/style/builders/labelframe.py +34 -0
  222. bootstack/style/builders/listview.py +369 -0
  223. bootstack/style/builders/menubar.py +91 -0
  224. bootstack/style/builders/menubutton.py +359 -0
  225. bootstack/style/builders/panedwindow.py +25 -0
  226. bootstack/style/builders/progressbar.py +67 -0
  227. bootstack/style/builders/radiobutton.py +99 -0
  228. bootstack/style/builders/scale.py +64 -0
  229. bootstack/style/builders/scrollbar.py +225 -0
  230. bootstack/style/builders/separator.py +49 -0
  231. bootstack/style/builders/sidenav.py +643 -0
  232. bootstack/style/builders/sizegrip.py +15 -0
  233. bootstack/style/builders/spinbox.py +119 -0
  234. bootstack/style/builders/switch.py +70 -0
  235. bootstack/style/builders/tabitem.py +204 -0
  236. bootstack/style/builders/togglegroup.py +294 -0
  237. bootstack/style/builders/toolbutton.py +275 -0
  238. bootstack/style/builders/tooltip.py +26 -0
  239. bootstack/style/builders/treeview.py +193 -0
  240. bootstack/style/builders/utils.py +455 -0
  241. bootstack/style/builders_tk/__init__.py +16 -0
  242. bootstack/style/builders_tk/defaults.py +229 -0
  243. bootstack/style/element.py +173 -0
  244. bootstack/style/fonts.py +123 -0
  245. bootstack/style/style.py +609 -0
  246. bootstack/style/style_builder_base.py +716 -0
  247. bootstack/style/style_builder_mixed.py +93 -0
  248. bootstack/style/style_builder_tk.py +109 -0
  249. bootstack/style/style_builder_ttk.py +353 -0
  250. bootstack/style/style_resolver.py +447 -0
  251. bootstack/style/theme.py +245 -0
  252. bootstack/style/theme_provider.py +471 -0
  253. bootstack/style/themes/__init__.py +128 -0
  254. bootstack/style/tk_patch.py +5 -0
  255. bootstack/style/token_maps.py +41 -0
  256. bootstack/style/types.py +32 -0
  257. bootstack/style/typography.py +523 -0
  258. bootstack/style/utility.py +746 -0
  259. bootstack/types.py +39 -0
  260. bootstack/validation/__init__.py +6 -0
  261. bootstack/validation/types.py +15 -0
  262. bootstack/validation/validation_result.py +17 -0
  263. bootstack/validation/validation_rules.py +205 -0
  264. bootstack/widgets/__init__.py +74 -0
  265. bootstack/widgets/_core/__init__.py +31 -0
  266. bootstack/widgets/_core/app_config.py +266 -0
  267. bootstack/widgets/_core/base.py +461 -0
  268. bootstack/widgets/_core/container.py +334 -0
  269. bootstack/widgets/_core/context.py +35 -0
  270. bootstack/widgets/_core/events.py +130 -0
  271. bootstack/widgets/_core/field_mixin.py +353 -0
  272. bootstack/widgets/_core/icon_image_props.py +72 -0
  273. bootstack/widgets/_core/image_binding.py +93 -0
  274. bootstack/widgets/_core/navmodel.py +345 -0
  275. bootstack/widgets/_core/options.py +210 -0
  276. bootstack/widgets/_core/selection_group.py +130 -0
  277. bootstack/widgets/_core/window_controls.py +96 -0
  278. bootstack/widgets/_core/window_menu.py +246 -0
  279. bootstack/widgets/_impl/__init__.py +1 -0
  280. bootstack/widgets/_impl/_internal/__init__.py +0 -0
  281. bootstack/widgets/_impl/_internal/wrapper_base.py +307 -0
  282. bootstack/widgets/_impl/_parts/__init__.py +11 -0
  283. bootstack/widgets/_impl/_parts/numberentry_part.py +385 -0
  284. bootstack/widgets/_impl/_parts/spinnerentry_part.py +434 -0
  285. bootstack/widgets/_impl/_parts/textentry_part.py +406 -0
  286. bootstack/widgets/_impl/composites/__init__.py +33 -0
  287. bootstack/widgets/_impl/composites/_dateutils.py +33 -0
  288. bootstack/widgets/_impl/composites/_image_fit.py +105 -0
  289. bootstack/widgets/_impl/composites/accordion.py +381 -0
  290. bootstack/widgets/_impl/composites/avatar.py +191 -0
  291. bootstack/widgets/_impl/composites/buttongroup.py +371 -0
  292. bootstack/widgets/_impl/composites/calendar.py +972 -0
  293. bootstack/widgets/_impl/composites/carousel.py +567 -0
  294. bootstack/widgets/_impl/composites/chart.py +882 -0
  295. bootstack/widgets/_impl/composites/compositeframe.py +298 -0
  296. bootstack/widgets/_impl/composites/contextmenu.py +1951 -0
  297. bootstack/widgets/_impl/composites/dateentry.py +404 -0
  298. bootstack/widgets/_impl/composites/dropdownbutton.py +325 -0
  299. bootstack/widgets/_impl/composites/expander.py +515 -0
  300. bootstack/widgets/_impl/composites/field.py +670 -0
  301. bootstack/widgets/_impl/composites/form.py +1066 -0
  302. bootstack/widgets/_impl/composites/gallery.py +551 -0
  303. bootstack/widgets/_impl/composites/list/__init__.py +15 -0
  304. bootstack/widgets/_impl/composites/list/listitem.py +802 -0
  305. bootstack/widgets/_impl/composites/list/listview.py +1433 -0
  306. bootstack/widgets/_impl/composites/menu/__init__.py +18 -0
  307. bootstack/widgets/_impl/composites/menu/model.py +358 -0
  308. bootstack/widgets/_impl/composites/menu/render_native.py +134 -0
  309. bootstack/widgets/_impl/composites/menu/render_themed.py +134 -0
  310. bootstack/widgets/_impl/composites/meter.py +860 -0
  311. bootstack/widgets/_impl/composites/numericentry.py +201 -0
  312. bootstack/widgets/_impl/composites/pagestack.py +395 -0
  313. bootstack/widgets/_impl/composites/passwordentry.py +142 -0
  314. bootstack/widgets/_impl/composites/pathentry.py +168 -0
  315. bootstack/widgets/_impl/composites/picture.py +289 -0
  316. bootstack/widgets/_impl/composites/radiogroup.py +511 -0
  317. bootstack/widgets/_impl/composites/scrolledtext.py +375 -0
  318. bootstack/widgets/_impl/composites/scrolledtext.pyi +186 -0
  319. bootstack/widgets/_impl/composites/scrollview.py +764 -0
  320. bootstack/widgets/_impl/composites/selectbox.py +1026 -0
  321. bootstack/widgets/_impl/composites/shell/__init__.py +40 -0
  322. bootstack/widgets/_impl/composites/shell/content_host.py +52 -0
  323. bootstack/widgets/_impl/composites/shell/layout.py +335 -0
  324. bootstack/widgets/_impl/composites/shell/nav_panel.py +345 -0
  325. bootstack/widgets/_impl/composites/shell/providers.py +558 -0
  326. bootstack/widgets/_impl/composites/shell/rail.py +117 -0
  327. bootstack/widgets/_impl/composites/shell/shell.py +581 -0
  328. bootstack/widgets/_impl/composites/shell/workspace.py +273 -0
  329. bootstack/widgets/_impl/composites/sidenav/__init__.py +16 -0
  330. bootstack/widgets/_impl/composites/sidenav/header.py +81 -0
  331. bootstack/widgets/_impl/composites/sidenav/separator.py +44 -0
  332. bootstack/widgets/_impl/composites/slider/__init__.py +7 -0
  333. bootstack/widgets/_impl/composites/slider/_shared.py +195 -0
  334. bootstack/widgets/_impl/composites/slider/rangeslider.py +982 -0
  335. bootstack/widgets/_impl/composites/slider/slider.py +851 -0
  336. bootstack/widgets/_impl/composites/spinnerentry.py +185 -0
  337. bootstack/widgets/_impl/composites/tableview/__init__.py +5 -0
  338. bootstack/widgets/_impl/composites/tableview/tableview.py +3388 -0
  339. bootstack/widgets/_impl/composites/tableview/types.py +169 -0
  340. bootstack/widgets/_impl/composites/tabs/__init__.py +23 -0
  341. bootstack/widgets/_impl/composites/tabs/tabitem.py +389 -0
  342. bootstack/widgets/_impl/composites/tabs/tabs.py +974 -0
  343. bootstack/widgets/_impl/composites/tabs/tabview.py +650 -0
  344. bootstack/widgets/_impl/composites/textarea/__init__.py +26 -0
  345. bootstack/widgets/_impl/composites/textarea/change.py +46 -0
  346. bootstack/widgets/_impl/composites/textarea/codeeditor.py +526 -0
  347. bootstack/widgets/_impl/composites/textarea/core.py +495 -0
  348. bootstack/widgets/_impl/composites/textarea/decoration.py +42 -0
  349. bootstack/widgets/_impl/composites/textarea/diff.py +127 -0
  350. bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +1 -0
  351. bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +138 -0
  352. bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +158 -0
  353. bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +138 -0
  354. bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +312 -0
  355. bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +177 -0
  356. bootstack/widgets/_impl/composites/textarea/filter.py +171 -0
  357. bootstack/widgets/_impl/composites/textarea/search_overlay.py +459 -0
  358. bootstack/widgets/_impl/composites/textarea/sidebar.py +88 -0
  359. bootstack/widgets/_impl/composites/textarea/style_registry.py +178 -0
  360. bootstack/widgets/_impl/composites/textarea/textarea.py +606 -0
  361. bootstack/widgets/_impl/composites/textarea/undo.py +217 -0
  362. bootstack/widgets/_impl/composites/textentry.py +57 -0
  363. bootstack/widgets/_impl/composites/timeentry.py +176 -0
  364. bootstack/widgets/_impl/composites/toast.py +390 -0
  365. bootstack/widgets/_impl/composites/toast_stack.py +156 -0
  366. bootstack/widgets/_impl/composites/togglegroup.py +418 -0
  367. bootstack/widgets/_impl/composites/toolbar.py +608 -0
  368. bootstack/widgets/_impl/composites/tooltip.py +491 -0
  369. bootstack/widgets/_impl/composites/tree/__init__.py +7 -0
  370. bootstack/widgets/_impl/composites/tree/source_binding.py +136 -0
  371. bootstack/widgets/_impl/composites/tree/treeitem.py +393 -0
  372. bootstack/widgets/_impl/composites/tree/treenode.py +174 -0
  373. bootstack/widgets/_impl/composites/tree/treeview.py +841 -0
  374. bootstack/widgets/_impl/mixins/__init__.py +24 -0
  375. bootstack/widgets/_impl/mixins/configure_mixin.py +216 -0
  376. bootstack/widgets/_impl/mixins/entry_mixin.py +134 -0
  377. bootstack/widgets/_impl/mixins/font_mixin.py +368 -0
  378. bootstack/widgets/_impl/mixins/icon_mixin.py +61 -0
  379. bootstack/widgets/_impl/mixins/localization_mixin.py +253 -0
  380. bootstack/widgets/_impl/mixins/signal_mixin.py +268 -0
  381. bootstack/widgets/_impl/mixins/validation_mixin.py +226 -0
  382. bootstack/widgets/_impl/primitives/__init__.py +49 -0
  383. bootstack/widgets/_impl/primitives/_menubutton.py +107 -0
  384. bootstack/widgets/_impl/primitives/badge.py +45 -0
  385. bootstack/widgets/_impl/primitives/button.py +76 -0
  386. bootstack/widgets/_impl/primitives/card.py +45 -0
  387. bootstack/widgets/_impl/primitives/checkbutton.py +124 -0
  388. bootstack/widgets/_impl/primitives/checktoggle.py +62 -0
  389. bootstack/widgets/_impl/primitives/combobox.py +156 -0
  390. bootstack/widgets/_impl/primitives/entry.py +87 -0
  391. bootstack/widgets/_impl/primitives/flexframe.py +448 -0
  392. bootstack/widgets/_impl/primitives/frame.py +185 -0
  393. bootstack/widgets/_impl/primitives/gridframe.py +546 -0
  394. bootstack/widgets/_impl/primitives/label.py +84 -0
  395. bootstack/widgets/_impl/primitives/labelframe.py +54 -0
  396. bootstack/widgets/_impl/primitives/optionmenu.py +387 -0
  397. bootstack/widgets/_impl/primitives/packframe.py +227 -0
  398. bootstack/widgets/_impl/primitives/panedwindow.py +45 -0
  399. bootstack/widgets/_impl/primitives/progressbar.py +83 -0
  400. bootstack/widgets/_impl/primitives/radiobutton.py +115 -0
  401. bootstack/widgets/_impl/primitives/radiotoggle.py +54 -0
  402. bootstack/widgets/_impl/primitives/scrollbar.py +42 -0
  403. bootstack/widgets/_impl/primitives/separator.py +43 -0
  404. bootstack/widgets/_impl/primitives/sizegrip.py +33 -0
  405. bootstack/widgets/_impl/primitives/spinbox.py +95 -0
  406. bootstack/widgets/_impl/primitives/switch.py +44 -0
  407. bootstack/widgets/_impl/primitives/treeview.py +69 -0
  408. bootstack/widgets/app.py +371 -0
  409. bootstack/widgets/appshell.py +1179 -0
  410. bootstack/widgets/avatar.py +140 -0
  411. bootstack/widgets/boolean_controls.py +455 -0
  412. bootstack/widgets/button.py +224 -0
  413. bootstack/widgets/buttongroup.py +239 -0
  414. bootstack/widgets/calendar.py +195 -0
  415. bootstack/widgets/card.py +159 -0
  416. bootstack/widgets/carousel.py +241 -0
  417. bootstack/widgets/chart.py +302 -0
  418. bootstack/widgets/codeeditor.py +675 -0
  419. bootstack/widgets/contextmenu.py +371 -0
  420. bootstack/widgets/datatable.py +688 -0
  421. bootstack/widgets/datefield.py +395 -0
  422. bootstack/widgets/divider.py +60 -0
  423. bootstack/widgets/expander.py +579 -0
  424. bootstack/widgets/form.py +200 -0
  425. bootstack/widgets/gallery.py +250 -0
  426. bootstack/widgets/gauge.py +168 -0
  427. bootstack/widgets/grid.py +121 -0
  428. bootstack/widgets/groupbox.py +162 -0
  429. bootstack/widgets/label.py +229 -0
  430. bootstack/widgets/listview.py +354 -0
  431. bootstack/widgets/menubutton.py +419 -0
  432. bootstack/widgets/numberfield.py +431 -0
  433. bootstack/widgets/pagestack.py +345 -0
  434. bootstack/widgets/passwordfield.py +383 -0
  435. bootstack/widgets/pathfield.py +454 -0
  436. bootstack/widgets/picture.py +251 -0
  437. bootstack/widgets/progressbar.py +106 -0
  438. bootstack/widgets/radio_variants.py +271 -0
  439. bootstack/widgets/radiogroup.py +228 -0
  440. bootstack/widgets/scrollbar.py +64 -0
  441. bootstack/widgets/scrollview.py +186 -0
  442. bootstack/widgets/select.py +302 -0
  443. bootstack/widgets/selectbutton.py +182 -0
  444. bootstack/widgets/sidebar_toggle.py +130 -0
  445. bootstack/widgets/sizegrip.py +42 -0
  446. bootstack/widgets/slider.py +408 -0
  447. bootstack/widgets/spinbox.py +147 -0
  448. bootstack/widgets/spinnerfield.py +413 -0
  449. bootstack/widgets/splash.py +367 -0
  450. bootstack/widgets/splitview.py +482 -0
  451. bootstack/widgets/stacks.py +249 -0
  452. bootstack/widgets/statusbar.py +189 -0
  453. bootstack/widgets/tabs.py +425 -0
  454. bootstack/widgets/textarea.py +459 -0
  455. bootstack/widgets/textfield.py +410 -0
  456. bootstack/widgets/theme_toggle.py +92 -0
  457. bootstack/widgets/timefield.py +340 -0
  458. bootstack/widgets/toast.py +314 -0
  459. bootstack/widgets/togglegroup.py +231 -0
  460. bootstack/widgets/toolbar.py +347 -0
  461. bootstack/widgets/tooltip.py +94 -0
  462. bootstack/widgets/tree.py +648 -0
  463. bootstack/widgets/types.py +305 -0
  464. bootstack/widgets/window.py +307 -0
  465. bootstack-0.1.0.dist-info/METADATA +301 -0
  466. bootstack-0.1.0.dist-info/RECORD +471 -0
  467. bootstack-0.1.0.dist-info/WHEEL +5 -0
  468. bootstack-0.1.0.dist-info/entry_points.txt +2 -0
  469. bootstack-0.1.0.dist-info/licenses/LICENSE +21 -0
  470. bootstack-0.1.0.dist-info/licenses/NOTICE +10 -0
  471. 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)