bootstack 0.1.0a1__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 (419) hide show
  1. bootstack/__init__.py +249 -0
  2. bootstack/__main__.py +5 -0
  3. bootstack/api/__init__.py +127 -0
  4. bootstack/api/app.py +30 -0
  5. bootstack/api/constants.py +3 -0
  6. bootstack/api/data.py +23 -0
  7. bootstack/api/dialogs.py +44 -0
  8. bootstack/api/i18n.py +17 -0
  9. bootstack/api/localization.py +16 -0
  10. bootstack/api/menu.py +7 -0
  11. bootstack/api/style.py +23 -0
  12. bootstack/api/utils.py +24 -0
  13. bootstack/api/widgets.py +137 -0
  14. bootstack/assets/__init__.py +24 -0
  15. bootstack/assets/bootstack-transparent.png +0 -0
  16. bootstack/assets/bootstack.ico +0 -0
  17. bootstack/assets/bootstack.png +0 -0
  18. bootstack/assets/elements/__init__.py +0 -0
  19. bootstack/assets/elements/badge-pill.png +0 -0
  20. bootstack/assets/elements/badge-square.png +0 -0
  21. bootstack/assets/elements/border.png +0 -0
  22. bootstack/assets/elements/button-compact.png +0 -0
  23. bootstack/assets/elements/button-default.png +0 -0
  24. bootstack/assets/elements/button-group-horizontal-after-compact.png +0 -0
  25. bootstack/assets/elements/button-group-horizontal-after-default.png +0 -0
  26. bootstack/assets/elements/button-group-horizontal-before-compact.png +0 -0
  27. bootstack/assets/elements/button-group-horizontal-before-default.png +0 -0
  28. bootstack/assets/elements/button-group-horizontal-center-compact.png +0 -0
  29. bootstack/assets/elements/button-group-horizontal-center-default.png +0 -0
  30. bootstack/assets/elements/button-group-vertical-after-compact.png +0 -0
  31. bootstack/assets/elements/button-group-vertical-after-default.png +0 -0
  32. bootstack/assets/elements/button-group-vertical-before-compact.png +0 -0
  33. bootstack/assets/elements/button-group-vertical-before-default.png +0 -0
  34. bootstack/assets/elements/button-group-vertical-center-compact.png +0 -0
  35. bootstack/assets/elements/button-group-vertical-center-default.png +0 -0
  36. bootstack/assets/elements/checkbox-checked.png +0 -0
  37. bootstack/assets/elements/checkbox-indeterminate.png +0 -0
  38. bootstack/assets/elements/checkbox-unchecked.png +0 -0
  39. bootstack/assets/elements/field.png +0 -0
  40. bootstack/assets/elements/input-after-compact.png +0 -0
  41. bootstack/assets/elements/input-after-default.png +0 -0
  42. bootstack/assets/elements/input-before-compact.png +0 -0
  43. bootstack/assets/elements/input-before-default.png +0 -0
  44. bootstack/assets/elements/input-compact.png +0 -0
  45. bootstack/assets/elements/input-default.png +0 -0
  46. bootstack/assets/elements/list-item-separated.png +0 -0
  47. bootstack/assets/elements/list-item.png +0 -0
  48. bootstack/assets/elements/manifest.toml +480 -0
  49. bootstack/assets/elements/menu-item.png +0 -0
  50. bootstack/assets/elements/nav-button-compact.png +0 -0
  51. bootstack/assets/elements/nav-button-default.png +0 -0
  52. bootstack/assets/elements/nav-icon-button-compact.png +0 -0
  53. bootstack/assets/elements/nav-icon-button-default.png +0 -0
  54. bootstack/assets/elements/notebook-client-border.png +0 -0
  55. bootstack/assets/elements/notebook-tab-active.png +0 -0
  56. bootstack/assets/elements/notebook-tab-bar.png +0 -0
  57. bootstack/assets/elements/notebook-tab-normal.png +0 -0
  58. bootstack/assets/elements/notebook-tab-pill.png +0 -0
  59. bootstack/assets/elements/progress-bar-horizontal-striped.png +0 -0
  60. bootstack/assets/elements/progress-bar-solid.png +0 -0
  61. bootstack/assets/elements/progress-bar-thin.png +0 -0
  62. bootstack/assets/elements/progress-bar-vertical-striped.png +0 -0
  63. bootstack/assets/elements/radio-selected.png +0 -0
  64. bootstack/assets/elements/radio-unselected.png +0 -0
  65. bootstack/assets/elements/scrollbar-horizontal.png +0 -0
  66. bootstack/assets/elements/scrollbar-vertical.png +0 -0
  67. bootstack/assets/elements/slider-handle-focus.png +0 -0
  68. bootstack/assets/elements/slider-handle.png +0 -0
  69. bootstack/assets/elements/slider-track-horizontal.png +0 -0
  70. bootstack/assets/elements/slider-track-vertical.png +0 -0
  71. bootstack/assets/elements/switch-off.png +0 -0
  72. bootstack/assets/elements/switch-on.png +0 -0
  73. bootstack/assets/elements/tabs-bar-horizontal.png +0 -0
  74. bootstack/assets/elements/tabs-bar-vertical.png +0 -0
  75. bootstack/assets/elements/tabs-pill.png +0 -0
  76. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
  77. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +853 -0
  78. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +875 -0
  79. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
  80. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +853 -0
  81. bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
  82. bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +853 -0
  83. bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
  84. bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +853 -0
  85. bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
  86. bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +875 -0
  87. bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
  88. bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +853 -0
  89. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
  90. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +853 -0
  91. bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
  92. bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +851 -0
  93. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
  94. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +842 -0
  95. bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
  96. bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +841 -0
  97. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
  98. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +914 -0
  99. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
  100. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +842 -0
  101. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
  102. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +841 -0
  103. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
  104. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +841 -0
  105. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
  106. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +842 -0
  107. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
  108. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +842 -0
  109. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
  110. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +842 -0
  111. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
  112. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +842 -0
  113. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
  114. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +842 -0
  115. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
  116. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +842 -0
  117. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
  118. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +842 -0
  119. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
  120. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +842 -0
  121. bootstack/assets/themes/__init__.py +0 -0
  122. bootstack/assets/themes/amber-dark.json +32 -0
  123. bootstack/assets/themes/amber-light.json +32 -0
  124. bootstack/assets/themes/aurora-dark.json +32 -0
  125. bootstack/assets/themes/aurora-light.json +32 -0
  126. bootstack/assets/themes/bootstrap-dark.json +32 -0
  127. bootstack/assets/themes/bootstrap-light.json +32 -0
  128. bootstack/assets/themes/classic-dark.json +32 -0
  129. bootstack/assets/themes/classic-light.json +32 -0
  130. bootstack/assets/themes/docs-dark.json +32 -0
  131. bootstack/assets/themes/docs-light.json +32 -0
  132. bootstack/assets/themes/forest-dark.json +32 -0
  133. bootstack/assets/themes/forest-light.json +32 -0
  134. bootstack/assets/themes/ocean-dark.json +32 -0
  135. bootstack/assets/themes/ocean-light.json +32 -0
  136. bootstack/assets/themes/rose-dark.json +32 -0
  137. bootstack/assets/themes/rose-light.json +32 -0
  138. bootstack/assets/widgets/__init__.py +0 -0
  139. bootstack/assets/widgets/badge-default.png +0 -0
  140. bootstack/assets/widgets/badge-pill.png +0 -0
  141. bootstack/assets/widgets/border.png +0 -0
  142. bootstack/assets/widgets/button-group-horizontal-after.png +0 -0
  143. bootstack/assets/widgets/button-group-horizontal-before.png +0 -0
  144. bootstack/assets/widgets/button-group-horizontal-center.png +0 -0
  145. bootstack/assets/widgets/button-group-vertical-after.png +0 -0
  146. bootstack/assets/widgets/button-group-vertical-before.png +0 -0
  147. bootstack/assets/widgets/button-group-vertical-center.png +0 -0
  148. bootstack/assets/widgets/button.png +0 -0
  149. bootstack/assets/widgets/checkbox-checked.png +0 -0
  150. bootstack/assets/widgets/checkbox-indeterminate.png +0 -0
  151. bootstack/assets/widgets/checkbox-unchecked.png +0 -0
  152. bootstack/assets/widgets/field.png +0 -0
  153. bootstack/assets/widgets/icon-button.png +0 -0
  154. bootstack/assets/widgets/input-inner.png +0 -0
  155. bootstack/assets/widgets/input-prefix.png +0 -0
  156. bootstack/assets/widgets/input-suffix.png +0 -0
  157. bootstack/assets/widgets/input.png +0 -0
  158. bootstack/assets/widgets/list-item-focus.png +0 -0
  159. bootstack/assets/widgets/list-item-separated.png +0 -0
  160. bootstack/assets/widgets/menu-item-separated.png +0 -0
  161. bootstack/assets/widgets/notebook-client-border.png +0 -0
  162. bootstack/assets/widgets/notebook-pill-active.png +0 -0
  163. bootstack/assets/widgets/notebook-pill-inactive.png +0 -0
  164. bootstack/assets/widgets/notebook-tab-active.png +0 -0
  165. bootstack/assets/widgets/notebook-tab-border.png +0 -0
  166. bootstack/assets/widgets/notebook-tab-normal.png +0 -0
  167. bootstack/assets/widgets/notebook-underline.png +0 -0
  168. bootstack/assets/widgets/progress-bar-horizontal-default.png +0 -0
  169. bootstack/assets/widgets/progress-bar-horizontal-striped.png +0 -0
  170. bootstack/assets/widgets/progress-bar-vertical-default.png +0 -0
  171. bootstack/assets/widgets/progress-bar-vertical-striped.png +0 -0
  172. bootstack/assets/widgets/progress-trough-horizontal.png +0 -0
  173. bootstack/assets/widgets/progress-trough-vertical.png +0 -0
  174. bootstack/assets/widgets/radio-selected.png +0 -0
  175. bootstack/assets/widgets/radio-unselected.png +0 -0
  176. bootstack/assets/widgets/scrollbar-horizontal-rounded.png +0 -0
  177. bootstack/assets/widgets/scrollbar-vertical-rounded.png +0 -0
  178. bootstack/assets/widgets/separator-horizontal.png +0 -0
  179. bootstack/assets/widgets/separator-vertical.png +0 -0
  180. bootstack/assets/widgets/slider-handle-focus.png +0 -0
  181. bootstack/assets/widgets/slider-handle.png +0 -0
  182. bootstack/assets/widgets/slider-track-horizontal.png +0 -0
  183. bootstack/assets/widgets/slider-track-vertical.png +0 -0
  184. bootstack/assets/widgets/switch-off.png +0 -0
  185. bootstack/assets/widgets/switch-on.png +0 -0
  186. bootstack/assets/widgets/tabs-bar-horizontal.png +0 -0
  187. bootstack/assets/widgets/tabs-bar-vertical.png +0 -0
  188. bootstack/assets/widgets/tabs-pill.png +0 -0
  189. bootstack/cli/__init__.py +124 -0
  190. bootstack/cli/__main__.py +6 -0
  191. bootstack/cli/add.py +439 -0
  192. bootstack/cli/build.py +115 -0
  193. bootstack/cli/config.py +287 -0
  194. bootstack/cli/demo.py +1267 -0
  195. bootstack/cli/doctor.py +195 -0
  196. bootstack/cli/list_cmd.py +71 -0
  197. bootstack/cli/promote.py +120 -0
  198. bootstack/cli/pyinstaller.py +246 -0
  199. bootstack/cli/run.py +99 -0
  200. bootstack/cli/start.py +105 -0
  201. bootstack/cli/templates/__init__.py +861 -0
  202. bootstack/constants.py +325 -0
  203. bootstack/core/__init__.py +34 -0
  204. bootstack/core/capabilities/__init__.py +45 -0
  205. bootstack/core/capabilities/after.py +103 -0
  206. bootstack/core/capabilities/bind.py +154 -0
  207. bootstack/core/capabilities/bindtags.py +112 -0
  208. bootstack/core/capabilities/busy.py +61 -0
  209. bootstack/core/capabilities/clipboard.py +88 -0
  210. bootstack/core/capabilities/focus.py +118 -0
  211. bootstack/core/capabilities/grab.py +65 -0
  212. bootstack/core/capabilities/grid.py +188 -0
  213. bootstack/core/capabilities/localization.py +231 -0
  214. bootstack/core/capabilities/pack.py +119 -0
  215. bootstack/core/capabilities/place.py +92 -0
  216. bootstack/core/capabilities/selection.py +136 -0
  217. bootstack/core/capabilities/signals.py +242 -0
  218. bootstack/core/capabilities/winfo.py +315 -0
  219. bootstack/core/colorutils.py +234 -0
  220. bootstack/core/exceptions.py +95 -0
  221. bootstack/core/images.py +283 -0
  222. bootstack/core/localization/README.md +90 -0
  223. bootstack/core/localization/__init__.py +13 -0
  224. bootstack/core/localization/intl_format.py +580 -0
  225. bootstack/core/localization/msgcat.py +425 -0
  226. bootstack/core/localization/specs.py +143 -0
  227. bootstack/core/mixins/__init__.py +1 -0
  228. bootstack/core/mixins/ttk_state.py +35 -0
  229. bootstack/core/mixins/widget.py +132 -0
  230. bootstack/core/publisher.py +147 -0
  231. bootstack/core/signals/README.md +112 -0
  232. bootstack/core/signals/__init__.py +8 -0
  233. bootstack/core/signals/integration.py +100 -0
  234. bootstack/core/signals/signal.py +317 -0
  235. bootstack/core/signals/types.py +4 -0
  236. bootstack/core/validation/__init__.py +5 -0
  237. bootstack/core/validation/types.py +13 -0
  238. bootstack/core/validation/validation_result.py +17 -0
  239. bootstack/core/validation/validation_rules.py +112 -0
  240. bootstack/core/variables.py +62 -0
  241. bootstack/datasource/README.md +607 -0
  242. bootstack/datasource/__init__.py +51 -0
  243. bootstack/datasource/base.py +474 -0
  244. bootstack/datasource/file_source.py +541 -0
  245. bootstack/datasource/memory_source.py +482 -0
  246. bootstack/datasource/sqlite_source.py +453 -0
  247. bootstack/datasource/types.py +259 -0
  248. bootstack/dialogs/__init__.py +56 -0
  249. bootstack/dialogs/colorchooser.py +674 -0
  250. bootstack/dialogs/colordropper.py +257 -0
  251. bootstack/dialogs/datedialog.py +404 -0
  252. bootstack/dialogs/dialog.py +514 -0
  253. bootstack/dialogs/filterdialog.py +358 -0
  254. bootstack/dialogs/fontdialog.py +339 -0
  255. bootstack/dialogs/formdialog.py +541 -0
  256. bootstack/dialogs/message.py +489 -0
  257. bootstack/dialogs/query.py +561 -0
  258. bootstack/py.typed +1 -0
  259. bootstack/runtime/__init__.py +3 -0
  260. bootstack/runtime/app.py +879 -0
  261. bootstack/runtime/base_window.py +786 -0
  262. bootstack/runtime/events.py +399 -0
  263. bootstack/runtime/menu.py +510 -0
  264. bootstack/runtime/shortcuts.py +423 -0
  265. bootstack/runtime/tk_patch.py +31 -0
  266. bootstack/runtime/toplevel.py +131 -0
  267. bootstack/runtime/utility.py +371 -0
  268. bootstack/runtime/visual_focus.py +228 -0
  269. bootstack/runtime/window_utilities.py +1043 -0
  270. bootstack/style/__init__.py +5498 -0
  271. bootstack/style/bootstyle.py +507 -0
  272. bootstack/style/bootstyle_builder_base.py +752 -0
  273. bootstack/style/bootstyle_builder_mixed.py +93 -0
  274. bootstack/style/bootstyle_builder_tk.py +109 -0
  275. bootstack/style/bootstyle_builder_ttk.py +354 -0
  276. bootstack/style/builders/__init__.py +51 -0
  277. bootstack/style/builders/badge.py +44 -0
  278. bootstack/style/builders/button.py +453 -0
  279. bootstack/style/builders/buttongroup.py +344 -0
  280. bootstack/style/builders/calendar.py +271 -0
  281. bootstack/style/builders/checkbutton.py +95 -0
  282. bootstack/style/builders/combobox.py +112 -0
  283. bootstack/style/builders/contextmenu.py +268 -0
  284. bootstack/style/builders/entry.py +83 -0
  285. bootstack/style/builders/expander.py +171 -0
  286. bootstack/style/builders/field.py +312 -0
  287. bootstack/style/builders/frame.py +27 -0
  288. bootstack/style/builders/label.py +28 -0
  289. bootstack/style/builders/labelframe.py +41 -0
  290. bootstack/style/builders/listview.py +267 -0
  291. bootstack/style/builders/menubar.py +74 -0
  292. bootstack/style/builders/menubutton.py +408 -0
  293. bootstack/style/builders/notebook.py +316 -0
  294. bootstack/style/builders/panedwindow.py +25 -0
  295. bootstack/style/builders/progressbar.py +71 -0
  296. bootstack/style/builders/radiobutton.py +68 -0
  297. bootstack/style/builders/scale.py +66 -0
  298. bootstack/style/builders/scrollbar.py +360 -0
  299. bootstack/style/builders/separator.py +45 -0
  300. bootstack/style/builders/sidenav.py +313 -0
  301. bootstack/style/builders/sizegrip.py +15 -0
  302. bootstack/style/builders/spinbox.py +119 -0
  303. bootstack/style/builders/switch.py +67 -0
  304. bootstack/style/builders/tabitem.py +205 -0
  305. bootstack/style/builders/toolbutton.py +260 -0
  306. bootstack/style/builders/tooltip.py +26 -0
  307. bootstack/style/builders/treeview.py +269 -0
  308. bootstack/style/builders/utils.py +404 -0
  309. bootstack/style/builders_tk/__init__.py +16 -0
  310. bootstack/style/builders_tk/defaults.py +229 -0
  311. bootstack/style/element.py +173 -0
  312. bootstack/style/style.py +499 -0
  313. bootstack/style/theme_provider.py +449 -0
  314. bootstack/style/tk_patch.py +5 -0
  315. bootstack/style/token_maps.py +42 -0
  316. bootstack/style/types.py +32 -0
  317. bootstack/style/typography.py +527 -0
  318. bootstack/style/utility.py +696 -0
  319. bootstack/themes/__init__.py +12 -0
  320. bootstack/themes/standard.py +415 -0
  321. bootstack/themes/user.py +45 -0
  322. bootstack/widgets/__init__.py +53 -0
  323. bootstack/widgets/composites/__init__.py +38 -0
  324. bootstack/widgets/composites/accordion.py +385 -0
  325. bootstack/widgets/composites/appshell.py +445 -0
  326. bootstack/widgets/composites/buttongroup.py +391 -0
  327. bootstack/widgets/composites/calendar.py +914 -0
  328. bootstack/widgets/composites/compositeframe.py +282 -0
  329. bootstack/widgets/composites/contextmenu.py +1754 -0
  330. bootstack/widgets/composites/dateentry.py +261 -0
  331. bootstack/widgets/composites/dropdownbutton.py +190 -0
  332. bootstack/widgets/composites/expander.py +508 -0
  333. bootstack/widgets/composites/field.py +448 -0
  334. bootstack/widgets/composites/floodgauge.py +434 -0
  335. bootstack/widgets/composites/form.py +983 -0
  336. bootstack/widgets/composites/labeledscale.py +209 -0
  337. bootstack/widgets/composites/list/__init__.py +15 -0
  338. bootstack/widgets/composites/list/listitem.py +733 -0
  339. bootstack/widgets/composites/list/listview.py +1507 -0
  340. bootstack/widgets/composites/menubar.py +303 -0
  341. bootstack/widgets/composites/meter.py +882 -0
  342. bootstack/widgets/composites/numericentry.py +183 -0
  343. bootstack/widgets/composites/pagestack.py +330 -0
  344. bootstack/widgets/composites/passwordentry.py +149 -0
  345. bootstack/widgets/composites/pathentry.py +223 -0
  346. bootstack/widgets/composites/radiogroup.py +466 -0
  347. bootstack/widgets/composites/scrolledtext.py +388 -0
  348. bootstack/widgets/composites/scrolledtext.pyi +186 -0
  349. bootstack/widgets/composites/scrollview.py +675 -0
  350. bootstack/widgets/composites/selectbox.py +544 -0
  351. bootstack/widgets/composites/sidenav/__init__.py +24 -0
  352. bootstack/widgets/composites/sidenav/group.py +485 -0
  353. bootstack/widgets/composites/sidenav/header.py +83 -0
  354. bootstack/widgets/composites/sidenav/item.py +413 -0
  355. bootstack/widgets/composites/sidenav/separator.py +51 -0
  356. bootstack/widgets/composites/sidenav/view.py +919 -0
  357. bootstack/widgets/composites/spinnerentry.py +232 -0
  358. bootstack/widgets/composites/tableview/__init__.py +5 -0
  359. bootstack/widgets/composites/tableview/tableview.py +2254 -0
  360. bootstack/widgets/composites/tableview/types.py +169 -0
  361. bootstack/widgets/composites/tabs/__init__.py +6 -0
  362. bootstack/widgets/composites/tabs/tabitem.py +372 -0
  363. bootstack/widgets/composites/tabs/tabs.py +478 -0
  364. bootstack/widgets/composites/tabs/tabview.py +352 -0
  365. bootstack/widgets/composites/textentry.py +90 -0
  366. bootstack/widgets/composites/timeentry.py +189 -0
  367. bootstack/widgets/composites/toast.py +364 -0
  368. bootstack/widgets/composites/togglegroup.py +382 -0
  369. bootstack/widgets/composites/toolbar.py +393 -0
  370. bootstack/widgets/composites/tooltip.py +404 -0
  371. bootstack/widgets/internal/__init__.py +0 -0
  372. bootstack/widgets/internal/wrapper_base.py +304 -0
  373. bootstack/widgets/mixins/__init__.py +25 -0
  374. bootstack/widgets/mixins/configure_mixin.py +186 -0
  375. bootstack/widgets/mixins/entry_mixin.py +70 -0
  376. bootstack/widgets/mixins/font_mixin.py +346 -0
  377. bootstack/widgets/mixins/icon_mixin.py +38 -0
  378. bootstack/widgets/mixins/localization_mixin.py +255 -0
  379. bootstack/widgets/mixins/signal_mixin.py +272 -0
  380. bootstack/widgets/mixins/validation_mixin.py +204 -0
  381. bootstack/widgets/parts/__init__.py +11 -0
  382. bootstack/widgets/parts/numberentry_part.py +345 -0
  383. bootstack/widgets/parts/spinnerentry_part.py +394 -0
  384. bootstack/widgets/parts/textentry_part.py +344 -0
  385. bootstack/widgets/primitives/__init__.py +55 -0
  386. bootstack/widgets/primitives/badge.py +44 -0
  387. bootstack/widgets/primitives/button.py +89 -0
  388. bootstack/widgets/primitives/card.py +66 -0
  389. bootstack/widgets/primitives/checkbutton.py +124 -0
  390. bootstack/widgets/primitives/checktoggle.py +53 -0
  391. bootstack/widgets/primitives/combobox.py +165 -0
  392. bootstack/widgets/primitives/entry.py +98 -0
  393. bootstack/widgets/primitives/frame.py +206 -0
  394. bootstack/widgets/primitives/gridframe.py +479 -0
  395. bootstack/widgets/primitives/label.py +95 -0
  396. bootstack/widgets/primitives/labelframe.py +63 -0
  397. bootstack/widgets/primitives/menubutton.py +118 -0
  398. bootstack/widgets/primitives/notebook.py +551 -0
  399. bootstack/widgets/primitives/optionmenu.py +248 -0
  400. bootstack/widgets/primitives/packframe.py +228 -0
  401. bootstack/widgets/primitives/panedwindow.py +58 -0
  402. bootstack/widgets/primitives/progressbar.py +95 -0
  403. bootstack/widgets/primitives/radiobutton.py +115 -0
  404. bootstack/widgets/primitives/radiotoggle.py +50 -0
  405. bootstack/widgets/primitives/scale.py +85 -0
  406. bootstack/widgets/primitives/scrollbar.py +56 -0
  407. bootstack/widgets/primitives/separator.py +56 -0
  408. bootstack/widgets/primitives/sizegrip.py +47 -0
  409. bootstack/widgets/primitives/spinbox.py +91 -0
  410. bootstack/widgets/primitives/switch.py +41 -0
  411. bootstack/widgets/primitives/treeview.py +77 -0
  412. bootstack/widgets/types.py +20 -0
  413. bootstack-0.1.0a1.dist-info/METADATA +196 -0
  414. bootstack-0.1.0a1.dist-info/RECORD +419 -0
  415. bootstack-0.1.0a1.dist-info/WHEEL +5 -0
  416. bootstack-0.1.0a1.dist-info/entry_points.txt +2 -0
  417. bootstack-0.1.0a1.dist-info/licenses/LICENSE +22 -0
  418. bootstack-0.1.0a1.dist-info/licenses/NOTICE +10 -0
  419. bootstack-0.1.0a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,425 @@
