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,459 @@
1
+ """SearchOverlay — find/replace bar for CodeEditor.
2
+
3
+ Docks at the bottom of the editor as a z-order overlay (always place()'d,
4
+ shown by lift(), hidden by lowering _core back on top). Opened with
5
+ Ctrl+F (find) or Ctrl+H (find + replace). Closed with Esc.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ import bisect
11
+ from typing import TYPE_CHECKING
12
+
13
+ from bootstack.widgets._impl.primitives.label import Label
14
+ from bootstack.widgets._impl.primitives.button import Button
15
+ from bootstack.widgets._impl.primitives.separator import Separator
16
+ from bootstack.widgets._impl.primitives.checktoggle import CheckToggle
17
+ from bootstack.widgets._impl.primitives.packframe import PackFrame
18
+ from bootstack.widgets._impl.composites.textentry import TextEntry
19
+ from bootstack.widgets._impl.composites.textarea.decoration import Position, RangeDecoration
20
+ from bootstack.signals.signal import Signal
21
+
22
+ if TYPE_CHECKING:
23
+ from bootstack.widgets._impl.composites.textarea.core import _MultilineCore
24
+ from bootstack.widgets.types import Master
25
+
26
+
27
+ _LAYER = "search"
28
+ _STYLE_MATCH = "search_match"
29
+ _STYLE_CURRENT = "search_current"
30
+ _DEBOUNCE_MS = 120
31
+
32
+
33
+ class SearchOverlay(PackFrame):
34
+ """Find/replace bar that overlays the bottom of the editor.
35
+
36
+ Created and owned by `CodeEditor` — not part of the public API.
37
+ Use `CodeEditor.show_search()` / `CodeEditor.show_replace()` /
38
+ `CodeEditor.hide_search()`.
39
+ """
40
+
41
+ def __init__(self, parent: Master, core: _MultilineCore) -> None:
42
+ super().__init__(parent, surface="chrome", padding=(8, 4), direction="vertical")
43
+ self._core = core
44
+ self._matches: list[tuple[int, int]] = []
45
+ self._current: int = -1
46
+ self._after_id: str | None = None
47
+ self._case_sig = Signal(False)
48
+ self._regex_sig = Signal(False)
49
+ self._count_sig = Signal("")
50
+ self._syncing_colors = False
51
+ self._visible = False
52
+ self._replace_visible = False
53
+
54
+ # ── decoration styles ─────────────────────────────────────────────
55
+ core.register_layer(_LAYER, priority=10)
56
+ core.define_style(_STYLE_MATCH, background="#ffff60", foreground="#000000")
57
+ core.define_style(_STYLE_CURRENT, background="#ff8000", foreground="#000000")
58
+
59
+ # ── find row ──────────────────────────────────────────────────────
60
+ _fr = PackFrame(self, direction="horizontal").pack(fill="x")
61
+
62
+ self._close_btn = Button(
63
+ _fr, icon="x", icon_only=True, variant="ghost",
64
+ density="compact", command=self.hide,
65
+ )
66
+ self._close_btn.pack()
67
+
68
+ self._find_entry = TextEntry(_fr, density="compact")
69
+ self._find_entry.insert_addon(Label, position="before", icon="search", icon_only=True)
70
+ self._find_entry.on_changed(self._on_query_changed)
71
+ self._find_entry.pack(fill="x", expand=True)
72
+
73
+ Separator(_fr, orient="vertical").pack(fill="y", padx=16)
74
+
75
+ self._case_toggle = CheckToggle(
76
+ _fr, icon="type", icon_only=True, signal=self._case_sig,
77
+ onvalue=True, offvalue=False,
78
+ command=self._on_option_changed, density="compact",
79
+ )
80
+ self._case_toggle.pack(padx=(0, 4))
81
+
82
+ self._regex_toggle = CheckToggle(
83
+ _fr, icon="regex", icon_only=True, signal=self._regex_sig,
84
+ onvalue=True, offvalue=False,
85
+ command=self._on_option_changed, density="compact",
86
+ )
87
+ self._regex_toggle.pack()
88
+
89
+ _mc = PackFrame(_fr, direction="horizontal").pack(padx=(16, 0))
90
+ Separator(_mc, orient="vertical").pack(fill="y")
91
+
92
+ self._count_lbl = Label(_mc, textsignal=self._count_sig)
93
+ self._count_lbl.pack(padx=(16, 0))
94
+
95
+ self._prev_btn = Button(
96
+ _mc, icon="chevron-up", icon_only=True, variant="ghost",
97
+ density="compact", command=self._prev_match,
98
+ )
99
+ self._prev_btn.pack()
100
+
101
+ self._next_btn = Button(
102
+ _mc, icon="chevron-down", icon_only=True, variant="ghost",
103
+ density="compact", command=self._next_match,
104
+ )
105
+ self._next_btn.pack()
106
+
107
+ # ── replace row (hidden by default) ───────────────────────────────
108
+ self._replace_row = PackFrame(self, direction="horizontal", padding=(0, 4, 0, 0))
109
+
110
+ self._replace_entry = TextEntry(self._replace_row, density="compact")
111
+ self._replace_entry.insert_addon(Label, position="before", icon="pencil")
112
+ self._replace_entry.pack(fill="x", expand=True)
113
+
114
+ self._replace_btn = Button(
115
+ self._replace_row, icon="pencil", icon_only=True,
116
+ variant="ghost", density="compact",
117
+ command=self._replace_current,
118
+ )
119
+ self._replace_btn.pack(padx=(8, 0))
120
+
121
+ self._replace_all_btn = Button(
122
+ self._replace_row, icon="pencil-fill", icon_only=True,
123
+ variant="ghost", density="compact",
124
+ command=self._replace_all,
125
+ )
126
+ self._replace_all_btn.pack(padx=(4, 0))
127
+
128
+ # ── key bindings — find entry ─────────────────────────────────────
129
+ _fi = self._find_entry.entry_widget
130
+ _fi.bind("<Return>", lambda _: self._next_match())
131
+ _fi.bind("<Shift-Return>", lambda _: self._prev_match())
132
+ _fi.bind("<Escape>", lambda _: self.hide())
133
+
134
+ # ── key bindings — replace entry ──────────────────────────────────
135
+ _ri = self._replace_entry.entry_widget
136
+ _ri.bind("<Return>", lambda _: self._replace_current())
137
+ _ri.bind("<Escape>", lambda _: self.hide())
138
+
139
+ # ── key bindings — editor text widget ─────────────────────────────
140
+ core.text.bind("<Control-f>", self._on_ctrl_f, add="+")
141
+ core.text.bind("<Control-F>", self._on_ctrl_f, add="+")
142
+ core.text.bind("<Control-h>", self._on_ctrl_h, add="+")
143
+ core.text.bind("<Control-H>", self._on_ctrl_h, add="+")
144
+ if core.winsys == "aqua":
145
+ core.text.bind("<Command-f>", self._on_ctrl_f, add="+")
146
+ core.text.bind("<Command-F>", self._on_ctrl_f, add="+")
147
+ core.text.bind("<Command-h>", self._on_ctrl_h, add="+")
148
+ core.text.bind("<Command-H>", self._on_ctrl_h, add="+")
149
+ core.text.bind("<Escape>", self._on_editor_escape, add="+")
150
+ core.text.bind("<<Change>>", self._on_content_changed, add="+")
151
+ core.text.bind("<<EditorBgChanged>>", self._on_theme_changed, add="+")
152
+
153
+ # Re-sync highlight colors after a theme rebuild. Bind <<BsThemeChanged>>
154
+ # on the root — fired ONCE by bootstack after the full rebuild (correct
155
+ # colors) — NOT the ttk <<ThemeChanged>>, which fires mid-rebuild.
156
+ self._theme_root = core.winfo_toplevel()
157
+ self._theme_bind_id = self._theme_root.bind(
158
+ "<<BsThemeChanged>>", self._on_theme_changed, add="+"
159
+ )
160
+ self.bind("<Destroy>", self._on_destroy, add="+")
161
+
162
+ # Measure find-row-only height for initial place() sizing.
163
+ self.update_idletasks()
164
+ self._height = max(self.winfo_reqheight(), 44)
165
+
166
+ # ── public API ────────────────────────────────────────────────────────
167
+
168
+ def show(self, replace: bool = False) -> None:
169
+ """Show the find bar, optionally with the replace row visible.
170
+
171
+ Args:
172
+ replace: If True, show the replace row and focus the replace entry.
173
+ """
174
+ self._sync_colors()
175
+ self._visible = True
176
+ self._set_replace_visible(replace)
177
+ self.lift()
178
+ if replace and self._replace_visible:
179
+ self._replace_entry.focus_set()
180
+ else:
181
+ self._find_entry.focus_set()
182
+ self._run_search()
183
+
184
+ def hide(self) -> None:
185
+ """Hide the find/replace bar and clear all highlights."""
186
+ self._visible = False
187
+ self._core.lift()
188
+ self._core.clear_decorations(_LAYER)
189
+ self._matches = []
190
+ self._current = -1
191
+ self._count_sig.set("")
192
+ self._set_entry_state("normal")
193
+ try:
194
+ self._core.text.focus_set()
195
+ except Exception:
196
+ pass
197
+
198
+ # ── replace logic ─────────────────────────────────────────────────────
199
+
200
+ def _replace_current(self) -> None:
201
+ """Replace the current match with the replace entry text."""
202
+ if not self._matches or self._current < 0:
203
+ return
204
+ if self._core._read_only:
205
+ return
206
+ text = self._core.value
207
+ start, end = self._matches[self._current]
208
+ replacement = self._expand_replacement(text[start:end])
209
+ line_starts = _build_line_starts(text)
210
+ s = _offset_to_position(line_starts, start).to_tk()
211
+ e = _offset_to_position(line_starts, end).to_tk()
212
+ self._core.undo_block_start()
213
+ self._core.text.delete(s, e)
214
+ self._core.text.insert(s, replacement)
215
+ self._core.undo_block_stop()
216
+ self._run_search()
217
+
218
+ def _replace_all(self) -> None:
219
+ """Replace every match with the replace entry text."""
220
+ if not self._matches:
221
+ return
222
+ if self._core._read_only:
223
+ return
224
+ text = self._core.value
225
+ line_starts = _build_line_starts(text)
226
+ # Process last-to-first so earlier offsets stay valid.
227
+ ops = [
228
+ (
229
+ _offset_to_position(line_starts, start).to_tk(),
230
+ _offset_to_position(line_starts, end).to_tk(),
231
+ self._expand_replacement(text[start:end]),
232
+ )
233
+ for start, end in reversed(self._matches)
234
+ ]
235
+ count = len(ops)
236
+ self._core.undo_block_start()
237
+ for s, e, r in ops:
238
+ self._core.text.delete(s, e)
239
+ self._core.text.insert(s, r)
240
+ self._core.undo_block_stop()
241
+ self._run_search()
242
+ self._count_sig.set(f"{count} replaced")
243
+
244
+ def _expand_replacement(self, matched_text: str) -> str:
245
+ """Return the replacement string, expanding regex back-references."""
246
+ replacement = self._replace_entry.value or ""
247
+ if self._regex_sig():
248
+ try:
249
+ query = self._find_entry.value or ""
250
+ flags = 0 if self._case_sig() else re.IGNORECASE
251
+ m = re.fullmatch(query, matched_text, flags)
252
+ if m:
253
+ return m.expand(replacement)
254
+ except Exception:
255
+ pass
256
+ return replacement
257
+
258
+ # ── search logic ──────────────────────────────────────────────────────
259
+
260
+ def _run_search(self) -> None:
261
+ self._after_id = None
262
+ query = self._find_entry.value or ""
263
+ if not query:
264
+ self._core.clear_decorations(_LAYER)
265
+ self._matches = []
266
+ self._current = -1
267
+ self._count_sig.set("")
268
+ self._set_entry_state("normal")
269
+ return
270
+
271
+ text = self._core.value
272
+ flags = 0 if self._case_sig() else re.IGNORECASE
273
+
274
+ try:
275
+ pattern = re.compile(
276
+ query if self._regex_sig() else re.escape(query),
277
+ flags,
278
+ )
279
+ except re.error:
280
+ self._set_entry_state("error")
281
+ self._core.clear_decorations(_LAYER)
282
+ self._matches = []
283
+ self._current = -1
284
+ self._count_sig.set("bad regex")
285
+ return
286
+
287
+ self._matches = [(m.start(), m.end()) for m in pattern.finditer(text)]
288
+
289
+ if not self._matches:
290
+ self._core.clear_decorations(_LAYER)
291
+ self._current = -1
292
+ self._count_sig.set("")
293
+ self._set_entry_state("no_match")
294
+ return
295
+
296
+ self._set_entry_state("normal")
297
+
298
+ try:
299
+ idx = self._core.text.index("insert")
300
+ line_no, col_no = idx.split(".")
301
+ cursor_offset = _position_to_offset(text, int(line_no), int(col_no))
302
+ except Exception:
303
+ cursor_offset = 0
304
+
305
+ starts = [m[0] for m in self._matches]
306
+ self._current = bisect.bisect_left(starts, cursor_offset) % len(self._matches)
307
+ self._apply_decorations(text)
308
+
309
+ def _apply_decorations(self, text: str) -> None:
310
+ if not self._matches:
311
+ self._core.clear_decorations(_LAYER)
312
+ return
313
+
314
+ line_starts = _build_line_starts(text)
315
+ decorations = [
316
+ RangeDecoration(
317
+ _offset_to_position(line_starts, start),
318
+ _offset_to_position(line_starts, end),
319
+ _STYLE_CURRENT if i == self._current else _STYLE_MATCH,
320
+ )
321
+ for i, (start, end) in enumerate(self._matches)
322
+ ]
323
+ self._core.set_decorations(_LAYER, decorations)
324
+
325
+ total = len(self._matches)
326
+ self._count_sig.set(f"{self._current + 1} / {total}")
327
+
328
+ if 0 <= self._current < total:
329
+ start_offset = self._matches[self._current][0]
330
+ pos = _offset_to_position(_build_line_starts(text), start_offset)
331
+ try:
332
+ self._core.text.see(pos.to_tk())
333
+ except Exception:
334
+ pass
335
+
336
+ def _next_match(self) -> None:
337
+ if not self._matches:
338
+ return
339
+ self._current = (self._current + 1) % len(self._matches)
340
+ self._apply_decorations(self._core.value)
341
+
342
+ def _prev_match(self) -> None:
343
+ if not self._matches:
344
+ return
345
+ self._current = (self._current - 1) % len(self._matches)
346
+ self._apply_decorations(self._core.value)
347
+
348
+ def _set_replace_visible(self, visible: bool) -> None:
349
+ if visible == self._replace_visible:
350
+ return
351
+ self._replace_visible = visible
352
+ if visible:
353
+ self._replace_row.pack(fill="x")
354
+ else:
355
+ self._replace_row.pack_forget()
356
+ self.update_idletasks()
357
+ self._height = max(self.winfo_reqheight(), 44)
358
+ try:
359
+ self.place_configure(height=self._height)
360
+ except Exception:
361
+ pass
362
+
363
+ def _set_entry_state(self, state: str) -> None:
364
+ accent_map = {"error": "danger", "no_match": "warning"}
365
+ try:
366
+ self._find_entry.configure(accent=accent_map.get(state, "primary"))
367
+ except Exception:
368
+ pass
369
+
370
+ # ── event handlers ────────────────────────────────────────────────────
371
+
372
+ def _on_ctrl_f(self, _event) -> str:
373
+ self.show(replace=False)
374
+ return "break"
375
+
376
+ def _on_ctrl_h(self, _event) -> str:
377
+ self.show(replace=True)
378
+ return "break"
379
+
380
+ def _on_editor_escape(self, _event) -> None:
381
+ if self._visible:
382
+ self.hide()
383
+
384
+ def _on_query_changed(self, _event=None) -> None:
385
+ self._schedule_search()
386
+
387
+ def _on_option_changed(self) -> None:
388
+ self._schedule_search()
389
+
390
+ def _on_content_changed(self, _event) -> None:
391
+ if self._visible:
392
+ self._schedule_search()
393
+
394
+ def _on_theme_changed(self, _event=None) -> None:
395
+ self._sync_colors()
396
+
397
+ def _on_destroy(self, event=None) -> None:
398
+ # Fire on any descendant <Destroy> — the unbind is idempotent. A guarded
399
+ # `is self` check is unreliable here (the container's own <Destroy> is
400
+ # not delivered to this binding), and the overlay never destroys its own
401
+ # children mid-life, so an early unbind is safe.
402
+ if self._theme_bind_id is not None and self._theme_root is not None:
403
+ try:
404
+ self._theme_root.unbind("<<BsThemeChanged>>", self._theme_bind_id)
405
+ except Exception:
406
+ pass
407
+ self._theme_bind_id = None
408
+ self._theme_root = None
409
+
410
+ def _sync_colors(self) -> None:
411
+ if self._syncing_colors:
412
+ return
413
+ self._syncing_colors = True
414
+ try:
415
+ self._core.define_style(_STYLE_MATCH, background="#ffff60", foreground="#000000")
416
+ self._core.define_style(_STYLE_CURRENT, background="#ff8000", foreground="#000000")
417
+ finally:
418
+ self._syncing_colors = False
419
+
420
+ def _schedule_search(self) -> None:
421
+ if self._after_id is not None:
422
+ try:
423
+ self._core.text.after_cancel(self._after_id)
424
+ except Exception:
425
+ pass
426
+ self._after_id = self._core.text.after(_DEBOUNCE_MS, self._run_search)
427
+
428
+
429
+ # ── position helpers ──────────────────────────────────────────────────────
430
+
431
+ def _build_line_starts(text: str) -> list[int]:
432
+ starts = [0]
433
+ for i, ch in enumerate(text):
434
+ if ch == "\n":
435
+ starts.append(i + 1)
436
+ return starts
437
+
438
+
439
+ def _offset_to_position(line_starts: list[int], offset: int) -> Position:
440
+ line_idx = bisect.bisect_right(line_starts, offset) - 1
441
+ col = offset - line_starts[line_idx]
442
+ return Position(line_idx + 1, col)
443
+
444
+
445
+ def _position_to_offset(text: str, line: int, col: int) -> int:
446
+ lines = text.split("\n")
447
+ offset = sum(len(lines[i]) + 1 for i in range(min(line - 1, len(lines))))
448
+ return offset + min(col, len(lines[line - 1]) if line <= len(lines) else 0)
449
+
450
+
451
+ def _luminance(hex_color: str) -> float:
452
+ h = hex_color.lstrip("#")
453
+ if len(h) < 6:
454
+ return 200.0
455
+ try:
456
+ r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
457
+ return 0.299 * r + 0.587 * g + 0.114 * b
458
+ except Exception:
459
+ return 200.0
@@ -0,0 +1,88 @@
1
+ """Sidebar protocol for TextArea/CodeEditor gutter widgets.
2
+
3
+ Port of idlelib/sidebar.py BaseSideBar. Sidebars are sibling widgets placed
4
+ in the core's grid, scroll-synced with the main Text widget.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Literal, TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from bootstack.widgets._impl.composites.textarea.core import _MultilineCore
12
+
13
+
14
+ class Sidebar:
15
+ """Base class for gutter widgets placed alongside the Text area.
16
+
17
+ Subclass and override `_build(core)` to create the sidebar widget,
18
+ then override `on_scroll` and `on_change` as needed.
19
+
20
+ The sidebar is placed in column 0 of the core's grid (left side).
21
+ The core calls `on_scroll` whenever the main text scrolls and
22
+ `on_change` (via the `<<Change>>` event) whenever content changes.
23
+ """
24
+
25
+ def __init__(self) -> None:
26
+ self._theme_bind_id: str | None = None
27
+ self._theme_root = None
28
+
29
+ def attach(self, core: _MultilineCore, side: Literal["left"] = "left") -> None:
30
+ """Attach this sidebar to *core* and add it to the grid.
31
+
32
+ Args:
33
+ core: The `_MultilineCore` to attach to.
34
+ side: Layout side. Only `"left"` is currently supported.
35
+ """
36
+ self._core = core
37
+ self._side = side
38
+ self._build(core)
39
+ core.text.bind("<<Change>>", self._on_change_event, add="+")
40
+ root = core.winfo_toplevel()
41
+ self._theme_bind_id = root.bind(
42
+ "<<BsThemeChanged>>", self._on_theme_event, add="+"
43
+ )
44
+ self._theme_root = root
45
+
46
+ def detach(self, core: _MultilineCore) -> None:
47
+ """Remove this sidebar from the grid and release resources."""
48
+ try:
49
+ core.text.unbind("<<Change>>", None)
50
+ except Exception:
51
+ pass
52
+ try:
53
+ if self._theme_bind_id and self._theme_root:
54
+ self._theme_root.unbind("<<BsThemeChanged>>", self._theme_bind_id)
55
+ except Exception:
56
+ pass
57
+ self._destroy()
58
+
59
+ # ── override in subclasses ────────────────────────────────────────────
60
+
61
+ def _build(self, core: _MultilineCore) -> None:
62
+ """Create the sidebar widget and grid it. Called by `attach`."""
63
+
64
+ def _destroy(self) -> None:
65
+ """Release resources. Called by `detach`."""
66
+
67
+ def on_scroll(self, first: str, last: str) -> None:
68
+ """Called when the main text scrolls.
69
+
70
+ Args:
71
+ first: First visible fraction (0.0–1.0).
72
+ last: Last visible fraction (0.0–1.0).
73
+ """
74
+
75
+ def on_change(self) -> None:
76
+ """Called after every insert/delete in the main text."""
77
+
78
+ def update_font(self) -> None:
79
+ """Called when the font changes."""
80
+
81
+ def update_colors(self, event=None) -> None:
82
+ """Called when the theme changes."""
83
+
84
+ def _on_change_event(self, _event=None) -> None:
85
+ self.on_change()
86
+
87
+ def _on_theme_event(self, event=None) -> None:
88
+ self.update_colors(event)