bootstack 0.1.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. bootstack/__init__.py +249 -0
  2. bootstack/__main__.py +5 -0
  3. bootstack/api/__init__.py +127 -0
  4. bootstack/api/app.py +30 -0
  5. bootstack/api/constants.py +3 -0
  6. bootstack/api/data.py +23 -0
  7. bootstack/api/dialogs.py +44 -0
  8. bootstack/api/i18n.py +17 -0
  9. bootstack/api/localization.py +16 -0
  10. bootstack/api/menu.py +7 -0
  11. bootstack/api/style.py +23 -0
  12. bootstack/api/utils.py +24 -0
  13. bootstack/api/widgets.py +137 -0
  14. bootstack/assets/__init__.py +24 -0
  15. bootstack/assets/bootstack-transparent.png +0 -0
  16. bootstack/assets/bootstack.ico +0 -0
  17. bootstack/assets/bootstack.png +0 -0
  18. bootstack/assets/elements/__init__.py +0 -0
  19. bootstack/assets/elements/badge-pill.png +0 -0
  20. bootstack/assets/elements/badge-square.png +0 -0
  21. bootstack/assets/elements/border.png +0 -0
  22. bootstack/assets/elements/button-compact.png +0 -0
  23. bootstack/assets/elements/button-default.png +0 -0
  24. bootstack/assets/elements/button-group-horizontal-after-compact.png +0 -0
  25. bootstack/assets/elements/button-group-horizontal-after-default.png +0 -0
  26. bootstack/assets/elements/button-group-horizontal-before-compact.png +0 -0
  27. bootstack/assets/elements/button-group-horizontal-before-default.png +0 -0
  28. bootstack/assets/elements/button-group-horizontal-center-compact.png +0 -0
  29. bootstack/assets/elements/button-group-horizontal-center-default.png +0 -0
  30. bootstack/assets/elements/button-group-vertical-after-compact.png +0 -0
  31. bootstack/assets/elements/button-group-vertical-after-default.png +0 -0
  32. bootstack/assets/elements/button-group-vertical-before-compact.png +0 -0
  33. bootstack/assets/elements/button-group-vertical-before-default.png +0 -0
  34. bootstack/assets/elements/button-group-vertical-center-compact.png +0 -0
  35. bootstack/assets/elements/button-group-vertical-center-default.png +0 -0
  36. bootstack/assets/elements/checkbox-checked.png +0 -0
  37. bootstack/assets/elements/checkbox-indeterminate.png +0 -0
  38. bootstack/assets/elements/checkbox-unchecked.png +0 -0
  39. bootstack/assets/elements/field.png +0 -0
  40. bootstack/assets/elements/input-after-compact.png +0 -0
  41. bootstack/assets/elements/input-after-default.png +0 -0
  42. bootstack/assets/elements/input-before-compact.png +0 -0
  43. bootstack/assets/elements/input-before-default.png +0 -0
  44. bootstack/assets/elements/input-compact.png +0 -0
  45. bootstack/assets/elements/input-default.png +0 -0
  46. bootstack/assets/elements/list-item-separated.png +0 -0
  47. bootstack/assets/elements/list-item.png +0 -0
  48. bootstack/assets/elements/manifest.toml +480 -0
  49. bootstack/assets/elements/menu-item.png +0 -0
  50. bootstack/assets/elements/nav-button-compact.png +0 -0
  51. bootstack/assets/elements/nav-button-default.png +0 -0
  52. bootstack/assets/elements/nav-icon-button-compact.png +0 -0
  53. bootstack/assets/elements/nav-icon-button-default.png +0 -0
  54. bootstack/assets/elements/notebook-client-border.png +0 -0
  55. bootstack/assets/elements/notebook-tab-active.png +0 -0
  56. bootstack/assets/elements/notebook-tab-bar.png +0 -0
  57. bootstack/assets/elements/notebook-tab-normal.png +0 -0
  58. bootstack/assets/elements/notebook-tab-pill.png +0 -0
  59. bootstack/assets/elements/progress-bar-horizontal-striped.png +0 -0
  60. bootstack/assets/elements/progress-bar-solid.png +0 -0
  61. bootstack/assets/elements/progress-bar-thin.png +0 -0
  62. bootstack/assets/elements/progress-bar-vertical-striped.png +0 -0
  63. bootstack/assets/elements/radio-selected.png +0 -0
  64. bootstack/assets/elements/radio-unselected.png +0 -0
  65. bootstack/assets/elements/scrollbar-horizontal.png +0 -0
  66. bootstack/assets/elements/scrollbar-vertical.png +0 -0
  67. bootstack/assets/elements/slider-handle-focus.png +0 -0
  68. bootstack/assets/elements/slider-handle.png +0 -0
  69. bootstack/assets/elements/slider-track-horizontal.png +0 -0
  70. bootstack/assets/elements/slider-track-vertical.png +0 -0
  71. bootstack/assets/elements/switch-off.png +0 -0
  72. bootstack/assets/elements/switch-on.png +0 -0
  73. bootstack/assets/elements/tabs-bar-horizontal.png +0 -0
  74. bootstack/assets/elements/tabs-bar-vertical.png +0 -0
  75. bootstack/assets/elements/tabs-pill.png +0 -0
  76. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
  77. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +853 -0
  78. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +875 -0
  79. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
  80. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +853 -0
  81. bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
  82. bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +853 -0
  83. bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
  84. bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +853 -0
  85. bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
  86. bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +875 -0
  87. bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
  88. bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +853 -0
  89. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
  90. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +853 -0
  91. bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
  92. bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +851 -0
  93. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
  94. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +842 -0
  95. bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
  96. bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +841 -0
  97. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
  98. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +914 -0
  99. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
  100. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +842 -0
  101. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
  102. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +841 -0
  103. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
  104. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +841 -0
  105. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
  106. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +842 -0
  107. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
  108. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +842 -0
  109. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
  110. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +842 -0
  111. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
  112. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +842 -0
  113. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
  114. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +842 -0
  115. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
  116. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +842 -0
  117. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
  118. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +842 -0
  119. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
  120. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +842 -0
  121. bootstack/assets/themes/__init__.py +0 -0
  122. bootstack/assets/themes/amber-dark.json +32 -0
  123. bootstack/assets/themes/amber-light.json +32 -0
  124. bootstack/assets/themes/aurora-dark.json +32 -0
  125. bootstack/assets/themes/aurora-light.json +32 -0
  126. bootstack/assets/themes/bootstrap-dark.json +32 -0
  127. bootstack/assets/themes/bootstrap-light.json +32 -0
  128. bootstack/assets/themes/classic-dark.json +32 -0
  129. bootstack/assets/themes/classic-light.json +32 -0
  130. bootstack/assets/themes/docs-dark.json +32 -0
  131. bootstack/assets/themes/docs-light.json +32 -0
  132. bootstack/assets/themes/forest-dark.json +32 -0
  133. bootstack/assets/themes/forest-light.json +32 -0
  134. bootstack/assets/themes/ocean-dark.json +32 -0
  135. bootstack/assets/themes/ocean-light.json +32 -0
  136. bootstack/assets/themes/rose-dark.json +32 -0
  137. bootstack/assets/themes/rose-light.json +32 -0
  138. bootstack/assets/widgets/__init__.py +0 -0
  139. bootstack/assets/widgets/badge-default.png +0 -0
  140. bootstack/assets/widgets/badge-pill.png +0 -0
  141. bootstack/assets/widgets/border.png +0 -0
  142. bootstack/assets/widgets/button-group-horizontal-after.png +0 -0
  143. bootstack/assets/widgets/button-group-horizontal-before.png +0 -0
  144. bootstack/assets/widgets/button-group-horizontal-center.png +0 -0
  145. bootstack/assets/widgets/button-group-vertical-after.png +0 -0
  146. bootstack/assets/widgets/button-group-vertical-before.png +0 -0
  147. bootstack/assets/widgets/button-group-vertical-center.png +0 -0
  148. bootstack/assets/widgets/button.png +0 -0
  149. bootstack/assets/widgets/checkbox-checked.png +0 -0
  150. bootstack/assets/widgets/checkbox-indeterminate.png +0 -0
  151. bootstack/assets/widgets/checkbox-unchecked.png +0 -0
  152. bootstack/assets/widgets/field.png +0 -0
  153. bootstack/assets/widgets/icon-button.png +0 -0
  154. bootstack/assets/widgets/input-inner.png +0 -0
  155. bootstack/assets/widgets/input-prefix.png +0 -0
  156. bootstack/assets/widgets/input-suffix.png +0 -0
  157. bootstack/assets/widgets/input.png +0 -0
  158. bootstack/assets/widgets/list-item-focus.png +0 -0
  159. bootstack/assets/widgets/list-item-separated.png +0 -0
  160. bootstack/assets/widgets/menu-item-separated.png +0 -0
  161. bootstack/assets/widgets/notebook-client-border.png +0 -0
  162. bootstack/assets/widgets/notebook-pill-active.png +0 -0
  163. bootstack/assets/widgets/notebook-pill-inactive.png +0 -0
  164. bootstack/assets/widgets/notebook-tab-active.png +0 -0
  165. bootstack/assets/widgets/notebook-tab-border.png +0 -0
  166. bootstack/assets/widgets/notebook-tab-normal.png +0 -0
  167. bootstack/assets/widgets/notebook-underline.png +0 -0
  168. bootstack/assets/widgets/progress-bar-horizontal-default.png +0 -0
  169. bootstack/assets/widgets/progress-bar-horizontal-striped.png +0 -0
  170. bootstack/assets/widgets/progress-bar-vertical-default.png +0 -0
  171. bootstack/assets/widgets/progress-bar-vertical-striped.png +0 -0
  172. bootstack/assets/widgets/progress-trough-horizontal.png +0 -0
  173. bootstack/assets/widgets/progress-trough-vertical.png +0 -0
  174. bootstack/assets/widgets/radio-selected.png +0 -0
  175. bootstack/assets/widgets/radio-unselected.png +0 -0
  176. bootstack/assets/widgets/scrollbar-horizontal-rounded.png +0 -0
  177. bootstack/assets/widgets/scrollbar-vertical-rounded.png +0 -0
  178. bootstack/assets/widgets/separator-horizontal.png +0 -0
  179. bootstack/assets/widgets/separator-vertical.png +0 -0
  180. bootstack/assets/widgets/slider-handle-focus.png +0 -0
  181. bootstack/assets/widgets/slider-handle.png +0 -0
  182. bootstack/assets/widgets/slider-track-horizontal.png +0 -0
  183. bootstack/assets/widgets/slider-track-vertical.png +0 -0
  184. bootstack/assets/widgets/switch-off.png +0 -0
  185. bootstack/assets/widgets/switch-on.png +0 -0
  186. bootstack/assets/widgets/tabs-bar-horizontal.png +0 -0
  187. bootstack/assets/widgets/tabs-bar-vertical.png +0 -0
  188. bootstack/assets/widgets/tabs-pill.png +0 -0
  189. bootstack/cli/__init__.py +124 -0
  190. bootstack/cli/__main__.py +6 -0
  191. bootstack/cli/add.py +439 -0
  192. bootstack/cli/build.py +115 -0
  193. bootstack/cli/config.py +287 -0
  194. bootstack/cli/demo.py +1267 -0
  195. bootstack/cli/doctor.py +195 -0
  196. bootstack/cli/list_cmd.py +71 -0
  197. bootstack/cli/promote.py +120 -0
  198. bootstack/cli/pyinstaller.py +246 -0
  199. bootstack/cli/run.py +99 -0
  200. bootstack/cli/start.py +105 -0
  201. bootstack/cli/templates/__init__.py +861 -0
  202. bootstack/constants.py +325 -0
  203. bootstack/core/__init__.py +34 -0
  204. bootstack/core/capabilities/__init__.py +45 -0
  205. bootstack/core/capabilities/after.py +103 -0
  206. bootstack/core/capabilities/bind.py +154 -0
  207. bootstack/core/capabilities/bindtags.py +112 -0
  208. bootstack/core/capabilities/busy.py +61 -0
  209. bootstack/core/capabilities/clipboard.py +88 -0
  210. bootstack/core/capabilities/focus.py +118 -0
  211. bootstack/core/capabilities/grab.py +65 -0
  212. bootstack/core/capabilities/grid.py +188 -0
  213. bootstack/core/capabilities/localization.py +231 -0
  214. bootstack/core/capabilities/pack.py +119 -0
  215. bootstack/core/capabilities/place.py +92 -0
  216. bootstack/core/capabilities/selection.py +136 -0
  217. bootstack/core/capabilities/signals.py +242 -0
  218. bootstack/core/capabilities/winfo.py +315 -0
  219. bootstack/core/colorutils.py +234 -0
  220. bootstack/core/exceptions.py +95 -0
  221. bootstack/core/images.py +283 -0
  222. bootstack/core/localization/README.md +90 -0
  223. bootstack/core/localization/__init__.py +13 -0
  224. bootstack/core/localization/intl_format.py +580 -0
  225. bootstack/core/localization/msgcat.py +425 -0
  226. bootstack/core/localization/specs.py +143 -0
  227. bootstack/core/mixins/__init__.py +1 -0
  228. bootstack/core/mixins/ttk_state.py +35 -0
  229. bootstack/core/mixins/widget.py +132 -0
  230. bootstack/core/publisher.py +147 -0
  231. bootstack/core/signals/README.md +112 -0
  232. bootstack/core/signals/__init__.py +8 -0
  233. bootstack/core/signals/integration.py +100 -0
  234. bootstack/core/signals/signal.py +317 -0
  235. bootstack/core/signals/types.py +4 -0
  236. bootstack/core/validation/__init__.py +5 -0
  237. bootstack/core/validation/types.py +13 -0
  238. bootstack/core/validation/validation_result.py +17 -0
  239. bootstack/core/validation/validation_rules.py +112 -0
  240. bootstack/core/variables.py +62 -0
  241. bootstack/datasource/README.md +607 -0
  242. bootstack/datasource/__init__.py +51 -0
  243. bootstack/datasource/base.py +474 -0
  244. bootstack/datasource/file_source.py +541 -0
  245. bootstack/datasource/memory_source.py +482 -0
  246. bootstack/datasource/sqlite_source.py +453 -0
  247. bootstack/datasource/types.py +259 -0
  248. bootstack/dialogs/__init__.py +56 -0
  249. bootstack/dialogs/colorchooser.py +674 -0
  250. bootstack/dialogs/colordropper.py +257 -0
  251. bootstack/dialogs/datedialog.py +404 -0
  252. bootstack/dialogs/dialog.py +514 -0
  253. bootstack/dialogs/filterdialog.py +358 -0
  254. bootstack/dialogs/fontdialog.py +339 -0
  255. bootstack/dialogs/formdialog.py +541 -0
  256. bootstack/dialogs/message.py +489 -0
  257. bootstack/dialogs/query.py +561 -0
  258. bootstack/py.typed +1 -0
  259. bootstack/runtime/__init__.py +3 -0
  260. bootstack/runtime/app.py +879 -0
  261. bootstack/runtime/base_window.py +786 -0
  262. bootstack/runtime/events.py +399 -0
  263. bootstack/runtime/menu.py +510 -0
  264. bootstack/runtime/shortcuts.py +423 -0
  265. bootstack/runtime/tk_patch.py +31 -0
  266. bootstack/runtime/toplevel.py +131 -0
  267. bootstack/runtime/utility.py +371 -0
  268. bootstack/runtime/visual_focus.py +228 -0
  269. bootstack/runtime/window_utilities.py +1043 -0
  270. bootstack/style/__init__.py +5498 -0
  271. bootstack/style/bootstyle.py +507 -0
  272. bootstack/style/bootstyle_builder_base.py +752 -0
  273. bootstack/style/bootstyle_builder_mixed.py +93 -0
  274. bootstack/style/bootstyle_builder_tk.py +109 -0
  275. bootstack/style/bootstyle_builder_ttk.py +354 -0
  276. bootstack/style/builders/__init__.py +51 -0
  277. bootstack/style/builders/badge.py +44 -0
  278. bootstack/style/builders/button.py +453 -0
  279. bootstack/style/builders/buttongroup.py +344 -0
  280. bootstack/style/builders/calendar.py +271 -0
  281. bootstack/style/builders/checkbutton.py +95 -0
  282. bootstack/style/builders/combobox.py +112 -0
  283. bootstack/style/builders/contextmenu.py +268 -0
  284. bootstack/style/builders/entry.py +83 -0
  285. bootstack/style/builders/expander.py +171 -0
  286. bootstack/style/builders/field.py +312 -0
  287. bootstack/style/builders/frame.py +27 -0
  288. bootstack/style/builders/label.py +28 -0
  289. bootstack/style/builders/labelframe.py +41 -0
  290. bootstack/style/builders/listview.py +267 -0
  291. bootstack/style/builders/menubar.py +74 -0
  292. bootstack/style/builders/menubutton.py +408 -0
  293. bootstack/style/builders/notebook.py +316 -0
  294. bootstack/style/builders/panedwindow.py +25 -0
  295. bootstack/style/builders/progressbar.py +71 -0
  296. bootstack/style/builders/radiobutton.py +68 -0
  297. bootstack/style/builders/scale.py +66 -0
  298. bootstack/style/builders/scrollbar.py +360 -0
  299. bootstack/style/builders/separator.py +45 -0
  300. bootstack/style/builders/sidenav.py +313 -0
  301. bootstack/style/builders/sizegrip.py +15 -0
  302. bootstack/style/builders/spinbox.py +119 -0
  303. bootstack/style/builders/switch.py +67 -0
  304. bootstack/style/builders/tabitem.py +205 -0
  305. bootstack/style/builders/toolbutton.py +260 -0
  306. bootstack/style/builders/tooltip.py +26 -0
  307. bootstack/style/builders/treeview.py +269 -0
  308. bootstack/style/builders/utils.py +404 -0
  309. bootstack/style/builders_tk/__init__.py +16 -0
  310. bootstack/style/builders_tk/defaults.py +229 -0
  311. bootstack/style/element.py +173 -0
  312. bootstack/style/style.py +499 -0
  313. bootstack/style/theme_provider.py +449 -0
  314. bootstack/style/tk_patch.py +5 -0
  315. bootstack/style/token_maps.py +42 -0
  316. bootstack/style/types.py +32 -0
  317. bootstack/style/typography.py +527 -0
  318. bootstack/style/utility.py +696 -0
  319. bootstack/themes/__init__.py +12 -0
  320. bootstack/themes/standard.py +415 -0
  321. bootstack/themes/user.py +45 -0
  322. bootstack/widgets/__init__.py +53 -0
  323. bootstack/widgets/composites/__init__.py +38 -0
  324. bootstack/widgets/composites/accordion.py +385 -0
  325. bootstack/widgets/composites/appshell.py +445 -0
  326. bootstack/widgets/composites/buttongroup.py +391 -0
  327. bootstack/widgets/composites/calendar.py +914 -0
  328. bootstack/widgets/composites/compositeframe.py +282 -0
  329. bootstack/widgets/composites/contextmenu.py +1754 -0
  330. bootstack/widgets/composites/dateentry.py +261 -0
  331. bootstack/widgets/composites/dropdownbutton.py +190 -0
  332. bootstack/widgets/composites/expander.py +508 -0
  333. bootstack/widgets/composites/field.py +448 -0
  334. bootstack/widgets/composites/floodgauge.py +434 -0
  335. bootstack/widgets/composites/form.py +983 -0
  336. bootstack/widgets/composites/labeledscale.py +209 -0
  337. bootstack/widgets/composites/list/__init__.py +15 -0
  338. bootstack/widgets/composites/list/listitem.py +733 -0
  339. bootstack/widgets/composites/list/listview.py +1507 -0
  340. bootstack/widgets/composites/menubar.py +303 -0
  341. bootstack/widgets/composites/meter.py +882 -0
  342. bootstack/widgets/composites/numericentry.py +183 -0
  343. bootstack/widgets/composites/pagestack.py +330 -0
  344. bootstack/widgets/composites/passwordentry.py +149 -0
  345. bootstack/widgets/composites/pathentry.py +223 -0
  346. bootstack/widgets/composites/radiogroup.py +466 -0
  347. bootstack/widgets/composites/scrolledtext.py +388 -0
  348. bootstack/widgets/composites/scrolledtext.pyi +186 -0
  349. bootstack/widgets/composites/scrollview.py +675 -0
  350. bootstack/widgets/composites/selectbox.py +544 -0
  351. bootstack/widgets/composites/sidenav/__init__.py +24 -0
  352. bootstack/widgets/composites/sidenav/group.py +485 -0
  353. bootstack/widgets/composites/sidenav/header.py +83 -0
  354. bootstack/widgets/composites/sidenav/item.py +413 -0
  355. bootstack/widgets/composites/sidenav/separator.py +51 -0
  356. bootstack/widgets/composites/sidenav/view.py +919 -0
  357. bootstack/widgets/composites/spinnerentry.py +232 -0
  358. bootstack/widgets/composites/tableview/__init__.py +5 -0
  359. bootstack/widgets/composites/tableview/tableview.py +2254 -0
  360. bootstack/widgets/composites/tableview/types.py +169 -0
  361. bootstack/widgets/composites/tabs/__init__.py +6 -0
  362. bootstack/widgets/composites/tabs/tabitem.py +372 -0
  363. bootstack/widgets/composites/tabs/tabs.py +478 -0
  364. bootstack/widgets/composites/tabs/tabview.py +352 -0
  365. bootstack/widgets/composites/textentry.py +90 -0
  366. bootstack/widgets/composites/timeentry.py +189 -0
  367. bootstack/widgets/composites/toast.py +364 -0
  368. bootstack/widgets/composites/togglegroup.py +382 -0
  369. bootstack/widgets/composites/toolbar.py +393 -0
  370. bootstack/widgets/composites/tooltip.py +404 -0
  371. bootstack/widgets/internal/__init__.py +0 -0
  372. bootstack/widgets/internal/wrapper_base.py +304 -0
  373. bootstack/widgets/mixins/__init__.py +25 -0
  374. bootstack/widgets/mixins/configure_mixin.py +186 -0
  375. bootstack/widgets/mixins/entry_mixin.py +70 -0
  376. bootstack/widgets/mixins/font_mixin.py +346 -0
  377. bootstack/widgets/mixins/icon_mixin.py +38 -0
  378. bootstack/widgets/mixins/localization_mixin.py +255 -0
  379. bootstack/widgets/mixins/signal_mixin.py +272 -0
  380. bootstack/widgets/mixins/validation_mixin.py +204 -0
  381. bootstack/widgets/parts/__init__.py +11 -0
  382. bootstack/widgets/parts/numberentry_part.py +345 -0
  383. bootstack/widgets/parts/spinnerentry_part.py +394 -0
  384. bootstack/widgets/parts/textentry_part.py +344 -0
  385. bootstack/widgets/primitives/__init__.py +55 -0
  386. bootstack/widgets/primitives/badge.py +44 -0
  387. bootstack/widgets/primitives/button.py +89 -0
  388. bootstack/widgets/primitives/card.py +66 -0
  389. bootstack/widgets/primitives/checkbutton.py +124 -0
  390. bootstack/widgets/primitives/checktoggle.py +53 -0
  391. bootstack/widgets/primitives/combobox.py +165 -0
  392. bootstack/widgets/primitives/entry.py +98 -0
  393. bootstack/widgets/primitives/frame.py +206 -0
  394. bootstack/widgets/primitives/gridframe.py +479 -0
  395. bootstack/widgets/primitives/label.py +95 -0
  396. bootstack/widgets/primitives/labelframe.py +63 -0
  397. bootstack/widgets/primitives/menubutton.py +118 -0
  398. bootstack/widgets/primitives/notebook.py +551 -0
  399. bootstack/widgets/primitives/optionmenu.py +248 -0
  400. bootstack/widgets/primitives/packframe.py +228 -0
  401. bootstack/widgets/primitives/panedwindow.py +58 -0
  402. bootstack/widgets/primitives/progressbar.py +95 -0
  403. bootstack/widgets/primitives/radiobutton.py +115 -0
  404. bootstack/widgets/primitives/radiotoggle.py +50 -0
  405. bootstack/widgets/primitives/scale.py +85 -0
  406. bootstack/widgets/primitives/scrollbar.py +56 -0
  407. bootstack/widgets/primitives/separator.py +56 -0
  408. bootstack/widgets/primitives/sizegrip.py +47 -0
  409. bootstack/widgets/primitives/spinbox.py +91 -0
  410. bootstack/widgets/primitives/switch.py +41 -0
  411. bootstack/widgets/primitives/treeview.py +77 -0
  412. bootstack/widgets/types.py +20 -0
  413. bootstack-0.1.0a1.dist-info/METADATA +196 -0
  414. bootstack-0.1.0a1.dist-info/RECORD +419 -0
  415. bootstack-0.1.0a1.dist-info/WHEEL +5 -0
  416. bootstack-0.1.0a1.dist-info/entry_points.txt +2 -0
  417. bootstack-0.1.0a1.dist-info/licenses/LICENSE +22 -0
  418. bootstack-0.1.0a1.dist-info/licenses/NOTICE +10 -0
  419. bootstack-0.1.0a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,358 @@