1
+ """Localization bridge for bootstack.
2
+
3
+ This module integrates Python gettext catalogs (compiled with Babel) with
4
+ Tcl/Tk's native msgcat. It preserves bootstack's existing msgcat-facing
5
+ APIs while enabling gettext-powered translations and runtime locale switching.
6
+
7
+ Key behaviors:
8
+ - Prefer gettext (.mo) translations when available, with Python '%' formatting.
9
+ - Fall back to Tcl msgcat for formatting (supports legacy placeholders like
10
+ '%1$s') and for untranslated strings.
11
+ - Keep runtime overrides set via set/set_many in sync with msgcat and
12
+ consult them first during translation.
13
+ - Auto-discover a 'locales/' directory for catalogs unless overridden.
14
+
15
+ Public API (unchanged signatures):
16
+ - MessageCatalog.translate(src, *fmtargs) -> str
17
+ - MessageCatalog.locale(newlocale: Optional[str] = None) -> str
18
+ - MessageCatalog.preferences() -> list[str]
19
+ - MessageCatalog.load(dirname) -> int
20
+ - MessageCatalog.set(locale, src, translated=None) -> None
21
+ - MessageCatalog.set_many(locale, *args) -> int
22
+ - MessageCatalog.max(*src) -> int
23
+ - MessageCatalog.init(root=None, locales_dir=None, domain='messages',
24
+ default_locale='en', strip_ampersands=True) -> None
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import gettext
30
+ from os import PathLike
31
+ from pathlib import Path
32
+ from tkinter import Tk
33
+ from typing import Any, Optional, Union
34
+
35
+
36
+
37
+ class MessageCatalog:
38
+ """Facade that unifies gettext and Tcl msgcat for bootstack.
39
+
40
+ Manages the active locale, a gettext translator, and runtime overrides.
41
+ Translation prefers gettext when available and falls back to Tcl msgcat
42
+ for both translation and printf-style formatting.
43
+ """
44
+ # --- internal state for gettext bridge ---
45
+ _inited: bool = False
46
+ _locales_dir: Path | None = None
47
+ _domain: str = "messages"
48
+ _locale: str = "en"
49
+ _gt: gettext.NullTranslations | None = None
50
+ _strip_amp: bool = True
51
+ _emit_event: bool = True
52
+ _event_name: str = "<<LocaleChanged>>"
53
+
54
+ # runtime overrides (compatible with your existing set/set_many usage)
55
+ _overrides: dict[str, dict[str, str]] = {}
56
+
57
+ # -------------- setup -------------------------------------------------
58
+
59
+ @staticmethod
60
+ def init(
61
+ root: Tk | None = None,
62
+ locales_dir: Union[str, Path, None] = None,
63
+ domain: str = "messages",
64
+ default_locale: str = "en",
65
+ strip_ampersands: bool = True,
66
+ emit_virtual_event: bool = True,
67
+ virtual_event_name: str = "<<LocaleChanged>>",
68
+ ) -> None:
69
+ """Initialize the translation system.
70
+
71
+ Args:
72
+ root: Optional Tk root to ensure msgcat is available.
73
+ locales_dir: Base directory containing gettext catalogs
74
+ (`<lang>/LC_MESSAGES/<domain>.mo`). If `None`, the
75
+ directory is auto-discovered.
76
+ domain: Gettext domain name.
77
+ default_locale: Locale to activate after initialization.
78
+ strip_ampersands: If true, remove mnemonic `&` markers.
79
+ emit_virtual_event: If true, generate a Tk virtual event after
80
+ locale changes so widgets can refresh themselves.
81
+ virtual_event_name: The virtual event name to emit when the
82
+ locale changes (default `"<<LocaleChanged>>"`).
83
+ """
84
+ # Ensure a Tk exists so msgcat calls work even if root is omitted
85
+ from bootstack.runtime.app import get_default_root
86
+ _ = root or get_default_root()
87
+
88
+ MessageCatalog._domain = domain
89
+ MessageCatalog._locales_dir = Path(locales_dir) if locales_dir else MessageCatalog._discover_locales_dir()
90
+ MessageCatalog._strip_amp = strip_ampersands
91
+ MessageCatalog._emit_event = bool(emit_virtual_event)
92
+ MessageCatalog._event_name = str(virtual_event_name or "<<LocaleChanged>>")
93
+ MessageCatalog._install_gettext(default_locale)
94
+ MessageCatalog._sync_msgcat_locale(default_locale)
95
+ MessageCatalog._inited = True
96
+
97
+ # -------------- core helpers -----------------------------------------
98
+
99
+ @staticmethod
100
+ def _install_gettext(lang: str) -> None:
101
+ """Install gettext catalogs for the requested language.
102
+
103
+ Prefers an exact match (e.g. `de_DE`) and falls back to base
104
+ language (`de`) if available.
105
+
106
+ Args:
107
+ lang: Requested locale code.
108
+ """
109
+ MessageCatalog._locale = MessageCatalog._normalize_lang(lang)
110
+ # Try exact match, then base language (e.g., de_DE -> de)
111
+ langs = [MessageCatalog._locale]
112
+ if "_" in MessageCatalog._locale:
113
+ langs.append(MessageCatalog._locale.split("_", 1)[0])
114
+ try:
115
+ MessageCatalog._gt = gettext.translation(
116
+ MessageCatalog._domain,
117
+ localedir=str(MessageCatalog._locales_dir or "locales"),
118
+ languages=langs,
119
+ fallback=True,
120
+ )
121
+ except Exception:
122
+ # fallback=True already returns a NullTranslations if not found
123
+ MessageCatalog._gt = gettext.NullTranslations()
124
+
125
+ @staticmethod
126
+ def _discover_locales_dir() -> Path:
127
+ """Return a plausible locales directory for this installation.
128
+
129
+ Priority order:
130
+ 1) `TTKBOOTSTRAP_LOCALES` environment variable
131
+ 2) Package asset `src/bootstack/assets/locales`
132
+ 3) Module-local `bootstack/localization/locales`
133
+ 4) Package-local `src/bootstack/locales`
134
+ 5) Repository root `locales/`
135
+ 6) Current working directory `./locales`
136
+ """
137
+ import os
138
+ env = os.environ.get("TTKBOOTSTRAP_LOCALES")
139
+ if env:
140
+ p = Path(env)
141
+ if p.exists():
142
+ return p
143
+ here = Path(__file__).resolve()
144
+ candidates = [
145
+ here.parents[2] / "assets" / "locales", # package assets: src/bootstack/assets/locales
146
+ here.parent / "locales", # module-local: bootstack/localization/locales
147
+ here.parents[1] / "locales", # package-local: src/bootstack/locales
148
+ here.parents[3] / "locales", # repo root: .../bootstack/locales
149
+ Path.cwd() / "locales", # current working dir
150
+ ]
151
+ for c in candidates:
152
+ try:
153
+ if c.exists() and c.is_dir() and any(
154
+ (c / d.name / "LC_MESSAGES").exists() for d in c.iterdir() if d.is_dir()):
155
+ return c
156
+ except Exception:
157
+ pass
158
+ return here.parents[3] / "locales"
159
+
160
+ @staticmethod
161
+ def _sync_msgcat_locale(lang: str) -> None:
162
+ """Set Tcl msgcat locale to match the Python-side locale.
163
+
164
+ Args:
165
+ lang: Locale code (e.g. `en`, `de_DE`).
166
+ """
167
+ from bootstack.runtime.app import get_default_root
168
+ root = get_default_root()
169
+
170
+ tcl_lang = MessageCatalog._to_msgcat_locale(lang)
171
+ try:
172
+ root.tk.call("::msgcat::mclocale", tcl_lang)
173
+ except Exception:
174
+ pass
175
+
176
+ @staticmethod
177
+ def _normalize_lang(code: str) -> str:
178
+ """Normalize a locale code to gettext style (`ll` or `ll_RR`).
179
+
180
+ Args:
181
+ code: Input locale code (e.g. `de-de`, `pt_br`).
182
+
183
+ Returns:
184
+ Normalized locale code for gettext use.
185
+ """
186
+ if not code:
187
+ return "en"
188
+ parts = code.replace("-", "_").split("_")
189
+ return parts[0].lower() if len(parts) == 1 else f"{parts[0].lower()}_{parts[1].upper()}"
190
+
191
+ @staticmethod
192
+ def _to_msgcat_locale(code: str) -> str:
193
+ """Normalize a locale code to msgcat style (`ll` or `ll_rr`).
194
+
195
+ Args:
196
+ code: Input locale code (e.g. `de-DE`, `pt_BR`).
197
+
198
+ Returns:
199
+ Lowercased region code used by msgcat.
200
+ """
201
+ parts = code.replace("-", "_").split("_")
202
+ return parts[0].lower() if len(parts) == 1 else f"{parts[0].lower()}_{parts[1].lower()}"
203
+
204
+ @staticmethod
205
+ def __join(*args: Any) -> str:
206
+ """Join format args for Tcl msgcat formatting.
207
+
208
+ Args:
209
+ *args: Positional values to forward to Tcl 'format'.
210
+
211
+ Returns:
212
+ String of brace-wrapped arguments joined by spaces.
213
+ """
214
+ new_args = []
215
+ for arg in args:
216
+ if isinstance(arg, str):
217
+ stripped = str(arg).strip('"')
218
+ new_args.append("{%s}" % stripped)
219
+ else:
220
+ new_args.append(str(arg))
221
+ return " ".join(new_args)
222
+
223
+ @staticmethod
224
+ def _strip_ampersands(s: str) -> str:
225
+ """Remove mnemonic ampersands from text.
226
+
227
+ Converts single '&' markers to nothing and turns '&&' into a
228
+ literal '&'. Useful for rendering toolkit-agnostic text.
229
+
230
+ Args:
231
+ s: Input string.
232
+
233
+ Returns:
234
+ Cleaned string with mnemonic indicators removed.
235
+ """
236
+ if not s or "&" not in s:
237
+ return s
238
+ out = []
239
+ i = 0
240
+ while i < len(s):
241
+ if s[i] == "&":
242
+ if i + 1 < len(s) and s[i + 1] == "&":
243
+ out.append("&")
244
+ i += 2
245
+ else:
246
+ i += 1 # skip mnemonic marker
247
+ else:
248
+ out.append(s[i])
249
+ i += 1
250
+ return "".join(out)
251
+
252
+ # -------------- public API (unchanged signatures) ---------------------
253
+
254
+ @staticmethod
255
+ def translate(src: str, *fmtargs: Any) -> str:
256
+ """Translate a message id according to the active locale.
257
+
258
+ Strategy:
259
+ 1) If a runtime override exists and formatting args are provided,
260
+ use Tcl msgcat so legacy '%1$s' placeholders work.
261
+ 2) Otherwise use overrides (with Python '%' formatting when args
262
+ are given).
263
+ 3) Next, try gettext; if Python '%' formatting fails, fall back
264
+ to Tcl formatting.
265
+ 4) Finally, fall back entirely to Tcl msgcat.
266
+
267
+ Args:
268
+ src: Message id to translate.
269
+ *fmtargs: Positional formatting values.
270
+
271
+ Returns:
272
+ Localized and formatted string.
273
+ """
274
+ from bootstack.runtime.app import get_default_root
275
+ root = get_default_root()
276
+
277
+ # Fast-path: if an override exists for this locale and formatting args were
278
+ # provided, prefer Tcl msgcat formatting so positional specifiers like %1$s
279
+ # work as in legacy behavior.
280
+ cur = MessageCatalog._locale
281
+ if fmtargs and cur in MessageCatalog._overrides and src in MessageCatalog._overrides[cur]:
282
+ command = f"::msgcat::mc {{{src}}} {MessageCatalog.__join(*fmtargs)}"
283
+ out = root.tk.eval(command)
284
+ return MessageCatalog._strip_ampersands(out) if MessageCatalog._strip_amp else out
285
+
286
+ # 1) overrides for current locale win first
287
+ cur = MessageCatalog._locale
288
+ if cur in MessageCatalog._overrides and src in MessageCatalog._overrides[cur]:
289
+ s = MessageCatalog._overrides[cur][src]
290
+ if MessageCatalog._strip_amp:
291
+ s = MessageCatalog._strip_ampersands(s)
292
+ # try Python formatting if args were passed; ignore on failure
293
+ if fmtargs:
294
+ try:
295
+ s = s % fmtargs
296
+ return s
297
+ except Exception:
298
+ pass
299
+ # no args or failed → return override as-is
300
+ return s
301
+
302
+ # 2) gettext translation (if inited)
303
+ if MessageCatalog._inited and MessageCatalog._gt is not None:
304
+ try:
305
+ s = MessageCatalog._gt.gettext(src)
306
+ # If gettext returns src unchanged, and we have no fmtargs,
307
+ # we'll consider falling back to msgcat for consistency.
308
+ if s != src:
309
+ if MessageCatalog._strip_amp:
310
+ s = MessageCatalog._strip_ampersands(s)
311
+ if fmtargs:
312
+ try:
313
+ return s % fmtargs
314
+ except Exception:
315
+ # fall through to Tcl formatting
316
+ pass
317
+ else:
318
+ return s
319
+ except Exception:
320
+ # ignore and fall back to msgcat
321
+ pass
322
+
323
+ # 3) Tcl msgcat fallback (preserves your current behavior exactly)
324
+ command = f"::msgcat::mc {{{src}}}"
325
+ if fmtargs:
326
+ command = f"{command} {MessageCatalog.__join(*fmtargs)}"
327
+ out = root.tk.eval(command)
328
+ return MessageCatalog._strip_ampersands(out) if MessageCatalog._strip_amp else out
329
+
330
+ @staticmethod
331
+ def locale(new_locale: Optional[str] = None) -> str:
332
+ """Get or set the current locale.
333
+
334
+ Args:
335
+ new_locale: If provided, switch both gettext and msgcat locales.
336
+
337
+ Returns:
338
+ The active normalized locale code (or Tcl's current code when
339
+ queried).
340
+ """
341
+ from bootstack.runtime.app import get_default_root
342
+ root = get_default_root()
343
+ if new_locale:
344
+ # switch gettext + msgcat
345
+ MessageCatalog._install_gettext(new_locale)
346
+ MessageCatalog._sync_msgcat_locale(new_locale)
347
+ # notify listeners (optional)
348
+ try:
349
+ if MessageCatalog._emit_event:
350
+ root.event_generate(MessageCatalog._event_name, data={"locale": new_locale}, when="tail")
351
+ except Exception:
352
+ pass
353
+ return MessageCatalog._locale
354
+ # query Tcl msgcat current locale
355
+ return root.tk.eval("::msgcat::mclocale")
356
+
357
+ @staticmethod
358
+ def preferences() -> list[str]:
359
+ """Return Tcl msgcat locale preferences (ordered)."""
360
+ from bootstack.runtime.app import get_default_root
361
+ root = get_default_root()
362
+ items = root.tk.eval("::msgcat::mcpreferences").split(" ")
363
+ return items[0:-1] if len(items) > 0 else []
364
+
365
+ @staticmethod
366
+ def load(dirname: Union[str, PathLike[str]]) -> int:
367
+ """Load Tcl .msg catalogs from a directory.
368
+
369
+ Args:
370
+ dirname: Directory containing msgcat .msg files.
371
+
372
+ Returns:
373
+ Number of files loaded, as reported by Tcl.
374
+ """
375
+ msgs = Path(dirname).as_posix()
376
+ from bootstack.runtime.app import get_default_root
377
+ root = get_default_root()
378
+ return int(root.tk.eval(f"::msgcat::mcload [list {msgs}]"))
379
+
380
+ @staticmethod
381
+ def set(locale: str, src: str, translated: Optional[str] = None) -> None:
382
+ """Define a single runtime translation and mirror it into msgcat.
383
+
384
+ Args:
385
+ locale: Target locale code.
386
+ src: Message id.
387
+ translated: Localized string.
388
+ """
389
+ loc = MessageCatalog._normalize_lang(locale)
390
+ MessageCatalog._overrides.setdefault(loc, {})[src] = translated or ""
391
+ from bootstack.runtime.app import get_default_root
392
+ root = get_default_root()
393
+ root.tk.eval("::msgcat::mcset %s {%s} {%s}" % (MessageCatalog._to_msgcat_locale(locale), src, translated or ""))
394
+
395
+ @staticmethod
396
+ def set_many(locale: str, *args: str) -> int:
397
+ """Bulk-define runtime translations and mirror into msgcat.
398
+
399
+ Args:
400
+ locale: Target locale code.
401
+ *args: Alternating message ids and translations.
402
+
403
+ Returns:
404
+ Number of messages set, as reported by Tcl.
405
+ """
406
+ loc = MessageCatalog._normalize_lang(locale)
407
+ # update Python overrides
408
+ pairs = list(args)
409
+ for i in range(0, len(pairs), 2):
410
+ k = pairs[i]
411
+ v = pairs[i + 1] if i + 1 < len(pairs) else ""
412
+ MessageCatalog._overrides.setdefault(loc, {})[k] = v
413
+
414
+ # update Tcl msgcat
415
+ from bootstack.runtime.app import get_default_root
416
+ root = get_default_root()
417
+ messages = " ".join(["{%s}" % x for x in args])
418
+ out = f"::msgcat::mcmset {MessageCatalog._to_msgcat_locale(locale)} {{{messages}}}"
419
+ return int(root.tk.eval(out))
420
+
421
+ @staticmethod
422
+ def max(*src: str) -> int:
423
+ from bootstack.runtime.app import get_default_root
424
+ root = get_default_root()
425
+ return int(root.tk.eval(f"::msgcat::mcmax {' '.join(src)}"))
@@ -0,0 +1,143 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Mapping, Optional, Tuple, Union
5
+
6
+ from .msgcat import MessageCatalog
7
+ from .intl_format import IntlFormatter, FormatSpec
8
+
9
+
10
+ class LocalizedSpec:
11
+ """Base class for all localization specifications.
12
+
13
+ Localization specs define how text or values should be localized when
14
+ the locale changes. Subclasses implement specific localization strategies.
15
+
16
+ Attributes:
17
+ enabled: Whether this spec is currently enabled for localization.
18
+ """
19
+ enabled: bool = True
20
+
21
+ def resolve(self, locale: str) -> str:
22
+ """Resolve this spec to a localized string for the given locale.
23
+
24
+ Args:
25
+ locale: The locale code to use for resolution.
26
+
27
+ Returns:
28
+ The localized string value.
29
+
30
+ Raises:
31
+ NotImplementedError: Subclasses must implement this method.
32
+ """
33
+ raise NotImplementedError("Subclasses must implement resolve().")
34
+
35
+
36
+ @dataclass
37
+ class LocalizedTextSpec(LocalizedSpec):
38
+ """Localization spec for translatable text via MessageCatalog.
39
+
40
+ This spec translates text using gettext catalogs and MessageCatalog,
41
+ with optional format arguments for interpolation.
42
+
43
+ Attributes:
44
+ key: The message ID or semantic key for translation lookup.
45
+ fmtargs: Tuple of formatting arguments for MessageCatalog interpolation.
46
+ original: Fallback literal text if translation fails.
47
+ enabled: Whether this spec is currently enabled for localization.
48
+ """
49
+ key: str
50
+ fmtargs: Tuple[Any, ...] = ()
51
+ original: Optional[str] = None
52
+
53
+ enabled: bool = True
54
+
55
+ def resolve(self, locale: str) -> str:
56
+ """Resolve to translated text using MessageCatalog.
57
+
58
+ Args:
59
+ locale: The locale code (unused, MessageCatalog uses its own state).
60
+
61
+ Returns:
62
+ The translated string, or the original/key as fallback.
63
+ """
64
+ try:
65
+ return MessageCatalog.translate(self.key, *self.fmtargs)
66
+ except Exception:
67
+ return self.original or self.key
68
+
69
+
70
+ @dataclass
71
+ class LocalizedValueSpec(LocalizedSpec):
72
+ """Localization spec for locale-aware value formatting.
73
+
74
+ This spec formats numbers, dates, times, and currency values using
75
+ IntlFormatter according to the current locale.
76
+
77
+ Attributes:
78
+ value: The value to format (number, date, datetime, time, etc.).
79
+ format_spec: IntlFormatter spec such as "currency", "decimal", "percent",
80
+ or a dict with formatting options.
81
+ enabled: Whether this spec is currently enabled for localization.
82
+ """
83
+ value: Any
84
+ format_spec: FormatSpec
85
+
86
+ enabled: bool = True
87
+
88
+ def resolve(self, locale: str) -> str:
89
+ """Resolve to a locale-formatted string using IntlFormatter.
90
+
91
+ Args:
92
+ locale: The locale code (unused, uses MessageCatalog's current locale).
93
+
94
+ Returns:
95
+ The formatted string, or str(value) as fallback.
96
+ """
97
+ try:
98
+ current_locale = MessageCatalog.locale()
99
+ fmt = IntlFormatter(locale=current_locale)
100
+ return fmt.format(self.value, self.format_spec)
101
+ except Exception:
102
+ return str(self.value)
103
+
104
+
105
+ def L(key: str, *fmtargs: Any) -> LocalizedTextSpec:
106
+ """Create a LocalizedTextSpec for translatable text.
107
+
108
+ Shorthand constructor that creates a text localization spec with the
109
+ translation key and optional format arguments.
110
+
111
+ Args:
112
+ key: The message ID or semantic key for translation lookup.
113
+ *fmtargs: Optional formatting arguments for string interpolation.
114
+
115
+ Returns:
116
+ A LocalizedTextSpec instance.
117
+
118
+ Examples:
119
+ >>> spec = L("greeting", "World")
120
+ >>> # Will translate "greeting" with "World" as format argument
121
+ """
122
+ return LocalizedTextSpec(key=key, fmtargs=fmtargs, original=key)
123
+
124
+
125
+ def LV(value: Any, format_spec: FormatSpec) -> LocalizedValueSpec:
126
+ """Create a LocalizedValueSpec for locale-aware value formatting.
127
+
128
+ Shorthand constructor that creates a value formatting spec for numbers,
129
+ dates, times, and currency values.
130
+
131
+ Args:
132
+ value: The value to format (number, date, datetime, time, etc.).
133
+ format_spec: IntlFormatter spec like "currency", "decimal", "percent",
134
+ or a dict with detailed formatting options.
135
+
136
+ Returns:
137
+ A LocalizedValueSpec instance.
138
+
139
+ Examples:
140
+ >>> spec = LV(1234.56, "currency")
141
+ >>> # Will format as currency in the current locale
142
+ """
143
+ return LocalizedValueSpec(value=value, format_spec=format_spec)
@@ -0,0 +1 @@
1
+ """Widget capability and TTK state mixins for bootstack widgets."""
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Iterable, Any
4
+
5
+
6
+ class TtkStateMixin:
7
+ """ttk-only widget helpers.
8
+
9
+ This mixin contains methods that exist on ttk widgets but not on classic Tk widgets
10
+ (e.g., `tk.Text`, `tk.Canvas`).
11
+ """
12
+
13
+ def state(self, statespec: str | Iterable[str] | None = None) -> Any:
14
+ """Get or modify the ttk state of the widget.
15
+
16
+ Args:
17
+ statespec: State specification (e.g. ("disabled",) or ("!disabled",)).
18
+ If None, returns the current state.
19
+
20
+ Returns:
21
+ The current state (getter) or an implementation-dependent result.
22
+ """
23
+ return super().state(statespec) # type: ignore[misc]
24
+
25
+ def instate(self, statespec: str | Iterable[str], callback: Any = None) -> bool:
26
+ """Test the ttk state of the widget.
27
+
28
+ Args:
29
+ statespec: State specification to test.
30
+ callback: Optional callable invoked if the test succeeds.
31
+
32
+ Returns:
33
+ True if the widget matches the state spec; otherwise False.
34
+ """
35
+ return super().instate(statespec, callback) # type: ignore[misc]