meerk40t 0.9.7030__py2.py3-none-any.whl → 0.9.7050__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/balormk/clone_loader.py +3 -2
- meerk40t/balormk/controller.py +38 -13
- meerk40t/balormk/cylindermod.py +1 -0
- meerk40t/balormk/device.py +13 -9
- meerk40t/balormk/driver.py +9 -2
- meerk40t/balormk/galvo_commands.py +3 -1
- meerk40t/balormk/gui/gui.py +6 -0
- meerk40t/balormk/livelightjob.py +338 -321
- meerk40t/balormk/mock_connection.py +4 -3
- meerk40t/balormk/usb_connection.py +11 -2
- meerk40t/camera/camera.py +19 -14
- meerk40t/camera/gui/camerapanel.py +6 -0
- meerk40t/core/cutplan.py +101 -78
- meerk40t/core/elements/element_treeops.py +435 -140
- meerk40t/core/elements/elements.py +100 -9
- meerk40t/core/elements/shapes.py +259 -72
- meerk40t/core/elements/tree_commands.py +10 -5
- meerk40t/core/node/blobnode.py +19 -4
- meerk40t/core/node/elem_ellipse.py +18 -8
- meerk40t/core/node/elem_image.py +51 -19
- meerk40t/core/node/elem_line.py +18 -8
- meerk40t/core/node/elem_path.py +18 -8
- meerk40t/core/node/elem_point.py +10 -4
- meerk40t/core/node/elem_polyline.py +19 -11
- meerk40t/core/node/elem_rect.py +18 -8
- meerk40t/core/node/elem_text.py +11 -5
- meerk40t/core/node/filenode.py +2 -8
- meerk40t/core/node/groupnode.py +11 -11
- meerk40t/core/node/image_processed.py +11 -5
- meerk40t/core/node/image_raster.py +11 -5
- meerk40t/core/node/node.py +64 -16
- meerk40t/core/node/refnode.py +2 -1
- meerk40t/core/planner.py +25 -11
- meerk40t/core/svg_io.py +91 -34
- meerk40t/device/dummydevice.py +7 -1
- meerk40t/extra/vtracer.py +222 -0
- meerk40t/grbl/device.py +96 -9
- meerk40t/grbl/driver.py +15 -5
- meerk40t/gui/about.py +20 -0
- meerk40t/gui/devicepanel.py +20 -16
- meerk40t/gui/gui_mixins.py +4 -0
- meerk40t/gui/icons.py +330 -253
- meerk40t/gui/laserpanel.py +27 -3
- meerk40t/gui/laserrender.py +41 -21
- meerk40t/gui/magnetoptions.py +158 -65
- meerk40t/gui/materialtest.py +569 -310
- meerk40t/gui/navigationpanels.py +229 -24
- meerk40t/gui/propertypanels/hatchproperty.py +2 -0
- meerk40t/gui/propertypanels/imageproperty.py +160 -106
- meerk40t/gui/propertypanels/wobbleproperty.py +6 -2
- meerk40t/gui/ribbon.py +6 -1
- meerk40t/gui/scenewidgets/gridwidget.py +29 -32
- meerk40t/gui/scenewidgets/rectselectwidget.py +190 -192
- meerk40t/gui/simulation.py +75 -77
- meerk40t/gui/spoolerpanel.py +27 -7
- meerk40t/gui/statusbarwidgets/defaultoperations.py +84 -48
- meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
- meerk40t/gui/tips.py +15 -1
- meerk40t/gui/toolwidgets/toolpointmove.py +3 -1
- meerk40t/gui/wxmmain.py +242 -114
- meerk40t/gui/wxmscene.py +107 -24
- meerk40t/gui/wxmtree.py +4 -2
- meerk40t/gui/wxutils.py +286 -15
- meerk40t/image/imagetools.py +129 -65
- meerk40t/internal_plugins.py +4 -0
- meerk40t/kernel/kernel.py +67 -18
- meerk40t/kernel/settings.py +28 -9
- meerk40t/lihuiyu/device.py +24 -12
- meerk40t/main.py +14 -9
- meerk40t/moshi/device.py +20 -6
- meerk40t/network/console_server.py +22 -6
- meerk40t/newly/device.py +10 -3
- meerk40t/newly/gui/gui.py +10 -0
- meerk40t/ruida/device.py +22 -2
- meerk40t/ruida/loader.py +9 -4
- meerk40t/ruida/rdjob.py +48 -8
- meerk40t/tools/geomstr.py +240 -123
- meerk40t/tools/rasterplotter.py +185 -94
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/METADATA +1 -1
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/RECORD +85 -84
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/LICENSE +0 -0
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/WHEEL +0 -0
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/entry_points.txt +0 -0
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/top_level.txt +0 -0
- {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/zip-safe +0 -0
meerk40t/gui/wxmscene.py
CHANGED
@@ -99,7 +99,12 @@ class ContourDetectionDialog(wx.Dialog):
|
|
99
99
|
def _init_ui(self, node):
|
100
100
|
main_sizer = wx.BoxSizer(wx.VERTICAL)
|
101
101
|
panel = ContourPanel(
|
102
|
-
self,
|
102
|
+
self,
|
103
|
+
wx.ID_ANY,
|
104
|
+
context=self.context,
|
105
|
+
node=node,
|
106
|
+
simplified=True,
|
107
|
+
direct_mode=True,
|
103
108
|
)
|
104
109
|
main_sizer.Add(panel, 1, wx.EXPAND, 0)
|
105
110
|
buttons = self.CreateStdDialogButtonSizer(wx.OK)
|
@@ -158,7 +163,9 @@ class MeerK40tScenePanel(wx.Panel):
|
|
158
163
|
# Save / Load the content of magnets
|
159
164
|
from os.path import join
|
160
165
|
|
161
|
-
self._magnet_file = join(
|
166
|
+
self._magnet_file = join(
|
167
|
+
self.context.kernel.os_information["WORKDIR"], "magnets.cfg"
|
168
|
+
)
|
162
169
|
self.load_magnets()
|
163
170
|
# Add a plugin routine to be called at the time of a full new start
|
164
171
|
context.kernel.register(
|
@@ -872,14 +879,12 @@ class MeerK40tScenePanel(wx.Panel):
|
|
872
879
|
@context.console_argument(
|
873
880
|
"action", type=str, help=_("Action: clear or set / delete with coordinate")
|
874
881
|
)
|
875
|
-
@context.console_argument(
|
876
|
-
"axis", type=str, help=_("Axis (X or Y)")
|
877
|
-
)
|
882
|
+
@context.console_argument("axis", type=str, help=_("Axis (X or Y)"))
|
878
883
|
@context.console_argument("pos", type=str, help=_("Position for magnetline"))
|
879
884
|
@context.console_command(
|
880
885
|
"magnet",
|
881
886
|
help=_("magnet <action> <axis> <position>"),
|
882
|
-
input_type="scene",
|
887
|
+
input_type=("scene", None),
|
883
888
|
)
|
884
889
|
def magnet_set(
|
885
890
|
command,
|
@@ -892,10 +897,20 @@ class MeerK40tScenePanel(wx.Panel):
|
|
892
897
|
):
|
893
898
|
def info(opt_msg):
|
894
899
|
channel(
|
895
|
-
_("You need to provide the intended action:")
|
896
|
-
|
897
|
-
_("
|
898
|
-
|
900
|
+
_("You need to provide the intended action:")
|
901
|
+
+ "\n"
|
902
|
+
+ _("clear x - clear y : will clear all magnets on the given axis")
|
903
|
+
+ "\n"
|
904
|
+
+ _(
|
905
|
+
"set x <pos> - set y <pos>: will set a magnet line on the given axis"
|
906
|
+
)
|
907
|
+
+ "\n"
|
908
|
+
+ _(
|
909
|
+
"delete x <pos> - delete y <pos>: will delete the magnet line on the given axis"
|
910
|
+
)
|
911
|
+
+ _(
|
912
|
+
"split x <count> - split y <count>: will generate <count> lines between the selection boundaries on the given axis"
|
913
|
+
)
|
899
914
|
)
|
900
915
|
if opt_msg:
|
901
916
|
channel(opt_msg)
|
@@ -906,17 +921,33 @@ class MeerK40tScenePanel(wx.Panel):
|
|
906
921
|
action = action.lower()
|
907
922
|
axis = axis.upper()
|
908
923
|
value = None
|
909
|
-
if
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
924
|
+
if action == "split":
|
925
|
+
if pos:
|
926
|
+
try:
|
927
|
+
value = int(pos)
|
928
|
+
except ValueError:
|
929
|
+
info(f"Invalid count: {pos}")
|
930
|
+
return
|
931
|
+
|
932
|
+
if value is None or value <= 0:
|
933
|
+
info(_("You need to provide a number of splits"))
|
915
934
|
return
|
935
|
+
else:
|
936
|
+
if pos:
|
937
|
+
try:
|
938
|
+
rel_len = (
|
939
|
+
self.context.device.view.width
|
940
|
+
if axis == "X"
|
941
|
+
else self.context.device.view.height
|
942
|
+
)
|
943
|
+
value = float(Length(pos, relative_length=rel_len))
|
944
|
+
except ValueError:
|
945
|
+
info(f"Invalid length: {pos}")
|
946
|
+
return
|
916
947
|
|
917
|
-
|
918
|
-
|
919
|
-
|
948
|
+
if action != "clear" and value is None:
|
949
|
+
info(_("You need to provide a position"))
|
950
|
+
return
|
920
951
|
|
921
952
|
if action == "clear":
|
922
953
|
if axis == "X":
|
@@ -927,7 +958,43 @@ class MeerK40tScenePanel(wx.Panel):
|
|
927
958
|
self.magnet_y.clear()
|
928
959
|
self.save_magnets()
|
929
960
|
self.context.signal("refresh_scene", "Scene")
|
930
|
-
channel
|
961
|
+
channel(
|
962
|
+
_("Deleted {count} magnet lines on axis {axis}").format(
|
963
|
+
axis=axis, count=count
|
964
|
+
)
|
965
|
+
)
|
966
|
+
elif action == "split":
|
967
|
+
bb = self.context.elements.selected_area()
|
968
|
+
if bb is None:
|
969
|
+
channel(_("Nothing selected"))
|
970
|
+
return
|
971
|
+
|
972
|
+
min_v = bb[0] if axis == "X" else bb[1]
|
973
|
+
max_v = bb[2] if axis == "X" else bb[3]
|
974
|
+
count = value + 1
|
975
|
+
delta = (max_v - min_v) / count
|
976
|
+
mvalue = min_v
|
977
|
+
while mvalue + delta < max_v:
|
978
|
+
mvalue += delta
|
979
|
+
if axis == "X":
|
980
|
+
if mvalue not in self.magnet_x:
|
981
|
+
self.magnet_x.append(mvalue)
|
982
|
+
else:
|
983
|
+
if mvalue not in self.magnet_y:
|
984
|
+
self.magnet_y.append(mvalue)
|
985
|
+
self.save_magnets()
|
986
|
+
channel(
|
987
|
+
_(
|
988
|
+
"Created {count} magnet lines on {axis}-axis between {min_len} and {max_len}"
|
989
|
+
).format(
|
990
|
+
count=count,
|
991
|
+
axis=axis,
|
992
|
+
min_len=Length(min_v, digits=1).length_mm,
|
993
|
+
max_len=Length(max_v, digits=1).length_mm,
|
994
|
+
)
|
995
|
+
)
|
996
|
+
self.context.signal("refresh_scene", "Scene")
|
997
|
+
|
931
998
|
elif action == "set":
|
932
999
|
done = False
|
933
1000
|
if axis == "X":
|
@@ -941,7 +1008,11 @@ class MeerK40tScenePanel(wx.Panel):
|
|
941
1008
|
self.save_magnets()
|
942
1009
|
self.context.signal("refresh_scene", "Scene")
|
943
1010
|
if done:
|
944
|
-
channel(
|
1011
|
+
channel(
|
1012
|
+
_("Magnetline appended at {pos} on axis {axis}").format(
|
1013
|
+
pos=pos, axis=axis
|
1014
|
+
)
|
1015
|
+
)
|
945
1016
|
else:
|
946
1017
|
channel(_("Magnetline was already present"))
|
947
1018
|
elif action.startswith("del"):
|
@@ -957,7 +1028,11 @@ class MeerK40tScenePanel(wx.Panel):
|
|
957
1028
|
self.save_magnets()
|
958
1029
|
self.context.signal("refresh_scene", "Scene")
|
959
1030
|
if done:
|
960
|
-
channel(
|
1031
|
+
channel(
|
1032
|
+
_("Magnetline removed at {pos} on axis {axis}").format(
|
1033
|
+
pos=pos, axis=axis
|
1034
|
+
)
|
1035
|
+
)
|
961
1036
|
else:
|
962
1037
|
channel(_("Magnetline was not existing"))
|
963
1038
|
|
@@ -1193,7 +1268,7 @@ class MeerK40tScenePanel(wx.Panel):
|
|
1193
1268
|
|
1194
1269
|
@signal_listener("create_magnets")
|
1195
1270
|
def listen_magnet_creation(self, origin, creation_list, *args):
|
1196
|
-
for
|
1271
|
+
for info, value in creation_list:
|
1197
1272
|
if info == "x":
|
1198
1273
|
self.toggle_x_magnet(value)
|
1199
1274
|
else:
|
@@ -1292,11 +1367,17 @@ class MeerK40tScenePanel(wx.Panel):
|
|
1292
1367
|
|
1293
1368
|
def recognize_background_contours(event=None):
|
1294
1369
|
def image_from_bitmap(myBitmap):
|
1370
|
+
img = myBitmap.ConvertToImage()
|
1371
|
+
buf = img.GetData()
|
1372
|
+
return Image.frombuffer(
|
1373
|
+
"RGB", tuple(myBitmap.GetSize()), bytes(buf), "raw", "RGB", 0, 1
|
1374
|
+
)
|
1295
1375
|
wx_image = myBitmap.ConvertToImage()
|
1296
1376
|
myPilImage = Image.new(
|
1297
1377
|
"RGB", (wx_image.GetWidth(), wx_image.GetHeight())
|
1298
1378
|
)
|
1299
|
-
|
1379
|
+
byte_data = bytes(wx_image.GetData())
|
1380
|
+
myPilImage.frombytes(byte_data)
|
1300
1381
|
return myPilImage
|
1301
1382
|
|
1302
1383
|
if not self.widget_scene.has_background:
|
@@ -1328,6 +1409,8 @@ class MeerK40tScenePanel(wx.Panel):
|
|
1328
1409
|
dpi=500,
|
1329
1410
|
)
|
1330
1411
|
# print (f"Node-Dimensions: {node.bbox()}")
|
1412
|
+
# self.context.elements.elem_branch.add_node(node)
|
1413
|
+
|
1331
1414
|
dlg = ContourDetectionDialog(self, self.context, node)
|
1332
1415
|
dlg.ShowModal()
|
1333
1416
|
dlg.end_dialog()
|
meerk40t/gui/wxmtree.py
CHANGED
@@ -1379,11 +1379,13 @@ class ShadowTree:
|
|
1379
1379
|
@param kwargs:
|
1380
1380
|
@return:
|
1381
1381
|
"""
|
1382
|
-
self.do_not_select = True
|
1383
1382
|
|
1384
1383
|
item = node._item
|
1385
1384
|
if item is None:
|
1386
|
-
|
1385
|
+
print(f"Item was None for node {repr(node)}")
|
1386
|
+
return
|
1387
|
+
|
1388
|
+
self.do_not_select = True
|
1387
1389
|
self.check_validity(item)
|
1388
1390
|
# We might need to update the decorations for all parent objects
|
1389
1391
|
informed = []
|
meerk40t/gui/wxutils.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
"""
|
2
2
|
Mixin functions for wxMeerk40t
|
3
3
|
"""
|
4
|
+
|
4
5
|
import platform
|
5
6
|
from typing import List
|
6
7
|
|
@@ -8,8 +9,8 @@ import wx
|
|
8
9
|
import wx.lib.mixins.listctrl as listmix
|
9
10
|
from wx.lib.scrolledpanel import ScrolledPanel as SP
|
10
11
|
|
11
|
-
from meerk40t.svgelements import Matrix
|
12
12
|
from meerk40t.core.units import ACCEPTED_ANGLE_UNITS, ACCEPTED_UNITS, Angle, Length
|
13
|
+
from meerk40t.svgelements import Matrix
|
13
14
|
|
14
15
|
_ = wx.GetTranslation
|
15
16
|
|
@@ -35,6 +36,7 @@ def get_matrix_scale(matrix):
|
|
35
36
|
res = 1
|
36
37
|
return res
|
37
38
|
|
39
|
+
|
38
40
|
def get_matrix_full_scale(matrix):
|
39
41
|
# We usually use the value_scale_x to establish a pixel size
|
40
42
|
# by counteracting the scene matrix, linewidth = 1 / matrix.value_scale_x()
|
@@ -54,6 +56,7 @@ def get_matrix_full_scale(matrix):
|
|
54
56
|
resy = 1
|
55
57
|
return resx, resy
|
56
58
|
|
59
|
+
|
57
60
|
def get_gc_scale(gc):
|
58
61
|
gcmat = gc.GetTransform()
|
59
62
|
mat_param = gcmat.Get()
|
@@ -67,6 +70,7 @@ def get_gc_scale(gc):
|
|
67
70
|
)
|
68
71
|
return get_matrix_scale(testmatrix)
|
69
72
|
|
73
|
+
|
70
74
|
def get_gc_full_scale(gc):
|
71
75
|
gcmat = gc.GetTransform()
|
72
76
|
mat_param = gcmat.Get()
|
@@ -80,6 +84,7 @@ def get_gc_full_scale(gc):
|
|
80
84
|
)
|
81
85
|
return get_matrix_full_scale(testmatrix)
|
82
86
|
|
87
|
+
|
83
88
|
def create_menu_for_choices(gui, choices: List[dict]) -> wx.Menu:
|
84
89
|
"""
|
85
90
|
Creates a menu for a given choices table.
|
@@ -851,6 +856,7 @@ class TextCtrl(wx.TextCtrl):
|
|
851
856
|
self._action_routine()
|
852
857
|
finally:
|
853
858
|
self._event_generated = None
|
859
|
+
|
854
860
|
return handler
|
855
861
|
|
856
862
|
if not self._default_values:
|
@@ -858,15 +864,20 @@ class TextCtrl(wx.TextCtrl):
|
|
858
864
|
return
|
859
865
|
menu = wx.Menu()
|
860
866
|
has_info = isinstance(self._default_values[0], (list, tuple))
|
861
|
-
item
|
867
|
+
item: wx.MenuItem = menu.Append(wx.ID_ANY, _("Default values..."), "")
|
862
868
|
item.Enable(False)
|
863
869
|
for info in self._default_values:
|
864
|
-
item = menu.Append(
|
865
|
-
|
870
|
+
item = menu.Append(
|
871
|
+
wx.ID_ANY, info[0] if has_info else info, info[1] if has_info else ""
|
872
|
+
)
|
873
|
+
self.Bind(
|
874
|
+
wx.EVT_MENU,
|
875
|
+
set_menu_value(info[0] if has_info else info),
|
876
|
+
id=item.GetId(),
|
877
|
+
)
|
866
878
|
self.PopupMenu(menu)
|
867
879
|
menu.Destroy()
|
868
880
|
|
869
|
-
|
870
881
|
@property
|
871
882
|
def warn_status(self):
|
872
883
|
return self._warn_status
|
@@ -1012,6 +1023,7 @@ class wxCheckBox(wx.CheckBox):
|
|
1012
1023
|
self._tool_tip = tooltip
|
1013
1024
|
super().SetToolTip(self._tool_tip)
|
1014
1025
|
|
1026
|
+
|
1015
1027
|
class wxComboBox(wx.ComboBox):
|
1016
1028
|
"""
|
1017
1029
|
This class wraps around wx.ComboBox and creates a series of mouse over tool tips to permit Linux tooltips that
|
@@ -1239,14 +1251,18 @@ class StaticBoxSizer(wx.StaticBoxSizer):
|
|
1239
1251
|
def SetLabel(self, label):
|
1240
1252
|
self.sbox.SetLabel(label)
|
1241
1253
|
|
1254
|
+
def GetLabel(self):
|
1255
|
+
return self.sbox.GetLabel()
|
1256
|
+
|
1242
1257
|
def Refresh(self, *args):
|
1243
1258
|
self.sbox.Refresh(*args)
|
1244
1259
|
|
1245
|
-
def Enable(self, enable:bool=True):
|
1260
|
+
def Enable(self, enable: bool = True):
|
1246
1261
|
"""Enable or disable the StaticBoxSizer and its children.
|
1247
1262
|
|
1248
1263
|
Enables or disables all children of the sizer recursively.
|
1249
1264
|
"""
|
1265
|
+
|
1250
1266
|
def enem(wind, flag):
|
1251
1267
|
for c in wind.GetChildren():
|
1252
1268
|
enem(c, flag)
|
@@ -1255,6 +1271,7 @@ class StaticBoxSizer(wx.StaticBoxSizer):
|
|
1255
1271
|
|
1256
1272
|
enem(self.sbox, enable)
|
1257
1273
|
|
1274
|
+
|
1258
1275
|
class ScrolledPanel(SP):
|
1259
1276
|
"""
|
1260
1277
|
We sometimes delete things fast enough that they call _SetupAfter when dead and crash.
|
@@ -1268,12 +1285,21 @@ class ScrolledPanel(SP):
|
|
1268
1285
|
except RuntimeError:
|
1269
1286
|
pass
|
1270
1287
|
|
1288
|
+
|
1271
1289
|
class wxListCtrl(wx.ListCtrl):
|
1272
1290
|
"""
|
1273
1291
|
wxListCtrl will extend a regular ListCtrl by saving / restoring column widths
|
1274
1292
|
"""
|
1293
|
+
|
1275
1294
|
def __init__(
|
1276
|
-
self,
|
1295
|
+
self,
|
1296
|
+
parent,
|
1297
|
+
ID=wx.ID_ANY,
|
1298
|
+
pos=wx.DefaultPosition,
|
1299
|
+
size=wx.DefaultSize,
|
1300
|
+
style=0,
|
1301
|
+
context=None,
|
1302
|
+
list_name=None,
|
1277
1303
|
):
|
1278
1304
|
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
|
1279
1305
|
self.context = context
|
@@ -1341,7 +1367,7 @@ class wxListCtrl(wx.ListCtrl):
|
|
1341
1367
|
# print(f"{self.list_name}, cols={self.GetColumnCount()}, available={list_width}, used={total}")
|
1342
1368
|
if total < list_width:
|
1343
1369
|
col = self.GetColumnCount() - 1
|
1344
|
-
if col < 0
|
1370
|
+
if col < 0:
|
1345
1371
|
return False
|
1346
1372
|
# print(f"Will adjust last column from {last} to {last + (list_width - total)}")
|
1347
1373
|
try:
|
@@ -1365,10 +1391,26 @@ class EditableListCtrl(wxListCtrl, listmix.TextEditMixin):
|
|
1365
1391
|
|
1366
1392
|
# ----------------------------------------------------------------------
|
1367
1393
|
def __init__(
|
1368
|
-
self,
|
1394
|
+
self,
|
1395
|
+
parent,
|
1396
|
+
ID=wx.ID_ANY,
|
1397
|
+
pos=wx.DefaultPosition,
|
1398
|
+
size=wx.DefaultSize,
|
1399
|
+
style=0,
|
1400
|
+
context=None,
|
1401
|
+
list_name=None,
|
1369
1402
|
):
|
1370
1403
|
"""Constructor"""
|
1371
|
-
wxListCtrl.__init__(
|
1404
|
+
wxListCtrl.__init__(
|
1405
|
+
self,
|
1406
|
+
parent=parent,
|
1407
|
+
ID=ID,
|
1408
|
+
pos=pos,
|
1409
|
+
size=size,
|
1410
|
+
style=style,
|
1411
|
+
context=context,
|
1412
|
+
list_name=list_name,
|
1413
|
+
)
|
1372
1414
|
listmix.TextEditMixin.__init__(self)
|
1373
1415
|
set_color_according_to_theme(self, "list_bg", "list_fg")
|
1374
1416
|
|
@@ -1451,7 +1493,7 @@ class wxRadioBox(StaticBoxSizer):
|
|
1451
1493
|
id=None,
|
1452
1494
|
label=None,
|
1453
1495
|
choices=None,
|
1454
|
-
majorDimension
|
1496
|
+
majorDimension=0,
|
1455
1497
|
style=0,
|
1456
1498
|
*args,
|
1457
1499
|
**kwargs,
|
@@ -1462,8 +1504,10 @@ class wxRadioBox(StaticBoxSizer):
|
|
1462
1504
|
self._labels = []
|
1463
1505
|
self._tool_tip = None
|
1464
1506
|
self._help = None
|
1465
|
-
super().__init__(
|
1466
|
-
|
1507
|
+
super().__init__(
|
1508
|
+
parent=parent, id=wx.ID_ANY, label=label, orientation=wx.VERTICAL
|
1509
|
+
)
|
1510
|
+
if majorDimension == 0 or style == wx.RA_SPECIFY_ROWS:
|
1467
1511
|
majorDimension = 1000
|
1468
1512
|
container = None
|
1469
1513
|
for idx, c in enumerate(self.choices):
|
@@ -1486,6 +1530,7 @@ class wxRadioBox(StaticBoxSizer):
|
|
1486
1530
|
event.Skip()
|
1487
1531
|
|
1488
1532
|
return mouse
|
1533
|
+
|
1489
1534
|
for ctrl in self._children:
|
1490
1535
|
ctrl.Bind(wx.EVT_MOTION, on_mouse_over_check(ctrl))
|
1491
1536
|
|
@@ -1541,6 +1586,7 @@ class wxRadioBox(StaticBoxSizer):
|
|
1541
1586
|
def Show(self, flag):
|
1542
1587
|
for ctrl in self._children + self._labels:
|
1543
1588
|
ctrl.Show(flag)
|
1589
|
+
super().Show(flag)
|
1544
1590
|
|
1545
1591
|
def Bind(self, event_type, routine):
|
1546
1592
|
self.parent.Bind(event_type, routine, self)
|
@@ -1567,16 +1613,241 @@ class wxRadioBox(StaticBoxSizer):
|
|
1567
1613
|
|
1568
1614
|
def GetHelpText(self):
|
1569
1615
|
return self._help
|
1616
|
+
|
1617
|
+
|
1570
1618
|
class wxStaticText(wx.StaticText):
|
1571
1619
|
def __init__(self, *args, **kwargs):
|
1572
1620
|
super().__init__(*args, **kwargs)
|
1573
1621
|
set_color_according_to_theme(self, "label_bg", "label_fg")
|
1574
1622
|
|
1623
|
+
|
1575
1624
|
class wxListBox(wx.ListBox):
|
1576
1625
|
def __init__(self, *args, **kwargs):
|
1577
1626
|
super().__init__(*args, **kwargs)
|
1578
1627
|
set_color_according_to_theme(self, "list_bg", "list_fg")
|
1579
1628
|
|
1629
|
+
|
1630
|
+
class wxCheckListBox(StaticBoxSizer):
|
1631
|
+
"""
|
1632
|
+
This class recreates the functionality of a wx.CheckListBox, as this class has issues to properly refresh in nested sizers
|
1633
|
+
|
1634
|
+
Known Limitations:
|
1635
|
+
- This custom implementation may not fully replicate all native wx.CheckListBox behaviors.
|
1636
|
+
- Keyboard navigation (e.g., arrow keys, space/enter to toggle) may not work as expected.
|
1637
|
+
- Accessibility features such as screen reader support may be limited or unavailable.
|
1638
|
+
- If your application requires full accessibility or native keyboard handling, consider using the native wx.CheckListBox where possible.
|
1639
|
+
"""
|
1640
|
+
|
1641
|
+
def __init__(
|
1642
|
+
self,
|
1643
|
+
parent=None,
|
1644
|
+
id=None,
|
1645
|
+
label=None,
|
1646
|
+
choices=None,
|
1647
|
+
majorDimension=0,
|
1648
|
+
style=0,
|
1649
|
+
*args,
|
1650
|
+
**kwargs,
|
1651
|
+
):
|
1652
|
+
self.parent = parent
|
1653
|
+
self.choices = choices
|
1654
|
+
self._children = []
|
1655
|
+
self._tool_tip = None
|
1656
|
+
self._help = None
|
1657
|
+
super().__init__(
|
1658
|
+
parent=parent, id=wx.ID_ANY, label=label, orientation=wx.VERTICAL
|
1659
|
+
)
|
1660
|
+
self.majorDimension = majorDimension
|
1661
|
+
self.style = style
|
1662
|
+
self._build_controls()
|
1663
|
+
|
1664
|
+
def _build_controls(self):
|
1665
|
+
"""
|
1666
|
+
Build the controls for the CheckListBox.
|
1667
|
+
This method is called during initialization to create the checkboxes.
|
1668
|
+
"""
|
1669
|
+
if self.choices is None:
|
1670
|
+
self.choices = []
|
1671
|
+
if self.majorDimension == 0 or self.style == wx.RA_SPECIFY_ROWS:
|
1672
|
+
self.majorDimension = 1000
|
1673
|
+
container = None
|
1674
|
+
for idx, c in enumerate(self.choices):
|
1675
|
+
if idx % self.majorDimension == 0:
|
1676
|
+
container = wx.BoxSizer(wx.HORIZONTAL)
|
1677
|
+
self.Add(container, 0, wx.EXPAND, 0)
|
1678
|
+
check_option = wx.CheckBox(self.parent, wx.ID_ANY, label=c)
|
1679
|
+
container.Add(check_option, 1, wx.ALIGN_CENTER_VERTICAL, 0)
|
1680
|
+
self._children.append(check_option)
|
1681
|
+
|
1682
|
+
if platform.system() == "Linux":
|
1683
|
+
|
1684
|
+
def on_mouse_over_check(ctrl):
|
1685
|
+
def mouse(event=None):
|
1686
|
+
ctrl.SetToolTip(self._tool_tip)
|
1687
|
+
event.Skip()
|
1688
|
+
|
1689
|
+
return mouse
|
1690
|
+
|
1691
|
+
for ctrl in self._children:
|
1692
|
+
ctrl.Bind(wx.EVT_MOTION, on_mouse_over_check(ctrl))
|
1693
|
+
|
1694
|
+
for ctrl in self._children:
|
1695
|
+
ctrl.Bind(wx.EVT_CHECKBOX, self.on_check)
|
1696
|
+
ctrl.Bind(wx.EVT_RIGHT_DOWN, self.on_right_click)
|
1697
|
+
|
1698
|
+
for ctrl in self._children:
|
1699
|
+
set_color_according_to_theme(ctrl, "text_bg", "text_fg")
|
1700
|
+
|
1701
|
+
@property
|
1702
|
+
def Children(self):
|
1703
|
+
return self._children
|
1704
|
+
|
1705
|
+
def GetParent(self):
|
1706
|
+
return self.parent
|
1707
|
+
|
1708
|
+
def SetToolTip(self, tooltip):
|
1709
|
+
self._tool_tip = tooltip
|
1710
|
+
for ctrl in self._children:
|
1711
|
+
ctrl.SetToolTip(self._tool_tip)
|
1712
|
+
|
1713
|
+
def Disable(self):
|
1714
|
+
self.Enable(False)
|
1715
|
+
|
1716
|
+
def EnableItem(self, n, flag):
|
1717
|
+
if 0 <= n < len(self._children):
|
1718
|
+
self._children[n].Enable(flag)
|
1719
|
+
|
1720
|
+
def Enable(self, flag):
|
1721
|
+
for ctrl in self._children:
|
1722
|
+
ctrl.Enable(flag)
|
1723
|
+
|
1724
|
+
def Hide(self):
|
1725
|
+
self.Show(False)
|
1726
|
+
|
1727
|
+
def Show(self, flag):
|
1728
|
+
for ctrl in self._children:
|
1729
|
+
ctrl.Show(flag)
|
1730
|
+
self.ShowItems(flag)
|
1731
|
+
|
1732
|
+
# def Bind(self, event_type, routine):
|
1733
|
+
# self.parent.Bind(event_type, routine, self)
|
1734
|
+
|
1735
|
+
def on_check(self, orgevent):
|
1736
|
+
#
|
1737
|
+
event = orgevent.Clone()
|
1738
|
+
event.SetEventType(wx.wxEVT_CHECKLISTBOX)
|
1739
|
+
event.SetId(self.Id)
|
1740
|
+
event.SetEventObject(self)
|
1741
|
+
# event.Int = self.GetSelection()
|
1742
|
+
wx.PostEvent(self.parent, event)
|
1743
|
+
|
1744
|
+
def on_right_click(self, event):
|
1745
|
+
menu = wx.Menu()
|
1746
|
+
parent = self.parent
|
1747
|
+
item = menu.Append(wx.ID_ANY, _("Check all"), "")
|
1748
|
+
parent.Bind(
|
1749
|
+
wx.EVT_MENU,
|
1750
|
+
lambda e: self.SetCheckedItems(range(len(self._children))),
|
1751
|
+
id=item.GetId(),
|
1752
|
+
)
|
1753
|
+
item = menu.Append(wx.ID_ANY, _("Uncheck all"), "")
|
1754
|
+
parent.Bind(wx.EVT_MENU, lambda e: self.SetCheckedItems([]), id=item.GetId())
|
1755
|
+
item = menu.Append(wx.ID_ANY, _("Invert selection"), "")
|
1756
|
+
parent.Bind(
|
1757
|
+
wx.EVT_MENU,
|
1758
|
+
lambda e: self.SetCheckedItems(
|
1759
|
+
[
|
1760
|
+
i
|
1761
|
+
for i in range(len(self._children))
|
1762
|
+
if not self._children[i].GetValue()
|
1763
|
+
]
|
1764
|
+
),
|
1765
|
+
id=item.GetId(),
|
1766
|
+
)
|
1767
|
+
parent.PopupMenu(menu)
|
1768
|
+
menu.Destroy()
|
1769
|
+
|
1770
|
+
def SetForegroundColour(self, wc):
|
1771
|
+
for ctrl in self._children:
|
1772
|
+
ctrl.SetForegroundColour(wc)
|
1773
|
+
|
1774
|
+
def SetBackgroundColour(self, wc):
|
1775
|
+
for ctrl in self._children:
|
1776
|
+
ctrl.SetBackgroundColour(wc)
|
1777
|
+
|
1778
|
+
def SetHelpText(self, help):
|
1779
|
+
self._help = help
|
1780
|
+
|
1781
|
+
def GetHelpText(self):
|
1782
|
+
return self._help
|
1783
|
+
|
1784
|
+
def Clear(self) -> None:
|
1785
|
+
with wx.BusyCursor():
|
1786
|
+
for child in self._children:
|
1787
|
+
child.Destroy()
|
1788
|
+
self._children.clear()
|
1789
|
+
self.choices.clear()
|
1790
|
+
|
1791
|
+
def Check(self, item: int, check: bool = True) -> None:
|
1792
|
+
"""
|
1793
|
+
Check or uncheck an item in the CheckListBox.
|
1794
|
+
:param item: The index of the item to check or uncheck.
|
1795
|
+
:param check: True to check, False to uncheck.
|
1796
|
+
"""
|
1797
|
+
if 0 <= item < len(self._children):
|
1798
|
+
self._children[item].SetValue(check)
|
1799
|
+
|
1800
|
+
def GetCheckItems(self) -> list:
|
1801
|
+
"""
|
1802
|
+
Get a list of indices of checked items in the CheckListBox.
|
1803
|
+
:return: A list of indices of checked items.
|
1804
|
+
"""
|
1805
|
+
return [idx for idx, ctrl in enumerate(self._children) if ctrl.GetValue()]
|
1806
|
+
|
1807
|
+
def GetCheckedStrings(self) -> list:
|
1808
|
+
"""
|
1809
|
+
Get a list of strings of checked items in the CheckListBox.
|
1810
|
+
:return: A list of strings of checked items.
|
1811
|
+
"""
|
1812
|
+
return [self.choices[idx] for idx in self.GetCheckItems()]
|
1813
|
+
|
1814
|
+
def GetSelections(self) -> list:
|
1815
|
+
"""
|
1816
|
+
Get a list of indices of selected items in the CheckListBox.
|
1817
|
+
:return: A list of indices of selected items.
|
1818
|
+
"""
|
1819
|
+
return self.GetCheckItems()
|
1820
|
+
|
1821
|
+
def SetCheckedStrings(self, choices):
|
1822
|
+
"""
|
1823
|
+
Set the checked items in the CheckListBox based on a list of strings.
|
1824
|
+
:param choices: A list of strings to check.
|
1825
|
+
"""
|
1826
|
+
for idx, choice in enumerate(self.choices):
|
1827
|
+
if choice in choices:
|
1828
|
+
self.Check(idx, True)
|
1829
|
+
else:
|
1830
|
+
self.Check(idx, False)
|
1831
|
+
|
1832
|
+
def SetCheckedItems(self, choices):
|
1833
|
+
"""
|
1834
|
+
Set the checked items in the CheckListBox based on a list of indices.
|
1835
|
+
:param choices: A list of indices to check.
|
1836
|
+
"""
|
1837
|
+
for idx in range(len(self._children)):
|
1838
|
+
self.Check(idx, idx in choices)
|
1839
|
+
|
1840
|
+
def Set(self, choices):
|
1841
|
+
"""
|
1842
|
+
Set the choices for the CheckListBox.
|
1843
|
+
:param choices: A list of strings to set as choices.
|
1844
|
+
"""
|
1845
|
+
# print (f"Setting choices for {self.GetLabel()}: {choices}")
|
1846
|
+
self.Clear()
|
1847
|
+
self.choices = list(choices)
|
1848
|
+
self._build_controls()
|
1849
|
+
|
1850
|
+
|
1580
1851
|
##############
|
1581
1852
|
# GUI KEYSTROKE FUNCTIONS
|
1582
1853
|
##############
|
@@ -1752,8 +2023,8 @@ def set_ctrl_value(ctrl, value):
|
|
1752
2023
|
try:
|
1753
2024
|
cursor = ctrl.GetInsertionPoint()
|
1754
2025
|
if ctrl.GetValue() != value:
|
1755
|
-
ctrl.SetValue(value)
|
1756
|
-
ctrl.SetInsertionPoint(min(len(value), cursor))
|
2026
|
+
ctrl.SetValue(str(value))
|
2027
|
+
ctrl.SetInsertionPoint(min(len(str(value)), cursor))
|
1757
2028
|
except RuntimeError:
|
1758
2029
|
# Control might already have been destroyed
|
1759
2030
|
pass
|