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,948 @@
1
+ from __future__ import annotations
2
+
3
+ import textwrap
4
+ from datetime import date
5
+ from typing import Any, Callable, Iterable, Literal, Mapping, Sequence
6
+
7
+ from bootstack.dialogs._impl.dialog import Dialog, DialogButton, ButtonSpec, ButtonRole
8
+ from bootstack.dialogs._impl.query import QueryBox as _QueryBox
9
+ from bootstack.dialogs._impl.formdialog import FormDialog as _InternalFormDialog
10
+ from bootstack.dialogs._impl.datedialog import DateDialog as _DateDialog
11
+ from bootstack.dialogs._impl.colorchooser import ColorChooserDialog as _InternalColorChooserDialog, ColorChoice
12
+ from bootstack.dialogs._impl.fontdialog import FontDialog as _InternalFontDialog, FontChoice
13
+ from bootstack.dialogs._impl.filterdialog import FilterDialog as _InternalFilterDialog
14
+ from bootstack.widgets._impl.primitives.label import Label as _Label
15
+ from bootstack.widgets._impl.primitives.frame import Frame as _Frame
16
+ from bootstack._core.images import _ImageService
17
+ from bootstack.style.style import get_theme_color as _get_theme_color
18
+
19
+ __all__ = [
20
+ # one-shot verbs
21
+ "alert", "confirm",
22
+ "ask_string", "ask_integer", "ask_float", "ask_date", "ask_date_range",
23
+ "ask_item", "ask_color", "ask_font", "ask_filter",
24
+ "ask_save_file", "ask_open_file", "ask_open_files", "ask_directory",
25
+ # dialog classes
26
+ "Dialog", "DialogButton", "FormDialog", "FilterDialog",
27
+ "ColorChooserDialog", "ColorChoice", "FontDialog", "FontChoice",
28
+ # type aliases
29
+ "SeverityToken",
30
+ ]
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Module-level convenience functions
35
+ # ---------------------------------------------------------------------------
36
+
37
+ _SEVERITY_ICONS: dict[str, str] = {
38
+ "info": "info-circle-fill",
39
+ "warning": "exclamation-triangle-fill",
40
+ "danger": "x-circle-fill",
41
+ "success": "check-circle-fill",
42
+ }
43
+
44
+ SeverityToken = Literal["info", "warning", "danger", "success"]
45
+
46
+
47
+ def _resolve_icon(
48
+ icon: str | None, severity: SeverityToken | None
49
+ ) -> tuple[str | None, str]:
50
+ """Return (icon_name, icon_color) based on explicit icon/severity."""
51
+ resolved = icon or _SEVERITY_ICONS.get(severity or "", None)
52
+ if severity and not icon:
53
+ try:
54
+ color = _get_theme_color(severity)
55
+ except Exception:
56
+ color = _get_theme_color("foreground")
57
+ else:
58
+ try:
59
+ color = _get_theme_color("foreground")
60
+ except Exception:
61
+ color = "#000000"
62
+ return resolved, color
63
+
64
+
65
+ def _build_message_content(
66
+ message: str, icon: str | None, icon_color: str, frame: Any
67
+ ) -> None:
68
+ """Build message body — used by alert() and confirm()."""
69
+ # Two spacers sandwich the content for vertical centering
70
+ _Frame(frame).pack(fill="x", expand=True)
71
+ container = _Frame(frame, padding=(20, 0))
72
+ container.pack(fill="x")
73
+ _Frame(frame).pack(fill="x", expand=True)
74
+
75
+ if icon:
76
+ try:
77
+ img = _ImageService.get_icon(icon, 48, icon_color)
78
+ icon_lbl = _Label(container, image=img)
79
+ icon_lbl.image = img # prevent GC
80
+ icon_lbl.pack(side="left", anchor="center", padx=(0, 16))
81
+ except Exception:
82
+ pass
83
+ msg_frame = _Frame(container)
84
+ msg_frame.pack(side="left", anchor="center")
85
+ for line in message.split("\n"):
86
+ wrapped = "\n".join(textwrap.wrap(line, width=60)) or line
87
+ _Label(msg_frame, text=wrapped).pack(anchor="w", pady=(0, 3))
88
+
89
+
90
+ def alert(
91
+ message: str,
92
+ *,
93
+ title: str = "",
94
+ ok_text: str = "OK",
95
+ severity: SeverityToken | None = None,
96
+ icon: str | None = None,
97
+ sound: bool | None = None,
98
+ parent: Any = None,
99
+ ) -> None:
100
+ """Show a message dialog with an OK button.
101
+
102
+ Args:
103
+ message: The message text to display.
104
+ title: Dialog window title.
105
+ ok_text: Label for the OK button. Defaults to `'OK'`.
106
+ severity: Visual severity level — sets a colored icon and controls the
107
+ default sound behavior. `'warning'` and `'danger'` ring the system
108
+ bell by default; `'info'` and `'success'` are silent.
109
+ icon: Icon name override. Takes precedence over `severity`.
110
+ sound: Override the alert sound. `True` always rings the bell,
111
+ `False` always suppresses it. `None` (default) defers to
112
+ `severity`: rings for `'warning'` and `'danger'`, silent
113
+ otherwise.
114
+ parent: Parent widget. Defaults to the active root window.
115
+ """
116
+ resolved_icon, icon_color = _resolve_icon(icon, severity)
117
+ if sound is None:
118
+ sound = severity in ("warning", "danger")
119
+ dlg = Dialog(
120
+ title=title or " ",
121
+ content_builder=lambda f: _build_message_content(message, resolved_icon, icon_color, f),
122
+ _raw_content=True,
123
+ buttons=[DialogButton(ok_text, role="secondary", result=True, default=True)],
124
+ alert=sound,
125
+ min_size=(400, 140),
126
+ parent=parent,
127
+ )
128
+ dlg.show()
129
+
130
+
131
+ def confirm(
132
+ message: str,
133
+ *,
134
+ title: str = "",
135
+ confirm_text: str = "Yes",
136
+ cancel_text: str = "No",
137
+ confirm_role: ButtonRole = "primary",
138
+ severity: SeverityToken | None = None,
139
+ icon: str | None = None,
140
+ sound: bool | None = None,
141
+ parent: Any = None,
142
+ ) -> bool:
143
+ """Show a confirmation dialog.
144
+
145
+ Args:
146
+ message: The question text to display.
147
+ title: Dialog window title.
148
+ confirm_text: Label for the confirm button. Defaults to `'Yes'`.
149
+ cancel_text: Label for the cancel button. Defaults to `'No'`.
150
+ confirm_role: Button role controlling styling. Use `'danger'` for
151
+ destructive actions, `'primary'` (default) for standard
152
+ confirmations. When `'danger'`, the confirm button is not
153
+ focused by default so :kbd:`Enter` does not accidentally trigger it.
154
+ Automatically overridden to `'danger'` when `severity='danger'`
155
+ and to warning styling when `severity='warning'`, unless set
156
+ explicitly.
157
+ severity: Visual severity level — sets a colored icon, adjusts the
158
+ confirm button color for `'danger'` and `'warning'`, and
159
+ controls the default sound behavior.
160
+ icon: Icon name override. Takes precedence over `severity`.
161
+ sound: Override the alert sound. `True` always rings the bell,
162
+ `False` always suppresses it. `None` (default) defers to
163
+ `severity`: rings for `'warning'` and `'danger'`, silent
164
+ otherwise.
165
+ parent: Parent widget. Defaults to the active root window.
166
+
167
+ Returns:
168
+ `True` if the user clicked the confirm button, `False` otherwise.
169
+ """
170
+ resolved_icon, icon_color = _resolve_icon(icon, severity)
171
+ if sound is None:
172
+ sound = severity in ("warning", "danger")
173
+
174
+ # Auto-derive confirm button styling from severity when not explicitly set
175
+ if confirm_role == "primary" and severity == "danger":
176
+ confirm_btn = DialogButton(confirm_text, role="danger", result=True, default=False)
177
+ elif confirm_role == "primary" and severity == "warning":
178
+ confirm_btn = DialogButton(confirm_text, role="secondary", accent="warning",
179
+ result=True, default=True)
180
+ else:
181
+ is_danger = confirm_role == "danger"
182
+ confirm_btn = DialogButton(confirm_text, role=confirm_role, result=True,
183
+ default=not is_danger)
184
+
185
+ dlg = Dialog(
186
+ title=title or " ",
187
+ content_builder=lambda f: _build_message_content(message, resolved_icon, icon_color, f),
188
+ _raw_content=True,
189
+ buttons=[
190
+ DialogButton(cancel_text, role="cancel"),
191
+ confirm_btn,
192
+ ],
193
+ alert=sound,
194
+ min_size=(400, 140),
195
+ parent=parent,
196
+ )
197
+ dlg.show()
198
+ return dlg.result is True
199
+
200
+
201
+ def ask_string(
202
+ prompt: str,
203
+ *,
204
+ title: str = "",
205
+ value: str | None = None,
206
+ value_format: str | None = None,
207
+ parent: Any = None,
208
+ ) -> str | None:
209
+ """Show a text-input dialog.
210
+
211
+ Args:
212
+ prompt: Prompt text displayed above the input field.
213
+ title: Dialog window title.
214
+ value: Pre-filled initial value.
215
+ value_format: ICU format pattern for parsing and displaying the value
216
+ (e.g. a phone mask or postal code pattern). See :ref:`value-formats`.
217
+ parent: Parent widget. Defaults to the active root window.
218
+
219
+ Returns:
220
+ The entered string, or `None` if canceled.
221
+ """
222
+ return _QueryBox.get_string(
223
+ prompt,
224
+ title=title or " ",
225
+ value=value,
226
+ value_format=value_format,
227
+ master=parent,
228
+ )
229
+
230
+
231
+ def ask_integer(
232
+ prompt: str,
233
+ *,
234
+ title: str = "",
235
+ value: int | None = None,
236
+ min_value: int | None = None,
237
+ max_value: int | None = None,
238
+ step: int | None = None,
239
+ value_format: str | None = None,
240
+ parent: Any = None,
241
+ ) -> int | None:
242
+ """Show an integer-input dialog with optional range validation.
243
+
244
+ Args:
245
+ prompt: Prompt text displayed above the input field.
246
+ title: Dialog window title.
247
+ value: Pre-filled initial value.
248
+ min_value: Minimum accepted value.
249
+ max_value: Maximum accepted value.
250
+ step: Increment/decrement step size.
251
+ value_format: ICU format pattern for displaying the value
252
+ (e.g. `'#,##0'` for thousands separators). See :ref:`value-formats`.
253
+ parent: Parent widget. Defaults to the active root window.
254
+
255
+ Returns:
256
+ The entered integer, or `None` if canceled.
257
+ """
258
+ return _QueryBox.get_integer(
259
+ prompt,
260
+ title=title or " ",
261
+ value=value,
262
+ minvalue=min_value,
263
+ maxvalue=max_value,
264
+ increment=step,
265
+ value_format=value_format,
266
+ master=parent,
267
+ )
268
+
269
+
270
+ def ask_float(
271
+ prompt: str,
272
+ *,
273
+ title: str = "",
274
+ value: float | None = None,
275
+ min_value: float | None = None,
276
+ max_value: float | None = None,
277
+ step: float | None = None,
278
+ value_format: str | None = None,
279
+ parent: Any = None,
280
+ ) -> float | None:
281
+ """Show a float-input dialog with optional range validation.
282
+
283
+ Args:
284
+ prompt: Prompt text displayed above the input field.
285
+ title: Dialog window title.
286
+ value: Pre-filled initial value.
287
+ min_value: Minimum accepted value.
288
+ max_value: Maximum accepted value.
289
+ step: Increment/decrement step size.
290
+ value_format: ICU format pattern for displaying the value
291
+ (e.g. `'$#,##0.00'` for currency, `'#,##0.##'` for decimals).
292
+ See :ref:`value-formats`.
293
+ parent: Parent widget. Defaults to the active root window.
294
+
295
+ Returns:
296
+ The entered float, or `None` if canceled.
297
+ """
298
+ return _QueryBox.get_float(
299
+ prompt,
300
+ title=title or " ",
301
+ value=value,
302
+ minvalue=min_value,
303
+ maxvalue=max_value,
304
+ increment=step,
305
+ value_format=value_format,
306
+ master=parent,
307
+ )
308
+
309
+
310
+ def ask_date(
311
+ *,
312
+ title: str = "",
313
+ value: date | None = None,
314
+ min_date: date | None = None,
315
+ max_date: date | None = None,
316
+ first_weekday: int = 6,
317
+ disabled_dates: list[date] | None = None,
318
+ parent: Any = None,
319
+ ) -> date | None:
320
+ """Show a calendar date-picker dialog.
321
+
322
+ Args:
323
+ title: Dialog window title.
324
+ value: Pre-selected initial date. Defaults to today.
325
+ min_date: Earliest selectable date (inclusive).
326
+ max_date: Latest selectable date (inclusive).
327
+ first_weekday: First day of the week. `0` = Monday, `6` = Sunday
328
+ (default).
329
+ disabled_dates: Specific dates to disable from selection.
330
+ parent: Parent widget. Defaults to the active root window.
331
+
332
+ Returns:
333
+ The selected `date`, or `None` if canceled.
334
+ """
335
+ dlg = _DateDialog(
336
+ master=parent,
337
+ title=title or " ",
338
+ initial_date=value,
339
+ min_date=min_date,
340
+ max_date=max_date,
341
+ first_weekday=first_weekday,
342
+ disabled_dates=disabled_dates,
343
+ )
344
+ dlg.show()
345
+ return dlg.result
346
+
347
+
348
+ def ask_date_range(
349
+ *,
350
+ title: str = "",
351
+ start_date: date | None = None,
352
+ end_date: date | None = None,
353
+ min_date: date | None = None,
354
+ max_date: date | None = None,
355
+ first_weekday: int = 6,
356
+ disabled_dates: list[date] | None = None,
357
+ parent: Any = None,
358
+ ) -> tuple[date, date] | None:
359
+ """Show a calendar dialog for selecting a start and end date range.
360
+
361
+ Args:
362
+ title: Dialog window title.
363
+ start_date: Pre-selected range start date.
364
+ end_date: Pre-selected range end date.
365
+ min_date: Earliest selectable date (inclusive).
366
+ max_date: Latest selectable date (inclusive).
367
+ first_weekday: First day of the week. `0` = Monday, `6` = Sunday
368
+ (default).
369
+ disabled_dates: Specific dates to disable from selection.
370
+ parent: Parent widget. Defaults to the active root window.
371
+
372
+ Returns:
373
+ A `(start, end)` tuple of `date` objects, or `None` if canceled.
374
+ """
375
+ dlg = _DateDialog(
376
+ master=parent,
377
+ title=title or " ",
378
+ selection_mode="range",
379
+ start_date=start_date,
380
+ end_date=end_date,
381
+ min_date=min_date,
382
+ max_date=max_date,
383
+ first_weekday=first_weekday,
384
+ disabled_dates=disabled_dates,
385
+ )
386
+ dlg.show()
387
+ return dlg.result
388
+
389
+
390
+ def ask_item(
391
+ prompt: str,
392
+ options: list[str],
393
+ *,
394
+ title: str = "",
395
+ value: str | None = None,
396
+ parent: Any = None,
397
+ ) -> str | None:
398
+ """Show a dropdown-selection dialog.
399
+
400
+ Args:
401
+ prompt: Prompt text displayed above the dropdown.
402
+ options: List of selectable items.
403
+ title: Dialog window title.
404
+ value: Pre-selected initial item.
405
+ parent: Parent widget. Defaults to the active root window.
406
+
407
+ Returns:
408
+ The selected item string, or `None` if canceled.
409
+ """
410
+ return _QueryBox.get_item(
411
+ prompt,
412
+ title=title or " ",
413
+ value=value,
414
+ items=options,
415
+ master=parent,
416
+ )
417
+
418
+
419
+ # ---------------------------------------------------------------------------
420
+ # FormDialog — public wrapper
421
+ # ---------------------------------------------------------------------------
422
+
423
+ class FormDialog:
424
+ """A dialog window that embeds a Form for structured data entry.
425
+
426
+ Args:
427
+ title: Dialog window title.
428
+ data: Initial data backing the form. Keys become field names.
429
+ items: Explicit form layout — `FormItem` / `GroupItem` / `TabsItem`
430
+ instances or equivalent dicts. If omitted, fields are inferred
431
+ from `data`.
432
+ col_count: Number of form columns. Default `1`.
433
+ min_col_width: Minimum column width in pixels.
434
+ on_data_change: Callback invoked on every field change, receiving
435
+ the current data dict.
436
+ on_close: Callback fired when the dialog closes by any means.
437
+ width: Explicit form width in pixels.
438
+ height: Explicit form height in pixels.
439
+ buttons: Footer button specs. Defaults to Cancel + OK.
440
+ min_size: Minimum dialog window size `(width, height)`.
441
+ max_size: Maximum dialog window size `(width, height)`.
442
+ resizable: Allow window resizing. Default `False`.
443
+ parent: Parent widget. Defaults to the active root window.
444
+ """
445
+
446
+ def __init__(
447
+ self,
448
+ *,
449
+ title: str = "Form",
450
+ data: dict[str, Any] | None = None,
451
+ items: Sequence[Any] | None = None,
452
+ col_count: int = 1,
453
+ min_col_width: int | None = None,
454
+ on_data_change: Callable[[dict[str, Any]], Any] | None = None,
455
+ on_close: Callable[[], Any] | None = None,
456
+ width: int | None = None,
457
+ height: int | None = None,
458
+ buttons: Iterable[ButtonSpec | str] | None = None,
459
+ min_size: tuple[int, int] | None = None,
460
+ max_size: tuple[int, int] | None = None,
461
+ resizable: tuple[bool, bool] | bool = False,
462
+ parent: Any = None,
463
+ ) -> None:
464
+ internal_kwargs: dict[str, Any] = {
465
+ "title": title,
466
+ "col_count": col_count,
467
+ "resizable": resizable,
468
+ }
469
+ if data is not None:
470
+ internal_kwargs["data"] = data
471
+ if items is not None:
472
+ internal_kwargs["items"] = items
473
+ if min_col_width is not None:
474
+ internal_kwargs["min_col_width"] = min_col_width
475
+ if on_data_change is not None:
476
+ internal_kwargs["on_data_change"] = on_data_change
477
+ if on_close is not None:
478
+ internal_kwargs["on_close"] = on_close
479
+ if width is not None:
480
+ internal_kwargs["width"] = width
481
+ if height is not None:
482
+ internal_kwargs["height"] = height
483
+ if buttons is not None:
484
+ internal_kwargs["buttons"] = buttons
485
+ if min_size is not None:
486
+ internal_kwargs["minsize"] = min_size
487
+ if max_size is not None:
488
+ internal_kwargs["maxsize"] = max_size
489
+
490
+ self._internal = _InternalFormDialog(parent, **internal_kwargs)
491
+
492
+ def show(
493
+ self,
494
+ *,
495
+ position: tuple[int, int] | None = None,
496
+ modal: bool | None = None,
497
+ ) -> "FormDialog":
498
+ """Display the dialog and block until it is closed.
499
+
500
+ Args:
501
+ position: Explicit `(x, y)` screen coordinates for the dialog.
502
+ Defaults to centered on the parent window.
503
+ modal: Override the default modality. `True` blocks the parent;
504
+ `False` shows a non-blocking dialog.
505
+
506
+ Returns:
507
+ `self` — allows chaining: `dlg = FormDialog(...).show(); dlg.result`.
508
+ """
509
+ self._internal.show(position=position, modal=modal)
510
+ return self
511
+
512
+ @property
513
+ def result(self) -> dict[str, Any] | None:
514
+ """Form data dict after closing, or `None` if canceled."""
515
+ return self._internal.result
516
+
517
+ @property
518
+ def form(self) -> Any:
519
+ """The embedded `Form` widget — for advanced programmatic access."""
520
+ return self._internal.form
521
+
522
+
523
+ # ---------------------------------------------------------------------------
524
+ # ColorChooserDialog — public wrapper
525
+ # ---------------------------------------------------------------------------
526
+
527
+ class ColorChooserDialog:
528
+ """A dialog for choosing a color.
529
+
530
+ The chooser has two tabs: a *Themed* tab of the active theme's color bands,
531
+ and a *Custom* tab with a hue/saturation spectrum and luminance slider.
532
+ Numeric fields on the right allow direct entry in RGB, HSL, or hex notation.
533
+
534
+ Args:
535
+ title: Dialog window title. Defaults to the localized "Color" string.
536
+ value: Pre-selected color as a hex string (e.g. `'#ff0000'`). Defaults
537
+ to the current theme background color.
538
+ parent: Parent widget. Defaults to the active root window.
539
+ """
540
+
541
+ def __init__(
542
+ self,
543
+ *,
544
+ title: str = "",
545
+ value: str | None = None,
546
+ parent: Any = None,
547
+ ) -> None:
548
+ self._internal = _InternalColorChooserDialog(
549
+ master=parent,
550
+ title=title or "color.chooser",
551
+ initial_color=value,
552
+ )
553
+
554
+ def show(
555
+ self,
556
+ *,
557
+ position: tuple[int, int] | None = None,
558
+ modal: bool = True,
559
+ ) -> "ColorChooserDialog":
560
+ """Display the dialog and block until it is closed.
561
+
562
+ Args:
563
+ position: Explicit `(x, y)` screen coordinates for the dialog.
564
+ Defaults to centered on the parent window.
565
+ modal: Block the parent window until closed. Default `True`.
566
+
567
+ Returns:
568
+ `self` — allows chaining: `dlg = ColorChooserDialog(...).show(); dlg.result`.
569
+ """
570
+ self._internal.show(position=position, modal=modal)
571
+ return self
572
+
573
+ @property
574
+ def result(self) -> ColorChoice | None:
575
+ """The selected color, or `None` if canceled.
576
+
577
+ Returns a `ColorChoice` namedtuple with three attributes:
578
+
579
+ - `rgb` — `(r, g, b)` tuple, each 0–255.
580
+ - `hsl` — `(h, s, l)` tuple: hue 0–360, saturation and luminance 0–100.
581
+ - `hex` — lowercase hex string, e.g. `'#ff0000'`.
582
+ """
583
+ return self._internal.result
584
+
585
+
586
+ # ---------------------------------------------------------------------------
587
+ # FontDialog — public wrapper
588
+ # ---------------------------------------------------------------------------
589
+
590
+ class FontDialog:
591
+ """A dialog for selecting a font family, size, weight, slant, and effects.
592
+
593
+ The dialog shows a scrollable list of font families, a size list, and
594
+ controls for weight (normal/bold), slant (roman/italic), underline, and
595
+ overstrike. A live preview panel shows sample text rendered in the
596
+ selected font.
597
+
598
+ Args:
599
+ title: Dialog window title. Defaults to the localized "Font" string.
600
+ default_font: Font token to show initially (e.g. `'body'`, `'code'`,
601
+ `'heading-lg'`). Defaults to `'body'`. See :doc:`/reference/typography`.
602
+ parent: Parent widget. Defaults to the active root window.
603
+ """
604
+
605
+ def __init__(
606
+ self,
607
+ *,
608
+ title: str = "",
609
+ default_font: str = "body",
610
+ parent: Any = None,
611
+ ) -> None:
612
+ self._internal = _InternalFontDialog(
613
+ title=title or "font.selector",
614
+ master=parent,
615
+ default_font=default_font,
616
+ )
617
+
618
+ def show(
619
+ self,
620
+ *,
621
+ position: tuple[int, int] | None = None,
622
+ modal: bool | None = None,
623
+ ) -> "FontDialog":
624
+ """Display the dialog and block until it is closed.
625
+
626
+ Args:
627
+ position: Explicit `(x, y)` screen coordinates for the dialog.
628
+ Defaults to centered on screen (the font dialog sizes itself on open).
629
+ modal: Override the default modality. `True` blocks the parent;
630
+ `False` shows a non-blocking dialog.
631
+
632
+ Returns:
633
+ `self` — allows chaining: `dlg = FontDialog(...).show(); dlg.result`.
634
+ """
635
+ self._internal.show(position=position, modal=modal)
636
+ return self
637
+
638
+ @property
639
+ def result(self) -> FontChoice | None:
640
+ """The selected font, or `None` if canceled.
641
+
642
+ Returns a `FontChoice` namedtuple with six attributes:
643
+
644
+ - `family` — font family name (str).
645
+ - `size` — point size (int).
646
+ - `weight` — `'normal'` or `'bold'`.
647
+ - `slant` — `'roman'` or `'italic'`.
648
+ - `underline` — `True` if underlined.
649
+ - `overstrike` — `True` if struck through.
650
+ """
651
+ return self._internal.result
652
+
653
+
654
+ # ---------------------------------------------------------------------------
655
+ # FilterDialog — public wrapper
656
+ # ---------------------------------------------------------------------------
657
+
658
+ class FilterDialog:
659
+ """A dialog for selecting multiple items from a list.
660
+
661
+ Displays a scrollable list of checkboxes. Optionally includes a search
662
+ box that narrows visible items and a "Select All" checkbox.
663
+
664
+ Args:
665
+ title: Dialog window title.
666
+ items: Items to display. Each item is a string or a dict with keys:
667
+
668
+ - `text` (str): Display label (required for dicts).
669
+ - `value` (Any): Value returned when selected. Defaults to `text`.
670
+ - `selected` (bool): Initial check state. Defaults to `False`.
671
+ enable_search: Include a search box that filters items by text.
672
+ Defaults to `False`.
673
+ enable_select_all: Include a "Select All" checkbox. Defaults to `False`.
674
+ parent: Parent widget. Defaults to the active root window.
675
+ """
676
+
677
+ def __init__(
678
+ self,
679
+ *,
680
+ title: str = "",
681
+ items: list[str | dict[str, Any]] | None = None,
682
+ enable_search: bool = False,
683
+ enable_select_all: bool = False,
684
+ parent: Any = None,
685
+ ) -> None:
686
+ self._title = title
687
+ self._items = items or []
688
+ self._enable_search = enable_search
689
+ self._enable_select_all = enable_select_all
690
+ self._parent = parent
691
+ self._result: list[Any] | None = None
692
+
693
+ def show(
694
+ self,
695
+ *,
696
+ position: tuple[int, int] | None = None,
697
+ modal: bool | None = None,
698
+ ) -> "FilterDialog":
699
+ """Display the dialog and block until it is closed.
700
+
701
+ Args:
702
+ position: Explicit `(x, y)` screen coordinates for the dialog.
703
+ Defaults to centered on the parent window.
704
+ modal: Override the default modality. `True` blocks the parent;
705
+ `False` shows a non-blocking dialog.
706
+
707
+ Returns:
708
+ `self` — allows chaining: `dlg = FilterDialog(...).show(); dlg.result`.
709
+ """
710
+ dlg = _InternalFilterDialog(
711
+ master=self._parent,
712
+ title=self._title or "Filter",
713
+ items=self._items,
714
+ enable_search=self._enable_search,
715
+ enable_select_all=self._enable_select_all,
716
+ )
717
+ dlg.show(position=position, modal=modal)
718
+ self._result = dlg.result
719
+ return self
720
+
721
+ @property
722
+ def result(self) -> list[Any] | None:
723
+ """List of selected values after closing, or `None` if canceled."""
724
+ return self._result
725
+
726
+
727
+ # ---------------------------------------------------------------------------
728
+ # Convenience functions — color, font, filter
729
+ # ---------------------------------------------------------------------------
730
+
731
+ def ask_color(
732
+ *,
733
+ title: str = "",
734
+ value: str | None = None,
735
+ parent: Any = None,
736
+ ) -> ColorChoice | None:
737
+ """Show a color chooser dialog.
738
+
739
+ Args:
740
+ title: Dialog window title.
741
+ value: Pre-selected color as a hex string (e.g. `'#ff0000'`). Defaults
742
+ to the current theme background color.
743
+ parent: Parent widget. Defaults to the active root window.
744
+
745
+ Returns:
746
+ A `ColorChoice` with `rgb`, `hsl`, and `hex` attributes, or
747
+ `None` if canceled.
748
+ """
749
+ dlg = ColorChooserDialog(title=title, value=value, parent=parent)
750
+ dlg.show()
751
+ return dlg.result
752
+
753
+
754
+ def ask_font(
755
+ *,
756
+ title: str = "",
757
+ default_font: str = "body",
758
+ parent: Any = None,
759
+ ) -> FontChoice | None:
760
+ """Show a font selector dialog.
761
+
762
+ Args:
763
+ title: Dialog window title.
764
+ default_font: Font token to show initially (e.g. `'body'`, `'code'`,
765
+ `'heading-lg'`). Defaults to `'body'`. See :doc:`/reference/typography`.
766
+ parent: Parent widget. Defaults to the active root window.
767
+
768
+ Returns:
769
+ A `FontChoice` with `family`, `size`, `weight`, `slant`,
770
+ `underline`, and `overstrike` attributes, or `None` if canceled.
771
+ """
772
+ dlg = FontDialog(title=title, default_font=default_font, parent=parent)
773
+ dlg.show()
774
+ return dlg.result
775
+
776
+
777
+ def ask_filter(
778
+ items: list[str | dict[str, Any]],
779
+ *,
780
+ title: str = "",
781
+ enable_search: bool = False,
782
+ enable_select_all: bool = False,
783
+ parent: Any = None,
784
+ ) -> list[Any] | None:
785
+ """Show a multi-select filter dialog.
786
+
787
+ Args:
788
+ items: Items to display. Each item is a string or a dict with keys:
789
+
790
+ - `text` (str): Display label (required for dicts).
791
+ - `value` (Any): Value returned when selected. Defaults to `text`.
792
+ - `selected` (bool): Initial check state. Defaults to `False`.
793
+ title: Dialog window title.
794
+ enable_search: Include a search box that filters items by text.
795
+ Defaults to `False`.
796
+ enable_select_all: Include a "Select All" checkbox. Defaults to `False`.
797
+ parent: Parent widget. Defaults to the active root window.
798
+
799
+ Returns:
800
+ A list of selected values, or `None` if canceled.
801
+ """
802
+ dlg = FilterDialog(
803
+ title=title,
804
+ items=items,
805
+ enable_search=enable_search,
806
+ enable_select_all=enable_select_all,
807
+ parent=parent,
808
+ )
809
+ dlg.show()
810
+ return dlg.result
811
+
812
+
813
+ # File-system dialogs (native OS choosers) ----------------------------------
814
+
815
+ def _run_file_dialog(_name: str, parent: Any, **options: Any) -> Any:
816
+ """Call a `tkinter.filedialog` function with cleaned options."""
817
+ import tkinter
818
+ from tkinter import filedialog
819
+
820
+ opts = {k: v for k, v in options.items() if v not in (None, "")}
821
+ master = parent if parent is not None else tkinter._default_root
822
+ if master is not None:
823
+ opts["parent"] = master
824
+ return getattr(filedialog, _name)(**opts)
825
+
826
+
827
+ def _coerce_file_types(file_types: Any) -> Any:
828
+ if not file_types:
829
+ return None
830
+ return [tuple(ft) for ft in file_types]
831
+
832
+
833
+ def ask_save_file(
834
+ *,
835
+ title: str = "",
836
+ initial_file: str = "",
837
+ initial_dir: str = "",
838
+ file_types: list[tuple[str, str]] | None = None,
839
+ default_extension: str = "",
840
+ parent: Any = None,
841
+ ) -> str | None:
842
+ """Show a native save dialog and return the chosen path.
843
+
844
+ Args:
845
+ title: Dialog window title.
846
+ initial_file: Suggested file name.
847
+ initial_dir: Directory to open in. Defaults to the last used location.
848
+ file_types: Selectable file types as `(label, pattern)` pairs, for
849
+ example `[('PNG image', '*.png'), ('All files', '*.*')]`.
850
+ default_extension: Extension appended when the user omits one (for
851
+ example `'.png'`).
852
+ parent: Parent widget. Defaults to the active root window.
853
+
854
+ Returns:
855
+ The chosen file path, or `None` if canceled.
856
+ """
857
+ path = _run_file_dialog(
858
+ "asksaveasfilename",
859
+ parent,
860
+ title=title,
861
+ initialfile=initial_file,
862
+ initialdir=initial_dir,
863
+ filetypes=_coerce_file_types(file_types),
864
+ defaultextension=default_extension,
865
+ )
866
+ return path or None
867
+
868
+
869
+ def ask_open_file(
870
+ *,
871
+ title: str = "",
872
+ initial_dir: str = "",
873
+ file_types: list[tuple[str, str]] | None = None,
874
+ parent: Any = None,
875
+ ) -> str | None:
876
+ """Show a native open dialog for a single file and return its path.
877
+
878
+ Args:
879
+ title: Dialog window title.
880
+ initial_dir: Directory to open in. Defaults to the last used location.
881
+ file_types: Selectable file types as `(label, pattern)` pairs, for
882
+ example `[('Images', '*.png *.jpg'), ('All files', '*.*')]`.
883
+ parent: Parent widget. Defaults to the active root window.
884
+
885
+ Returns:
886
+ The chosen file path, or `None` if canceled.
887
+ """
888
+ path = _run_file_dialog(
889
+ "askopenfilename",
890
+ parent,
891
+ title=title,
892
+ initialdir=initial_dir,
893
+ filetypes=_coerce_file_types(file_types),
894
+ )
895
+ return path or None
896
+
897
+
898
+ def ask_open_files(
899
+ *,
900
+ title: str = "",
901
+ initial_dir: str = "",
902
+ file_types: list[tuple[str, str]] | None = None,
903
+ parent: Any = None,
904
+ ) -> list[str]:
905
+ """Show a native open dialog allowing several files.
906
+
907
+ Args:
908
+ title: Dialog window title.
909
+ initial_dir: Directory to open in. Defaults to the last used location.
910
+ file_types: Selectable file types as `(label, pattern)` pairs.
911
+ parent: Parent widget. Defaults to the active root window.
912
+
913
+ Returns:
914
+ The chosen file paths, or an empty list if canceled.
915
+ """
916
+ paths = _run_file_dialog(
917
+ "askopenfilenames",
918
+ parent,
919
+ title=title,
920
+ initialdir=initial_dir,
921
+ filetypes=_coerce_file_types(file_types),
922
+ )
923
+ return list(paths) if isinstance(paths, (list, tuple)) else []
924
+
925
+
926
+ def ask_directory(
927
+ *,
928
+ title: str = "",
929
+ initial_dir: str = "",
930
+ parent: Any = None,
931
+ ) -> str | None:
932
+ """Show a native folder chooser and return the chosen directory.
933
+
934
+ Args:
935
+ title: Dialog window title.
936
+ initial_dir: Directory to open in. Defaults to the last used location.
937
+ parent: Parent widget. Defaults to the active root window.
938
+
939
+ Returns:
940
+ The chosen directory path, or `None` if canceled.
941
+ """
942
+ path = _run_file_dialog(
943
+ "askdirectory",
944
+ parent,
945
+ title=title,
946
+ initialdir=initial_dir,
947
+ )
948
+ return path or None