meerk40t 0.9.3001__py2.py3-none-any.whl → 0.9.7020__py2.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 (446) hide show
  1. meerk40t/__init__.py +1 -1
  2. meerk40t/balormk/balor_params.py +167 -167
  3. meerk40t/balormk/clone_loader.py +457 -457
  4. meerk40t/balormk/controller.py +1566 -1512
  5. meerk40t/balormk/cylindermod.py +64 -0
  6. meerk40t/balormk/device.py +966 -1959
  7. meerk40t/balormk/driver.py +778 -591
  8. meerk40t/balormk/galvo_commands.py +1194 -0
  9. meerk40t/balormk/gui/balorconfig.py +237 -111
  10. meerk40t/balormk/gui/balorcontroller.py +191 -184
  11. meerk40t/balormk/gui/baloroperationproperties.py +116 -115
  12. meerk40t/balormk/gui/corscene.py +845 -0
  13. meerk40t/balormk/gui/gui.py +179 -147
  14. meerk40t/balormk/livelightjob.py +466 -382
  15. meerk40t/balormk/mock_connection.py +131 -109
  16. meerk40t/balormk/plugin.py +133 -135
  17. meerk40t/balormk/usb_connection.py +306 -301
  18. meerk40t/camera/__init__.py +1 -1
  19. meerk40t/camera/camera.py +514 -397
  20. meerk40t/camera/gui/camerapanel.py +1241 -1095
  21. meerk40t/camera/gui/gui.py +58 -58
  22. meerk40t/camera/plugin.py +441 -399
  23. meerk40t/ch341/__init__.py +27 -27
  24. meerk40t/ch341/ch341device.py +628 -628
  25. meerk40t/ch341/libusb.py +595 -589
  26. meerk40t/ch341/mock.py +171 -171
  27. meerk40t/ch341/windriver.py +157 -157
  28. meerk40t/constants.py +13 -0
  29. meerk40t/core/__init__.py +1 -1
  30. meerk40t/core/bindalias.py +550 -539
  31. meerk40t/core/core.py +47 -47
  32. meerk40t/core/cutcode/cubiccut.py +73 -73
  33. meerk40t/core/cutcode/cutcode.py +315 -312
  34. meerk40t/core/cutcode/cutgroup.py +141 -137
  35. meerk40t/core/cutcode/cutobject.py +192 -185
  36. meerk40t/core/cutcode/dwellcut.py +37 -37
  37. meerk40t/core/cutcode/gotocut.py +29 -29
  38. meerk40t/core/cutcode/homecut.py +29 -29
  39. meerk40t/core/cutcode/inputcut.py +34 -34
  40. meerk40t/core/cutcode/linecut.py +33 -33
  41. meerk40t/core/cutcode/outputcut.py +34 -34
  42. meerk40t/core/cutcode/plotcut.py +335 -335
  43. meerk40t/core/cutcode/quadcut.py +61 -61
  44. meerk40t/core/cutcode/rastercut.py +168 -148
  45. meerk40t/core/cutcode/waitcut.py +34 -34
  46. meerk40t/core/cutplan.py +1843 -1316
  47. meerk40t/core/drivers.py +330 -329
  48. meerk40t/core/elements/align.py +801 -669
  49. meerk40t/core/elements/branches.py +1858 -1507
  50. meerk40t/core/elements/clipboard.py +229 -219
  51. meerk40t/core/elements/element_treeops.py +4595 -2837
  52. meerk40t/core/elements/element_types.py +125 -105
  53. meerk40t/core/elements/elements.py +4315 -3617
  54. meerk40t/core/elements/files.py +117 -64
  55. meerk40t/core/elements/geometry.py +473 -224
  56. meerk40t/core/elements/grid.py +467 -316
  57. meerk40t/core/elements/materials.py +158 -94
  58. meerk40t/core/elements/notes.py +50 -38
  59. meerk40t/core/elements/offset_clpr.py +934 -912
  60. meerk40t/core/elements/offset_mk.py +963 -955
  61. meerk40t/core/elements/penbox.py +339 -267
  62. meerk40t/core/elements/placements.py +300 -83
  63. meerk40t/core/elements/render.py +785 -687
  64. meerk40t/core/elements/shapes.py +2618 -2092
  65. meerk40t/core/elements/testcases.py +105 -0
  66. meerk40t/core/elements/trace.py +651 -563
  67. meerk40t/core/elements/tree_commands.py +415 -409
  68. meerk40t/core/elements/undo_redo.py +116 -58
  69. meerk40t/core/elements/wordlist.py +319 -200
  70. meerk40t/core/exceptions.py +9 -9
  71. meerk40t/core/laserjob.py +220 -220
  72. meerk40t/core/logging.py +63 -63
  73. meerk40t/core/node/blobnode.py +83 -86
  74. meerk40t/core/node/bootstrap.py +105 -103
  75. meerk40t/core/node/branch_elems.py +40 -31
  76. meerk40t/core/node/branch_ops.py +45 -38
  77. meerk40t/core/node/branch_regmark.py +48 -41
  78. meerk40t/core/node/cutnode.py +29 -32
  79. meerk40t/core/node/effect_hatch.py +375 -257
  80. meerk40t/core/node/effect_warp.py +398 -0
  81. meerk40t/core/node/effect_wobble.py +441 -309
  82. meerk40t/core/node/elem_ellipse.py +404 -309
  83. meerk40t/core/node/elem_image.py +1082 -801
  84. meerk40t/core/node/elem_line.py +358 -292
  85. meerk40t/core/node/elem_path.py +259 -201
  86. meerk40t/core/node/elem_point.py +129 -102
  87. meerk40t/core/node/elem_polyline.py +310 -246
  88. meerk40t/core/node/elem_rect.py +376 -286
  89. meerk40t/core/node/elem_text.py +445 -418
  90. meerk40t/core/node/filenode.py +59 -40
  91. meerk40t/core/node/groupnode.py +138 -74
  92. meerk40t/core/node/image_processed.py +777 -766
  93. meerk40t/core/node/image_raster.py +156 -113
  94. meerk40t/core/node/layernode.py +31 -31
  95. meerk40t/core/node/mixins.py +135 -107
  96. meerk40t/core/node/node.py +1427 -1304
  97. meerk40t/core/node/nutils.py +117 -114
  98. meerk40t/core/node/op_cut.py +463 -335
  99. meerk40t/core/node/op_dots.py +296 -251
  100. meerk40t/core/node/op_engrave.py +414 -311
  101. meerk40t/core/node/op_image.py +755 -369
  102. meerk40t/core/node/op_raster.py +787 -522
  103. meerk40t/core/node/place_current.py +37 -40
  104. meerk40t/core/node/place_point.py +329 -126
  105. meerk40t/core/node/refnode.py +58 -47
  106. meerk40t/core/node/rootnode.py +225 -219
  107. meerk40t/core/node/util_console.py +48 -48
  108. meerk40t/core/node/util_goto.py +84 -65
  109. meerk40t/core/node/util_home.py +61 -61
  110. meerk40t/core/node/util_input.py +102 -102
  111. meerk40t/core/node/util_output.py +102 -102
  112. meerk40t/core/node/util_wait.py +65 -65
  113. meerk40t/core/parameters.py +709 -707
  114. meerk40t/core/planner.py +875 -785
  115. meerk40t/core/plotplanner.py +656 -652
  116. meerk40t/core/space.py +120 -113
  117. meerk40t/core/spoolers.py +706 -705
  118. meerk40t/core/svg_io.py +1836 -1549
  119. meerk40t/core/treeop.py +534 -445
  120. meerk40t/core/undos.py +278 -124
  121. meerk40t/core/units.py +784 -680
  122. meerk40t/core/view.py +393 -322
  123. meerk40t/core/webhelp.py +62 -62
  124. meerk40t/core/wordlist.py +513 -504
  125. meerk40t/cylinder/cylinder.py +247 -0
  126. meerk40t/cylinder/gui/cylindersettings.py +41 -0
  127. meerk40t/cylinder/gui/gui.py +24 -0
  128. meerk40t/device/__init__.py +1 -1
  129. meerk40t/device/basedevice.py +322 -123
  130. meerk40t/device/devicechoices.py +50 -0
  131. meerk40t/device/dummydevice.py +163 -128
  132. meerk40t/device/gui/defaultactions.py +618 -602
  133. meerk40t/device/gui/effectspanel.py +114 -0
  134. meerk40t/device/gui/formatterpanel.py +253 -290
  135. meerk40t/device/gui/warningpanel.py +337 -260
  136. meerk40t/device/mixins.py +13 -13
  137. meerk40t/dxf/__init__.py +1 -1
  138. meerk40t/dxf/dxf_io.py +766 -554
  139. meerk40t/dxf/plugin.py +47 -35
  140. meerk40t/external_plugins.py +79 -79
  141. meerk40t/external_plugins_build.py +28 -28
  142. meerk40t/extra/cag.py +112 -116
  143. meerk40t/extra/coolant.py +403 -0
  144. meerk40t/extra/encode_detect.py +204 -0
  145. meerk40t/extra/ezd.py +1165 -1165
  146. meerk40t/extra/hershey.py +834 -340
  147. meerk40t/extra/imageactions.py +322 -316
  148. meerk40t/extra/inkscape.py +628 -622
  149. meerk40t/extra/lbrn.py +424 -424
  150. meerk40t/extra/outerworld.py +283 -0
  151. meerk40t/extra/param_functions.py +1542 -1556
  152. meerk40t/extra/potrace.py +257 -253
  153. meerk40t/extra/serial_exchange.py +118 -0
  154. meerk40t/extra/updater.py +602 -453
  155. meerk40t/extra/vectrace.py +147 -146
  156. meerk40t/extra/winsleep.py +83 -83
  157. meerk40t/extra/xcs_reader.py +597 -0
  158. meerk40t/fill/fills.py +781 -335
  159. meerk40t/fill/patternfill.py +1061 -1061
  160. meerk40t/fill/patterns.py +614 -567
  161. meerk40t/grbl/control.py +87 -87
  162. meerk40t/grbl/controller.py +990 -903
  163. meerk40t/grbl/device.py +1084 -768
  164. meerk40t/grbl/driver.py +989 -771
  165. meerk40t/grbl/emulator.py +532 -497
  166. meerk40t/grbl/gcodejob.py +783 -767
  167. meerk40t/grbl/gui/grblconfiguration.py +373 -298
  168. meerk40t/grbl/gui/grblcontroller.py +485 -271
  169. meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
  170. meerk40t/grbl/gui/grbloperationconfig.py +105 -0
  171. meerk40t/grbl/gui/gui.py +147 -116
  172. meerk40t/grbl/interpreter.py +44 -44
  173. meerk40t/grbl/loader.py +22 -22
  174. meerk40t/grbl/mock_connection.py +56 -56
  175. meerk40t/grbl/plugin.py +294 -264
  176. meerk40t/grbl/serial_connection.py +93 -88
  177. meerk40t/grbl/tcp_connection.py +81 -79
  178. meerk40t/grbl/ws_connection.py +112 -0
  179. meerk40t/gui/__init__.py +1 -1
  180. meerk40t/gui/about.py +2042 -296
  181. meerk40t/gui/alignment.py +1644 -1608
  182. meerk40t/gui/autoexec.py +199 -0
  183. meerk40t/gui/basicops.py +791 -670
  184. meerk40t/gui/bufferview.py +77 -71
  185. meerk40t/gui/busy.py +232 -133
  186. meerk40t/gui/choicepropertypanel.py +1662 -1469
  187. meerk40t/gui/consolepanel.py +706 -542
  188. meerk40t/gui/devicepanel.py +687 -581
  189. meerk40t/gui/dialogoptions.py +110 -107
  190. meerk40t/gui/executejob.py +316 -306
  191. meerk40t/gui/fonts.py +90 -90
  192. meerk40t/gui/functionwrapper.py +252 -0
  193. meerk40t/gui/gui_mixins.py +729 -0
  194. meerk40t/gui/guicolors.py +205 -182
  195. meerk40t/gui/help_assets/help_assets.py +218 -201
  196. meerk40t/gui/helper.py +154 -0
  197. meerk40t/gui/hersheymanager.py +1440 -846
  198. meerk40t/gui/icons.py +3422 -2747
  199. meerk40t/gui/imagesplitter.py +555 -508
  200. meerk40t/gui/keymap.py +354 -344
  201. meerk40t/gui/laserpanel.py +897 -806
  202. meerk40t/gui/laserrender.py +1470 -1232
  203. meerk40t/gui/lasertoolpanel.py +805 -793
  204. meerk40t/gui/magnetoptions.py +436 -0
  205. meerk40t/gui/materialmanager.py +2944 -0
  206. meerk40t/gui/materialtest.py +1722 -1694
  207. meerk40t/gui/mkdebug.py +646 -359
  208. meerk40t/gui/mwindow.py +163 -140
  209. meerk40t/gui/navigationpanels.py +2605 -2467
  210. meerk40t/gui/notes.py +143 -142
  211. meerk40t/gui/opassignment.py +414 -410
  212. meerk40t/gui/operation_info.py +310 -299
  213. meerk40t/gui/plugin.py +500 -328
  214. meerk40t/gui/position.py +714 -669
  215. meerk40t/gui/preferences.py +901 -650
  216. meerk40t/gui/propertypanels/attributes.py +1461 -1131
  217. meerk40t/gui/propertypanels/blobproperty.py +117 -114
  218. meerk40t/gui/propertypanels/consoleproperty.py +83 -80
  219. meerk40t/gui/propertypanels/gotoproperty.py +77 -0
  220. meerk40t/gui/propertypanels/groupproperties.py +223 -217
  221. meerk40t/gui/propertypanels/hatchproperty.py +489 -469
  222. meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
  223. meerk40t/gui/propertypanels/inputproperty.py +59 -58
  224. meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
  225. meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
  226. meerk40t/gui/propertypanels/outputproperty.py +59 -58
  227. meerk40t/gui/propertypanels/pathproperty.py +389 -380
  228. meerk40t/gui/propertypanels/placementproperty.py +1214 -383
  229. meerk40t/gui/propertypanels/pointproperty.py +140 -136
  230. meerk40t/gui/propertypanels/propertywindow.py +313 -181
  231. meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
  232. meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
  233. meerk40t/gui/propertypanels/textproperty.py +770 -755
  234. meerk40t/gui/propertypanels/waitproperty.py +56 -55
  235. meerk40t/gui/propertypanels/warpproperty.py +121 -0
  236. meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
  237. meerk40t/gui/ribbon.py +2471 -2210
  238. meerk40t/gui/scene/scene.py +1100 -1051
  239. meerk40t/gui/scene/sceneconst.py +22 -22
  240. meerk40t/gui/scene/scenepanel.py +439 -349
  241. meerk40t/gui/scene/scenespacewidget.py +365 -365
  242. meerk40t/gui/scene/widget.py +518 -505
  243. meerk40t/gui/scenewidgets/affinemover.py +215 -215
  244. meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
  245. meerk40t/gui/scenewidgets/bedwidget.py +120 -97
  246. meerk40t/gui/scenewidgets/elementswidget.py +137 -107
  247. meerk40t/gui/scenewidgets/gridwidget.py +785 -745
  248. meerk40t/gui/scenewidgets/guidewidget.py +765 -765
  249. meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
  250. meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
  251. meerk40t/gui/scenewidgets/nodeselector.py +28 -28
  252. meerk40t/gui/scenewidgets/rectselectwidget.py +592 -346
  253. meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
  254. meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
  255. meerk40t/gui/scenewidgets/selectionwidget.py +2958 -2756
  256. meerk40t/gui/simpleui.py +362 -333
  257. meerk40t/gui/simulation.py +2451 -2094
  258. meerk40t/gui/snapoptions.py +208 -203
  259. meerk40t/gui/spoolerpanel.py +1227 -1180
  260. meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
  261. meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
  262. meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
  263. meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
  264. meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
  265. meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
  266. meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
  267. meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
  268. meerk40t/gui/themes.py +200 -78
  269. meerk40t/gui/tips.py +590 -0
  270. meerk40t/gui/toolwidgets/circlebrush.py +35 -35
  271. meerk40t/gui/toolwidgets/toolcircle.py +248 -242
  272. meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
  273. meerk40t/gui/toolwidgets/tooldraw.py +97 -90
  274. meerk40t/gui/toolwidgets/toolellipse.py +219 -212
  275. meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
  276. meerk40t/gui/toolwidgets/toolline.py +39 -144
  277. meerk40t/gui/toolwidgets/toollinetext.py +79 -236
  278. meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
  279. meerk40t/gui/toolwidgets/toolmeasure.py +163 -216
  280. meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
  281. meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
  282. meerk40t/gui/toolwidgets/toolparameter.py +754 -668
  283. meerk40t/gui/toolwidgets/toolplacement.py +108 -108
  284. meerk40t/gui/toolwidgets/toolpoint.py +68 -59
  285. meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
  286. meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
  287. meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
  288. meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
  289. meerk40t/gui/toolwidgets/toolrect.py +211 -207
  290. meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
  291. meerk40t/gui/toolwidgets/toolribbon.py +598 -113
  292. meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
  293. meerk40t/gui/toolwidgets/tooltext.py +98 -89
  294. meerk40t/gui/toolwidgets/toolvector.py +213 -204
  295. meerk40t/gui/toolwidgets/toolwidget.py +39 -39
  296. meerk40t/gui/usbconnect.py +98 -91
  297. meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
  298. meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
  299. meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
  300. meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
  301. meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
  302. meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
  303. meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
  304. meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
  305. meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
  306. meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
  307. meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
  308. meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
  309. meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
  310. meerk40t/gui/wordlisteditor.py +985 -931
  311. meerk40t/gui/wxmeerk40t.py +1447 -1169
  312. meerk40t/gui/wxmmain.py +5644 -4112
  313. meerk40t/gui/wxmribbon.py +1591 -1076
  314. meerk40t/gui/wxmscene.py +1631 -1453
  315. meerk40t/gui/wxmtree.py +2416 -2089
  316. meerk40t/gui/wxutils.py +1769 -1099
  317. meerk40t/gui/zmatrix.py +102 -102
  318. meerk40t/image/__init__.py +1 -1
  319. meerk40t/image/dither.py +429 -0
  320. meerk40t/image/imagetools.py +2793 -2269
  321. meerk40t/internal_plugins.py +150 -130
  322. meerk40t/kernel/__init__.py +63 -12
  323. meerk40t/kernel/channel.py +259 -212
  324. meerk40t/kernel/context.py +538 -538
  325. meerk40t/kernel/exceptions.py +41 -41
  326. meerk40t/kernel/functions.py +463 -414
  327. meerk40t/kernel/jobs.py +100 -100
  328. meerk40t/kernel/kernel.py +3828 -3571
  329. meerk40t/kernel/lifecycles.py +71 -71
  330. meerk40t/kernel/module.py +49 -49
  331. meerk40t/kernel/service.py +147 -147
  332. meerk40t/kernel/settings.py +383 -343
  333. meerk40t/lihuiyu/controller.py +883 -876
  334. meerk40t/lihuiyu/device.py +1181 -1069
  335. meerk40t/lihuiyu/driver.py +1466 -1372
  336. meerk40t/lihuiyu/gui/gui.py +127 -106
  337. meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
  338. meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
  339. meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
  340. meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
  341. meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
  342. meerk40t/lihuiyu/interpreter.py +53 -53
  343. meerk40t/lihuiyu/laserspeed.py +450 -450
  344. meerk40t/lihuiyu/loader.py +90 -90
  345. meerk40t/lihuiyu/parser.py +404 -404
  346. meerk40t/lihuiyu/plugin.py +101 -102
  347. meerk40t/lihuiyu/tcp_connection.py +111 -109
  348. meerk40t/main.py +231 -165
  349. meerk40t/moshi/builder.py +788 -781
  350. meerk40t/moshi/controller.py +505 -499
  351. meerk40t/moshi/device.py +495 -442
  352. meerk40t/moshi/driver.py +862 -696
  353. meerk40t/moshi/gui/gui.py +78 -76
  354. meerk40t/moshi/gui/moshicontrollergui.py +538 -522
  355. meerk40t/moshi/gui/moshidrivergui.py +87 -75
  356. meerk40t/moshi/plugin.py +43 -43
  357. meerk40t/network/console_server.py +140 -57
  358. meerk40t/network/kernelserver.py +10 -9
  359. meerk40t/network/tcp_server.py +142 -140
  360. meerk40t/network/udp_server.py +103 -77
  361. meerk40t/network/web_server.py +404 -0
  362. meerk40t/newly/controller.py +1158 -1144
  363. meerk40t/newly/device.py +874 -732
  364. meerk40t/newly/driver.py +540 -412
  365. meerk40t/newly/gui/gui.py +219 -188
  366. meerk40t/newly/gui/newlyconfig.py +116 -101
  367. meerk40t/newly/gui/newlycontroller.py +193 -186
  368. meerk40t/newly/gui/operationproperties.py +51 -51
  369. meerk40t/newly/mock_connection.py +82 -82
  370. meerk40t/newly/newly_params.py +56 -56
  371. meerk40t/newly/plugin.py +1214 -1246
  372. meerk40t/newly/usb_connection.py +322 -322
  373. meerk40t/rotary/gui/gui.py +52 -46
  374. meerk40t/rotary/gui/rotarysettings.py +240 -232
  375. meerk40t/rotary/rotary.py +202 -98
  376. meerk40t/ruida/control.py +291 -91
  377. meerk40t/ruida/controller.py +138 -1088
  378. meerk40t/ruida/device.py +676 -231
  379. meerk40t/ruida/driver.py +534 -472
  380. meerk40t/ruida/emulator.py +1494 -1491
  381. meerk40t/ruida/exceptions.py +4 -4
  382. meerk40t/ruida/gui/gui.py +71 -76
  383. meerk40t/ruida/gui/ruidaconfig.py +239 -72
  384. meerk40t/ruida/gui/ruidacontroller.py +187 -184
  385. meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
  386. meerk40t/ruida/loader.py +54 -52
  387. meerk40t/ruida/mock_connection.py +57 -109
  388. meerk40t/ruida/plugin.py +124 -87
  389. meerk40t/ruida/rdjob.py +2084 -945
  390. meerk40t/ruida/serial_connection.py +116 -0
  391. meerk40t/ruida/tcp_connection.py +146 -0
  392. meerk40t/ruida/udp_connection.py +73 -0
  393. meerk40t/svgelements.py +9671 -9669
  394. meerk40t/tools/driver_to_path.py +584 -579
  395. meerk40t/tools/geomstr.py +5583 -4680
  396. meerk40t/tools/jhfparser.py +357 -292
  397. meerk40t/tools/kerftest.py +904 -890
  398. meerk40t/tools/livinghinges.py +1168 -1033
  399. meerk40t/tools/pathtools.py +987 -949
  400. meerk40t/tools/pmatrix.py +234 -0
  401. meerk40t/tools/pointfinder.py +942 -942
  402. meerk40t/tools/polybool.py +941 -940
  403. meerk40t/tools/rasterplotter.py +1660 -547
  404. meerk40t/tools/shxparser.py +1047 -901
  405. meerk40t/tools/ttfparser.py +726 -446
  406. meerk40t/tools/zinglplotter.py +595 -593
  407. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/LICENSE +21 -21
  408. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/METADATA +150 -139
  409. meerk40t-0.9.7020.dist-info/RECORD +446 -0
  410. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/WHEEL +1 -1
  411. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/top_level.txt +0 -1
  412. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/zip-safe +1 -1
  413. meerk40t/balormk/elementlightjob.py +0 -159
  414. meerk40t-0.9.3001.dist-info/RECORD +0 -437
  415. test/bootstrap.py +0 -63
  416. test/test_cli.py +0 -12
  417. test/test_core_cutcode.py +0 -418
  418. test/test_core_elements.py +0 -144
  419. test/test_core_plotplanner.py +0 -397
  420. test/test_core_viewports.py +0 -312
  421. test/test_drivers_grbl.py +0 -108
  422. test/test_drivers_lihuiyu.py +0 -443
  423. test/test_drivers_newly.py +0 -113
  424. test/test_element_degenerate_points.py +0 -43
  425. test/test_elements_classify.py +0 -97
  426. test/test_elements_penbox.py +0 -22
  427. test/test_file_svg.py +0 -176
  428. test/test_fill.py +0 -155
  429. test/test_geomstr.py +0 -1523
  430. test/test_geomstr_nodes.py +0 -18
  431. test/test_imagetools_actualize.py +0 -306
  432. test/test_imagetools_wizard.py +0 -258
  433. test/test_kernel.py +0 -200
  434. test/test_laser_speeds.py +0 -3303
  435. test/test_length.py +0 -57
  436. test/test_lifecycle.py +0 -66
  437. test/test_operations.py +0 -251
  438. test/test_operations_hatch.py +0 -57
  439. test/test_ruida.py +0 -19
  440. test/test_spooler.py +0 -22
  441. test/test_tools_rasterplotter.py +0 -29
  442. test/test_wobble.py +0 -133
  443. test/test_zingl.py +0 -124
  444. {test → meerk40t/cylinder}/__init__.py +0 -0
  445. /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
  446. {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7020.dist-info}/entry_points.txt +0 -0
