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,496 @@
1
+ """Cross-platform keyboard shortcut management.
2
+
3
+ This module provides a service for managing keyboard shortcuts with
4
+ automatic platform detection and modifier key translation.
5
+
6
+ Examples:
7
+ .. code-block:: python
8
+
9
+ import bootstack as bs
10
+ from bootstack.shortcuts import get_shortcuts
11
+
12
+ app = bs.App()
13
+ shortcuts = get_shortcuts()
14
+
15
+ # Register shortcuts
16
+ shortcuts.register("save", "Mod+S", save_file)
17
+ shortcuts.register("open", "Mod+O", open_file)
18
+ shortcuts.register("quit", "Mod+Q", app.quit)
19
+
20
+ # Bind to app window
21
+ shortcuts.bind_to(app)
22
+
23
+ # Menu displays shortcut text automatically
24
+ menu.add_command(text="Save", shortcut="save", command=save_file)
25
+
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import sys
30
+ from dataclasses import dataclass
31
+ from typing import Callable, Any
32
+ from weakref import WeakSet
33
+
34
+ IS_MAC = sys.platform == 'darwin'
35
+ IS_WIN = sys.platform == 'win32'
36
+
37
+ # Modifier display symbols for Mac
38
+ _MAC_SYMBOLS = {
39
+ 'mod': '\u2318', # ⌘ Command
40
+ 'command': '\u2318', # ⌘
41
+ 'ctrl': '\u2303', # ⌃ Control
42
+ 'control': '\u2303', # ⌃
43
+ 'alt': '\u2325', # ⌥ Option
44
+ 'option': '\u2325', # ⌥
45
+ 'shift': '\u21E7', # ⇧
46
+ }
47
+
48
+ # Modifier display names for Windows/Linux
49
+ _WIN_NAMES = {
50
+ 'mod': 'Ctrl',
51
+ 'command': 'Ctrl',
52
+ 'ctrl': 'Ctrl',
53
+ 'control': 'Ctrl',
54
+ 'alt': 'Alt',
55
+ 'option': 'Alt',
56
+ 'shift': 'Shift',
57
+ }
58
+
59
+ # Tkinter binding modifiers
60
+ _BINDING_MODIFIERS = {
61
+ 'mod': 'Command' if IS_MAC else 'Control',
62
+ 'command': 'Command',
63
+ 'ctrl': 'Control',
64
+ 'control': 'Control',
65
+ 'alt': 'Option' if IS_MAC else 'Alt',
66
+ 'option': 'Option',
67
+ 'shift': 'Shift',
68
+ }
69
+
70
+
71
+ @dataclass
72
+ class Shortcut:
73
+ """A registered keyboard shortcut."""
74
+
75
+ key: str
76
+ """Unique identifier for lookup (e.g. `'save'`, `'file.open'`)."""
77
+
78
+ pattern: str
79
+ """Shortcut pattern (e.g. `'Mod+S'`, `'Shift+Alt+N'`)."""
80
+
81
+ command: Callable
82
+ """Function to execute when triggered."""
83
+
84
+ @property
85
+ def display(self) -> str:
86
+ """Platform-appropriate display string for menus.
87
+
88
+ Returns:
89
+ On Mac: "⇧⌘S" (symbols, no separators)
90
+ On Windows/Linux: "Ctrl+Shift+S" (names with + separators)
91
+ """
92
+ parts = self.pattern.split('+')
93
+ key_part = parts[-1]
94
+ mod_parts = [p.lower() for p in parts[:-1]]
95
+
96
+ if IS_MAC:
97
+ # Mac style: symbols without separators, specific order
98
+ # Standard order: Control, Option, Shift, Command
99
+ ordered_mods = []
100
+ for mod in ['ctrl', 'control', 'alt', 'option', 'shift', 'mod', 'command']:
101
+ if mod in mod_parts:
102
+ symbol = _MAC_SYMBOLS.get(mod, mod)
103
+ if symbol not in ordered_mods:
104
+ ordered_mods.append(symbol)
105
+ return ''.join(ordered_mods) + key_part.upper()
106
+ else:
107
+ # Windows/Linux style: names with + separators
108
+ mod_names = []
109
+ for mod in mod_parts:
110
+ name = _WIN_NAMES.get(mod, mod.capitalize())
111
+ if name not in mod_names:
112
+ mod_names.append(name)
113
+ return '+'.join(mod_names + [key_part.upper()])
114
+
115
+ @property
116
+ def binding(self) -> str:
117
+ """Tkinter binding string.
118
+
119
+ Returns:
120
+ Binding string like "<Control-s>" or "<Command-Shift-s>"
121
+ """
122
+ parts = self.pattern.split('+')
123
+ key_part = parts[-1].lower()
124
+ mod_parts = [p.lower() for p in parts[:-1]]
125
+
126
+ # Convert to Tkinter modifier names
127
+ tk_mods = []
128
+ for mod in mod_parts:
129
+ tk_mod = _BINDING_MODIFIERS.get(mod, mod.capitalize())
130
+ if tk_mod not in tk_mods:
131
+ tk_mods.append(tk_mod)
132
+
133
+ # Handle function keys - keep uppercase
134
+ if key_part.upper().startswith('F') and key_part[1:].isdigit():
135
+ key_part = key_part.upper()
136
+
137
+ return f"<{'-'.join(tk_mods + [key_part])}>"
138
+
139
+
140
+ class Shortcuts:
141
+ """Singleton service for managing keyboard shortcuts.
142
+
143
+ Provides cross-platform keyboard shortcut registration and binding.
144
+ The service automatically translates modifier keys for each platform:
145
+
146
+ - `Mod` becomes `Ctrl` on Windows/Linux, `Command` on Mac
147
+ - `Alt` becomes `Alt` on Windows/Linux, `Option` on Mac
148
+ - `Shift` works the same on all platforms
149
+
150
+ Examples:
151
+ .. code-block:: python
152
+
153
+ shortcuts = get_shortcuts()
154
+
155
+ # Register shortcuts
156
+ shortcuts.register("save", "Mod+S", save_file)
157
+ shortcuts.register("undo", "Mod+Z", undo)
158
+ shortcuts.register("find", "Mod+F", find)
159
+
160
+ # Bind all to window
161
+ shortcuts.bind_to(app)
162
+
163
+ # Get display string for menu
164
+ shortcuts.display("save") # "Ctrl+S" on Windows, "⌘S" on Mac
165
+
166
+ """
167
+
168
+ _instance: 'Shortcuts | None' = None
169
+
170
+ def __new__(cls):
171
+ if cls._instance is None:
172
+ cls._instance = super().__new__(cls)
173
+ cls._instance._shortcuts: dict[str, Shortcut] = {}
174
+ cls._instance._bound_windows: WeakSet = WeakSet()
175
+ cls._instance._bindings: dict[int, list[tuple[str, str]]] = {}
176
+ return cls._instance
177
+
178
+ def register(self, key: str, pattern: str, command: Callable) -> Shortcut:
179
+ """Register a keyboard shortcut.
180
+
181
+ Args:
182
+ key: Unique identifier (e.g., "save", "file.open")
183
+ pattern: Shortcut pattern using symbolic modifiers:
184
+ - `Mod+S` - Primary modifier + S
185
+ - `Mod+Shift+S` - Primary modifier + Shift + S
186
+ - `Alt+F4` - Alt + F4
187
+ - `F5` - Function key alone
188
+ command: Function to call when triggered
189
+
190
+ Returns:
191
+ The created Shortcut object.
192
+
193
+ Raises:
194
+ ValueError: If key is already registered.
195
+
196
+ Examples:
197
+ .. code-block:: python
198
+
199
+ shortcuts.register("save", "Mod+S", save_file)
200
+ shortcuts.register("quit", "Mod+Q", app.quit)
201
+ shortcuts.register("refresh", "F5", refresh)
202
+
203
+ """
204
+ if key in self._shortcuts:
205
+ raise ValueError(f"Shortcut key '{key}' is already registered")
206
+
207
+ shortcut = Shortcut(key=key, pattern=pattern, command=command)
208
+ self._shortcuts[key] = shortcut
209
+
210
+ # Bind to any already-bound windows
211
+ for window in self._bound_windows:
212
+ self._bind_shortcut(window, shortcut)
213
+
214
+ return shortcut
215
+
216
+ def unregister(self, key: str) -> None:
217
+ """Remove a registered shortcut.
218
+
219
+ Args:
220
+ key: The shortcut key to remove.
221
+
222
+ Raises:
223
+ KeyError: If key is not registered.
224
+ """
225
+ if key not in self._shortcuts:
226
+ raise KeyError(f"Shortcut key '{key}' is not registered")
227
+
228
+ shortcut = self._shortcuts.pop(key)
229
+
230
+ # Unbind from all windows
231
+ for window in self._bound_windows:
232
+ self._unbind_shortcut(window, shortcut)
233
+
234
+ def get(self, key: str) -> Shortcut | None:
235
+ """Get a shortcut by key.
236
+
237
+ Args:
238
+ key: The shortcut key to look up.
239
+
240
+ Returns:
241
+ The Shortcut object, or None if not found.
242
+ """
243
+ return self._shortcuts.get(key)
244
+
245
+ def display(self, key: str) -> str:
246
+ """Get display string for a shortcut key.
247
+
248
+ Args:
249
+ key: The shortcut key to look up.
250
+
251
+ Returns:
252
+ Platform-appropriate display string (e.g., "Ctrl+S" or "⌘S"),
253
+ or empty string if not found.
254
+ """
255
+ shortcut = self._shortcuts.get(key)
256
+ return shortcut.display if shortcut else ''
257
+
258
+ def binding(self, key: str) -> str:
259
+ """Get Tkinter binding string for a shortcut key.
260
+
261
+ Args:
262
+ key: The shortcut key to look up.
263
+
264
+ Returns:
265
+ Tkinter binding string (e.g., "<Control-s>"),
266
+ or empty string if not found.
267
+ """
268
+ shortcut = self._shortcuts.get(key)
269
+ return shortcut.binding if shortcut else ''
270
+
271
+ def bind_to(self, window: Any) -> None:
272
+ """Bind all registered shortcuts to a window.
273
+
274
+ Args:
275
+ window: The window (App, Toplevel, Tk) to bind shortcuts to.
276
+
277
+ Note:
278
+ Shortcuts registered after calling bind_to will also be
279
+ automatically bound to this window.
280
+ """
281
+ if window in self._bound_windows:
282
+ return
283
+
284
+ self._bound_windows.add(window)
285
+ window_id = id(window)
286
+ self._bindings[window_id] = []
287
+
288
+ for shortcut in self._shortcuts.values():
289
+ self._bind_shortcut(window, shortcut)
290
+
291
+ def unbind_from(self, window: Any) -> None:
292
+ """Remove all shortcut bindings from a window.
293
+
294
+ Args:
295
+ window: The window to unbind shortcuts from.
296
+ """
297
+ if window not in self._bound_windows:
298
+ return
299
+
300
+ self._bound_windows.discard(window)
301
+ window_id = id(window)
302
+
303
+ # Unbind all shortcuts
304
+ bindings = self._bindings.pop(window_id, [])
305
+ for binding_str, func_id in bindings:
306
+ try:
307
+ window.unbind(binding_str, func_id)
308
+ except Exception:
309
+ pass
310
+
311
+ def all(self) -> dict[str, Shortcut]:
312
+ """Get all registered shortcuts.
313
+
314
+ Returns:
315
+ Dictionary mapping keys to Shortcut objects.
316
+ """
317
+ return dict(self._shortcuts)
318
+
319
+ def _bind_shortcut(self, window: Any, shortcut: Shortcut) -> None:
320
+ """Bind a single shortcut to a window."""
321
+ window_id = id(window)
322
+ binding_str = shortcut.binding
323
+
324
+ def handler(event):
325
+ shortcut.command()
326
+ return 'break'
327
+
328
+ try:
329
+ func_id = window.bind(binding_str, handler, add='+')
330
+ if window_id in self._bindings:
331
+ self._bindings[window_id].append((binding_str, func_id))
332
+ except Exception:
333
+ pass
334
+
335
+ def _unbind_shortcut(self, window: Any, shortcut: Shortcut) -> None:
336
+ """Unbind a single shortcut from a window."""
337
+ window_id = id(window)
338
+ binding_str = shortcut.binding
339
+
340
+ if window_id not in self._bindings:
341
+ return
342
+
343
+ # Find and remove the binding
344
+ bindings = self._bindings[window_id]
345
+ for i, (b_str, func_id) in enumerate(bindings):
346
+ if b_str == binding_str:
347
+ try:
348
+ window.unbind(binding_str, func_id)
349
+ except Exception:
350
+ pass
351
+ bindings.pop(i)
352
+ break
353
+
354
+
355
+ def get_shortcuts() -> Shortcuts:
356
+ """Get the global Shortcuts service instance.
357
+
358
+ Returns:
359
+ The singleton Shortcuts instance.
360
+
361
+ Examples:
362
+ .. code-block:: python
363
+
364
+ from bootstack.shortcuts import get_shortcuts
365
+
366
+ shortcuts = get_shortcuts()
367
+ shortcuts.register("save", "Mod+S", save_file)
368
+ shortcuts.bind_to(app)
369
+
370
+ """
371
+ return Shortcuts()
372
+
373
+
374
+ def classify_shortcut(spec: str | None) -> str:
375
+ """Classify a shortcut spec into one of four kinds.
376
+
377
+ Returns one of:
378
+
379
+ - `'empty'` — `spec` is `None` or empty.
380
+ - `'registered'` — `spec` matches a key registered with the
381
+ `Shortcuts` service. The keypress is (or will be) bound there; a
382
+ caller should treat it as display-only.
383
+ - `'pattern'` — a modifier expression like `'Mod+S'` or a bare
384
+ function key like `'F5'`. Not registered; a caller may register and
385
+ bind it themselves.
386
+ - `'literal'` — anything else; a plain display string to show as-is.
387
+
388
+ Args:
389
+ spec: A registered key, modifier pattern, or literal display.
390
+ """
391
+ if not spec:
392
+ return 'empty'
393
+ if Shortcuts().get(spec) is not None:
394
+ return 'registered'
395
+ looks_like_pattern = '+' in spec or (
396
+ len(spec) >= 2
397
+ and spec[0].upper() == 'F'
398
+ and spec[1:].isdigit()
399
+ )
400
+ return 'pattern' if looks_like_pattern else 'literal'
401
+
402
+
403
+ def format_shortcut(spec: str | None) -> str:
404
+ """Resolve a shortcut spec to its platform-appropriate display string.
405
+
406
+ Accepts three forms in order of precedence:
407
+
408
+ 1. **Registered key** — the key passed to `Shortcuts.register`
409
+ (e.g. `"save"`, `"file.open"`). Returns the registered
410
+ shortcut's `display`.
411
+ 2. **Pattern** — a modifier expression like `"Mod+S"`,
412
+ `"Ctrl+Shift+N"`, or `"F5"`. Treated as a transient pattern;
413
+ returns the pattern's platform display without registering anything.
414
+ 3. **Plain text** — anything that doesn't parse as a pattern (no
415
+ modifier separator, not a function key) is returned unchanged so
416
+ callers can still pass a literal display string.
417
+
418
+ Args:
419
+ spec: A registered key, modifier pattern, or literal display.
420
+
421
+ Returns:
422
+ Platform-appropriate display string, or empty string when `spec`
423
+ is empty/None.
424
+
425
+ Examples:
426
+ .. code-block:: python
427
+
428
+ format_shortcut("save") # "⌘S" on Mac, "Ctrl+S" on Win/Linux
429
+ # (assumes 'save' is registered)
430
+ format_shortcut("Mod+Shift+N") # "⇧⌘N" on Mac, "Ctrl+Shift+N" on Win
431
+ format_shortcut("F5") # "F5" everywhere
432
+ format_shortcut("⌘S") # "⌘S" — passes through unchanged
433
+
434
+ """
435
+ kind = classify_shortcut(spec)
436
+ if kind == 'empty':
437
+ return ''
438
+ if kind == 'registered':
439
+ return Shortcuts().get(spec).display
440
+ if kind == 'pattern':
441
+ # Transient Shortcut for display only — no registration, no binding.
442
+ return Shortcut(key='', pattern=spec, command=lambda: None).display
443
+ # 'literal' — treat as a display string.
444
+ return spec
445
+
446
+
447
+ # Tk on Aqua renders these modifier WORDS as the ⌘/⌥/⌃/⇧ glyphs in menu
448
+ # accelerators. It does NOT parse the glyphs themselves, so a native tk.Menu
449
+ # must be given the word form — not `Shortcut.display`.
450
+ _AQUA_ACCEL_WORDS = {
451
+ 'mod': 'Command', 'command': 'Command', 'cmd': 'Command',
452
+ 'ctrl': 'Control', 'control': 'Control',
453
+ 'alt': 'Option', 'option': 'Option', 'opt': 'Option',
454
+ 'shift': 'Shift',
455
+ }
456
+
457
+
458
+ def tk_aqua_accelerator(spec: str | None) -> str:
459
+ """Accelerator string for a native macOS `tk.Menu` entry.
460
+
461
+ Tk on Aqua converts modifier *words* (`Command`, `Option`, `Control`,
462
+ `Shift`) into the ⌘/⌥/⌃/⇧ glyphs when drawing a menu accelerator, but it
463
+ does not understand a pre-rendered glyph string. So a native menu must be
464
+ given the word form (e.g. `'Command+N'`) rather than `format_shortcut`'s
465
+ display (`'⌘N'`), which would show the modifier but drop the key.
466
+
467
+ Accepts the same three forms as `format_shortcut` — a registered key
468
+ (resolved to its pattern), a pattern, or a literal (returned unchanged).
469
+
470
+ Args:
471
+ spec: A registered key, modifier pattern, or literal display.
472
+
473
+ Returns:
474
+ A Tk-Aqua accelerator string, or empty string when `spec` is empty.
475
+ """
476
+ kind = classify_shortcut(spec)
477
+ if kind == 'empty':
478
+ return ''
479
+ if kind == 'literal':
480
+ return spec
481
+ pattern = Shortcuts().get(spec).pattern if kind == 'registered' else spec
482
+
483
+ parts = pattern.split('+')
484
+ key = parts[-1]
485
+ mods: list[str] = []
486
+ for part in parts[:-1]:
487
+ word = _AQUA_ACCEL_WORDS.get(part.lower(), part.capitalize())
488
+ if word not in mods:
489
+ mods.append(word)
490
+ return '+'.join(mods + [key.upper()])
491
+
492
+
493
+ __all__ = [
494
+ 'Shortcut', 'Shortcuts', 'get_shortcuts', 'format_shortcut',
495
+ 'classify_shortcut', 'tk_aqua_accelerator',
496
+ ]
@@ -0,0 +1,43 @@
1
+ """Install-time patching for Tk widgets only.
2
+
3
+ This module applies bootstack's Tk autostyle behavior to Tk widgets by
4
+ wrapping their constructors. It does not affect ttk widgets, which are
5
+ provided as explicit subclasses in the bootstack.widgets package.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import tkinter as tk
11
+
12
+ from bootstack.style.style_resolver import StyleResolver
13
+
14
+ # Native Tk widget classes whose constructors get the autostyle wrapper.
15
+ # Limited to the raw Tk widgets the framework actually instantiates or
16
+ # subclasses (everything else is built on ttk). Implementation detail of this
17
+ # patch — intentionally not exported anywhere.
18
+ _TK_WIDGETS = (
19
+ tk.Tk, # application root window
20
+ tk.Toplevel, # secondary windows, dialogs, popups
21
+ tk.Frame, # Slider/RangeSlider/multiline-text container subclasses
22
+ tk.Text, # TextArea / CodeEditor cores
23
+ tk.Canvas, # sliders, scroll view, gauge drawing
24
+ tk.Menu, # context menus and menu bars
25
+ )
26
+
27
+
28
+ def install_tk_autostyle() -> None:
29
+ """Patch Tk widgets to enable autostyle via StyleResolver.
30
+
31
+ - Wraps Tk widget `__init__` with surface color inheritance and theme
32
+ background application using registered Tk builders.
33
+ - Leaves ttk widgets untouched; ttk integration is done via wrappers.
34
+ """
35
+ # Ensure Tk builders are registered
36
+ import bootstack.style.builders_tk # noqa: F401
37
+
38
+ for widget in _TK_WIDGETS:
39
+ _init = StyleResolver.override_tk_widget_constructor(widget.__init__)
40
+ widget.__init__ = _init
41
+
42
+
43
+ __all__ = ["install_tk_autostyle"]