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,511 @@
1
+ from __future__ import annotations
2
+
3
+ from tkinter import StringVar
4
+ from typing import Any, Callable, Literal, TYPE_CHECKING
5
+
6
+ from typing_extensions import TypedDict, Unpack
7
+
8
+ from bootstack.widgets._impl.primitives.radiobutton import RadioButton
9
+ from bootstack.widgets._impl.primitives.label import Label
10
+ from bootstack.widgets._impl.mixins.configure_mixin import configure_delegate
11
+ from bootstack.widgets._impl.primitives import Frame
12
+ from bootstack.widgets.types import Master, AccentToken, Orient, SurfaceToken, WidgetState
13
+
14
+ if TYPE_CHECKING:
15
+ from bootstack.signals import Signal
16
+ import tkinter as tk
17
+
18
+
19
+ class RadioGroupKwargs(TypedDict, total=False):
20
+ variable: Any
21
+ signal: Signal[Any]
22
+ value: str
23
+ orient: Orient
24
+ accent: AccentToken | str
25
+ text: str
26
+ labelanchor: Literal['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw']
27
+ state: WidgetState
28
+ show_indicator: bool
29
+ show_border: bool
30
+ surface: SurfaceToken | str
31
+ localize: bool | Literal['auto']
32
+ style_options: dict[str, Any]
33
+ # Frame options
34
+ padding: Any
35
+ width: int
36
+ height: int
37
+
38
+
39
+ class RadioGroup(Frame):
40
+ """A group of radio buttons with automatic state tracking and optional label.
41
+
42
+ The RadioGroup widget provides a convenient way to create groups of radio
43
+ buttons with automatic state management. It supports both horizontal and
44
+ vertical orientations, and can display an optional label in various positions.
45
+ """
46
+
47
+ def __init__(self, master: Master = None, **kwargs: Unpack[RadioGroupKwargs]):
48
+ """Initialize the RadioGroup.
49
+
50
+ Args:
51
+ master: Parent widget. If None, uses the default root window.
52
+
53
+ Other Parameters:
54
+ orient: Layout orientation - 'horizontal' (default) or 'vertical'.
55
+ accent: Accent token for styling (e.g., 'primary', 'success', 'danger').
56
+ Defaults to 'primary'.
57
+ text: Optional label text to display.
58
+ labelanchor: Label position - 'n' (top, default), 's' (bottom),
59
+ 'e' (right), 'w' (left), or combinations like 'nw', 'ne', etc.
60
+ variable: Optional tk.StringVar for controlling the selected value.
61
+ See [tkinter Variables](https://docs.python.org/3/library/tkinter.html#tkinter-variables).
62
+ signal: Optional Signal instance for reactive programming.
63
+ value: Initial selected value.
64
+ state: Initial state for all buttons - 'normal' (default) or 'disabled'.
65
+ show_indicator: Whether child buttons show the standard radio indicator.
66
+ Defaults to True. Set to False for icon-only or custom-styled groups.
67
+ Can be overridden per-button in `add()`.
68
+ show_border: If True, draws a border around the group.
69
+ surface: Optional surface token; otherwise inherited.
70
+ style_options: Additional style options passed to child buttons.
71
+ padding: Frame padding. Defaults to 1.
72
+ width: Requested width in pixels.
73
+ height: Requested height in pixels.
74
+ """
75
+ # Extract RadioGroup-specific options before super().__init__
76
+ self._orientation = kwargs.pop('orient', 'horizontal')
77
+ accent_value = kwargs.pop('accent', None) or 'primary'
78
+ self._labeltext = kwargs.pop('text', None)
79
+ self._labelanchor = kwargs.pop('labelanchor', 'n')
80
+ self._state = kwargs.pop('state', 'normal')
81
+ # Localization mode for the group label (items are localized by the
82
+ # public wrapper per-button). None means inherit the app default.
83
+ self._localize = kwargs.pop('localize', None)
84
+ self._show_indicator = kwargs.pop('show_indicator', True)
85
+
86
+ # Handle signal/variable/value
87
+ initial_value = kwargs.pop('value', None)
88
+ style_options = kwargs.pop('style_options', {})
89
+
90
+ # Initialize internal state
91
+ self._buttons: dict[str, RadioButton] = {}
92
+ self._label: Label | None = None
93
+ self._button_container: Frame | None = None
94
+
95
+ # Extract signal/variable before Frame init
96
+ signal_value = kwargs.pop('signal', None)
97
+ variable_value = kwargs.pop('variable', None)
98
+
99
+ # Handle padding default
100
+ if 'padding' not in kwargs:
101
+ kwargs['padding'] = 1
102
+
103
+ # Call super().__init__() - just Frame now
104
+ super().__init__(master, style_options=style_options, **kwargs)
105
+
106
+ # TTKWrapperBase.__init__wrapper overwrites self._accent (it pops accent
107
+ # from kwargs, but accent was already popped above, so it sees None).
108
+ # Restore the correct value after super().__init__ completes.
109
+ self._accent = accent_value
110
+
111
+ # Handle variable/signal setup manually
112
+ if signal_value is not None:
113
+ # Signal provided - extract its variable
114
+ self._signal = signal_value
115
+ self._variable = signal_value.var
116
+ elif variable_value is not None:
117
+ # Variable provided - wrap in Signal
118
+ from bootstack.signals import Signal
119
+ self._variable = variable_value
120
+ self._signal = Signal.from_variable(variable_value)
121
+ # Set initial value if provided
122
+ if initial_value is not None:
123
+ self._variable.set(initial_value)
124
+ else:
125
+ # Neither provided - create internal variable
126
+ from bootstack.signals import Signal
127
+ internal_var = StringVar(value=initial_value or '')
128
+ self._variable = internal_var
129
+ self._signal = Signal.from_variable(internal_var)
130
+
131
+ # Build the UI
132
+ self._build_ui()
133
+
134
+ def _label_localize_kwargs(self) -> dict:
135
+ """Forward the group's localize mode to the title Label when set."""
136
+ return {} if self._localize is None else {"localize": self._localize}
137
+
138
+ def _build_ui(self):
139
+ """Construct the widget layout."""
140
+ # Create button container
141
+ self._button_container = Frame(self)
142
+
143
+ # Create label if text provided
144
+ if self._labeltext:
145
+ self._label = Label(self, text=self._labeltext, **self._label_localize_kwargs())
146
+
147
+ # Position based on labelanchor
148
+ self._update_layout()
149
+
150
+ def _update_layout(self):
151
+ """Update grid layout based on labelanchor."""
152
+ # Clear existing layout
153
+ for widget in self.winfo_children():
154
+ widget.grid_forget()
155
+
156
+ # Determine anchor direction from labelanchor
157
+ # n/s = top/bottom, e/w = right/left
158
+ anchor = self._labelanchor.lower()
159
+
160
+ # Normalize compound anchors to primary direction
161
+ # Priority: n > s > w > e (vertical placement preferred over horizontal)
162
+ if 'n' in anchor:
163
+ primary = 'n'
164
+ elif 's' in anchor:
165
+ primary = 's'
166
+ elif 'w' in anchor:
167
+ primary = 'w'
168
+ elif 'e' in anchor:
169
+ primary = 'e'
170
+ else:
171
+ # Default to north
172
+ primary = 'n'
173
+
174
+ # Layout based on primary anchor
175
+ if primary == 'n':
176
+ # Label on top
177
+ if self._label:
178
+ self._label.grid(row=0, column=0, sticky='w', pady=(0, 4))
179
+ self._button_container.grid(row=1, column=0, sticky='ew')
180
+ self.grid_columnconfigure(0, weight=1)
181
+ elif primary == 's':
182
+ # Label on bottom
183
+ self._button_container.grid(row=0, column=0, sticky='ew')
184
+ if self._label:
185
+ self._label.grid(row=1, column=0, sticky='w', pady=(4, 0))
186
+ self.grid_columnconfigure(0, weight=1)
187
+ elif primary == 'w':
188
+ # Label on left
189
+ if self._label:
190
+ self._label.grid(row=0, column=0, sticky='w', padx=(0, 8))
191
+ self._button_container.grid(row=0, column=1, sticky='ew')
192
+ self.grid_columnconfigure(1, weight=1)
193
+ else: # 'e'
194
+ # Label on right
195
+ self._button_container.grid(row=0, column=0, sticky='ew')
196
+ if self._label:
197
+ self._label.grid(row=0, column=1, sticky='w', padx=(8, 0))
198
+ self.grid_columnconfigure(0, weight=1)
199
+
200
+ @property
201
+ def variable(self) -> 'tk.Variable':
202
+ """Get the underlying tk.Variable.
203
+
204
+ See [tkinter Variables](https://docs.python.org/3/library/tkinter.html#tkinter-variables).
205
+ """
206
+ return self._variable
207
+
208
+ @variable.setter
209
+ def variable(self, value: 'tk.Variable') -> None:
210
+ """Set the variable."""
211
+ from bootstack.signals import Signal
212
+ self._variable = value
213
+ self._signal = Signal.from_variable(value)
214
+ # Update all buttons to use new variable
215
+ for button in self._buttons.values():
216
+ button.configure(variable=value)
217
+
218
+ @property
219
+ def signal(self) -> 'Signal[Any]':
220
+ """Get the signal."""
221
+ return self._signal
222
+
223
+ @signal.setter
224
+ def signal(self, value: 'Signal[Any]') -> None:
225
+ """Set the signal."""
226
+ self._signal = value
227
+ self._variable = value.var
228
+ # Update all buttons to use new variable
229
+ for button in self._buttons.values():
230
+ button.configure(variable=value.var)
231
+
232
+ def add(self, text: str = None, value: Any = None, key: str | None = None, **kwargs: Any) -> RadioButton:
233
+ """Add a radio button to the group.
234
+
235
+ Args:
236
+ text: Text to display on the button.
237
+ value: Value this button represents (required).
238
+ key: Unique identifier. Defaults to value.
239
+ **kwargs: Additional arguments passed to RadioButton.
240
+
241
+ Returns:
242
+ The created RadioButton widget.
243
+
244
+ Raises:
245
+ ValueError: If value is None or if a button with the same key already exists.
246
+ """
247
+ if value is None:
248
+ raise ValueError("The 'value' argument is required.")
249
+
250
+ key = key or value
251
+ if key in self._buttons:
252
+ raise ValueError(f"A button with the key '{key}' already exists.")
253
+
254
+ btn_kwargs = kwargs.copy()
255
+ # Use accent for button styling
256
+ if 'accent' not in btn_kwargs:
257
+ btn_kwargs['accent'] = self._accent
258
+ # Apply current state if not explicitly provided
259
+ if 'state' not in btn_kwargs:
260
+ btn_kwargs['state'] = self._state
261
+ # Forward group-level show_indicator unless overridden per-button
262
+ if 'show_indicator' not in btn_kwargs:
263
+ btn_kwargs['show_indicator'] = self._show_indicator
264
+
265
+ button = RadioButton(
266
+ self._button_container,
267
+ text=text,
268
+ value=value,
269
+ variable=self.variable,
270
+ **btn_kwargs
271
+ )
272
+
273
+ if self._orientation == 'horizontal':
274
+ button.pack(side='left', padx=6)
275
+ else: # vertical
276
+ button.pack(side='top', anchor='w', pady=4)
277
+
278
+ self._buttons[key] = button
279
+ return button
280
+
281
+ def get(self) -> str:
282
+ """Return the currently selected value."""
283
+ return self.variable.get()
284
+
285
+ def set(self, value: str) -> None:
286
+ """Set the selected value.
287
+
288
+ Args:
289
+ value: The value to select (must match a button's value).
290
+
291
+ Raises:
292
+ TypeError: If value is not a string.
293
+ ValueError: If value doesn't exist in the group.
294
+ """
295
+ if not isinstance(value, str):
296
+ raise TypeError(f"RadioGroup requires a string value, got {type(value).__name__}")
297
+
298
+ # Allow empty string (deselection)
299
+ if value and value not in self._buttons:
300
+ valid_values = list(self._buttons.keys())
301
+ raise ValueError(
302
+ f"Value '{value}' not found in group. "
303
+ f"Valid values: {valid_values}"
304
+ )
305
+
306
+ self.variable.set(value)
307
+
308
+ @property
309
+ def value(self) -> str:
310
+ """Get or set the selected value."""
311
+ return self.get()
312
+
313
+ @value.setter
314
+ def value(self, value: str) -> None:
315
+ self.set(value)
316
+
317
+ def text_for(self, value: Any) -> str | None:
318
+ """Return the display text of the option with the given value, or None.
319
+
320
+ The selection variable coerces values to strings, so match on the
321
+ stringified key when an exact lookup misses.
322
+ """
323
+ if value is None or value == "":
324
+ return None
325
+ button = self._buttons.get(value)
326
+ if button is None:
327
+ button = next((b for k, b in self._buttons.items() if str(k) == str(value)), None)
328
+ return button.cget('text') if button is not None else None
329
+
330
+ def remove(self, key: str):
331
+ """Remove a button by its key.
332
+
333
+ Args:
334
+ key: The key of the button to remove.
335
+
336
+ Raises:
337
+ KeyError: If no button with the given key exists.
338
+ """
339
+ if key not in self._buttons:
340
+ raise KeyError(f"No button with key '{key}'")
341
+ button = self._buttons.pop(key)
342
+ button.destroy()
343
+
344
+ def items(self) -> tuple[RadioButton, ...]:
345
+ """Get all button widgets in the group.
346
+
347
+ Returns:
348
+ A tuple of all RadioButton instances in the group.
349
+ """
350
+ return tuple(self._buttons.values())
351
+
352
+ def item(self, key: str) -> RadioButton:
353
+ """Get a button by its key.
354
+
355
+ Args:
356
+ key: The key of the button to retrieve.
357
+
358
+ Returns:
359
+ The RadioButton instance.
360
+
361
+ Raises:
362
+ KeyError: If no button with the given key exists.
363
+ """
364
+ if key not in self._buttons:
365
+ raise KeyError(f"No button with key '{key}'")
366
+ return self._buttons[key]
367
+
368
+ def configure_item(self, key: str, option: str = None, **kwargs: Any):
369
+ """Configure a specific button by its key.
370
+
371
+ Args:
372
+ key: The key of the button to configure.
373
+ option: If provided, return the value of this option.
374
+ **kwargs: Configuration options to apply to the button.
375
+
376
+ Returns:
377
+ If option is provided, returns the value of that option.
378
+ """
379
+ button = self.item(key)
380
+ if option is not None:
381
+ return button.cget(option)
382
+ button.configure(**kwargs)
383
+
384
+ def on_changed(self, callback: Callable) -> Any:
385
+ """Subscribe to value changes.
386
+
387
+ Args:
388
+ callback: Called when the selected value changes. Receives
389
+ `new_value: str` directly (not a Tk event object).
390
+
391
+ Returns:
392
+ Subscription ID — pass to `off_changed()` to unsubscribe.
393
+ """
394
+ return self._signal.subscribe(callback)
395
+
396
+ def off_changed(self, bind_id: Any) -> None:
397
+ """Cancel a subscription created by `on_changed()`.
398
+
399
+ Args:
400
+ bind_id: The handle returned by `on_changed()`.
401
+ """
402
+ if bind_id is not None:
403
+ bind_id.cancel()
404
+
405
+ @configure_delegate('accent')
406
+ def _delegate_accent(self, value=None):
407
+ """Get or set the accent. Updates all buttons when changed."""
408
+ if value is None:
409
+ return self._accent
410
+
411
+ self._accent = value
412
+ # Update all buttons with new accent
413
+ for button in self._buttons.values():
414
+ button.configure(accent=value)
415
+
416
+ @configure_delegate('orient')
417
+ def _delegate_orient(self, value=None):
418
+ """Get or set orientation ('horizontal' or 'vertical'). Repacks buttons when changed."""
419
+ if value is None:
420
+ return self._orientation
421
+
422
+ if value not in ('horizontal', 'vertical'):
423
+ raise ValueError("orient must be 'horizontal' or 'vertical'")
424
+
425
+ self._orientation = value
426
+
427
+ # Repack all buttons in new orientation
428
+ for button in self._buttons.values():
429
+ button.pack_forget()
430
+ if self._orientation == 'horizontal':
431
+ button.pack(side='left', padx=6)
432
+ else:
433
+ button.pack(side='top', anchor='w', pady=4)
434
+
435
+ @configure_delegate('labelanchor')
436
+ def _delegate_labelanchor(self, value=None):
437
+ """Get or set label anchor position. Updates layout when changed."""
438
+ if value is None:
439
+ return self._labelanchor
440
+
441
+ valid_anchors = ('n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw')
442
+ if value not in valid_anchors:
443
+ raise ValueError(f"labelanchor must be one of {valid_anchors}")
444
+
445
+ self._labelanchor = value
446
+ self._update_layout()
447
+
448
+ @configure_delegate('text')
449
+ def _delegate_text(self, value=None):
450
+ """Get or set label text. Creates or removes label as needed."""
451
+ if value is None:
452
+ return self._labeltext if self._labeltext else ''
453
+
454
+ self._labeltext = value
455
+ if value and not self._label:
456
+ # Create label if it doesn't exist
457
+ self._label = Label(self, text=value, **self._label_localize_kwargs())
458
+ self._update_layout()
459
+ elif self._label:
460
+ if value:
461
+ self._label.configure(text=value)
462
+ else:
463
+ # Remove label if text is empty
464
+ self._label.destroy()
465
+ self._label = None
466
+ self._update_layout()
467
+
468
+ @configure_delegate('value')
469
+ def _delegate_value(self, value=None):
470
+ """Get or set the selected value."""
471
+ if value is None:
472
+ return self.get()
473
+ self.set(value)
474
+
475
+ @configure_delegate('state')
476
+ def _delegate_state(self, value=None):
477
+ """Get or set state for all buttons ('normal' or 'disabled')."""
478
+ if value is None:
479
+ return self._state
480
+
481
+ if value not in ('normal', 'disabled'):
482
+ raise ValueError("state must be 'normal' or 'disabled'")
483
+
484
+ self._state = value
485
+ # Update all buttons with new state
486
+ for button in self._buttons.values():
487
+ button.configure(state=value)
488
+
489
+ def keys(self) -> tuple[str, ...]:
490
+ """Get all button keys.
491
+
492
+ Returns:
493
+ A tuple of all button keys in the group.
494
+ """
495
+ return tuple(self._buttons.keys())
496
+
497
+ def values(self) -> tuple[str, ...]:
498
+ """Get all possible values.
499
+
500
+ Returns:
501
+ A tuple of all values that can be selected.
502
+ """
503
+ return tuple(self._buttons.keys())
504
+
505
+ def __len__(self) -> int:
506
+ """Return the number of buttons in the group."""
507
+ return len(self._buttons)
508
+
509
+ def __contains__(self, key: str) -> bool:
510
+ """Check if a button with the given key exists in the group."""
511
+ return key in self._buttons