meerk40t/gui/wxmribbon.py CHANGED
@@ -1,1076 +1,1591 @@
1
- """
2
- The WxmRibbon Bar is a core aspect of MeerK40t's interaction. To allow fully dynamic buttons we use the ribbonbar
3
- control widget built into meerk40t.
4
-
5
- Panels are created in a static fashion within this class. But the contents of those individual ribbon panels are defined
6
- in the kernel lookup.
7
-
8
- service.register(
9
- "button/control/Redlight",
10
- {
11
- ...<def>...
12
- },
13
- )
14
-
15
- This setup allows us to define a large series of buttons with a button prefix and the name of a panel it should be
16
- added to as well as a unique name. This defines within the meerk40t ecosystem a methodology of specifically creating
17
- dynamic buttons. When the service changes because of a switch in the device (for example) it will trigger the lookup
18
- listeners which will update their contents, triggering the update within this control.
19
- """
20
-
21
- import copy
22
- import math
23
- import platform
24
- import threading
25
-
26
- import wx
27
- from wx import aui
28
-
29
- from meerk40t.gui.icons import (
30
- STD_ICON_SIZE,
31
- get_default_icon_size,
32
- icon_add_new,
33
- icon_trash,
34
- icons8_down,
35
- icons8_opened_folder,
36
- icons8_up,
37
- )
38
- from meerk40t.gui.ribbon import RibbonBarPanel
39
- from meerk40t.gui.wxutils import StaticBoxSizer, dip_size
40
- from meerk40t.kernel import Settings, lookup_listener, signal_listener
41
-
42
- _ = wx.GetTranslation
43
-
44
-
45
- def register_panel_ribbon(window, context):
46
- iconsize = get_default_icon_size()
47
- minh = 3 * iconsize + 25
48
- pane_ribbon = (
49
- aui.AuiPaneInfo()
50
- .Name("ribbon")
51
- .Top()
52
- .BestSize(300, minh)
53
- .FloatingSize(640, minh)
54
- .Caption(_("Ribbon"))
55
- .CaptionVisible(not context.pane_lock)
56
- )
57
- pane_ribbon.dock_proportion = 640
58
- ribbon = MKRibbonBarPanel(
59
- window, wx.ID_ANY, context=context, pane=pane_ribbon, identifier="primary"
60
- )
61
- pane_ribbon.control = ribbon
62
-
63
- window.on_pane_create(pane_ribbon)
64
- context.register("pane/ribbon", pane_ribbon)
65
- context.register("ribbonbar/primary", ribbon)
66
-
67
- minh = int(1.5 * iconsize)
68
- pane_tool = (
69
- aui.AuiPaneInfo()
70
- .Name("tools")
71
- .Left()
72
- .BestSize(minh, 300)
73
- .FloatingSize(minh, 640)
74
- .Caption(_("Tools"))
75
- .CaptionVisible(not context.pane_lock)
76
- )
77
- pane_tool.dock_proportion = 640
78
- ribbon = MKRibbonBarPanel(
79
- window,
80
- wx.ID_ANY,
81
- context=context,
82
- pane=pane_tool,
83
- identifier="tools",
84
- orientation="auto",
85
- show_labels=False,
86
- )
87
- pane_tool.control = ribbon
88
-
89
- window.on_pane_create(pane_tool)
90
- context.register("pane/tools", pane_tool)
91
- context.register("ribbonbar/tools", ribbon)
92
-
93
- choices = [
94
- {
95
- "attr": "ribbon_show_labels",
96
- "object": context,
97
- "default": False,
98
- "type": bool,
99
- "label": _("Show the Ribbon Labels"),
100
- "tip": _(
101
- "Active: Show the labels for ribbonbar.\n"
102
- "Inactive: Hide the ribbon labels."
103
- ),
104
- "page": "Gui",
105
- "section": "Appearance",
106
- },
107
- ]
108
-
109
- context.kernel.register_choices("preferences", choices)
110
-
111
-
112
- class MKRibbonBarPanel(RibbonBarPanel):
113
- def __init__(
114
- self,
115
- parent,
116
- id,
117
- context=None,
118
- pane=None,
119
- identifier=None,
120
- orientation=None,
121
- show_labels=None,
122
- **kwds,
123
- ):
124
- RibbonBarPanel.__init__(self, parent, id, context, pane, **kwds)
125
- self.pane = pane
126
- self.identifier = identifier
127
- if orientation is not None:
128
- if orientation.lower().startswith("h"):
129
- self.art.orientation = self.art.RIBBON_ORIENTATION_HORIZONTAL
130
- elif orientation.lower().startswith("v"):
131
- self.art.orientation = self.art.RIBBON_ORIENTATION_VERTICAL
132
- elif orientation.lower().startswith("a"):
133
- self.art.orientation = self.art.RIBBON_ORIENTATION_AUTO
134
- if show_labels is not None:
135
- self.art.show_labels = show_labels
136
- # Make myself known in context
137
- if not hasattr(context, "_ribbons"):
138
- self.context._ribbons = dict()
139
- self.context._ribbons[self.identifier] = self
140
-
141
- self.storage = Settings(self.context.kernel.name, f"ribbon_{identifier}.cfg")
142
- self.storage.read_configuration()
143
-
144
- self.allow_labels = bool(show_labels is None or show_labels)
145
- # Layout properties.
146
- if self.allow_labels:
147
- self.toggle_show_labels(context.setting(bool, "ribbon_show_labels", True))
148
-
149
- self._pages = []
150
- self.set_default_pages()
151
- # Define Ribbon.
152
- self.__set_ribbonbar()
153
-
154
- def get_default_config(self):
155
- if self.identifier == "tools":
156
- config = [
157
- {
158
- "id": "tools", # identifier
159
- "label": "Tools", # Label
160
- "panels": [ # Panels to include
161
- {
162
- "id": "select",
163
- "label": "Select + Edit",
164
- "seq": 1,
165
- },
166
- {
167
- "id": "lasercontrol",
168
- "label": "Laser",
169
- "seq": 2,
170
- },
171
- {
172
- "id": "tool",
173
- "label": "Create",
174
- "seq": 3,
175
- },
176
- {
177
- "id": "extended_tools",
178
- "label": "Extended Tools",
179
- "seq": 4,
180
- },
181
- {
182
- "id": "group",
183
- "label": "Group",
184
- "seq": 5,
185
- },
186
- ],
187
- "seq": 1, # Sequence
188
- },
189
- ]
190
- elif self.identifier == "primary":
191
- config = [
192
- {
193
- "id": "home", # identifier
194
- "label": "Project", # Label
195
- "panels": [ # Panels to include
196
- {
197
- "id": "project",
198
- "label": "Project",
199
- "seq": 1,
200
- },
201
- {
202
- "id": "basicediting",
203
- "label": "Edit",
204
- "seq": 2,
205
- },
206
- {
207
- "id": "undo",
208
- "label": "Undo",
209
- "seq": 3,
210
- },
211
- {
212
- "id": "preparation",
213
- "label": "Prepare",
214
- "seq": 4,
215
- },
216
- {
217
- "id": "control",
218
- "label": "Control",
219
- "seq": 5,
220
- },
221
- {
222
- "id": "jobstart",
223
- "label": "Execute",
224
- "seq": 5,
225
- },
226
- ],
227
- "seq": 1, # Sequence
228
- },
229
- {
230
- "id": "modify",
231
- "label": "Modify",
232
- "panels": [
233
- {
234
- "id": "undo",
235
- "label": "Undo",
236
- "seq": 1,
237
- },
238
- {
239
- "id": "modify",
240
- "label": "Modification",
241
- "seq": 2,
242
- },
243
- {
244
- "id": "geometry",
245
- "label": "Geometry",
246
- "seq": 3,
247
- },
248
- {
249
- "id": "align",
250
- "label": "Alignment",
251
- "seq": 4,
252
- },
253
- ],
254
- "seq": 2,
255
- },
256
- {
257
- "id": "config",
258
- "label": "Config",
259
- "panels": [ # Panels to include
260
- {
261
- "id": "config",
262
- "label": "Configuration",
263
- "seq": 1,
264
- },
265
- {
266
- "id": "device",
267
- "label": "Devices",
268
- "seq": 1,
269
- },
270
- ],
271
- "seq": 3,
272
- },
273
- ]
274
- else:
275
- config = []
276
-
277
- return config
278
-
279
- def get_current_config(self):
280
- # Is the storage empty? Then we use the default config
281
- config = self.get_default_config()
282
- testid = self.storage.read_persistent(str, "Ribbon", "identifier", "")
283
- # print(f"testid='{testid}', should be: {self.identifier}")
284
- if testid != self.identifier:
285
- # Thats fishy...
286
- return config
287
- flag = self.storage.read_persistent(
288
- bool, "Ribbon", "show_labels", self.art.show_labels
289
- )
290
- self.art.show_labels = flag
291
- newconfig = []
292
- page_idx = 0
293
- while True:
294
- section = f"Page_{page_idx + 1}"
295
- info = self.storage.read_persistent_string_dict(section)
296
- if info is None or len(info) == 0:
297
- break
298
- # print (info)
299
- newpage = {"id": "id", "label": "Label", "seq": 1}
300
- k = f"{section}/id"
301
- if k in info:
302
- value = self.storage.read_persistent(str, section, "id", "")
303
- newpage["id"] = value
304
- k = f"{section}/label"
305
- if k in info:
306
- value = self.storage.read_persistent(str, section, "label", "")
307
- newpage["label"] = value
308
- k = f"{section}/seq"
309
- if k in info:
310
- value = self.storage.read_persistent(int, section, "seq", None)
311
- if value:
312
- try:
313
- newpage["seq"] = int(value)
314
- except ValueError:
315
- pass
316
- panel_list = []
317
- panel_idx = 0
318
- while True:
319
- k = f"panel_{panel_idx + 1}"
320
- panel_info = self.storage.read_persistent(tuple, section, k, None)
321
- # print (f"{k} : {panel_info}")
322
- if (
323
- panel_info is None
324
- or not isinstance(panel_info, (list, tuple))
325
- or len(panel_info) < 3
326
- ):
327
- break
328
- panel_dict = dict()
329
- panel_dict["id"] = panel_info[0]
330
- panel_dict["seq"] = int(panel_info[1])
331
- panel_dict["label"] = panel_info[2]
332
- panel_list.append(panel_dict)
333
-
334
- panel_idx += 1
335
-
336
- newpage["panels"] = panel_list
337
- if len(panel_list) > 0:
338
- newconfig.append(newpage)
339
-
340
- page_idx += 1
341
-
342
- if len(newconfig) > 0:
343
- config = newconfig
344
- return config
345
-
346
- def set_default_pages(self):
347
- self._pages = self.get_current_config()
348
- paths = []
349
- for page_entry in self._pages:
350
- for panel_entry in page_entry["panels"]:
351
- ppath = f"button/{panel_entry['id']}/*"
352
- if ppath not in paths:
353
- new_values = []
354
- for obj, kname, sname in self.context.kernel.find(ppath):
355
- new_values.append((obj, kname, sname))
356
- self.set_panel_buttons(panel_entry["id"], new_values)
357
- paths.append(ppath)
358
-
359
- def set_panel_buttons(self, key, new_values):
360
- found = 0
361
- for page_entry in self._pages:
362
- if "_object" not in page_entry:
363
- continue
364
- if "panels" not in page_entry:
365
- continue
366
- pageobj = page_entry["_object"]
367
- for panel_entry in page_entry["panels"]:
368
- if "_object" not in panel_entry:
369
- continue
370
- panelobj = panel_entry["_object"]
371
- if panel_entry["id"].lower() == key.lower():
372
- # print (key, page_entry["id"], panel_entry)
373
- panelobj.set_buttons(new_values)
374
- found += 1
375
- # if found == 0:
376
- # print (f"Did not find a panel for {key}")
377
-
378
- def restart(self):
379
- self.storage.read_configuration()
380
- self.art.establish_colors()
381
- self.pages = []
382
- self.set_default_pages()
383
- # Define Ribbon.
384
- self.__set_ribbonbar()
385
- # And now we query the kernel to recreate the buttons
386
- paths = []
387
- for page_entry in self._pages:
388
- for panel_entry in page_entry["panels"]:
389
- ppath = f"button/{panel_entry['id']}/*"
390
- if ppath not in paths:
391
- new_values = []
392
- for obj, kname, sname in self.context.kernel.find(ppath):
393
- new_values.append((obj, kname, sname))
394
- self.set_panel_buttons(panel_entry["id"], new_values)
395
- paths.append(ppath)
396
- self.art.current_page = self.first_page()
397
- self.modified()
398
-
399
- @signal_listener("ribbon_recreate")
400
- def on_recreate(self, origin, target, *args):
401
- if target is not None and target != self.identifier:
402
- return
403
- self.restart()
404
-
405
- @signal_listener("ribbon_show_labels")
406
- def on_show_labels_change(self, origin, value, *args):
407
- if self.allow_labels:
408
- self.toggle_show_labels(value)
409
-
410
- @lookup_listener("button/basicediting")
411
- def set_editing_buttons(self, new_values, old_values):
412
- self.set_panel_buttons("basicediting", new_values)
413
-
414
- @lookup_listener("button/undo")
415
- def set_undo_buttons(self, new_values, old_values):
416
- self.set_panel_buttons("undo", new_values)
417
-
418
- @lookup_listener("button/project")
419
- def set_project_buttons(self, new_values, old_values):
420
- self.set_panel_buttons("project", new_values)
421
-
422
- @lookup_listener("button/modify")
423
- def set_modify_buttons(self, new_values, old_values):
424
- self.set_panel_buttons("modify", new_values)
425
-
426
- @lookup_listener("button/geometry")
427
- def set_geometry_buttons(self, new_values, old_values):
428
- self.set_panel_buttons("geometry", new_values)
429
-
430
- @lookup_listener("button/preparation")
431
- def set_preparation_buttons(self, new_values, old_values):
432
- self.set_panel_buttons("preparation", new_values)
433
-
434
- @lookup_listener("button/jobstart")
435
- def set_jobstart_buttons(self, new_values, old_values):
436
- self.set_panel_buttons("jobstart", new_values)
437
-
438
- @lookup_listener("button/control")
439
- def set_control_buttons(self, new_values, old_values):
440
- self.set_panel_buttons("control", new_values)
441
-
442
- @lookup_listener("button/device")
443
- def set_device_buttons(self, new_values, old_values):
444
- self.set_panel_buttons("device", new_values)
445
-
446
- @lookup_listener("button/config")
447
- def set_config_buttons(self, new_values, old_values):
448
- self.set_panel_buttons("config", new_values)
449
-
450
- @lookup_listener("button/align")
451
- def set_align_buttons(self, new_values, old_values):
452
- self.set_panel_buttons("align", new_values)
453
-
454
- @lookup_listener("button/select")
455
- def set_select_buttons(self, new_values, old_values):
456
- self.set_panel_buttons("select", new_values)
457
-
458
- @lookup_listener("button/tool")
459
- def set_tool_buttons(self, new_values, old_values):
460
- self.set_panel_buttons("tool", new_values)
461
-
462
- @lookup_listener("button/lasercontrol")
463
- def set_lasercontrol_buttons(self, new_values, old_values):
464
- self.set_panel_buttons("lasercontrol", new_values)
465
-
466
- @lookup_listener("button/extended_tools")
467
- def set_tool_extended_buttons(self, new_values, old_values):
468
- self.set_panel_buttons("extended_tools", new_values)
469
-
470
- @lookup_listener("button/group")
471
- def set_group_buttons(self, new_values, old_values):
472
- self.set_panel_buttons("group", new_values)
473
-
474
- @signal_listener("emphasized")
475
- def on_emphasis_change(self, origin, *args):
476
- self.apply_enable_rules()
477
-
478
- @signal_listener("undoredo")
479
- def on_undostate_change(self, origin, *args):
480
- self.apply_enable_rules()
481
-
482
- @signal_listener("selected")
483
- def on_selected_change(self, origin, node=None, *args):
484
- self.apply_enable_rules()
485
-
486
- @signal_listener("icons")
487
- def on_requested_change(self, origin, node=None, *args):
488
- self.apply_enable_rules()
489
- self.redrawn()
490
-
491
- @signal_listener("tool_changed")
492
- def on_tool_changed(self, origin, newtool=None, *args):
493
- # Signal provides a tuple with (togglegroup, id)
494
- if newtool is None:
495
- return
496
- if isinstance(newtool, (list, tuple)):
497
- group = newtool[0].lower() if newtool[0] is not None else ""
498
- identifier = newtool[1].lower() if newtool[1] is not None else ""
499
- else:
500
- group = newtool
501
- identifier = ""
502
-
503
- for page in self.pages:
504
- for panel in page.panels:
505
- for button in panel.buttons:
506
- if button.group != group:
507
- continue
508
- button.set_button_toggle(button.identifier == identifier)
509
- self.apply_enable_rules()
510
- self.modified()
511
-
512
- def __set_ribbonbar(self):
513
- """
514
- GUI Specific creation of ribbonbar.
515
- @return:
516
- """
517
- for entry in sorted(self._pages, key=lambda d: d["seq"]):
518
- name_id = entry["id"]
519
- label = _(entry["label"])
520
- panel_list = entry["panels"]
521
- page = self.add_page(
522
- name_id,
523
- wx.ID_ANY,
524
- label,
525
- icons8_opened_folder.GetBitmap(resize=16),
526
- )
527
- entry["_object"] = page
528
- for pentry in sorted(panel_list, key=lambda d: d["seq"]):
529
- p_name_id = pentry["id"]
530
- p_label = _(pentry["label"])
531
- panel = self.add_panel(
532
- p_name_id,
533
- parent=page,
534
- id=wx.ID_ANY,
535
- label=p_label,
536
- icon=icons8_opened_folder.GetBitmap(),
537
- )
538
- pentry["_object"] = panel
539
-
540
- self.modified()
541
-
542
- def pane_show(self):
543
- pass
544
-
545
- def pane_hide(self):
546
- """
547
- On pane_hide all the listeners are disabled.
548
- @return:
549
- """
550
- for page in self.pages:
551
- for panel in page.panels:
552
- for key, listener in panel._registered_signals:
553
- self.context.unlisten(key, listener)
554
-
555
- @signal_listener("page")
556
- def on_page_signal(self, origin, pagename=None, *args):
557
- """
558
- Page listener to force the given active page on the triggering of a page signal.
559
- @param origin:
560
- @param pagename:
561
- @param args:
562
- @return:
563
- """
564
- if pagename is None:
565
- return
566
- pagename = pagename.lower()
567
- if pagename == "":
568
- pagename = "project"
569
- for p in self.pages:
570
- if p.label.lower() == pagename:
571
- self.art.current_page = p
572
- self.modified()
573
- if getattr(self.context.root, "_active_page", "") != pagename:
574
- setattr(self.context.root, "_active_page", pagename)
575
-
576
-
577
- class RibbonEditor(wx.Panel):
578
- """
579
- RibbonEditor is a panel that allows you to define the content
580
- of the ribbonbar on top of Mks main window
581
- """
582
-
583
- def __init__(self, *args, context=None, **kwds):
584
- # begin wxGlade: PassesPanel.__init__
585
- kwds["style"] = kwds.get("style", 0)
586
- wx.Panel.__init__(self, *args, **kwds)
587
-
588
- self.context = context
589
- self.ribbon_identifier = "primary"
590
-
591
- self.available_options = []
592
- self.available_labels = []
593
- self._config = None
594
- self.current_page = None
595
-
596
- sizer_main = wx.BoxSizer(wx.HORIZONTAL)
597
- sizer_ribbons = StaticBoxSizer(self, wx.ID_ANY, _("Ribbons"), wx.VERTICAL)
598
-
599
- sizer_pages = StaticBoxSizer(self, wx.ID_ANY, _("Pages"), wx.VERTICAL)
600
- sizer_panels = StaticBoxSizer(self, wx.ID_ANY, _("Panels"), wx.VERTICAL)
601
- sizer_available_panels = StaticBoxSizer(
602
- self, wx.ID_ANY, _("Available Panels"), wx.VERTICAL
603
- )
604
-
605
- choices = [v for v in self.context._ribbons]
606
- self.combo_ribbons = wx.ComboBox(
607
- self, wx.ID_ANY, choices=choices, style=wx.CB_DROPDOWN | wx.CB_READONLY
608
- )
609
- self.check_labels = wx.CheckBox(self, wx.ID_ANY, _("Show the Ribbon Labels"))
610
-
611
- sizer_ribbons.Add(self.combo_ribbons, 0, wx.EXPAND, 0)
612
- sizer_ribbons.Add(self.check_labels, 0, wx.EXPAND, 0)
613
- self.list_pages = wx.ListBox(self, wx.ID_ANY, style=wx.LB_SINGLE)
614
- self.text_param_page = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
615
-
616
- self.list_panels = wx.ListBox(self, wx.ID_ANY, style=wx.LB_SINGLE)
617
- self.button_add_page = wx.StaticBitmap(
618
- self, wx.ID_ANY, size=dip_size(self, 30, 30)
619
- )
620
- self.button_del_page = wx.StaticBitmap(
621
- self, wx.ID_ANY, size=dip_size(self, 30, 30)
622
- )
623
- self.button_up_page = wx.StaticBitmap(
624
- self, wx.ID_ANY, size=dip_size(self, 30, 30)
625
- )
626
- self.button_down_page = wx.StaticBitmap(
627
- self, wx.ID_ANY, size=dip_size(self, 30, 20)
628
- )
629
- testsize = dip_size(self, 20, 20)
630
- iconsize = testsize[0]
631
- self.button_add_page.SetBitmap(
632
- icon_add_new.GetBitmap(resize=iconsize, buffer=1)
633
- )
634
- self.button_del_page.SetBitmap(icon_trash.GetBitmap(resize=iconsize, buffer=1))
635
- self.button_up_page.SetBitmap(icons8_up.GetBitmap(resize=iconsize, buffer=1))
636
- self.button_down_page.SetBitmap(
637
- icons8_down.GetBitmap(resize=iconsize, buffer=1)
638
- )
639
-
640
- self.button_del_panel = wx.StaticBitmap(
641
- self, wx.ID_ANY, size=dip_size(self, 30, 30)
642
- )
643
- self.button_up_panel = wx.StaticBitmap(
644
- self, wx.ID_ANY, size=dip_size(self, 30, 30)
645
- )
646
- self.button_down_panel = wx.StaticBitmap(
647
- self, wx.ID_ANY, size=dip_size(self, 30, 30)
648
- )
649
- self.button_del_panel.SetBitmap(icon_trash.GetBitmap(resize=iconsize, buffer=1))
650
- self.button_up_panel.SetBitmap(icons8_up.GetBitmap(resize=iconsize, buffer=1))
651
- self.button_down_panel.SetBitmap(
652
- icons8_down.GetBitmap(resize=iconsize, buffer=1)
653
- )
654
-
655
- self.list_options = wx.ListBox(self, wx.ID_ANY, style=wx.LB_SINGLE)
656
- self.button_add_panel = wx.Button(self, wx.ID_ANY, _("Add to page"))
657
- self.button_apply = wx.Button(self, wx.ID_ANY, _("Apply"))
658
- self.button_reset = wx.Button(self, wx.ID_ANY, _("Reset to Default"))
659
-
660
- sizer_button = wx.BoxSizer(wx.VERTICAL)
661
- sizer_button.Add(self.button_add_panel, 1, wx.EXPAND, 0)
662
- sizer_button.Add(self.button_apply, 1, wx.EXPAND, 0)
663
- sizer_button.Add(self.button_reset, 1, wx.EXPAND, 0)
664
-
665
- sizer_available_panels.Add(self.list_options, 1, wx.EXPAND, 0)
666
- sizer_available_panels.Add(sizer_button, 0, wx.EXPAND, 0)
667
-
668
- hsizer_page = wx.BoxSizer(wx.HORIZONTAL)
669
- hsizer_page.Add(self.text_param_page, 1, wx.EXPAND, 0)
670
- hsizer_page.Add(self.button_add_page, 0, wx.EXPAND, 0)
671
- hsizer_page.Add(self.button_del_page, 0, wx.EXPAND, 0)
672
- hsizer_page.Add(self.button_up_page, 0, wx.EXPAND, 0)
673
- hsizer_page.Add(self.button_down_page, 0, wx.EXPAND, 0)
674
-
675
- sizer_pages.Add(self.list_pages, 1, wx.EXPAND, 0)
676
- sizer_pages.Add(hsizer_page, 0, wx.EXPAND, 0)
677
-
678
- hsizer_panel = wx.BoxSizer(wx.HORIZONTAL)
679
- hsizer_panel.Add(self.button_del_panel, 0, wx.EXPAND, 0)
680
- hsizer_panel.Add(self.button_up_panel, 0, wx.EXPAND, 0)
681
- hsizer_panel.Add(self.button_down_panel, 0, wx.EXPAND, 0)
682
- sizer_panels.Add(self.list_panels, 1, wx.EXPAND, 0)
683
- sizer_panels.Add(hsizer_panel, 0, wx.EXPAND, 0)
684
-
685
- sizer_active_config = wx.BoxSizer(wx.VERTICAL)
686
- sizer_active_config.Add(sizer_ribbons, 0, wx.EXPAND, 0)
687
- sizer_active_config.Add(sizer_pages, 1, wx.EXPAND, 0)
688
- sizer_active_config.Add(sizer_panels, 1, wx.EXPAND, 0)
689
-
690
- sizer_main.Add(sizer_active_config, 1, wx.EXPAND, 0)
691
- sizer_main.Add(sizer_available_panels, 1, wx.EXPAND, 0)
692
-
693
- # Explanatory tooltips
694
- self.button_add_panel.SetToolTip(
695
- _("Add the selected panel to the selected page")
696
- )
697
- self.button_apply.SetToolTip(_("Apply the configuration"))
698
- self.button_reset.SetToolTip(
699
- _("Reset the ribbon appearance to the default configuration")
700
- )
701
-
702
- self.button_add_page.SetToolTip(_("Add an additional page to the ribbon"))
703
- self.button_del_page.SetToolTip(_("Remove the selected page from the list"))
704
- self.button_down_page.SetToolTip(
705
- _("Decrease the position of the selected page")
706
- )
707
- self.button_up_page.SetToolTip(_("Increase the position of the selected page"))
708
- self.text_param_page.SetToolTip(_("Modify the label of the selected page"))
709
-
710
- self.button_del_panel.SetToolTip(_("Remove the selected panel from the list"))
711
- self.button_down_panel.SetToolTip(
712
- _("Decrease the position of the selected panel")
713
- )
714
- self.button_up_panel.SetToolTip(
715
- _("Increase the position of the selected panel")
716
- )
717
- self.check_labels.SetToolTip(
718
- _("Allow/suppress the display of labels beneath the icons in the ribbon")
719
- )
720
-
721
- self.fill_options()
722
- self.SetSizer(sizer_main)
723
- self.Layout()
724
- self.list_options.Bind(wx.EVT_LISTBOX, self.on_list_options_click)
725
- self.list_options.Bind(wx.EVT_LISTBOX_DCLICK, self.on_list_options_dclick)
726
-
727
- self.button_add_panel.Bind(wx.EVT_BUTTON, self.on_button_add_panel_click)
728
- self.button_apply.Bind(wx.EVT_BUTTON, self.on_button_apply_click)
729
- self.button_reset.Bind(wx.EVT_BUTTON, self.on_button_reset_click)
730
- self.combo_ribbons.Bind(wx.EVT_COMBOBOX, self.on_combo_ribbon)
731
-
732
- self.list_pages.Bind(wx.EVT_LISTBOX, self.on_list_pages_click)
733
- self.list_panels.Bind(wx.EVT_LISTBOX, self.on_list_panels_click)
734
-
735
- self.text_param_page.Bind(wx.EVT_TEXT, self.on_text_label)
736
- self.text_param_page.Bind(wx.EVT_TEXT_ENTER, self.on_text_label_enter)
737
-
738
- self.button_add_page.Bind(wx.EVT_LEFT_DOWN, self.on_button_add_page_click)
739
- self.button_del_page.Bind(wx.EVT_LEFT_DOWN, self.on_button_page_delete)
740
- self.button_up_page.Bind(wx.EVT_LEFT_DOWN, self.on_move_page_up)
741
- self.button_down_page.Bind(wx.EVT_LEFT_DOWN, self.on_move_page_down)
742
-
743
- self.button_del_panel.Bind(wx.EVT_LEFT_DOWN, self.on_button_panel_delete)
744
- self.button_up_panel.Bind(wx.EVT_LEFT_DOWN, self.on_move_panel_up)
745
- self.button_down_panel.Bind(wx.EVT_LEFT_DOWN, self.on_move_panel_down)
746
-
747
- self.combo_ribbons.SetSelection(0)
748
- self.on_combo_ribbon(None)
749
-
750
- # ---- Generic routines to access data
751
-
752
- def current_ribbon(self):
753
- result = None
754
- if hasattr(self.context, "_ribbons"):
755
- if self.ribbon_identifier in self.context._ribbons:
756
- result = self.context._ribbons[self.ribbon_identifier]
757
- return result
758
-
759
- def get_page(self, pageid=None):
760
- if pageid is None:
761
- pageid = self.current_page
762
- for p in self._config:
763
- if p["id"] == pageid:
764
- return p
765
- return None
766
-
767
- def update_page(self, pageid, newlabel=None, newseq=None):
768
- for p in self._config:
769
- if p["id"] == pageid:
770
- if newlabel is not None:
771
- p["label"] = newlabel
772
- if newseq is not None:
773
- p["seq"] = newseq
774
-
775
- # ---- Routines to fill listboxes
776
-
777
- def fill_pages(self, reload=False, reposition=None):
778
- self.list_pages.Clear()
779
- if reload is None:
780
- reload = False
781
- if reload:
782
- rib = self.current_ribbon()
783
- if rib is None:
784
- return
785
- self._config = rib.get_current_config()
786
- pages = []
787
- idx = 0
788
- for p_idx, page_entry in enumerate(
789
- sorted(self._config, key=lambda d: d["seq"])
790
- ):
791
- pages.append(f"{page_entry['id']} ({page_entry['label']})")
792
- if reposition is not None and page_entry["id"] == reposition:
793
- idx = p_idx
794
- self.list_pages.SetItems(pages)
795
- if len(pages) > 0:
796
- self.list_pages.SetSelection(idx)
797
- self.on_list_pages_click(None)
798
-
799
- def fill_panels(self, pageid=None, reposition=None):
800
- if pageid is None:
801
- pageid = self.current_page
802
- self.list_panels.Clear()
803
- panels = []
804
- tidx = 0
805
- cidx = 0
806
- for page_entry in self._config:
807
- if pageid == page_entry["id"]:
808
- panel_list = page_entry["panels"]
809
- for panel_entry in sorted(panel_list, key=lambda d: d["seq"]):
810
- panels.append(f"{panel_entry['id']}")
811
- if reposition is not None and reposition == panel_entry["id"]:
812
- tidx = cidx
813
- cidx += 1
814
-
815
- self.list_panels.SetItems(panels)
816
- if len(panels) > 0:
817
- self.list_panels.SetSelection(tidx)
818
-
819
- def fill_options(self):
820
- # Query all registered button sections
821
- self.available_options.clear()
822
- self.available_labels.clear()
823
- for d_name in self.context.kernel.match("button", suffix=False):
824
- secs = d_name.split("/")
825
- # print(f"{d_name} {secs}")
826
- if len(secs) > 2:
827
- section = secs[1]
828
- button = secs[2]
829
- try:
830
- idx = self.available_options.index(section)
831
- self.available_labels[idx] += f", {button}"
832
- except ValueError:
833
- self.available_options.append(section)
834
- self.available_labels.append(button)
835
-
836
- self.list_options.Clear()
837
- self.list_options.SetItems(self.available_options)
838
-
839
- # ---- Event Handler Routines
840
-
841
- def on_combo_ribbon(self, event):
842
- idx = self.combo_ribbons.GetSelection()
843
- if idx < 0:
844
- return
845
- rlist = list(self.context._ribbons.keys())
846
- if len(rlist) > 0:
847
- self.ribbon_identifier = rlist[idx]
848
- rib = self.current_ribbon()
849
- self.check_labels.SetValue(rib.art.show_labels)
850
- self.check_labels.Enable(rib.allow_labels)
851
- self.fill_pages(reload=True)
852
-
853
- def on_move_page_up(self, event):
854
- newconfig = []
855
- for p_idx, page_entry in enumerate(
856
- sorted(self._config, key=lambda d: d["seq"])
857
- ):
858
- if page_entry["id"] == self.current_page and p_idx > 0:
859
- newconfig.insert(p_idx - 1, page_entry)
860
- else:
861
- newconfig.append(page_entry)
862
- for p_idx, page_entry in enumerate(newconfig):
863
- page_entry["seq"] = p_idx + 1
864
- self._config = newconfig
865
- self.fill_pages(reposition=self.current_page)
866
-
867
- def on_move_page_down(self, event):
868
- newconfig = []
869
- for p_idx, page_entry in enumerate(
870
- sorted(self._config, key=lambda d: d["seq"], reverse=True)
871
- ):
872
- if page_entry["id"] == self.current_page and p_idx > 0:
873
- newconfig.insert(1, page_entry)
874
- else:
875
- newconfig.insert(0, page_entry)
876
- for p_idx, page_entry in enumerate(newconfig):
877
- page_entry["seq"] = p_idx + 1
878
- self._config = newconfig
879
- self.fill_pages(reposition=self.current_page)
880
-
881
- def on_list_pages_click(self, event):
882
- idx = self.list_pages.GetSelection()
883
- flag1 = idx >= 0
884
- self.button_del_page.Enable(flag1)
885
- self.button_down_page.Enable(flag1)
886
- self.button_up_page.Enable(flag1)
887
- self.button_add_page.Enable(True)
888
- page_label = ""
889
- if idx >= 0:
890
- term = self.list_pages.GetStringSelection()
891
- idx = term.find(" (")
892
- if idx >= 0:
893
- page = term[0:idx]
894
- self.current_page = page
895
- self.fill_panels()
896
- p = self.get_page(page)
897
- if p is not None:
898
- page_label = p["label"]
899
- self.text_param_page.SetValue(page_label)
900
-
901
- def on_button_page_delete(self, event):
902
- new_config = []
903
- for p in self._config:
904
- if p["id"] != self.current_page:
905
- new_config.append(p)
906
- if len(new_config) != self._config:
907
- self._config = new_config
908
- self.current_page = None
909
- self.fill_pages()
910
-
911
- def on_button_add_page_click(self, event):
912
- newid = f"page_{len(self._config) + 1}"
913
- newentry = {
914
- "id": newid,
915
- "label": f"Page {len(self._config) + 1}",
916
- "seq": len(self._config) + 1,
917
- "panels": [],
918
- }
919
- self._config.append(newentry)
920
- self.fill_pages(reposition=newid)
921
-
922
- def on_move_panel_up(self, event):
923
- idx = self.list_panels.GetSelection()
924
- if idx < 0:
925
- return
926
- panelid = self.list_panels.GetString(idx)
927
- for page in self._config:
928
- if page["id"] != self.current_page:
929
- continue
930
- panel_list = page["panels"]
931
- newconfig = []
932
- for p_idx, panel_entry in enumerate(
933
- sorted(panel_list, key=lambda d: d["seq"])
934
- ):
935
- if panel_entry["id"] == panelid and p_idx > 0:
936
- newconfig.insert(p_idx - 1, panel_entry)
937
- else:
938
- newconfig.append(panel_entry)
939
- for p_idx, panel_entry in enumerate(newconfig):
940
- panel_entry["seq"] = p_idx + 1
941
- page["panels"] = newconfig
942
- self.fill_panels(reposition=panelid)
943
-
944
- def on_move_panel_down(self, event):
945
- idx = self.list_panels.GetSelection()
946
- if idx < 0:
947
- return
948
- panelid = self.list_panels.GetString(idx)
949
- for page in self._config:
950
- if page["id"] != self.current_page:
951
- continue
952
- panelid = self.list_panels.GetString(idx)
953
- panel_list = page["panels"]
954
- newconfig = []
955
- for p_idx, panel_entry in enumerate(
956
- sorted(panel_list, key=lambda d: d["seq"], reverse=True)
957
- ):
958
- if panel_entry["id"] == panelid and p_idx > 0:
959
- newconfig.insert(1, panel_entry)
960
- else:
961
- newconfig.insert(0, panel_entry)
962
- for p_idx, panel_entry in enumerate(newconfig):
963
- panel_entry["seq"] = p_idx + 1
964
- page["panels"] = newconfig
965
- self.fill_panels(reposition=panelid)
966
-
967
- def on_button_panel_delete(self, event):
968
- idx = self.list_panels.GetSelection()
969
- if idx < 0:
970
- return
971
- panelid = self.list_panels.GetString(idx)
972
- for page in self._config:
973
- if page["id"] != self.current_page:
974
- continue
975
- panel_list = page["panels"]
976
- new_config = []
977
- seqnum = 1
978
- for panel in sorted(panel_list, key=lambda d: d["seq"]):
979
- if panel["id"] != panelid:
980
- panel["seq"] = seqnum
981
- new_config.append(panel)
982
- seqnum += 1
983
- page["panels"] = new_config
984
- self.fill_panels()
985
-
986
- def on_list_panels_click(self, event):
987
- idx = self.list_panels.GetSelection()
988
- flag1 = idx >= 0
989
- self.button_del_panel.Enable(flag1)
990
- self.button_down_panel.Enable(flag1)
991
- self.button_up_panel.Enable(flag1)
992
- if idx < 0:
993
- return
994
-
995
- def on_text_label(self, event):
996
- label = self.text_param_page.GetValue()
997
- self.update_page(self.current_page, newlabel=label)
998
-
999
- def on_text_label_enter(self, event):
1000
- self.fill_pages(reposition=self.current_page)
1001
-
1002
- def on_list_options_click(self, event):
1003
- ttip = ""
1004
- idx = self.list_options.GetSelection()
1005
- if idx >= 0:
1006
- self.button_add_panel.Enable(True)
1007
- ttip = f"{self.available_options[idx]}: {self.available_labels[idx]}"
1008
- else:
1009
- self.button_add_panel.Enable(False)
1010
- self.list_options.SetToolTip(ttip)
1011
-
1012
- def on_list_options_dclick(self, event):
1013
- self.on_button_add_panel_click(event)
1014
-
1015
- def on_button_add_panel_click(self, event):
1016
- idx = self.list_options.GetSelection()
1017
- if idx < 0:
1018
- return
1019
- panel = self.available_options[idx]
1020
- for page in self._config:
1021
- if page["id"] != self.current_page:
1022
- continue
1023
- if panel not in page["panels"]:
1024
- newpanel = {
1025
- "id": panel,
1026
- "label": panel,
1027
- "seq": len(page["panels"]) + 2,
1028
- }
1029
- page["panels"].append(newpanel)
1030
- self.fill_pages(reposition=self.current_page)
1031
-
1032
- def on_button_apply_click(self, event):
1033
- ob = self.current_ribbon()
1034
- if ob is None:
1035
- return
1036
- if len(self._config) == 0:
1037
- return
1038
- ob.storage.delete_all_persistent()
1039
- ob.storage.write_persistent("Ribbon", "identifier", self.ribbon_identifier)
1040
- show_labels = self.check_labels.GetValue()
1041
- ob.storage.write_persistent("Ribbon", "show_labels", show_labels)
1042
-
1043
- for p_idx, page_entry in enumerate(
1044
- sorted(self._config, key=lambda d: d["seq"])
1045
- ):
1046
- section = f"Page_{p_idx + 1}"
1047
- ob.storage.write_persistent(section, "id", page_entry["id"])
1048
- ob.storage.write_persistent(section, "label", page_entry["label"])
1049
- ob.storage.write_persistent(section, "seq", page_entry["seq"])
1050
- panel_list = page_entry["panels"]
1051
- for panel_idx, pentry in enumerate(
1052
- sorted(panel_list, key=lambda d: d["seq"])
1053
- ):
1054
- key = f"Panel_{panel_idx + 1}"
1055
- result = (
1056
- pentry["id"],
1057
- pentry["seq"],
1058
- pentry["label"],
1059
- )
1060
- ob.storage.write_persistent(section, key, result)
1061
- ob.storage.write_configuration()
1062
- ob.restart()
1063
- # self.context.signal("ribbon_recreate", self.ribbon_identifier)
1064
-
1065
- def on_button_reset_click(self, event):
1066
- rib = self.current_ribbon()
1067
- if rib is None:
1068
- return
1069
- self._config = rib.get_default_config()
1070
- self.fill_pages()
1071
-
1072
- def pane_hide(self):
1073
- pass
1074
-
1075
- def pane_show(self):
1076
- pass
1
+ """
2
+ The WxmRibbon Bar is a core aspect of MeerK40t's interaction. To allow fully dynamic buttons we use the ribbonbar
3
+ control widget built into meerk40t.
4
+
5
+ Panels are created in a static fashion within this class. But the contents of those individual ribbon panels are defined
6
+ in the kernel lookup.
7
+
8
+ service.register(
9
+ "button/control/Redlight",
10
+ {
11
+ ...<def>...
12
+ },
13
+ )
14
+
15
+ This setup allows us to define a large series of buttons with a button prefix and the name of a panel it should be
16
+ added to as well as a unique name. This defines within the meerk40t ecosystem a methodology of specifically creating
17
+ dynamic buttons. When the service changes because of a switch in the device (for example) it will trigger the lookup
18
+ listeners which will update their contents, triggering the update within this control.
19
+ """
20
+
21
+ import wx
22
+ from wx import aui
23
+
24
+ from meerk40t.gui.icons import (
25
+ get_default_icon_size,
26
+ icon_add_new,
27
+ icon_edit,
28
+ icon_trash,
29
+ icons8_down,
30
+ icons8_opened_folder,
31
+ icons8_up,
32
+ )
33
+ from meerk40t.gui.ribbon import RibbonBarPanel
34
+ from meerk40t.gui.wxutils import (
35
+ StaticBoxSizer,
36
+ TextCtrl,
37
+ dip_size,
38
+ wxButton,
39
+ wxCheckBox,
40
+ wxComboBox,
41
+ wxListBox,
42
+ wxStaticBitmap,
43
+ wxStaticText,
44
+ )
45
+ from meerk40t.kernel import Settings, lookup_listener, signal_listener
46
+
47
+ _ = wx.GetTranslation
48
+
49
+
50
+ def register_panel_ribbon(window, context):
51
+ iconsize = get_default_icon_size(context)
52
+ minh = 3 * iconsize + 25
53
+ pane_ribbon = (
54
+ aui.AuiPaneInfo()
55
+ .Name("ribbon")
56
+ .Top()
57
+ .BestSize(300, minh)
58
+ .FloatingSize(640, minh)
59
+ .Caption(_("Ribbon"))
60
+ .CaptionVisible(not context.pane_lock)
61
+ )
62
+ pane_ribbon.dock_proportion = 640
63
+ ribbon = MKRibbonBarPanel(
64
+ window, wx.ID_ANY, context=context, pane=pane_ribbon, identifier="primary"
65
+ )
66
+ pane_ribbon.control = ribbon
67
+ pane_ribbon.helptext = _("Toolbar with the main commands to control jobs and devices")
68
+
69
+ window.on_pane_create(pane_ribbon)
70
+ context.register("pane/ribbon", pane_ribbon)
71
+ context.register("ribbonbar/primary", ribbon)
72
+
73
+ minh = int(1.5 * iconsize)
74
+ pane_tool = (
75
+ aui.AuiPaneInfo()
76
+ .Name("tools")
77
+ .Left()
78
+ .BestSize(minh, 300)
79
+ .FloatingSize(minh, 640)
80
+ .Caption(_("Tools"))
81
+ .CaptionVisible(not context.pane_lock)
82
+ )
83
+ pane_tool.dock_proportion = 640
84
+ pane_tool.helptext = _("Icon-bar with the main object creation tools")
85
+ ribbon = MKRibbonBarPanel(
86
+ window,
87
+ wx.ID_ANY,
88
+ context=context,
89
+ pane=pane_tool,
90
+ identifier="tools",
91
+ orientation="auto",
92
+ show_labels=False,
93
+ )
94
+ pane_tool.control = ribbon
95
+
96
+ window.on_pane_create(pane_tool)
97
+ context.register("pane/tools", pane_tool)
98
+ context.register("ribbonbar/tools", ribbon)
99
+
100
+ pane_edittool = (
101
+ aui.AuiPaneInfo()
102
+ .Name("edittools")
103
+ .Left()
104
+ .BestSize(minh, 300)
105
+ .FloatingSize(minh, 640)
106
+ .Caption(_("Modify"))
107
+ .CaptionVisible(not context.pane_lock)
108
+ )
109
+ pane_edittool.dock_proportion = 640
110
+ pane_edittool.helptext = _("Icon-bar with object modification tools")
111
+ ribbon = MKRibbonBarPanel(
112
+ window,
113
+ wx.ID_ANY,
114
+ context=context,
115
+ pane=pane_edittool,
116
+ identifier="edittools",
117
+ orientation="auto",
118
+ show_labels=False,
119
+ )
120
+ pane_edittool.control = ribbon
121
+
122
+ window.on_pane_create(pane_edittool)
123
+ context.register("pane/edittools", pane_edittool)
124
+ context.register("ribbonbar/edittools", ribbon)
125
+
126
+ choices = [
127
+ {
128
+ "attr": "ribbon_show_labels",
129
+ "object": context,
130
+ "default": False,
131
+ "type": bool,
132
+ "label": _("Show the Ribbon Labels"),
133
+ "tip": _(
134
+ "Active: Show the labels for ribbonbar.\n"
135
+ "Inactive: Hide the ribbon labels."
136
+ ),
137
+ "page": "Gui",
138
+ "section": "Appearance",
139
+ },
140
+ ]
141
+
142
+ context.kernel.register_choices("preferences", choices)
143
+
144
+
145
+ class MKRibbonBarPanel(RibbonBarPanel):
146
+ def __init__(
147
+ self,
148
+ parent,
149
+ id,
150
+ context=None,
151
+ pane=None,
152
+ identifier=None,
153
+ orientation=None,
154
+ show_labels=None,
155
+ **kwds,
156
+ ):
157
+ RibbonBarPanel.__init__(self, parent, id, context, pane, **kwds)
158
+ self.pane = pane
159
+ self.identifier = identifier
160
+ self.userbuttons = []
161
+ if orientation is not None:
162
+ if orientation.lower().startswith("h"):
163
+ self.art.orientation = self.art.RIBBON_ORIENTATION_HORIZONTAL
164
+ elif orientation.lower().startswith("v"):
165
+ self.art.orientation = self.art.RIBBON_ORIENTATION_VERTICAL
166
+ elif orientation.lower().startswith("a"):
167
+ self.art.orientation = self.art.RIBBON_ORIENTATION_AUTO
168
+ if show_labels is not None:
169
+ self.art.show_labels = show_labels
170
+ # Make myself known in context
171
+ if not hasattr(context, "_ribbons"):
172
+ self.context._ribbons = {}
173
+ self.context._ribbons[self.identifier] = self
174
+
175
+ self.storage = Settings(
176
+ self.context.kernel.name, f"ribbon_{identifier}.cfg", create_backup=True
177
+ ) # keep backup
178
+ self.storage.read_configuration()
179
+
180
+ self.allow_labels = bool(show_labels is None or show_labels)
181
+ # Layout properties.
182
+ if self.allow_labels:
183
+ self.toggle_show_labels(context.setting(bool, "ribbon_show_labels", True))
184
+
185
+ self._pages = []
186
+ self.set_default_pages()
187
+ # Define Ribbon.
188
+ self.__set_ribbonbar()
189
+
190
+ def get_default_config(self):
191
+ button_config = []
192
+ if self.identifier == "tools":
193
+ ribbon_config = [
194
+ {
195
+ "id": "tools", # identifier
196
+ "label": "Tools", # Label
197
+ "panels": [ # Panels to include
198
+ {
199
+ "id": "select",
200
+ "label": "Select + Edit",
201
+ "seq": 1,
202
+ },
203
+ {
204
+ "id": "lasercontrol",
205
+ "label": "Laser",
206
+ "seq": 2,
207
+ },
208
+ {
209
+ "id": "tool",
210
+ "label": "Create",
211
+ "seq": 3,
212
+ },
213
+ {
214
+ "id": "extended_tools",
215
+ "label": "Extended Tools",
216
+ "seq": 4,
217
+ },
218
+ {
219
+ "id": "group",
220
+ "label": "Group",
221
+ "seq": 5,
222
+ },
223
+ ],
224
+ "seq": 1, # Sequence
225
+ },
226
+ ]
227
+ elif self.identifier == "primary":
228
+ ribbon_config = [
229
+ {
230
+ "id": "home", # identifier
231
+ "label": "Project", # Label
232
+ "panels": [ # Panels to include
233
+ {
234
+ "id": "project",
235
+ "label": "Project",
236
+ "seq": 1,
237
+ },
238
+ {
239
+ "id": "basicediting",
240
+ "label": "Edit",
241
+ "seq": 2,
242
+ },
243
+ {
244
+ "id": "undo",
245
+ "label": "Undo",
246
+ "seq": 3,
247
+ },
248
+ {
249
+ "id": "preparation",
250
+ "label": "Prepare",
251
+ "seq": 4,
252
+ },
253
+ {
254
+ "id": "control",
255
+ "label": "Control",
256
+ "seq": 5,
257
+ },
258
+ {
259
+ "id": "jobstart",
260
+ "label": "Execute",
261
+ "seq": 6,
262
+ },
263
+ {
264
+ "id": "user",
265
+ "label": "User",
266
+ "seq": 7,
267
+ },
268
+ ],
269
+ "seq": 1, # Sequence
270
+ },
271
+ {
272
+ "id": "modify",
273
+ "label": "Modify",
274
+ "panels": [
275
+ {
276
+ "id": "undo",
277
+ "label": "Undo",
278
+ "seq": 1,
279
+ },
280
+ {
281
+ "id": "modify",
282
+ "label": "Modification",
283
+ "seq": 2,
284
+ },
285
+ {
286
+ "id": "geometry",
287
+ "label": "Geometry",
288
+ "seq": 3,
289
+ },
290
+ {
291
+ "id": "align",
292
+ "label": "Alignment",
293
+ "seq": 4,
294
+ },
295
+ ],
296
+ "seq": 2,
297
+ },
298
+ {
299
+ "id": "config",
300
+ "label": "Config",
301
+ "panels": [ # Panels to include
302
+ {
303
+ "id": "config",
304
+ "label": "Configuration",
305
+ "seq": 1,
306
+ },
307
+ {
308
+ "id": "device",
309
+ "label": "Devices",
310
+ "seq": 1,
311
+ },
312
+ ],
313
+ "seq": 3,
314
+ },
315
+ ]
316
+ elif self.identifier == "edittools":
317
+ ribbon_config = [
318
+ {
319
+ "id": "modify",
320
+ "label": "Modify",
321
+ "panels": [
322
+ {
323
+ "id": "undo",
324
+ "label": "Undo",
325
+ "seq": 1,
326
+ },
327
+ {
328
+ "id": "group",
329
+ "label": "Group",
330
+ "seq": 2,
331
+ },
332
+ {
333
+ "id": "modify",
334
+ "label": "Modification",
335
+ "seq": 3,
336
+ },
337
+ {
338
+ "id": "geometry",
339
+ "label": "Geometry",
340
+ "seq": 4,
341
+ },
342
+ {
343
+ "id": "align",
344
+ "label": "Alignment",
345
+ "seq": 5,
346
+ },
347
+ ],
348
+ "seq": 1,
349
+ },
350
+ ]
351
+ else:
352
+ ribbon_config = []
353
+
354
+ return ribbon_config, button_config
355
+
356
+ def get_current_config(self):
357
+ import meerk40t.gui.icons as mkicons
358
+
359
+ icon_list = []
360
+ for entry in dir(mkicons):
361
+ # print (entry)
362
+ if entry.startswith("icon"):
363
+ s = getattr(mkicons, entry)
364
+ if isinstance(s, (mkicons.VectorIcon, mkicons.PyEmbeddedImage)):
365
+ icon_list.append(entry)
366
+
367
+ # Is the storage empty? Then we use the default config
368
+ ribbon_config, button_config = self.get_default_config()
369
+ testid = self.storage.read_persistent(str, "Ribbon", "identifier", "")
370
+ # print(f"testid='{testid}', should be: {self.identifier}")
371
+ if testid != self.identifier:
372
+ # Thats fishy...
373
+ return ribbon_config, button_config
374
+ flag = self.storage.read_persistent(
375
+ bool, "Ribbon", "show_labels", self.art.show_labels
376
+ )
377
+ self.art.show_labels = flag
378
+ newconfig = []
379
+ newbuttons = []
380
+ page_idx = 0
381
+ while True:
382
+ section = f"Page_{page_idx + 1}"
383
+ info = self.storage.read_persistent_string_dict(section)
384
+ if info is None or len(info) == 0:
385
+ break
386
+ # print (info)
387
+ newpage = {"id": "id", "label": "Label", "seq": 1}
388
+ k = f"{section}/id"
389
+ if k in info:
390
+ value = self.storage.read_persistent(str, section, "id", "")
391
+ newpage["id"] = value
392
+ k = f"{section}/label"
393
+ if k in info:
394
+ value = self.storage.read_persistent(str, section, "label", "")
395
+ newpage["label"] = value
396
+ k = f"{section}/seq"
397
+ if k in info:
398
+ value = self.storage.read_persistent(int, section, "seq", None)
399
+ if value:
400
+ try:
401
+ newpage["seq"] = int(value)
402
+ except ValueError:
403
+ pass
404
+ panel_list = []
405
+ panel_idx = 0
406
+ while True:
407
+ k = f"panel_{panel_idx + 1}"
408
+ panel_info = self.storage.read_persistent(tuple, section, k, None)
409
+ # print (f"{k} : {panel_info}")
410
+ if (
411
+ panel_info is None
412
+ or not isinstance(panel_info, (list, tuple))
413
+ or len(panel_info) < 3
414
+ ):
415
+ break
416
+ panel_dict = {}
417
+ panel_dict["id"] = panel_info[0]
418
+ panel_dict["seq"] = int(panel_info[1])
419
+ panel_dict["label"] = panel_info[2]
420
+ panel_list.append(panel_dict)
421
+
422
+ panel_idx += 1
423
+
424
+ newpage["panels"] = panel_list
425
+ if panel_list:
426
+ newconfig.append(newpage)
427
+
428
+ page_idx += 1
429
+
430
+ # ------ Read user defined buttons...
431
+ button_idx = 0
432
+
433
+ def delayed_command(command):
434
+ def handler(*args):
435
+ cmd = command.replace("\\n", "\n")
436
+ self.context(cmd + "\n")
437
+
438
+ return handler
439
+
440
+ while True:
441
+ section = f"Button_{button_idx + 1}"
442
+ info = self.storage.read_persistent_string_dict(section)
443
+ if info is None or len(info) == 0:
444
+ break
445
+ # print (info)
446
+ userbutton = {
447
+ "id": f"user_button_{button_idx+1}",
448
+ "label": "Label",
449
+ "action_left": "",
450
+ "action_right": "",
451
+ "enable": "always",
452
+ "visible": "always",
453
+ "tip": _("This could be your tooltip"),
454
+ "icon": "icon_edit",
455
+ "seq": 1,
456
+ }
457
+ for content in ("label", "tip", "action_left", "action_right"):
458
+ k = f"{section}/{content}"
459
+ if k in info:
460
+ value = self.storage.read_persistent(str, section, content, "")
461
+ userbutton[content] = value
462
+ for content in ("enable", "visible"):
463
+ k = f"{section}/{content}"
464
+ if k in info:
465
+ value = self.storage.read_persistent(str, section, content, "")
466
+ if value in ("always", "selected", "selected2"):
467
+ userbutton[content] = value
468
+
469
+ k = f"{section}/icon"
470
+ if k in info:
471
+ value = self.storage.read_persistent(str, section, "icon", "")
472
+ if value in icon_list:
473
+ userbutton["icon"] = value
474
+ k = f"{section}/seq"
475
+ if k in info:
476
+ value = self.storage.read_persistent(int, section, "seq", None)
477
+ if value:
478
+ try:
479
+ userbutton["seq"] = int(value)
480
+ except ValueError:
481
+ pass
482
+ d = {
483
+ "label": userbutton["label"],
484
+ "icon": getattr(mkicons, userbutton["icon"], None),
485
+ "tip": userbutton["tip"],
486
+ }
487
+ if userbutton["action_left"]:
488
+ d["action"] = delayed_command(userbutton["action_left"])
489
+ if userbutton["action_right"]:
490
+ d["action_right"] = delayed_command(userbutton["action_right"])
491
+ if userbutton["enable"] == "selected":
492
+ d["rule_enabled"] = (
493
+ lambda cond: len(list(self.context.elements.elems(emphasized=True)))
494
+ > 0
495
+ )
496
+ if userbutton["enable"] == "selected2":
497
+ d["rule_enabled"] = (
498
+ lambda cond: len(list(self.context.elements.elems(emphasized=True)))
499
+ > 1
500
+ )
501
+ if userbutton["visible"] == "selected":
502
+ d["rule_visible"] = (
503
+ lambda cond: len(list(self.context.elements.elems(emphasized=True)))
504
+ > 0
505
+ )
506
+ if userbutton["visible"] == "selected2":
507
+ d["rule_visible"] = (
508
+ lambda cond: len(list(self.context.elements.elems(emphasized=True)))
509
+ > 1
510
+ )
511
+
512
+ self.context.kernel.register(f"button/user/{userbutton['id']}", d)
513
+ button_config.append(userbutton)
514
+ button_idx += 1
515
+
516
+ if newconfig:
517
+ ribbon_config = newconfig
518
+ return ribbon_config, button_config
519
+
520
+ def set_default_pages(self):
521
+ self._pages, self.userbuttons = self.get_current_config()
522
+ paths = []
523
+ for page_entry in self._pages:
524
+ for panel_entry in page_entry["panels"]:
525
+ ppath = f"button/{panel_entry['id']}/*"
526
+ if ppath not in paths:
527
+ new_values = []
528
+ new_values.extend(
529
+ (obj, kname, sname)
530
+ for obj, kname, sname in self.context.kernel.find(ppath)
531
+ )
532
+ self.set_panel_buttons(panel_entry["id"], new_values)
533
+ paths.append(ppath)
534
+
535
+ def set_panel_buttons(self, key, new_values):
536
+ found = 0
537
+ for page_entry in self._pages:
538
+ if "_object" not in page_entry:
539
+ continue
540
+ if "panels" not in page_entry:
541
+ continue
542
+ for panel_entry in page_entry["panels"]:
543
+ if "_object" not in panel_entry:
544
+ continue
545
+ panelobj = panel_entry["_object"]
546
+ if panel_entry["id"].lower() == key.lower():
547
+ # print (key, page_entry["id"], panel_entry)
548
+ panelobj.set_buttons(new_values)
549
+ found += 1
550
+ # if found == 0:
551
+ # print (f"Did not find a panel for {key}")
552
+
553
+ def restart(self):
554
+ self.storage.read_configuration()
555
+ self.art.establish_colors()
556
+ self.pages = []
557
+ self.userbuttons = []
558
+ self.set_default_pages()
559
+ # Define Ribbon.
560
+ self.__set_ribbonbar()
561
+ # And now we query the kernel to recreate the buttons
562
+ paths = []
563
+ for page_entry in self._pages:
564
+ for panel_entry in page_entry["panels"]:
565
+ ppath = f"button/{panel_entry['id']}/*"
566
+ if ppath not in paths:
567
+ new_values = []
568
+ new_values.extend(
569
+ (obj, kname, sname)
570
+ for obj, kname, sname in self.context.kernel.find(ppath)
571
+ )
572
+ self.set_panel_buttons(panel_entry["id"], new_values)
573
+ paths.append(ppath)
574
+ self.art.current_page = self.first_page()
575
+ self.modified()
576
+
577
+ @signal_listener("ribbon_recreate")
578
+ def on_recreate(self, origin, target, *args):
579
+ if target is not None and target != self.identifier:
580
+ return
581
+ self.restart()
582
+
583
+ @signal_listener("ribbon_show_labels")
584
+ def on_show_labels_change(self, origin, value, *args):
585
+ if self.allow_labels:
586
+ self.toggle_show_labels(value)
587
+
588
+ # Notabene: the lookup listener design will look for partial fits!
589
+ # So a listener to "button/tool" would get changes from
590
+ # "button/tool", "button/tools" and "button/toolabcdefgh"
591
+ # So to add buttons of your own make sure you are using a distinct name!
592
+
593
+ @lookup_listener("button/basicediting")
594
+ def set_editing_buttons(self, new_values, old_values):
595
+ self.set_panel_buttons("basicediting", new_values)
596
+
597
+ @lookup_listener("button/undo")
598
+ def set_undo_buttons(self, new_values, old_values):
599
+ self.set_panel_buttons("undo", new_values)
600
+
601
+ @lookup_listener("button/project")
602
+ def set_project_buttons(self, new_values, old_values):
603
+ self.set_panel_buttons("project", new_values)
604
+
605
+ @lookup_listener("button/modify")
606
+ def set_modify_buttons(self, new_values, old_values):
607
+ self.set_panel_buttons("modify", new_values)
608
+
609
+ @lookup_listener("button/geometry")
610
+ def set_geometry_buttons(self, new_values, old_values):
611
+ self.set_panel_buttons("geometry", new_values)
612
+
613
+ @lookup_listener("button/preparation")
614
+ def set_preparation_buttons(self, new_values, old_values):
615
+ self.set_panel_buttons("preparation", new_values)
616
+
617
+ @lookup_listener("button/jobstart")
618
+ def set_jobstart_buttons(self, new_values, old_values):
619
+ self.set_panel_buttons("jobstart", new_values)
620
+
621
+ @lookup_listener("button/control")
622
+ def set_control_buttons(self, new_values, old_values):
623
+ self.set_panel_buttons("control", new_values)
624
+
625
+ @lookup_listener("button/device")
626
+ def set_device_buttons(self, new_values, old_values):
627
+ self.set_panel_buttons("device", new_values)
628
+
629
+ @lookup_listener("button/config")
630
+ def set_config_buttons(self, new_values, old_values):
631
+ self.set_panel_buttons("config", new_values)
632
+
633
+ @lookup_listener("button/align")
634
+ def set_align_buttons(self, new_values, old_values):
635
+ self.set_panel_buttons("align", new_values)
636
+
637
+ @lookup_listener("button/select")
638
+ def set_select_buttons(self, new_values, old_values):
639
+ self.set_panel_buttons("select", new_values)
640
+
641
+ @lookup_listener("button/tools")
642
+ def set_tool_buttons(self, new_values, old_values):
643
+ self.set_panel_buttons("tool", new_values)
644
+
645
+ @lookup_listener("button/lasercontrol")
646
+ def set_lasercontrol_buttons(self, new_values, old_values):
647
+ self.set_panel_buttons("lasercontrol", new_values)
648
+
649
+ @lookup_listener("button/extended_tools")
650
+ def set_tool_extended_buttons(self, new_values, old_values):
651
+ self.set_panel_buttons("extended_tools", new_values)
652
+
653
+ @lookup_listener("button/group")
654
+ def set_group_buttons(self, new_values, old_values):
655
+ self.set_panel_buttons("group", new_values)
656
+
657
+ @lookup_listener("button/user")
658
+ def set_user_buttons(self, new_values, old_values):
659
+ self.set_panel_buttons("user", new_values)
660
+
661
+ @signal_listener("emphasized")
662
+ def on_emphasis_change(self, origin, *args):
663
+ # self.context.elements.set_start_time("Ribbon rule")
664
+ self.apply_enable_rules()
665
+ # self.context.elements.set_end_time("Ribbon rule")
666
+
667
+ @signal_listener("undoredo")
668
+ def on_undostate_change(self, origin, *args):
669
+ self.apply_enable_rules()
670
+
671
+ @signal_listener("selected")
672
+ @signal_listener("element_property_update")
673
+ def on_selected_change(self, origin, node=None, *args):
674
+ self.apply_enable_rules()
675
+
676
+ @signal_listener("icons")
677
+ def on_requested_change(self, origin, node=None, *args):
678
+ self.apply_enable_rules()
679
+ self.redrawn()
680
+
681
+ @signal_listener("tool_changed")
682
+ def on_tool_changed(self, origin, newtool=None, *args):
683
+ # Signal provides a tuple with (togglegroup, id)
684
+ if newtool is None:
685
+ return
686
+ if isinstance(newtool, (list, tuple)):
687
+ group = newtool[0].lower() if newtool[0] is not None else ""
688
+ identifier = newtool[1].lower() if newtool[1] is not None else ""
689
+ else:
690
+ group = newtool
691
+ identifier = ""
692
+
693
+ for page in self.pages:
694
+ for panel in page.panels:
695
+ for button in panel.buttons:
696
+ if button.group != group:
697
+ continue
698
+ button.set_button_toggle(button.identifier == identifier)
699
+ self.apply_enable_rules()
700
+ self.modified()
701
+
702
+ def __set_ribbonbar(self):
703
+ """
704
+ GUI Specific creation of ribbonbar.
705
+ @return:
706
+ """
707
+ for entry in sorted(self._pages, key=lambda d: d["seq"]):
708
+ name_id = entry["id"]
709
+ label = _(entry["label"])
710
+ panel_list = entry["panels"]
711
+ page = self.add_page(
712
+ name_id,
713
+ wx.ID_ANY,
714
+ label,
715
+ icons8_opened_folder.GetBitmap(resize=16),
716
+ )
717
+ entry["_object"] = page
718
+ for pentry in sorted(panel_list, key=lambda d: d["seq"]):
719
+ p_name_id = pentry["id"]
720
+ p_label = _(pentry["label"])
721
+ panel = self.add_panel(
722
+ p_name_id,
723
+ parent=page,
724
+ id=wx.ID_ANY,
725
+ label=p_label,
726
+ icon=icons8_opened_folder.GetBitmap(),
727
+ )
728
+ pentry["_object"] = panel
729
+
730
+ self.modified()
731
+
732
+ def pane_show(self):
733
+ pass
734
+
735
+ def pane_hide(self):
736
+ """
737
+ On pane_hide all the listeners are disabled.
738
+ @return:
739
+ """
740
+ for page in self.pages:
741
+ for panel in page.panels:
742
+ for key, listener in panel._registered_signals:
743
+ self.context.unlisten(key, listener)
744
+
745
+ @signal_listener("page")
746
+ def on_page_signal(self, origin, pagename=None, *args):
747
+ """
748
+ Page listener to force the given active page on the triggering of a page signal.
749
+ @param origin:
750
+ @param pagename:
751
+ @param args:
752
+ @return:
753
+ """
754
+ if pagename is None:
755
+ return
756
+ pagename = pagename.lower()
757
+ if pagename == "":
758
+ pagename = "home"
759
+ for p in self.pages:
760
+ # print (f"compare '{p.reference.lower()}' to '{pagename}'")
761
+ if p.reference.lower() == pagename:
762
+ self.art.current_page = p
763
+ self.modified()
764
+ if getattr(self.context.root, "_active_page", "") != pagename:
765
+ setattr(self.context.root, "_active_page", pagename)
766
+
767
+
768
+ class RibbonEditor(wx.Panel):
769
+ """
770
+ RibbonEditor is a panel that allows you to define the content
771
+ of the ribbonbar on top of Mks main window
772
+ """
773
+
774
+ def __init__(self, *args, context=None, **kwds):
775
+ # begin wxGlade: PassesPanel.__init__
776
+ kwds["style"] = kwds.get("style", 0)
777
+ wx.Panel.__init__(self, *args, **kwds)
778
+
779
+ self.context = context
780
+ self.context.themes.set_window_colors(self)
781
+ self.SetHelpText("ribboneditor")
782
+ self.ribbon_identifier = "primary"
783
+
784
+ self.available_options = []
785
+ self.available_labels = []
786
+ self._config = None
787
+ self._config_buttons = None
788
+ self.current_page = None
789
+ self.current_button = None
790
+
791
+ sizer_main = wx.BoxSizer(wx.HORIZONTAL)
792
+ sizer_ribbons = StaticBoxSizer(self, wx.ID_ANY, _("Ribbons"), wx.VERTICAL)
793
+
794
+ sizer_pages = StaticBoxSizer(self, wx.ID_ANY, _("Pages"), wx.VERTICAL)
795
+ sizer_panels = StaticBoxSizer(self, wx.ID_ANY, _("Panels"), wx.VERTICAL)
796
+ sizer_available_panels = StaticBoxSizer(
797
+ self, wx.ID_ANY, _("Available Panels"), wx.VERTICAL
798
+ )
799
+
800
+ choices = [v for v in self.context._ribbons]
801
+ self.combo_ribbons = wxComboBox(
802
+ self, wx.ID_ANY, choices=choices, style=wx.CB_DROPDOWN | wx.CB_READONLY
803
+ )
804
+ self.check_labels = wxCheckBox(self, wx.ID_ANY, _("Show the Ribbon Labels"))
805
+
806
+ sizer_ribbons.Add(self.combo_ribbons, 0, wx.EXPAND, 0)
807
+ sizer_ribbons.Add(self.check_labels, 0, wx.EXPAND, 0)
808
+ self.list_pages = wxListBox(self, wx.ID_ANY, style=wx.LB_SINGLE)
809
+
810
+ self.text_param_page = TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
811
+
812
+ self.list_panels = wxListBox(self, wx.ID_ANY, style=wx.LB_SINGLE)
813
+ bsize = dip_size(self, 30, 30)
814
+ self.button_add_page = wxStaticBitmap(self, wx.ID_ANY, size=bsize)
815
+ self.button_del_page = wxStaticBitmap(self, wx.ID_ANY, size=bsize)
816
+ self.button_up_page = wxStaticBitmap(self, wx.ID_ANY, size=bsize)
817
+ self.button_down_page = wxStaticBitmap(self, wx.ID_ANY, size=bsize)
818
+
819
+ testsize = dip_size(self, 20, 20)
820
+ iconsize = testsize[0]
821
+ # Circumvent a WXPython bug at high resolutions under Windows
822
+ bmp = icon_trash.GetBitmap(resize=iconsize, buffer=1)
823
+ self.button_del_page.SetBitmap(bmp)
824
+ testsize = self.button_del_page.GetBitmap().Size
825
+ if testsize[0] != iconsize:
826
+ iconsize = int(iconsize * iconsize / testsize[0])
827
+
828
+ self.button_add_page.SetBitmap(icon_add_new.GetBitmap(resize=iconsize))
829
+ self.button_del_page.SetBitmap(icon_trash.GetBitmap(resize=iconsize, buffer=1))
830
+ self.button_up_page.SetBitmap(icons8_up.GetBitmap(resize=iconsize, buffer=1))
831
+ self.button_down_page.SetBitmap(
832
+ icons8_down.GetBitmap(resize=iconsize, buffer=1)
833
+ )
834
+
835
+ self.button_del_panel = wxStaticBitmap(self, wx.ID_ANY, size=bsize)
836
+ self.button_up_panel = wxStaticBitmap(self, wx.ID_ANY, size=bsize)
837
+ self.button_down_panel = wxStaticBitmap(self, wx.ID_ANY, size=bsize)
838
+
839
+ self.button_del_panel.SetBitmap(icon_trash.GetBitmap(resize=iconsize, buffer=1))
840
+ self.button_up_panel.SetBitmap(icons8_up.GetBitmap(resize=iconsize, buffer=1))
841
+ self.button_down_panel.SetBitmap(
842
+ icons8_down.GetBitmap(resize=iconsize, buffer=1)
843
+ )
844
+
845
+ self.list_options = wxListBox(self, wx.ID_ANY, style=wx.LB_SINGLE)
846
+ self.button_add_panel = wxButton(self, wx.ID_ANY, _("Add to page"))
847
+ self.button_apply = wxButton(self, wx.ID_ANY, _("Apply"))
848
+ self.button_reset = wxButton(self, wx.ID_ANY, _("Reset to Default"))
849
+
850
+ sizer_button = wx.BoxSizer(wx.VERTICAL)
851
+ sizer_button.Add(self.button_add_panel, 1, wx.EXPAND, 0)
852
+ sizer_button.Add(self.button_apply, 1, wx.EXPAND, 0)
853
+ sizer_button.Add(self.button_reset, 1, wx.EXPAND, 0)
854
+
855
+ sizer_available_panels.Add(self.list_options, 1, wx.EXPAND, 0)
856
+ sizer_available_panels.Add(sizer_button, 0, wx.EXPAND, 0)
857
+
858
+ hsizer_page = wx.BoxSizer(wx.HORIZONTAL)
859
+ hsizer_page.Add(self.text_param_page, 1, wx.EXPAND, 0)
860
+ hsizer_page.Add(self.button_add_page, 0, wx.EXPAND, 0)
861
+ hsizer_page.Add(self.button_del_page, 0, wx.EXPAND, 0)
862
+ hsizer_page.Add(self.button_up_page, 0, wx.EXPAND, 0)
863
+ hsizer_page.Add(self.button_down_page, 0, wx.EXPAND, 0)
864
+
865
+ sizer_pages.Add(self.list_pages, 1, wx.EXPAND, 0)
866
+ sizer_pages.Add(hsizer_page, 0, wx.EXPAND, 0)
867
+
868
+ hsizer_panel = wx.BoxSizer(wx.HORIZONTAL)
869
+ hsizer_panel.Add(self.button_del_panel, 0, wx.EXPAND, 0)
870
+ hsizer_panel.Add(self.button_up_panel, 0, wx.EXPAND, 0)
871
+ hsizer_panel.Add(self.button_down_panel, 0, wx.EXPAND, 0)
872
+ sizer_panels.Add(self.list_panels, 1, wx.EXPAND, 0)
873
+ sizer_panels.Add(hsizer_panel, 0, wx.EXPAND, 0)
874
+
875
+ sizer_active_config = wx.BoxSizer(wx.VERTICAL)
876
+ sizer_active_config.Add(sizer_ribbons, 0, wx.EXPAND, 0)
877
+ sizer_active_config.Add(sizer_pages, 1, wx.EXPAND, 0)
878
+ sizer_active_config.Add(sizer_panels, 1, wx.EXPAND, 0)
879
+
880
+ sizer_main.Add(sizer_active_config, 1, wx.EXPAND, 0)
881
+
882
+ sizer_buttons = StaticBoxSizer(
883
+ self, wx.ID_ANY, _("User-defined buttons"), wx.VERTICAL
884
+ )
885
+ self.list_buttons = wxListBox(self, wx.ID_ANY, style=wx.LB_SINGLE)
886
+ sizer_button_buttons = wx.BoxSizer(wx.HORIZONTAL)
887
+
888
+ self.button_add_button = wxStaticBitmap(self, wx.ID_ANY, size=bsize)
889
+ self.button_del_button = wxStaticBitmap(self, wx.ID_ANY, size=bsize)
890
+ self.button_edit_button = wxStaticBitmap(self, wx.ID_ANY, size=bsize)
891
+ self.button_up_button = wxStaticBitmap(self, wx.ID_ANY, size=bsize)
892
+ self.button_down_button = wxStaticBitmap(self, wx.ID_ANY, size=bsize)
893
+
894
+ self.button_add_button.SetBitmap(icon_add_new.GetBitmap(resize=iconsize))
895
+ self.button_del_button.SetBitmap(
896
+ icon_trash.GetBitmap(resize=iconsize, buffer=1)
897
+ )
898
+ self.button_edit_button.SetBitmap(
899
+ icon_edit.GetBitmap(resize=iconsize, buffer=1)
900
+ )
901
+ self.button_up_button.SetBitmap(icons8_up.GetBitmap(resize=iconsize, buffer=1))
902
+ self.button_down_button.SetBitmap(
903
+ icons8_down.GetBitmap(resize=iconsize, buffer=1)
904
+ )
905
+
906
+ sizer_button_buttons.Add(self.button_add_button, 0, wx.EXPAND, 0)
907
+ sizer_button_buttons.Add(self.button_del_button, 0, wx.EXPAND, 0)
908
+ sizer_button_buttons.Add(self.button_edit_button, 0, wx.EXPAND, 0)
909
+ sizer_button_buttons.Add(self.button_up_button, 0, wx.EXPAND, 0)
910
+ sizer_button_buttons.Add(self.button_down_button, 0, wx.EXPAND, 0)
911
+
912
+ sizer_buttons.Add(self.list_buttons, 1, wx.EXPAND, 0)
913
+ sizer_buttons.Add(sizer_button_buttons, 0, wx.EXPAND, 0)
914
+
915
+ sizer_right = wx.BoxSizer(wx.VERTICAL)
916
+ sizer_right.Add(sizer_available_panels, 1, wx.EXPAND, 0)
917
+ sizer_right.Add(sizer_buttons, 1, wx.EXPAND, 0)
918
+ sizer_main.Add(sizer_right, 1, wx.EXPAND, 0)
919
+
920
+ # Explanatory tooltips
921
+ self.button_add_panel.SetToolTip(
922
+ _("Add the selected panel to the selected page")
923
+ )
924
+ self.button_apply.SetToolTip(_("Apply the configuration"))
925
+ self.button_reset.SetToolTip(
926
+ _("Reset the ribbon appearance to the default configuration")
927
+ )
928
+
929
+ self.button_add_page.SetToolTip(_("Add an additional page to the ribbon"))
930
+ self.button_del_page.SetToolTip(_("Remove the selected page from the list"))
931
+ self.button_down_page.SetToolTip(
932
+ _("Decrease the position of the selected page")
933
+ )
934
+ self.button_up_page.SetToolTip(_("Increase the position of the selected page"))
935
+ self.text_param_page.SetToolTip(_("Modify the label of the selected page"))
936
+
937
+ self.button_del_panel.SetToolTip(_("Remove the selected panel from the list"))
938
+ self.button_down_panel.SetToolTip(
939
+ _("Decrease the position of the selected panel")
940
+ )
941
+ self.button_up_panel.SetToolTip(
942
+ _("Increase the position of the selected panel")
943
+ )
944
+ self.check_labels.SetToolTip(
945
+ _("Allow/suppress the display of labels beneath the icons in the ribbon")
946
+ )
947
+
948
+ self.button_add_button.SetToolTip(_("Add an additional user-defined button"))
949
+ self.button_del_button.SetToolTip(
950
+ _("Remove the selected user-defined button from the list")
951
+ )
952
+ self.button_down_button.SetToolTip(
953
+ _("Decrease the position of the selected button")
954
+ )
955
+ self.button_up_button.SetToolTip(
956
+ _("Increase the position of the selected button")
957
+ )
958
+ self.button_edit_button.SetToolTip(
959
+ _("Modify the content of the selected button")
960
+ )
961
+
962
+ self.fill_options()
963
+ self.SetSizer(sizer_main)
964
+ self.Layout()
965
+ self.list_options.Bind(wx.EVT_LISTBOX, self.on_list_options_click)
966
+ self.list_options.Bind(wx.EVT_LISTBOX_DCLICK, self.on_list_options_dclick)
967
+
968
+ self.button_add_panel.Bind(wx.EVT_BUTTON, self.on_button_add_panel_click)
969
+ self.button_apply.Bind(wx.EVT_BUTTON, self.on_button_apply_click)
970
+ self.button_reset.Bind(wx.EVT_BUTTON, self.on_button_reset_click)
971
+ self.combo_ribbons.Bind(wx.EVT_COMBOBOX, self.on_combo_ribbon)
972
+
973
+ self.list_pages.Bind(wx.EVT_LISTBOX, self.on_list_pages_click)
974
+ self.list_panels.Bind(wx.EVT_LISTBOX, self.on_list_panels_click)
975
+
976
+ self.list_buttons.Bind(wx.EVT_LISTBOX, self.on_list_buttons_click)
977
+ self.list_buttons.Bind(wx.EVT_LISTBOX_DCLICK, self.on_button_edit_button)
978
+
979
+ self.text_param_page.Bind(wx.EVT_TEXT, self.on_text_label)
980
+ self.text_param_page.Bind(wx.EVT_TEXT_ENTER, self.on_text_label_enter)
981
+
982
+ self.button_add_page.Bind(wx.EVT_LEFT_DOWN, self.on_button_add_page_click)
983
+ self.button_del_page.Bind(wx.EVT_LEFT_DOWN, self.on_button_page_delete)
984
+ self.button_up_page.Bind(wx.EVT_LEFT_DOWN, self.on_move_page_up)
985
+ self.button_down_page.Bind(wx.EVT_LEFT_DOWN, self.on_move_page_down)
986
+
987
+ self.button_del_panel.Bind(wx.EVT_LEFT_DOWN, self.on_button_panel_delete)
988
+ self.button_up_panel.Bind(wx.EVT_LEFT_DOWN, self.on_move_panel_up)
989
+ self.button_down_panel.Bind(wx.EVT_LEFT_DOWN, self.on_move_panel_down)
990
+
991
+ self.button_add_button.Bind(wx.EVT_LEFT_DOWN, self.on_button_add_button)
992
+ self.button_del_button.Bind(wx.EVT_LEFT_DOWN, self.on_button_button_delete)
993
+ self.button_up_button.Bind(wx.EVT_LEFT_DOWN, self.on_move_button_up)
994
+ self.button_down_button.Bind(wx.EVT_LEFT_DOWN, self.on_move_button_down)
995
+ self.button_edit_button.Bind(wx.EVT_LEFT_DOWN, self.on_button_edit_button)
996
+
997
+ self.combo_ribbons.SetSelection(0)
998
+ self.on_combo_ribbon(None)
999
+ self.fill_button()
1000
+
1001
+ # ---- Generic routines to access data
1002
+
1003
+ def current_ribbon(self):
1004
+ return (
1005
+ self.context._ribbons[self.ribbon_identifier]
1006
+ if hasattr(self.context, "_ribbons")
1007
+ and self.ribbon_identifier in self.context._ribbons
1008
+ else None
1009
+ )
1010
+
1011
+ def get_page(self, pageid=None):
1012
+ if pageid is None:
1013
+ pageid = self.current_page
1014
+ for p in self._config:
1015
+ if p["id"] == pageid:
1016
+ return p
1017
+ return None
1018
+
1019
+ def update_page(self, pageid, newlabel=None, newseq=None):
1020
+ for p in self._config:
1021
+ if p["id"] == pageid:
1022
+ if newlabel is not None:
1023
+ p["label"] = newlabel
1024
+ if newseq is not None:
1025
+ p["seq"] = newseq
1026
+
1027
+ # ---- Routines to fill listboxes
1028
+
1029
+ def fill_pages(self, reload=False, reposition=None):
1030
+ self.list_pages.Clear()
1031
+ if reload is None:
1032
+ reload = False
1033
+ if reload:
1034
+ rib = self.current_ribbon()
1035
+ if rib is None:
1036
+ return
1037
+ self._config, self._config_buttons = rib.get_current_config()
1038
+ pages = []
1039
+ idx = 0
1040
+ for p_idx, page_entry in enumerate(
1041
+ sorted(self._config, key=lambda d: d["seq"])
1042
+ ):
1043
+ pages.append(f"{page_entry['id']} ({page_entry['label']})")
1044
+ if reposition is not None and page_entry["id"] == reposition:
1045
+ idx = p_idx
1046
+ self.list_pages.SetItems(pages)
1047
+ if pages:
1048
+ self.list_pages.SetSelection(idx)
1049
+ self.on_list_pages_click(None)
1050
+
1051
+ def fill_button(self, reload=False, reposition=None):
1052
+ self.list_buttons.Clear()
1053
+ buttons = []
1054
+ idx = 0
1055
+ for b_idx, button_entry in enumerate(
1056
+ sorted(self._config_buttons, key=lambda d: d["seq"])
1057
+ ):
1058
+ buttons.append(
1059
+ f"{button_entry['id']} ({button_entry['label']}): {button_entry['action_left']}"
1060
+ )
1061
+ if reposition is not None and button_entry["id"] == reposition:
1062
+ idx = b_idx
1063
+ self.list_buttons.SetItems(buttons)
1064
+ if buttons:
1065
+ self.list_buttons.SetSelection(idx)
1066
+ self.on_list_buttons_click(None)
1067
+
1068
+ def fill_panels(self, pageid=None, reposition=None):
1069
+ if pageid is None:
1070
+ pageid = self.current_page
1071
+ self.list_panels.Clear()
1072
+ panels = []
1073
+ tidx = 0
1074
+ cidx = 0
1075
+ for page_entry in self._config:
1076
+ if pageid == page_entry["id"]:
1077
+ panel_list = page_entry["panels"]
1078
+ for panel_entry in sorted(panel_list, key=lambda d: d["seq"]):
1079
+ panels.append(f"{panel_entry['id']}")
1080
+ if reposition is not None and reposition == panel_entry["id"]:
1081
+ tidx = cidx
1082
+ cidx += 1
1083
+
1084
+ self.list_panels.SetItems(panels)
1085
+ if panels:
1086
+ self.list_panels.SetSelection(tidx)
1087
+
1088
+ def fill_options(self):
1089
+ # Query all registered button sections
1090
+ self.available_options.clear()
1091
+ self.available_labels.clear()
1092
+ for d_name in self.context.kernel.match("button", suffix=False):
1093
+ secs = d_name.split("/")
1094
+ # print(f"{d_name} {secs}")
1095
+ if len(secs) > 2:
1096
+ section = secs[1]
1097
+ button = secs[2]
1098
+ try:
1099
+ idx = self.available_options.index(section)
1100
+ self.available_labels[idx] += f", {button}"
1101
+ except ValueError:
1102
+ self.available_options.append(section)
1103
+ self.available_labels.append(button)
1104
+
1105
+ self.list_options.Clear()
1106
+ self.list_options.SetItems(self.available_options)
1107
+
1108
+ # ---- Event Handler Routines
1109
+
1110
+ def on_combo_ribbon(self, event):
1111
+ idx = self.combo_ribbons.GetSelection()
1112
+ if idx < 0:
1113
+ return
1114
+ rlist = list(self.context._ribbons.keys())
1115
+ if len(rlist) > 0:
1116
+ self.ribbon_identifier = rlist[idx]
1117
+ rib = self.current_ribbon()
1118
+ self.check_labels.SetValue(rib.art.show_labels)
1119
+ self.check_labels.Enable(rib.allow_labels)
1120
+ self.fill_pages(reload=True)
1121
+
1122
+ def on_move_page_up(self, event):
1123
+ newconfig = []
1124
+ for p_idx, page_entry in enumerate(
1125
+ sorted(self._config, key=lambda d: d["seq"])
1126
+ ):
1127
+ if page_entry["id"] == self.current_page and p_idx > 0:
1128
+ newconfig.insert(p_idx - 1, page_entry)
1129
+ else:
1130
+ newconfig.append(page_entry)
1131
+ for p_idx, page_entry in enumerate(newconfig):
1132
+ page_entry["seq"] = p_idx + 1
1133
+ self._config = newconfig
1134
+ self.fill_pages(reposition=self.current_page)
1135
+
1136
+ def on_move_page_down(self, event):
1137
+ newconfig = []
1138
+ for p_idx, page_entry in enumerate(
1139
+ sorted(self._config, key=lambda d: d["seq"], reverse=True)
1140
+ ):
1141
+ if page_entry["id"] == self.current_page and p_idx > 0:
1142
+ newconfig.insert(1, page_entry)
1143
+ else:
1144
+ newconfig.insert(0, page_entry)
1145
+ for p_idx, page_entry in enumerate(newconfig):
1146
+ page_entry["seq"] = p_idx + 1
1147
+ self._config = newconfig
1148
+ self.fill_pages(reposition=self.current_page)
1149
+
1150
+ def on_list_pages_click(self, event):
1151
+ idx = self.list_pages.GetSelection()
1152
+ flag1 = idx >= 0
1153
+ self.button_del_page.Enable(flag1)
1154
+ self.button_down_page.Enable(flag1)
1155
+ self.button_up_page.Enable(flag1)
1156
+ self.button_add_page.Enable(True)
1157
+ page_label = ""
1158
+ if idx >= 0:
1159
+ term = self.list_pages.GetStringSelection()
1160
+ tidx = term.find(" (")
1161
+ if tidx >= 0:
1162
+ page = term[:tidx]
1163
+ self.current_page = page
1164
+ self.fill_panels()
1165
+ p = self.get_page(page)
1166
+ if p is not None:
1167
+ page_label = p["label"]
1168
+ self.text_param_page.SetValue(page_label)
1169
+
1170
+ def on_button_page_delete(self, event):
1171
+ new_config = []
1172
+ new_config.extend(p for p in self._config if p["id"] != self.current_page)
1173
+ if len(new_config) != self._config:
1174
+ self._config = new_config
1175
+ self.current_page = None
1176
+ self.fill_pages()
1177
+
1178
+ def on_button_add_page_click(self, event):
1179
+ newid = f"page_{len(self._config) + 1}"
1180
+ newentry = {
1181
+ "id": newid,
1182
+ "label": f"Page {len(self._config) + 1}",
1183
+ "seq": len(self._config) + 1,
1184
+ "panels": [],
1185
+ }
1186
+ self._config.append(newentry)
1187
+ self.fill_pages(reposition=newid)
1188
+
1189
+ def on_move_panel_up(self, event):
1190
+ idx = self.list_panels.GetSelection()
1191
+ if idx < 0:
1192
+ return
1193
+ panelid = self.list_panels.GetString(idx)
1194
+ for page in self._config:
1195
+ if page["id"] != self.current_page:
1196
+ continue
1197
+ panel_list = page["panels"]
1198
+ newconfig = []
1199
+ for p_idx, panel_entry in enumerate(
1200
+ sorted(panel_list, key=lambda d: d["seq"])
1201
+ ):
1202
+ if panel_entry["id"] == panelid and p_idx > 0:
1203
+ newconfig.insert(p_idx - 1, panel_entry)
1204
+ else:
1205
+ newconfig.append(panel_entry)
1206
+ for p_idx, panel_entry in enumerate(newconfig):
1207
+ panel_entry["seq"] = p_idx + 1
1208
+ page["panels"] = newconfig
1209
+ self.fill_panels(reposition=panelid)
1210
+
1211
+ def on_move_panel_down(self, event):
1212
+ idx = self.list_panels.GetSelection()
1213
+ if idx < 0:
1214
+ return
1215
+ panelid = self.list_panels.GetString(idx)
1216
+ for page in self._config:
1217
+ if page["id"] != self.current_page:
1218
+ continue
1219
+ panelid = self.list_panels.GetString(idx)
1220
+ panel_list = page["panels"]
1221
+ newconfig = []
1222
+ for p_idx, panel_entry in enumerate(
1223
+ sorted(panel_list, key=lambda d: d["seq"], reverse=True)
1224
+ ):
1225
+ if panel_entry["id"] == panelid and p_idx > 0:
1226
+ newconfig.insert(1, panel_entry)
1227
+ else:
1228
+ newconfig.insert(0, panel_entry)
1229
+ for p_idx, panel_entry in enumerate(newconfig):
1230
+ panel_entry["seq"] = p_idx + 1
1231
+ page["panels"] = newconfig
1232
+ self.fill_panels(reposition=panelid)
1233
+
1234
+ def on_button_panel_delete(self, event):
1235
+ idx = self.list_panels.GetSelection()
1236
+ if idx < 0:
1237
+ return
1238
+ panelid = self.list_panels.GetString(idx)
1239
+ for page in self._config:
1240
+ if page["id"] != self.current_page:
1241
+ continue
1242
+ panel_list = page["panels"]
1243
+ new_config = []
1244
+ seqnum = 1
1245
+ for panel in sorted(panel_list, key=lambda d: d["seq"]):
1246
+ if panel["id"] != panelid:
1247
+ panel["seq"] = seqnum
1248
+ new_config.append(panel)
1249
+ seqnum += 1
1250
+ page["panels"] = new_config
1251
+ self.fill_panels()
1252
+
1253
+ def on_list_panels_click(self, event):
1254
+ idx = self.list_panels.GetSelection()
1255
+ flag1 = idx >= 0
1256
+ self.button_del_panel.Enable(flag1)
1257
+ self.button_down_panel.Enable(flag1)
1258
+ self.button_up_panel.Enable(flag1)
1259
+ if idx < 0:
1260
+ return
1261
+
1262
+ def on_text_label(self, event):
1263
+ label = self.text_param_page.GetValue()
1264
+ self.update_page(self.current_page, newlabel=label)
1265
+
1266
+ def on_text_label_enter(self, event):
1267
+ self.fill_pages(reposition=self.current_page)
1268
+
1269
+ def on_list_options_click(self, event):
1270
+ ttip = ""
1271
+ idx = self.list_options.GetSelection()
1272
+ if idx >= 0:
1273
+ self.button_add_panel.Enable(True)
1274
+ ttip = f"{self.available_options[idx]}: {self.available_labels[idx]}"
1275
+ else:
1276
+ self.button_add_panel.Enable(False)
1277
+ self.list_options.SetToolTip(ttip)
1278
+
1279
+ def on_list_options_dclick(self, event):
1280
+ self.on_button_add_panel_click(event)
1281
+
1282
+ def on_button_add_panel_click(self, event):
1283
+ idx = self.list_options.GetSelection()
1284
+ if idx < 0:
1285
+ return
1286
+ panel = self.available_options[idx]
1287
+ for page in self._config:
1288
+ if page["id"] != self.current_page:
1289
+ continue
1290
+ if panel not in page["panels"]:
1291
+ newpanel = {
1292
+ "id": panel,
1293
+ "label": panel,
1294
+ "seq": len(page["panels"]) + 2,
1295
+ }
1296
+ page["panels"].append(newpanel)
1297
+ self.fill_pages(reposition=self.current_page)
1298
+
1299
+ def on_button_apply_click(self, event):
1300
+ ob = self.current_ribbon()
1301
+ if ob is None:
1302
+ return
1303
+ if len(self._config) == 0:
1304
+ return
1305
+ ob.storage.delete_all_persistent()
1306
+ ob.storage.write_persistent("Ribbon", "identifier", self.ribbon_identifier)
1307
+ show_labels = self.check_labels.GetValue()
1308
+ ob.storage.write_persistent("Ribbon", "show_labels", show_labels)
1309
+
1310
+ for p_idx, page_entry in enumerate(
1311
+ sorted(self._config, key=lambda d: d["seq"])
1312
+ ):
1313
+ section = f"Page_{p_idx + 1}"
1314
+ ob.storage.write_persistent(section, "id", page_entry["id"])
1315
+ ob.storage.write_persistent(section, "label", page_entry["label"])
1316
+ ob.storage.write_persistent(section, "seq", page_entry["seq"])
1317
+ panel_list = page_entry["panels"]
1318
+ for panel_idx, pentry in enumerate(
1319
+ sorted(panel_list, key=lambda d: d["seq"])
1320
+ ):
1321
+ key = f"Panel_{panel_idx + 1}"
1322
+ result = (
1323
+ pentry["id"],
1324
+ pentry["seq"],
1325
+ pentry["label"],
1326
+ )
1327
+ ob.storage.write_persistent(section, key, result)
1328
+ # Write all user-defined buttons
1329
+ for idx, entry in enumerate(
1330
+ sorted(self._config_buttons, key=lambda d: d["seq"])
1331
+ ):
1332
+ section = f"Button_{idx + 1}"
1333
+ ob.storage.write_persistent(section, "id", entry["id"])
1334
+ ob.storage.write_persistent(section, "label", entry["label"])
1335
+ ob.storage.write_persistent(section, "tip", entry["tip"])
1336
+ ob.storage.write_persistent(section, "action_left", entry["action_left"])
1337
+ ob.storage.write_persistent(section, "action_right", entry["action_right"])
1338
+ ob.storage.write_persistent(section, "enable", entry["enable"])
1339
+ ob.storage.write_persistent(section, "visible", entry["visible"])
1340
+ ob.storage.write_persistent(section, "icon", entry["icon"])
1341
+ ob.storage.write_persistent(section, "seq", entry["seq"])
1342
+
1343
+ ob.storage.write_configuration()
1344
+ ob.restart()
1345
+ # self.context.signal("ribbon_recreate", self.ribbon_identifier)
1346
+
1347
+ def on_button_reset_click(self, event):
1348
+ rib = self.current_ribbon()
1349
+ if rib is None:
1350
+ return
1351
+ self._config, self._config_buttons = rib.get_default_config()
1352
+ self.fill_pages()
1353
+ self.fill_button()
1354
+
1355
+ def on_list_buttons_click(self, event):
1356
+ idx = self.list_buttons.GetSelection()
1357
+ self.current_button = None if idx < 0 else self._config_buttons[idx]["id"]
1358
+ flag1 = idx >= 0
1359
+ self.button_del_button.Enable(flag1)
1360
+ self.button_down_button.Enable(flag1)
1361
+ self.button_up_button.Enable(flag1)
1362
+ self.button_edit_button.Enable(flag1)
1363
+ if idx < 0:
1364
+ return
1365
+
1366
+ def on_move_button_up(self, event):
1367
+ newconfig = []
1368
+ for idx, entry in enumerate(
1369
+ sorted(self._config_buttons, key=lambda d: d["seq"])
1370
+ ):
1371
+ if entry["id"] == self.current_button and idx > 0:
1372
+ newconfig.insert(idx - 1, entry)
1373
+ else:
1374
+ newconfig.append(entry)
1375
+ for idx, entry in enumerate(newconfig):
1376
+ entry["seq"] = idx + 1
1377
+ self._config_buttons = newconfig
1378
+ self.fill_button(reposition=self.current_button)
1379
+
1380
+ def on_move_button_down(self, event):
1381
+ newconfig = []
1382
+ for idx, entry in enumerate(
1383
+ sorted(self._config_buttons, key=lambda d: d["seq"], reverse=True)
1384
+ ):
1385
+ if entry["id"] == self.current_button and idx > 0:
1386
+ newconfig.insert(1, entry)
1387
+ else:
1388
+ newconfig.insert(0, entry)
1389
+ for idx, entry in enumerate(newconfig):
1390
+ entry["seq"] = idx + 1
1391
+ self._config_buttons = newconfig
1392
+ self.fill_button(reposition=self.current_button)
1393
+
1394
+ def on_button_button_delete(self, event):
1395
+ new_config = []
1396
+ new_config.extend(
1397
+ p for p in self._config_buttons if p["id"] != self.current_button
1398
+ )
1399
+ if len(new_config) != self._config_buttons:
1400
+ self._config_buttons = new_config
1401
+ self.current_button = None
1402
+ self.fill_button()
1403
+ self.fill_panels()
1404
+
1405
+ def on_button_add_button(self, event):
1406
+ button_idx = len(self._config_buttons) + 1
1407
+ newid = f"user_button_{button_idx}"
1408
+ newentry = {
1409
+ "id": newid,
1410
+ "label": f"Button {button_idx}",
1411
+ "enable": "always",
1412
+ "visible": "always",
1413
+ "action_left": "echo Enter your command here",
1414
+ "action_right": "",
1415
+ "icon": "icon_edit",
1416
+ "tip": _("This could be your tooltip"),
1417
+ "seq": button_idx,
1418
+ }
1419
+ self._config_buttons.append(newentry)
1420
+ self.current_button = newid
1421
+ self.fill_button(reposition=newid)
1422
+ self.fill_panels()
1423
+ # Edit it immediately...
1424
+ self.on_button_edit_button(None)
1425
+
1426
+ def edit_a_button(self, entry):
1427
+ # We edit the thing in place if needed...
1428
+ dlg = wx.Dialog(
1429
+ self,
1430
+ wx.ID_ANY,
1431
+ _("Edit Button"),
1432
+ style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
1433
+ )
1434
+ self.context.themes.set_window_colors(dlg)
1435
+ sizer = wx.BoxSizer(wx.VERTICAL)
1436
+
1437
+ line1 = wx.BoxSizer(wx.HORIZONTAL)
1438
+ label1 = wxStaticText(dlg, wx.ID_ANY, _("Label"))
1439
+ txt_label = TextCtrl(dlg, wx.ID_ANY, value=entry["label"])
1440
+ line1.Add(label1, 0, wx.EXPAND, 0)
1441
+ line1.Add(txt_label, 1, wx.EXPAND, 0)
1442
+
1443
+ line2 = wx.BoxSizer(wx.HORIZONTAL)
1444
+ label2 = wxStaticText(dlg, wx.ID_ANY, _("Tooltip"))
1445
+ txt_tip = TextCtrl(dlg, wx.ID_ANY, value=entry["tip"])
1446
+ line2.Add(label2, 0, wx.EXPAND, 0)
1447
+ line2.Add(txt_tip, 1, wx.EXPAND, 0)
1448
+
1449
+ line3 = wx.BoxSizer(wx.HORIZONTAL)
1450
+ label3 = wxStaticText(dlg, wx.ID_ANY, _("Action left click"))
1451
+ txt_action_left = TextCtrl(dlg, wx.ID_ANY, value=entry["action_left"])
1452
+ line3.Add(label3, 0, wx.EXPAND, 0)
1453
+ line3.Add(txt_action_left, 1, wx.EXPAND, 0)
1454
+
1455
+ line4 = wx.BoxSizer(wx.HORIZONTAL)
1456
+ label4 = wxStaticText(dlg, wx.ID_ANY, _("Action right click"))
1457
+ txt_action_right = TextCtrl(dlg, wx.ID_ANY, value=entry["action_right"])
1458
+ line4.Add(label4, 0, wx.EXPAND, 0)
1459
+ line4.Add(txt_action_right, 1, wx.EXPAND, 0)
1460
+
1461
+ rule_options = (
1462
+ _("Always"),
1463
+ _("When at least one element selected"),
1464
+ _("When at least two elemente selected"),
1465
+ )
1466
+ line5 = wx.BoxSizer(wx.HORIZONTAL)
1467
+ label5 = wxStaticText(dlg, wx.ID_ANY, _("Rule to enable"))
1468
+ combo_enable = wxComboBox(
1469
+ dlg, wx.ID_ANY, choices=rule_options, style=wx.CB_DROPDOWN | wx.CB_READONLY
1470
+ )
1471
+ if entry["enable"] == "selected2":
1472
+ idx = 2
1473
+ elif entry["enable"] == "selected":
1474
+ idx = 1
1475
+ else:
1476
+ idx = 0
1477
+ combo_enable.SetSelection(idx)
1478
+ line5.Add(label5, 0, wx.EXPAND, 0)
1479
+ line5.Add(combo_enable, 1, wx.EXPAND, 0)
1480
+
1481
+ line6 = wx.BoxSizer(wx.HORIZONTAL)
1482
+ label6 = wxStaticText(dlg, wx.ID_ANY, _("Rule to display"))
1483
+ combo_visible = wxComboBox(
1484
+ dlg, wx.ID_ANY, choices=rule_options, style=wx.CB_DROPDOWN | wx.CB_READONLY
1485
+ )
1486
+ if entry["visible"] == "selected2":
1487
+ idx = 2
1488
+ elif entry["visible"] == "selected":
1489
+ idx = 1
1490
+ else:
1491
+ idx = 0
1492
+ combo_visible.SetSelection(idx)
1493
+ line6.Add(label6, 0, wx.EXPAND, 0)
1494
+ line6.Add(combo_visible, 1, wx.EXPAND, 0)
1495
+
1496
+ import meerk40t.gui.icons as mkicons
1497
+
1498
+ icon_list = []
1499
+ for icon in dir(mkicons):
1500
+ # print (entry)
1501
+ if icon.startswith("icon"):
1502
+ s = getattr(mkicons, icon)
1503
+ if isinstance(s, (mkicons.VectorIcon, mkicons.PyEmbeddedImage)):
1504
+ icon_list.append(icon)
1505
+
1506
+ line7 = wx.BoxSizer(wx.HORIZONTAL)
1507
+ label7 = wxStaticText(dlg, wx.ID_ANY, _("Icon"))
1508
+ combo_icon = wxComboBox(
1509
+ dlg, wx.ID_ANY, choices=icon_list, style=wx.CB_DROPDOWN | wx.CB_READONLY
1510
+ )
1511
+ preview = wxStaticBitmap(dlg, wx.ID_ANY, size=dip_size(self, 30, 30))
1512
+ if entry["icon"] in icon_list:
1513
+ combo_icon.SetValue(entry["icon"])
1514
+ line7.Add(label7, 0, wx.EXPAND, 0)
1515
+ line7.Add(combo_icon, 1, wx.EXPAND, 0)
1516
+
1517
+ line0 = wx.BoxSizer(wx.HORIZONTAL)
1518
+ line0.AddStretchSpacer(1)
1519
+ line0.Add(preview, 0, wx.EXPAND, 0)
1520
+
1521
+ def on_icon(event):
1522
+ icon_name = combo_icon.GetValue()
1523
+ ps = preview.GetSize()
1524
+ if icon_name:
1525
+ icon = getattr(mkicons, icon_name, None)
1526
+ if icon:
1527
+ icon_edit.GetBitmap
1528
+ preview.SetBitmap(icon.GetBitmap(resize=ps[0]))
1529
+
1530
+ combo_icon.Bind(wx.EVT_COMBOBOX, on_icon)
1531
+ on_icon(None)
1532
+
1533
+ button_sizer = dlg.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL)
1534
+ sizer.Add(line0, 0, wx.EXPAND, 0)
1535
+ sizer.Add(line1, 0, wx.EXPAND, 0)
1536
+ sizer.Add(line2, 0, wx.EXPAND, 0)
1537
+ sizer.Add(line3, 0, wx.EXPAND, 0)
1538
+ sizer.Add(line4, 0, wx.EXPAND, 0)
1539
+ sizer.Add(line5, 0, wx.EXPAND, 0)
1540
+ sizer.Add(line6, 0, wx.EXPAND, 0)
1541
+ sizer.Add(line7, 0, wx.EXPAND, 0)
1542
+ sizer.Add(button_sizer, 1, wx.EXPAND, 0)
1543
+
1544
+ xmax = 0
1545
+ ymax = 0
1546
+ labels = (label1, label2, label3, label4, label5, label6, label7)
1547
+ for ctrl in labels:
1548
+ ls = ctrl.GetSize()
1549
+ if ls[0] > xmax:
1550
+ xmax = ls[0]
1551
+ if ls[1] > ymax:
1552
+ ymax = ls[1]
1553
+ for ctrl in labels:
1554
+ ctrl.SetMinSize(wx.Size(xmax, ymax))
1555
+ for s in (line1, line2, line3, line4, line5, line6, line7):
1556
+ s.Layout()
1557
+
1558
+ dlg.SetSizer(sizer)
1559
+ dlg.Layout()
1560
+ dlg.CenterOnParent()
1561
+ res = dlg.ShowModal()
1562
+ if res == wx.ID_OK:
1563
+ entry["label"] = txt_label.GetValue()
1564
+ entry["tip"] = txt_tip.GetValue()
1565
+ entry["action_left"] = txt_action_left.GetValue()
1566
+ entry["action_right"] = txt_action_right.GetValue()
1567
+ ruler = ("always", "selected", "selected2")
1568
+ idx = combo_enable.GetSelection()
1569
+ idx = min(2, max(0, idx)) # between 0 and 2
1570
+ entry["enable"] = ruler[idx]
1571
+ idx = combo_visible.GetSelection()
1572
+ idx = min(2, max(0, idx)) # between 0 and 2
1573
+ entry["visible"] = ruler[idx]
1574
+ entry["icon"] = combo_icon.GetValue()
1575
+
1576
+ dlg.Destroy()
1577
+
1578
+ def on_button_edit_button(self, event):
1579
+ if self.current_button is None:
1580
+ return
1581
+ for entry in self._config_buttons:
1582
+ if entry["id"] == self.current_button:
1583
+ self.edit_a_button(entry)
1584
+ break
1585
+ self.fill_button(reposition=self.current_button)
1586
+
1587
+ def pane_hide(self):
1588
+ pass
1589
+
1590
+ def pane_show(self):
1591
+ pass