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/ribbon.py
CHANGED
@@ -1,2210 +1,2468 @@
|
|
1
|
-
"""
|
2
|
-
The RibbonBar is a scratch control widget. All the buttons are dynamically generated. The contents of those individual
|
3
|
-
ribbon panels are defined by implementing classes.
|
4
|
-
|
5
|
-
The primary method of defining a panel is by calling the `set_buttons()` on the panel.
|
6
|
-
|
7
|
-
control_panel.set_buttons(
|
8
|
-
{
|
9
|
-
"label": _("Red Dot On"),
|
10
|
-
"icon": icons8_flash_on,
|
11
|
-
"tip": _("Turn Redlight On"),
|
12
|
-
"action": lambda v: service("red on\n"),
|
13
|
-
"toggle": {
|
14
|
-
"label": _("Red Dot Off"),
|
15
|
-
"action": lambda v: service("red off\n"),
|
16
|
-
"icon": icons8_flash_off,
|
17
|
-
"signal": "grbl_red_dot",
|
18
|
-
},
|
19
|
-
"rule_enabled": lambda v: has_red_dot_enabled(),
|
20
|
-
}
|
21
|
-
)
|
22
|
-
|
23
|
-
Would, for example, register a button in the control panel the definitions for label, icon, tip, action are all
|
24
|
-
standard with regard to buttons.
|
25
|
-
|
26
|
-
The toggle defines an alternative set of values for the toggle state of the button.
|
27
|
-
|
28
|
-
The multi defines a series of alternative states, and creates a hybrid button with a drop-down to select the state
|
29
|
-
desired.
|
30
|
-
|
31
|
-
Other properties like `rule_enabled` provides a check for whether this button should be enabled or not.
|
32
|
-
|
33
|
-
The `toggle_attr` will permit a toggle to set an attribute on the given `object` which would default to the root
|
34
|
-
context but could need to set a more local object attribute.
|
35
|
-
|
36
|
-
If a `signal` is assigned as an aspect of multi it triggers that option multi-button option.
|
37
|
-
If a `signal` is assigned within the toggle it sets the state of the given toggle. These should be compatible with
|
38
|
-
the signals issued by choice panels.
|
39
|
-
|
40
|
-
The action is a function which is run when the button is pressed.
|
41
|
-
"""
|
42
|
-
|
43
|
-
import copy
|
44
|
-
import
|
45
|
-
import
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
from meerk40t.
|
51
|
-
from meerk40t.
|
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
|
-
self.position[
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
self.
|
96
|
-
self.
|
97
|
-
self.
|
98
|
-
self.
|
99
|
-
self.
|
100
|
-
self.
|
101
|
-
self.
|
102
|
-
self.
|
103
|
-
self.
|
104
|
-
|
105
|
-
|
106
|
-
self.
|
107
|
-
|
108
|
-
|
109
|
-
self.
|
110
|
-
|
111
|
-
|
112
|
-
self.
|
113
|
-
|
114
|
-
|
115
|
-
self.
|
116
|
-
|
117
|
-
|
118
|
-
self.
|
119
|
-
|
120
|
-
|
121
|
-
self.
|
122
|
-
self.client_data = None
|
123
|
-
self.state = 0
|
124
|
-
self.dropdown = None
|
125
|
-
self.overflow = False
|
126
|
-
|
127
|
-
self.state_pressed = None
|
128
|
-
self.state_unpressed = None
|
129
|
-
self.group = None
|
130
|
-
self.toggle_attr = None
|
131
|
-
self.identifier = None
|
132
|
-
self.action = None
|
133
|
-
self.action_right = None
|
134
|
-
self.rule_enabled = None
|
135
|
-
self.rule_visible = None
|
136
|
-
self.min_width = 0
|
137
|
-
self.min_height = 0
|
138
|
-
self.default_width = int(self.max_size / 2)
|
139
|
-
self.icon_size = self.default_width
|
140
|
-
|
141
|
-
self.set_aspect(**description)
|
142
|
-
self.apply_enable_rules()
|
143
|
-
|
144
|
-
def set_aspect(
|
145
|
-
self,
|
146
|
-
label=None,
|
147
|
-
icon=None,
|
148
|
-
tip=None,
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
@param
|
164
|
-
@param
|
165
|
-
@param
|
166
|
-
@param
|
167
|
-
@param
|
168
|
-
@param
|
169
|
-
@param
|
170
|
-
@param
|
171
|
-
@param
|
172
|
-
@param
|
173
|
-
@param
|
174
|
-
@
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
self.default_width =
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
#
|
186
|
-
#
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
self.
|
194
|
-
|
195
|
-
self.
|
196
|
-
|
197
|
-
self.
|
198
|
-
|
199
|
-
self.
|
200
|
-
self.
|
201
|
-
self.
|
202
|
-
self.
|
203
|
-
self.
|
204
|
-
self.
|
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
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
if self.
|
303
|
-
|
304
|
-
|
305
|
-
self.
|
306
|
-
|
307
|
-
self.
|
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
|
-
self.
|
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
|
-
self.
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
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
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
self.
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
self.
|
602
|
-
self.
|
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
|
-
|
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
|
-
self.
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
self.
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
def
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
self.
|
817
|
-
self.
|
818
|
-
self.
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
self.
|
840
|
-
self.
|
841
|
-
self.
|
842
|
-
self.
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
self.
|
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
|
-
|
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
|
-
if
|
958
|
-
self.
|
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
|
-
|
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
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
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
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
self.
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1290
|
-
if self.
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1304
|
-
self.
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
self.
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
self.
|
1335
|
-
|
1336
|
-
|
1337
|
-
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
|
1356
|
-
)
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
|
1386
|
-
|
1387
|
-
|
1388
|
-
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
)
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1400
|
-
|
1401
|
-
if
|
1402
|
-
|
1403
|
-
|
1404
|
-
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1408
|
-
|
1409
|
-
|
1410
|
-
|
1411
|
-
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1443
|
-
|
1444
|
-
|
1445
|
-
|
1446
|
-
|
1447
|
-
|
1448
|
-
|
1449
|
-
)
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
1468
|
-
|
1469
|
-
|
1470
|
-
|
1471
|
-
|
1472
|
-
|
1473
|
-
|
1474
|
-
|
1475
|
-
|
1476
|
-
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1500
|
-
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
|
1505
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
1605
|
-
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
|
1610
|
-
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1618
|
-
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
1622
|
-
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
1630
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
1656
|
-
|
1657
|
-
|
1658
|
-
|
1659
|
-
|
1660
|
-
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
1668
|
-
|
1669
|
-
|
1670
|
-
|
1671
|
-
|
1672
|
-
|
1673
|
-
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1686
|
-
|
1687
|
-
|
1688
|
-
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
|
1696
|
-
|
1697
|
-
|
1698
|
-
|
1699
|
-
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
1716
|
-
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1725
|
-
|
1726
|
-
|
1727
|
-
|
1728
|
-
|
1729
|
-
|
1730
|
-
|
1731
|
-
|
1732
|
-
|
1733
|
-
|
1734
|
-
|
1735
|
-
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
1739
|
-
|
1740
|
-
|
1741
|
-
|
1742
|
-
|
1743
|
-
|
1744
|
-
|
1745
|
-
|
1746
|
-
|
1747
|
-
|
1748
|
-
|
1749
|
-
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
1768
|
-
|
1769
|
-
)
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
1786
|
-
|
1787
|
-
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
1792
|
-
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
1804
|
-
|
1805
|
-
|
1806
|
-
|
1807
|
-
|
1808
|
-
|
1809
|
-
|
1810
|
-
|
1811
|
-
|
1812
|
-
|
1813
|
-
|
1814
|
-
|
1815
|
-
|
1816
|
-
|
1817
|
-
|
1818
|
-
|
1819
|
-
|
1820
|
-
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
1825
|
-
|
1826
|
-
|
1827
|
-
|
1828
|
-
|
1829
|
-
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
1837
|
-
|
1838
|
-
|
1839
|
-
|
1840
|
-
|
1841
|
-
|
1842
|
-
|
1843
|
-
|
1844
|
-
|
1845
|
-
|
1846
|
-
|
1847
|
-
|
1848
|
-
|
1849
|
-
|
1850
|
-
|
1851
|
-
|
1852
|
-
|
1853
|
-
|
1854
|
-
|
1855
|
-
|
1856
|
-
|
1857
|
-
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1861
|
-
|
1862
|
-
|
1863
|
-
|
1864
|
-
|
1865
|
-
|
1866
|
-
|
1867
|
-
|
1868
|
-
|
1869
|
-
|
1870
|
-
|
1871
|
-
|
1872
|
-
|
1873
|
-
|
1874
|
-
|
1875
|
-
|
1876
|
-
|
1877
|
-
|
1878
|
-
|
1879
|
-
|
1880
|
-
|
1881
|
-
|
1882
|
-
|
1883
|
-
|
1884
|
-
|
1885
|
-
|
1886
|
-
|
1887
|
-
|
1888
|
-
|
1889
|
-
|
1890
|
-
|
1891
|
-
|
1892
|
-
|
1893
|
-
|
1894
|
-
|
1895
|
-
|
1896
|
-
|
1897
|
-
|
1898
|
-
|
1899
|
-
|
1900
|
-
|
1901
|
-
|
1902
|
-
|
1903
|
-
|
1904
|
-
|
1905
|
-
|
1906
|
-
|
1907
|
-
|
1908
|
-
|
1909
|
-
|
1910
|
-
|
1911
|
-
|
1912
|
-
|
1913
|
-
|
1914
|
-
|
1915
|
-
|
1916
|
-
|
1917
|
-
|
1918
|
-
|
1919
|
-
|
1920
|
-
|
1921
|
-
|
1922
|
-
|
1923
|
-
|
1924
|
-
|
1925
|
-
|
1926
|
-
|
1927
|
-
|
1928
|
-
|
1929
|
-
|
1930
|
-
|
1931
|
-
|
1932
|
-
|
1933
|
-
|
1934
|
-
|
1935
|
-
|
1936
|
-
|
1937
|
-
x
|
1938
|
-
|
1939
|
-
|
1940
|
-
|
1941
|
-
|
1942
|
-
|
1943
|
-
|
1944
|
-
|
1945
|
-
|
1946
|
-
|
1947
|
-
|
1948
|
-
|
1949
|
-
|
1950
|
-
|
1951
|
-
|
1952
|
-
|
1953
|
-
|
1954
|
-
|
1955
|
-
|
1956
|
-
|
1957
|
-
|
1958
|
-
|
1959
|
-
|
1960
|
-
|
1961
|
-
|
1962
|
-
|
1963
|
-
|
1964
|
-
|
1965
|
-
|
1966
|
-
|
1967
|
-
|
1968
|
-
|
1969
|
-
|
1970
|
-
|
1971
|
-
|
1972
|
-
|
1973
|
-
|
1974
|
-
|
1975
|
-
|
1976
|
-
|
1977
|
-
|
1978
|
-
|
1979
|
-
|
1980
|
-
|
1981
|
-
|
1982
|
-
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
1987
|
-
|
1988
|
-
|
1989
|
-
|
1990
|
-
|
1991
|
-
|
1992
|
-
|
1993
|
-
|
1994
|
-
|
1995
|
-
|
1996
|
-
|
1997
|
-
else:
|
1998
|
-
|
1999
|
-
|
2000
|
-
|
2001
|
-
|
2002
|
-
|
2003
|
-
|
2004
|
-
|
2005
|
-
|
2006
|
-
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2010
|
-
|
2011
|
-
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
2016
|
-
|
2017
|
-
|
2018
|
-
|
2019
|
-
|
2020
|
-
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
2024
|
-
|
2025
|
-
|
2026
|
-
|
2027
|
-
|
2028
|
-
|
2029
|
-
|
2030
|
-
|
2031
|
-
|
2032
|
-
|
2033
|
-
|
2034
|
-
|
2035
|
-
|
2036
|
-
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2040
|
-
|
2041
|
-
|
2042
|
-
|
2043
|
-
|
2044
|
-
|
2045
|
-
|
2046
|
-
|
2047
|
-
|
2048
|
-
|
2049
|
-
|
2050
|
-
|
2051
|
-
|
2052
|
-
|
2053
|
-
|
2054
|
-
|
2055
|
-
|
2056
|
-
|
2057
|
-
|
2058
|
-
|
2059
|
-
|
2060
|
-
|
2061
|
-
|
2062
|
-
|
2063
|
-
|
2064
|
-
|
2065
|
-
|
2066
|
-
|
2067
|
-
|
2068
|
-
|
2069
|
-
|
2070
|
-
|
2071
|
-
|
2072
|
-
|
2073
|
-
|
2074
|
-
|
2075
|
-
|
2076
|
-
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
2080
|
-
|
2081
|
-
|
2082
|
-
|
2083
|
-
|
2084
|
-
|
2085
|
-
|
2086
|
-
|
2087
|
-
|
2088
|
-
|
2089
|
-
|
2090
|
-
|
2091
|
-
|
2092
|
-
|
2093
|
-
|
2094
|
-
|
2095
|
-
|
2096
|
-
|
2097
|
-
|
2098
|
-
|
2099
|
-
|
2100
|
-
|
2101
|
-
|
2102
|
-
|
2103
|
-
|
2104
|
-
|
2105
|
-
if
|
2106
|
-
|
2107
|
-
|
2108
|
-
|
2109
|
-
|
2110
|
-
|
2111
|
-
|
2112
|
-
|
2113
|
-
|
2114
|
-
|
2115
|
-
|
2116
|
-
|
2117
|
-
|
2118
|
-
|
2119
|
-
|
2120
|
-
|
2121
|
-
|
2122
|
-
|
2123
|
-
|
2124
|
-
|
2125
|
-
|
2126
|
-
|
2127
|
-
|
2128
|
-
|
2129
|
-
|
2130
|
-
|
2131
|
-
|
2132
|
-
|
2133
|
-
|
2134
|
-
|
2135
|
-
|
2136
|
-
|
2137
|
-
|
2138
|
-
|
2139
|
-
|
2140
|
-
|
2141
|
-
|
2142
|
-
|
2143
|
-
|
2144
|
-
|
2145
|
-
|
2146
|
-
|
2147
|
-
|
2148
|
-
|
2149
|
-
|
2150
|
-
|
2151
|
-
|
2152
|
-
|
2153
|
-
|
2154
|
-
|
2155
|
-
|
2156
|
-
|
2157
|
-
|
2158
|
-
|
2159
|
-
|
2160
|
-
|
2161
|
-
|
2162
|
-
|
2163
|
-
|
2164
|
-
|
2165
|
-
|
2166
|
-
|
2167
|
-
|
2168
|
-
|
2169
|
-
|
2170
|
-
|
2171
|
-
|
2172
|
-
|
2173
|
-
|
2174
|
-
|
2175
|
-
|
2176
|
-
|
2177
|
-
|
2178
|
-
|
2179
|
-
|
2180
|
-
|
2181
|
-
|
2182
|
-
|
2183
|
-
|
2184
|
-
|
2185
|
-
|
2186
|
-
|
2187
|
-
|
2188
|
-
|
2189
|
-
|
2190
|
-
|
2191
|
-
|
2192
|
-
|
2193
|
-
|
2194
|
-
|
2195
|
-
|
2196
|
-
|
2197
|
-
|
2198
|
-
|
2199
|
-
|
2200
|
-
|
2201
|
-
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2205
|
-
|
2206
|
-
|
2207
|
-
|
2208
|
-
|
2209
|
-
|
2210
|
-
|
1
|
+
"""
|
2
|
+
The RibbonBar is a scratch control widget. All the buttons are dynamically generated. The contents of those individual
|
3
|
+
ribbon panels are defined by implementing classes.
|
4
|
+
|
5
|
+
The primary method of defining a panel is by calling the `set_buttons()` on the panel.
|
6
|
+
|
7
|
+
control_panel.set_buttons(
|
8
|
+
{
|
9
|
+
"label": _("Red Dot On"),
|
10
|
+
"icon": icons8_flash_on,
|
11
|
+
"tip": _("Turn Redlight On"),
|
12
|
+
"action": lambda v: service("red on\n"),
|
13
|
+
"toggle": {
|
14
|
+
"label": _("Red Dot Off"),
|
15
|
+
"action": lambda v: service("red off\n"),
|
16
|
+
"icon": icons8_flash_off,
|
17
|
+
"signal": "grbl_red_dot",
|
18
|
+
},
|
19
|
+
"rule_enabled": lambda v: has_red_dot_enabled(),
|
20
|
+
}
|
21
|
+
)
|
22
|
+
|
23
|
+
Would, for example, register a button in the control panel the definitions for label, icon, tip, action are all
|
24
|
+
standard with regard to buttons.
|
25
|
+
|
26
|
+
The toggle defines an alternative set of values for the toggle state of the button.
|
27
|
+
|
28
|
+
The multi defines a series of alternative states, and creates a hybrid button with a drop-down to select the state
|
29
|
+
desired.
|
30
|
+
|
31
|
+
Other properties like `rule_enabled` provides a check for whether this button should be enabled or not.
|
32
|
+
|
33
|
+
The `toggle_attr` will permit a toggle to set an attribute on the given `object` which would default to the root
|
34
|
+
context but could need to set a more local object attribute.
|
35
|
+
|
36
|
+
If a `signal` is assigned as an aspect of multi it triggers that option multi-button option.
|
37
|
+
If a `signal` is assigned within the toggle it sets the state of the given toggle. These should be compatible with
|
38
|
+
the signals issued by choice panels.
|
39
|
+
|
40
|
+
The action is a function which is run when the button is pressed.
|
41
|
+
"""
|
42
|
+
|
43
|
+
import copy
|
44
|
+
import platform
|
45
|
+
import threading
|
46
|
+
|
47
|
+
import wx
|
48
|
+
|
49
|
+
from meerk40t.gui.icons import STD_ICON_SIZE
|
50
|
+
from meerk40t.kernel import Job
|
51
|
+
from meerk40t.svgelements import Color
|
52
|
+
|
53
|
+
_ = wx.GetTranslation
|
54
|
+
|
55
|
+
COLOR_MODE_DEFAULT = 0
|
56
|
+
COLOR_MODE_COLOR = 1
|
57
|
+
COLOR_MODE_DARK = 2
|
58
|
+
|
59
|
+
|
60
|
+
class DropDown:
|
61
|
+
"""
|
62
|
+
Dropdowns are the triangle click addons that expand the button list to having other functions.
|
63
|
+
|
64
|
+
This primarily stores the position of the given dropdown.
|
65
|
+
"""
|
66
|
+
|
67
|
+
def __init__(self):
|
68
|
+
self.position = None
|
69
|
+
|
70
|
+
def contains(self, pos):
|
71
|
+
"""
|
72
|
+
Is this drop down hit by this position.
|
73
|
+
|
74
|
+
@param pos:
|
75
|
+
@return:
|
76
|
+
"""
|
77
|
+
if self.position is None:
|
78
|
+
return False
|
79
|
+
x, y = pos
|
80
|
+
return (
|
81
|
+
self.position[0] < x < self.position[2]
|
82
|
+
and self.position[1] < y < self.position[3]
|
83
|
+
)
|
84
|
+
|
85
|
+
|
86
|
+
class Button:
|
87
|
+
"""
|
88
|
+
Buttons store most of the relevant data as to how to display the current aspect of the given button. This
|
89
|
+
includes things like tool-tip, the drop-down if needed, whether its in the overflow, the pressed and unpressed
|
90
|
+
aspects of the buttons and enable/disable rules.
|
91
|
+
"""
|
92
|
+
|
93
|
+
def __init__(self, context, parent, button_id, kind, description):
|
94
|
+
self.context = context
|
95
|
+
self.parent = parent
|
96
|
+
self.id = button_id
|
97
|
+
self.kind = kind
|
98
|
+
self.button_dict = description
|
99
|
+
self.enabled = True
|
100
|
+
self.visible = True
|
101
|
+
self._aspects = {}
|
102
|
+
self.key = "original"
|
103
|
+
self.object = None
|
104
|
+
|
105
|
+
self.position = None
|
106
|
+
self.toggle = False
|
107
|
+
|
108
|
+
self.label = None
|
109
|
+
self.icon = None
|
110
|
+
|
111
|
+
self.bitmap = None
|
112
|
+
self.bitmap_disabled = None
|
113
|
+
|
114
|
+
self.min_size = 15
|
115
|
+
self.max_size = 150
|
116
|
+
|
117
|
+
self.available_bitmaps = {}
|
118
|
+
self.available_bitmaps_disabled = {}
|
119
|
+
|
120
|
+
self.tip = None
|
121
|
+
self.help = None
|
122
|
+
self.client_data = None
|
123
|
+
self.state = 0
|
124
|
+
self.dropdown = None
|
125
|
+
self.overflow = False
|
126
|
+
|
127
|
+
self.state_pressed = None
|
128
|
+
self.state_unpressed = None
|
129
|
+
self.group = None
|
130
|
+
self.toggle_attr = None
|
131
|
+
self.identifier = None
|
132
|
+
self.action = None
|
133
|
+
self.action_right = None
|
134
|
+
self.rule_enabled = None
|
135
|
+
self.rule_visible = None
|
136
|
+
self.min_width = 0
|
137
|
+
self.min_height = 0
|
138
|
+
self.default_width = int(self.max_size / 2)
|
139
|
+
self.icon_size = self.default_width
|
140
|
+
|
141
|
+
self.set_aspect(**description)
|
142
|
+
self.apply_enable_rules()
|
143
|
+
|
144
|
+
def set_aspect(
|
145
|
+
self,
|
146
|
+
label=None,
|
147
|
+
icon=None,
|
148
|
+
tip=None,
|
149
|
+
help=None,
|
150
|
+
group=None,
|
151
|
+
toggle_attr=None,
|
152
|
+
identifier=None,
|
153
|
+
action=None,
|
154
|
+
action_right=None,
|
155
|
+
rule_enabled=None,
|
156
|
+
rule_visible=None,
|
157
|
+
object=None,
|
158
|
+
**kwargs,
|
159
|
+
):
|
160
|
+
"""
|
161
|
+
This sets all the different aspects that buttons generally have.
|
162
|
+
|
163
|
+
@param label: button label
|
164
|
+
@param icon: icon used for this button
|
165
|
+
@param tip: tool tip for the button
|
166
|
+
@param help: help information for aspect
|
167
|
+
@param group: Group the button exists in for radio-toggles
|
168
|
+
@param toggle_attr: The attribute that should be changed on toggle.
|
169
|
+
@param identifier: Identifier in the group or toggle
|
170
|
+
@param action: Action taken when button is pressed.
|
171
|
+
@param action_right: Action taken when button is clicked with right mouse button.
|
172
|
+
@param rule_enabled: Rule by which the button is enabled or disabled
|
173
|
+
@param rule_visible: Rule by which the button will be hidden or shown
|
174
|
+
@param object: object which the toggle_attr is an attr applied to
|
175
|
+
@param kwargs:
|
176
|
+
@return:
|
177
|
+
"""
|
178
|
+
self.label = label
|
179
|
+
resize_param = kwargs.get("size")
|
180
|
+
if resize_param is None:
|
181
|
+
self.default_width = int(self.max_size / 2)
|
182
|
+
else:
|
183
|
+
self.default_width = resize_param
|
184
|
+
|
185
|
+
# We need to cast the icon explicitly to PyEmbeddedImage
|
186
|
+
# as otherwise a strange type error is thrown:
|
187
|
+
# TypeError: GetBitmap() got an unexpected keyword argument 'force_darkmode'
|
188
|
+
# Well...
|
189
|
+
from meerk40t.gui.icons import PyEmbeddedImage, VectorIcon
|
190
|
+
|
191
|
+
if not isinstance(icon, VectorIcon):
|
192
|
+
icon = PyEmbeddedImage(icon.data)
|
193
|
+
self.icon = icon
|
194
|
+
|
195
|
+
self.available_bitmaps.clear()
|
196
|
+
self.available_bitmaps_disabled.clear()
|
197
|
+
self.get_bitmaps(self.default_width)
|
198
|
+
|
199
|
+
self.tip = tip
|
200
|
+
self.help = help
|
201
|
+
self.group = group
|
202
|
+
self.toggle_attr = toggle_attr
|
203
|
+
self.identifier = identifier
|
204
|
+
self.action = action
|
205
|
+
self.action_right = action_right
|
206
|
+
self.rule_enabled = rule_enabled
|
207
|
+
self.rule_visible = rule_visible
|
208
|
+
if object is not None:
|
209
|
+
self.object = object
|
210
|
+
else:
|
211
|
+
self.object = self.context
|
212
|
+
if self.kind == "hybrid":
|
213
|
+
self.dropdown = DropDown()
|
214
|
+
self.modified()
|
215
|
+
|
216
|
+
def get_bitmaps(self, point_size):
|
217
|
+
top = self.parent.parent.parent
|
218
|
+
darkm = bool(top.art.color_mode == COLOR_MODE_DARK)
|
219
|
+
if point_size < self.min_size:
|
220
|
+
point_size = self.min_size
|
221
|
+
if point_size > self.max_size:
|
222
|
+
point_size = self.max_size
|
223
|
+
self.icon_size = int(point_size)
|
224
|
+
edge = int(point_size / 25.0) + 1
|
225
|
+
key = str(self.icon_size)
|
226
|
+
if key not in self.available_bitmaps:
|
227
|
+
self.available_bitmaps[key] = self.icon.GetBitmap(
|
228
|
+
resize=self.icon_size,
|
229
|
+
noadjustment=True,
|
230
|
+
force_darkmode=darkm,
|
231
|
+
buffer=edge,
|
232
|
+
)
|
233
|
+
self.available_bitmaps_disabled[key] = self.icon.GetBitmap(
|
234
|
+
resize=self.icon_size,
|
235
|
+
color=Color("grey"),
|
236
|
+
noadjustment=True,
|
237
|
+
buffer=edge,
|
238
|
+
)
|
239
|
+
self.bitmap = self.available_bitmaps[key]
|
240
|
+
self.bitmap_disabled = self.available_bitmaps_disabled[key]
|
241
|
+
|
242
|
+
def _restore_button_aspect(self, key):
|
243
|
+
"""
|
244
|
+
Restores a saved button aspect for the given key. Given a key to the alternative aspect we restore the given
|
245
|
+
aspect.
|
246
|
+
|
247
|
+
@param key: aspect key to set.
|
248
|
+
@return:
|
249
|
+
"""
|
250
|
+
try:
|
251
|
+
alt = self._aspects[key]
|
252
|
+
except KeyError:
|
253
|
+
return
|
254
|
+
self.set_aspect(**alt)
|
255
|
+
self.key = key
|
256
|
+
|
257
|
+
def _store_button_aspect(self, key, **kwargs):
|
258
|
+
"""
|
259
|
+
Stores visual aspects of the buttons within the "_aspects" dictionary.
|
260
|
+
|
261
|
+
This stores the various icons, labels, help, and other properties found on the button.
|
262
|
+
|
263
|
+
@param key: aspects to store.
|
264
|
+
@param kwargs: Additional aspects to implement that are not necessarily currently set on the button.
|
265
|
+
@return:
|
266
|
+
"""
|
267
|
+
self._aspects[key] = {
|
268
|
+
"action": self.action,
|
269
|
+
"action_right": self.action_right,
|
270
|
+
"label": self.label,
|
271
|
+
"tip": self.tip,
|
272
|
+
"help": self.help,
|
273
|
+
"icon": self.icon,
|
274
|
+
"client_data": self.client_data,
|
275
|
+
"rule_enabled": self.rule_enabled,
|
276
|
+
"rule_visible": self.rule_visible,
|
277
|
+
"toggle_attr": self.toggle_attr,
|
278
|
+
"object": self.object,
|
279
|
+
}
|
280
|
+
self._update_button_aspect(key, **kwargs)
|
281
|
+
|
282
|
+
def _update_button_aspect(self, key, **kwargs):
|
283
|
+
"""
|
284
|
+
Directly update the button aspects via the kwargs, aspect dictionary *must* exist.
|
285
|
+
|
286
|
+
@param self:
|
287
|
+
@param key:
|
288
|
+
@param kwargs:
|
289
|
+
@return:
|
290
|
+
"""
|
291
|
+
key_dict = self._aspects[key]
|
292
|
+
for k in kwargs:
|
293
|
+
if kwargs[k] is not None:
|
294
|
+
key_dict[k] = kwargs[k]
|
295
|
+
|
296
|
+
def apply_enable_rules(self):
|
297
|
+
"""
|
298
|
+
Calls rule_enabled() and returns whether the given rule enables the button.
|
299
|
+
|
300
|
+
@return:
|
301
|
+
"""
|
302
|
+
if self.rule_enabled is not None:
|
303
|
+
try:
|
304
|
+
v = self.rule_enabled(0)
|
305
|
+
if v != self.enabled:
|
306
|
+
self.enabled = v
|
307
|
+
self.modified()
|
308
|
+
except (AttributeError, TypeError):
|
309
|
+
pass
|
310
|
+
if self.rule_visible is not None:
|
311
|
+
v = self.rule_visible(0)
|
312
|
+
if v != self.visible:
|
313
|
+
self.visible = v
|
314
|
+
if not self.visible:
|
315
|
+
self.position = None
|
316
|
+
self.modified()
|
317
|
+
else:
|
318
|
+
if not self.visible:
|
319
|
+
self.visible = True
|
320
|
+
self.modified()
|
321
|
+
|
322
|
+
def contains(self, pos):
|
323
|
+
"""
|
324
|
+
Is this button hit by this position.
|
325
|
+
|
326
|
+
@param pos:
|
327
|
+
@return:
|
328
|
+
"""
|
329
|
+
if self.position is None:
|
330
|
+
return False
|
331
|
+
x, y = pos
|
332
|
+
return (
|
333
|
+
self.position[0] < x < self.position[2]
|
334
|
+
and self.position[1] < y < self.position[3]
|
335
|
+
)
|
336
|
+
|
337
|
+
def click(self, event=None, recurse=True):
|
338
|
+
"""
|
339
|
+
Process button click of button at provided button_id
|
340
|
+
|
341
|
+
@return:
|
342
|
+
"""
|
343
|
+
if self.group:
|
344
|
+
# Toggle radio buttons
|
345
|
+
if self.state_pressed is None:
|
346
|
+
# Regular button
|
347
|
+
self.toggle = True
|
348
|
+
else:
|
349
|
+
# Real toggle button
|
350
|
+
self.toggle = not self.toggle
|
351
|
+
if self.toggle: # got toggled
|
352
|
+
button_group = self.parent.group_lookup.get(self.group, [])
|
353
|
+
|
354
|
+
for obutton in button_group:
|
355
|
+
# Untoggle all other buttons in this group.
|
356
|
+
if obutton.group == self.group and obutton.id != self.id:
|
357
|
+
obutton.set_button_toggle(False)
|
358
|
+
else: # got untoggled...
|
359
|
+
# so let's activate the first button of the group (implicitly defined as default...)
|
360
|
+
button_group = self.parent.group_lookup.get(self.group)
|
361
|
+
if button_group and recurse:
|
362
|
+
first_button = button_group[0]
|
363
|
+
first_button.set_button_toggle(True)
|
364
|
+
first_button.click(recurse=False)
|
365
|
+
return
|
366
|
+
if self.action is not None:
|
367
|
+
# We have an action to call.
|
368
|
+
self.action(None)
|
369
|
+
|
370
|
+
if self.state_pressed is None:
|
371
|
+
# Unless button has a pressed state we have finished.
|
372
|
+
return
|
373
|
+
|
374
|
+
# There is a pressed state which requires that we have a toggle.
|
375
|
+
self.toggle = not self.toggle
|
376
|
+
if self.toggle:
|
377
|
+
# Call the toggle_attr restore the pressed state.
|
378
|
+
if self.toggle_attr is not None:
|
379
|
+
setattr(self.object, self.toggle_attr, True)
|
380
|
+
self.context.signal(self.toggle_attr, True, self.object)
|
381
|
+
self._restore_button_aspect(self.state_pressed)
|
382
|
+
else:
|
383
|
+
# Call the toggle_attr restore the unpressed state.
|
384
|
+
if self.toggle_attr is not None:
|
385
|
+
setattr(self.object, self.toggle_attr, False)
|
386
|
+
self.context.signal(self.toggle_attr, False, self.object)
|
387
|
+
self._restore_button_aspect(self.state_unpressed)
|
388
|
+
|
389
|
+
def drop_click(self):
|
390
|
+
"""
|
391
|
+
Drop down of a hybrid button was clicked.
|
392
|
+
|
393
|
+
We make a menu popup and fill it with the data about the multi-button
|
394
|
+
@return:
|
395
|
+
"""
|
396
|
+
if self.toggle:
|
397
|
+
return
|
398
|
+
top = self.parent.parent.parent
|
399
|
+
menu = wx.Menu()
|
400
|
+
item = menu.Append(wx.ID_ANY, "...")
|
401
|
+
item.Enable(False)
|
402
|
+
for v in self.button_dict["multi"]:
|
403
|
+
item = menu.Append(wx.ID_ANY, v.get("label"))
|
404
|
+
tip = v.get("tip")
|
405
|
+
if tip:
|
406
|
+
item.SetHelp(tip)
|
407
|
+
if v.get("identifier") == self.identifier:
|
408
|
+
item.SetItemLabel(v.get("label") + "(*)")
|
409
|
+
icon = v.get("icon")
|
410
|
+
if icon:
|
411
|
+
# There seems to be a bug to display icons in a submenu consistently
|
412
|
+
# print (f"Had a bitmap for {v.get('label')}")
|
413
|
+
item.SetBitmap(icon.GetBitmap(resize=STD_ICON_SIZE / 2, buffer=2))
|
414
|
+
top.Bind(wx.EVT_MENU, self.drop_menu_click(v), id=item.GetId())
|
415
|
+
top.PopupMenu(menu)
|
416
|
+
|
417
|
+
def drop_menu_click(self, v):
|
418
|
+
"""
|
419
|
+
Creates menu_item_click processors for the various menus created for a drop-click
|
420
|
+
|
421
|
+
@param v:
|
422
|
+
@return:
|
423
|
+
"""
|
424
|
+
|
425
|
+
def menu_item_click(event):
|
426
|
+
"""
|
427
|
+
Process menu item click.
|
428
|
+
|
429
|
+
@param event:
|
430
|
+
@return:
|
431
|
+
"""
|
432
|
+
key_id = v.get("identifier")
|
433
|
+
try:
|
434
|
+
setattr(self.object, self.save_id, key_id)
|
435
|
+
except AttributeError:
|
436
|
+
pass
|
437
|
+
self.state_unpressed = key_id
|
438
|
+
self._restore_button_aspect(key_id)
|
439
|
+
# self.ensure_realize()
|
440
|
+
# And now execute it, provided it would be enabled...
|
441
|
+
auto_execute = self.context.setting(bool, "button_multi_menu_execute", True)
|
442
|
+
if auto_execute:
|
443
|
+
is_visible = True
|
444
|
+
is_enabled = True
|
445
|
+
if self.rule_visible:
|
446
|
+
try:
|
447
|
+
is_visible = self.rule_visible(0)
|
448
|
+
except (AttributeError, TypeError):
|
449
|
+
is_visible = False
|
450
|
+
if self.rule_enabled:
|
451
|
+
try:
|
452
|
+
is_enabled = self.rule_enabled(0)
|
453
|
+
except (AttributeError, TypeError):
|
454
|
+
is_enabled = False
|
455
|
+
if is_visible and is_enabled:
|
456
|
+
try:
|
457
|
+
self.action(None)
|
458
|
+
except AttributeError:
|
459
|
+
pass
|
460
|
+
|
461
|
+
return menu_item_click
|
462
|
+
|
463
|
+
def _setup_multi_button(self):
|
464
|
+
"""
|
465
|
+
Store alternative aspects for multi-buttons, load stored previous state.
|
466
|
+
|
467
|
+
@return:
|
468
|
+
"""
|
469
|
+
multi_aspects = self.button_dict["multi"]
|
470
|
+
# This is the key used for the multi button.
|
471
|
+
multi_ident = self.button_dict.get("identifier")
|
472
|
+
self.save_id = multi_ident
|
473
|
+
try:
|
474
|
+
self.object.setting(str, self.save_id, "default")
|
475
|
+
except AttributeError:
|
476
|
+
# This is not a context, we tried.
|
477
|
+
pass
|
478
|
+
initial_value = getattr(self.object, self.save_id, "default")
|
479
|
+
if "signal" in self.button_dict and "attr" in self.button_dict:
|
480
|
+
self._create_generic_signal_for_multi(self.object, self.button_dict.get("attr"), self.button_dict.get("signal"))
|
481
|
+
|
482
|
+
for i, v in enumerate(multi_aspects):
|
483
|
+
# These are values for the outer identifier
|
484
|
+
key = v.get("identifier", i)
|
485
|
+
self._store_button_aspect(key, **v)
|
486
|
+
if "signal" in v:
|
487
|
+
self._create_signal_for_multi(key, v["signal"])
|
488
|
+
|
489
|
+
if key == initial_value:
|
490
|
+
self._restore_button_aspect(key)
|
491
|
+
|
492
|
+
def _create_signal_for_multi(self, key, signal):
|
493
|
+
"""
|
494
|
+
Creates a signal to restore the state of a multi button.
|
495
|
+
|
496
|
+
@param key:
|
497
|
+
@param signal:
|
498
|
+
@return:
|
499
|
+
"""
|
500
|
+
|
501
|
+
def multi_click(origin, *args):
|
502
|
+
self._restore_button_aspect(key)
|
503
|
+
|
504
|
+
self.context.listen(signal, multi_click)
|
505
|
+
self.parent._registered_signals.append((signal, multi_click))
|
506
|
+
|
507
|
+
def _create_generic_signal_for_multi(self, q_object, q_attr, signal):
|
508
|
+
"""
|
509
|
+
Creates a signal to restore the state of a multi button.
|
510
|
+
|
511
|
+
@param key:
|
512
|
+
@param signal:
|
513
|
+
@return:
|
514
|
+
"""
|
515
|
+
|
516
|
+
def multi_click(origin, *args):
|
517
|
+
try:
|
518
|
+
key = getattr(q_object, q_attr)
|
519
|
+
except AttributeError:
|
520
|
+
return
|
521
|
+
self._restore_button_aspect(key)
|
522
|
+
|
523
|
+
self.context.listen(signal, multi_click)
|
524
|
+
self.parent._registered_signals.append((signal, multi_click))
|
525
|
+
|
526
|
+
def _setup_toggle_button(self):
|
527
|
+
"""
|
528
|
+
Store toggle and original aspects for toggle-buttons
|
529
|
+
|
530
|
+
@param self:
|
531
|
+
@return:
|
532
|
+
"""
|
533
|
+
resize_param = self.button_dict.get("size")
|
534
|
+
|
535
|
+
self.state_pressed = "toggle"
|
536
|
+
self.state_unpressed = "original"
|
537
|
+
self._store_button_aspect(self.state_unpressed)
|
538
|
+
|
539
|
+
toggle_button_dict = self.button_dict.get("toggle")
|
540
|
+
key = toggle_button_dict.get("identifier", self.state_pressed)
|
541
|
+
if "signal" in toggle_button_dict:
|
542
|
+
self._create_signal_for_toggle(toggle_button_dict.get("signal"))
|
543
|
+
self._store_button_aspect(key, **toggle_button_dict)
|
544
|
+
|
545
|
+
# Set initial value by identifer and object
|
546
|
+
if self.toggle_attr is not None and getattr(
|
547
|
+
self.object, self.toggle_attr, False
|
548
|
+
):
|
549
|
+
self.set_button_toggle(True)
|
550
|
+
self.modified()
|
551
|
+
|
552
|
+
def _create_signal_for_toggle(self, signal):
|
553
|
+
"""
|
554
|
+
Creates a signal toggle which will listen for the given signal and set the toggle-state to the given set_value
|
555
|
+
|
556
|
+
E.G. If a toggle has a signal called "tracing" and the context.signal("tracing", True) is called this will
|
557
|
+
automatically set the toggle state.
|
558
|
+
|
559
|
+
Note: It will not call any of the associated actions, it will simply set the toggle state.
|
560
|
+
|
561
|
+
@param signal:
|
562
|
+
@return:
|
563
|
+
"""
|
564
|
+
|
565
|
+
def toggle_click(origin, *args):
|
566
|
+
set_value = getattr(self.object, self.toggle_attr) if self.toggle_attr else not self.toggle
|
567
|
+
self.set_button_toggle(set_value)
|
568
|
+
|
569
|
+
self.context.listen(signal, toggle_click)
|
570
|
+
self.parent._registered_signals.append((signal, toggle_click))
|
571
|
+
|
572
|
+
def set_button_toggle(self, toggle_state):
|
573
|
+
"""
|
574
|
+
Set the button's toggle state to the given toggle_state
|
575
|
+
|
576
|
+
@param toggle_state:
|
577
|
+
@return:
|
578
|
+
"""
|
579
|
+
self.toggle = toggle_state
|
580
|
+
if toggle_state:
|
581
|
+
self._restore_button_aspect(self.state_pressed)
|
582
|
+
else:
|
583
|
+
self._restore_button_aspect(self.state_unpressed)
|
584
|
+
|
585
|
+
def modified(self):
|
586
|
+
"""
|
587
|
+
This button was modified and should be redrawn.
|
588
|
+
@return:
|
589
|
+
"""
|
590
|
+
self.parent.modified()
|
591
|
+
|
592
|
+
|
593
|
+
class RibbonPanel:
|
594
|
+
"""
|
595
|
+
Ribbon Panel is a panel of buttons within the page.
|
596
|
+
"""
|
597
|
+
|
598
|
+
def __init__(self, context, parent, id, label, icon):
|
599
|
+
self.context = context
|
600
|
+
self.parent = parent
|
601
|
+
self.id = id
|
602
|
+
self.label = label
|
603
|
+
self.icon = icon
|
604
|
+
|
605
|
+
self._registered_signals = list()
|
606
|
+
self.button_lookup = {}
|
607
|
+
self.group_lookup = {}
|
608
|
+
|
609
|
+
self.buttons = []
|
610
|
+
self.position = None
|
611
|
+
self.available_position = None
|
612
|
+
self._overflow = list()
|
613
|
+
self._overflow_position = None
|
614
|
+
|
615
|
+
def visible_buttons(self):
|
616
|
+
for button in self.buttons:
|
617
|
+
if button.visible:
|
618
|
+
yield button
|
619
|
+
|
620
|
+
@property
|
621
|
+
def visible_button_count(self):
|
622
|
+
pcount = 0
|
623
|
+
for button in self.buttons:
|
624
|
+
if button is not None and button.visible:
|
625
|
+
pcount += 1
|
626
|
+
return pcount
|
627
|
+
|
628
|
+
def clear_buttons(self):
|
629
|
+
self.buttons.clear()
|
630
|
+
self.parent.modified()
|
631
|
+
self._overflow = list()
|
632
|
+
self._overflow_position = None
|
633
|
+
|
634
|
+
def set_buttons(self, new_values):
|
635
|
+
"""
|
636
|
+
Set buttons is the primary button configuration routine. It is responsible for clearing and recreating buttons.
|
637
|
+
|
638
|
+
* The button definition is a dynamically created and stored dictionary.
|
639
|
+
* Buttons are sorted by priority.
|
640
|
+
* Multi buttons get a hybrid type.
|
641
|
+
* Toggle buttons get a toggle type (Unless they are also multi).
|
642
|
+
* Created button objects have attributes assigned to them.
|
643
|
+
* toggle, parent, group, identifier, toggle_identifier, action, right, rule_enabled
|
644
|
+
* Multi-buttons have an identifier attr which is applied to the root context, or given "object".
|
645
|
+
* The identifier is used to set the state of the object, the attr-identifier is set to the value-identifier
|
646
|
+
* Toggle buttons have a toggle_identifier, this is used to set and retrieve the state of the toggle.
|
647
|
+
|
648
|
+
|
649
|
+
@param new_values: dictionary of button values to use.
|
650
|
+
@return:
|
651
|
+
"""
|
652
|
+
# print (f"Setbuttons called for {self.label}")
|
653
|
+
self.modified()
|
654
|
+
self.clear_buttons()
|
655
|
+
button_descriptions = []
|
656
|
+
for desc, name, sname in new_values:
|
657
|
+
button_descriptions.append(desc)
|
658
|
+
|
659
|
+
# Sort buttons by priority
|
660
|
+
def sort_priority(elem):
|
661
|
+
return elem.get("priority", 0)
|
662
|
+
|
663
|
+
button_descriptions.sort(key=sort_priority)
|
664
|
+
|
665
|
+
for desc in button_descriptions:
|
666
|
+
# Every registered button in the updated lookup gets created.
|
667
|
+
b = self._create_button(desc)
|
668
|
+
|
669
|
+
# Store newly created button in the various lookups
|
670
|
+
self.button_lookup[b.id] = b
|
671
|
+
group = desc.get("group")
|
672
|
+
if group is not None:
|
673
|
+
c_group = self.group_lookup.get(group)
|
674
|
+
if c_group is None:
|
675
|
+
c_group = []
|
676
|
+
self.group_lookup[group] = c_group
|
677
|
+
c_group.append(b)
|
678
|
+
|
679
|
+
def _create_button(self, desc):
|
680
|
+
"""
|
681
|
+
Creates a button and places it on the button_bar depending on the required definition.
|
682
|
+
|
683
|
+
@param desc:
|
684
|
+
@return:
|
685
|
+
"""
|
686
|
+
show_tip = not self.context.disable_tool_tips
|
687
|
+
# NewIdRef is only available after 4.1
|
688
|
+
try:
|
689
|
+
new_id = wx.NewIdRef()
|
690
|
+
except AttributeError:
|
691
|
+
new_id = wx.NewId()
|
692
|
+
|
693
|
+
# Create kind of button. Multi buttons are hybrid. Else, regular button or toggle-type
|
694
|
+
if "multi" in desc:
|
695
|
+
# Button is a multi-type button
|
696
|
+
b = Button(
|
697
|
+
self.context, self, button_id=new_id, kind="hybrid", description=desc
|
698
|
+
)
|
699
|
+
self.buttons.append(b)
|
700
|
+
b._setup_multi_button()
|
701
|
+
else:
|
702
|
+
bkind = "normal"
|
703
|
+
if "group" in desc or "toggle" in desc:
|
704
|
+
bkind = "toggle"
|
705
|
+
b = Button(
|
706
|
+
self.context, self, button_id=new_id, kind=bkind, description=desc
|
707
|
+
)
|
708
|
+
self.buttons.append(b)
|
709
|
+
|
710
|
+
if "toggle" in desc:
|
711
|
+
b._setup_toggle_button()
|
712
|
+
return b
|
713
|
+
|
714
|
+
def contains(self, pos):
|
715
|
+
"""
|
716
|
+
Does the given position hit the current panel.
|
717
|
+
|
718
|
+
@param pos:
|
719
|
+
@return:
|
720
|
+
"""
|
721
|
+
if self.position is None:
|
722
|
+
return False
|
723
|
+
x, y = pos
|
724
|
+
return (
|
725
|
+
self.position[0] < x < self.position[2]
|
726
|
+
and self.position[1] < y < self.position[3]
|
727
|
+
)
|
728
|
+
|
729
|
+
def modified(self):
|
730
|
+
"""
|
731
|
+
Modified call parent page.
|
732
|
+
@return:
|
733
|
+
"""
|
734
|
+
self.parent.modified()
|
735
|
+
|
736
|
+
def overflow_click(self):
|
737
|
+
"""
|
738
|
+
Click of overflow. Overflow exists if some icons are not able to be shown.
|
739
|
+
|
740
|
+
We make a menu popup and fill it with the overflow commands.
|
741
|
+
|
742
|
+
@return:
|
743
|
+
"""
|
744
|
+
# print (f"Overflow click called for {self.label}")
|
745
|
+
menu = wx.Menu()
|
746
|
+
top = self.parent.parent # .parent
|
747
|
+
for v in self._overflow:
|
748
|
+
item = menu.Append(wx.ID_ANY, v.label)
|
749
|
+
item.Enable(v.enabled)
|
750
|
+
if callable(v.tip):
|
751
|
+
item.SetHelp(v.tip())
|
752
|
+
else:
|
753
|
+
item.SetHelp(v.tip)
|
754
|
+
if v.icon:
|
755
|
+
item.SetBitmap(v.icon.GetBitmap(resize=STD_ICON_SIZE / 2, buffer=2))
|
756
|
+
top.Bind(wx.EVT_MENU, v.click, id=item.Id)
|
757
|
+
top.PopupMenu(menu)
|
758
|
+
|
759
|
+
|
760
|
+
class RibbonPage:
|
761
|
+
"""
|
762
|
+
Ribbon Page is a page of buttons this is the series of ribbon panels as triggered by the different tags.
|
763
|
+
"""
|
764
|
+
|
765
|
+
def __init__(self, context, parent, id, label, icon, reference):
|
766
|
+
self.context = context
|
767
|
+
self.parent = parent
|
768
|
+
self.id = id
|
769
|
+
self.label = label
|
770
|
+
self.icon = icon
|
771
|
+
self.panels = []
|
772
|
+
self.position = None
|
773
|
+
self.tab_position = None
|
774
|
+
self.visible = True
|
775
|
+
self.reference = reference
|
776
|
+
|
777
|
+
def add_panel(self, panel, ref):
|
778
|
+
"""
|
779
|
+
Adds a panel to this page.
|
780
|
+
@param panel:
|
781
|
+
@param ref:
|
782
|
+
@return:
|
783
|
+
"""
|
784
|
+
self.panels.append(panel)
|
785
|
+
if ref is not None:
|
786
|
+
# print(f"Setattr in add_panel: {ref} = {panel}")
|
787
|
+
setattr(self, ref, panel)
|
788
|
+
|
789
|
+
def contains(self, pos):
|
790
|
+
"""
|
791
|
+
Does this position hit the tab position of this page.
|
792
|
+
@param pos:
|
793
|
+
@return:
|
794
|
+
"""
|
795
|
+
if self.tab_position is None:
|
796
|
+
return False
|
797
|
+
x, y = pos
|
798
|
+
return (
|
799
|
+
self.tab_position[0] < x < self.tab_position[2]
|
800
|
+
and self.tab_position[1] < y < self.tab_position[3]
|
801
|
+
)
|
802
|
+
|
803
|
+
def modified(self):
|
804
|
+
"""
|
805
|
+
Call modified to parent RibbonBarPanel.
|
806
|
+
|
807
|
+
@return:
|
808
|
+
"""
|
809
|
+
self.parent.modified()
|
810
|
+
|
811
|
+
|
812
|
+
class RibbonBarPanel(wx.Control):
|
813
|
+
def __init__(self, parent, id, context=None, pane=None, **kwds):
|
814
|
+
super().__init__(parent, id, **kwds)
|
815
|
+
self.context = context
|
816
|
+
self.pages = []
|
817
|
+
self.pane = pane
|
818
|
+
jobname = f"realize_ribbon_bar_{self.GetId()}"
|
819
|
+
# print (f"Requesting job with name: '{jobname}'")
|
820
|
+
self._redraw_job = Job(
|
821
|
+
process=self._paint_main_on_buffer,
|
822
|
+
job_name=jobname,
|
823
|
+
interval=0.1,
|
824
|
+
times=1,
|
825
|
+
run_main=True,
|
826
|
+
)
|
827
|
+
# Layout properties.
|
828
|
+
self.art = Art(self)
|
829
|
+
|
830
|
+
# Define Ribbon.
|
831
|
+
self._redraw_lock = threading.Lock()
|
832
|
+
self._paint_dirty = True
|
833
|
+
self._layout_dirty = True
|
834
|
+
self._ribbon_buffer = None
|
835
|
+
|
836
|
+
# self._overflow = list()
|
837
|
+
# self._overflow_position = None
|
838
|
+
|
839
|
+
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
|
840
|
+
self.Bind(wx.EVT_ERASE_BACKGROUND, self.on_erase_background)
|
841
|
+
self.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter)
|
842
|
+
self.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave)
|
843
|
+
self.Bind(wx.EVT_MOTION, self.on_mouse_move)
|
844
|
+
self.Bind(wx.EVT_PAINT, self.on_paint)
|
845
|
+
self.Bind(wx.EVT_SIZE, self.on_size)
|
846
|
+
|
847
|
+
self.Bind(wx.EVT_LEFT_DOWN, self.on_click)
|
848
|
+
self.Bind(wx.EVT_RIGHT_UP, self.on_click_right)
|
849
|
+
|
850
|
+
# Tooltip logic - as we do have a single control,
|
851
|
+
# this will prevent wxPython from resetting the timer
|
852
|
+
# when hovering to a different button
|
853
|
+
self._tooltip = ""
|
854
|
+
jobname = f"tooltip_ribbon_bar_{self.GetId()}"
|
855
|
+
# print (f"Requesting job with name: '{jobname}'")
|
856
|
+
tooltip_delay = self.context.setting(int, "tooltip_delay", 100)
|
857
|
+
interval = tooltip_delay / 1000.0
|
858
|
+
self._tooltip_job = Job(
|
859
|
+
process=self._exec_tooltip_job,
|
860
|
+
job_name=jobname,
|
861
|
+
interval=interval,
|
862
|
+
times=1,
|
863
|
+
run_main=True,
|
864
|
+
)
|
865
|
+
|
866
|
+
# Preparation for individual page visibility
|
867
|
+
def visible_pages(self):
|
868
|
+
count = 0
|
869
|
+
for p in self.pages:
|
870
|
+
if p.visible:
|
871
|
+
count += 1
|
872
|
+
return count
|
873
|
+
|
874
|
+
def first_page(self):
|
875
|
+
# returns the first visible page
|
876
|
+
for p in self.pages:
|
877
|
+
if p.visible:
|
878
|
+
return p
|
879
|
+
return None
|
880
|
+
|
881
|
+
def modified(self):
|
882
|
+
"""
|
883
|
+
if modified then we flag the layout and paint as dirty and call for a refresh of the ribbonbar.
|
884
|
+
@return:
|
885
|
+
"""
|
886
|
+
# (f"Modified called for RibbonBar with {self.visible_pages()} pages")
|
887
|
+
self._paint_dirty = True
|
888
|
+
self._layout_dirty = True
|
889
|
+
self.context.schedule(self._redraw_job)
|
890
|
+
|
891
|
+
def redrawn(self):
|
892
|
+
"""
|
893
|
+
if refresh needed then we flag the paint as dirty and call for a refresh of the ribbonbar.
|
894
|
+
@return:
|
895
|
+
"""
|
896
|
+
self._paint_dirty = True
|
897
|
+
self.context.schedule(self._redraw_job)
|
898
|
+
|
899
|
+
def on_size(self, event: wx.SizeEvent):
|
900
|
+
self._set_buffer()
|
901
|
+
self.modified()
|
902
|
+
|
903
|
+
def on_erase_background(self, event):
|
904
|
+
pass
|
905
|
+
|
906
|
+
def on_mouse_enter(self, event: wx.MouseEvent):
|
907
|
+
pass
|
908
|
+
|
909
|
+
def on_mouse_leave(self, event: wx.MouseEvent):
|
910
|
+
self.art.hover_tab = None
|
911
|
+
self.art.hover_button = None
|
912
|
+
self.art.hover_dropdown = None
|
913
|
+
self.redrawn()
|
914
|
+
|
915
|
+
def stop_tooltip_job(self):
|
916
|
+
self._tooltip_job.cancel()
|
917
|
+
|
918
|
+
def start_tooltip_job(self):
|
919
|
+
# print (f"Schedule a job with {self._tooltip_job.interval:.2f}sec")
|
920
|
+
self.context.schedule(self._tooltip_job)
|
921
|
+
|
922
|
+
def _exec_tooltip_job(self):
|
923
|
+
# print (f"Executed with {self._tooltip}")
|
924
|
+
try:
|
925
|
+
super().SetToolTip(self._tooltip)
|
926
|
+
except RuntimeError:
|
927
|
+
# Could happen on a shutdown...
|
928
|
+
return
|
929
|
+
|
930
|
+
def SetToolTip(self, message):
|
931
|
+
if callable(message):
|
932
|
+
self._tooltip = message()
|
933
|
+
else:
|
934
|
+
self._tooltip = message
|
935
|
+
if message == "":
|
936
|
+
self.stop_tooltip_job()
|
937
|
+
super().SetToolTip(message)
|
938
|
+
else:
|
939
|
+
# we restart the job and delete the tooltip in the meantime
|
940
|
+
super().SetToolTip("")
|
941
|
+
self.start_tooltip_job()
|
942
|
+
|
943
|
+
def _check_hover_dropdown(self, drop, pos):
|
944
|
+
if drop is not None and not drop.contains(pos):
|
945
|
+
drop = None
|
946
|
+
if drop is not self.art.hover_dropdown:
|
947
|
+
self.art.hover_dropdown = drop
|
948
|
+
self.redrawn()
|
949
|
+
|
950
|
+
def _check_hover_button(self, pos):
|
951
|
+
hover = self._overflow_at_position(pos)
|
952
|
+
if hover is not None:
|
953
|
+
self.SetToolTip(_("There is more to see - click to display"))
|
954
|
+
self.SetHelpText("")
|
955
|
+
return
|
956
|
+
hover = self._button_at_position(pos)
|
957
|
+
if hover is not None:
|
958
|
+
self._check_hover_dropdown(hover.dropdown, pos)
|
959
|
+
if hover is not None and hover is self.art.hover_button:
|
960
|
+
return
|
961
|
+
self.art.hover_button = hover
|
962
|
+
if hover is None:
|
963
|
+
hover = self._button_at_position(pos, use_all=True)
|
964
|
+
if hover is not None:
|
965
|
+
self.SetToolTip(hover.tip)
|
966
|
+
hhelp = hover.help
|
967
|
+
if hhelp is None:
|
968
|
+
hhelp = ""
|
969
|
+
self.SetHelpText(hhelp)
|
970
|
+
else:
|
971
|
+
self.SetToolTip("")
|
972
|
+
self.SetHelpText("")
|
973
|
+
|
974
|
+
self.redrawn()
|
975
|
+
|
976
|
+
def _check_hover_tab(self, pos):
|
977
|
+
hover = self._pagetab_at_position(pos)
|
978
|
+
if hover is not self.art.hover_tab:
|
979
|
+
self.art.hover_tab = hover
|
980
|
+
self.redrawn()
|
981
|
+
|
982
|
+
def on_mouse_move(self, event: wx.MouseEvent):
|
983
|
+
pos = event.Position
|
984
|
+
self._check_hover_button(pos)
|
985
|
+
self._check_hover_tab(pos)
|
986
|
+
|
987
|
+
def on_paint(self, event: wx.PaintEvent):
|
988
|
+
"""
|
989
|
+
Ribbonbar paint event calls the paints the bitmap self._ribbon_buffer. If self._ribbon_buffer does not exist
|
990
|
+
initially it is created in the self.scene.update_buffer_ui_thread() call.
|
991
|
+
"""
|
992
|
+
if self._paint_dirty:
|
993
|
+
self._paint_main_on_buffer()
|
994
|
+
|
995
|
+
try:
|
996
|
+
wx.BufferedPaintDC(self, self._ribbon_buffer)
|
997
|
+
except (RuntimeError, AssertionError, TypeError):
|
998
|
+
pass
|
999
|
+
|
1000
|
+
def _paint_main_on_buffer(self):
|
1001
|
+
"""Performs redrawing of the data in the UI thread."""
|
1002
|
+
# print (f"Redraw job started for RibbonBar with {self.visible_pages()} pages")
|
1003
|
+
if self._redraw_lock.acquire(timeout=0.2):
|
1004
|
+
try:
|
1005
|
+
buf = self._set_buffer()
|
1006
|
+
dc = wx.MemoryDC()
|
1007
|
+
dc.SelectObject(buf)
|
1008
|
+
if self._layout_dirty:
|
1009
|
+
self.art.layout(dc, self)
|
1010
|
+
self._layout_dirty = False
|
1011
|
+
self.art.paint_main(dc, self)
|
1012
|
+
dc.SelectObject(wx.NullBitmap)
|
1013
|
+
del dc
|
1014
|
+
self._paint_dirty = False
|
1015
|
+
except (RuntimeError, AssertionError):
|
1016
|
+
pass
|
1017
|
+
# Shutdown error
|
1018
|
+
finally:
|
1019
|
+
self._redraw_lock.release()
|
1020
|
+
try:
|
1021
|
+
self.Refresh() # Paint buffer on screen.
|
1022
|
+
except RuntimeError:
|
1023
|
+
# Shutdown error
|
1024
|
+
pass
|
1025
|
+
|
1026
|
+
def prefer_horizontal(self):
|
1027
|
+
result = None
|
1028
|
+
if self.pane is not None:
|
1029
|
+
try:
|
1030
|
+
pane = self.pane.manager.GetPane(self.pane.name)
|
1031
|
+
if pane.IsDocked():
|
1032
|
+
# if self.pane.name == "tools":
|
1033
|
+
# print (
|
1034
|
+
# f"Pane: {pane.name}: {pane.dock_direction}, State: {pane.IsOk()}/{pane.IsDocked()}/{pane.IsFloating()}"
|
1035
|
+
# )
|
1036
|
+
if pane.dock_direction in (1, 3):
|
1037
|
+
# Horizontal
|
1038
|
+
result = True
|
1039
|
+
elif pane.dock_direction in (2, 4):
|
1040
|
+
# Vertical
|
1041
|
+
result = False
|
1042
|
+
# else:
|
1043
|
+
# if self.pane.name == "tools":
|
1044
|
+
# print (
|
1045
|
+
# f"Pane: {pane.name}: {pane.IsFloating()}"
|
1046
|
+
# )
|
1047
|
+
except (AttributeError, RuntimeError):
|
1048
|
+
# Unknown error occurred
|
1049
|
+
pass
|
1050
|
+
|
1051
|
+
if result is None:
|
1052
|
+
# Floating...
|
1053
|
+
width, height = self.ClientSize
|
1054
|
+
if width <= 0:
|
1055
|
+
width = 1
|
1056
|
+
if height <= 0:
|
1057
|
+
height = 1
|
1058
|
+
result = bool(width >= height)
|
1059
|
+
|
1060
|
+
return result
|
1061
|
+
|
1062
|
+
def _set_buffer(self):
|
1063
|
+
"""
|
1064
|
+
Set the value for the self._Buffer bitmap equal to the panel's clientSize.
|
1065
|
+
"""
|
1066
|
+
if (
|
1067
|
+
self._ribbon_buffer is None
|
1068
|
+
or self._ribbon_buffer.GetSize() != self.ClientSize
|
1069
|
+
or not self._ribbon_buffer.IsOk()
|
1070
|
+
):
|
1071
|
+
width, height = self.ClientSize
|
1072
|
+
if width <= 0:
|
1073
|
+
width = 1
|
1074
|
+
if height <= 0:
|
1075
|
+
height = 1
|
1076
|
+
self._ribbon_buffer = wx.Bitmap(width, height)
|
1077
|
+
return self._ribbon_buffer
|
1078
|
+
|
1079
|
+
def toggle_show_labels(self, v):
|
1080
|
+
self.art.show_labels = v
|
1081
|
+
self.modified()
|
1082
|
+
|
1083
|
+
def _overflow_at_position(self, pos):
|
1084
|
+
for page in self.pages:
|
1085
|
+
if page is not self.art.current_page or not page.visible:
|
1086
|
+
continue
|
1087
|
+
for panel in page.panels:
|
1088
|
+
x, y = pos
|
1089
|
+
# print (f"Checking: {panel.label}: ({x},{y}) in ({panel._overflow_position})")
|
1090
|
+
if panel._overflow_position is None:
|
1091
|
+
continue
|
1092
|
+
if (
|
1093
|
+
panel._overflow_position[0] < x < panel._overflow_position[2]
|
1094
|
+
and panel._overflow_position[1] < y < panel._overflow_position[3]
|
1095
|
+
):
|
1096
|
+
# print (f"Found a panel: {panel.label}")
|
1097
|
+
return panel
|
1098
|
+
return None
|
1099
|
+
|
1100
|
+
def _button_at_position(self, pos, use_all=False):
|
1101
|
+
"""
|
1102
|
+
Find the button at the given position, so long as that button is enabled.
|
1103
|
+
|
1104
|
+
@param pos:
|
1105
|
+
@return:
|
1106
|
+
"""
|
1107
|
+
for page in self.pages:
|
1108
|
+
if page is not self.art.current_page or not page.visible:
|
1109
|
+
continue
|
1110
|
+
for panel in page.panels:
|
1111
|
+
for button in panel.visible_buttons():
|
1112
|
+
if (
|
1113
|
+
button.contains(pos)
|
1114
|
+
and (button.enabled or use_all)
|
1115
|
+
and not button.overflow
|
1116
|
+
):
|
1117
|
+
return button
|
1118
|
+
return None
|
1119
|
+
|
1120
|
+
def _pagetab_at_position(self, pos):
|
1121
|
+
"""
|
1122
|
+
Find the page tab at the given position.
|
1123
|
+
|
1124
|
+
@param pos:
|
1125
|
+
@return:
|
1126
|
+
"""
|
1127
|
+
for page in self.pages:
|
1128
|
+
if page.visible and page.contains(pos):
|
1129
|
+
return page
|
1130
|
+
return None
|
1131
|
+
|
1132
|
+
def on_click_right(self, event: wx.MouseEvent):
|
1133
|
+
"""
|
1134
|
+
Handles the ``wx.EVT_RIGHT_DOWN`` event
|
1135
|
+
:param event: a :class:`MouseEvent` event to be processed.
|
1136
|
+
"""
|
1137
|
+
pos = event.Position
|
1138
|
+
button = self._button_at_position(pos)
|
1139
|
+
if button is not None:
|
1140
|
+
action = button.action_right
|
1141
|
+
if action:
|
1142
|
+
action(event)
|
1143
|
+
else:
|
1144
|
+
# Click on background, off menu to edit and set colors
|
1145
|
+
def set_color(newmode):
|
1146
|
+
self.context.root.ribbon_color = newmode
|
1147
|
+
# Force refresh
|
1148
|
+
self.context.signal("ribbon_recreate", None)
|
1149
|
+
|
1150
|
+
top = self # .parent
|
1151
|
+
c_mode = self.context.root.setting(int, "ribbon_color", COLOR_MODE_DEFAULT)
|
1152
|
+
menu = wx.Menu()
|
1153
|
+
item = menu.Append(wx.ID_ANY, _("Colorscheme"))
|
1154
|
+
item.Enable(False)
|
1155
|
+
item = menu.Append(wx.ID_ANY, _("System Default"), "", wx.ITEM_CHECK)
|
1156
|
+
item.Check(bool(c_mode == COLOR_MODE_DEFAULT))
|
1157
|
+
top.Bind(
|
1158
|
+
wx.EVT_MENU, lambda v: set_color(COLOR_MODE_DEFAULT), id=item.GetId()
|
1159
|
+
)
|
1160
|
+
item = menu.Append(wx.ID_ANY, _("Colored"), "", wx.ITEM_CHECK)
|
1161
|
+
item.Check(bool(c_mode == COLOR_MODE_COLOR))
|
1162
|
+
top.Bind(
|
1163
|
+
wx.EVT_MENU, lambda v: set_color(COLOR_MODE_COLOR), id=item.GetId()
|
1164
|
+
)
|
1165
|
+
item = menu.Append(wx.ID_ANY, _("Black"), "", wx.ITEM_CHECK)
|
1166
|
+
item.Check(bool(c_mode == COLOR_MODE_DARK))
|
1167
|
+
top.Bind(wx.EVT_MENU, lambda v: set_color(COLOR_MODE_DARK), id=item.GetId())
|
1168
|
+
item = menu.AppendSeparator()
|
1169
|
+
haslabel = self.art.show_labels
|
1170
|
+
item = menu.Append(wx.ID_ANY, _("Show Labels"), "", wx.ITEM_CHECK)
|
1171
|
+
if not getattr(self, "allow_labels", True):
|
1172
|
+
item.Enable(False)
|
1173
|
+
item.Check(haslabel)
|
1174
|
+
top.Bind(
|
1175
|
+
wx.EVT_MENU,
|
1176
|
+
lambda v: self.toggle_show_labels(not haslabel),
|
1177
|
+
id=item.GetId(),
|
1178
|
+
)
|
1179
|
+
item = menu.AppendSeparator()
|
1180
|
+
item = menu.Append(wx.ID_ANY, _("Customize Toolbars"))
|
1181
|
+
|
1182
|
+
def show_pref():
|
1183
|
+
self.context("window open Preferences\n")
|
1184
|
+
self.context.signal("preferences", "ribbon")
|
1185
|
+
|
1186
|
+
top.Bind(
|
1187
|
+
wx.EVT_MENU,
|
1188
|
+
lambda v: show_pref(),
|
1189
|
+
id=item.GetId(),
|
1190
|
+
)
|
1191
|
+
top.PopupMenu(menu)
|
1192
|
+
|
1193
|
+
def on_click(self, event: wx.MouseEvent):
|
1194
|
+
"""
|
1195
|
+
The ribbon bar was clicked. We check the various parts of the ribbonbar that could have been clicked in the
|
1196
|
+
preferred click order. Overflow, pagetab, drop-down, button.
|
1197
|
+
@param event:
|
1198
|
+
@return:
|
1199
|
+
"""
|
1200
|
+
pos = event.Position
|
1201
|
+
|
1202
|
+
page = self._pagetab_at_position(pos)
|
1203
|
+
overflow = self._overflow_at_position(pos)
|
1204
|
+
if overflow is not None:
|
1205
|
+
overflow.overflow_click()
|
1206
|
+
self.modified()
|
1207
|
+
return
|
1208
|
+
|
1209
|
+
button = self._button_at_position(pos)
|
1210
|
+
if page is not None and button is None:
|
1211
|
+
self.art.current_page = page
|
1212
|
+
self.apply_enable_rules()
|
1213
|
+
self.modified()
|
1214
|
+
return
|
1215
|
+
if button is None:
|
1216
|
+
return
|
1217
|
+
drop = button.dropdown
|
1218
|
+
if drop is not None and drop.contains(pos):
|
1219
|
+
button.drop_click()
|
1220
|
+
self.modified()
|
1221
|
+
return
|
1222
|
+
button.click()
|
1223
|
+
self.modified()
|
1224
|
+
|
1225
|
+
def _all_buttons(self):
|
1226
|
+
"""
|
1227
|
+
Helper to cycle through all buttons in the panels that are currently visible.
|
1228
|
+
@return:
|
1229
|
+
"""
|
1230
|
+
for page in self.pages:
|
1231
|
+
if page is not self.art.current_page or not page.visible:
|
1232
|
+
continue
|
1233
|
+
for panel in page.panels:
|
1234
|
+
for button in panel.buttons:
|
1235
|
+
yield button
|
1236
|
+
|
1237
|
+
def apply_enable_rules(self):
|
1238
|
+
"""
|
1239
|
+
Applies all enable rules for all buttons that are currently seen.
|
1240
|
+
@return:
|
1241
|
+
"""
|
1242
|
+
for button in self._all_buttons():
|
1243
|
+
button.apply_enable_rules()
|
1244
|
+
|
1245
|
+
def add_page(self, ref, id, label, icon):
|
1246
|
+
"""
|
1247
|
+
Add a page to the ribbonbar.
|
1248
|
+
@param ref:
|
1249
|
+
@param id:
|
1250
|
+
@param label:
|
1251
|
+
@param icon:
|
1252
|
+
@return:
|
1253
|
+
"""
|
1254
|
+
page = RibbonPage(
|
1255
|
+
self.context,
|
1256
|
+
self,
|
1257
|
+
id,
|
1258
|
+
label,
|
1259
|
+
icon,
|
1260
|
+
ref,
|
1261
|
+
)
|
1262
|
+
if ref is not None:
|
1263
|
+
# print(f"Setattr in add_page: {ref} = {page}")
|
1264
|
+
setattr(self, ref, page)
|
1265
|
+
if self.art.current_page is None:
|
1266
|
+
self.art.current_page = page
|
1267
|
+
self.pages.append(page)
|
1268
|
+
self._layout_dirty = True
|
1269
|
+
return page
|
1270
|
+
|
1271
|
+
def remove_page(self, pageid):
|
1272
|
+
"""
|
1273
|
+
Remove a page from the ribbonbar.
|
1274
|
+
@param pageid:
|
1275
|
+
@return:
|
1276
|
+
"""
|
1277
|
+
for pidx, page in enumerate(self.pages):
|
1278
|
+
if page.id == pageid:
|
1279
|
+
if self.art.current_page is page:
|
1280
|
+
self.art.current_page = None
|
1281
|
+
for panel in page.panels:
|
1282
|
+
panel.clear_buttons()
|
1283
|
+
del panel
|
1284
|
+
self.pages.pop(pidx)
|
1285
|
+
break
|
1286
|
+
|
1287
|
+
self._layout_dirty = True
|
1288
|
+
|
1289
|
+
def validate_current_page(self):
|
1290
|
+
if self.art.current_page is None or not self.art.current_page.visible:
|
1291
|
+
self.art.current_page = self.first_page()
|
1292
|
+
|
1293
|
+
def add_panel(self, ref, parent: RibbonPage, id, label, icon):
|
1294
|
+
"""
|
1295
|
+
Add a panel to the ribbon bar. Parent must be a page.
|
1296
|
+
@param ref:
|
1297
|
+
@param parent:
|
1298
|
+
@param id:
|
1299
|
+
@param label:
|
1300
|
+
@param icon:
|
1301
|
+
@return:
|
1302
|
+
"""
|
1303
|
+
panel = RibbonPanel(
|
1304
|
+
self.context,
|
1305
|
+
parent=parent,
|
1306
|
+
id=id,
|
1307
|
+
label=label,
|
1308
|
+
icon=icon,
|
1309
|
+
)
|
1310
|
+
parent.add_panel(panel, ref)
|
1311
|
+
self._layout_dirty = True
|
1312
|
+
return panel
|
1313
|
+
|
1314
|
+
|
1315
|
+
class Art:
|
1316
|
+
def __init__(self, parent):
|
1317
|
+
self.RIBBON_ORIENTATION_AUTO = 0
|
1318
|
+
self.RIBBON_ORIENTATION_HORIZONTAL = 1
|
1319
|
+
self.RIBBON_ORIENTATION_VERTICAL = 2
|
1320
|
+
self.orientation = self.RIBBON_ORIENTATION_AUTO
|
1321
|
+
self.parent = parent
|
1322
|
+
self.between_button_buffer = 3
|
1323
|
+
self.panel_button_buffer = 3
|
1324
|
+
self.page_panel_buffer = 3
|
1325
|
+
self.between_panel_buffer = 5
|
1326
|
+
|
1327
|
+
self.tab_width = 70
|
1328
|
+
self.tab_height = 20
|
1329
|
+
self.tab_tab_buffer = 10
|
1330
|
+
self.tab_initial_buffer = 30
|
1331
|
+
self.tab_text_buffer = 2
|
1332
|
+
self.edge_page_buffer = 4
|
1333
|
+
self.rounded_radius = 3
|
1334
|
+
self.font_sizes = {}
|
1335
|
+
|
1336
|
+
self.bitmap_text_buffer = 5
|
1337
|
+
self.dropdown_height = 20
|
1338
|
+
self.overflow_width = 20
|
1339
|
+
self.text_dropdown_buffer = 7
|
1340
|
+
self.show_labels = True
|
1341
|
+
|
1342
|
+
self.establish_colors()
|
1343
|
+
|
1344
|
+
self.current_page = None
|
1345
|
+
self.hover_tab = None
|
1346
|
+
self.hover_button = None
|
1347
|
+
self.hover_dropdown = None
|
1348
|
+
|
1349
|
+
def establish_colors(self):
|
1350
|
+
self.text_color = copy.copy(
|
1351
|
+
wx.SystemSettings().GetColour(wx.SYS_COLOUR_BTNTEXT)
|
1352
|
+
)
|
1353
|
+
self.text_color_inactive = copy.copy(self.text_color).ChangeLightness(50)
|
1354
|
+
self.text_color_disabled = wx.Colour("Dark Grey")
|
1355
|
+
self.black_color = copy.copy(
|
1356
|
+
wx.SystemSettings().GetColour(wx.SYS_COLOUR_BTNTEXT)
|
1357
|
+
)
|
1358
|
+
|
1359
|
+
self.button_face_hover = copy.copy(
|
1360
|
+
wx.SystemSettings().GetColour(wx.SYS_COLOUR_HIGHLIGHT)
|
1361
|
+
).ChangeLightness(150)
|
1362
|
+
# self.button_face_hover = copy.copy(
|
1363
|
+
# wx.SystemSettings().GetColour(wx.SYS_COLOUR_GRADIENTACTIVECAPTION)
|
1364
|
+
# )
|
1365
|
+
self.inactive_background = copy.copy(
|
1366
|
+
wx.SystemSettings().GetColour(wx.SYS_COLOUR_INACTIVECAPTION)
|
1367
|
+
)
|
1368
|
+
self.inactive_text = copy.copy(
|
1369
|
+
wx.SystemSettings().GetColour(wx.SYS_COLOUR_GRAYTEXT)
|
1370
|
+
)
|
1371
|
+
self.tooltip_foreground = copy.copy(
|
1372
|
+
wx.SystemSettings().GetColour(wx.SYS_COLOUR_INFOTEXT)
|
1373
|
+
)
|
1374
|
+
self.tooltip_background = copy.copy(
|
1375
|
+
wx.SystemSettings().GetColour(wx.SYS_COLOUR_INFOBK)
|
1376
|
+
)
|
1377
|
+
self.button_face = copy.copy(
|
1378
|
+
wx.SystemSettings().GetColour(wx.SYS_COLOUR_BTNHILIGHT)
|
1379
|
+
)
|
1380
|
+
self.ribbon_background = copy.copy(
|
1381
|
+
wx.SystemSettings().GetColour(wx.SYS_COLOUR_BTNHILIGHT)
|
1382
|
+
)
|
1383
|
+
self.highlight = copy.copy(
|
1384
|
+
wx.SystemSettings().GetColour(wx.SYS_COLOUR_HOTLIGHT)
|
1385
|
+
)
|
1386
|
+
|
1387
|
+
# Do we have a setting for the color?
|
1388
|
+
c_mode = self.parent.context.root.setting(int, "ribbon_color", 0)
|
1389
|
+
# 0 system default
|
1390
|
+
# 1 colored background
|
1391
|
+
# 2 forced dark_mode
|
1392
|
+
if c_mode < COLOR_MODE_DEFAULT or c_mode > COLOR_MODE_DARK:
|
1393
|
+
c_mode = COLOR_MODE_DEFAULT
|
1394
|
+
if (
|
1395
|
+
c_mode == COLOR_MODE_DEFAULT
|
1396
|
+
and self.parent.context.themes.dark # wx.SystemSettings().GetColour(wx.SYS_COLOUR_WINDOW)[0] < 127
|
1397
|
+
):
|
1398
|
+
c_mode = COLOR_MODE_DARK # dark mode
|
1399
|
+
self.color_mode = c_mode
|
1400
|
+
|
1401
|
+
if self.color_mode == COLOR_MODE_DARK:
|
1402
|
+
# This is rather crude, as a dark mode could also
|
1403
|
+
# be based e.g. on a dark blue scheme
|
1404
|
+
self.button_face = wx.BLACK
|
1405
|
+
self.ribbon_background = wx.BLACK
|
1406
|
+
self.text_color = wx.WHITE
|
1407
|
+
self.text_color_inactive = copy.copy(self.text_color)
|
1408
|
+
self.text_color_disabled = wx.Colour("Light Grey")
|
1409
|
+
self.black_color = wx.WHITE
|
1410
|
+
self.inactive_background = wx.BLACK
|
1411
|
+
OS_NAME = platform.system()
|
1412
|
+
if OS_NAME == "Windows":
|
1413
|
+
self.button_face_hover = wx.BLUE
|
1414
|
+
if self.color_mode == COLOR_MODE_COLOR:
|
1415
|
+
self.ribbon_background = copy.copy(
|
1416
|
+
wx.SystemSettings().GetColour(wx.SYS_COLOUR_GRADIENTINACTIVECAPTION)
|
1417
|
+
)
|
1418
|
+
self.button_face = copy.copy(
|
1419
|
+
wx.SystemSettings().GetColour(wx.SYS_COLOUR_GRADIENTACTIVECAPTION)
|
1420
|
+
)
|
1421
|
+
self.button_face_hover = wx.Colour("gold").ChangeLightness(150)
|
1422
|
+
self.highlight = wx.Colour("gold")
|
1423
|
+
|
1424
|
+
# Let's adjust the fontsize for the page headers
|
1425
|
+
screen_wd, screen_ht = wx.GetDisplaySize()
|
1426
|
+
ptdefault = 10
|
1427
|
+
if screen_wd <= 800 or screen_ht <= 600:
|
1428
|
+
ptdefault = 8
|
1429
|
+
self.tab_height = 16
|
1430
|
+
try:
|
1431
|
+
wxsize = wx.Size(ptdefault, ptdefault)
|
1432
|
+
dipsize = self.parent.FromDIP(wxsize)
|
1433
|
+
ptsize = int(wxsize[0] + 0.5 * (dipsize[0] - wxsize[0]))
|
1434
|
+
# print(ptdefault, wxsize[0], ptsize, dipsize[0])
|
1435
|
+
except AttributeError:
|
1436
|
+
ptsize = ptdefault
|
1437
|
+
self.default_font = wx.Font(
|
1438
|
+
ptsize, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL
|
1439
|
+
)
|
1440
|
+
|
1441
|
+
def paint_main(self, dc, ribbon):
|
1442
|
+
"""
|
1443
|
+
Main paint routine. This should delegate, in paint order, to the things on screen that require painting.
|
1444
|
+
@return:
|
1445
|
+
"""
|
1446
|
+
self._paint_background(dc)
|
1447
|
+
self.parent.validate_current_page()
|
1448
|
+
|
1449
|
+
if ribbon.visible_pages() > 1:
|
1450
|
+
for page in ribbon.pages:
|
1451
|
+
if page.visible:
|
1452
|
+
self._paint_tab(dc, page)
|
1453
|
+
else:
|
1454
|
+
self.current_page = ribbon.first_page()
|
1455
|
+
if self.current_page is not None:
|
1456
|
+
if self.current_page.position is None:
|
1457
|
+
# print("Was dirty...")
|
1458
|
+
self.layout(dc, self.parent)
|
1459
|
+
|
1460
|
+
for page in ribbon.pages:
|
1461
|
+
if page is not self.current_page or not page.visible:
|
1462
|
+
continue
|
1463
|
+
|
1464
|
+
dc.SetBrush(wx.Brush(self.ribbon_background))
|
1465
|
+
x, y, x1, y1 = page.position
|
1466
|
+
dc.DrawRoundedRectangle(
|
1467
|
+
int(x), int(y), int(x1 - x), int(y1 - y), self.rounded_radius
|
1468
|
+
)
|
1469
|
+
self.look_at_button_font_sizes(dc, page)
|
1470
|
+
for panel in page.panels:
|
1471
|
+
# We suppress empty panels
|
1472
|
+
if panel is None or panel.visible_button_count == 0:
|
1473
|
+
continue
|
1474
|
+
self._paint_panel(dc, panel)
|
1475
|
+
for button in panel.visible_buttons():
|
1476
|
+
self._paint_button(dc, button)
|
1477
|
+
|
1478
|
+
def look_at_button_font_sizes(self, dc, page):
|
1479
|
+
self.font_sizes = {}
|
1480
|
+
for panel in page.panels:
|
1481
|
+
# We suppress empty panels
|
1482
|
+
if panel is None or panel.visible_button_count == 0:
|
1483
|
+
continue
|
1484
|
+
for button in panel.visible_buttons():
|
1485
|
+
x, y, x1, y1 = button.position
|
1486
|
+
start_y = y
|
1487
|
+
w = int(round(x1 - x, 2))
|
1488
|
+
h = int(round(y1 - y, 2))
|
1489
|
+
img_h = h
|
1490
|
+
# do we have text? if yes let's reduce the available space in y
|
1491
|
+
if self.show_labels: # Regardless whether we have a label or not...
|
1492
|
+
img_h -= self.bitmap_text_buffer
|
1493
|
+
ptsize = min(18, int(round(min(w, img_h) / 5.0, 2)) * 2)
|
1494
|
+
img_h -= int(ptsize * 1.35)
|
1495
|
+
|
1496
|
+
button.get_bitmaps(min(w, img_h))
|
1497
|
+
if button.enabled:
|
1498
|
+
bitmap = button.bitmap
|
1499
|
+
else:
|
1500
|
+
bitmap = button.bitmap_disabled
|
1501
|
+
|
1502
|
+
bitmap_width, bitmap_height = bitmap.Size
|
1503
|
+
# if button.label in ("Circle", "Ellipse", "Wordlist", "Property Window"):
|
1504
|
+
# print (f"N - {button.label}: {bitmap_width}x{bitmap_height} in {w}x{h}")
|
1505
|
+
bs = min(bitmap_width, bitmap_height)
|
1506
|
+
ptsize = self.get_font_size(bs)
|
1507
|
+
y += bitmap_height
|
1508
|
+
|
1509
|
+
text_edge = self.bitmap_text_buffer
|
1510
|
+
if button.label and self.show_labels:
|
1511
|
+
show_text = True
|
1512
|
+
label_text = list(button.label.split(" "))
|
1513
|
+
# We try to establish whether this would fit properly.
|
1514
|
+
# We allow a small oversize of 25% to the button,
|
1515
|
+
# before we try to reduce the fontsize
|
1516
|
+
wouldfit = False
|
1517
|
+
while not wouldfit:
|
1518
|
+
total_text_height = 0
|
1519
|
+
testfont = wx.Font(
|
1520
|
+
ptsize,
|
1521
|
+
wx.FONTFAMILY_SWISS,
|
1522
|
+
wx.FONTSTYLE_NORMAL,
|
1523
|
+
wx.FONTWEIGHT_NORMAL,
|
1524
|
+
)
|
1525
|
+
test_y = y + text_edge
|
1526
|
+
dc.SetFont(testfont)
|
1527
|
+
wouldfit = True
|
1528
|
+
i = 0
|
1529
|
+
while i < len(label_text):
|
1530
|
+
# We know by definition that all single words
|
1531
|
+
# are okay for drawing, now we check whether
|
1532
|
+
# we can draw multiple in one line
|
1533
|
+
word = label_text[i]
|
1534
|
+
cont = True
|
1535
|
+
while cont:
|
1536
|
+
cont = False
|
1537
|
+
if i < len(label_text) - 1:
|
1538
|
+
nextword = label_text[i + 1]
|
1539
|
+
test = word + " " + nextword
|
1540
|
+
tw, th = dc.GetTextExtent(test)
|
1541
|
+
if tw < w:
|
1542
|
+
word = test
|
1543
|
+
i += 1
|
1544
|
+
cont = True
|
1545
|
+
|
1546
|
+
text_width, text_height = dc.GetTextExtent(word)
|
1547
|
+
if text_width > w:
|
1548
|
+
wouldfit = False
|
1549
|
+
break
|
1550
|
+
test_y += text_height
|
1551
|
+
total_text_height += text_height
|
1552
|
+
if test_y > y1:
|
1553
|
+
wouldfit = False
|
1554
|
+
text_edge = 0
|
1555
|
+
break
|
1556
|
+
i += 1
|
1557
|
+
|
1558
|
+
if wouldfit:
|
1559
|
+
# Let's see how much we have...
|
1560
|
+
if ptsize in self.font_sizes:
|
1561
|
+
self.font_sizes[ptsize] += 1
|
1562
|
+
else:
|
1563
|
+
self.font_sizes[ptsize] = 1
|
1564
|
+
break
|
1565
|
+
|
1566
|
+
ptsize -= 2
|
1567
|
+
if ptsize < 6: # too small
|
1568
|
+
break
|
1569
|
+
|
1570
|
+
def _paint_tab(self, dc: wx.DC, page: RibbonPage):
|
1571
|
+
"""
|
1572
|
+
Paint the individual page tab.
|
1573
|
+
|
1574
|
+
@param dc:
|
1575
|
+
@param page:
|
1576
|
+
@return:
|
1577
|
+
"""
|
1578
|
+
horizontal = self.parent.prefer_horizontal()
|
1579
|
+
highlight_via_color = False
|
1580
|
+
|
1581
|
+
dc.SetPen(wx.Pen(self.black_color))
|
1582
|
+
show_rect = True
|
1583
|
+
if page is not self.current_page:
|
1584
|
+
dc.SetBrush(wx.Brush(self.button_face))
|
1585
|
+
dc.SetTextForeground(self.text_color_inactive)
|
1586
|
+
if not highlight_via_color:
|
1587
|
+
show_rect = False
|
1588
|
+
else:
|
1589
|
+
dc.SetBrush(wx.Brush(self.highlight))
|
1590
|
+
dc.SetBrush(wx.Brush(self.highlight))
|
1591
|
+
dc.SetTextForeground(self.text_color)
|
1592
|
+
if not highlight_via_color:
|
1593
|
+
dc.SetBrush(wx.Brush(self.button_face))
|
1594
|
+
if page is self.hover_tab and self.hover_button is None:
|
1595
|
+
dc.SetBrush(wx.Brush(self.button_face_hover))
|
1596
|
+
show_rect = True
|
1597
|
+
x, y, x1, y1 = page.tab_position
|
1598
|
+
if show_rect:
|
1599
|
+
dc.DrawRoundedRectangle(
|
1600
|
+
int(x), int(y), int(x1 - x), int(y1 - y), self.rounded_radius
|
1601
|
+
)
|
1602
|
+
dc.SetFont(self.default_font)
|
1603
|
+
text_width, text_height = dc.GetTextExtent(page.label)
|
1604
|
+
tpx = int(x + (x1 - x - text_width) / 2)
|
1605
|
+
tpy = int(y + self.tab_text_buffer)
|
1606
|
+
if horizontal:
|
1607
|
+
dc.DrawText(page.label, tpx, tpy)
|
1608
|
+
else:
|
1609
|
+
tpx = int(x + self.tab_text_buffer)
|
1610
|
+
tpy = int(y1 - (y1 - y - text_width) / 2)
|
1611
|
+
dc.DrawRotatedText(page.label, tpx, tpy, 90)
|
1612
|
+
|
1613
|
+
def _paint_background(self, dc: wx.DC):
|
1614
|
+
"""
|
1615
|
+
Paint the background of the ribbonbar.
|
1616
|
+
@param dc:
|
1617
|
+
@return:
|
1618
|
+
"""
|
1619
|
+
w, h = dc.Size
|
1620
|
+
dc.SetBrush(wx.Brush(self.ribbon_background))
|
1621
|
+
dc.SetPen(wx.TRANSPARENT_PEN)
|
1622
|
+
dc.DrawRectangle(0, 0, w, h)
|
1623
|
+
|
1624
|
+
def _paint_panel(self, dc: wx.DC, panel: RibbonPanel):
|
1625
|
+
"""
|
1626
|
+
Paint the ribbonpanel of the given panel.
|
1627
|
+
@param dc:
|
1628
|
+
@param panel:
|
1629
|
+
@return:
|
1630
|
+
"""
|
1631
|
+
if not panel.position:
|
1632
|
+
# print(f"Panel position was not set for {panel.label}")
|
1633
|
+
return
|
1634
|
+
x, y, x1, y1 = panel.position
|
1635
|
+
# print(f"Painting panel {panel.label}: {panel.position}")
|
1636
|
+
dc.SetBrush(wx.Brush(self.ribbon_background))
|
1637
|
+
dc.SetPen(wx.Pen(self.black_color))
|
1638
|
+
dc.DrawRoundedRectangle(
|
1639
|
+
int(x), int(y), int(x1 - x), int(y1 - y), self.rounded_radius
|
1640
|
+
)
|
1641
|
+
"""
|
1642
|
+
Paint the overflow of buttons that cannot be stored within the required width.
|
1643
|
+
|
1644
|
+
@param dc:
|
1645
|
+
@return:
|
1646
|
+
"""
|
1647
|
+
if not panel._overflow_position:
|
1648
|
+
return
|
1649
|
+
x, y, x1, y1 = panel._overflow_position
|
1650
|
+
dc.SetBrush(wx.Brush(self.highlight))
|
1651
|
+
dc.SetPen(wx.Pen(self.black_color))
|
1652
|
+
dc.DrawRoundedRectangle(
|
1653
|
+
int(x), int(y), int(x1 - x), int(y1 - y), self.rounded_radius
|
1654
|
+
)
|
1655
|
+
r = min((y1 - y) / 2, (x1 - x) / 2) - 2
|
1656
|
+
cx = (x + x1) / 2
|
1657
|
+
cy = -r / 2 + (y + y1) / 2
|
1658
|
+
# print (f"area: {x},{y}-{x1},{y1} - center={cx},{cy} r={r}")
|
1659
|
+
# points = [
|
1660
|
+
# (
|
1661
|
+
# int(cx + r * math.cos(math.radians(angle))),
|
1662
|
+
# int(cy + r * math.sin(math.radians(angle))),
|
1663
|
+
# )
|
1664
|
+
# for angle in (0, 90, 180)
|
1665
|
+
# ]
|
1666
|
+
lx = x + (x1 - x) / 8
|
1667
|
+
rx = x1 - (x1 - x) / 8
|
1668
|
+
mx = x + (x1 - x) / 2
|
1669
|
+
ty = y + (y1 - y) * 2 / 8
|
1670
|
+
by = y1 - (y1 - y) * 2 / 8
|
1671
|
+
points = [
|
1672
|
+
(int(lx), int(ty)),
|
1673
|
+
(int(rx), int(ty)),
|
1674
|
+
(int(mx), int(by)),
|
1675
|
+
(int(lx), int(ty)),
|
1676
|
+
]
|
1677
|
+
dc.SetPen(wx.Pen(self.black_color))
|
1678
|
+
dc.SetBrush(wx.Brush(self.inactive_background))
|
1679
|
+
dc.DrawPolygon(points)
|
1680
|
+
|
1681
|
+
def _paint_dropdown(self, dc: wx.DC, dropdown: DropDown):
|
1682
|
+
"""
|
1683
|
+
Paint the dropdown on the button containing a dropdown.
|
1684
|
+
|
1685
|
+
@param dc:
|
1686
|
+
@param dropdown:
|
1687
|
+
@return:
|
1688
|
+
"""
|
1689
|
+
x, y, x1, y1 = dropdown.position
|
1690
|
+
if dropdown is self.hover_dropdown:
|
1691
|
+
dc.SetBrush(wx.Brush(wx.Colour(self.highlight)))
|
1692
|
+
else:
|
1693
|
+
dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
1694
|
+
dc.SetPen(wx.TRANSPARENT_PEN)
|
1695
|
+
|
1696
|
+
dc.DrawRoundedRectangle(
|
1697
|
+
int(x), int(y), int(x1 - x), int(y1 - y), self.rounded_radius
|
1698
|
+
)
|
1699
|
+
lx = x + (x1 - x) / 8
|
1700
|
+
rx = x1 - (x1 - x) / 8
|
1701
|
+
mx = x + (x1 - x) / 2
|
1702
|
+
ty = y + (y1 - y) * 2 / 8
|
1703
|
+
by = y1 - (y1 - y) * 2 / 8
|
1704
|
+
points = [
|
1705
|
+
(int(lx), int(ty)),
|
1706
|
+
(int(rx), int(ty)),
|
1707
|
+
(int(mx), int(by)),
|
1708
|
+
(int(lx), int(ty)),
|
1709
|
+
]
|
1710
|
+
dc.SetPen(wx.Pen(self.black_color))
|
1711
|
+
dc.SetBrush(wx.Brush(self.inactive_background))
|
1712
|
+
dc.DrawPolygon(points)
|
1713
|
+
|
1714
|
+
def _paint_button(self, dc: wx.DC, button: Button):
|
1715
|
+
"""
|
1716
|
+
Paint the given button on the screen.
|
1717
|
+
|
1718
|
+
@param dc:
|
1719
|
+
@param button:
|
1720
|
+
@return:
|
1721
|
+
"""
|
1722
|
+
if button.overflow or not button.visible or button.position is None:
|
1723
|
+
return
|
1724
|
+
|
1725
|
+
dc.SetBrush(wx.Brush(self.button_face))
|
1726
|
+
dc.SetPen(wx.TRANSPARENT_PEN)
|
1727
|
+
dc.SetTextForeground(self.text_color)
|
1728
|
+
if not button.enabled:
|
1729
|
+
dc.SetBrush(wx.Brush(self.inactive_background))
|
1730
|
+
dc.SetPen(wx.TRANSPARENT_PEN)
|
1731
|
+
dc.SetTextForeground(self.text_color_disabled)
|
1732
|
+
if button.toggle:
|
1733
|
+
dc.SetBrush(wx.Brush(self.highlight))
|
1734
|
+
dc.SetPen(wx.Pen(self.black_color))
|
1735
|
+
if self.hover_button is button and self.hover_dropdown is None:
|
1736
|
+
dc.SetBrush(wx.Brush(self.button_face_hover))
|
1737
|
+
dc.SetPen(wx.Pen(self.black_color))
|
1738
|
+
|
1739
|
+
x, y, x1, y1 = button.position
|
1740
|
+
start_y = y
|
1741
|
+
w = int(round(x1 - x, 2))
|
1742
|
+
h = int(round(y1 - y, 2))
|
1743
|
+
img_h = h
|
1744
|
+
# do we have text? if yes let's reduce the available space in y
|
1745
|
+
if self.show_labels: # Regardless whether we have a label or not...
|
1746
|
+
img_h -= self.bitmap_text_buffer
|
1747
|
+
ptsize = min(18, int(round(min(w, img_h) / 5.0, 2)) * 2)
|
1748
|
+
img_h -= int(ptsize * 1.35)
|
1749
|
+
|
1750
|
+
button.get_bitmaps(min(w, img_h))
|
1751
|
+
if button.enabled:
|
1752
|
+
bitmap = button.bitmap
|
1753
|
+
else:
|
1754
|
+
bitmap = button.bitmap_disabled
|
1755
|
+
|
1756
|
+
# Let's clip the output
|
1757
|
+
dc.SetClippingRegion(int(x), int(y), int(w), int(h))
|
1758
|
+
|
1759
|
+
dc.DrawRoundedRectangle(int(x), int(y), int(w), int(h), self.rounded_radius)
|
1760
|
+
bitmap_width, bitmap_height = bitmap.Size
|
1761
|
+
# if button.label in ("Circle", "Ellipse", "Wordlist", "Property Window"):
|
1762
|
+
# print (f"N - {button.label}: {bitmap_width}x{bitmap_height} in {w}x{h}")
|
1763
|
+
bs = min(bitmap_width, bitmap_height)
|
1764
|
+
ptsize = self.get_best_font_size(bs)
|
1765
|
+
font = wx.Font(
|
1766
|
+
ptsize, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL
|
1767
|
+
)
|
1768
|
+
|
1769
|
+
dc.DrawBitmap(bitmap, int(x + (w - bitmap_width) / 2), int(y))
|
1770
|
+
|
1771
|
+
# # For debug purposes: draw rectangle around bitmap
|
1772
|
+
# dc.SetBrush(wx.TRANSPARENT_BRUSH)
|
1773
|
+
# dc.SetPen(wx.RED_PEN)
|
1774
|
+
# dc.DrawRectangle(int(x + (w - bitmap_width) / 2), int(y), bitmap_width, bitmap_height)
|
1775
|
+
|
1776
|
+
y += bitmap_height
|
1777
|
+
|
1778
|
+
text_edge = self.bitmap_text_buffer
|
1779
|
+
if button.label and self.show_labels:
|
1780
|
+
show_text = True
|
1781
|
+
label_text = list(button.label.split(" "))
|
1782
|
+
# We try to establish whether this would fit properly.
|
1783
|
+
# We allow a small oversize of 25% to the button,
|
1784
|
+
# before we try to reduce the fontsize
|
1785
|
+
wouldfit = False
|
1786
|
+
while not wouldfit:
|
1787
|
+
total_text_height = 0
|
1788
|
+
testfont = wx.Font(
|
1789
|
+
ptsize,
|
1790
|
+
wx.FONTFAMILY_SWISS,
|
1791
|
+
wx.FONTSTYLE_NORMAL,
|
1792
|
+
wx.FONTWEIGHT_NORMAL,
|
1793
|
+
)
|
1794
|
+
test_y = y + text_edge
|
1795
|
+
dc.SetFont(testfont)
|
1796
|
+
wouldfit = True
|
1797
|
+
i = 0
|
1798
|
+
while i < len(label_text):
|
1799
|
+
# We know by definition that all single words
|
1800
|
+
# are okay for drawing, now we check whether
|
1801
|
+
# we can draw multiple in one line
|
1802
|
+
word = label_text[i]
|
1803
|
+
cont = True
|
1804
|
+
while cont:
|
1805
|
+
cont = False
|
1806
|
+
if i < len(label_text) - 1:
|
1807
|
+
nextword = label_text[i + 1]
|
1808
|
+
test = word + " " + nextword
|
1809
|
+
tw, th = dc.GetTextExtent(test)
|
1810
|
+
if tw < w:
|
1811
|
+
word = test
|
1812
|
+
i += 1
|
1813
|
+
cont = True
|
1814
|
+
|
1815
|
+
text_width, text_height = dc.GetTextExtent(word)
|
1816
|
+
if text_width > w:
|
1817
|
+
wouldfit = False
|
1818
|
+
break
|
1819
|
+
test_y += text_height
|
1820
|
+
total_text_height += text_height
|
1821
|
+
if test_y > y1:
|
1822
|
+
wouldfit = False
|
1823
|
+
text_edge = 0
|
1824
|
+
break
|
1825
|
+
i += 1
|
1826
|
+
|
1827
|
+
if wouldfit:
|
1828
|
+
font = testfont
|
1829
|
+
break
|
1830
|
+
|
1831
|
+
ptsize -= 2
|
1832
|
+
if ptsize < 6: # too small
|
1833
|
+
break
|
1834
|
+
if not wouldfit:
|
1835
|
+
show_text = False
|
1836
|
+
label_text = list()
|
1837
|
+
else:
|
1838
|
+
show_text = False
|
1839
|
+
label_text = list()
|
1840
|
+
if show_text:
|
1841
|
+
# if it wasn't a full fit, the new textsize might still be okay to be drawn at the intended position
|
1842
|
+
text_edge = min(
|
1843
|
+
max(0, start_y + h - y - total_text_height), self.bitmap_text_buffer
|
1844
|
+
)
|
1845
|
+
|
1846
|
+
y += text_edge
|
1847
|
+
dc.SetFont(font)
|
1848
|
+
i = 0
|
1849
|
+
while i < len(label_text):
|
1850
|
+
# We know by definition that all single words
|
1851
|
+
# are okay for drawing, now we check whether
|
1852
|
+
# we can draw multiple in one line
|
1853
|
+
word = label_text[i]
|
1854
|
+
cont = True
|
1855
|
+
while cont:
|
1856
|
+
cont = False
|
1857
|
+
if i < len(label_text) - 1:
|
1858
|
+
nextword = label_text[i + 1]
|
1859
|
+
test = word + " " + nextword
|
1860
|
+
tw, th = dc.GetTextExtent(test)
|
1861
|
+
if tw < w:
|
1862
|
+
word = test
|
1863
|
+
i += 1
|
1864
|
+
cont = True
|
1865
|
+
|
1866
|
+
text_width, text_height = dc.GetTextExtent(word)
|
1867
|
+
dc.DrawText(
|
1868
|
+
word,
|
1869
|
+
int(x + (w / 2.0) - (text_width / 2)),
|
1870
|
+
int(y),
|
1871
|
+
)
|
1872
|
+
y += text_height
|
1873
|
+
i += 1
|
1874
|
+
if button.dropdown is not None and button.dropdown.position is not None:
|
1875
|
+
self._paint_dropdown(dc, button.dropdown)
|
1876
|
+
dc.DestroyClippingRegion()
|
1877
|
+
|
1878
|
+
def layout(self, dc: wx.DC, ribbon):
|
1879
|
+
"""
|
1880
|
+
Performs the layout of the page. This is determined to be the size of the ribbon minus any edge buffering.
|
1881
|
+
|
1882
|
+
@param dc:
|
1883
|
+
@param ribbon:
|
1884
|
+
@return:
|
1885
|
+
"""
|
1886
|
+
ribbon_width, ribbon_height = dc.Size
|
1887
|
+
# print(f"ribbon: {dc.Size}")
|
1888
|
+
horizontal = self.parent.prefer_horizontal()
|
1889
|
+
xpos = 0
|
1890
|
+
ypos = 0
|
1891
|
+
has_page_header = ribbon.visible_pages() > 1
|
1892
|
+
dc.SetFont(self.default_font)
|
1893
|
+
for pn, page in enumerate(ribbon.pages):
|
1894
|
+
if not page.visible:
|
1895
|
+
continue
|
1896
|
+
# Set tab positioning.
|
1897
|
+
# Compute tabwidth according to be displayed label,
|
1898
|
+
# if bigger than default then extend width
|
1899
|
+
if has_page_header:
|
1900
|
+
line_width, line_height = dc.GetTextExtent(page.label)
|
1901
|
+
if line_height + 4 > self.tab_height:
|
1902
|
+
self.tab_height = line_height + 4
|
1903
|
+
for former in range(0, pn):
|
1904
|
+
former_page = ribbon.pages[former]
|
1905
|
+
t_x, t_y, t_x1, t_y1 = former_page.tab_position
|
1906
|
+
if horizontal:
|
1907
|
+
t_y1 = t_y + self.tab_height * 2
|
1908
|
+
else:
|
1909
|
+
t_x1 = t_x + self.tab_height * 2
|
1910
|
+
former_page.tab_position = (t_x, t_y, t_x1, t_y1)
|
1911
|
+
|
1912
|
+
tabwidth = max(line_width + 2 * self.tab_tab_buffer, self.tab_width)
|
1913
|
+
if horizontal:
|
1914
|
+
t_x = pn * self.tab_tab_buffer + xpos + self.tab_initial_buffer
|
1915
|
+
t_x1 = t_x + tabwidth
|
1916
|
+
t_y = ypos
|
1917
|
+
t_y1 = t_y + self.tab_height * 2
|
1918
|
+
else:
|
1919
|
+
t_y = pn * self.tab_tab_buffer + ypos + self.tab_initial_buffer
|
1920
|
+
t_y1 = t_y + tabwidth
|
1921
|
+
t_x = xpos
|
1922
|
+
t_x1 = t_x + self.tab_height * 2
|
1923
|
+
page.tab_position = (t_x, t_y, t_x1, t_y1)
|
1924
|
+
if horizontal:
|
1925
|
+
xpos += tabwidth
|
1926
|
+
else:
|
1927
|
+
ypos += tabwidth
|
1928
|
+
else:
|
1929
|
+
page.tab_position = (0, 0, 0, 0)
|
1930
|
+
if page is not self.current_page:
|
1931
|
+
continue
|
1932
|
+
|
1933
|
+
page_width = ribbon_width - self.edge_page_buffer
|
1934
|
+
page_height = ribbon_height - self.edge_page_buffer
|
1935
|
+
if horizontal:
|
1936
|
+
page_width -= self.edge_page_buffer
|
1937
|
+
x = self.edge_page_buffer
|
1938
|
+
if has_page_header:
|
1939
|
+
y = self.tab_height
|
1940
|
+
page_height -= self.tab_height
|
1941
|
+
else:
|
1942
|
+
x = self.edge_page_buffer
|
1943
|
+
y = 0
|
1944
|
+
else:
|
1945
|
+
page_height -= self.edge_page_buffer
|
1946
|
+
y = self.edge_page_buffer
|
1947
|
+
if has_page_header:
|
1948
|
+
x = self.tab_height
|
1949
|
+
page_width -= self.tab_height
|
1950
|
+
else:
|
1951
|
+
y = self.edge_page_buffer
|
1952
|
+
x = 0
|
1953
|
+
|
1954
|
+
# Page start position.
|
1955
|
+
if horizontal:
|
1956
|
+
if has_page_header:
|
1957
|
+
y = self.tab_height
|
1958
|
+
page_height += self.edge_page_buffer
|
1959
|
+
else:
|
1960
|
+
x = self.edge_page_buffer
|
1961
|
+
y = 0
|
1962
|
+
else:
|
1963
|
+
if has_page_header:
|
1964
|
+
x = self.tab_height
|
1965
|
+
page_width += self.edge_page_buffer
|
1966
|
+
else:
|
1967
|
+
y = self.edge_page_buffer
|
1968
|
+
x = 0
|
1969
|
+
# Set page position.
|
1970
|
+
page.position = (
|
1971
|
+
x,
|
1972
|
+
y,
|
1973
|
+
x + page_width,
|
1974
|
+
y + page_height,
|
1975
|
+
)
|
1976
|
+
|
1977
|
+
# if self.parent.visible_pages() == 1:
|
1978
|
+
# print(f"page: {page.position}")
|
1979
|
+
self.page_layout(dc, page)
|
1980
|
+
|
1981
|
+
def preferred_button_size_for_page(self, dc, page):
|
1982
|
+
x, y, max_x, max_y = page.position
|
1983
|
+
page_width = max_x - x
|
1984
|
+
page_height = max_y - y
|
1985
|
+
horizontal = self.parent.prefer_horizontal()
|
1986
|
+
is_horizontal = (self.orientation == self.RIBBON_ORIENTATION_HORIZONTAL) or (
|
1987
|
+
horizontal and self.orientation == self.RIBBON_ORIENTATION_AUTO
|
1988
|
+
)
|
1989
|
+
# Count buttons and panels
|
1990
|
+
total_button_count = 0
|
1991
|
+
panel_count = 0
|
1992
|
+
for panel in page.panels:
|
1993
|
+
plen = panel.visible_button_count
|
1994
|
+
total_button_count += plen
|
1995
|
+
if plen > 0:
|
1996
|
+
panel_count += 1
|
1997
|
+
# else:
|
1998
|
+
# print(f"No buttons for {panel.label} found during layout")
|
1999
|
+
# Calculate h/v counts for panels and buttons
|
2000
|
+
if is_horizontal:
|
2001
|
+
all_button_horizontal = max(total_button_count, 1)
|
2002
|
+
all_button_vertical = 1
|
2003
|
+
|
2004
|
+
all_panel_horizontal = max(panel_count, 1)
|
2005
|
+
all_panel_vertical = 1
|
2006
|
+
else:
|
2007
|
+
all_button_horizontal = 1
|
2008
|
+
all_button_vertical = max(total_button_count, 1)
|
2009
|
+
|
2010
|
+
all_panel_horizontal = 1
|
2011
|
+
all_panel_vertical = max(panel_count, 1)
|
2012
|
+
|
2013
|
+
# Calculate optimal width/height for just buttons.
|
2014
|
+
button_width_across_panels = page_width
|
2015
|
+
button_width_across_panels -= (
|
2016
|
+
all_panel_horizontal - 1
|
2017
|
+
) * self.between_panel_buffer
|
2018
|
+
button_width_across_panels -= 2 * self.page_panel_buffer
|
2019
|
+
|
2020
|
+
button_height_across_panels = page_height
|
2021
|
+
button_height_across_panels -= (
|
2022
|
+
all_panel_vertical - 1
|
2023
|
+
) * self.between_panel_buffer
|
2024
|
+
button_height_across_panels -= 2 * self.page_panel_buffer
|
2025
|
+
|
2026
|
+
for p, panel in enumerate(page.panels):
|
2027
|
+
if p == 0:
|
2028
|
+
# Remove high-and-low perpendicular panel_button_buffer
|
2029
|
+
if is_horizontal:
|
2030
|
+
button_height_across_panels -= 2 * self.panel_button_buffer
|
2031
|
+
else:
|
2032
|
+
button_width_across_panels -= 2 * self.panel_button_buffer
|
2033
|
+
for b, button in enumerate(list(panel.visible_buttons())):
|
2034
|
+
if b == 0:
|
2035
|
+
# First and last buffers.
|
2036
|
+
if is_horizontal:
|
2037
|
+
button_width_across_panels -= 2 * self.panel_button_buffer
|
2038
|
+
else:
|
2039
|
+
button_height_across_panels -= 2 * self.panel_button_buffer
|
2040
|
+
else:
|
2041
|
+
# Each gap between buttons
|
2042
|
+
if is_horizontal:
|
2043
|
+
button_width_across_panels -= self.between_button_buffer
|
2044
|
+
else:
|
2045
|
+
button_height_across_panels -= self.between_button_buffer
|
2046
|
+
|
2047
|
+
# Calculate width/height for each button.
|
2048
|
+
button_width = button_width_across_panels / all_button_horizontal
|
2049
|
+
button_height = button_height_across_panels / all_button_vertical
|
2050
|
+
|
2051
|
+
return button_width, button_height
|
2052
|
+
|
2053
|
+
def page_layout(self, dc, page):
|
2054
|
+
"""
|
2055
|
+
Determine the layout of the page. This calls for each panel to be set relative to the number of buttons it
|
2056
|
+
contains.
|
2057
|
+
|
2058
|
+
@param dc:
|
2059
|
+
@param page:
|
2060
|
+
@return:
|
2061
|
+
"""
|
2062
|
+
x, y, max_x, max_y = page.position
|
2063
|
+
is_horizontal = (self.orientation == self.RIBBON_ORIENTATION_HORIZONTAL) or (
|
2064
|
+
self.parent.prefer_horizontal()
|
2065
|
+
and self.orientation == self.RIBBON_ORIENTATION_AUTO
|
2066
|
+
)
|
2067
|
+
button_width, button_height = self.preferred_button_size_for_page(dc, page)
|
2068
|
+
x += self.page_panel_buffer
|
2069
|
+
y += self.page_panel_buffer
|
2070
|
+
"""
|
2071
|
+
THIS ALGORITHM NEEDS STILL TO BE IMPLEMENTED
|
2072
|
+
--------------------------------------------
|
2073
|
+
We discuss now the horizontal case, the same
|
2074
|
+
logic would apply for the vertical case.
|
2075
|
+
We iterate through the sizes and establish the space
|
2076
|
+
needed for every panel:
|
2077
|
+
1) We calculate the required button dimensions for all
|
2078
|
+
combinations of tiny/small/regular icons plus with/without labels
|
2079
|
+
2) We get the minimum amount of columns required to display
|
2080
|
+
the buttons (taking the vertical extent i.e. the amount
|
2081
|
+
of available rows into account).
|
2082
|
+
This will provide us with a solution that would need
|
2083
|
+
the least horizontal space.
|
2084
|
+
3) That may lead to a situation where you would still
|
2085
|
+
have horizontal space available for the panels.
|
2086
|
+
Hence we do a second pass where we assign additional space
|
2087
|
+
to all panels that need more than one row of icons.
|
2088
|
+
As we will do this for all possible size combinations,
|
2089
|
+
we will chose eventually that solution that has the
|
2090
|
+
fewest amount of buttons in overflow.
|
2091
|
+
"""
|
2092
|
+
# 1 Calculate button sizes - this is not required
|
2093
|
+
# here, already done during button creation
|
2094
|
+
|
2095
|
+
# 2 Loop over all sizes
|
2096
|
+
|
2097
|
+
# Now that we have gathered all information we can assign
|
2098
|
+
# the space...
|
2099
|
+
available_space = 0
|
2100
|
+
p = -1
|
2101
|
+
for panel in page.panels:
|
2102
|
+
if panel.visible_button_count == 0:
|
2103
|
+
continue
|
2104
|
+
p += 1
|
2105
|
+
if p != 0:
|
2106
|
+
# Non-first move between panel gap.
|
2107
|
+
if is_horizontal:
|
2108
|
+
x += self.between_panel_buffer
|
2109
|
+
else:
|
2110
|
+
y += self.between_panel_buffer
|
2111
|
+
|
2112
|
+
if is_horizontal:
|
2113
|
+
single_panel_horizontal = max(panel.visible_button_count, 1)
|
2114
|
+
single_panel_vertical = 1
|
2115
|
+
else:
|
2116
|
+
single_panel_horizontal = 1
|
2117
|
+
single_panel_vertical = max(panel.visible_button_count, 1)
|
2118
|
+
|
2119
|
+
panel_width = (
|
2120
|
+
single_panel_horizontal * button_width
|
2121
|
+
+ (single_panel_horizontal - 1) * self.between_button_buffer
|
2122
|
+
+ 2 * self.panel_button_buffer
|
2123
|
+
)
|
2124
|
+
panel_height = (
|
2125
|
+
single_panel_vertical * button_height
|
2126
|
+
+ (single_panel_vertical - 1) * self.between_button_buffer
|
2127
|
+
+ 2 * self.panel_button_buffer
|
2128
|
+
)
|
2129
|
+
if is_horizontal:
|
2130
|
+
sx = available_space
|
2131
|
+
sy = 0
|
2132
|
+
else:
|
2133
|
+
sx = 0
|
2134
|
+
sy = available_space
|
2135
|
+
panel_width += sx
|
2136
|
+
panel_height += sy
|
2137
|
+
# print (f"{panel.label} was {panel.position} will be {x}, {y}, {x + panel_width}, {y + panel_height}")
|
2138
|
+
panel.position = x, y, x + panel_width, y + panel_height
|
2139
|
+
panel_max_x, panel_max_y = self.panel_layout(dc, panel)
|
2140
|
+
# print (f"Max values: {panel_max_x}, {panel_max_y}")
|
2141
|
+
# Do we have more space than needed?
|
2142
|
+
available_space = 0
|
2143
|
+
if panel._overflow_position is None:
|
2144
|
+
recalc = False
|
2145
|
+
# print (f"({x}, {y}) - ({x + panel_width}, {y+panel_height}), sx={sx}, sy={sy}")
|
2146
|
+
if is_horizontal:
|
2147
|
+
available_space = max(
|
2148
|
+
0, x + panel_width - panel_max_x - self.panel_button_buffer
|
2149
|
+
)
|
2150
|
+
# print (f"x={x + panel_width}, {panel_max_x} will become: {panel_max_x + self.panel_button_buffer}, available={available_space}")
|
2151
|
+
if available_space != 0:
|
2152
|
+
panel_width = panel_max_x + self.panel_button_buffer - x
|
2153
|
+
recalc = True
|
2154
|
+
else:
|
2155
|
+
available_space = max(
|
2156
|
+
0, y + panel_height - panel_max_y - self.panel_button_buffer
|
2157
|
+
)
|
2158
|
+
# print (f"y={y + panel_height}, {panel_max_y} will become: {panel_max_y + self.panel_button_buffer}, available={available_space}")
|
2159
|
+
if available_space != 0:
|
2160
|
+
panel_height = panel_max_y + self.panel_button_buffer - y
|
2161
|
+
recalc = True
|
2162
|
+
if recalc:
|
2163
|
+
panel.position = x, y, x + panel_width, y + panel_height
|
2164
|
+
self.panel_layout(dc, panel)
|
2165
|
+
|
2166
|
+
if is_horizontal:
|
2167
|
+
x += panel_width
|
2168
|
+
else:
|
2169
|
+
y += panel_height
|
2170
|
+
|
2171
|
+
def panel_layout(self, dc: wx.DC, panel):
|
2172
|
+
x, y, max_x, max_y = panel.position
|
2173
|
+
panel_width = max_x - x
|
2174
|
+
panel_height = max_y - y
|
2175
|
+
# print(f"Panel: {panel.label}: {panel.position}")
|
2176
|
+
horizontal = self.parent.prefer_horizontal()
|
2177
|
+
is_horizontal = (self.orientation == self.RIBBON_ORIENTATION_HORIZONTAL) or (
|
2178
|
+
horizontal and self.orientation == self.RIBBON_ORIENTATION_AUTO
|
2179
|
+
)
|
2180
|
+
plen = panel.visible_button_count
|
2181
|
+
# if plen == 0:
|
2182
|
+
# print(f"layout for panel '{panel.label}' without buttons!")
|
2183
|
+
|
2184
|
+
distribute_evenly = False
|
2185
|
+
if is_horizontal:
|
2186
|
+
button_horizontal = max(plen, 1)
|
2187
|
+
button_vertical = 1
|
2188
|
+
else:
|
2189
|
+
button_horizontal = 1
|
2190
|
+
button_vertical = max(plen, 1)
|
2191
|
+
|
2192
|
+
all_button_width = (
|
2193
|
+
panel_width
|
2194
|
+
- (button_horizontal - 1) * self.between_button_buffer
|
2195
|
+
- 2 * self.panel_button_buffer
|
2196
|
+
)
|
2197
|
+
all_button_height = (
|
2198
|
+
panel_height
|
2199
|
+
- (button_vertical - 1) * self.between_button_buffer
|
2200
|
+
- 2 * self.panel_button_buffer
|
2201
|
+
)
|
2202
|
+
|
2203
|
+
button_width = all_button_width / button_horizontal
|
2204
|
+
button_height = all_button_height / button_vertical
|
2205
|
+
# 'tiny'-size of a default 50x50 icon
|
2206
|
+
minim_size = 15
|
2207
|
+
button_width = max(minim_size, button_width)
|
2208
|
+
button_height = max(minim_size, button_height)
|
2209
|
+
|
2210
|
+
x += self.panel_button_buffer
|
2211
|
+
y += self.panel_button_buffer
|
2212
|
+
panel._overflow.clear()
|
2213
|
+
panel._overflow_position = None
|
2214
|
+
for b, button in enumerate(list(panel.visible_buttons())):
|
2215
|
+
bitmapsize = button.max_size
|
2216
|
+
while bitmapsize > button.min_size:
|
2217
|
+
if bitmapsize <= button_height and bitmapsize <= button_width:
|
2218
|
+
break
|
2219
|
+
bitmapsize -= 5
|
2220
|
+
button.get_bitmaps(bitmapsize)
|
2221
|
+
self.button_calc(dc, button)
|
2222
|
+
if b == 0:
|
2223
|
+
max_width = button.min_width
|
2224
|
+
max_height = button.min_height
|
2225
|
+
else:
|
2226
|
+
max_width = max(max_width, button.min_width)
|
2227
|
+
max_height = max(max_height, button.min_height)
|
2228
|
+
|
2229
|
+
target_height = button_height
|
2230
|
+
target_width = button_width
|
2231
|
+
# print(f"Target: {panel.label} - {target_width}x{target_height}")
|
2232
|
+
for b, button in enumerate(list(panel.visible_buttons())):
|
2233
|
+
this_width = target_width
|
2234
|
+
this_height = target_height
|
2235
|
+
local_width = 1.25 * button.min_width
|
2236
|
+
local_height = 1.25 * button.min_height
|
2237
|
+
if not distribute_evenly:
|
2238
|
+
if button_horizontal > 1 or is_horizontal:
|
2239
|
+
this_width = min(this_width, local_width)
|
2240
|
+
if button_vertical > 1 or not is_horizontal:
|
2241
|
+
this_height = min(this_height, local_height)
|
2242
|
+
if b != 0:
|
2243
|
+
# Move across button gap if not first button.
|
2244
|
+
if is_horizontal:
|
2245
|
+
x += self.between_button_buffer
|
2246
|
+
else:
|
2247
|
+
y += self.between_button_buffer
|
2248
|
+
button.position = x, y, x + this_width, y + this_height
|
2249
|
+
if is_horizontal:
|
2250
|
+
is_overflow = False
|
2251
|
+
if x + this_width > panel.position[2]:
|
2252
|
+
is_overflow = True
|
2253
|
+
# Let's establish whether there is place for another row of icons underneath
|
2254
|
+
# print(
|
2255
|
+
# f"Horizontal Overflow: y={y}, b-height={max_height}, new max={y + 2 * max_height + self.panel_button_buffer}, panel: {panel.position[3]}"
|
2256
|
+
# )
|
2257
|
+
if (
|
2258
|
+
y + 2 + max_height + self.panel_button_buffer
|
2259
|
+
< panel.position[3]
|
2260
|
+
):
|
2261
|
+
is_overflow = False
|
2262
|
+
target_height = max_height
|
2263
|
+
# Reset height of all previous buttons:
|
2264
|
+
for bb, bbutton in enumerate(list(panel.visible_buttons())):
|
2265
|
+
if bb >= b:
|
2266
|
+
break
|
2267
|
+
bbutton.position = (
|
2268
|
+
bbutton.position[0],
|
2269
|
+
bbutton.position[1],
|
2270
|
+
bbutton.position[2],
|
2271
|
+
bbutton.position[1] + max_height,
|
2272
|
+
)
|
2273
|
+
x = panel.position[0] + self.panel_button_buffer
|
2274
|
+
y += max_height + self.panel_button_buffer
|
2275
|
+
button.position = x, y, x + this_width, y + max_height
|
2276
|
+
if is_overflow and panel._overflow_position is None:
|
2277
|
+
ppx, ppy, ppx1, ppy1 = panel.position
|
2278
|
+
panel._overflow_position = (ppx1 - 15, ppy, ppx1, ppy1)
|
2279
|
+
else:
|
2280
|
+
is_overflow = False
|
2281
|
+
if y + this_height > panel.position[3]:
|
2282
|
+
is_overflow = True
|
2283
|
+
# print(
|
2284
|
+
# f"Vertical Overflow: x={x}, b-width={max_width}, new max={x + 2 * max_width + self.panel_button_buffer}, panel: {panel.position[2]}"
|
2285
|
+
# )
|
2286
|
+
# Let's establish whether there is place for another column of icons to the right
|
2287
|
+
if x + 2 * max_width + self.panel_button_buffer < panel.position[2]:
|
2288
|
+
is_overflow = False
|
2289
|
+
target_width = max_width
|
2290
|
+
# Reset width of all previous buttons:
|
2291
|
+
for bb, bbutton in enumerate(list(panel.visible_buttons())):
|
2292
|
+
if bb >= b:
|
2293
|
+
break
|
2294
|
+
bbutton.position = (
|
2295
|
+
bbutton.position[0],
|
2296
|
+
bbutton.position[1],
|
2297
|
+
bbutton.position[0] + max_width,
|
2298
|
+
bbutton.position[3],
|
2299
|
+
)
|
2300
|
+
y = panel.position[1] + self.panel_button_buffer
|
2301
|
+
x += max_width + self.panel_button_buffer
|
2302
|
+
button.position = x, y, x + max_width, y + this_height
|
2303
|
+
if is_overflow and panel._overflow_position is None:
|
2304
|
+
ppx, ppy, ppx1, ppy1 = panel.position
|
2305
|
+
panel._overflow_position = (ppx, ppy1 - 15, ppx1, ppy1)
|
2306
|
+
|
2307
|
+
# print(f"button: {button.position}")
|
2308
|
+
|
2309
|
+
if is_horizontal:
|
2310
|
+
x += this_width
|
2311
|
+
else:
|
2312
|
+
y += this_height
|
2313
|
+
x = 0
|
2314
|
+
y = 0
|
2315
|
+
for button in panel.visible_buttons():
|
2316
|
+
button.overflow = False
|
2317
|
+
if button.position[2] > panel.position[2]:
|
2318
|
+
button.overflow = True
|
2319
|
+
elif button.position[3] > panel.position[3]:
|
2320
|
+
button.overflow = True
|
2321
|
+
elif (
|
2322
|
+
is_horizontal
|
2323
|
+
and panel._overflow_position is not None
|
2324
|
+
and button.position[2] > panel._overflow_position[0]
|
2325
|
+
):
|
2326
|
+
button.overflow = True
|
2327
|
+
elif (
|
2328
|
+
not is_horizontal
|
2329
|
+
and panel._overflow_position is not None
|
2330
|
+
and button.position[3] > panel._overflow_position[1]
|
2331
|
+
):
|
2332
|
+
button.overflow = True
|
2333
|
+
# if panel.label == "Create":
|
2334
|
+
# print (f"{button.label}: {button.overflow}, {button.position}, {panel.position}, {panel._overflow_position}")
|
2335
|
+
if button.overflow:
|
2336
|
+
panel._overflow.append(button)
|
2337
|
+
else:
|
2338
|
+
x = max(x, button.position[2])
|
2339
|
+
y = max(y, button.position[3])
|
2340
|
+
self.button_layout(dc, button)
|
2341
|
+
if panel._overflow_position is not None:
|
2342
|
+
x = max(x, panel._overflow_position[2])
|
2343
|
+
y = max(y, panel._overflow_position[3])
|
2344
|
+
|
2345
|
+
return min(x, panel.position[2]), min(y, panel.position[3])
|
2346
|
+
|
2347
|
+
def button_calc(self, dc: wx.DC, button):
|
2348
|
+
bitmap = button.bitmap
|
2349
|
+
ptsize = self.get_font_size(button.icon_size)
|
2350
|
+
font = wx.Font(
|
2351
|
+
ptsize, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL
|
2352
|
+
)
|
2353
|
+
|
2354
|
+
dc.SetFont(font)
|
2355
|
+
bitmap_width, bitmap_height = bitmap.Size
|
2356
|
+
bitmap_height = max(bitmap_height, button.icon_size)
|
2357
|
+
bitmap_width = max(bitmap_width, button.icon_size)
|
2358
|
+
|
2359
|
+
# Calculate text height/width
|
2360
|
+
text_width = 0
|
2361
|
+
text_height = 0
|
2362
|
+
if button.label and self.show_labels:
|
2363
|
+
label_text = list(button.label.split(" "))
|
2364
|
+
i = 0
|
2365
|
+
while i < len(label_text):
|
2366
|
+
# We know by definition that all single words
|
2367
|
+
# are okay for drawing, now we check whether
|
2368
|
+
# we can draw multiple in one line
|
2369
|
+
word = label_text[i]
|
2370
|
+
cont = True
|
2371
|
+
while cont:
|
2372
|
+
cont = False
|
2373
|
+
if i < len(label_text) - 1:
|
2374
|
+
nextword = label_text[i + 1]
|
2375
|
+
test = word + " " + nextword
|
2376
|
+
tw, th = dc.GetTextExtent(test)
|
2377
|
+
if tw < bitmap_width:
|
2378
|
+
word = test
|
2379
|
+
i += 1
|
2380
|
+
cont = True
|
2381
|
+
line_width, line_height = dc.GetTextExtent(word)
|
2382
|
+
text_width = max(text_width, line_width)
|
2383
|
+
text_height += line_height
|
2384
|
+
i += 1
|
2385
|
+
|
2386
|
+
# Calculate button_width/button_height
|
2387
|
+
button_width = max(bitmap_width, text_width)
|
2388
|
+
button_height = bitmap_height
|
2389
|
+
button_height += 2 * self.panel_button_buffer
|
2390
|
+
button_width += 2 * self.panel_button_buffer
|
2391
|
+
if button.label and self.show_labels:
|
2392
|
+
# button_height += + self.panel_button_buffer
|
2393
|
+
button_height += self.bitmap_text_buffer + text_height
|
2394
|
+
|
2395
|
+
button.min_width = button_width
|
2396
|
+
button.min_height = button_height
|
2397
|
+
# print (f"layout for {button.label} ({button.bitmapsize}): {button.min_width}x{button.min_height}, icon={bitmap_width}x{bitmap_height}")
|
2398
|
+
|
2399
|
+
def button_layout(self, dc: wx.DC, button):
|
2400
|
+
x, y, max_x, max_y = button.position
|
2401
|
+
bitmap = button.bitmap
|
2402
|
+
bitmap_width, bitmap_height = bitmap.Size
|
2403
|
+
if button.kind == "hybrid" and button.key != "toggle":
|
2404
|
+
# Calculate text height/width
|
2405
|
+
# Calculate dropdown
|
2406
|
+
# Same size regardless of bitmap-size
|
2407
|
+
sizx = 15
|
2408
|
+
sizy = 15
|
2409
|
+
if min(bitmap_width, bitmap_height) > 70:
|
2410
|
+
sizx = 20
|
2411
|
+
sizy = 20
|
2412
|
+
elif min(bitmap_width, bitmap_height) > 100:
|
2413
|
+
sizx = 25
|
2414
|
+
sizy = 25
|
2415
|
+
|
2416
|
+
# Let's see whether we have enough room
|
2417
|
+
extx = (x + max_x) / 2 + bitmap_width / 2 + sizx - 1
|
2418
|
+
exty = y + bitmap_height + sizy - 1
|
2419
|
+
extx = max(x - sizx, min(extx, max_x - 1))
|
2420
|
+
exty = max(y + sizy, min(exty, max_y - 1))
|
2421
|
+
gap = 15
|
2422
|
+
if bitmap_height < 30:
|
2423
|
+
gap = 3
|
2424
|
+
|
2425
|
+
# print (f"{bitmap_width}x{bitmap_height} - siz={sizx}, gap={gap}")
|
2426
|
+
button.dropdown.position = (
|
2427
|
+
extx - sizx,
|
2428
|
+
exty - sizy - gap,
|
2429
|
+
extx,
|
2430
|
+
exty - gap,
|
2431
|
+
)
|
2432
|
+
# button.dropdown.position = (
|
2433
|
+
# x + bitmap_width / 2,
|
2434
|
+
# y + bitmap_height / 2,
|
2435
|
+
# x + bitmap_width,
|
2436
|
+
# y + bitmap_height,
|
2437
|
+
# )
|
2438
|
+
# print (
|
2439
|
+
# f"Required for {button.label}: button: {x},{y} to {max_x},{max_y}," +
|
2440
|
+
# f"dropd: {extx-sizx},{exty-sizy} to {extx},{exty}"
|
2441
|
+
# )
|
2442
|
+
|
2443
|
+
def get_font_size(self, imgsize):
|
2444
|
+
if imgsize <= 20:
|
2445
|
+
ptsize = 6
|
2446
|
+
elif imgsize <= 30:
|
2447
|
+
ptsize = 8
|
2448
|
+
elif imgsize <= 40:
|
2449
|
+
ptsize = 10
|
2450
|
+
elif imgsize <= 60:
|
2451
|
+
ptsize = 12
|
2452
|
+
elif imgsize <= 80:
|
2453
|
+
ptsize = 14
|
2454
|
+
else:
|
2455
|
+
ptsize = 16
|
2456
|
+
return ptsize
|
2457
|
+
|
2458
|
+
def get_best_font_size(self, imgsize):
|
2459
|
+
sizes = [(pt, amount) for pt, amount in self.font_sizes.items()]
|
2460
|
+
sizes.sort(key=lambda e: e[1], reverse=True)
|
2461
|
+
best = 32768
|
2462
|
+
if len(sizes):
|
2463
|
+
# Take the one where we have most...
|
2464
|
+
best = sizes[0][0]
|
2465
|
+
ptsize = self.get_font_size(imgsize)
|
2466
|
+
if ptsize > best:
|
2467
|
+
ptsize = best
|
2468
|
+
return ptsize
|