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.
Files changed (85) hide show
  1. meerk40t/balormk/clone_loader.py +3 -2
  2. meerk40t/balormk/controller.py +38 -13
  3. meerk40t/balormk/cylindermod.py +1 -0
  4. meerk40t/balormk/device.py +13 -9
  5. meerk40t/balormk/driver.py +9 -2
  6. meerk40t/balormk/galvo_commands.py +3 -1
  7. meerk40t/balormk/gui/gui.py +6 -0
  8. meerk40t/balormk/livelightjob.py +338 -321
  9. meerk40t/balormk/mock_connection.py +4 -3
  10. meerk40t/balormk/usb_connection.py +11 -2
  11. meerk40t/camera/camera.py +19 -14
  12. meerk40t/camera/gui/camerapanel.py +6 -0
  13. meerk40t/core/cutplan.py +101 -78
  14. meerk40t/core/elements/element_treeops.py +435 -140
  15. meerk40t/core/elements/elements.py +100 -9
  16. meerk40t/core/elements/shapes.py +259 -72
  17. meerk40t/core/elements/tree_commands.py +10 -5
  18. meerk40t/core/node/blobnode.py +19 -4
  19. meerk40t/core/node/elem_ellipse.py +18 -8
  20. meerk40t/core/node/elem_image.py +51 -19
  21. meerk40t/core/node/elem_line.py +18 -8
  22. meerk40t/core/node/elem_path.py +18 -8
  23. meerk40t/core/node/elem_point.py +10 -4
  24. meerk40t/core/node/elem_polyline.py +19 -11
  25. meerk40t/core/node/elem_rect.py +18 -8
  26. meerk40t/core/node/elem_text.py +11 -5
  27. meerk40t/core/node/filenode.py +2 -8
  28. meerk40t/core/node/groupnode.py +11 -11
  29. meerk40t/core/node/image_processed.py +11 -5
  30. meerk40t/core/node/image_raster.py +11 -5
  31. meerk40t/core/node/node.py +64 -16
  32. meerk40t/core/node/refnode.py +2 -1
  33. meerk40t/core/planner.py +25 -11
  34. meerk40t/core/svg_io.py +91 -34
  35. meerk40t/device/dummydevice.py +7 -1
  36. meerk40t/extra/vtracer.py +222 -0
  37. meerk40t/grbl/device.py +96 -9
  38. meerk40t/grbl/driver.py +15 -5
  39. meerk40t/gui/about.py +20 -0
  40. meerk40t/gui/devicepanel.py +20 -16
  41. meerk40t/gui/gui_mixins.py +4 -0
  42. meerk40t/gui/icons.py +330 -253
  43. meerk40t/gui/laserpanel.py +27 -3
  44. meerk40t/gui/laserrender.py +41 -21
  45. meerk40t/gui/magnetoptions.py +158 -65
  46. meerk40t/gui/materialtest.py +569 -310
  47. meerk40t/gui/navigationpanels.py +229 -24
  48. meerk40t/gui/propertypanels/hatchproperty.py +2 -0
  49. meerk40t/gui/propertypanels/imageproperty.py +160 -106
  50. meerk40t/gui/propertypanels/wobbleproperty.py +6 -2
  51. meerk40t/gui/ribbon.py +6 -1
  52. meerk40t/gui/scenewidgets/gridwidget.py +29 -32
  53. meerk40t/gui/scenewidgets/rectselectwidget.py +190 -192
  54. meerk40t/gui/simulation.py +75 -77
  55. meerk40t/gui/spoolerpanel.py +27 -7
  56. meerk40t/gui/statusbarwidgets/defaultoperations.py +84 -48
  57. meerk40t/gui/statusbarwidgets/infowidget.py +2 -2
  58. meerk40t/gui/tips.py +15 -1
  59. meerk40t/gui/toolwidgets/toolpointmove.py +3 -1
  60. meerk40t/gui/wxmmain.py +242 -114
  61. meerk40t/gui/wxmscene.py +107 -24
  62. meerk40t/gui/wxmtree.py +4 -2
  63. meerk40t/gui/wxutils.py +286 -15
  64. meerk40t/image/imagetools.py +129 -65
  65. meerk40t/internal_plugins.py +4 -0
  66. meerk40t/kernel/kernel.py +67 -18
  67. meerk40t/kernel/settings.py +28 -9
  68. meerk40t/lihuiyu/device.py +24 -12
  69. meerk40t/main.py +14 -9
  70. meerk40t/moshi/device.py +20 -6
  71. meerk40t/network/console_server.py +22 -6
  72. meerk40t/newly/device.py +10 -3
  73. meerk40t/newly/gui/gui.py +10 -0
  74. meerk40t/ruida/device.py +22 -2
  75. meerk40t/ruida/loader.py +9 -4
  76. meerk40t/ruida/rdjob.py +48 -8
  77. meerk40t/tools/geomstr.py +240 -123
  78. meerk40t/tools/rasterplotter.py +185 -94
  79. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/METADATA +1 -1
  80. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/RECORD +85 -84
  81. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/LICENSE +0 -0
  82. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/WHEEL +0 -0
  83. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/entry_points.txt +0 -0
  84. {meerk40t-0.9.7030.dist-info → meerk40t-0.9.7050.dist-info}/top_level.txt +0 -0
  85. {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, wx.ID_ANY, context=self.context, node=node, simplified=True, direct_mode=True,
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(self.context.kernel.os_information["WORKDIR"], "magnets.cfg")
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:") + "\n" +
896
- _("clear x - clear y : will clear all magnets on the given axis") + "\n" +
897
- _("set x <pos> - set y <pos>: will set a magnet line on the given axis") + "\n" +
898
- _("delete x <pos> - delete y <pos>: will delete the magnet line on the given axis")
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 pos:
910
- try:
911
- rel_len = self.context.device.view.width if axis == "X" else self.context.device.view.height
912
- value = float(Length(pos, relative_length=rel_len))
913
- except ValueError:
914
- info (f"Invalid length: {pos}")
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
- if action != "clear" and value is None:
918
- info(_("You need to provide a position"))
919
- return
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 (_("Deleted {count} magnet lines on axis {axis}").format(axis=axis, count=count))
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(_("Magnetline appended at {pos} on axis {axis}").format(pos=pos, axis=axis))
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(_("Magnetline removed at {pos} on axis {axis}").format(pos=pos, axis=axis))
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 (info, value) in creation_list:
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
- myPilImage.frombytes(wx_image.GetData())
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
- raise ValueError(f"Item was None for node {repr(node)}")
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 : wx.MenuItem = menu.Append(wx.ID_ANY, _("Default values..."), "")
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(wx.ID_ANY, info[0] if has_info else info, info[1] if has_info else "")
865
- self.Bind(wx.EVT_MENU, set_menu_value(info[0] if has_info else info), id=item.GetId())
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, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, context=None, list_name=None
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, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, context=None, list_name=None,
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__(self, parent=parent, ID=ID, pos=pos, size=size, style=style, context=context, list_name=list_name)
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 = 0,
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__(parent=parent, id=wx.ID_ANY, label=label, orientation=wx.VERTICAL)
1466
- if majorDimension == 0 or style==wx.RA_SPECIFY_ROWS:
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