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,482 @@
1
+ """In-memory data source implementation with filtering, sorting, and pagination.
2
+
3
+ Provides a pure-Python in-memory data manager that supports:
4
+ - Pagination with configurable page size
5
+ - SQL-like filtering with WHERE clause syntax
6
+ - Multi-column sorting with ASC/DESC
7
+ - Full CRUD operations (create, read, update, delete)
8
+ - Record selection tracking
9
+ - CSV export
10
+ - Inferred schema from data
11
+
12
+ The MemoryDataSource stores all data in memory and provides fast access for
13
+ small to medium datasets. For larger datasets or persistence requirements,
14
+ consider using SqliteDataSource instead.
15
+
16
+ Filtering Syntax:
17
+ Supports SQL-like WHERE conditions:
18
+ - Comparisons: =, !=, >, >=, <, <=
19
+ - String operations: CONTAINS, STARTSWITH, ENDSWITH
20
+ - Set operations: IN ('val1', 'val2')
21
+ - Pattern matching: LIKE with % and _ wildcards
22
+ - Logical operators: AND, OR
23
+ - Literals: 'string', "string", 123, 3.14, true, false, null
24
+
25
+ Example:
26
+ ```python
27
+ set_filter("status = 'active' AND age >= 18")
28
+ set_filter("name LIKE 'John%'")
29
+ ```
30
+
31
+ Sorting Syntax:
32
+ Multi-column sorting with ASC/DESC:
33
+ set_sort("last_name ASC, age DESC")
34
+ set_sort("priority DESC, created_at ASC")
35
+
36
+ Data Format:
37
+ - Records must be dictionaries or will be auto-wrapped as {"text": str(value)}
38
+ - Each record automatically gets an 'id' field (integer, auto-generated if missing)
39
+ - Each record automatically gets a 'selected' field (0 or 1)
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ import csv
45
+ import re
46
+ from collections.abc import Sequence
47
+ from typing import Any, Dict, List, Optional, Union, Mapping, Iterable, Tuple
48
+
49
+ from bootstack.datasource.base import BaseDataSource
50
+ from bootstack.datasource.types import Primitive
51
+
52
+
53
+ class MemoryDataSource(BaseDataSource):
54
+ """In-memory data manager with pagination, filtering, sorting, and CRUD operations.
55
+
56
+ Stores all records in memory as dictionaries with automatic ID generation and
57
+ selection tracking. Provides SQL-like filtering and sorting syntax for intuitive
58
+ data manipulation.
59
+
60
+ The datasource maintains an internal index for O(1) ID lookups and supports
61
+ dynamic schema inference from provided data.
62
+
63
+ Args:
64
+ page_size: Number of records per page (default: 10)
65
+
66
+ Attributes:
67
+ page_size: Current page size setting
68
+
69
+ Example:
70
+ ```python
71
+ ds = MemoryDataSource(page_size=20)
72
+ ds.set_data([
73
+ {"name": "Alice", "age": 30},
74
+ {"name": "Bob", "age": 25},
75
+ ])
76
+ ds.set_filter("age >= 30")
77
+ page = ds.get_page(0)
78
+ ```
79
+ """
80
+
81
+ def __init__(self, page_size: int = 10):
82
+ """Initialize the in-memory datasource with defaults.
83
+
84
+ Args:
85
+ page_size: Number of records returned per page when paginating.
86
+ """
87
+ super().__init__(page_size)
88
+ self._table = "records"
89
+ self._columns: List[str] = []
90
+ self._data: List[Dict[str, Any]] = []
91
+ self._id_index: Dict[Any, int] = {}
92
+ self._where_sql: str = ""
93
+ self._order_by_sql: str = ""
94
+ self._filter_predicate = None
95
+ self._sort_keys: List[Tuple[str, bool]] = []
96
+
97
+ def _rebuild_id_index(self) -> None:
98
+ """Rebuild the ID-to-position index for fast lookups."""
99
+ self._id_index.clear()
100
+ for i, rec in enumerate(self._data):
101
+ self._id_index[rec.get("id")] = i
102
+
103
+ def _ensure_selected_column(self) -> None:
104
+ """Ensure all records have a 'selected' field."""
105
+ if "selected" not in self._columns:
106
+ self._columns.append("selected")
107
+ for r in self._data:
108
+ r.setdefault("selected", 0)
109
+
110
+ def _ensure_id(self) -> None:
111
+ """Ensure all records have unique integer IDs."""
112
+ used = set()
113
+ max_id = 0
114
+ for r in self._data:
115
+ if "id" in r and isinstance(r["id"], int):
116
+ used.add(r["id"])
117
+ max_id = max(max_id, r["id"])
118
+ for r in self._data:
119
+ if "id" not in r or not isinstance(r["id"], int) or r["id"] in used:
120
+ max_id += 1
121
+ r["id"] = max_id
122
+ used.add(max_id)
123
+ self._rebuild_id_index()
124
+
125
+ @staticmethod
126
+ def _like_to_regex(pattern: str) -> re.Pattern:
127
+ """Convert SQL LIKE pattern to regex (% -> .*, _ -> .)."""
128
+ esc = ""
129
+ for ch in pattern:
130
+ if ch in ".^$*+?{}[]\\|()":
131
+ esc += "\\" + ch
132
+ else:
133
+ esc += ch
134
+ esc = esc.replace("%", ".*").replace("_", ".")
135
+ return re.compile("^" + esc + "$", re.IGNORECASE)
136
+
137
+ def _parse_filter(self, where_sql: str):
138
+ """Parse WHERE clause into a predicate function.
139
+
140
+ Args:
141
+ where_sql: SQL WHERE clause
142
+
143
+ Returns:
144
+ Predicate function that evaluates rows, or None if no filter
145
+ """
146
+ if not where_sql:
147
+ return None
148
+
149
+ tokens = re.split(r"\s+(AND|OR)\s+", where_sql, flags=re.IGNORECASE)
150
+ terms: List[Tuple[str, str, Any]] = []
151
+ ops_between: List[str] = []
152
+
153
+ def parse_term(t: str) -> Tuple[str, str, Any]:
154
+ m_in = re.match(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s+IN\s*\((.*)\)\s*$", t, flags=re.IGNORECASE)
155
+ if m_in:
156
+ col, inner = m_in.group(1), m_in.group(2)
157
+ parts = [p.strip() for p in inner.split(",") if p.strip()]
158
+ values = [self._coerce_literal(p) for p in parts]
159
+ return col, "IN", values
160
+
161
+ m_like = re.match(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s+LIKE\s+(.+)\s*$", t, flags=re.IGNORECASE)
162
+ if m_like:
163
+ col, val = m_like.group(1), self._coerce_literal(m_like.group(2))
164
+ return col, "LIKE", val
165
+
166
+ for op in ("CONTAINS", "STARTSWITH", "ENDSWITH"):
167
+ m = re.match(rf"^\s*([A-Za-z_][A-Za-z0-9_]*)\s+{op}\s+(.+)\s*$", t, flags=re.IGNORECASE)
168
+ if m:
169
+ col, val = m.group(1), self._coerce_literal(m.group(2))
170
+ return col, op.upper(), val
171
+
172
+ m = re.match(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(=|!=|>=|>|<=|<)\s*(.+)\s*$", t)
173
+ if m:
174
+ col, op, val = m.group(1), m.group(2), self._coerce_literal(m.group(3))
175
+ return col, op, val
176
+
177
+ m = re.match(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*$", t)
178
+ if m:
179
+ col = m.group(1)
180
+ return col, "truthy", True
181
+
182
+ raise ValueError(f"Unrecognized filter term: {t!r}")
183
+
184
+ for i, tok in enumerate(tokens):
185
+ if i % 2 == 0:
186
+ if not tok.strip():
187
+ continue
188
+ terms.append(parse_term(tok))
189
+ else:
190
+ ops_between.append(tok.strip().upper())
191
+
192
+ if not terms:
193
+ return None
194
+
195
+ prepared: List[Tuple[str, str, Any]] = []
196
+ for col, op, val in terms:
197
+ if op == "LIKE" and isinstance(val, str):
198
+ prepared.append((col, op, self._like_to_regex(val)))
199
+ else:
200
+ prepared.append((col, op, val))
201
+
202
+ def predicate(row: Mapping[str, Any]) -> bool:
203
+ def eval_term(col: str, op: str, val: Any) -> bool:
204
+ rv = row.get(col, None)
205
+ try:
206
+ if op == "=": return rv == val
207
+ if op == "!=": return rv != val
208
+ if op == ">": return (rv is not None) and (val is not None) and rv > val
209
+ if op == ">=": return (rv is not None) and (val is not None) and rv >= val
210
+ if op == "<": return (rv is not None) and (val is not None) and rv < val
211
+ if op == "<=": return (rv is not None) and (val is not None) and rv <= val
212
+ if op == "CONTAINS":
213
+ return (rv is not None) and (val is not None) and (str(val).lower() in str(rv).lower())
214
+ if op == "STARTSWITH":
215
+ return (rv is not None) and (val is not None) and str(rv).lower().startswith(str(val).lower())
216
+ if op == "ENDSWITH":
217
+ return (rv is not None) and (val is not None) and str(rv).lower().endswith(str(val).lower())
218
+ if op == "IN":
219
+ return rv in val
220
+ if op == "LIKE" and isinstance(val, re.Pattern):
221
+ return (rv is not None) and bool(val.match(str(rv)))
222
+ if op == "truthy":
223
+ return bool(rv)
224
+ except Exception:
225
+ return False
226
+ return False
227
+
228
+ result = eval_term(*prepared[0])
229
+ for j, t in enumerate(prepared[1:], start=0):
230
+ op_between = ops_between[j] if j < len(ops_between) else "AND"
231
+ if op_between == "AND":
232
+ result = result and eval_term(*t)
233
+ else:
234
+ result = result or eval_term(*t)
235
+ return result
236
+
237
+ return predicate
238
+
239
+ def _parse_sort(self, order_by_sql: str) -> List[Tuple[str, bool]]:
240
+ """Parse ORDER BY clause into list of (column, reverse_bool) tuples."""
241
+ if not order_by_sql:
242
+ return []
243
+ parts = [p.strip() for p in order_by_sql.split(",") if p.strip()]
244
+ out: List[Tuple[str, bool]] = []
245
+ for p in parts:
246
+ m = re.match(r"^([A-Za-z_][A-Za-z0-9_]*)(?:\s+(ASC|DESC))?$", p, flags=re.IGNORECASE)
247
+ if not m:
248
+ continue
249
+ col = m.group(1)
250
+ dir_tok = (m.group(2) or "ASC").upper()
251
+ out.append((col, dir_tok == "DESC"))
252
+ return out
253
+
254
+ def _apply_filter_and_sort(self, rows: Iterable[Dict[str, Any]]) -> List[Dict[str, Any]]:
255
+ """Apply current filter and sort to row collection."""
256
+ if self._filter_predicate:
257
+ rows = [r for r in rows if self._filter_predicate(r)]
258
+ else:
259
+ rows = list(rows)
260
+
261
+ if self._sort_keys:
262
+ def key_func(r: Dict[str, Any]):
263
+ key_parts = []
264
+ for col, reverse in self._sort_keys:
265
+ v = r.get(col)
266
+ key_parts.append((v is None, v))
267
+ return tuple(key_parts)
268
+
269
+ for col, rev in reversed(self._sort_keys):
270
+ rows.sort(key=lambda r, c=col: (r.get(c) is None, r.get(c)), reverse=rev)
271
+
272
+ return rows
273
+
274
+ def set_data(self, records: Union[Sequence[Primitive], Sequence[Dict[str, Any]]]) -> "MemoryDataSource":
275
+ """Load records into datasource.
276
+
277
+ Args:
278
+ records: Sequence of dicts or primitives (auto-wrapped as {"text": str(x)})
279
+
280
+ Returns:
281
+ Self for method chaining
282
+ """
283
+ if not records:
284
+ self._data = []
285
+ self._columns = []
286
+ self._rebuild_id_index()
287
+ return self
288
+
289
+ if records and not self._is_mapping(records[0]):
290
+ records = [dict(text=str(x)) for x in records]
291
+
292
+ data: List[Dict[str, Any]] = []
293
+ for i, rec in enumerate(records):
294
+ r = dict(rec)
295
+ r.setdefault("id", i)
296
+ r.setdefault("selected", 0)
297
+ data.append(r)
298
+
299
+ self._data = data
300
+ self._columns = list(self._data[0].keys())
301
+ self._ensure_id()
302
+ self._ensure_selected_column()
303
+ return self
304
+
305
+ def set_filter(self, where_sql: str = ""):
306
+ """Apply SQL-like WHERE filter to data."""
307
+ self._where_sql = where_sql or ""
308
+ self._filter_predicate = self._parse_filter(self._where_sql)
309
+
310
+ def set_sort(self, order_by_sql: str = ""):
311
+ """Apply SQL-like ORDER BY sorting to data."""
312
+ self._order_by_sql = order_by_sql or ""
313
+ self._sort_keys = self._parse_sort(self._order_by_sql)
314
+
315
+ def _filtered_sorted_rows(self) -> List[Dict[str, Any]]:
316
+ """Get all rows with current filter and sort applied."""
317
+ return self._apply_filter_and_sort(self._data)
318
+
319
+ def get_page(self, page: Optional[int] = None) -> List[Dict[str, Any]]:
320
+ """Get records for specified page."""
321
+ if page is not None:
322
+ self._page = max(0, int(page))
323
+ rows = self._filtered_sorted_rows()
324
+ start = self._page * self.page_size
325
+ end = start + self.page_size
326
+ return [dict(r) for r in rows[start:end]]
327
+
328
+ def next_page(self) -> List[Dict[str, Any]]:
329
+ """Advance to next page and return its records."""
330
+ if self.has_next_page():
331
+ self._page += 1
332
+ return self.get_page()
333
+
334
+ def prev_page(self) -> List[Dict[str, Any]]:
335
+ """Move to previous page and return its records."""
336
+ self._page = max(0, self._page - 1)
337
+ return self.get_page()
338
+
339
+ def has_next_page(self) -> bool:
340
+ """Check if more pages exist after current page."""
341
+ return (self._page + 1) * self.page_size < self.total_count()
342
+
343
+ def total_count(self) -> int:
344
+ """Get total number of records matching current filter."""
345
+ return len(self._filtered_sorted_rows())
346
+
347
+ def _generate_new_id(self) -> int:
348
+ """Generate next available integer ID."""
349
+ if not self._data:
350
+ return 1
351
+ return max(int(r.get("id", 0)) for r in self._data) + 1
352
+
353
+ def create_record(self, record: Dict[str, Any]) -> int:
354
+ """Create new record and return its ID."""
355
+ r = dict(record)
356
+ if "id" not in r:
357
+ r["id"] = self._generate_new_id()
358
+ if "selected" not in r:
359
+ r["selected"] = 0
360
+ self._data.append(r)
361
+ self._columns = list(set(self._columns) | set(r.keys()))
362
+ self._id_index[r["id"]] = len(self._data) - 1
363
+ return r["id"]
364
+
365
+ def read_record(self, record_id: Any) -> Optional[Dict[str, Any]]:
366
+ """Retrieve single record by ID."""
367
+ idx = self._id_index.get(record_id)
368
+ if idx is None:
369
+ return None
370
+ return dict(self._data[idx])
371
+
372
+ def update_record(self, record_id: Any, updates: Dict[str, Any]) -> bool:
373
+ """Update record fields by ID."""
374
+ if not updates:
375
+ return False
376
+ idx = self._id_index.get(record_id)
377
+ if idx is None:
378
+ return False
379
+ self._data[idx].update(updates)
380
+ self._columns = list(set(self._columns) | set(updates.keys()))
381
+ return True
382
+
383
+ def delete_record(self, record_id: Any) -> bool:
384
+ """Delete record by ID."""
385
+ idx = self._id_index.get(record_id)
386
+ if idx is None:
387
+ return False
388
+ self._data.pop(idx)
389
+ self._rebuild_id_index()
390
+ return True
391
+
392
+ def select_record(self, record_id: Any) -> bool:
393
+ """Mark record as selected."""
394
+ return self._set_selected_flag(record_id, 1)
395
+
396
+ def unselect_record(self, record_id: Any) -> bool:
397
+ """Mark record as unselected."""
398
+ return self._set_selected_flag(record_id, 0)
399
+
400
+ def select_all(self, current_page_only: bool = False) -> int:
401
+ """Select all records (optionally only current page)."""
402
+ self._ensure_selected_column()
403
+ if current_page_only:
404
+ ids = [r["id"] for r in self.get_page()]
405
+ count = 0
406
+ idset = set(ids)
407
+ for r in self._data:
408
+ if r["id"] in idset and r.get("selected") != 1:
409
+ r["selected"] = 1
410
+ count += 1
411
+ return count
412
+ else:
413
+ count = 0
414
+ for r in self._data:
415
+ if r.get("selected") != 1:
416
+ r["selected"] = 1
417
+ count += 1
418
+ return count
419
+
420
+ def unselect_all(self, current_page_only: bool = False) -> int:
421
+ """Unselect all records (optionally only current page)."""
422
+ self._ensure_selected_column()
423
+ if current_page_only:
424
+ ids = [r["id"] for r in self.get_page()]
425
+ count = 0
426
+ idset = set(ids)
427
+ for r in self._data:
428
+ if r["id"] in idset and r.get("selected") != 0:
429
+ r["selected"] = 0
430
+ count += 1
431
+ return count
432
+ else:
433
+ count = 0
434
+ for r in self._data:
435
+ if r.get("selected") != 0:
436
+ r["selected"] = 0
437
+ count += 1
438
+ return count
439
+
440
+ def _set_selected_flag(self, record_id: Any, flag: int) -> bool:
441
+ """Set selection flag for record by ID."""
442
+ self._ensure_selected_column()
443
+ idx = self._id_index.get(record_id)
444
+ if idx is None:
445
+ return False
446
+ self._data[idx]["selected"] = 1 if flag else 0
447
+ return True
448
+
449
+ def get_selected(self, page: Optional[int] = None) -> List[Dict[str, Any]]:
450
+ """Get selected records, optionally paginated."""
451
+ self._ensure_selected_column()
452
+ rows = [r for r in self._data if r.get("selected") == 1]
453
+ rows = self._apply_filter_and_sort(rows)
454
+ if page is None:
455
+ return [dict(r) for r in rows]
456
+ start = max(0, int(page)) * self.page_size
457
+ end = start + self.page_size
458
+ return [dict(r) for r in rows[start:end]]
459
+
460
+ def selected_count(self) -> int:
461
+ """Get total number of selected records."""
462
+ self._ensure_selected_column()
463
+ return sum(1 for r in self._data if r.get("selected") == 1)
464
+
465
+ def export_to_csv(self, filepath: str, include_all: bool = True) -> None:
466
+ """Export records to CSV file."""
467
+ rows = self._data if include_all else [r for r in self._data if r.get("selected") == 1]
468
+ if not rows:
469
+ return
470
+ fieldnames = list(self._columns) if self._columns else list(rows[0].keys())
471
+ with open(filepath, mode="w", newline="", encoding="utf-8") as f:
472
+ writer = csv.DictWriter(f, fieldnames=fieldnames)
473
+ writer.writeheader()
474
+ for r in rows:
475
+ writer.writerow({k: r.get(k) for k in fieldnames})
476
+
477
+ def get_page_from_index(self, start_index: int, count: int) -> List[Dict[str, Any]]:
478
+ """Get records by start index and count (respects filter/sort)."""
479
+ rows = self._filtered_sorted_rows()
480
+ start = max(0, int(start_index))
481
+ end = start + max(0, int(count))
482
+ return [dict(r) for r in rows[start:end]]