meerk40t 0.9.3001__py2.py3-none-any.whl → 0.9.7010__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.
- meerk40t/__init__.py +1 -1
- meerk40t/balormk/balor_params.py +167 -167
- meerk40t/balormk/clone_loader.py +457 -457
- meerk40t/balormk/controller.py +1566 -1512
- meerk40t/balormk/cylindermod.py +64 -0
- meerk40t/balormk/device.py +966 -1959
- meerk40t/balormk/driver.py +778 -591
- meerk40t/balormk/galvo_commands.py +1195 -0
- meerk40t/balormk/gui/balorconfig.py +237 -111
- meerk40t/balormk/gui/balorcontroller.py +191 -184
- meerk40t/balormk/gui/baloroperationproperties.py +116 -115
- meerk40t/balormk/gui/corscene.py +845 -0
- meerk40t/balormk/gui/gui.py +179 -147
- meerk40t/balormk/livelightjob.py +466 -382
- meerk40t/balormk/mock_connection.py +131 -109
- meerk40t/balormk/plugin.py +133 -135
- meerk40t/balormk/usb_connection.py +306 -301
- meerk40t/camera/__init__.py +1 -1
- meerk40t/camera/camera.py +514 -397
- meerk40t/camera/gui/camerapanel.py +1241 -1095
- meerk40t/camera/gui/gui.py +58 -58
- meerk40t/camera/plugin.py +441 -399
- meerk40t/ch341/__init__.py +27 -27
- meerk40t/ch341/ch341device.py +628 -628
- meerk40t/ch341/libusb.py +595 -589
- meerk40t/ch341/mock.py +171 -171
- meerk40t/ch341/windriver.py +157 -157
- meerk40t/constants.py +13 -0
- meerk40t/core/__init__.py +1 -1
- meerk40t/core/bindalias.py +550 -539
- meerk40t/core/core.py +47 -47
- meerk40t/core/cutcode/cubiccut.py +73 -73
- meerk40t/core/cutcode/cutcode.py +315 -312
- meerk40t/core/cutcode/cutgroup.py +141 -137
- meerk40t/core/cutcode/cutobject.py +192 -185
- meerk40t/core/cutcode/dwellcut.py +37 -37
- meerk40t/core/cutcode/gotocut.py +29 -29
- meerk40t/core/cutcode/homecut.py +29 -29
- meerk40t/core/cutcode/inputcut.py +34 -34
- meerk40t/core/cutcode/linecut.py +33 -33
- meerk40t/core/cutcode/outputcut.py +34 -34
- meerk40t/core/cutcode/plotcut.py +335 -335
- meerk40t/core/cutcode/quadcut.py +61 -61
- meerk40t/core/cutcode/rastercut.py +168 -148
- meerk40t/core/cutcode/waitcut.py +34 -34
- meerk40t/core/cutplan.py +1843 -1316
- meerk40t/core/drivers.py +330 -329
- meerk40t/core/elements/align.py +801 -669
- meerk40t/core/elements/branches.py +1844 -1507
- meerk40t/core/elements/clipboard.py +229 -219
- meerk40t/core/elements/element_treeops.py +4561 -2837
- meerk40t/core/elements/element_types.py +125 -105
- meerk40t/core/elements/elements.py +4329 -3617
- meerk40t/core/elements/files.py +117 -64
- meerk40t/core/elements/geometry.py +473 -224
- meerk40t/core/elements/grid.py +467 -316
- meerk40t/core/elements/materials.py +158 -94
- meerk40t/core/elements/notes.py +50 -38
- meerk40t/core/elements/offset_clpr.py +933 -912
- meerk40t/core/elements/offset_mk.py +963 -955
- meerk40t/core/elements/penbox.py +339 -267
- meerk40t/core/elements/placements.py +300 -83
- meerk40t/core/elements/render.py +785 -687
- meerk40t/core/elements/shapes.py +2618 -2092
- meerk40t/core/elements/trace.py +651 -563
- meerk40t/core/elements/tree_commands.py +415 -409
- meerk40t/core/elements/undo_redo.py +116 -58
- meerk40t/core/elements/wordlist.py +319 -200
- meerk40t/core/exceptions.py +9 -9
- meerk40t/core/laserjob.py +220 -220
- meerk40t/core/logging.py +63 -63
- meerk40t/core/node/blobnode.py +83 -86
- meerk40t/core/node/bootstrap.py +105 -103
- meerk40t/core/node/branch_elems.py +40 -31
- meerk40t/core/node/branch_ops.py +45 -38
- meerk40t/core/node/branch_regmark.py +48 -41
- meerk40t/core/node/cutnode.py +29 -32
- meerk40t/core/node/effect_hatch.py +375 -257
- meerk40t/core/node/effect_warp.py +398 -0
- meerk40t/core/node/effect_wobble.py +441 -309
- meerk40t/core/node/elem_ellipse.py +404 -309
- meerk40t/core/node/elem_image.py +1082 -801
- meerk40t/core/node/elem_line.py +358 -292
- meerk40t/core/node/elem_path.py +259 -201
- meerk40t/core/node/elem_point.py +129 -102
- meerk40t/core/node/elem_polyline.py +310 -246
- meerk40t/core/node/elem_rect.py +376 -286
- meerk40t/core/node/elem_text.py +445 -418
- meerk40t/core/node/filenode.py +59 -40
- meerk40t/core/node/groupnode.py +138 -74
- meerk40t/core/node/image_processed.py +777 -766
- meerk40t/core/node/image_raster.py +156 -113
- meerk40t/core/node/layernode.py +31 -31
- meerk40t/core/node/mixins.py +135 -107
- meerk40t/core/node/node.py +1427 -1304
- meerk40t/core/node/nutils.py +117 -114
- meerk40t/core/node/op_cut.py +462 -335
- meerk40t/core/node/op_dots.py +296 -251
- meerk40t/core/node/op_engrave.py +414 -311
- meerk40t/core/node/op_image.py +755 -369
- meerk40t/core/node/op_raster.py +787 -522
- meerk40t/core/node/place_current.py +37 -40
- meerk40t/core/node/place_point.py +329 -126
- meerk40t/core/node/refnode.py +58 -47
- meerk40t/core/node/rootnode.py +225 -219
- meerk40t/core/node/util_console.py +48 -48
- meerk40t/core/node/util_goto.py +84 -65
- meerk40t/core/node/util_home.py +61 -61
- meerk40t/core/node/util_input.py +102 -102
- meerk40t/core/node/util_output.py +102 -102
- meerk40t/core/node/util_wait.py +65 -65
- meerk40t/core/parameters.py +709 -707
- meerk40t/core/planner.py +875 -785
- meerk40t/core/plotplanner.py +656 -652
- meerk40t/core/space.py +120 -113
- meerk40t/core/spoolers.py +706 -705
- meerk40t/core/svg_io.py +1836 -1549
- meerk40t/core/treeop.py +534 -445
- meerk40t/core/undos.py +278 -124
- meerk40t/core/units.py +784 -680
- meerk40t/core/view.py +393 -322
- meerk40t/core/webhelp.py +62 -62
- meerk40t/core/wordlist.py +513 -504
- meerk40t/cylinder/cylinder.py +247 -0
- meerk40t/cylinder/gui/cylindersettings.py +41 -0
- meerk40t/cylinder/gui/gui.py +24 -0
- meerk40t/device/__init__.py +1 -1
- meerk40t/device/basedevice.py +322 -123
- meerk40t/device/devicechoices.py +50 -0
- meerk40t/device/dummydevice.py +163 -128
- meerk40t/device/gui/defaultactions.py +618 -602
- meerk40t/device/gui/effectspanel.py +114 -0
- meerk40t/device/gui/formatterpanel.py +253 -290
- meerk40t/device/gui/warningpanel.py +337 -260
- meerk40t/device/mixins.py +13 -13
- meerk40t/dxf/__init__.py +1 -1
- meerk40t/dxf/dxf_io.py +766 -554
- meerk40t/dxf/plugin.py +47 -35
- meerk40t/external_plugins.py +79 -79
- meerk40t/external_plugins_build.py +28 -28
- meerk40t/extra/cag.py +112 -116
- meerk40t/extra/coolant.py +403 -0
- meerk40t/extra/encode_detect.py +198 -0
- meerk40t/extra/ezd.py +1165 -1165
- meerk40t/extra/hershey.py +835 -340
- meerk40t/extra/imageactions.py +322 -316
- meerk40t/extra/inkscape.py +630 -622
- meerk40t/extra/lbrn.py +424 -424
- meerk40t/extra/outerworld.py +284 -0
- meerk40t/extra/param_functions.py +1542 -1556
- meerk40t/extra/potrace.py +257 -253
- meerk40t/extra/serial_exchange.py +118 -0
- meerk40t/extra/updater.py +602 -453
- meerk40t/extra/vectrace.py +147 -146
- meerk40t/extra/winsleep.py +83 -83
- meerk40t/extra/xcs_reader.py +597 -0
- meerk40t/fill/fills.py +781 -335
- meerk40t/fill/patternfill.py +1061 -1061
- meerk40t/fill/patterns.py +614 -567
- meerk40t/grbl/control.py +87 -87
- meerk40t/grbl/controller.py +990 -903
- meerk40t/grbl/device.py +1081 -768
- meerk40t/grbl/driver.py +989 -771
- meerk40t/grbl/emulator.py +532 -497
- meerk40t/grbl/gcodejob.py +783 -767
- meerk40t/grbl/gui/grblconfiguration.py +373 -298
- meerk40t/grbl/gui/grblcontroller.py +485 -271
- meerk40t/grbl/gui/grblhardwareconfig.py +269 -153
- meerk40t/grbl/gui/grbloperationconfig.py +105 -0
- meerk40t/grbl/gui/gui.py +147 -116
- meerk40t/grbl/interpreter.py +44 -44
- meerk40t/grbl/loader.py +22 -22
- meerk40t/grbl/mock_connection.py +56 -56
- meerk40t/grbl/plugin.py +294 -264
- meerk40t/grbl/serial_connection.py +93 -88
- meerk40t/grbl/tcp_connection.py +81 -79
- meerk40t/grbl/ws_connection.py +112 -0
- meerk40t/gui/__init__.py +1 -1
- meerk40t/gui/about.py +2042 -296
- meerk40t/gui/alignment.py +1644 -1608
- meerk40t/gui/autoexec.py +199 -0
- meerk40t/gui/basicops.py +791 -670
- meerk40t/gui/bufferview.py +77 -71
- meerk40t/gui/busy.py +170 -133
- meerk40t/gui/choicepropertypanel.py +1673 -1469
- meerk40t/gui/consolepanel.py +706 -542
- meerk40t/gui/devicepanel.py +687 -581
- meerk40t/gui/dialogoptions.py +110 -107
- meerk40t/gui/executejob.py +316 -306
- meerk40t/gui/fonts.py +90 -90
- meerk40t/gui/functionwrapper.py +252 -0
- meerk40t/gui/gui_mixins.py +729 -0
- meerk40t/gui/guicolors.py +205 -182
- meerk40t/gui/help_assets/help_assets.py +218 -201
- meerk40t/gui/helper.py +154 -0
- meerk40t/gui/hersheymanager.py +1430 -846
- meerk40t/gui/icons.py +3422 -2747
- meerk40t/gui/imagesplitter.py +555 -508
- meerk40t/gui/keymap.py +354 -344
- meerk40t/gui/laserpanel.py +892 -806
- meerk40t/gui/laserrender.py +1470 -1232
- meerk40t/gui/lasertoolpanel.py +805 -793
- meerk40t/gui/magnetoptions.py +436 -0
- meerk40t/gui/materialmanager.py +2917 -0
- meerk40t/gui/materialtest.py +1722 -1694
- meerk40t/gui/mkdebug.py +646 -359
- meerk40t/gui/mwindow.py +163 -140
- meerk40t/gui/navigationpanels.py +2605 -2467
- meerk40t/gui/notes.py +143 -142
- meerk40t/gui/opassignment.py +414 -410
- meerk40t/gui/operation_info.py +310 -299
- meerk40t/gui/plugin.py +494 -328
- meerk40t/gui/position.py +714 -669
- meerk40t/gui/preferences.py +901 -650
- meerk40t/gui/propertypanels/attributes.py +1461 -1131
- meerk40t/gui/propertypanels/blobproperty.py +117 -114
- meerk40t/gui/propertypanels/consoleproperty.py +83 -80
- meerk40t/gui/propertypanels/gotoproperty.py +77 -0
- meerk40t/gui/propertypanels/groupproperties.py +223 -217
- meerk40t/gui/propertypanels/hatchproperty.py +489 -469
- meerk40t/gui/propertypanels/imageproperty.py +2244 -1384
- meerk40t/gui/propertypanels/inputproperty.py +59 -58
- meerk40t/gui/propertypanels/opbranchproperties.py +82 -80
- meerk40t/gui/propertypanels/operationpropertymain.py +1890 -1638
- meerk40t/gui/propertypanels/outputproperty.py +59 -58
- meerk40t/gui/propertypanels/pathproperty.py +389 -380
- meerk40t/gui/propertypanels/placementproperty.py +1214 -383
- meerk40t/gui/propertypanels/pointproperty.py +140 -136
- meerk40t/gui/propertypanels/propertywindow.py +313 -181
- meerk40t/gui/propertypanels/rasterwizardpanels.py +996 -912
- meerk40t/gui/propertypanels/regbranchproperties.py +76 -0
- meerk40t/gui/propertypanels/textproperty.py +770 -755
- meerk40t/gui/propertypanels/waitproperty.py +56 -55
- meerk40t/gui/propertypanels/warpproperty.py +121 -0
- meerk40t/gui/propertypanels/wobbleproperty.py +255 -204
- meerk40t/gui/ribbon.py +2468 -2210
- meerk40t/gui/scene/scene.py +1100 -1051
- meerk40t/gui/scene/sceneconst.py +22 -22
- meerk40t/gui/scene/scenepanel.py +439 -349
- meerk40t/gui/scene/scenespacewidget.py +365 -365
- meerk40t/gui/scene/widget.py +518 -505
- meerk40t/gui/scenewidgets/affinemover.py +215 -215
- meerk40t/gui/scenewidgets/attractionwidget.py +315 -309
- meerk40t/gui/scenewidgets/bedwidget.py +120 -97
- meerk40t/gui/scenewidgets/elementswidget.py +137 -107
- meerk40t/gui/scenewidgets/gridwidget.py +785 -745
- meerk40t/gui/scenewidgets/guidewidget.py +765 -765
- meerk40t/gui/scenewidgets/laserpathwidget.py +66 -66
- meerk40t/gui/scenewidgets/machineoriginwidget.py +86 -86
- meerk40t/gui/scenewidgets/nodeselector.py +28 -28
- meerk40t/gui/scenewidgets/rectselectwidget.py +589 -346
- meerk40t/gui/scenewidgets/relocatewidget.py +33 -33
- meerk40t/gui/scenewidgets/reticlewidget.py +83 -83
- meerk40t/gui/scenewidgets/selectionwidget.py +2952 -2756
- meerk40t/gui/simpleui.py +357 -333
- meerk40t/gui/simulation.py +2431 -2094
- meerk40t/gui/snapoptions.py +208 -203
- meerk40t/gui/spoolerpanel.py +1227 -1180
- meerk40t/gui/statusbarwidgets/defaultoperations.py +480 -353
- meerk40t/gui/statusbarwidgets/infowidget.py +520 -483
- meerk40t/gui/statusbarwidgets/opassignwidget.py +356 -355
- meerk40t/gui/statusbarwidgets/selectionwidget.py +172 -171
- meerk40t/gui/statusbarwidgets/shapepropwidget.py +754 -236
- meerk40t/gui/statusbarwidgets/statusbar.py +272 -260
- meerk40t/gui/statusbarwidgets/statusbarwidget.py +268 -270
- meerk40t/gui/statusbarwidgets/strokewidget.py +267 -251
- meerk40t/gui/themes.py +200 -78
- meerk40t/gui/tips.py +591 -0
- meerk40t/gui/toolwidgets/circlebrush.py +35 -35
- meerk40t/gui/toolwidgets/toolcircle.py +248 -242
- meerk40t/gui/toolwidgets/toolcontainer.py +82 -77
- meerk40t/gui/toolwidgets/tooldraw.py +97 -90
- meerk40t/gui/toolwidgets/toolellipse.py +219 -212
- meerk40t/gui/toolwidgets/toolimagecut.py +25 -132
- meerk40t/gui/toolwidgets/toolline.py +39 -144
- meerk40t/gui/toolwidgets/toollinetext.py +79 -236
- meerk40t/gui/toolwidgets/toollinetext_inline.py +296 -0
- meerk40t/gui/toolwidgets/toolmeasure.py +160 -216
- meerk40t/gui/toolwidgets/toolnodeedit.py +2088 -2074
- meerk40t/gui/toolwidgets/toolnodemove.py +92 -94
- meerk40t/gui/toolwidgets/toolparameter.py +754 -668
- meerk40t/gui/toolwidgets/toolplacement.py +108 -108
- meerk40t/gui/toolwidgets/toolpoint.py +68 -59
- meerk40t/gui/toolwidgets/toolpointlistbuilder.py +294 -0
- meerk40t/gui/toolwidgets/toolpointmove.py +183 -0
- meerk40t/gui/toolwidgets/toolpolygon.py +288 -403
- meerk40t/gui/toolwidgets/toolpolyline.py +38 -196
- meerk40t/gui/toolwidgets/toolrect.py +211 -207
- meerk40t/gui/toolwidgets/toolrelocate.py +72 -72
- meerk40t/gui/toolwidgets/toolribbon.py +598 -113
- meerk40t/gui/toolwidgets/tooltabedit.py +546 -0
- meerk40t/gui/toolwidgets/tooltext.py +98 -89
- meerk40t/gui/toolwidgets/toolvector.py +213 -204
- meerk40t/gui/toolwidgets/toolwidget.py +39 -39
- meerk40t/gui/usbconnect.py +98 -91
- meerk40t/gui/utilitywidgets/buttonwidget.py +18 -18
- meerk40t/gui/utilitywidgets/checkboxwidget.py +90 -90
- meerk40t/gui/utilitywidgets/controlwidget.py +14 -14
- meerk40t/gui/utilitywidgets/cyclocycloidwidget.py +343 -340
- meerk40t/gui/utilitywidgets/debugwidgets.py +148 -0
- meerk40t/gui/utilitywidgets/handlewidget.py +27 -27
- meerk40t/gui/utilitywidgets/harmonograph.py +450 -447
- meerk40t/gui/utilitywidgets/openclosewidget.py +40 -40
- meerk40t/gui/utilitywidgets/rotationwidget.py +54 -54
- meerk40t/gui/utilitywidgets/scalewidget.py +75 -75
- meerk40t/gui/utilitywidgets/seekbarwidget.py +183 -183
- meerk40t/gui/utilitywidgets/togglewidget.py +142 -142
- meerk40t/gui/utilitywidgets/toolbarwidget.py +8 -8
- meerk40t/gui/wordlisteditor.py +985 -931
- meerk40t/gui/wxmeerk40t.py +1444 -1169
- meerk40t/gui/wxmmain.py +5578 -4112
- meerk40t/gui/wxmribbon.py +1591 -1076
- meerk40t/gui/wxmscene.py +1635 -1453
- meerk40t/gui/wxmtree.py +2410 -2089
- meerk40t/gui/wxutils.py +1769 -1099
- meerk40t/gui/zmatrix.py +102 -102
- meerk40t/image/__init__.py +1 -1
- meerk40t/image/dither.py +429 -0
- meerk40t/image/imagetools.py +2778 -2269
- meerk40t/internal_plugins.py +150 -130
- meerk40t/kernel/__init__.py +63 -12
- meerk40t/kernel/channel.py +259 -212
- meerk40t/kernel/context.py +538 -538
- meerk40t/kernel/exceptions.py +41 -41
- meerk40t/kernel/functions.py +463 -414
- meerk40t/kernel/jobs.py +100 -100
- meerk40t/kernel/kernel.py +3809 -3571
- meerk40t/kernel/lifecycles.py +71 -71
- meerk40t/kernel/module.py +49 -49
- meerk40t/kernel/service.py +147 -147
- meerk40t/kernel/settings.py +383 -343
- meerk40t/lihuiyu/controller.py +883 -876
- meerk40t/lihuiyu/device.py +1181 -1069
- meerk40t/lihuiyu/driver.py +1466 -1372
- meerk40t/lihuiyu/gui/gui.py +127 -106
- meerk40t/lihuiyu/gui/lhyaccelgui.py +377 -363
- meerk40t/lihuiyu/gui/lhycontrollergui.py +741 -651
- meerk40t/lihuiyu/gui/lhydrivergui.py +470 -446
- meerk40t/lihuiyu/gui/lhyoperationproperties.py +238 -237
- meerk40t/lihuiyu/gui/tcpcontroller.py +226 -190
- meerk40t/lihuiyu/interpreter.py +53 -53
- meerk40t/lihuiyu/laserspeed.py +450 -450
- meerk40t/lihuiyu/loader.py +90 -90
- meerk40t/lihuiyu/parser.py +404 -404
- meerk40t/lihuiyu/plugin.py +101 -102
- meerk40t/lihuiyu/tcp_connection.py +111 -109
- meerk40t/main.py +231 -165
- meerk40t/moshi/builder.py +788 -781
- meerk40t/moshi/controller.py +505 -499
- meerk40t/moshi/device.py +495 -442
- meerk40t/moshi/driver.py +862 -696
- meerk40t/moshi/gui/gui.py +78 -76
- meerk40t/moshi/gui/moshicontrollergui.py +538 -522
- meerk40t/moshi/gui/moshidrivergui.py +87 -75
- meerk40t/moshi/plugin.py +43 -43
- meerk40t/network/console_server.py +102 -57
- meerk40t/network/kernelserver.py +10 -9
- meerk40t/network/tcp_server.py +142 -140
- meerk40t/network/udp_server.py +103 -77
- meerk40t/network/web_server.py +390 -0
- meerk40t/newly/controller.py +1158 -1144
- meerk40t/newly/device.py +874 -732
- meerk40t/newly/driver.py +540 -412
- meerk40t/newly/gui/gui.py +219 -188
- meerk40t/newly/gui/newlyconfig.py +116 -101
- meerk40t/newly/gui/newlycontroller.py +193 -186
- meerk40t/newly/gui/operationproperties.py +51 -51
- meerk40t/newly/mock_connection.py +82 -82
- meerk40t/newly/newly_params.py +56 -56
- meerk40t/newly/plugin.py +1214 -1246
- meerk40t/newly/usb_connection.py +322 -322
- meerk40t/rotary/gui/gui.py +52 -46
- meerk40t/rotary/gui/rotarysettings.py +240 -232
- meerk40t/rotary/rotary.py +202 -98
- meerk40t/ruida/control.py +291 -91
- meerk40t/ruida/controller.py +138 -1088
- meerk40t/ruida/device.py +672 -231
- meerk40t/ruida/driver.py +534 -472
- meerk40t/ruida/emulator.py +1494 -1491
- meerk40t/ruida/exceptions.py +4 -4
- meerk40t/ruida/gui/gui.py +71 -76
- meerk40t/ruida/gui/ruidaconfig.py +239 -72
- meerk40t/ruida/gui/ruidacontroller.py +187 -184
- meerk40t/ruida/gui/ruidaoperationproperties.py +48 -47
- meerk40t/ruida/loader.py +54 -52
- meerk40t/ruida/mock_connection.py +57 -109
- meerk40t/ruida/plugin.py +124 -87
- meerk40t/ruida/rdjob.py +2084 -945
- meerk40t/ruida/serial_connection.py +116 -0
- meerk40t/ruida/tcp_connection.py +146 -0
- meerk40t/ruida/udp_connection.py +73 -0
- meerk40t/svgelements.py +9671 -9669
- meerk40t/tools/driver_to_path.py +584 -579
- meerk40t/tools/geomstr.py +5583 -4680
- meerk40t/tools/jhfparser.py +357 -292
- meerk40t/tools/kerftest.py +904 -890
- meerk40t/tools/livinghinges.py +1168 -1033
- meerk40t/tools/pathtools.py +987 -949
- meerk40t/tools/pmatrix.py +234 -0
- meerk40t/tools/pointfinder.py +942 -942
- meerk40t/tools/polybool.py +940 -940
- meerk40t/tools/rasterplotter.py +1660 -547
- meerk40t/tools/shxparser.py +989 -901
- meerk40t/tools/ttfparser.py +726 -446
- meerk40t/tools/zinglplotter.py +595 -593
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/LICENSE +21 -21
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/METADATA +150 -139
- meerk40t-0.9.7010.dist-info/RECORD +445 -0
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/WHEEL +1 -1
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/top_level.txt +0 -1
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/zip-safe +1 -1
- meerk40t/balormk/elementlightjob.py +0 -159
- meerk40t-0.9.3001.dist-info/RECORD +0 -437
- test/bootstrap.py +0 -63
- test/test_cli.py +0 -12
- test/test_core_cutcode.py +0 -418
- test/test_core_elements.py +0 -144
- test/test_core_plotplanner.py +0 -397
- test/test_core_viewports.py +0 -312
- test/test_drivers_grbl.py +0 -108
- test/test_drivers_lihuiyu.py +0 -443
- test/test_drivers_newly.py +0 -113
- test/test_element_degenerate_points.py +0 -43
- test/test_elements_classify.py +0 -97
- test/test_elements_penbox.py +0 -22
- test/test_file_svg.py +0 -176
- test/test_fill.py +0 -155
- test/test_geomstr.py +0 -1523
- test/test_geomstr_nodes.py +0 -18
- test/test_imagetools_actualize.py +0 -306
- test/test_imagetools_wizard.py +0 -258
- test/test_kernel.py +0 -200
- test/test_laser_speeds.py +0 -3303
- test/test_length.py +0 -57
- test/test_lifecycle.py +0 -66
- test/test_operations.py +0 -251
- test/test_operations_hatch.py +0 -57
- test/test_ruida.py +0 -19
- test/test_spooler.py +0 -22
- test/test_tools_rasterplotter.py +0 -29
- test/test_wobble.py +0 -133
- test/test_zingl.py +0 -124
- {test → meerk40t/cylinder}/__init__.py +0 -0
- /meerk40t/{core/element_commands.py → cylinder/gui/__init__.py} +0 -0
- {meerk40t-0.9.3001.dist-info → meerk40t-0.9.7010.dist-info}/entry_points.txt +0 -0
meerk40t/gui/wxutils.py
CHANGED
@@ -1,1099 +1,1769 @@
|
|
1
|
-
"""
|
2
|
-
Mixin functions for wxMeerk40t
|
3
|
-
"""
|
4
|
-
import platform
|
5
|
-
from typing import List
|
6
|
-
|
7
|
-
import wx
|
8
|
-
import wx.lib.mixins.listctrl as listmix
|
9
|
-
from wx.lib.scrolledpanel import ScrolledPanel as SP
|
10
|
-
|
11
|
-
from meerk40t.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
choice
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
)
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
if
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
return
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
if
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
self.
|
585
|
-
|
586
|
-
|
587
|
-
self.
|
588
|
-
self.
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
self.
|
593
|
-
|
594
|
-
|
595
|
-
self.
|
596
|
-
self.
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
#
|
640
|
-
self.
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
)
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
super().
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
self.
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
self
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
self.
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
self.
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
event.
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
#
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
wx.
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
wx.
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
def
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1
|
+
"""
|
2
|
+
Mixin functions for wxMeerk40t
|
3
|
+
"""
|
4
|
+
import platform
|
5
|
+
from typing import List
|
6
|
+
|
7
|
+
import wx
|
8
|
+
import wx.lib.mixins.listctrl as listmix
|
9
|
+
from wx.lib.scrolledpanel import ScrolledPanel as SP
|
10
|
+
|
11
|
+
from meerk40t.svgelements import Matrix
|
12
|
+
from meerk40t.core.units import ACCEPTED_ANGLE_UNITS, ACCEPTED_UNITS, Angle, Length
|
13
|
+
|
14
|
+
_ = wx.GetTranslation
|
15
|
+
|
16
|
+
|
17
|
+
##############
|
18
|
+
# DYNAMIC CHOICE
|
19
|
+
# NODE MENU
|
20
|
+
##############
|
21
|
+
|
22
|
+
|
23
|
+
def get_matrix_scale(matrix):
|
24
|
+
# We usually use the value_scale_x to establish a pixel size
|
25
|
+
# by counteracting the scene matrix, linewidth = 1 / matrix.value_scale_x()
|
26
|
+
# For a rotated scene this crashes, so we need to take
|
27
|
+
# that into consideration, so let's look at the
|
28
|
+
# distance from (1, 0) to (0, 0) and call this our scale
|
29
|
+
from math import sqrt
|
30
|
+
|
31
|
+
x0, y0 = matrix.point_in_matrix_space((0, 0))
|
32
|
+
x1, y1 = matrix.point_in_matrix_space((1, 0))
|
33
|
+
res = sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)
|
34
|
+
if res < 1e-8:
|
35
|
+
res = 1
|
36
|
+
return res
|
37
|
+
|
38
|
+
def get_matrix_full_scale(matrix):
|
39
|
+
# We usually use the value_scale_x to establish a pixel size
|
40
|
+
# by counteracting the scene matrix, linewidth = 1 / matrix.value_scale_x()
|
41
|
+
# For a rotated scene this crashes, so we need to take
|
42
|
+
# that into consideration, so let's look at the
|
43
|
+
# distance from (1, 0) to (0, 0) and call this our scale
|
44
|
+
from math import sqrt
|
45
|
+
|
46
|
+
x0, y0 = matrix.point_in_matrix_space((0, 0))
|
47
|
+
x1, y1 = matrix.point_in_matrix_space((1, 0))
|
48
|
+
resx = sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)
|
49
|
+
if resx < 1e-8:
|
50
|
+
resx = 1
|
51
|
+
x1, y1 = matrix.point_in_matrix_space((0, 1))
|
52
|
+
resy = sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)
|
53
|
+
if resy < 1e-8:
|
54
|
+
resy = 1
|
55
|
+
return resx, resy
|
56
|
+
|
57
|
+
def get_gc_scale(gc):
|
58
|
+
gcmat = gc.GetTransform()
|
59
|
+
mat_param = gcmat.Get()
|
60
|
+
testmatrix = Matrix(
|
61
|
+
mat_param[0],
|
62
|
+
mat_param[1],
|
63
|
+
mat_param[2],
|
64
|
+
mat_param[3],
|
65
|
+
mat_param[4],
|
66
|
+
mat_param[5],
|
67
|
+
)
|
68
|
+
return get_matrix_scale(testmatrix)
|
69
|
+
|
70
|
+
def get_gc_full_scale(gc):
|
71
|
+
gcmat = gc.GetTransform()
|
72
|
+
mat_param = gcmat.Get()
|
73
|
+
testmatrix = Matrix(
|
74
|
+
mat_param[0],
|
75
|
+
mat_param[1],
|
76
|
+
mat_param[2],
|
77
|
+
mat_param[3],
|
78
|
+
mat_param[4],
|
79
|
+
mat_param[5],
|
80
|
+
)
|
81
|
+
return get_matrix_full_scale(testmatrix)
|
82
|
+
|
83
|
+
def create_menu_for_choices(gui, choices: List[dict]) -> wx.Menu:
|
84
|
+
"""
|
85
|
+
Creates a menu for a given choices table.
|
86
|
+
|
87
|
+
Processes submenus, references, radio_state as needed.
|
88
|
+
"""
|
89
|
+
menu = wx.Menu()
|
90
|
+
submenus = {}
|
91
|
+
choice = dict()
|
92
|
+
|
93
|
+
def get(key, default=None):
|
94
|
+
try:
|
95
|
+
return choice[key]
|
96
|
+
except KeyError:
|
97
|
+
return default
|
98
|
+
|
99
|
+
def execute(choice):
|
100
|
+
func = choice["action"]
|
101
|
+
func_kwargs = choice["kwargs"]
|
102
|
+
func_args = choice["kwargs"]
|
103
|
+
|
104
|
+
def specific(event=None):
|
105
|
+
func(*func_args, **func_kwargs)
|
106
|
+
|
107
|
+
return specific
|
108
|
+
|
109
|
+
def set_bool(choice, value):
|
110
|
+
obj = choice["object"]
|
111
|
+
param = choice["attr"]
|
112
|
+
|
113
|
+
def check(event=None):
|
114
|
+
setattr(obj, param, value)
|
115
|
+
|
116
|
+
return check
|
117
|
+
|
118
|
+
for c in choices:
|
119
|
+
choice = c
|
120
|
+
submenu_name = get("submenu")
|
121
|
+
submenu = None
|
122
|
+
if submenu_name and submenu_name in submenus:
|
123
|
+
submenu = submenus[submenu_name]
|
124
|
+
else:
|
125
|
+
if get("separate_before", default=False):
|
126
|
+
menu.AppendSeparator()
|
127
|
+
c["separate_before"] = False
|
128
|
+
if submenu_name:
|
129
|
+
submenu = wx.Menu()
|
130
|
+
menu.AppendSubMenu(submenu, submenu_name)
|
131
|
+
submenus[submenu_name] = submenu
|
132
|
+
|
133
|
+
menu_context = submenu if submenu is not None else menu
|
134
|
+
if get("separate_before", default=False):
|
135
|
+
menu.AppendSeparator()
|
136
|
+
c["separate_before"] = False
|
137
|
+
t = get("type")
|
138
|
+
if t == bool:
|
139
|
+
item = menu_context.Append(
|
140
|
+
wx.ID_ANY, get("label"), get("tip"), wx.ITEM_CHECK
|
141
|
+
)
|
142
|
+
obj = get("object")
|
143
|
+
param = get("attr")
|
144
|
+
check = bool(getattr(obj, param, False))
|
145
|
+
item.Check(check)
|
146
|
+
gui.Bind(
|
147
|
+
wx.EVT_MENU,
|
148
|
+
set_bool(choice, not check),
|
149
|
+
item,
|
150
|
+
)
|
151
|
+
elif t == "action":
|
152
|
+
item = menu_context.Append(
|
153
|
+
wx.ID_ANY, get("label"), get("tip"), wx.ITEM_NORMAL
|
154
|
+
)
|
155
|
+
gui.Bind(
|
156
|
+
wx.EVT_MENU,
|
157
|
+
execute(choice),
|
158
|
+
item,
|
159
|
+
)
|
160
|
+
if not submenu and get("separate_after", default=False):
|
161
|
+
menu.AppendSeparator()
|
162
|
+
return menu
|
163
|
+
|
164
|
+
|
165
|
+
def create_choices_for_node(node, elements) -> List[dict]:
|
166
|
+
"""
|
167
|
+
Converts a node tree operation menu to a choices dictionary to display the menu items in a choice panel.
|
168
|
+
|
169
|
+
@param node:
|
170
|
+
@param elements:
|
171
|
+
@return:
|
172
|
+
"""
|
173
|
+
choices = []
|
174
|
+
from meerk40t.core.treeop import get_tree_operation_for_node
|
175
|
+
|
176
|
+
tree_operations_for_node = get_tree_operation_for_node(elements)
|
177
|
+
for func in tree_operations_for_node(node):
|
178
|
+
choice = {}
|
179
|
+
choices.append(choice)
|
180
|
+
choice["action"] = func
|
181
|
+
choice["type"] = "action"
|
182
|
+
choice["submenu"] = func.submenu
|
183
|
+
choice["kwargs"] = dict()
|
184
|
+
choice["args"] = tuple()
|
185
|
+
choice["separate_before"] = func.separate_before
|
186
|
+
choice["separate_after"] = func.separate_after
|
187
|
+
choice["label"] = func.name
|
188
|
+
choice["real_name"] = func.real_name
|
189
|
+
choice["tip"] = func.help
|
190
|
+
choice["radio"] = func.radio
|
191
|
+
choice["reference"] = func.reference
|
192
|
+
choice["user_prompt"] = func.user_prompt
|
193
|
+
choice["calcs"] = func.calcs
|
194
|
+
choice["values"] = func.values
|
195
|
+
return choices
|
196
|
+
|
197
|
+
|
198
|
+
def create_menu_for_node_TEST(gui, node, elements) -> wx.Menu:
|
199
|
+
"""
|
200
|
+
Test code towards unifying choices and tree nodes into choices that parse to menus.
|
201
|
+
|
202
|
+
This is unused experimental code. Testing the potential interrelationships between choices for the choice panels
|
203
|
+
and dynamic node menus.
|
204
|
+
|
205
|
+
@param gui:
|
206
|
+
@param node:
|
207
|
+
@param elements:
|
208
|
+
@return:
|
209
|
+
"""
|
210
|
+
choices = create_choices_for_node(node, elements)
|
211
|
+
return create_menu_for_choices(gui, choices)
|
212
|
+
|
213
|
+
|
214
|
+
##############
|
215
|
+
# DYNAMIC NODE MENU
|
216
|
+
##############
|
217
|
+
|
218
|
+
|
219
|
+
def create_menu_for_node(gui, node, elements, optional_2nd_node=None) -> wx.Menu:
|
220
|
+
"""
|
221
|
+
Create menu for a particular node. Does not invoke the menu.
|
222
|
+
|
223
|
+
Processes submenus, references, radio_state as needed.
|
224
|
+
"""
|
225
|
+
menu = wx.Menu()
|
226
|
+
submenus = {}
|
227
|
+
radio_check_not_needed = []
|
228
|
+
from meerk40t.core.treeop import get_tree_operation_for_node
|
229
|
+
|
230
|
+
tree_operations_for_node = get_tree_operation_for_node(elements)
|
231
|
+
|
232
|
+
def menu_functions(f, node):
|
233
|
+
func_dict = dict(f.func_dict)
|
234
|
+
|
235
|
+
def specific(event=None):
|
236
|
+
prompts = f.user_prompt
|
237
|
+
if len(prompts) > 0:
|
238
|
+
with wx.Dialog(
|
239
|
+
None,
|
240
|
+
wx.ID_ANY,
|
241
|
+
_("Parameters"),
|
242
|
+
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
|
243
|
+
) as dlg:
|
244
|
+
gui.context.themes.set_window_colors(dlg)
|
245
|
+
|
246
|
+
sizer = wx.BoxSizer(wx.VERTICAL)
|
247
|
+
fields = []
|
248
|
+
for prompt in prompts:
|
249
|
+
label = wxStaticText(dlg, wx.ID_ANY, prompt["prompt"])
|
250
|
+
sizer.Add(label, 0, wx.EXPAND, 0)
|
251
|
+
dtype = prompt["type"]
|
252
|
+
if dtype == bool:
|
253
|
+
control = wxCheckBox(dlg, wx.ID_ANY)
|
254
|
+
else:
|
255
|
+
control = TextCtrl(dlg, wx.ID_ANY)
|
256
|
+
control.SetMaxSize(dip_size(dlg, 75, -1))
|
257
|
+
fields.append(control)
|
258
|
+
sizer.Add(control, 0, wx.EXPAND, 0)
|
259
|
+
sizer.AddSpacer(23)
|
260
|
+
b_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
261
|
+
button_OK = wxButton(dlg, wx.ID_OK, _("OK"))
|
262
|
+
button_CANCEL = wxButton(dlg, wx.ID_CANCEL, _("Cancel"))
|
263
|
+
# dlg.SetAffirmativeId(button_OK.GetId())
|
264
|
+
# dlg.SetEscapeId(button_CANCEL.GetId())
|
265
|
+
b_sizer.Add(button_OK, 0, wx.EXPAND, 0)
|
266
|
+
b_sizer.Add(button_CANCEL, 0, wx.EXPAND, 0)
|
267
|
+
sizer.Add(b_sizer, 0, wx.EXPAND, 0)
|
268
|
+
sizer.Fit(dlg)
|
269
|
+
dlg.SetSizer(sizer)
|
270
|
+
dlg.Layout()
|
271
|
+
|
272
|
+
response = dlg.ShowModal()
|
273
|
+
if response != wx.ID_OK:
|
274
|
+
return
|
275
|
+
for prompt, control in zip(prompts, fields):
|
276
|
+
dtype = prompt["type"]
|
277
|
+
try:
|
278
|
+
value = dtype(control.GetValue())
|
279
|
+
except ValueError:
|
280
|
+
return
|
281
|
+
func_dict[prompt["attr"]] = value
|
282
|
+
# for prompt in prompts:
|
283
|
+
# response = elements.kernel.prompt(prompt["type"], prompt["prompt"])
|
284
|
+
# if response is None:
|
285
|
+
# return
|
286
|
+
# func_dict[prompt["attr"]] = response
|
287
|
+
f(node, **func_dict)
|
288
|
+
|
289
|
+
return specific
|
290
|
+
|
291
|
+
# Check specifically for the optional first (use case: reference nodes)
|
292
|
+
if optional_2nd_node is not None:
|
293
|
+
mc1 = menu.MenuItemCount
|
294
|
+
last_was_separator = False
|
295
|
+
|
296
|
+
for func in tree_operations_for_node(optional_2nd_node):
|
297
|
+
submenu_name = func.submenu
|
298
|
+
submenu = None
|
299
|
+
if submenu_name and submenu_name in submenus:
|
300
|
+
submenu = submenus[submenu_name]
|
301
|
+
else:
|
302
|
+
if submenu_name:
|
303
|
+
last_was_separator = False
|
304
|
+
subs = submenu_name.split("|")
|
305
|
+
common = ""
|
306
|
+
parent_menu = menu
|
307
|
+
for sname in subs:
|
308
|
+
if sname == "":
|
309
|
+
continue
|
310
|
+
if common:
|
311
|
+
common += "|"
|
312
|
+
common += sname
|
313
|
+
if common in submenus:
|
314
|
+
submenu = submenus[common]
|
315
|
+
parent_menu = submenu
|
316
|
+
else:
|
317
|
+
submenu = wx.Menu()
|
318
|
+
if func.separate_before:
|
319
|
+
last_was_separator = True
|
320
|
+
parent_menu.AppendSeparator()
|
321
|
+
func.separate_before = False
|
322
|
+
|
323
|
+
parent_menu.AppendSubMenu(submenu, sname, func.help)
|
324
|
+
submenus[common] = submenu
|
325
|
+
parent_menu = submenu
|
326
|
+
|
327
|
+
menu_context = submenu if submenu is not None else menu
|
328
|
+
if func.separate_before:
|
329
|
+
menu_context.AppendSeparator()
|
330
|
+
if func.reference is not None:
|
331
|
+
menu_context.AppendSubMenu(
|
332
|
+
create_menu_for_node(
|
333
|
+
gui,
|
334
|
+
func.reference(optional_2nd_node),
|
335
|
+
elements,
|
336
|
+
optional_2nd_node,
|
337
|
+
),
|
338
|
+
func.real_name,
|
339
|
+
)
|
340
|
+
continue
|
341
|
+
if func.radio_state is not None:
|
342
|
+
last_was_separator = False
|
343
|
+
item = menu_context.Append(
|
344
|
+
wx.ID_ANY, func.real_name, func.help, wx.ITEM_RADIO
|
345
|
+
)
|
346
|
+
check = func.radio_state
|
347
|
+
item.Check(check)
|
348
|
+
if check and menu_context not in radio_check_not_needed:
|
349
|
+
radio_check_not_needed.append(menu_context)
|
350
|
+
if func.enabled:
|
351
|
+
gui.Bind(
|
352
|
+
wx.EVT_MENU,
|
353
|
+
menu_functions(func, optional_2nd_node),
|
354
|
+
item,
|
355
|
+
)
|
356
|
+
else:
|
357
|
+
item.Enable(False)
|
358
|
+
else:
|
359
|
+
last_was_separator = False
|
360
|
+
if hasattr(func, "check_state") and func.check_state is not None:
|
361
|
+
check = func.check_state
|
362
|
+
kind = wx.ITEM_CHECK
|
363
|
+
else:
|
364
|
+
kind = wx.ITEM_NORMAL
|
365
|
+
check = None
|
366
|
+
item = menu_context.Append(wx.ID_ANY, func.real_name, func.help, kind)
|
367
|
+
if check is not None:
|
368
|
+
item.Check(check)
|
369
|
+
if func.enabled:
|
370
|
+
gui.Bind(
|
371
|
+
wx.EVT_MENU,
|
372
|
+
menu_functions(func, node),
|
373
|
+
item,
|
374
|
+
)
|
375
|
+
else:
|
376
|
+
item.Enable(False)
|
377
|
+
if menu_context not in radio_check_not_needed:
|
378
|
+
radio_check_not_needed.append(menu_context)
|
379
|
+
if not submenu and func.separate_after:
|
380
|
+
last_was_separator = True
|
381
|
+
menu.AppendSeparator()
|
382
|
+
mc2 = menu.MenuItemCount
|
383
|
+
if not last_was_separator and mc2 - mc1 > 0:
|
384
|
+
menu.AppendSeparator()
|
385
|
+
|
386
|
+
for func in tree_operations_for_node(node):
|
387
|
+
submenu_name = func.submenu
|
388
|
+
submenu = None
|
389
|
+
if submenu_name and submenu_name in submenus:
|
390
|
+
submenu = submenus[submenu_name]
|
391
|
+
else:
|
392
|
+
if submenu_name:
|
393
|
+
subs = submenu_name.split("|")
|
394
|
+
common = ""
|
395
|
+
parent_menu = menu
|
396
|
+
for sname in subs:
|
397
|
+
if sname == "":
|
398
|
+
continue
|
399
|
+
if common:
|
400
|
+
common += "|"
|
401
|
+
common += sname
|
402
|
+
if common in submenus:
|
403
|
+
submenu = submenus[common]
|
404
|
+
parent_menu = submenu
|
405
|
+
else:
|
406
|
+
submenu = wx.Menu()
|
407
|
+
if func.separate_before:
|
408
|
+
parent_menu.AppendSeparator()
|
409
|
+
func.separate_before = False
|
410
|
+
parent_menu.AppendSubMenu(submenu, sname, func.help)
|
411
|
+
submenus[common] = submenu
|
412
|
+
parent_menu = submenu
|
413
|
+
|
414
|
+
menu_context = submenu if submenu is not None else menu
|
415
|
+
if func.separate_before:
|
416
|
+
menu_context.AppendSeparator()
|
417
|
+
func.separate_before = False
|
418
|
+
if func.reference is not None:
|
419
|
+
menu_context.AppendSubMenu(
|
420
|
+
create_menu_for_node(gui, func.reference(node), elements),
|
421
|
+
func.real_name,
|
422
|
+
)
|
423
|
+
continue
|
424
|
+
if func.radio_state is not None:
|
425
|
+
item = menu_context.Append(
|
426
|
+
wx.ID_ANY, func.real_name, func.help, wx.ITEM_RADIO
|
427
|
+
)
|
428
|
+
check = func.radio_state
|
429
|
+
item.Check(check)
|
430
|
+
if check and menu_context not in radio_check_not_needed:
|
431
|
+
radio_check_not_needed.append(menu_context)
|
432
|
+
if func.enabled:
|
433
|
+
gui.Bind(
|
434
|
+
wx.EVT_MENU,
|
435
|
+
menu_functions(func, node),
|
436
|
+
item,
|
437
|
+
)
|
438
|
+
else:
|
439
|
+
item.Enable(False)
|
440
|
+
else:
|
441
|
+
if hasattr(func, "check_state") and func.check_state is not None:
|
442
|
+
check = func.check_state
|
443
|
+
kind = wx.ITEM_CHECK
|
444
|
+
else:
|
445
|
+
kind = wx.ITEM_NORMAL
|
446
|
+
check = None
|
447
|
+
item = menu_context.Append(wx.ID_ANY, func.real_name, func.help, kind)
|
448
|
+
if check is not None:
|
449
|
+
item.Check(check)
|
450
|
+
if func.enabled:
|
451
|
+
gui.Bind(
|
452
|
+
wx.EVT_MENU,
|
453
|
+
menu_functions(func, node),
|
454
|
+
item,
|
455
|
+
)
|
456
|
+
else:
|
457
|
+
item.Enable(False)
|
458
|
+
|
459
|
+
if menu_context not in radio_check_not_needed:
|
460
|
+
radio_check_not_needed.append(menu_context)
|
461
|
+
if not submenu and func.separate_after:
|
462
|
+
menu.AppendSeparator()
|
463
|
+
for submenu in submenus.values():
|
464
|
+
plain = True
|
465
|
+
for item in submenu.GetMenuItems():
|
466
|
+
if not (item.IsSeparator() or item.IsSubMenu() or not item.IsEnabled()):
|
467
|
+
plain = False
|
468
|
+
break
|
469
|
+
if plain and submenu not in radio_check_not_needed:
|
470
|
+
radio_check_not_needed.append(submenu)
|
471
|
+
|
472
|
+
if submenu not in radio_check_not_needed:
|
473
|
+
item = submenu.Append(
|
474
|
+
wx.ID_ANY,
|
475
|
+
_("Other value..."),
|
476
|
+
_("Value set using properties"),
|
477
|
+
wx.ITEM_RADIO,
|
478
|
+
)
|
479
|
+
item.Check(True)
|
480
|
+
return menu
|
481
|
+
|
482
|
+
|
483
|
+
def create_menu(gui, node, elements):
|
484
|
+
"""
|
485
|
+
Create menu items. This is used for both the scene and the tree to create menu items.
|
486
|
+
|
487
|
+
@param gui: Gui used to create menu items.
|
488
|
+
@param node: The Node clicked on for the generated menu.
|
489
|
+
@param elements: elements service for use with node creation
|
490
|
+
@return:
|
491
|
+
"""
|
492
|
+
if node is None:
|
493
|
+
return
|
494
|
+
# Is it a reference object?
|
495
|
+
optional_node = None
|
496
|
+
if hasattr(node, "node"):
|
497
|
+
optional_node = node
|
498
|
+
node = node.node
|
499
|
+
|
500
|
+
menu = create_menu_for_node(gui, node, elements, optional_node)
|
501
|
+
if menu.MenuItemCount != 0:
|
502
|
+
gui.PopupMenu(menu)
|
503
|
+
menu.Destroy()
|
504
|
+
|
505
|
+
|
506
|
+
##############
|
507
|
+
# GUI CONTROL OVERRIDES
|
508
|
+
##############
|
509
|
+
def set_color_according_to_theme(control, background, foreground):
|
510
|
+
win = control
|
511
|
+
while win is not None:
|
512
|
+
if hasattr(win, "context") and hasattr(win.context, "themes"):
|
513
|
+
if background:
|
514
|
+
col = win.context.themes.get(background)
|
515
|
+
if col:
|
516
|
+
control.SetBackgroundColour(col)
|
517
|
+
if foreground:
|
518
|
+
col = win.context.themes.get(foreground)
|
519
|
+
if col:
|
520
|
+
control.SetForegroundColour(col)
|
521
|
+
break
|
522
|
+
win = win.GetParent()
|
523
|
+
|
524
|
+
|
525
|
+
class TextCtrl(wx.TextCtrl):
|
526
|
+
"""
|
527
|
+
Just to add some of the more common things we need, i.e. smaller default size...
|
528
|
+
|
529
|
+
Allow text boxes of specific types so that we can have consistent options for dealing with them.
|
530
|
+
"""
|
531
|
+
|
532
|
+
def __init__(
|
533
|
+
self,
|
534
|
+
parent,
|
535
|
+
id=wx.ID_ANY,
|
536
|
+
value="",
|
537
|
+
pos=wx.DefaultPosition,
|
538
|
+
size=wx.DefaultSize,
|
539
|
+
style=0,
|
540
|
+
validator=wx.DefaultValidator,
|
541
|
+
name="",
|
542
|
+
check="",
|
543
|
+
limited=False,
|
544
|
+
nonzero=False,
|
545
|
+
):
|
546
|
+
if value is None:
|
547
|
+
value = ""
|
548
|
+
super().__init__(
|
549
|
+
parent,
|
550
|
+
id=id,
|
551
|
+
value=value,
|
552
|
+
pos=pos,
|
553
|
+
size=size,
|
554
|
+
style=style,
|
555
|
+
validator=validator,
|
556
|
+
name=name,
|
557
|
+
)
|
558
|
+
self.parent = parent
|
559
|
+
self.extend_default_units_if_empty = True
|
560
|
+
self._check = check
|
561
|
+
self._style = style
|
562
|
+
self._nonzero = nonzero
|
563
|
+
if self._nonzero is None:
|
564
|
+
self._nonzero = False
|
565
|
+
# For the sake of readability we allow multiple occurrences of
|
566
|
+
# the same character in the string even if it's unnecessary...
|
567
|
+
floatstr = "+-.eE0123456789"
|
568
|
+
unitstr = "".join(ACCEPTED_UNITS)
|
569
|
+
anglestr = "".join(ACCEPTED_ANGLE_UNITS)
|
570
|
+
self.charpattern = ""
|
571
|
+
if self._check == "length":
|
572
|
+
self.charpattern = floatstr + unitstr
|
573
|
+
elif self._check == "percent":
|
574
|
+
self.charpattern = floatstr + r"%"
|
575
|
+
elif self._check == "float":
|
576
|
+
self.charpattern = floatstr
|
577
|
+
elif self._check == "angle":
|
578
|
+
self.charpattern = floatstr + anglestr
|
579
|
+
elif self._check == "int":
|
580
|
+
self.charpattern = r"-+0123456789"
|
581
|
+
self.lower_limit = None
|
582
|
+
self.upper_limit = None
|
583
|
+
self.lower_limit_err = None
|
584
|
+
self.upper_limit_err = None
|
585
|
+
self.lower_limit_warn = None
|
586
|
+
self.upper_limit_warn = None
|
587
|
+
self._default_color_background = self.GetBackgroundColour()
|
588
|
+
self._error_color_background = wx.RED
|
589
|
+
self._warn_color_background = wx.YELLOW
|
590
|
+
self._modify_color_background = self._default_color_background
|
591
|
+
|
592
|
+
self._default_color_foreground = self.GetForegroundColour()
|
593
|
+
self._error_color_foreground = self._default_color_foreground
|
594
|
+
self._warn_color_foreground = wx.BLACK
|
595
|
+
self._modify_color_foreground = self._default_color_foreground
|
596
|
+
self._warn_status = "modified"
|
597
|
+
|
598
|
+
self._last_valid_value = None
|
599
|
+
self._event_generated = None
|
600
|
+
self._action_routine = None
|
601
|
+
self._default_values = None
|
602
|
+
|
603
|
+
# You can set this to False, if you don't want logic to interfere with text input
|
604
|
+
self.execute_action_on_change = True
|
605
|
+
|
606
|
+
if self._check is not None and self._check != "":
|
607
|
+
self.Bind(wx.EVT_KEY_DOWN, self.on_char)
|
608
|
+
self.Bind(wx.EVT_KEY_UP, self.on_check)
|
609
|
+
self.Bind(wx.EVT_SET_FOCUS, self.on_enter_field)
|
610
|
+
self.Bind(wx.EVT_KILL_FOCUS, self.on_leave_field)
|
611
|
+
self.Bind(wx.EVT_RIGHT_DOWN, self.on_right_click)
|
612
|
+
if self._style & wx.TE_PROCESS_ENTER != 0:
|
613
|
+
self.Bind(wx.EVT_TEXT_ENTER, self.on_enter)
|
614
|
+
_MIN_WIDTH, _MAX_WIDTH = self.validate_widths()
|
615
|
+
self.SetMinSize(dip_size(self, _MIN_WIDTH, -1))
|
616
|
+
if limited:
|
617
|
+
self.SetMaxSize(dip_size(self, _MAX_WIDTH, -1))
|
618
|
+
set_color_according_to_theme(self, "text_bg", "text_fg")
|
619
|
+
|
620
|
+
def validate_widths(self):
|
621
|
+
minpattern = "0000"
|
622
|
+
maxpattern = "999999999.99mm"
|
623
|
+
if self._check == "length":
|
624
|
+
minpattern = "0000"
|
625
|
+
maxpattern = "999999999.99mm"
|
626
|
+
elif self._check == "percent":
|
627
|
+
minpattern = "0000"
|
628
|
+
maxpattern = "99.99%"
|
629
|
+
elif self._check == "float":
|
630
|
+
minpattern = "0000"
|
631
|
+
maxpattern = "99999.99"
|
632
|
+
elif self._check == "angle":
|
633
|
+
minpattern = "0000"
|
634
|
+
maxpattern = "9999.99deg"
|
635
|
+
elif self._check == "int":
|
636
|
+
minpattern = "0000"
|
637
|
+
maxpattern = "-999999"
|
638
|
+
# Let's be a bit more specific: what is the minimum size of the textcontrol fonts
|
639
|
+
# to hold these patterns
|
640
|
+
tfont = self.GetFont()
|
641
|
+
xsize = 15
|
642
|
+
imgBit = wx.Bitmap(xsize, xsize)
|
643
|
+
dc = wx.MemoryDC(imgBit)
|
644
|
+
dc.SelectObject(imgBit)
|
645
|
+
dc.SetFont(tfont)
|
646
|
+
f_width, f_height, f_descent, f_external_leading = dc.GetFullTextExtent(
|
647
|
+
minpattern
|
648
|
+
)
|
649
|
+
minw = f_width + 5
|
650
|
+
f_width, f_height, f_descent, f_external_leading = dc.GetFullTextExtent(
|
651
|
+
maxpattern
|
652
|
+
)
|
653
|
+
maxw = f_width + 10
|
654
|
+
# Now release dc
|
655
|
+
dc.SelectObject(wx.NullBitmap)
|
656
|
+
return minw, maxw
|
657
|
+
|
658
|
+
def SetActionRoutine(self, action_routine):
|
659
|
+
"""
|
660
|
+
This routine will be called after a lost_focus / text_enter event,
|
661
|
+
it's a simple way of dealing with all the
|
662
|
+
ctrl.bind(wx.EVT_KILL_FOCUS / wx.EVT_TEXT_ENTER) things
|
663
|
+
Yes, you can still have them, but you should call
|
664
|
+
ctrl.prevalidate()
|
665
|
+
then to ensure the logic to avoid invalid content is been called.
|
666
|
+
If you need to programmatically distinguish between a lost focus
|
667
|
+
and text_enter event, then consult
|
668
|
+
ctrl.event_generated()
|
669
|
+
this will give back wx.EVT_KILL_FOCUS or wx.EVT_TEXT_ENTER
|
670
|
+
"""
|
671
|
+
self._action_routine = action_routine
|
672
|
+
|
673
|
+
def event_generated(self):
|
674
|
+
"""
|
675
|
+
This routine will give back wx.EVT_KILL_FOCUS or wx.EVT_TEXT_ENTER
|
676
|
+
if called during an execution of the validator routine, see above,
|
677
|
+
or None in any other case
|
678
|
+
"""
|
679
|
+
return self._event_generated
|
680
|
+
|
681
|
+
def set_default_values(self, def_values):
|
682
|
+
self._default_values = def_values
|
683
|
+
|
684
|
+
def get_warn_status(self, txt):
|
685
|
+
status = ""
|
686
|
+
try:
|
687
|
+
value = None
|
688
|
+
if self._check == "float":
|
689
|
+
value = float(txt)
|
690
|
+
elif self._check == "percent":
|
691
|
+
if txt.endswith("%"):
|
692
|
+
value = float(txt[:-1]) / 100.0
|
693
|
+
else:
|
694
|
+
value = float(txt)
|
695
|
+
elif self._check == "int":
|
696
|
+
value = int(txt)
|
697
|
+
elif self._check == "empty":
|
698
|
+
if len(txt) == 0:
|
699
|
+
status = "error"
|
700
|
+
elif self._check == "length":
|
701
|
+
value = Length(txt)
|
702
|
+
elif self._check == "angle":
|
703
|
+
value = Angle(txt)
|
704
|
+
# we passed so far, thus the values are syntactically correct
|
705
|
+
# Now check for content compliance
|
706
|
+
if value is not None:
|
707
|
+
if self.lower_limit is not None and value < self.lower_limit:
|
708
|
+
value = self.lower_limit
|
709
|
+
self.SetValue(str(value))
|
710
|
+
status = "default"
|
711
|
+
if self.upper_limit is not None and value > self.upper_limit:
|
712
|
+
value = self.upper_limit
|
713
|
+
self.SetValue(str(value))
|
714
|
+
status = "default"
|
715
|
+
if self.lower_limit_warn is not None and value < self.lower_limit_warn:
|
716
|
+
status = "warning"
|
717
|
+
if self.upper_limit_warn is not None and value > self.upper_limit_warn:
|
718
|
+
status = "warning"
|
719
|
+
if self.lower_limit_err is not None and value < self.lower_limit_err:
|
720
|
+
status = "error"
|
721
|
+
if self.upper_limit_err is not None and value > self.upper_limit_err:
|
722
|
+
status = "error"
|
723
|
+
if self._nonzero and value == 0:
|
724
|
+
status = "error"
|
725
|
+
except ValueError:
|
726
|
+
status = "error"
|
727
|
+
return status
|
728
|
+
|
729
|
+
def SetValue(self, newvalue):
|
730
|
+
identical = False
|
731
|
+
current = super().GetValue()
|
732
|
+
if self._check == "float":
|
733
|
+
try:
|
734
|
+
v1 = float(current)
|
735
|
+
v2 = float(newvalue)
|
736
|
+
if v1 == v2:
|
737
|
+
identical = True
|
738
|
+
except ValueError:
|
739
|
+
pass
|
740
|
+
if identical:
|
741
|
+
# print (f"...ignored {current}={v1}, {newvalue}={v2}")
|
742
|
+
return
|
743
|
+
# print(f"SetValue called: {current} != {newvalue}")
|
744
|
+
self._last_valid_value = newvalue
|
745
|
+
status = self.get_warn_status(newvalue)
|
746
|
+
self.warn_status = status
|
747
|
+
cursor = self.GetInsertionPoint()
|
748
|
+
super().SetValue(newvalue)
|
749
|
+
cursor = min(len(newvalue), cursor)
|
750
|
+
self.SetInsertionPoint(cursor)
|
751
|
+
|
752
|
+
def set_error_level(self, err_min, err_max):
|
753
|
+
self.lower_limit_err = err_min
|
754
|
+
self.upper_limit_err = err_max
|
755
|
+
|
756
|
+
def set_warn_level(self, warn_min, warn_max):
|
757
|
+
self.lower_limit_warn = warn_min
|
758
|
+
self.upper_limit_warn = warn_max
|
759
|
+
|
760
|
+
def set_range(self, range_min, range_max):
|
761
|
+
self.lower_limit = range_min
|
762
|
+
self.upper_limit = range_max
|
763
|
+
|
764
|
+
def prevalidate(self, origin=None):
|
765
|
+
# Check whether the field is okay, if not then put it to the last value
|
766
|
+
txt = super().GetValue()
|
767
|
+
# print (f"prevalidate called from: {origin}, check={self._check}, content:{txt}")
|
768
|
+
if self.warn_status == "error" and self._last_valid_value is not None:
|
769
|
+
# ChangeValue is not creating any events...
|
770
|
+
self.ChangeValue(self._last_valid_value)
|
771
|
+
self.warn_status = ""
|
772
|
+
elif (
|
773
|
+
txt != "" and self._check == "length" and self.extend_default_units_if_empty
|
774
|
+
):
|
775
|
+
# Do we have non-existing units provided? --> Change content
|
776
|
+
purenumber = True
|
777
|
+
unitstr = "".join(ACCEPTED_UNITS)
|
778
|
+
for c in unitstr:
|
779
|
+
if c in txt:
|
780
|
+
purenumber = False
|
781
|
+
break
|
782
|
+
if purenumber and hasattr(self.parent, "context"):
|
783
|
+
context = self.parent.context
|
784
|
+
root = context.root
|
785
|
+
root.setting(str, "units_name", "mm")
|
786
|
+
units = root.units_name
|
787
|
+
if units in ("inch", "inches"):
|
788
|
+
units = "in"
|
789
|
+
txt = txt.strip() + units
|
790
|
+
self.ChangeValue(txt)
|
791
|
+
elif (
|
792
|
+
txt != "" and self._check == "angle" and self.extend_default_units_if_empty
|
793
|
+
):
|
794
|
+
# Do we have non-existing units provided? --> Change content
|
795
|
+
purenumber = True
|
796
|
+
unitstr = "".join(ACCEPTED_ANGLE_UNITS)
|
797
|
+
for c in unitstr:
|
798
|
+
if c in txt:
|
799
|
+
purenumber = False
|
800
|
+
break
|
801
|
+
if purenumber and hasattr(self.parent, "context"):
|
802
|
+
context = self.parent.context
|
803
|
+
root = context.root
|
804
|
+
root.setting(str, "angle_units", "deg")
|
805
|
+
units = root.angle_units
|
806
|
+
txt = txt.strip() + units
|
807
|
+
self.ChangeValue(txt)
|
808
|
+
|
809
|
+
def on_enter_field(self, event):
|
810
|
+
self._last_valid_value = super().GetValue()
|
811
|
+
event.Skip()
|
812
|
+
|
813
|
+
def on_leave_field(self, event):
|
814
|
+
# Needs to be passed on
|
815
|
+
event.Skip()
|
816
|
+
self.prevalidate("leave")
|
817
|
+
if self._action_routine is not None:
|
818
|
+
self._event_generated = wx.EVT_KILL_FOCUS
|
819
|
+
try:
|
820
|
+
self._action_routine()
|
821
|
+
finally:
|
822
|
+
self._event_generated = None
|
823
|
+
self.SelectNone()
|
824
|
+
# We assume it's been dealt with, so we recolor...
|
825
|
+
self.SetModified(False)
|
826
|
+
self.warn_status = self._warn_status
|
827
|
+
|
828
|
+
def on_enter(self, event):
|
829
|
+
# Let others deal with it after me
|
830
|
+
event.Skip()
|
831
|
+
self.prevalidate("enter")
|
832
|
+
if self._action_routine is not None:
|
833
|
+
self._event_generated = wx.EVT_TEXT_ENTER
|
834
|
+
try:
|
835
|
+
self._action_routine()
|
836
|
+
finally:
|
837
|
+
self._event_generated = None
|
838
|
+
self.SelectNone()
|
839
|
+
# We assume it's been dealt with, so we recolor...
|
840
|
+
self.SetModified(False)
|
841
|
+
self.warn_status = self._warn_status
|
842
|
+
|
843
|
+
def on_right_click(self, event):
|
844
|
+
def set_menu_value(to_be_set):
|
845
|
+
def handler(event):
|
846
|
+
self.SetValue(to_be_set)
|
847
|
+
self.prevalidate("enter")
|
848
|
+
if self._action_routine is not None:
|
849
|
+
self._event_generated = wx.EVT_TEXT_ENTER
|
850
|
+
try:
|
851
|
+
self._action_routine()
|
852
|
+
finally:
|
853
|
+
self._event_generated = None
|
854
|
+
return handler
|
855
|
+
|
856
|
+
if not self._default_values:
|
857
|
+
event.Skip()
|
858
|
+
return
|
859
|
+
menu = wx.Menu()
|
860
|
+
has_info = isinstance(self._default_values[0], (list, tuple))
|
861
|
+
item : wx.MenuItem = menu.Append(wx.ID_ANY, _("Default values..."), "")
|
862
|
+
item.Enable(False)
|
863
|
+
for info in self._default_values:
|
864
|
+
item = menu.Append(wx.ID_ANY, info[0] if has_info else info, info[1] if has_info else "")
|
865
|
+
self.Bind(wx.EVT_MENU, set_menu_value(info[0] if has_info else info), id=item.GetId())
|
866
|
+
self.PopupMenu(menu)
|
867
|
+
menu.Destroy()
|
868
|
+
|
869
|
+
|
870
|
+
@property
|
871
|
+
def warn_status(self):
|
872
|
+
return self._warn_status
|
873
|
+
|
874
|
+
@warn_status.setter
|
875
|
+
def warn_status(self, value):
|
876
|
+
self._warn_status = value
|
877
|
+
background = self._default_color_background
|
878
|
+
foreground = self._default_color_foreground
|
879
|
+
if value == "modified":
|
880
|
+
# Is it modified?
|
881
|
+
if self.IsModified():
|
882
|
+
background = self._modify_color_background
|
883
|
+
foreground = self._modify_color_foreground
|
884
|
+
elif value == "warning":
|
885
|
+
background = self._warn_color_background
|
886
|
+
foreground = self._warn_color_foreground
|
887
|
+
elif value == "error":
|
888
|
+
background = self._error_color_background
|
889
|
+
foreground = self._error_color_foreground
|
890
|
+
self.SetBackgroundColour(background)
|
891
|
+
self.SetForegroundColour(foreground)
|
892
|
+
self.Refresh()
|
893
|
+
|
894
|
+
def on_char(self, event):
|
895
|
+
proceed = True
|
896
|
+
# The French azerty keyboard generates numbers by pressing Shift + some key
|
897
|
+
# Under Linux this is not properly translated by GetUnicodeKey and
|
898
|
+
# is hence leading to a 'wrong' character being recognised (the original key).
|
899
|
+
# So we can't rely on a proper representation if the Shift-Key
|
900
|
+
# is held down, sigh.
|
901
|
+
if self.charpattern != "" and not event.ShiftDown():
|
902
|
+
keyc = event.GetUnicodeKey()
|
903
|
+
special = False
|
904
|
+
if event.RawControlDown() or event.ControlDown() or event.AltDown():
|
905
|
+
# GetUnicodeKey ignores all special keys, so we need to acknowledge that
|
906
|
+
special = True
|
907
|
+
if keyc == 127: # delete
|
908
|
+
special = True
|
909
|
+
if keyc != wx.WXK_NONE and not special:
|
910
|
+
# a 'real' character?
|
911
|
+
if keyc >= ord(" "):
|
912
|
+
char = chr(keyc).lower()
|
913
|
+
if char not in self.charpattern:
|
914
|
+
proceed = False
|
915
|
+
# print(f"Ignored: {keyc} - {char}")
|
916
|
+
if proceed:
|
917
|
+
event.DoAllowNextEvent()
|
918
|
+
event.Skip()
|
919
|
+
|
920
|
+
def on_check(self, event):
|
921
|
+
event.Skip()
|
922
|
+
txt = super().GetValue()
|
923
|
+
status = self.get_warn_status(txt)
|
924
|
+
if status == "":
|
925
|
+
status = "modified"
|
926
|
+
self.warn_status = status
|
927
|
+
# Is it a valid value?
|
928
|
+
lenokay = True
|
929
|
+
if len(txt) == 0 and self._check in (
|
930
|
+
"float",
|
931
|
+
"length",
|
932
|
+
"angle",
|
933
|
+
"int",
|
934
|
+
"percent",
|
935
|
+
):
|
936
|
+
lenokay = False
|
937
|
+
if (
|
938
|
+
self.execute_action_on_change
|
939
|
+
and status == "modified"
|
940
|
+
and hasattr(self.parent, "context")
|
941
|
+
and lenokay
|
942
|
+
):
|
943
|
+
if getattr(self.parent.context.root, "process_while_typing", False):
|
944
|
+
if self._action_routine is not None:
|
945
|
+
self._event_generated = wx.EVT_TEXT
|
946
|
+
try:
|
947
|
+
self._action_routine()
|
948
|
+
finally:
|
949
|
+
self._event_generated = None
|
950
|
+
|
951
|
+
@property
|
952
|
+
def is_changed(self):
|
953
|
+
return self.GetValue() != self._last_valid_value
|
954
|
+
|
955
|
+
@property
|
956
|
+
def Value(self):
|
957
|
+
return self.GetValue()
|
958
|
+
|
959
|
+
def GetValue(self):
|
960
|
+
result = super().GetValue()
|
961
|
+
if (
|
962
|
+
result != ""
|
963
|
+
and self._check == "length"
|
964
|
+
and self.extend_default_units_if_empty
|
965
|
+
):
|
966
|
+
purenumber = True
|
967
|
+
unitstr = "".join(ACCEPTED_UNITS)
|
968
|
+
for c in unitstr:
|
969
|
+
if c in result:
|
970
|
+
purenumber = False
|
971
|
+
break
|
972
|
+
if purenumber and hasattr(self.parent, "context"):
|
973
|
+
context = self.parent.context
|
974
|
+
root = context.root
|
975
|
+
root.setting(str, "units_name", "mm")
|
976
|
+
units = root.units_name
|
977
|
+
if units in ("inch", "inches"):
|
978
|
+
units = "in"
|
979
|
+
result = result.strip()
|
980
|
+
if result.endswith("."):
|
981
|
+
result += "0"
|
982
|
+
result += units
|
983
|
+
return result
|
984
|
+
|
985
|
+
|
986
|
+
class wxCheckBox(wx.CheckBox):
|
987
|
+
"""
|
988
|
+
This class wraps around wx.CheckBox and creates a series of mouse over tool tips to permit Linux tooltips that
|
989
|
+
otherwise do not show.
|
990
|
+
"""
|
991
|
+
|
992
|
+
def __init__(
|
993
|
+
self,
|
994
|
+
*args,
|
995
|
+
**kwargs,
|
996
|
+
):
|
997
|
+
self._tool_tip = None
|
998
|
+
super().__init__(*args, **kwargs)
|
999
|
+
if platform.system() == "Linux":
|
1000
|
+
|
1001
|
+
def on_mouse_over_check(ctrl):
|
1002
|
+
def mouse(event=None):
|
1003
|
+
ctrl.SetToolTip(self._tool_tip)
|
1004
|
+
event.Skip()
|
1005
|
+
|
1006
|
+
return mouse
|
1007
|
+
|
1008
|
+
self.Bind(wx.EVT_MOTION, on_mouse_over_check(super()))
|
1009
|
+
set_color_according_to_theme(self, "text_bg", "text_fg")
|
1010
|
+
|
1011
|
+
def SetToolTip(self, tooltip):
|
1012
|
+
self._tool_tip = tooltip
|
1013
|
+
super().SetToolTip(self._tool_tip)
|
1014
|
+
|
1015
|
+
class wxComboBox(wx.ComboBox):
|
1016
|
+
"""
|
1017
|
+
This class wraps around wx.ComboBox and creates a series of mouse over tool tips to permit Linux tooltips that
|
1018
|
+
otherwise do not show.
|
1019
|
+
"""
|
1020
|
+
|
1021
|
+
def __init__(
|
1022
|
+
self,
|
1023
|
+
*args,
|
1024
|
+
**kwargs,
|
1025
|
+
):
|
1026
|
+
self._tool_tip = None
|
1027
|
+
super().__init__(*args, **kwargs)
|
1028
|
+
if platform.system() == "Linux":
|
1029
|
+
|
1030
|
+
def on_mouse_over_check(ctrl):
|
1031
|
+
def mouse(event=None):
|
1032
|
+
ctrl.SetToolTip(self._tool_tip)
|
1033
|
+
event.Skip()
|
1034
|
+
|
1035
|
+
return mouse
|
1036
|
+
|
1037
|
+
self.Bind(wx.EVT_MOTION, on_mouse_over_check(super()))
|
1038
|
+
set_color_according_to_theme(self, "text_bg", "text_fg")
|
1039
|
+
|
1040
|
+
def SetToolTip(self, tooltip):
|
1041
|
+
self._tool_tip = tooltip
|
1042
|
+
super().SetToolTip(self._tool_tip)
|
1043
|
+
|
1044
|
+
|
1045
|
+
class wxTreeCtrl(wx.TreeCtrl):
|
1046
|
+
"""
|
1047
|
+
This class wraps around wx.TreeCtrl and creates a series of mouse over tool tips to permit Linux tooltips that
|
1048
|
+
otherwise do not show.
|
1049
|
+
"""
|
1050
|
+
|
1051
|
+
def __init__(
|
1052
|
+
self,
|
1053
|
+
*args,
|
1054
|
+
**kwargs,
|
1055
|
+
):
|
1056
|
+
self._tool_tip = None
|
1057
|
+
super().__init__(*args, **kwargs)
|
1058
|
+
if platform.system() == "Linux":
|
1059
|
+
|
1060
|
+
def on_mouse_over_check(ctrl):
|
1061
|
+
def mouse(event=None):
|
1062
|
+
ctrl.SetToolTip(self._tool_tip)
|
1063
|
+
event.Skip()
|
1064
|
+
|
1065
|
+
return mouse
|
1066
|
+
|
1067
|
+
self.Bind(wx.EVT_MOTION, on_mouse_over_check(super()))
|
1068
|
+
set_color_according_to_theme(self, "list_bg", "list_fg")
|
1069
|
+
|
1070
|
+
def SetToolTip(self, tooltip):
|
1071
|
+
self._tool_tip = tooltip
|
1072
|
+
super().SetToolTip(self._tool_tip)
|
1073
|
+
|
1074
|
+
|
1075
|
+
class wxBitmapButton(wx.BitmapButton):
|
1076
|
+
"""
|
1077
|
+
This class wraps around wx.Button and creates a series of mouse over tool tips to permit Linux tooltips that
|
1078
|
+
otherwise do not show.
|
1079
|
+
"""
|
1080
|
+
|
1081
|
+
def __init__(
|
1082
|
+
self,
|
1083
|
+
*args,
|
1084
|
+
**kwargs,
|
1085
|
+
):
|
1086
|
+
self._tool_tip = None
|
1087
|
+
super().__init__(*args, **kwargs)
|
1088
|
+
if platform.system() == "Linux":
|
1089
|
+
|
1090
|
+
def on_mouse_over_check(ctrl):
|
1091
|
+
def mouse(event=None):
|
1092
|
+
ctrl.SetToolTip(self._tool_tip)
|
1093
|
+
event.Skip()
|
1094
|
+
|
1095
|
+
return mouse
|
1096
|
+
|
1097
|
+
self.Bind(wx.EVT_MOTION, on_mouse_over_check(super()))
|
1098
|
+
set_color_according_to_theme(self, "button_bg", "button_fg")
|
1099
|
+
|
1100
|
+
def SetToolTip(self, tooltip):
|
1101
|
+
self._tool_tip = tooltip
|
1102
|
+
super().SetToolTip(self._tool_tip)
|
1103
|
+
|
1104
|
+
|
1105
|
+
class wxButton(wx.Button):
|
1106
|
+
"""
|
1107
|
+
This class wraps around wx.Button and creates a series of mouse over tool tips to permit Linux tooltips that
|
1108
|
+
otherwise do not show.
|
1109
|
+
"""
|
1110
|
+
|
1111
|
+
def __init__(
|
1112
|
+
self,
|
1113
|
+
*args,
|
1114
|
+
**kwargs,
|
1115
|
+
):
|
1116
|
+
self._tool_tip = None
|
1117
|
+
super().__init__(*args, **kwargs)
|
1118
|
+
if platform.system() == "Linux":
|
1119
|
+
|
1120
|
+
def on_mouse_over_check(ctrl):
|
1121
|
+
def mouse(event=None):
|
1122
|
+
ctrl.SetToolTip(self._tool_tip)
|
1123
|
+
event.Skip()
|
1124
|
+
|
1125
|
+
return mouse
|
1126
|
+
|
1127
|
+
self.Bind(wx.EVT_MOTION, on_mouse_over_check(super()))
|
1128
|
+
set_color_according_to_theme(self, "button_bg", "button_fg")
|
1129
|
+
|
1130
|
+
def SetToolTip(self, tooltip):
|
1131
|
+
self._tool_tip = tooltip
|
1132
|
+
super().SetToolTip(self._tool_tip)
|
1133
|
+
|
1134
|
+
|
1135
|
+
class wxToggleButton(wx.ToggleButton):
|
1136
|
+
"""
|
1137
|
+
This class wraps around wx.ToggleButton and creates a series of mouse over tool tips to permit Linux tooltips that
|
1138
|
+
otherwise do not show.
|
1139
|
+
"""
|
1140
|
+
|
1141
|
+
def __init__(
|
1142
|
+
self,
|
1143
|
+
*args,
|
1144
|
+
**kwargs,
|
1145
|
+
):
|
1146
|
+
self._tool_tip = None
|
1147
|
+
super().__init__(*args, **kwargs)
|
1148
|
+
if platform.system() == "Linux":
|
1149
|
+
|
1150
|
+
def on_mouse_over_check(ctrl):
|
1151
|
+
def mouse(event=None):
|
1152
|
+
ctrl.SetToolTip(self._tool_tip)
|
1153
|
+
event.Skip()
|
1154
|
+
|
1155
|
+
return mouse
|
1156
|
+
|
1157
|
+
self.Bind(wx.EVT_MOTION, on_mouse_over_check(super()))
|
1158
|
+
set_color_according_to_theme(self, "button_bg", "button_fg")
|
1159
|
+
self.bitmap_toggled = None
|
1160
|
+
self.bitmap_untoggled = None
|
1161
|
+
|
1162
|
+
def update_button(self, value):
|
1163
|
+
# We just act as a man in the middle
|
1164
|
+
if value is None:
|
1165
|
+
value = self.GetValue()
|
1166
|
+
if value:
|
1167
|
+
if self.bitmap_toggled is not None:
|
1168
|
+
self.SetBitmap(self.bitmap_toggled)
|
1169
|
+
else:
|
1170
|
+
if self.bitmap_untoggled is not None:
|
1171
|
+
self.SetBitmap(self.bitmap_untoggled)
|
1172
|
+
|
1173
|
+
def SetValue(self, value):
|
1174
|
+
super().SetValue(value)
|
1175
|
+
self.update_button(value)
|
1176
|
+
|
1177
|
+
def SetToolTip(self, tooltip):
|
1178
|
+
self._tool_tip = tooltip
|
1179
|
+
super().SetToolTip(self._tool_tip)
|
1180
|
+
|
1181
|
+
|
1182
|
+
class wxStaticBitmap(wx.StaticBitmap):
|
1183
|
+
"""
|
1184
|
+
This class wraps around wx.StaticBitmap and creates a series of mouse over tool tips to permit Linux tooltips that
|
1185
|
+
otherwise do not show.
|
1186
|
+
"""
|
1187
|
+
|
1188
|
+
def __init__(
|
1189
|
+
self,
|
1190
|
+
*args,
|
1191
|
+
**kwargs,
|
1192
|
+
):
|
1193
|
+
self._tool_tip = None
|
1194
|
+
super().__init__(*args, **kwargs)
|
1195
|
+
if platform.system() == "Linux":
|
1196
|
+
|
1197
|
+
def on_mouse_over_check(ctrl):
|
1198
|
+
def mouse(event=None):
|
1199
|
+
ctrl.SetToolTip(self._tool_tip)
|
1200
|
+
event.Skip()
|
1201
|
+
|
1202
|
+
return mouse
|
1203
|
+
|
1204
|
+
self.Bind(wx.EVT_MOTION, on_mouse_over_check(super()))
|
1205
|
+
set_color_according_to_theme(self, "button_bg", "button_fg")
|
1206
|
+
|
1207
|
+
def SetToolTip(self, tooltip):
|
1208
|
+
self._tool_tip = tooltip
|
1209
|
+
super().SetToolTip(self._tool_tip)
|
1210
|
+
|
1211
|
+
|
1212
|
+
class StaticBoxSizer(wx.StaticBoxSizer):
|
1213
|
+
def __init__(
|
1214
|
+
self,
|
1215
|
+
parent,
|
1216
|
+
id=wx.ID_ANY,
|
1217
|
+
label="",
|
1218
|
+
orientation=wx.HORIZONTAL,
|
1219
|
+
*args,
|
1220
|
+
**kwargs,
|
1221
|
+
):
|
1222
|
+
if label is None:
|
1223
|
+
label = ""
|
1224
|
+
self.sbox = wx.StaticBox(parent, id, label=label)
|
1225
|
+
self.sbox.SetMinSize(dip_size(self, 50, 50))
|
1226
|
+
super().__init__(self.sbox, orientation)
|
1227
|
+
self.parent = parent
|
1228
|
+
|
1229
|
+
@property
|
1230
|
+
def Id(self):
|
1231
|
+
return self.sbox.Id
|
1232
|
+
|
1233
|
+
def GetId(self):
|
1234
|
+
return self.Id
|
1235
|
+
|
1236
|
+
def Show(self, show=True):
|
1237
|
+
self.sbox.Show(show)
|
1238
|
+
|
1239
|
+
def SetLabel(self, label):
|
1240
|
+
self.sbox.SetLabel(label)
|
1241
|
+
|
1242
|
+
def Refresh(self, *args):
|
1243
|
+
self.sbox.Refresh(*args)
|
1244
|
+
|
1245
|
+
def Enable(self, enable:bool=True):
|
1246
|
+
"""Enable or disable the StaticBoxSizer and its children.
|
1247
|
+
|
1248
|
+
Enables or disables all children of the sizer recursively.
|
1249
|
+
"""
|
1250
|
+
def enem(wind, flag):
|
1251
|
+
for c in wind.GetChildren():
|
1252
|
+
enem(c, flag)
|
1253
|
+
if hasattr(wind, "Enable"):
|
1254
|
+
wind.Enable(flag)
|
1255
|
+
|
1256
|
+
enem(self.sbox, enable)
|
1257
|
+
|
1258
|
+
class ScrolledPanel(SP):
|
1259
|
+
"""
|
1260
|
+
We sometimes delete things fast enough that they call _SetupAfter when dead and crash.
|
1261
|
+
"""
|
1262
|
+
|
1263
|
+
def _SetupAfter(self, scrollToTop):
|
1264
|
+
try:
|
1265
|
+
self.SetVirtualSize(self.GetBestVirtualSize())
|
1266
|
+
if scrollToTop:
|
1267
|
+
self.Scroll(0, 0)
|
1268
|
+
except RuntimeError:
|
1269
|
+
pass
|
1270
|
+
|
1271
|
+
class wxListCtrl(wx.ListCtrl):
|
1272
|
+
"""
|
1273
|
+
wxListCtrl will extend a regular ListCtrl by saving / restoring column widths
|
1274
|
+
"""
|
1275
|
+
def __init__(
|
1276
|
+
self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, context=None, list_name=None
|
1277
|
+
):
|
1278
|
+
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
|
1279
|
+
self.context = context
|
1280
|
+
self.list_name = list_name
|
1281
|
+
# The resize event is never triggered, so tap into the parent...
|
1282
|
+
# parent.Bind(wx.EVT_SIZE, self.proxy_resize_event, self)
|
1283
|
+
parent.Bind(wx.EVT_SIZE, self.proxy_resize_event, parent)
|
1284
|
+
parent.Bind(wx.EVT_LIST_COL_END_DRAG, self.proxy_col_resized, self)
|
1285
|
+
set_color_according_to_theme(self, "list_bg", "list_fg")
|
1286
|
+
|
1287
|
+
def save_column_widths(self):
|
1288
|
+
if self.context is None or self.list_name is None:
|
1289
|
+
return
|
1290
|
+
try:
|
1291
|
+
sizes = list()
|
1292
|
+
for col in range(self.GetColumnCount()):
|
1293
|
+
sizes.append(self.GetColumnWidth(col))
|
1294
|
+
self.context.setting(tuple, self.list_name, None)
|
1295
|
+
setattr(self.context, self.list_name, sizes)
|
1296
|
+
except RuntimeError:
|
1297
|
+
# Could happen if the control is already destroyed
|
1298
|
+
return
|
1299
|
+
|
1300
|
+
def load_column_widths(self):
|
1301
|
+
if self.context is None or self.list_name is None:
|
1302
|
+
return
|
1303
|
+
sizes = self.context.setting(tuple, self.list_name, None)
|
1304
|
+
if sizes is None:
|
1305
|
+
return
|
1306
|
+
# print(f"Found for {self.list_name}: {sizes}")
|
1307
|
+
available = self.GetColumnCount()
|
1308
|
+
for idx, width in enumerate(sizes):
|
1309
|
+
if idx >= available:
|
1310
|
+
break
|
1311
|
+
self.SetColumnWidth(idx, width)
|
1312
|
+
|
1313
|
+
def resize_columns(self):
|
1314
|
+
self.load_column_widths()
|
1315
|
+
# we could at least try to make use of the available space
|
1316
|
+
dummy = self.adjust_last_column()
|
1317
|
+
|
1318
|
+
def proxy_col_resized(self, event):
|
1319
|
+
# We are not touching the event object to allow other routines to tap into it
|
1320
|
+
event.Skip()
|
1321
|
+
# print (f"col resized called from {self.GetId()} - {self.list_name}")
|
1322
|
+
dummy = self.adjust_last_column()
|
1323
|
+
self.save_column_widths()
|
1324
|
+
|
1325
|
+
def adjust_last_column(self, size_to_use=None):
|
1326
|
+
# gap is the amount of pixels to be reserved to allow for a vertical scrollbar
|
1327
|
+
gap = 30
|
1328
|
+
size = size_to_use
|
1329
|
+
if size is None:
|
1330
|
+
size = self.GetSize()
|
1331
|
+
list_width = size[0]
|
1332
|
+
total = gap
|
1333
|
+
last = 0
|
1334
|
+
for col in range(self.GetColumnCount()):
|
1335
|
+
try:
|
1336
|
+
last = self.GetColumnWidth(col)
|
1337
|
+
total += last
|
1338
|
+
except Exception as e:
|
1339
|
+
# print(f"Strange, crashed for column {col} of {self.GetColumnCount()}: {e}")
|
1340
|
+
return False
|
1341
|
+
# print(f"{self.list_name}, cols={self.GetColumnCount()}, available={list_width}, used={total}")
|
1342
|
+
if total < list_width:
|
1343
|
+
col = self.GetColumnCount() - 1
|
1344
|
+
if col < 0 :
|
1345
|
+
return False
|
1346
|
+
# print(f"Will adjust last column from {last} to {last + (list_width - total)}")
|
1347
|
+
try:
|
1348
|
+
self.SetColumnWidth(col, last + (list_width - total))
|
1349
|
+
except Exception as e:
|
1350
|
+
# print(f"Something strange happened while resizing the last columns for {self.list_name}: {e}")
|
1351
|
+
return False
|
1352
|
+
return True
|
1353
|
+
return False
|
1354
|
+
|
1355
|
+
def proxy_resize_event(self, event):
|
1356
|
+
# We are not touching the event object to allow other routines to tap into it
|
1357
|
+
event.Skip()
|
1358
|
+
# print (f"Resize called from {self.GetId()} - {self.list_name}: {event.Size}")
|
1359
|
+
if self.adjust_last_column(event.Size):
|
1360
|
+
self.save_column_widths()
|
1361
|
+
|
1362
|
+
|
1363
|
+
class EditableListCtrl(wxListCtrl, listmix.TextEditMixin):
|
1364
|
+
"""TextEditMixin allows any column to be edited."""
|
1365
|
+
|
1366
|
+
# ----------------------------------------------------------------------
|
1367
|
+
def __init__(
|
1368
|
+
self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, context=None, list_name=None,
|
1369
|
+
):
|
1370
|
+
"""Constructor"""
|
1371
|
+
wxListCtrl.__init__(self, parent=parent, ID=ID, pos=pos, size=size, style=style, context=context, list_name=list_name)
|
1372
|
+
listmix.TextEditMixin.__init__(self)
|
1373
|
+
set_color_according_to_theme(self, "list_bg", "list_fg")
|
1374
|
+
|
1375
|
+
|
1376
|
+
class HoverButton(wxButton):
|
1377
|
+
"""
|
1378
|
+
Provide a button with Hover-Color changing ability.
|
1379
|
+
"""
|
1380
|
+
|
1381
|
+
def __init__(self, parent, ID, label):
|
1382
|
+
super().__init__(parent, ID, label)
|
1383
|
+
self._focus_color = None
|
1384
|
+
self._disable_color = None
|
1385
|
+
self._foreground_color = self.GetForegroundColour()
|
1386
|
+
self._background_color = self.GetBackgroundColour()
|
1387
|
+
self.Bind(wx.EVT_ENTER_WINDOW, self.on_enter)
|
1388
|
+
self.Bind(wx.EVT_LEAVE_WINDOW, self.on_leave)
|
1389
|
+
# self.Bind(wx.EVT_MOUSE_EVENTS, self.on_mouse)
|
1390
|
+
set_color_according_to_theme(self, "list_bg", "list_fg")
|
1391
|
+
|
1392
|
+
def SetFocusColour(self, color):
|
1393
|
+
self._focus_color = wx.Colour(color)
|
1394
|
+
|
1395
|
+
def SetDisabledBackgroundColour(self, color):
|
1396
|
+
self._disable_color = wx.Colour(color)
|
1397
|
+
|
1398
|
+
def SetForegroundColour(self, color):
|
1399
|
+
self._foreground_color = wx.Colour(color)
|
1400
|
+
super().SetForegroundColour(color)
|
1401
|
+
|
1402
|
+
def SetBackgroundColour(self, color):
|
1403
|
+
self._background_color = wx.Colour(color)
|
1404
|
+
super().SetBackgroundColour(color)
|
1405
|
+
|
1406
|
+
def GetFocusColour(self, color):
|
1407
|
+
return self._focus_color
|
1408
|
+
|
1409
|
+
def Enable(self, value):
|
1410
|
+
if value:
|
1411
|
+
super().SetBackgroundColour(self._background_color)
|
1412
|
+
else:
|
1413
|
+
if self._disable_color is None:
|
1414
|
+
r, g, b, a = self._background_color.Get()
|
1415
|
+
color = wx.Colour(
|
1416
|
+
min(255, int(1.5 * r)),
|
1417
|
+
min(255, int(1.5 * g)),
|
1418
|
+
min(255, int(1.5 * b)),
|
1419
|
+
)
|
1420
|
+
else:
|
1421
|
+
color = self._disable_color
|
1422
|
+
super().SetBackgroundColour(color)
|
1423
|
+
super().Enable(value)
|
1424
|
+
self.Refresh()
|
1425
|
+
|
1426
|
+
def on_enter(self, event):
|
1427
|
+
if self._focus_color is not None:
|
1428
|
+
super().SetForegroundColour(self._focus_color)
|
1429
|
+
self.Refresh()
|
1430
|
+
event.Skip()
|
1431
|
+
|
1432
|
+
def on_leave(self, event):
|
1433
|
+
super().SetForegroundColour(self._foreground_color)
|
1434
|
+
self.Refresh()
|
1435
|
+
event.Skip()
|
1436
|
+
|
1437
|
+
# def on_mouse(self, event):
|
1438
|
+
# if event.Leaving():
|
1439
|
+
# self.on_leave(event)
|
1440
|
+
# event.Skip()
|
1441
|
+
|
1442
|
+
|
1443
|
+
class wxRadioBox(StaticBoxSizer):
|
1444
|
+
"""
|
1445
|
+
This class recreates the functionality of a wx.RadioBox, as this class does not recognize / honor parent color values, so a manual darkmode logic fails
|
1446
|
+
"""
|
1447
|
+
|
1448
|
+
def __init__(
|
1449
|
+
self,
|
1450
|
+
parent=None,
|
1451
|
+
id=None,
|
1452
|
+
label=None,
|
1453
|
+
choices=None,
|
1454
|
+
majorDimension = 0,
|
1455
|
+
style=0,
|
1456
|
+
*args,
|
1457
|
+
**kwargs,
|
1458
|
+
):
|
1459
|
+
self.parent = parent
|
1460
|
+
self.choices = choices
|
1461
|
+
self._children = []
|
1462
|
+
self._labels = []
|
1463
|
+
self._tool_tip = None
|
1464
|
+
self._help = None
|
1465
|
+
super().__init__(parent=parent, id=wx.ID_ANY, label=label, orientation=wx.VERTICAL)
|
1466
|
+
if majorDimension == 0 or style==wx.RA_SPECIFY_ROWS:
|
1467
|
+
majorDimension = 1000
|
1468
|
+
container = None
|
1469
|
+
for idx, c in enumerate(self.choices):
|
1470
|
+
if idx % majorDimension == 0:
|
1471
|
+
container = wx.BoxSizer(wx.HORIZONTAL)
|
1472
|
+
self.Add(container, 0, wx.EXPAND, 0)
|
1473
|
+
st = 0
|
1474
|
+
if idx == 0:
|
1475
|
+
st = wx.RB_GROUP
|
1476
|
+
|
1477
|
+
radio_option = wx.RadioButton(parent, wx.ID_ANY, label=c, style=st)
|
1478
|
+
container.Add(radio_option, 1, wx.ALIGN_CENTER_VERTICAL, 0)
|
1479
|
+
self._children.append(radio_option)
|
1480
|
+
|
1481
|
+
if platform.system() == "Linux":
|
1482
|
+
|
1483
|
+
def on_mouse_over_check(ctrl):
|
1484
|
+
def mouse(event=None):
|
1485
|
+
ctrl.SetToolTip(self._tool_tip)
|
1486
|
+
event.Skip()
|
1487
|
+
|
1488
|
+
return mouse
|
1489
|
+
for ctrl in self._children:
|
1490
|
+
ctrl.Bind(wx.EVT_MOTION, on_mouse_over_check(ctrl))
|
1491
|
+
|
1492
|
+
for ctrl in self._children:
|
1493
|
+
ctrl.Bind(wx.EVT_RADIOBUTTON, self.on_radio)
|
1494
|
+
|
1495
|
+
for ctrl in self._children + self._labels:
|
1496
|
+
set_color_according_to_theme(ctrl, "text_bg", "text_fg")
|
1497
|
+
|
1498
|
+
@property
|
1499
|
+
def Children(self):
|
1500
|
+
return self._children
|
1501
|
+
|
1502
|
+
def GetParent(self):
|
1503
|
+
return self.parent
|
1504
|
+
|
1505
|
+
def SetToolTip(self, tooltip):
|
1506
|
+
self._tool_tip = tooltip
|
1507
|
+
for ctrl in self._children:
|
1508
|
+
ctrl.SetToolTip(self._tool_tip)
|
1509
|
+
|
1510
|
+
def Select(self, n):
|
1511
|
+
self.SetSelection(n)
|
1512
|
+
|
1513
|
+
def SetSelection(self, n):
|
1514
|
+
for idx, ctrl in enumerate(self._children):
|
1515
|
+
ctrl.SetValue(idx == n)
|
1516
|
+
|
1517
|
+
def GetSelection(self):
|
1518
|
+
for idx, ctrl in enumerate(self._children):
|
1519
|
+
if ctrl.GetValue():
|
1520
|
+
return idx
|
1521
|
+
return -1
|
1522
|
+
|
1523
|
+
def GetStringSelection(self):
|
1524
|
+
idx = self.GetSelection()
|
1525
|
+
return None if idx < 0 else self.choices[idx]
|
1526
|
+
|
1527
|
+
def Disable(self):
|
1528
|
+
self.Enable(False)
|
1529
|
+
|
1530
|
+
def EnableItem(self, n, flag):
|
1531
|
+
if 0 <= n < len(self._children):
|
1532
|
+
self._children[n].Enable(flag)
|
1533
|
+
|
1534
|
+
def Enable(self, flag):
|
1535
|
+
for ctrl in self._children:
|
1536
|
+
ctrl.Enable(flag)
|
1537
|
+
|
1538
|
+
def Hide(self):
|
1539
|
+
self.Show(False)
|
1540
|
+
|
1541
|
+
def Show(self, flag):
|
1542
|
+
for ctrl in self._children + self._labels:
|
1543
|
+
ctrl.Show(flag)
|
1544
|
+
|
1545
|
+
def Bind(self, event_type, routine):
|
1546
|
+
self.parent.Bind(event_type, routine, self)
|
1547
|
+
|
1548
|
+
def on_radio(self, orgevent):
|
1549
|
+
#
|
1550
|
+
event = orgevent.Clone()
|
1551
|
+
event.SetEventType(wx.wxEVT_RADIOBOX)
|
1552
|
+
event.SetId(self.Id)
|
1553
|
+
event.SetEventObject(self)
|
1554
|
+
event.Int = self.GetSelection()
|
1555
|
+
wx.PostEvent(self.parent, event)
|
1556
|
+
|
1557
|
+
def SetForegroundColour(self, wc):
|
1558
|
+
for ctrl in self._children + self._labels:
|
1559
|
+
ctrl.SetForegroundColour(wc)
|
1560
|
+
|
1561
|
+
def SetBackgroundColour(self, wc):
|
1562
|
+
for ctrl in self._children + self._labels:
|
1563
|
+
ctrl.SetBackgroundColour(wc)
|
1564
|
+
|
1565
|
+
def SetHelpText(self, help):
|
1566
|
+
self._help = help
|
1567
|
+
|
1568
|
+
def GetHelpText(self):
|
1569
|
+
return self._help
|
1570
|
+
class wxStaticText(wx.StaticText):
|
1571
|
+
def __init__(self, *args, **kwargs):
|
1572
|
+
super().__init__(*args, **kwargs)
|
1573
|
+
set_color_according_to_theme(self, "label_bg", "label_fg")
|
1574
|
+
|
1575
|
+
class wxListBox(wx.ListBox):
|
1576
|
+
def __init__(self, *args, **kwargs):
|
1577
|
+
super().__init__(*args, **kwargs)
|
1578
|
+
set_color_according_to_theme(self, "list_bg", "list_fg")
|
1579
|
+
|
1580
|
+
##############
|
1581
|
+
# GUI KEYSTROKE FUNCTIONS
|
1582
|
+
##############
|
1583
|
+
|
1584
|
+
WX_METAKEYS = [
|
1585
|
+
wx.WXK_START,
|
1586
|
+
wx.WXK_WINDOWS_LEFT,
|
1587
|
+
wx.WXK_WINDOWS_RIGHT,
|
1588
|
+
]
|
1589
|
+
|
1590
|
+
WX_MODIFIERS = {
|
1591
|
+
wx.WXK_CONTROL: "ctrl",
|
1592
|
+
wx.WXK_RAW_CONTROL: "macctl",
|
1593
|
+
wx.WXK_ALT: "alt",
|
1594
|
+
wx.WXK_SHIFT: "shift",
|
1595
|
+
wx.WXK_START: "start",
|
1596
|
+
wx.WXK_WINDOWS_LEFT: "win-left",
|
1597
|
+
wx.WXK_WINDOWS_RIGHT: "win-right",
|
1598
|
+
}
|
1599
|
+
|
1600
|
+
WX_SPECIALKEYS = {
|
1601
|
+
wx.WXK_F1: "f1",
|
1602
|
+
wx.WXK_F2: "f2",
|
1603
|
+
wx.WXK_F3: "f3",
|
1604
|
+
wx.WXK_F4: "f4",
|
1605
|
+
wx.WXK_F5: "f5",
|
1606
|
+
wx.WXK_F6: "f6",
|
1607
|
+
wx.WXK_F7: "f7",
|
1608
|
+
wx.WXK_F8: "f8",
|
1609
|
+
wx.WXK_F9: "f9",
|
1610
|
+
wx.WXK_F10: "f10",
|
1611
|
+
wx.WXK_F11: "f11",
|
1612
|
+
wx.WXK_F12: "f12",
|
1613
|
+
wx.WXK_F13: "f13",
|
1614
|
+
wx.WXK_F14: "f14",
|
1615
|
+
wx.WXK_F15: "f15",
|
1616
|
+
wx.WXK_F16: "f16",
|
1617
|
+
wx.WXK_F17: "f17",
|
1618
|
+
wx.WXK_F18: "f18",
|
1619
|
+
wx.WXK_F19: "f19",
|
1620
|
+
wx.WXK_F20: "f20",
|
1621
|
+
wx.WXK_F21: "f21",
|
1622
|
+
wx.WXK_F22: "f22",
|
1623
|
+
wx.WXK_F23: "f23",
|
1624
|
+
wx.WXK_F24: "f24",
|
1625
|
+
wx.WXK_ADD: "+",
|
1626
|
+
wx.WXK_END: "end",
|
1627
|
+
wx.WXK_NUMPAD0: "numpad0",
|
1628
|
+
wx.WXK_NUMPAD1: "numpad1",
|
1629
|
+
wx.WXK_NUMPAD2: "numpad2",
|
1630
|
+
wx.WXK_NUMPAD3: "numpad3",
|
1631
|
+
wx.WXK_NUMPAD4: "numpad4",
|
1632
|
+
wx.WXK_NUMPAD5: "numpad5",
|
1633
|
+
wx.WXK_NUMPAD6: "numpad6",
|
1634
|
+
wx.WXK_NUMPAD7: "numpad7",
|
1635
|
+
wx.WXK_NUMPAD8: "numpad8",
|
1636
|
+
wx.WXK_NUMPAD9: "numpad9",
|
1637
|
+
wx.WXK_NUMPAD_ADD: "numpad_add",
|
1638
|
+
wx.WXK_NUMPAD_SUBTRACT: "numpad_subtract",
|
1639
|
+
wx.WXK_NUMPAD_MULTIPLY: "numpad_multiply",
|
1640
|
+
wx.WXK_NUMPAD_DIVIDE: "numpad_divide",
|
1641
|
+
wx.WXK_NUMPAD_DECIMAL: "numpad.",
|
1642
|
+
wx.WXK_NUMPAD_ENTER: "numpad_enter",
|
1643
|
+
wx.WXK_NUMPAD_RIGHT: "numpad_right",
|
1644
|
+
wx.WXK_NUMPAD_LEFT: "numpad_left",
|
1645
|
+
wx.WXK_NUMPAD_UP: "numpad_up",
|
1646
|
+
wx.WXK_NUMPAD_DOWN: "numpad_down",
|
1647
|
+
wx.WXK_NUMPAD_DELETE: "numpad_delete",
|
1648
|
+
wx.WXK_NUMPAD_INSERT: "numpad_insert",
|
1649
|
+
wx.WXK_NUMPAD_PAGEUP: "numpad_pgup",
|
1650
|
+
wx.WXK_NUMPAD_PAGEDOWN: "numpad_pgdn",
|
1651
|
+
wx.WXK_NUMPAD_HOME: "numpad_home",
|
1652
|
+
wx.WXK_NUMPAD_END: "numpad_end",
|
1653
|
+
wx.WXK_NUMLOCK: "num_lock",
|
1654
|
+
wx.WXK_SCROLL: "scroll_lock",
|
1655
|
+
wx.WXK_CAPITAL: "caps_lock",
|
1656
|
+
wx.WXK_HOME: "home",
|
1657
|
+
wx.WXK_DOWN: "down",
|
1658
|
+
wx.WXK_UP: "up",
|
1659
|
+
wx.WXK_RIGHT: "right",
|
1660
|
+
wx.WXK_LEFT: "left",
|
1661
|
+
wx.WXK_ESCAPE: "escape",
|
1662
|
+
wx.WXK_BACK: "back",
|
1663
|
+
wx.WXK_PAUSE: "pause",
|
1664
|
+
wx.WXK_PAGEDOWN: "pagedown",
|
1665
|
+
wx.WXK_PAGEUP: "pageup",
|
1666
|
+
wx.WXK_PRINT: "print",
|
1667
|
+
wx.WXK_RETURN: "return",
|
1668
|
+
wx.WXK_SPACE: "space",
|
1669
|
+
wx.WXK_TAB: "tab",
|
1670
|
+
wx.WXK_DELETE: "delete",
|
1671
|
+
wx.WXK_INSERT: "insert",
|
1672
|
+
wx.WXK_SPECIAL1: "special1",
|
1673
|
+
wx.WXK_SPECIAL2: "special2",
|
1674
|
+
wx.WXK_SPECIAL3: "special3",
|
1675
|
+
wx.WXK_SPECIAL4: "special4",
|
1676
|
+
wx.WXK_SPECIAL5: "special5",
|
1677
|
+
wx.WXK_SPECIAL6: "special6",
|
1678
|
+
wx.WXK_SPECIAL7: "special7",
|
1679
|
+
wx.WXK_SPECIAL8: "special8",
|
1680
|
+
wx.WXK_SPECIAL9: "special9",
|
1681
|
+
wx.WXK_SPECIAL10: "special10",
|
1682
|
+
wx.WXK_SPECIAL11: "special11",
|
1683
|
+
wx.WXK_SPECIAL12: "special12",
|
1684
|
+
wx.WXK_SPECIAL13: "special13",
|
1685
|
+
wx.WXK_SPECIAL14: "special14",
|
1686
|
+
wx.WXK_SPECIAL15: "special15",
|
1687
|
+
wx.WXK_SPECIAL16: "special16",
|
1688
|
+
wx.WXK_SPECIAL17: "special17",
|
1689
|
+
wx.WXK_SPECIAL18: "special18",
|
1690
|
+
wx.WXK_SPECIAL19: "special19",
|
1691
|
+
wx.WXK_SPECIAL20: "special20",
|
1692
|
+
wx.WXK_CLEAR: "clear",
|
1693
|
+
wx.WXK_WINDOWS_MENU: "menu",
|
1694
|
+
}
|
1695
|
+
|
1696
|
+
|
1697
|
+
def is_navigation_key(keyvalue):
|
1698
|
+
if keyvalue is None:
|
1699
|
+
return False
|
1700
|
+
if "right" in keyvalue:
|
1701
|
+
return True
|
1702
|
+
if "left" in keyvalue:
|
1703
|
+
return True
|
1704
|
+
if "up" in keyvalue and "pgup" not in keyvalue and "pageup" not in keyvalue:
|
1705
|
+
return True
|
1706
|
+
if "down" in keyvalue and "pagedown" not in keyvalue:
|
1707
|
+
return True
|
1708
|
+
if "tab" in keyvalue:
|
1709
|
+
return True
|
1710
|
+
if "return" in keyvalue:
|
1711
|
+
return True
|
1712
|
+
return False
|
1713
|
+
|
1714
|
+
|
1715
|
+
def get_key_name(event, return_modifier=False):
|
1716
|
+
keyvalue = ""
|
1717
|
+
# https://wxpython.org/Phoenix/docs/html/wx.KeyEvent.html
|
1718
|
+
key = event.GetUnicodeKey()
|
1719
|
+
if key == wx.WXK_NONE:
|
1720
|
+
key = event.GetKeyCode()
|
1721
|
+
if event.RawControlDown() and not event.ControlDown():
|
1722
|
+
keyvalue += "macctl+" # Deliberately not macctrl+
|
1723
|
+
elif event.ControlDown():
|
1724
|
+
keyvalue += "ctrl+"
|
1725
|
+
if event.AltDown() or key == wx.WXK_ALT:
|
1726
|
+
keyvalue += "alt+"
|
1727
|
+
if event.ShiftDown():
|
1728
|
+
keyvalue += "shift+"
|
1729
|
+
if event.MetaDown() or key in WX_METAKEYS:
|
1730
|
+
keyvalue += "meta+"
|
1731
|
+
# if return_modifier and keyvalue: print("key", key, keyvalue)
|
1732
|
+
if key in WX_MODIFIERS:
|
1733
|
+
return keyvalue if return_modifier else None
|
1734
|
+
if key in WX_SPECIALKEYS:
|
1735
|
+
keyvalue += WX_SPECIALKEYS[key]
|
1736
|
+
else:
|
1737
|
+
keyvalue += chr(key)
|
1738
|
+
# print("key", key, keyvalue)
|
1739
|
+
return keyvalue.lower()
|
1740
|
+
|
1741
|
+
|
1742
|
+
def disable_window(window):
|
1743
|
+
for m in window.Children:
|
1744
|
+
if hasattr(m, "Disable"):
|
1745
|
+
m.Disable()
|
1746
|
+
if hasattr(m, "Children"):
|
1747
|
+
disable_window(m)
|
1748
|
+
|
1749
|
+
|
1750
|
+
def set_ctrl_value(ctrl, value):
|
1751
|
+
# Let's try to save the caret position
|
1752
|
+
try:
|
1753
|
+
cursor = ctrl.GetInsertionPoint()
|
1754
|
+
if ctrl.GetValue() != value:
|
1755
|
+
ctrl.SetValue(value)
|
1756
|
+
ctrl.SetInsertionPoint(min(len(value), cursor))
|
1757
|
+
except RuntimeError:
|
1758
|
+
# Control might already have been destroyed
|
1759
|
+
pass
|
1760
|
+
|
1761
|
+
|
1762
|
+
def dip_size(frame, x, y):
|
1763
|
+
# wx.Window.FromDIP was introduced with wxPython 4.1, so not all distros may have this
|
1764
|
+
wxsize = wx.Size(x, y)
|
1765
|
+
try:
|
1766
|
+
dipsize = frame.FromDIP(wxsize)
|
1767
|
+
return dipsize
|
1768
|
+
except AttributeError:
|
1769
|
+
return wxsize
|