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,945 @@
1
+ """Base window functionality shared between App (Tk) and Toplevel.
2
+
3
+ This module provides the BaseWindow mixin class that encapsulates common
4
+ window management functionality used by both the main App and Toplevel classes.
5
+ This eliminates code duplication and ensures consistent behavior across all
6
+ window types.
7
+
8
+ Standard widget APIs (events, scheduling, clipboard, geometry managers, winfo) are documented under capabilities and are
9
+ available through normal Tk/Ttk inheritance.”
10
+
11
+ The BaseWindow mixin provides:
12
+ - Window manager (wm) pass-throughs with modern docstrings
13
+ - Window configuration (size, position, constraints)
14
+ - Alpha transparency handling (platform-aware)
15
+ - Icon management
16
+ - Positioning utilities
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import os
22
+ import sys
23
+ import tkinter
24
+ import warnings
25
+ from typing import Literal, Optional, Tuple, Callable, Any, List
26
+
27
+ from bootstack.i18n import MessageCatalog
28
+ from bootstack._runtime.window_utilities import AnchorPoint, WindowPositioning, WindowSizing
29
+
30
+
31
+ def on_visibility_alpha(event: tkinter.Event) -> None:
32
+ """Set Window or Toplevel alpha value on Visibility (X11).
33
+
34
+ X11 requires alpha to be set after the window is visible, so we bind
35
+ to the <Visibility> event and set alpha then, unbinding after first use.
36
+
37
+ Args:
38
+ event: The visibility event containing the widget.
39
+ """
40
+ widget = event.widget
41
+ if hasattr(widget, 'alpha') and hasattr(widget, 'alpha_bind'):
42
+ if widget.alpha_bind:
43
+ widget.unbind(widget.alpha_bind)
44
+ widget.attributes("-alpha", widget.alpha)
45
+
46
+
47
+ class BaseWindow:
48
+ """Base window behavior shared by bootstack windows.
49
+
50
+ This class is intended to be used as a mixin alongside `tkinter.Tk` or
51
+ `tkinter.Toplevel`, for example:
52
+
53
+ class App(BaseWindow, tkinter.Tk): ...
54
+ class Window(BaseWindow, tkinter.Toplevel): ...
55
+
56
+ The methods below are thin pass-throughs to Tk's window manager ("wm")
57
+ functionality, primarily to provide modern, consistent docstrings and a
58
+ curated, documented API surface.
59
+ """
60
+
61
+ _title_message_id: str | None = None
62
+
63
+ # -------------------------------------------------------------------------
64
+ # Setup methods (bootstack-specific)
65
+ # -------------------------------------------------------------------------
66
+
67
+ def _setup_window(
68
+ self,
69
+ title: str = "bootstack",
70
+ size: Optional[tuple[int, int]] = None,
71
+ position: Optional[tuple[int, int]] = None,
72
+ minsize: Optional[tuple[int, int]] = None,
73
+ maxsize: Optional[tuple[int, int]] = None,
74
+ resizable: Optional[tuple[bool, bool]] = None,
75
+ transient: Optional[tkinter.Misc] = None,
76
+ overrideredirect: bool = False,
77
+ alpha: float = 1.0,
78
+ window_style: Optional[str] = None,
79
+ center_on_parent: Optional[tkinter.Misc] = None,
80
+ center_on_screen: bool = False,
81
+ ) -> None:
82
+ """Configure common window properties.
83
+
84
+ This method should be called during window initialization to set up
85
+ standard window properties. It handles platform differences automatically.
86
+ The window is temporarily hidden during setup so that sizing and
87
+ positioning are applied before it is shown.
88
+
89
+ Args:
90
+ title: Window title shown in titlebar.
91
+ size: Optional (width, height) in pixels.
92
+ position: Optional (x, y) position on screen.
93
+ minsize: Optional (width, height) minimum window size.
94
+ maxsize: Optional (width, height) maximum window size.
95
+ resizable: Optional (width, height) resizable flags.
96
+ transient: Optional master window for transient behavior.
97
+ overrideredirect: If True, removes window decorations.
98
+ alpha: Transparency level (0.0 to 1.0). Platform-aware.
99
+ window_style: Windows-only pywinstyles effect. Options include
100
+ 'mica', 'acrylic', 'aero', 'transparent', 'win7', etc.
101
+ If None, uses AppSettings.window_style (defaults to 'mica').
102
+ center_on_parent: If provided, center window over this parent widget
103
+ after layout. Ignored when `position` is also given.
104
+ center_on_screen: If True, center window on screen after layout.
105
+ Ignored when `position` or `center_on_parent` is also given.
106
+
107
+ Note:
108
+ The window must already be initialized (tkinter.Tk.__init__ or
109
+ tkinter.Toplevel.__init__ called) before calling this method.
110
+ """
111
+ # Get window_style from AppSettings if not explicitly provided
112
+ if window_style is None:
113
+ try:
114
+ from bootstack._runtime.app import get_app_settings
115
+ window_style = get_app_settings().window_style
116
+ except (ImportError, RuntimeError):
117
+ # No app registered yet, use default
118
+ window_style = 'mica'
119
+
120
+ # Store window style for later application
121
+ self._window_style = window_style
122
+ # Hide window until we finish applying geometry/resizing to avoid flicker
123
+ self.withdraw()
124
+
125
+ # Store window system for platform-specific behavior
126
+ if not hasattr(self, 'winsys'):
127
+ self.winsys = self.tk.call('tk', 'windowingsystem')
128
+
129
+ # Basic configuration - localize title automatically
130
+ self.title(title)
131
+
132
+ # Bind locale changes
133
+ self.winfo_toplevel().bind("<<LocaleChanged>>", self._handle_locale_changed)
134
+
135
+ # Geometry
136
+ if size is not None:
137
+ width, height = size
138
+ self.geometry(f"{width}x{height}")
139
+
140
+ if position is not None:
141
+ xpos, ypos = position
142
+ self.geometry(f"+{xpos}+{ypos}")
143
+
144
+ # Size constraints
145
+ if minsize is not None:
146
+ width, height = minsize
147
+ self.minsize(width, height)
148
+
149
+ if maxsize is not None:
150
+ width, height = maxsize
151
+ self.maxsize(width, height)
152
+
153
+ if resizable is not None:
154
+ width, height = resizable
155
+ self.resizable(width, height)
156
+
157
+ # Window attributes
158
+ if transient is not None:
159
+ self.transient(transient)
160
+
161
+ if overrideredirect:
162
+ # Skip overrideredirect on macOS - causes click handling issues and crashes
163
+ if self.winsys != 'aqua':
164
+ self.overrideredirect(1)
165
+
166
+ # Alpha transparency (platform-aware)
167
+ self._setup_alpha(alpha)
168
+
169
+ # Centering (only when no explicit position given)
170
+ if position is None:
171
+ self.update_idletasks()
172
+ if center_on_parent is not None:
173
+ x, y = WindowPositioning.center_on_parent(self, center_on_parent)
174
+ x, y = WindowPositioning.ensure_on_screen(self, x, y)
175
+ self.geometry(f'+{x}+{y}')
176
+ elif center_on_screen:
177
+ x, y = WindowPositioning.center_on_screen(self)
178
+ x, y = WindowPositioning.ensure_on_screen(self, x, y)
179
+ self.geometry(f'+{x}+{y}')
180
+
181
+ def _apply_window_style(self) -> None:
182
+ """Apply pywinstyles effect on Windows.
183
+
184
+ Uses the window_style set during initialization. Only applies on Windows
185
+ and only if the style hasn't already been applied.
186
+ """
187
+ if getattr(self, '_window_style_applied', False):
188
+ return
189
+ window_style = getattr(self, '_window_style', 'mica')
190
+ if window_style is None:
191
+ return
192
+ if not hasattr(self, 'winsys') or self.winsys != 'win32':
193
+ return
194
+ try:
195
+ import pywinstyles
196
+ pywinstyles.apply_style(self, window_style)
197
+ self._window_style_applied = True
198
+ self.after(0, self._update_chrome_color)
199
+ self.bind('<<ThemeChanged>>', self._schedule_chrome_update, add='+')
200
+ except Exception:
201
+ pass
202
+
203
+ def _schedule_chrome_update(self, _event=None) -> None:
204
+ """Coalesce chrome re-tinting on theme change.
205
+
206
+ The toplevel receives `<<ThemeChanged>>` ~1400x per theme rebuild (once
207
+ per ttk style reconfigure). Cancel any pending update and schedule a
208
+ single one so the (DWM) chrome repaint runs once, not hundreds of times.
209
+ """
210
+ pending = getattr(self, '_chrome_update_after', None)
211
+ if pending is not None:
212
+ try:
213
+ self.after_cancel(pending)
214
+ except Exception:
215
+ pass
216
+ self._chrome_update_after = self.after(0, self._update_chrome_color)
217
+
218
+ def _update_chrome_color(self) -> None:
219
+ if getattr(self, '_updating_chrome', False):
220
+ return
221
+ self._updating_chrome = True
222
+ try:
223
+ import pywinstyles
224
+ from bootstack.style.style import get_theme_provider
225
+ color = get_theme_provider().colors['chrome']
226
+ # The DWM header/border calls are expensive (~100ms each); skip them
227
+ # when the chrome color is unchanged (redundant theme events, or a
228
+ # theme switch that keeps the same chrome).
229
+ if getattr(self, '_chrome_color_applied', None) != color:
230
+ self._chrome_color_applied = color
231
+ pywinstyles.change_header_color(self, color)
232
+ # Match the window border to the chrome too, so it honors the
233
+ # theme (otherwise Windows leaves a light border in dark mode).
234
+ pywinstyles.change_border_color(self, color)
235
+ except Exception:
236
+ pass
237
+ finally:
238
+ self._updating_chrome = False
239
+
240
+ def _setup_alpha(self, alpha: float) -> None:
241
+ """Configure window alpha transparency in a platform-aware manner.
242
+
243
+ Handles platform differences in alpha transparency support:
244
+ - X11: Requires setting alpha after window is visible
245
+ - Windows/macOS: Can set alpha immediately
246
+
247
+ Args:
248
+ alpha: Transparency level (0.0 = fully transparent, 1.0 = opaque).
249
+
250
+ Note:
251
+ On X11, this binds to <Visibility> event to set alpha after
252
+ the window becomes visible. The binding is automatically
253
+ removed after first use.
254
+ """
255
+ if alpha is not None and alpha != 1.0:
256
+ if self.winsys == 'x11':
257
+ # X11 requires alpha to be set after window is visible
258
+ self.alpha = alpha
259
+ self.alpha_bind = self.bind("<Visibility>", on_visibility_alpha, '+')
260
+ else:
261
+ # Windows and macOS can set alpha immediately
262
+ self.attributes("-alpha", alpha)
263
+
264
+ def _setup_icon(self, iconphoto: Optional[str], default_icon_enabled: bool = True) -> None:
265
+ """Configure window icon from file path or use default.
266
+
267
+ Sets up the window icon, with support for:
268
+ - Custom icon from file path or PhotoImage object
269
+ - Default bootstack.png from package assets (when iconphoto is None and default_icon_enabled=True)
270
+ - No icon (when iconphoto is None and default_icon_enabled=False)
271
+
272
+ Args:
273
+ iconphoto: Path to icon image file, PhotoImage object, or None for default/no icon.
274
+ default_icon_enabled: Whether to use default icon when iconphoto is None.
275
+
276
+ Note:
277
+ The icon is stored in self._icon to prevent garbage collection (for PhotoImage icons).
278
+ On failure to load custom icon, falls back to default icon if enabled.
279
+ """
280
+ if iconphoto is None:
281
+ if default_icon_enabled:
282
+ try:
283
+ from pathlib import Path
284
+ import bootstack
285
+ assets = Path(bootstack.__file__).parent / 'assets'
286
+ if sys.platform == 'win32':
287
+ self.wm_iconbitmap(str(assets / 'bootstack.ico'))
288
+ else:
289
+ self._icon = tkinter.PhotoImage(file=str(assets / 'bootstack.png'), master=self)
290
+ self.iconphoto(True, self._icon)
291
+ except (ImportError, FileNotFoundError, tkinter.TclError, Exception):
292
+ pass
293
+ return
294
+
295
+ # User provided a custom icon — a path string or a photo-image object
296
+ # (Tk's PhotoImage or Pillow's ImageTk.PhotoImage, both accepted by
297
+ # iconphoto()).
298
+ try:
299
+ if isinstance(iconphoto, (str, os.PathLike)):
300
+ path = str(iconphoto)
301
+ if path.lower().endswith('.ico'):
302
+ self.wm_iconbitmap(path)
303
+ else:
304
+ self._icon = tkinter.PhotoImage(file=path, master=self)
305
+ self.iconphoto(True, self._icon)
306
+ else:
307
+ self._icon = iconphoto
308
+ self.iconphoto(True, self._icon)
309
+ except (tkinter.TclError, Exception) as e:
310
+ print(f'Failed to load icon: {iconphoto} - {e}')
311
+ if default_icon_enabled:
312
+ try:
313
+ from pathlib import Path
314
+ import bootstack
315
+ assets = Path(bootstack.__file__).parent / 'assets'
316
+ if sys.platform == 'win32':
317
+ self.wm_iconbitmap(str(assets / 'bootstack.ico'))
318
+ else:
319
+ self._icon = tkinter.PhotoImage(file=str(assets / 'bootstack.png'), master=self)
320
+ self.iconphoto(True, self._icon)
321
+ except (ImportError, FileNotFoundError, tkinter.TclError, Exception):
322
+ pass
323
+
324
+ def _handle_locale_changed(self, *_):
325
+ """Handle locale change events by updating the localized title."""
326
+ if self._title_message_id:
327
+ self.title(self._title_message_id)
328
+
329
+ # -------------------------------------------------------------------------
330
+ # Close handler chain
331
+ # -------------------------------------------------------------------------
332
+
333
+ def _init_close_handlers(self) -> None:
334
+ """Initialize the close handler list and register the dispatch function.
335
+
336
+ Idempotent — safe to call multiple times.
337
+ """
338
+ if not hasattr(self, '_close_handlers'):
339
+ self._close_handlers: List[Callable[[], bool | None]] = []
340
+ self.protocol("WM_DELETE_WINDOW", self._close_dispatch)
341
+
342
+ def _close_dispatch(self) -> None:
343
+ """WM_DELETE_WINDOW handler that runs registered close handlers.
344
+
345
+ Runs each handler in registration order. If any returns `False` the
346
+ close is vetoed and the window stays open. If all pass (return `None`
347
+ or `True`), `_do_close()` is called to perform the actual close.
348
+ """
349
+ for handler in list(getattr(self, '_close_handlers', [])):
350
+ try:
351
+ result = handler()
352
+ except Exception:
353
+ result = None
354
+ if result is False:
355
+ return
356
+ self._do_close()
357
+
358
+ def _do_close(self) -> None:
359
+ """Perform the default close action.
360
+
361
+ Subclasses may override this to change what happens after all handlers
362
+ pass. The default is `self.destroy()`.
363
+ """
364
+ try:
365
+ self.destroy()
366
+ except tkinter.TclError:
367
+ pass
368
+
369
+ def close(self) -> None:
370
+ """Close the window programmatically, bypassing the close handlers.
371
+
372
+ This is the uniform close contract every top-level window honors. It
373
+ performs the default close action directly (`_do_close()`), so the
374
+ veto handlers registered with `add_close_handler()` — which guard the
375
+ user clicking the window's close button — are not run. The application
376
+ root overrides this to quit its event loop instead.
377
+ """
378
+ self._do_close()
379
+
380
+ def add_close_handler(self, callback: Callable[[], bool | None]) -> None:
381
+ """Register a callback invoked when the window's close button is clicked.
382
+
383
+ Handlers are called in registration order. Return `False` from a
384
+ handler to veto the close; return `None` or `True` to allow it.
385
+ All handlers that pass are run before the window closes.
386
+
387
+ Args:
388
+ callback: Called with no arguments when close is requested.
389
+ Return `False` to cancel the close, `None` or `True` to proceed.
390
+ """
391
+ self._init_close_handlers()
392
+ self._close_handlers.append(callback)
393
+
394
+ def remove_close_handler(self, callback: Callable[[], bool | None]) -> None:
395
+ """Remove a previously registered close handler.
396
+
397
+ Args:
398
+ callback: The handler to remove. No-op if not found.
399
+ """
400
+ if hasattr(self, '_close_handlers'):
401
+ try:
402
+ self._close_handlers.remove(callback)
403
+ except ValueError:
404
+ pass
405
+
406
+ # -------------------------------------------------------------------------
407
+ # Window manager (wm) — pass-throughs with modern docstrings
408
+ # -------------------------------------------------------------------------
409
+
410
+ def show(self) -> None:
411
+ """Show the window after it has been fully initialized.
412
+
413
+ This forces a geometry/layout pass before mapping the window, which is
414
+ useful if you have performed setup that affects sizing.
415
+ """
416
+ # Apply Windows window style while still withdrawn to prevent flash.
417
+ # The update() flushes input/IO events so pywinstyles can attach to a
418
+ # fully realized HWND on Windows; on Aqua it can hang indefinitely
419
+ # processing children's pending events (see Dialog._position_dialog
420
+ # for the same pattern), so gate it on the platform that needs it.
421
+ self.update_idletasks()
422
+ self._apply_window_style()
423
+ if getattr(self, 'winsys', None) == 'win32':
424
+ self.update()
425
+
426
+ # Reveal-after-settle: some content can only realize its final size once
427
+ # the window is mapped (e.g. a ScrollView's canvas filling its viewport).
428
+ # Map the window invisibly, let it settle, then reveal it — so it never
429
+ # visibly shifts into place on open. Restores any prior alpha.
430
+ try:
431
+ prev_alpha = self.attributes('-alpha')
432
+ except tkinter.TclError:
433
+ prev_alpha = None
434
+ if prev_alpha is not None:
435
+ try:
436
+ self.attributes('-alpha', 0.0)
437
+ except tkinter.TclError:
438
+ prev_alpha = None
439
+
440
+ try:
441
+ self.deiconify()
442
+ self.update_idletasks()
443
+ if getattr(self, 'winsys', None) == 'win32':
444
+ self.update()
445
+ finally:
446
+ # Always restore visibility — if settling raised (e.g. a pending
447
+ # callback in update()), the window must not stay stuck at alpha 0.
448
+ if prev_alpha is not None:
449
+ try:
450
+ self.attributes('-alpha', prev_alpha)
451
+ except tkinter.TclError:
452
+ pass
453
+
454
+ def title(self, value: str | None = None) -> str:
455
+ """Get or set the window title.
456
+
457
+ If you localize UI text (e.g., via a message catalog), the title is
458
+ automatically translated using the MessageCatalog.
459
+
460
+ Args:
461
+ value: The new title text. If None, return the current title.
462
+
463
+ Returns:
464
+ The current title string (getter) or the title string after setting.
465
+ """
466
+ if value is None:
467
+ return super().title()
468
+ self._title_message_id = value
469
+ return super().title(MessageCatalog.translate(self._title_message_id))
470
+
471
+ def geometry(self, new_geometry: str | None = None) -> str:
472
+ """Get or set the window geometry.
473
+
474
+ Geometry strings use the standard Tk format:
475
+
476
+ - "{width}x{height}" (size only)
477
+ - "+{x}+{y}" (position only)
478
+ - "{width}x{height}+{x}+{y}" (size + position)
479
+
480
+ Args:
481
+ new_geometry: The geometry string to apply. If None, return the
482
+ current geometry string.
483
+
484
+ Returns:
485
+ The current geometry string (getter) or the geometry after setting.
486
+ """
487
+ return super().geometry(new_geometry)
488
+
489
+ def state(self, newstate: str | None = None) -> str:
490
+ """Get or set the window manager state.
491
+
492
+ Common states include:
493
+
494
+ - "normal": displayed normally
495
+ - "iconic": minimized (iconified)
496
+ - "withdrawn": hidden (not shown)
497
+ - "zoomed": maximized (platform/window-manager dependent)
498
+
499
+ Args:
500
+ newstate: State to apply. If None, return the current state.
501
+
502
+ Returns:
503
+ The current state string.
504
+ """
505
+ return super().state(newstate)
506
+
507
+ def attributes(self, *args: Any) -> Any:
508
+ """Get or set platform-specific window attributes.
509
+
510
+ This method forwards to Tk's "wm attributes" command. Common attributes:
511
+
512
+ - "-alpha" (float 0.0-1.0): transparency
513
+ - "-fullscreen" (bool): fullscreen mode
514
+ - "-topmost" (bool): keep window above others
515
+
516
+ The exact supported attributes vary by platform/window manager.
517
+
518
+ Args:
519
+ *args: Arguments accepted by Tk's `wm attributes`. Common forms are:
520
+ - (name,) to query a single attribute
521
+ - (name, value) to set an attribute
522
+ - () to query all supported attributes (platform dependent)
523
+
524
+ Returns:
525
+ The queried attribute value(s), or an implementation-dependent
526
+ result when setting.
527
+ """
528
+ return super().attributes(*args)
529
+
530
+ def iconify(self) -> None:
531
+ """Minimize (iconify) the window."""
532
+ return super().iconify()
533
+
534
+ def deiconify(self) -> None:
535
+ """Show a minimized or withdrawn window.
536
+
537
+ This restores a window that has been hidden with `withdraw()` or
538
+ minimized with `iconify()`.
539
+ """
540
+ return super().deiconify()
541
+
542
+ def withdraw(self) -> None:
543
+ """Hide the window without destroying it.
544
+
545
+ A withdrawn window is unmapped and typically removed from
546
+ taskbar/window lists. Use `deiconify()` to show it again.
547
+ """
548
+ return super().withdraw()
549
+
550
+ def resizable(self, width: bool | None = None, height: bool | None = None) -> tuple[int, int] | None:
551
+ """Get or set whether the user can resize the window.
552
+
553
+ Args:
554
+ width: If provided, enable/disable horizontal resizing.
555
+ height: If provided, enable/disable vertical resizing.
556
+
557
+ Returns:
558
+ When called as a getter (both args None), returns `(width_flag, height_flag)`
559
+ where each flag is 0/1. When called as a setter, Tk returns None.
560
+ """
561
+ return super().resizable(width, height)
562
+
563
+ def minsize(self, width: int | None = None, height: int | None = None) -> tuple[int, int] | None:
564
+ """Get or set the minimum window size in pixels.
565
+
566
+ Args:
567
+ width: Minimum width in pixels. If None, act as a getter.
568
+ height: Minimum height in pixels. If None, act as a getter.
569
+
570
+ Returns:
571
+ When called as a getter (both args None), returns `(width, height)`.
572
+ When called as a setter, Tk returns None.
573
+ """
574
+ return super().minsize(width, height)
575
+
576
+ def maxsize(self, width: int | None = None, height: int | None = None) -> tuple[int, int] | None:
577
+ """Get or set the maximum window size in pixels.
578
+
579
+ Args:
580
+ width: Maximum width in pixels. If None, act as a getter.
581
+ height: Maximum height in pixels. If None, act as a getter.
582
+
583
+ Returns:
584
+ When called as a getter (both args None), returns `(width, height)`.
585
+ When called as a setter, Tk returns None.
586
+ """
587
+ return super().maxsize(width, height)
588
+
589
+ def transient(self, master: tkinter.Misc | None = None) -> tkinter.Misc | None:
590
+ """Get or set the transient parent (window relationship).
591
+
592
+ Transient windows typically stay on top of their parent and may be
593
+ omitted from the taskbar. This is commonly used for dialogs.
594
+
595
+ Args:
596
+ master: The parent window. If None, return the current transient parent.
597
+
598
+ Returns:
599
+ The current transient parent (getter) or the provided master (setter),
600
+ depending on Tk/platform behavior.
601
+ """
602
+ return super().transient(master)
603
+
604
+ def protocol(self, name: str, func: Callable[[], Any] | None = None) -> Any:
605
+ """Get or set a window manager protocol handler.
606
+
607
+ The most common protocol is "WM_DELETE_WINDOW" (close button).
608
+
609
+ Args:
610
+ name: Protocol name.
611
+ func: Handler to register. If None, return the current handler (if any).
612
+
613
+ Returns:
614
+ The current handler when queried, or an implementation-dependent result
615
+ when setting.
616
+ """
617
+ return super().protocol(name, func)
618
+
619
+ def overrideredirect(self, boolean: bool | None = None) -> bool | None:
620
+ """Get or set override-redirect mode.
621
+
622
+ When enabled, the window manager typically does not decorate or manage
623
+ the window (no title bar/borders). Useful for popups/menus; use with care.
624
+
625
+ Note:
626
+ On macOS (Aqua), overrideredirect is disabled due to Tk/Cocoa issues
627
+ with click handling and event processing that can cause crashes.
628
+
629
+ Args:
630
+ boolean: True to enable override-redirect, False to disable. If None,
631
+ return the current value.
632
+
633
+ Returns:
634
+ The current override-redirect value when queried, or None when set.
635
+ """
636
+ # Skip overrideredirect on macOS - causes click handling issues and crashes
637
+ if boolean and hasattr(self, 'winsys') and self.winsys == 'aqua':
638
+ return None
639
+ return super().overrideredirect(boolean)
640
+
641
+ # -------------------------------------------------------------------------
642
+ # Convenience wrappers: intent-based names for common tasks
643
+ # -------------------------------------------------------------------------
644
+
645
+ def on_close(self, handler: Callable[[], bool | None]) -> None:
646
+ """Register a handler for the window close button.
647
+
648
+ Equivalent to `add_close_handler(handler)`. Handlers are called in
649
+ registration order; return `False` to veto the close.
650
+
651
+ Args:
652
+ handler: Called with no arguments when close is requested.
653
+ Return `False` to cancel, `None` or `True` to proceed.
654
+ """
655
+ self.add_close_handler(handler)
656
+
657
+ def hide(self) -> None:
658
+ """Hide the window (alias for `withdraw()`)."""
659
+ self.withdraw()
660
+
661
+ def minimize(self) -> None:
662
+ """Minimize the window (alias for `iconify()`)."""
663
+ self.iconify()
664
+
665
+ def maximize(self) -> None:
666
+ """Maximize the window where supported.
667
+
668
+ Tk uses `state("zoomed")` to request maximized windows on some platforms.
669
+ On unsupported window managers this may raise `tkinter.TclError`.
670
+ """
671
+ try:
672
+ self.state("zoomed")
673
+ except tkinter.TclError:
674
+ pass
675
+
676
+ def set_topmost(self, value: bool = True) -> None:
677
+ """Enable/disable always-on-top behavior where supported.
678
+
679
+ Args:
680
+ value: True to keep the window above others; False to disable.
681
+ """
682
+ try:
683
+ self.attributes("-topmost", bool(value))
684
+ except tkinter.TclError:
685
+ pass
686
+
687
+ # Backward compatibility alias
688
+ keep_on_top = set_topmost
689
+
690
+ def set_fullscreen(self, value: bool = True) -> None:
691
+ """Enable/disable fullscreen where supported.
692
+
693
+ Args:
694
+ value: True to enter fullscreen; False to exit.
695
+ """
696
+ try:
697
+ self.attributes("-fullscreen", bool(value))
698
+ except tkinter.TclError:
699
+ pass
700
+
701
+ def set_alpha(self, value: float) -> None:
702
+ """Set window opacity where supported.
703
+
704
+ Args:
705
+ value: Opacity from 0.0 (transparent) to 1.0 (opaque).
706
+ """
707
+ try:
708
+ self.attributes("-alpha", float(value))
709
+ except tkinter.TclError:
710
+ pass
711
+
712
+ # -------------------------------------------------------------------------
713
+ # Positioning utilities
714
+ # -------------------------------------------------------------------------
715
+
716
+ def place_center(self) -> None:
717
+ """Position the window in the center of the screen.
718
+
719
+ Centers the window on the primary display. For multi-monitor setups,
720
+ this typically centers on the monitor containing the mouse pointer.
721
+
722
+ The window geometry is updated immediately.
723
+
724
+ Examples:
725
+ >>> app = App()
726
+ >>> app.place_center()
727
+ >>> app.mainloop()
728
+ """
729
+ x, y = WindowPositioning.center_on_screen(self)
730
+ x, y = WindowPositioning.ensure_on_screen(self, x, y)
731
+ self.geometry(f'+{x}+{y}')
732
+
733
+ def place_center_on(self, parent: tkinter.Misc) -> None:
734
+ """Position the window centered on a parent widget or window.
735
+
736
+ Centers this window on the specified parent widget or window.
737
+ Commonly used for dialogs to center them on their parent window.
738
+
739
+ Args:
740
+ parent: The parent widget or window to center on.
741
+
742
+ Examples:
743
+ >>> parent = App()
744
+ >>> dialog = Toplevel()
745
+ >>> dialog.place_center_on(parent)
746
+ """
747
+ x, y = WindowPositioning.center_on_parent(self, parent)
748
+ x, y = WindowPositioning.ensure_on_screen(self, x, y)
749
+ self.geometry(f'+{x}+{y}')
750
+
751
+ def place_at(self, x: int, y: int, ensure_visible: bool = True) -> None:
752
+ """Position the window at specific screen coordinates.
753
+
754
+ Places the window at the given (x, y) coordinates, optionally
755
+ adjusting the position to ensure the window remains fully visible
756
+ on screen.
757
+
758
+ Args:
759
+ x: X coordinate on screen (in pixels from left edge).
760
+ y: Y coordinate on screen (in pixels from top edge).
761
+ ensure_visible: If True, adjusts coordinates to keep window on screen.
762
+
763
+ Examples:
764
+ >>> window = App()
765
+ >>> window.place_at(100, 100)
766
+ """
767
+ if ensure_visible:
768
+ x, y = WindowPositioning.ensure_on_screen(self, x, y)
769
+ self.geometry(f'+{x}+{y}')
770
+
771
+ def place_anchor(
772
+ self,
773
+ anchor_to: tkinter.Misc,
774
+ anchor_point: AnchorPoint = 'sw',
775
+ window_point: AnchorPoint = 'nw',
776
+ offset: Tuple[int, int] = (0, 0),
777
+ ensure_visible: bool = True
778
+ ) -> None:
779
+ """Position window relative to another widget using anchor points.
780
+
781
+ Uses tkinter's standard anchor naming (n, s, e, w, ne, nw, se, sw, center).
782
+ This is useful for dropdowns, tooltips, context menus, and popovers.
783
+
784
+ Args:
785
+ anchor_to: The widget to anchor this window to.
786
+ anchor_point: Which point on the anchor widget to use (default 'sw').
787
+ window_point: Which point on this window to align (default 'nw').
788
+ offset: Additional (x, y) offset in pixels.
789
+ ensure_visible: If True, adjusts position to keep window on screen.
790
+
791
+ Examples:
792
+ >>> # Show dialog below button
793
+ >>> dialog = Toplevel()
794
+ >>> dialog.place_anchor(
795
+ ... anchor_to=button,
796
+ ... anchor_point='sw', # button's bottom-left
797
+ ... window_point='nw', # dialog's top-left
798
+ ... offset=(0, 5)
799
+ ... )
800
+ """
801
+ WindowPositioning.position_with_anchor(
802
+ window=self,
803
+ anchor_to=anchor_to,
804
+ anchor_point=anchor_point,
805
+ window_point=window_point,
806
+ offset=offset,
807
+ ensure_visible=ensure_visible
808
+ )
809
+
810
+ def place_dropdown(
811
+ self,
812
+ trigger_widget: tkinter.Misc,
813
+ prefer_below: bool = True,
814
+ align: Literal['left', 'right', 'center'] = 'left',
815
+ offset: Tuple[int, int] = (0, 2),
816
+ ensure_visible: bool = True,
817
+ auto_flip: bool = True
818
+ ) -> None:
819
+ """Position window as a dropdown relative to a trigger widget.
820
+
821
+ Smart positioning that automatically flips above/below based on
822
+ available space. Perfect for combobox dropdowns, autocomplete
823
+ suggestions, and dropdown menus.
824
+
825
+ Args:
826
+ trigger_widget: The widget that triggers the dropdown.
827
+ prefer_below: If True, tries to show below trigger; else tries above.
828
+ align: Horizontal alignment ('left', 'right', or 'center').
829
+ offset: Additional (x, y) offset in pixels.
830
+ ensure_visible: If True, adjusts position to keep window on screen.
831
+ auto_flip: If True, automatically flips above/below if no room.
832
+
833
+ Examples:
834
+ >>> # Dropdown menu below button
835
+ >>> menu = Toplevel()
836
+ >>> menu.place_dropdown(
837
+ ... trigger_widget=button,
838
+ ... prefer_below=True,
839
+ ... align='left'
840
+ ... )
841
+ """
842
+ WindowPositioning.position_dropdown(
843
+ window=self,
844
+ trigger_widget=trigger_widget,
845
+ prefer_below=prefer_below,
846
+ align=align,
847
+ offset=offset,
848
+ ensure_visible=ensure_visible,
849
+ auto_flip=auto_flip
850
+ )
851
+
852
+ def place_cursor(
853
+ self,
854
+ offset: Tuple[int, int] = (5, 5),
855
+ ensure_visible: bool = True
856
+ ) -> None:
857
+ """Position window at the current mouse cursor location.
858
+
859
+ Useful for context menus, tooltips that follow the cursor, or
860
+ click-to-show dialogs.
861
+
862
+ Args:
863
+ offset: Additional (x, y) offset from cursor in pixels.
864
+ ensure_visible: If True, adjusts position to keep window on screen.
865
+
866
+ Examples:
867
+ >>> # Show context menu at cursor
868
+ >>> menu = Toplevel()
869
+ >>> menu.place_cursor(offset=(2, 2))
870
+ """
871
+ WindowPositioning.position_at_cursor(
872
+ window=self,
873
+ offset=offset,
874
+ ensure_visible=ensure_visible
875
+ )
876
+
877
+ # Backward compatibility aliases
878
+ def place_window_center(self) -> None:
879
+ """Alias for place_center(). Deprecated, use place_center() instead."""
880
+ self.place_center()
881
+
882
+ position_center = place_center # Additional alias for compatibility
883
+
884
+ # -------------------------------------------------------------------------
885
+ # Sizing utilities
886
+ # -------------------------------------------------------------------------
887
+
888
+ def set_default_size(
889
+ self,
890
+ width_ratio: float = 0.6,
891
+ height_ratio: float = 0.7,
892
+ min_width: int = 400,
893
+ min_height: int = 300,
894
+ max_width: Optional[int] = None,
895
+ max_height: Optional[int] = None
896
+ ) -> None:
897
+ """Set window size as a percentage of screen size.
898
+
899
+ Calculates and applies a window size based on screen dimensions,
900
+ constrained by minimum and optional maximum sizes. Useful for
901
+ creating responsive windows that adapt to different screen sizes.
902
+
903
+ Args:
904
+ width_ratio: Proportion of screen width (0.0 to 1.0).
905
+ height_ratio: Proportion of screen height (0.0 to 1.0).
906
+ min_width: Minimum window width in pixels.
907
+ min_height: Minimum window height in pixels.
908
+ max_width: Optional maximum window width in pixels.
909
+ max_height: Optional maximum window height in pixels.
910
+
911
+ Examples:
912
+ >>> window = App()
913
+ >>> window.set_default_size(width_ratio=0.8, height_ratio=0.8)
914
+ >>> # Window will be 80% of screen size
915
+ """
916
+ width, height = WindowSizing.get_default_size(
917
+ self, width_ratio, height_ratio,
918
+ min_width, min_height, max_width, max_height
919
+ )
920
+ self.geometry(f"{width}x{height}")
921
+
922
+ def apply_size_constraints(
923
+ self,
924
+ minsize: Optional[tuple[int, int]] = None,
925
+ maxsize: Optional[tuple[int, int]] = None,
926
+ resizable: Optional[tuple[bool, bool]] = None
927
+ ) -> None:
928
+ """Apply multiple size constraints at once.
929
+
930
+ Convenience method to set minsize, maxsize, and resizable in one call.
931
+
932
+ Args:
933
+ minsize: Optional (width, height) minimum size.
934
+ maxsize: Optional (width, height) maximum size.
935
+ resizable: Optional (width, height) resizable flags.
936
+
937
+ Examples:
938
+ >>> window = App()
939
+ >>> window.apply_size_constraints(
940
+ ... minsize=(400, 300),
941
+ ... resizable=(True, False) # Width resizable, height fixed
942
+ ... )
943
+ """
944
+ WindowSizing.apply_size_constraints(self, minsize, maxsize, resizable)
945
+