1
+ """FilterDialog - A dialog for filtering and selecting multiple items from a list.
2
+
3
+ This module provides a multi-select dialog with optional search and select-all
4
+ functionality. Items can be displayed with checkboxes, and the dialog supports
5
+ both standard and frameless (borderless) display modes.
6
+ """
7
+
8
+ from tkinter import Widget
9
+ from typing import Any, Callable, Literal, Optional, Tuple, Union
10
+ from types import SimpleNamespace
11
+
12
+ from bootstack.widgets.primitives import CheckButton, Frame, Label, Separator
13
+ from bootstack.widgets.types import Master
14
+ from bootstack.widgets.composites.textentry import TextEntry
15
+ from bootstack.runtime.app import Window
16
+ from bootstack.dialogs import Dialog, DialogButton
17
+ from bootstack.widgets.composites.scrollview import ScrollView
18
+ from bootstack.runtime.window_utilities import AnchorPoint
19
+
20
+ ttk = SimpleNamespace(
21
+ Checkbutton=CheckButton,
22
+ Frame=Frame,
23
+ Label=Label,
24
+ Separator=Separator,
25
+ TextEntry=TextEntry,
26
+ Window=Window,
27
+ )
28
+
29
+
30
+ class FilterDialogContent(ttk.Frame):
31
+ """Internal content frame for FilterDialog.
32
+
33
+ This frame contains the search box, select-all checkbox, and scrollable
34
+ list of checkboxes for filtering items.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ master: Master = None,
40
+ enable_search: bool = False,
41
+ enable_select_all: bool = False,
42
+ items: list[str | dict[str, Any]] = None
43
+ ):
44
+ """Initialize the FilterDialogContent frame.
45
+
46
+ Args:
47
+ master: Parent widget. If None, uses the default root window.
48
+ enable_search: If True, includes a search box to filter items by text.
49
+ enable_select_all: If True, includes a "Select All" checkbox.
50
+ items: List of items to display. Can be strings or dicts with keys:
51
+ - text (str): Display text (required for dict items)
52
+ - value (Any): Value to return when selected (defaults to text)
53
+ - selected (bool): Initial selection state (defaults to False)
54
+ """
55
+ super().__init__(master)
56
+ self._enable_search = enable_search
57
+ self._enable_select_all = enable_select_all
58
+ self._selected_items = []
59
+ self._items = self._normalize_items(items)
60
+ self._filter: str | None = None
61
+ self._check_buttons: dict[str, ttk.Checkbutton] = {}
62
+ self._select_all_cb: ttk.Checkbutton | None = None
63
+ self._scroll_container: ttk.Frame | None = None
64
+ self._build_content()
65
+
66
+ def _normalize_items(self, items: list[str | dict[str, Any]] | None) -> list[dict[str, Any]]:
67
+ """Normalize items to a consistent dict format.
68
+
69
+ Args:
70
+ items: List of strings or dicts representing items.
71
+
72
+ Returns:
73
+ List of dicts with 'text', 'value', and 'selected' keys.
74
+
75
+ Raises:
76
+ ValueError: If a dict item is missing the 'text' key.
77
+ """
78
+ if items is None:
79
+ return []
80
+
81
+ out = []
82
+ for item in items:
83
+ if isinstance(item, str):
84
+ out.append(dict(text=item, value=item, selected=False))
85
+ else:
86
+ if 'text' not in item:
87
+ raise ValueError('Item must have "text" key')
88
+
89
+ if 'selected' not in item:
90
+ item['selected'] = False
91
+ item.setdefault('value', item['text'])
92
+ if item['selected']:
93
+ self._selected_items.append(item['value'])
94
+ out.append(item)
95
+ return out
96
+
97
+ def _build_content(self):
98
+ """Build and layout all UI components."""
99
+ # Search box
100
+ if self._enable_search:
101
+ search_entry = ttk.TextEntry(self)
102
+ search_entry.insert_addon(ttk.Label, 'before', icon='search')
103
+ search_entry.pack(fill='x')
104
+ search_entry.on_input(self._on_search_input)
105
+
106
+ # Select all checkbox
107
+ if self._enable_select_all:
108
+ self._select_all_cb = ttk.Checkbutton(self, text='edit.select_all')
109
+ self._select_all_cb.invoke()
110
+ self._select_all_cb.invoke()
111
+ self._select_all_cb['command'] = self._handle_select_all
112
+ self._select_all_cb.pack(fill='x', padx=8, pady=(12, 8))
113
+ ttk.Separator(self).pack(fill='x')
114
+
115
+ # Scrollable container for checkboxes
116
+ scroll_view = ScrollView(
117
+ self,
118
+ scroll_direction='vertical',
119
+ scrollbar_visibility='always'
120
+ )
121
+ scroll_view.pack(fill='both', expand=False, pady=(8, 0))
122
+ scroll_view.configure(height=230)
123
+
124
+ self._scroll_container = ttk.Frame(scroll_view.canvas)
125
+ scroll_view.add(self._scroll_container)
126
+
127
+ # Stretch container frame to canvas width for full-width checkboxes
128
+ def _configure_scroll_width(event):
129
+ scroll_view.canvas.itemconfig(scroll_view._window_id, width=event.width)
130
+ scroll_view.canvas.bind('<Configure>', _configure_scroll_width)
131
+
132
+ # Add item checkboxes
133
+ for item in self._items:
134
+ cb = ttk.Checkbutton(self._scroll_container, text=item['text'])
135
+ cb.invoke()
136
+ if not item['selected']:
137
+ cb.invoke()
138
+ cb['command'] = lambda value=item['value']: self._on_item_clicked(value)
139
+ cb.pack(fill='x', padx=8, pady=8)
140
+ self._check_buttons[item['text']] = cb
141
+
142
+ def _handle_select_all(self):
143
+ """Toggle selection state of all checkboxes."""
144
+ if 'selected' in self._select_all_cb.state():
145
+ for item in self._check_buttons.values():
146
+ item.instate(['!selected'], item.invoke)
147
+ else:
148
+ for item in self._check_buttons.values():
149
+ item.instate(['selected'], item.invoke)
150
+
151
+ def _on_search_input(self, event):
152
+ """Filter visible checkboxes based on search text."""
153
+ self._filter = (event.data['text'] or '').lower()
154
+
155
+ for key, cb in self._check_buttons.items():
156
+ if self._filter in key.lower():
157
+ cb.pack(fill='x', padx=8, pady=8)
158
+ else:
159
+ cb.pack_forget()
160
+
161
+ def _on_item_clicked(self, value):
162
+ """Update selected items list when a checkbox is clicked."""
163
+ if value in self._selected_items:
164
+ self._selected_items.remove(value)
165
+ else:
166
+ self._selected_items.append(value)
167
+
168
+ def get_selected_items(self):
169
+ """Return the list of selected item values."""
170
+ return self._selected_items
171
+
172
+
173
+ class FilterDialog(ttk.Frame):
174
+ """A dialog for filtering and selecting multiple items from a list.
175
+
176
+ This dialog displays a list of checkboxes with optional search and select-all
177
+ functionality. When the user clicks OK, the selected values are stored in
178
+ the result property.
179
+
180
+ !!! note "Events"
181
+
182
+ `<<SelectionChange>>`: Triggered when OK is clicked and selections are confirmed.
183
+ Provides `event.data` with key: `selected` (list[Any]).
184
+
185
+ Attributes:
186
+ result (list[Any] | None): List of selected values after dialog is closed,
187
+ or None if canceled.
188
+ """
189
+
190
+ def __init__(
191
+ self,
192
+ master: Master = None,
193
+ title: str = "Filter",
194
+ items: list[str | dict[str, Any]] = None,
195
+ enable_search: bool = False,
196
+ enable_select_all: bool = False,
197
+ frameless: bool = False
198
+ ):
199
+ """Initialize the FilterDialog.
200
+
201
+ Args:
202
+ master: Parent widget. If None, uses the default root window.
203
+ title: Dialog window title.
204
+ items: List of items to display. Can be strings or dicts with keys:
205
+ - text (str): Display text (required for dict items)
206
+ - value (Any): Value to return when selected (defaults to text)
207
+ - selected (bool): Initial selection state (defaults to False)
208
+ enable_search: If True, includes a search box to filter items by text.
209
+ enable_select_all: If True, includes a "Select All" checkbox.
210
+ frameless: If True, removes window decorations and displays with a
211
+ border frame. Enables dismiss-on-outside-click behavior.
212
+ """
213
+ super().__init__(master)
214
+ self._master = master if master else self.master
215
+ self._title = title
216
+ self._items = items or []
217
+ self._enable_search = enable_search
218
+ self._enable_select_all = enable_select_all
219
+ self._frameless = frameless
220
+ self._content: FilterDialogContent | None = None
221
+ self._dialog: Dialog | None = None
222
+ self.result: list[Any] | None = None
223
+
224
+ def _build_content(self, parent):
225
+ """Build the dialog content with FilterDialogContent."""
226
+ self._content = FilterDialogContent(
227
+ master=parent,
228
+ enable_search=self._enable_search,
229
+ enable_select_all=self._enable_select_all,
230
+ items=self._items
231
+ )
232
+ padding = 0 if self._frameless else 10
233
+ self._content.pack(fill='both', expand=True, padx=padding, pady=padding)
234
+
235
+ # Setup dismiss-on-click for frameless after dialog is built
236
+ if self._frameless:
237
+ parent.after(10, self._setup_frameless_dismiss)
238
+
239
+ def _on_ok(self, dialog: Dialog):
240
+ """Save selected items to result when OK button is clicked."""
241
+ if self._content:
242
+ self.result = self._content.get_selected_items()
243
+ # Generate selection changed event on confirmation
244
+ if self.result is not None:
245
+ self.event_generate('<<SelectionChange>>', data={"selected": self.result.copy()})
246
+
247
+ def on_selection_changed(self, callback: Callable) -> str:
248
+ """Bind to `<<SelectionChange>>`. Callback receives `event.data = {"selected": list[Any]}`.
249
+
250
+ Returns:
251
+ Binding identifier for use with off_selection_changed().
252
+ """
253
+ return self.bind('<<SelectionChange>>', callback, add=True)
254
+
255
+ def off_selection_changed(self, bind_id: str = None):
256
+ """Unbind callback from <<SelectionChange>> event.
257
+
258
+ Args:
259
+ bind_id: Binding identifier from on_selection_changed().
260
+ """
261
+ self.unbind('<<SelectionChange>>', bind_id)
262
+
263
+ def _on_click(self, event):
264
+ """Check if click is outside dialog and dismiss if so."""
265
+ if not self._dialog or not self._dialog.toplevel:
266
+ return
267
+
268
+ try:
269
+ # Get dialog window bounds
270
+ x = self._dialog.toplevel.winfo_rootx()
271
+ y = self._dialog.toplevel.winfo_rooty()
272
+ w = self._dialog.toplevel.winfo_width()
273
+ h = self._dialog.toplevel.winfo_height()
274
+
275
+ # Check if click is outside dialog
276
+ if not (x <= event.x_root <= x + w and y <= event.y_root <= y + h):
277
+ self._dialog.toplevel.destroy()
278
+ except:
279
+ pass
280
+
281
+ def _setup_frameless_dismiss(self):
282
+ """Setup dismiss-on-outside-click for frameless dialogs."""
283
+ if self._frameless and self._dialog and self._dialog.toplevel:
284
+ # Bind to root window to catch all clicks
285
+ root = self._dialog.toplevel.winfo_toplevel()
286
+ while root.master:
287
+ root = root.master.winfo_toplevel()
288
+ root.bind('<Button-1>', self._on_click, add='+')
289
+
290
+ def show(
291
+ self,
292
+ position: Optional[Tuple[int, int]] = None,
293
+ modal: Optional[bool] = None,
294
+ *,
295
+ anchor_to: Optional[Union[Widget, Literal["screen", "cursor", "parent"]]] = None,
296
+ anchor_point: AnchorPoint = 'center',
297
+ window_point: AnchorPoint = 'center',
298
+ offset: Tuple[int, int] = (0, 0),
299
+ auto_flip: Union[bool, Literal['vertical', 'horizontal']] = False
300
+ ) -> Optional[list[Any]]:
301
+ """Show the dialog and return the selected items.
302
+
303
+ Args:
304
+ position: Optional (x, y) coordinates to position the dialog.
305
+ If provided, takes precedence over anchor-based positioning.
306
+ modal: Override the mode's default modality.
307
+ If None, uses True (modal mode).
308
+ anchor_to: Positioning target. Can be:
309
+ - Widget: Anchor to a specific widget
310
+ - "screen": Anchor to screen edges/corners
311
+ - "cursor": Anchor to mouse cursor location
312
+ - "parent": Anchor to parent window (same as widget)
313
+ - None: Centers on parent (default)
314
+ anchor_point: Point on the anchor target (n, s, e, w, ne, nw, se, sw, center).
315
+ Default 'center'.
316
+ window_point: Point on the dialog window (n, s, e, w, ne, nw, se, sw, center).
317
+ Default 'center'.
318
+ offset: Additional (x, y) offset in pixels from the anchor position.
319
+ auto_flip: Smart positioning to keep window on screen.
320
+ - False: No flipping (default)
321
+ - True: Flip both vertically and horizontally as needed
322
+ - 'vertical': Only flip up/down
323
+ - 'horizontal': Only flip left/right
324
+
325
+ Returns:
326
+ List of selected item values, or None if canceled.
327
+ """
328
+ self._dialog: Dialog = Dialog(
329
+ master=self._master,
330
+ title=self._title,
331
+ content_builder=self._build_content,
332
+ buttons=[
333
+ DialogButton(text="button.cancel", role="cancel", result=None),
334
+ DialogButton(
335
+ text="button.ok",
336
+ role="primary",
337
+ result=True,
338
+ default=True,
339
+ command=self._on_ok
340
+ )
341
+ ],
342
+ minsize=(250, 200),
343
+ maxsize=(250, 380),
344
+ resizable=(False, True),
345
+ mode="modal",
346
+ frameless=self._frameless
347
+ )
348
+
349
+ self._dialog.show(
350
+ position=position,
351
+ modal=modal,
352
+ anchor_to=anchor_to,
353
+ anchor_point=anchor_point,
354
+ window_point=window_point,
355
+ offset=offset,
356
+ auto_flip=auto_flip
357
+ )
358
+ return self.result
@@ -0,0 +1,339 @@
1
+ """FontDialog implementation for selecting and previewing fonts."""
2
+
3
+ import tkinter
4
+ from tkinter import Text, Variable, font
5
+ from types import SimpleNamespace
6
+ from typing import Any, Optional
7
+
8
+ from typing_extensions import Unpack
9
+
10
+ from bootstack.constants import *
11
+ from bootstack.core.localization import MessageCatalog
12
+ from bootstack.dialogs.dialog import Dialog, DialogButton, ShowOptions
13
+ from bootstack.runtime.app import Window
14
+ from bootstack.runtime.utility import scale_size
15
+ from bootstack.style.style import get_style
16
+ from bootstack.widgets.primitives import (
17
+ CheckButton,
18
+ Frame,
19
+ Label,
20
+ LabelFrame,
21
+ RadioButton,
22
+ Scrollbar,
23
+ TreeView,
24
+ )
25
+
26
+ ttk = SimpleNamespace(
27
+ Checkbutton=CheckButton,
28
+ Frame=Frame,
29
+ Label=Label,
30
+ Labelframe=LabelFrame,
31
+ Radiobutton=RadioButton,
32
+ Scrollbar=Scrollbar,
33
+ Treeview=TreeView,
34
+ Text=Text,
35
+ Variable=Variable,
36
+ Window=Window,
37
+ use_style=get_style,
38
+ )
39
+
40
+
41
+ class FontDialog:
42
+ """A dialog for selecting and previewing fonts.
43
+
44
+ This dialog provides a comprehensive interface for selecting fonts,
45
+ including family, size, weight, slant, and effects (underline, overstrike).
46
+ The selected font is returned as a `tkinter.font.Font` object when OK is
47
+ pressed, or None if canceled.
48
+
49
+ Attributes:
50
+ result (font.Font | None): The selected font, or None if canceled.
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ title: str = "font.selector", # Use semantic key as default
56
+ master: Optional[tkinter.Misc] = None,
57
+ default_font: str = "TkDefaultFont"
58
+ ):
59
+ """Create a font selection dialog.
60
+
61
+ Args:
62
+ title: The dialog window title. Will be localized automatically.
63
+ Defaults to semantic key 'font.selector'.
64
+ master: Parent widget. The dialog will be modal and centered on this widget.
65
+ default_font: Name of the initial font to display. Can be any valid tkinter
66
+ font name (e.g., 'TkDefaultFont', 'TkFixedFont', 'TkTextFont',
67
+ 'TkHeadingFont', etc.) or a custom font name.
68
+ """
69
+ # Title is now automatically localized by BaseWindow._setup_window
70
+ self._style = ttk.use_style()
71
+ self._default = font.nametofont(default_font)
72
+ self._actual = self._default.actual()
73
+ self._size = ttk.Variable(value=self._actual["size"])
74
+ self._family = ttk.Variable(value=self._actual["family"])
75
+ self._slant = ttk.Variable(value=self._actual["slant"])
76
+ self._weight = ttk.Variable(value=self._actual["weight"])
77
+ self._overstrike = ttk.Variable(value=self._actual["overstrike"])
78
+ self._underline = ttk.Variable(value=self._actual["underline"])
79
+ self._preview_font = font.Font()
80
+ self._preview_text: Optional[ttk.Text] = None
81
+
82
+ self._slant.trace_add("write", self._update_font_preview)
83
+ self._weight.trace_add("write", self._update_font_preview)
84
+ self._overstrike.trace_add("write", self._update_font_preview)
85
+ self._underline.trace_add("write", self._update_font_preview)
86
+
87
+ self._update_font_preview()
88
+ self._families = {self._family.get()}
89
+ for f in font.families():
90
+ if all([f, not f.startswith("@"), "emoji" not in f.lower()]):
91
+ self._families.add(f)
92
+
93
+ # Create the underlying dialog
94
+ self._dialog = Dialog(
95
+ master=master,
96
+ title=title,
97
+ content_builder=self._create_content,
98
+ buttons=[
99
+ DialogButton(
100
+ text="button.cancel",
101
+ role="cancel",
102
+ result=None,
103
+ ),
104
+ DialogButton(
105
+ text="button.ok",
106
+ role="primary",
107
+ default=True,
108
+ command=lambda dlg: self._on_submit(),
109
+ result=self._preview_font,
110
+ ),
111
+ ],
112
+ )
113
+
114
+ def _create_content(self, master: tkinter.Widget) -> None:
115
+ """Create the dialog body with font selection controls."""
116
+ # Set dialog size
117
+ width = scale_size(master, 800)
118
+ height = scale_size(master, 600)
119
+ if self._dialog.toplevel:
120
+ self._dialog.toplevel.geometry(f"{width}x{height}")
121
+
122
+ family_size_frame = ttk.Frame(master, padding=10)
123
+ family_size_frame.pack(fill=X, anchor=N)
124
+ initial_focus = self._font_families_selector(family_size_frame)
125
+ self._font_size_selector(family_size_frame)
126
+ self._font_options_selectors(master, padding=10)
127
+ self._font_preview(master, padding=10)
128
+
129
+ # Set initial focus
130
+ if initial_focus and self._dialog.toplevel:
131
+ self._dialog.toplevel.after(100, initial_focus.focus_set)
132
+
133
+ def _font_families_selector(self, master: tkinter.Misc) -> ttk.Treeview:
134
+ """Create and populate the font family selection list."""
135
+ container = ttk.Frame(master)
136
+ container.pack(fill=BOTH, expand=YES, side=LEFT)
137
+
138
+ header = ttk.Label(
139
+ container,
140
+ text="font.family",
141
+ font="TkHeadingFont",
142
+ )
143
+ header.pack(fill=X, pady=(0, 2), anchor=N)
144
+
145
+ listbox = ttk.Treeview(
146
+ master=container,
147
+ height=5,
148
+ show="",
149
+ columns=[0],
150
+ )
151
+ listbox.column(0, width=scale_size(listbox, 250))
152
+ listbox.pack(side=LEFT, fill=BOTH, expand=YES)
153
+
154
+ listbox_vbar = ttk.Scrollbar(
155
+ container,
156
+ command=listbox.yview,
157
+ orient=VERTICAL,
158
+ variant="round",
159
+ )
160
+ listbox_vbar.pack(side=RIGHT, fill=Y)
161
+ listbox.configure(yscrollcommand=listbox_vbar.set)
162
+
163
+ for f in sorted(self._families):
164
+ listbox.insert("", iid=f, index='end', tags=[f], values=[f])
165
+ listbox.tag_configure(f, font=(f, self._size.get()))
166
+
167
+ iid = self._family.get()
168
+ listbox.selection_set(iid) # select default value
169
+ listbox.see(iid) # ensure default is visible
170
+ listbox.bind("<<TreeviewSelect>>", lambda e: self._on_select_font_family(e))
171
+ return listbox
172
+
173
+ def _font_size_selector(self, master: tkinter.Misc) -> None:
174
+ """Create and populate the font size selection list."""
175
+ container = ttk.Frame(master)
176
+ container.pack(side=LEFT, fill=Y, padx=(10, 0))
177
+
178
+ header = ttk.Label(
179
+ container,
180
+ text="font.size",
181
+ font="TkHeadingFont",
182
+ )
183
+ header.pack(fill=X, pady=(0, 2), anchor=N)
184
+
185
+ sizes_listbox = ttk.Treeview(container, height=7, columns=[0], show="")
186
+ sizes_listbox.column(0, width=scale_size(sizes_listbox, 48))
187
+
188
+ sizes = [*range(8, 13), *range(13, 30, 2), 36, 48, 72]
189
+ for s in sizes:
190
+ sizes_listbox.insert("", iid=s, index='end', values=[s])
191
+
192
+ iid = self._size.get()
193
+ sizes_listbox.selection_set(iid)
194
+ sizes_listbox.see(iid)
195
+ sizes_listbox.bind("<<TreeviewSelect>>", lambda e: self._on_select_font_size(e))
196
+
197
+ sizes_listbox_vbar = ttk.Scrollbar(
198
+ master=container,
199
+ orient=VERTICAL,
200
+ command=sizes_listbox.yview,
201
+ variant="round",
202
+ )
203
+ sizes_listbox.configure(yscrollcommand=sizes_listbox_vbar.set)
204
+ sizes_listbox.pack(side=LEFT, fill=Y, expand=YES, anchor=N)
205
+ sizes_listbox_vbar.pack(side=LEFT, fill=Y, expand=YES)
206
+
207
+ def _font_options_selectors(self, master: tkinter.Misc, padding: int) -> None:
208
+ """Create font option controls (weight, slant, effects)."""
209
+ container = ttk.Frame(master, padding=padding)
210
+ container.pack(fill=X, padx=2, pady=2, anchor=N)
211
+
212
+ weight_lframe = ttk.Labelframe(container, text="font.weight", padding=5) # Use semantic key
213
+ weight_lframe.pack(side=LEFT, fill=X, expand=YES)
214
+ opt_normal = ttk.Radiobutton(
215
+ master=weight_lframe,
216
+ text="font.weight.normal",
217
+ value="normal",
218
+ variable=self._weight,
219
+ )
220
+ opt_normal.invoke()
221
+ opt_normal.pack(side=LEFT, padx=5, pady=5)
222
+ opt_bold = ttk.Radiobutton(
223
+ master=weight_lframe,
224
+ text="font.weight.bold",
225
+ value="bold",
226
+ variable=self._weight,
227
+ )
228
+ opt_bold.pack(side=LEFT, padx=5, pady=5)
229
+
230
+ slant_lframe = ttk.Labelframe(container, text="font.slant", padding=5) # Use semantic key
231
+ slant_lframe.pack(side=LEFT, fill=X, padx=10, expand=YES)
232
+ opt_roman = ttk.Radiobutton(
233
+ master=slant_lframe,
234
+ text="font.slant.roman",
235
+ value="roman",
236
+ variable=self._slant,
237
+ )
238
+ opt_roman.invoke()
239
+ opt_roman.pack(side=LEFT, padx=5, pady=5)
240
+ opt_italic = ttk.Radiobutton(
241
+ master=slant_lframe,
242
+ text="font.slant.italic",
243
+ value="italic",
244
+ variable=self._slant,
245
+ )
246
+ opt_italic.pack(side=LEFT, padx=5, pady=5)
247
+
248
+ effects_lframe = ttk.Labelframe(container, text="font.effects", padding=5) # Use semantic key
249
+ effects_lframe.pack(side=LEFT, padx=(2, 0), fill=X, expand=YES)
250
+ opt_underline = ttk.Checkbutton(
251
+ master=effects_lframe,
252
+ text="font.effects.underline",
253
+ variable=self._underline,
254
+ )
255
+ opt_underline.pack(side=LEFT, padx=5, pady=5)
256
+ opt_overstrike = ttk.Checkbutton(
257
+ master=effects_lframe,
258
+ text="font.effects.overstrike",
259
+ variable=self._overstrike,
260
+ )
261
+ opt_overstrike.pack(side=LEFT, padx=5, pady=5)
262
+
263
+ def _font_preview(self, master: tkinter.Misc, padding: int) -> None:
264
+ """Create the font preview text widget."""
265
+ container = ttk.Frame(master, padding=padding, height=scale_size(master, 150))
266
+ container.pack(fill=BOTH, expand=YES, anchor=N)
267
+ container.pack_propagate(False)
268
+
269
+ header = ttk.Label(
270
+ container,
271
+ text="font.preview",
272
+ font="TkHeadingFont",
273
+ )
274
+ header.pack(fill=X, pady=2, anchor=N)
275
+
276
+ content = MessageCatalog.translate("font.preview_text")
277
+ self._preview_text = ttk.Text(
278
+ master=container,
279
+ height=3,
280
+ font=self._preview_font,
281
+ )
282
+ self._preview_text.insert(END, content)
283
+ self._preview_text.pack(fill=BOTH, expand=YES)
284
+
285
+ def _on_select_font_family(self, e: tkinter.Event) -> None:
286
+ """Handle font family selection event."""
287
+ if not self._dialog.toplevel:
288
+ return
289
+ tree: ttk.Treeview = self._dialog.toplevel.nametowidget(e.widget)
290
+ font_family = tree.selection()[0]
291
+ self._family.set(value=font_family)
292
+ self._update_font_preview()
293
+
294
+ def _on_select_font_size(self, e: tkinter.Event) -> None:
295
+ """Handle font size selection event."""
296
+ if not self._dialog.toplevel:
297
+ return
298
+ tree: ttk.Treeview = self._dialog.toplevel.nametowidget(e.widget)
299
+ fontsize = tree.selection()[0]
300
+ self._size.set(value=fontsize)
301
+ self._update_font_preview()
302
+
303
+ def _on_submit(self) -> None:
304
+ """Handle OK button - update result with current font."""
305
+ self._dialog.result = self._preview_font
306
+
307
+ def _update_font_preview(self, *_: Any) -> None:
308
+ """Update the preview font based on current selections."""
309
+ family = self._family.get()
310
+ size = self._size.get()
311
+ slant = self._slant.get()
312
+ overstrike = self._overstrike.get()
313
+ underline = self._underline.get()
314
+ weight = self._weight.get()
315
+
316
+ self._preview_font.config(
317
+ family=family,
318
+ size=size,
319
+ slant=slant,
320
+ overstrike=overstrike,
321
+ underline=underline,
322
+ weight=weight,
323
+ )
324
+ if self._preview_text:
325
+ try:
326
+ self._preview_text.configure(font=self._preview_font)
327
+ except Exception:
328
+ pass
329
+
330
+ def show(self, **kwargs: Unpack[ShowOptions]) -> None:
331
+ """Show the dialog."""
332
+ kwargs.setdefault('modal', True)
333
+ kwargs.setdefault('anchor_to', 'screen')
334
+ self._dialog.show(**kwargs)
335
+
336
+ @property
337
+ def result(self) -> Optional[font.Font]:
338
+ """The selected font, or None if canceled."""
339
+ return self._dialog.result