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,118 @@
1
+ from __future__ import annotations
2
+
3
+ from tkinter import ttk
4
+ from typing import Any, Literal, TYPE_CHECKING, TypedDict
5
+
6
+ from typing_extensions import Unpack
7
+
8
+ from bootstack.core.mixins.ttk_state import TtkStateMixin
9
+ from bootstack.core.mixins.widget import WidgetCapabilitiesMixin
10
+ from bootstack.widgets.internal.wrapper_base import TTKWrapperBase
11
+ from bootstack.widgets.mixins import IconMixin, LocalizationMixin, TextSignalMixin
12
+ from bootstack.widgets.types import Master
13
+
14
+ if TYPE_CHECKING:
15
+ from bootstack.core.signals import Signal
16
+
17
+
18
+ class MenuButtonKwargs(TypedDict, total=False):
19
+ # Standard ttk.Menubutton options
20
+ text: Any
21
+ image: Any
22
+ icon: Any
23
+ icon_only: bool
24
+ compound: Literal['text', 'image', 'top', 'bottom', 'left', 'right', 'center', 'none'] | str
25
+ direction: Any
26
+ menu: Any
27
+ padding: Any
28
+ state: Literal['normal', 'active', 'disabled', 'readonly'] | str
29
+ takefocus: Any
30
+ style: str
31
+ class_: str
32
+ cursor: str
33
+ name: str
34
+ textvariable: Any
35
+ textsignal: Signal[Any]
36
+
37
+ # bootstack-specific extensions
38
+ bootstyle: str # DEPRECATED: Use accent and variant instead
39
+ accent: str
40
+ density: Literal['default', 'compact']
41
+ variant: str
42
+ surface: str
43
+ style_options: dict[str, Any]
44
+ localize: bool | Literal['auto']
45
+
46
+
47
+ class MenuButton(LocalizationMixin, TextSignalMixin, IconMixin, TTKWrapperBase, WidgetCapabilitiesMixin, TtkStateMixin, ttk.Menubutton):
48
+ """bootstack wrapper for `ttk.Menubutton` with bootstyle and icon support."""
49
+
50
+ _ttk_base = ttk.Menubutton
51
+
52
+ def __init__(self, master: Master = None, **kwargs: Unpack[MenuButtonKwargs]) -> None:
53
+ """Create a themed bootstack Menubutton.
54
+
55
+ Args:
56
+ master: Parent widget. If None, uses the default root window.
57
+
58
+ Other Parameters:
59
+ text (str): Text to display in the menubutton.
60
+ image (PhotoImage): Image to display.
61
+ icon (str | dict): Theme-aware icon spec handled by the style system.
62
+ icon_only (bool): If True, removes the additional padding reserved for label text.
63
+ compound (str): Placement of the image relative to text.
64
+ direction (str): Direction for the menu to appear.
65
+ menu (Menu): Associated tk.Menu instance.
66
+ padding (int | tuple): Extra space around the content.
67
+ density (str): The vertical and horizontal compactness of widget content, e.g. 'default', 'compact'.
68
+ state (str): Widget state.
69
+ takefocus (bool): Whether the widget participates in focus traversal.
70
+ textvariable (Variable): Tk variable linked to the text.
71
+ textsignal (Signal): Reactive Signal linked to the text (auto-synced with textvariable).
72
+ style (str): Explicit ttk style name (overrides accent/variant).
73
+ accent (str): Accent token for styling, e.g. 'primary', 'danger', 'success'.
74
+ variant (str): Style variant, e.g. 'solid', 'outline', 'ghost'.
75
+ bootstyle (str): DEPRECATED - Use `accent` and `variant` instead.
76
+ Combined style tokens (e.g., 'primary', 'ghost').
77
+ surface (str): Optional surface token; otherwise inherited.
78
+ style_options (dict): Optional dict forwarded to the style builder.
79
+ localize (bool | Literal['auto']): Determines the widget's localization mode.
80
+ """
81
+ kwargs.update(style_options=self._capture_style_options(['icon_only', 'icon', 'density'], kwargs))
82
+ super().__init__(master, **kwargs)
83
+
84
+ # When a native tk.Menu is attached, post it ourselves with the
85
+ # focus-ring affordance offset so it aligns with the visible button
86
+ # border. Subclasses (OptionMenu, DropdownButton) use a ContextMenu
87
+ # and override show_menu, so this only affects the native path.
88
+ # On Aqua, Tk's native Menubutton class binding drives an NSMenu
89
+ # state machine that manages mouse/focus capture; bypassing it with
90
+ # a manual `menu post` strands focus after item selection, so we
91
+ # skip the offset path on Mac and let the default class binding run.
92
+ if self.tk.call('tk', 'windowingsystem') != 'aqua':
93
+ self.bind("<Button-1>", self._on_button_press, add="+")
94
+
95
+ def _on_button_press(self, _event):
96
+ """Focus self, and if a native menu is attached, post it with offset."""
97
+ self.focus_set()
98
+ try:
99
+ menu_path = self.cget('menu')
100
+ except Exception:
101
+ menu_path = None
102
+ if not menu_path:
103
+ return None
104
+ try:
105
+ # If the menu is already visible, unpost so a second click closes it.
106
+ if int(self.tk.eval(f"winfo viewable {menu_path}")):
107
+ self.tk.call(menu_path, 'unpost')
108
+ return 'break'
109
+ from bootstack.style.bootstyle_builder_base import BootstyleBuilderBase
110
+ offset = BootstyleBuilderBase.scale_from_source(10)
111
+ x = self.winfo_rootx() + offset
112
+ y = self.winfo_rooty() + self.winfo_height()
113
+ self.tk.call(menu_path, 'post', x, y)
114
+ except Exception:
115
+ return None
116
+ # Suppress Tk's default class binding so it doesn't also post the
117
+ # menu at its un-offset position.
118
+ return 'break'
@@ -0,0 +1,551 @@
1
+ from __future__ import annotations
2
+
3
+ import tkinter
4
+ from tkinter import ttk
5
+ from typing import Any, Callable, Literal, Optional, TypedDict
6
+
7
+ from typing_extensions import Unpack
8
+
9
+ from bootstack.core import NavigationError
10
+ from bootstack.core.mixins.ttk_state import TtkStateMixin
11
+ from bootstack.core.mixins.widget import WidgetCapabilitiesMixin
12
+ from bootstack.core.localization import MessageCatalog
13
+ from bootstack.widgets.internal.wrapper_base import TTKWrapperBase
14
+ from bootstack.widgets.primitives import Frame
15
+ from bootstack.widgets.types import Master
16
+
17
+ ChangeReason = Literal['user', 'api', 'hide', 'forget', 'reorder', 'unknown']
18
+ ChangeMethod = Literal['click', 'key', 'programmatic', 'unknown']
19
+
20
+ Tab = tkinter.Widget | int | str
21
+
22
+
23
+ class TabRef(TypedDict):
24
+ index: int | None
25
+ key: str | None
26
+ label: str | None
27
+
28
+
29
+ class NotebookKwargs(TypedDict, total=False):
30
+ # Standard ttk.Notebook options
31
+ padding: Any
32
+ height: int
33
+ width: int
34
+ style: str
35
+ class_: str
36
+ cursor: str
37
+ name: str
38
+
39
+ # bootstack-specific extensions
40
+ bootstyle: str # DEPRECATED: Use accent and variant instead
41
+ accent: str
42
+ variant: str
43
+ surface: str
44
+ style_options: dict[str, Any]
45
+
46
+
47
+ class TabOptions(TypedDict, total=False):
48
+ state: Literal['normal', 'disabled', 'hidden']
49
+ sticky: str
50
+ padding: str | float | tuple[str | float] | tuple[str | float, str | float] | tuple[
51
+ str | float, str | float, str | float] | tuple[str | float, str | float, str | float, str | float]
52
+ text: str
53
+ compound: Literal["top", "left", "center", "right", "bottom", "none"]
54
+ image: Any
55
+ underline: int
56
+ fmtargs: tuple[Any, ...] | list[Any]
57
+
58
+
59
+ class Notebook(TTKWrapperBase, WidgetCapabilitiesMixin, TtkStateMixin, ttk.Notebook):
60
+ """A themed tabbed container widget with enhanced navigation and event tracking.
61
+
62
+ The Notebook widget provides a tabbed interface where only one tab's content
63
+ is visible at a time. Tabs can be referenced by key (str), index (int), or
64
+ widget instance.
65
+
66
+ !!! note "Events"
67
+
68
+ - `<<NotebookTabChange>>`: Triggered when the selected tab changes.
69
+ - `<<NotebookTabActivate>>`: Triggered when a tab becomes active.
70
+ - `<<NotebookTabDeactivate>>`: Triggered when a tab becomes inactive.
71
+
72
+ All events provide `event.data` with keys: `current` (TabRef), `previous`
73
+ (TabRef), `reason` ('user', 'api', 'hide', 'forget', 'reorder'),
74
+ `via` ('click', 'key', 'programmatic').
75
+ """
76
+
77
+ _ttk_base = ttk.Notebook
78
+
79
+ def __init__(self, master: Master = None, **kwargs: Unpack[NotebookKwargs]) -> None:
80
+ """Create a themed bootstack Notebook with optional bootstyle extensions.
81
+
82
+ Args:
83
+ master: Parent widget. If None, uses the default root window.
84
+
85
+ Other Parameters:
86
+ padding (int | tuple): Extra space around the tab header and pane area.
87
+ height (int): Requested widget height in pixels.
88
+ width (int): Requested widget width in pixels.
89
+ style (str): Explicit ttk style name that overrides accent/variant.
90
+ accent (str): Accent token for styling, e.g. 'primary', 'secondary'.
91
+ variant (str): Style variant (if applicable).
92
+ bootstyle (str): DEPRECATED - Use `accent` and `variant` instead.
93
+ Combined style tokens (e.g., 'primary', 'secondary').
94
+ surface (str): Optional surface color token; inherits from the current theme if omitted.
95
+ style_options (dict): Additional options forwarded to the style builder.
96
+ """
97
+ super().__init__(master, **kwargs)
98
+ self._key_registry: dict[str, tkinter.Misc] = {} # key -> widget
99
+ self._tk_to_key: dict[str, str] = {} # tk id -> key
100
+ self._auto_counter = 0 # for auto keys: tab1, tab2, ...
101
+ self._tab_locale_tokens: dict[str, tuple[str, tuple[Any, ...]]] = {}
102
+ self.bind("<<LocaleChanged>>", self._refresh_tab_labels, add="+")
103
+
104
+ # change tracking for enriched events
105
+ self._last_selected: str | None = None
106
+ self._last_change_reason: ChangeReason = 'unknown'
107
+ self._last_change_via: ChangeMethod = 'unknown'
108
+
109
+ # ---- Internal helpers ----
110
+ def _mark_api_change(self, reason: ChangeReason = 'api') -> None:
111
+ """Record a programmatic change reason so the next change can report it.
112
+
113
+ This method tracks the reason for tab changes initiated through the API
114
+ (as opposed to user interactions), allowing event handlers to provide
115
+ detailed change context.
116
+
117
+ Args:
118
+ reason: The reason for the change ('api', 'hide', 'forget', 'reorder').
119
+ """
120
+ self._last_change_reason = reason
121
+ self._last_change_via = 'programmatic'
122
+
123
+ def _make_key(self, key: Optional[str]) -> str:
124
+ """Return a unique, stable key for a tab; auto generated (tabN) if none provided.
125
+
126
+ Args:
127
+ key: Optional tab key. If None, generates an auto key like 'tab1', 'tab2', etc.
128
+
129
+ Returns:
130
+ A unique tab key string.
131
+
132
+ Raises:
133
+ NavigationError: If the key already exists in the registry.
134
+ ValueError: If key is an empty string.
135
+ """
136
+ if key is not None and not key:
137
+ raise ValueError("Tab key cannot be an empty string")
138
+
139
+ if key in self._key_registry:
140
+ raise NavigationError(
141
+ message=f"Duplicate tab key: {key}",
142
+ hint="Tab keys must be unique per Notebook."
143
+ )
144
+ elif key is not None:
145
+ return key
146
+
147
+ # auto-generate
148
+ while True:
149
+ self._auto_counter += 1
150
+ key = f"tab{self._auto_counter}"
151
+ if key not in self._key_registry:
152
+ return key
153
+
154
+ def _to_tab_id(self, tab: Tab) -> str:
155
+ """Resolve a tab reference (key/index/widget) to a tkinter tab ID.
156
+
157
+ This method provides flexible tab referencing by accepting multiple
158
+ input types and converting them to the tkinter widget ID string format
159
+ that the underlying ttk.Notebook expects.
160
+
161
+ Args:
162
+ tab: Tab reference, which can be:
163
+ - A widget instance (tkinter.Widget)
164
+ - An integer index (0-based position)
165
+ - A string key (from _key_registry)
166
+ - A string widget ID (fallback)
167
+
168
+ Returns:
169
+ The tkinter widget ID string for the tab.
170
+
171
+ Raises:
172
+ NavigationError: If the tab reference is invalid, out of range,
173
+ or of an unsupported type.
174
+ """
175
+ # tab is widget
176
+ if isinstance(tab, tkinter.Misc):
177
+ return str(tab)
178
+
179
+ # tab is index
180
+ if isinstance(tab, int):
181
+ # index
182
+ tabs = self.tab()
183
+ try:
184
+ return tabs[tab]
185
+ except Exception:
186
+ raise NavigationError(
187
+ message=f"Tab index out of range: {tab}",
188
+ hint=f"Valid range is 0..{len(tabs) - 1}."
189
+ ) from None
190
+
191
+ # tab is key
192
+ if isinstance(tab, str) and tab in self._key_registry:
193
+ return str(self._key_registry[tab])
194
+
195
+ # fallback: assume tab is widget id
196
+ if isinstance(tab, str):
197
+ return tab
198
+
199
+ raise NavigationError(
200
+ message=f"Unsupported tab reference: {tab}",
201
+ hint="Use a tab key (str), index (int), or widget"
202
+ )
203
+
204
+ def _tab_ref(self, tabid: str | None) -> TabRef | None:
205
+ """Return a simplified tab reference ({index, key, label}) or None if invalid.
206
+
207
+ Args:
208
+ tabid: The tkinter widget ID of the tab to reference.
209
+
210
+ Returns:
211
+ A TabRef dictionary with index, key, and label fields, or None if the
212
+ tabid is invalid or doesn't exist in the notebook.
213
+ """
214
+ ref: TabRef = {"index": None, "key": None, "label": None}
215
+ if not tabid:
216
+ return None
217
+ try:
218
+ ref['index'] = self.index(tabid)
219
+ ref['label'] = self.tab(tabid, 'text')
220
+ except tkinter.TclError:
221
+ return None
222
+ ref['key'] = self._tk_to_key.get(tabid)
223
+ return ref
224
+
225
+ def _register_tab_token(self, tabid: str, token: str | None, fmtargs: tuple[Any, ...]) -> None:
226
+ """Track the semantic key used for a tab so it can be retranslated."""
227
+ if not token:
228
+ self._tab_locale_tokens.pop(tabid, None)
229
+ return
230
+ self._tab_locale_tokens[tabid] = (token, fmtargs)
231
+
232
+ def _refresh_tab_labels(self, event: Any = None) -> None:
233
+ """Refresh all tab labels when the locale changes."""
234
+ for tabid, (token, fmtargs) in list(self._tab_locale_tokens.items()):
235
+ text = MessageCatalog.translate(token, *fmtargs)
236
+ ttk.Notebook.tab(self, tabid, text=text)
237
+
238
+
239
+ def hide(self, tab: Tab) -> None:
240
+ """Hide a tab without removing it; selection may change implicitly"""
241
+ self._mark_api_change('hide')
242
+ super().hide(self._to_tab_id(tab))
243
+
244
+ def index(self, tab: Tab) -> int:
245
+ """Return the current position of a tab"""
246
+ return super().index(self._to_tab_id(tab))
247
+
248
+ def select(self, tab: Tab = None) -> str | None:
249
+ """Select a tab or return the current tab id"""
250
+ if tab is None:
251
+ return super().select()
252
+ else:
253
+ self._mark_api_change('api')
254
+ return super().select(self._to_tab_id(tab))
255
+
256
+ def add(
257
+ self,
258
+ child: tkinter.Widget = None,
259
+ *,
260
+ key: str | None = None,
261
+ text: str | None = None,
262
+ state: Literal['normal', 'disabled', 'hidden'] | None = None,
263
+ sticky: str | None = None,
264
+ compound: Literal['top', 'left', 'center', 'right', 'bottom', 'none'] | None = None,
265
+ image: Any = None,
266
+ underline: int | None = None,
267
+ fmtargs: tuple[Any, ...] | list[Any] = (),
268
+ **kwargs
269
+ ) -> tkinter.Widget:
270
+ """Add a new tab to the notebook, optionally creating a Frame.
271
+
272
+ If the widget is currently managed by the notebook but hidden, it is
273
+ restored to its previous position.
274
+
275
+ Args:
276
+ child (Widget | None): The widget to add as a tab. If None, creates a Frame.
277
+ key (str | None): A unique human-friendly identifier for referencing the tab.
278
+ text (str | None): The text of the tab label.
279
+ state (str | None): One of 'normal', 'disabled', 'hidden'.
280
+ sticky (str | None): How the content is positioned in the pane area.
281
+ compound (str | None): Image placement relative to text.
282
+ image (PhotoImage | None): The image to display in the tab.
283
+ underline (int | None): Index of character to underline in the label.
284
+ fmtargs (tuple | list): Format arguments for localized text.
285
+ **kwargs: When child is None, these are passed to Frame (e.g., padding, bootstyle).
286
+
287
+ Returns:
288
+ Widget: The tab content widget (passed or created Frame).
289
+ """
290
+ return self.insert(
291
+ 'end', child, key=key, text=text, state=state, sticky=sticky,
292
+ compound=compound, image=image, underline=underline, fmtargs=fmtargs, **kwargs
293
+ )
294
+
295
+ def insert(
296
+ self,
297
+ index: str | int,
298
+ child: tkinter.Widget = None,
299
+ *,
300
+ key: str | None = None,
301
+ text: str | None = None,
302
+ state: Literal['normal', 'disabled', 'hidden'] | None = None,
303
+ sticky: str | None = None,
304
+ compound: Literal['top', 'left', 'center', 'right', 'bottom', 'none'] | None = None,
305
+ image: Any = None,
306
+ underline: int | None = None,
307
+ fmtargs: tuple[Any, ...] | list[Any] = (),
308
+ **kwargs
309
+ ) -> tkinter.Widget:
310
+ """Insert a widget as a tab at position `index`, optionally creating a Frame.
311
+
312
+ Args:
313
+ index (str | int): Position to insert the widget. Defaults to 'end'.
314
+ child (Widget | None): The widget to insert as a tab. If None, creates a Frame.
315
+ key (str | None): A unique human-friendly identifier for referencing the tab.
316
+ text (str | None): The text of the tab label.
317
+ state (str | None): One of 'normal', 'disabled', 'hidden'.
318
+ sticky (str | None): How the content is positioned in the pane area.
319
+ compound (str | None): Image placement relative to text.
320
+ image (PhotoImage | None): The image to display in the tab.
321
+ underline (int | None): Index of character to underline in the label.
322
+ fmtargs (tuple | list): Format arguments for localized text.
323
+ **kwargs: When child is None, these are passed to Frame (e.g., padding, bootstyle).
324
+
325
+ Returns:
326
+ Widget: The tab content widget (passed or created Frame).
327
+ """
328
+ # Create Frame with kwargs if no child provided
329
+ if child is None:
330
+ child = Frame(self, **kwargs)
331
+
332
+ self._mark_api_change('reorder')
333
+
334
+ # Build tab options dict (only include non-None values)
335
+ tab_opts = {}
336
+ if text is not None:
337
+ tab_opts['text'] = MessageCatalog.translate(text, *fmtargs)
338
+ if state is not None:
339
+ tab_opts['state'] = state
340
+ if sticky is not None:
341
+ tab_opts['sticky'] = sticky
342
+ if compound is not None:
343
+ tab_opts['compound'] = compound
344
+ if image is not None:
345
+ tab_opts['image'] = image
346
+ if underline is not None:
347
+ tab_opts['underline'] = underline
348
+
349
+ super().insert(index, child, **tab_opts)
350
+ tab_key = self._make_key(key)
351
+ self._tk_to_key[str(child)] = tab_key
352
+ self._key_registry[tab_key] = child
353
+ self._register_tab_token(str(child), text, tuple(fmtargs))
354
+ return child
355
+
356
+ def remove(self, tab: Tab) -> None:
357
+ """Remove a tab and clean registry"""
358
+ self._mark_api_change('forget')
359
+ tabid = self._to_tab_id(tab)
360
+ key = self._tk_to_key.pop(tabid, None)
361
+ if key:
362
+ self._key_registry.pop(key, None)
363
+ self._tab_locale_tokens.pop(tabid, None)
364
+ super().forget(tabid)
365
+
366
+ def forget(self, tab: Tab) -> None:
367
+ """Hide or forget a tab while keeping the registry consistent."""
368
+ tabid = self._to_tab_id(tab)
369
+ self._tab_locale_tokens.pop(tabid, None)
370
+ super().forget(tabid)
371
+
372
+ def tab(self, tab: Tab, option: str = None, **kwargs) -> Any:
373
+ """Configure or query tab configuration.
374
+
375
+ Args:
376
+ tab (Tab): The tab to configure. Can be an index, key, or widget.
377
+ option (str): The option to query.
378
+
379
+ Other Parameters:
380
+ state (str): One of 'normal', 'disabled', 'hidden'.
381
+ sticky (str): How the content is positioned in the pane area.
382
+ padding (int | tuple): Extra space between notebook and pane.
383
+ text (str): The text of the tab label.
384
+ compound (str): Image placement relative to text.
385
+ image (PhotoImage): The image to display in the tab.
386
+ underline (int): Index of character to underline in the label.
387
+
388
+ Returns:
389
+ Any: The value of option if specified, otherwise None.
390
+ """
391
+ tabid = self._to_tab_id(tab)
392
+ fmtargs = tuple(kwargs.pop('fmtargs', ()))
393
+ text_token = kwargs.get('text')
394
+ if text_token is not None:
395
+ kwargs['text'] = MessageCatalog.translate(text_token, *fmtargs)
396
+ result = super().tab(tabid, option, **kwargs)
397
+ if text_token is not None:
398
+ self._register_tab_token(tabid, text_token, fmtargs)
399
+ return result
400
+
401
+ configure_tab = tab # alias for tab
402
+
403
+ def item(self, key: str) -> tkinter.Widget:
404
+ """Get a tab's content widget by its key.
405
+
406
+ Args:
407
+ key: The key of the tab to retrieve.
408
+
409
+ Returns:
410
+ The tab's content widget.
411
+
412
+ Raises:
413
+ KeyError: If no tab with the given key exists.
414
+ """
415
+ if key not in self._key_registry:
416
+ raise KeyError(f"No tab with key '{key}'")
417
+ return self._key_registry[key]
418
+
419
+ def items(self) -> tuple[tkinter.Widget, ...]:
420
+ """Get all tab content widgets in order.
421
+
422
+ Returns:
423
+ A tuple of all tab content widgets in tab order.
424
+ """
425
+ tab_ids = super().tabs()
426
+ return tuple(self.nametowidget(tid) for tid in tab_ids)
427
+
428
+ def keys(self) -> tuple[str, ...]:
429
+ """Get all tab keys in order.
430
+
431
+ Returns:
432
+ A tuple of all tab keys in tab order.
433
+ """
434
+ tab_ids = super().tabs()
435
+ return tuple(self._tk_to_key.get(tid, '') for tid in tab_ids)
436
+
437
+ def configure_item(self, key: str, option: str = None, **kwargs: Any) -> Any:
438
+ """Configure a specific tab by its key.
439
+
440
+ Args:
441
+ key: The key of the tab to configure.
442
+ option: If provided, return the value of this option.
443
+ **kwargs: Configuration options to apply to the tab.
444
+
445
+ Returns:
446
+ If option is provided, returns the value of that option.
447
+ """
448
+ return self.tab(key, option, **kwargs)
449
+
450
+ def on_tab_activated(self, callback: Callable[[Any], Any]) -> str:
451
+ """Bind a callback to the `<<NotebookTabActivate>>` event.
452
+
453
+ The `event.data` payload includes `current` (TabRef), `previous`
454
+ (TabRef), `reason` (ChangeReason), and `via` (ChangeMethod).
455
+
456
+ Args:
457
+ callback (Callable): Function to call when a tab is activated.
458
+
459
+ Returns:
460
+ str: The funcid that can be used with `off_tab_activated()`.
461
+ """
462
+ return self.bind("<<NotebookTabActivate>>", callback, add=True)
463
+
464
+ def off_tab_activated(self, bind_id: str | None = None) -> None:
465
+ """Remove a `<<NotebookTabActivate>>` binding.
466
+
467
+ Args:
468
+ bind_id (str): The bind_id returned by `on_tab_activated()`.
469
+ """
470
+ self.unbind("<<NotebookTabActivate>>", bind_id)
471
+
472
+ def on_tab_deactivated(self, callback: Callable[[Any], Any]) -> str:
473
+ """Bind a callback to the `<<NotebookTabDeactivate>>` event.
474
+
475
+ The `event.data` payload includes `current` (TabRef), `previous`
476
+ (TabRef), `reason` (ChangeReason), and `via` (ChangeMethod).
477
+
478
+ Args:
479
+ callback (Callable): Function to call when a tab is deactivated.
480
+
481
+ Returns:
482
+ str: The funcid that can be used with `off_tab_deactivated()`.
483
+ """
484
+ return self.bind("<<NotebookTabDeactivate>>", callback, add=True)
485
+
486
+ def off_tab_deactivated(self, bind_id: str | None = None) -> None:
487
+ """Remove a `<<NotebookTabDeactivate>>` binding.
488
+
489
+ Args:
490
+ bind_id (str): The bind_id returned by `on_tab_deactivated()`.
491
+ """
492
+ self.unbind("<<NotebookTabDeactivate>>", bind_id)
493
+
494
+ def on_tab_changed(self, callback: Callable[[Any], Any]) -> str:
495
+ """Bind a callback to the `<<NotebookTabChange>>` event.
496
+
497
+ This also emits `<<NotebookTabActivate>>` and `<<NotebookTabDeactivate>>`
498
+ events for the affected tabs.
499
+
500
+ The `event.data` payload includes `current` (TabRef), `previous`
501
+ (TabRef), `reason` (ChangeReason), and `via` (ChangeMethod).
502
+
503
+ Args:
504
+ callback (Callable): Function to call when the tab selection changes.
505
+
506
+ Returns:
507
+ str: The funcid that can be used with `off_tab_changed()`.
508
+ """
509
+
510
+ def build_payload(event: Any) -> Any:
511
+ """Attach NotebookChanged data payload to the event"""
512
+ payload = dict(
513
+ current=self._tab_ref(self.select()),
514
+ previous=self._tab_ref(self._last_selected),
515
+ reason=self._last_change_reason or 'unknown',
516
+ via=self._last_change_via or 'unknown',
517
+ )
518
+ event.data = payload
519
+ return event
520
+
521
+ def fire_lifecycle(event: Any) -> None:
522
+ """Emit per-tab lifecycle events when selection truly changes."""
523
+ c, p = event.data["current"], event.data["previous"]
524
+ c_key, p_key = (c or {}).get("key"), (p or {}).get("key")
525
+ changed = (c_key != p_key) if (c_key or p_key) else ((c or {}).get("index") != (p or {}).get("index"))
526
+ if p and changed:
527
+ self.event_generate("<<NotebookTabDeactivate>>", data={"tab": p})
528
+ if c and changed:
529
+ self.event_generate("<<NotebookTabActivate>>", data={"tab": c})
530
+
531
+ def commit(event: Any) -> None:
532
+ """Reset change-tracking fields after dispatching the change event."""
533
+ self._last_selected = self.select()
534
+ self._last_change_reason = 'unknown'
535
+ self._last_change_via = 'unknown'
536
+
537
+ def wrapper(event: Any) -> Any:
538
+ payload = build_payload(event)
539
+ fire_lifecycle(payload)
540
+ commit(payload)
541
+ return callback(event)
542
+
543
+ return self.bind("<<NotebookTabChange>>", wrapper, add=True)
544
+
545
+ def off_tab_changed(self, bind_id: str | None = None) -> None:
546
+ """Remove a `<<NotebookTabChange>>` binding.
547
+
548
+ Args:
549
+ bind_id (str): The bind_id returned by `on_tab_changed()`.
550
+ """
551
+ self.unbind("<<NotebookTabChange>>", bind_id)