Skip to content

Commit e23edfe

Browse files
committed
Optimize paste from clipboard (see #17)
1 parent b723d51 commit e23edfe

File tree

2 files changed

+107
-34
lines changed

2 files changed

+107
-34
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Enhanced HexEditor:
1919
- Add INSERT key to add a single byte (#20)
2020
- Add BACK and DELETE key in hex cell editor (#20)
2121
- Add basic arrow keys in hex cell editor doing the same as Escape (#20)
22+
- Optimize paste from clipboard (#17)
2223

2324
-------------------------------------------------------------------------------
2425
## [0.0.19] - 2022-09-07

construct_editor/wx_widgets/wx_hex_editor.py

Lines changed: 106 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import dataclasses
33
import logging
44
import math
5+
import re
56
import typing as t
67

78
import wx
@@ -15,8 +16,6 @@
1516
logger.propagate = False
1617

1718

18-
19-
2019
# #####################################################################################################################
2120
# ############################################## HexEditorBinaryData ##################################################
2221
# #####################################################################################################################
@@ -600,6 +599,20 @@ def Clone(self):
600599
return HexCellEditor(self.parentgrid)
601600

602601

602+
@dataclasses.dataclass
603+
class ContextMenuItem:
604+
wx_id: int
605+
name: str
606+
callback: t.Callable[[wx.CommandEvent], t.Any]
607+
608+
# None = option
609+
# True = toggle selected
610+
# False = toggle unselected
611+
toggle_state: t.Optional[bool]
612+
613+
enabled: bool
614+
615+
603616
# #####################################################################################################################
604617
# ############################################## Grid.Grid ############################################################
605618
# #####################################################################################################################
@@ -620,7 +633,9 @@ def __init__(
620633
self._table = table
621634
self._binary_data = binary_data
622635
self.read_only = read_only
623-
self.on_selection_changed: "CallbackList[[int, t.Optional[int]]]" = CallbackList()
636+
self.on_selection_changed: "CallbackList[[int, t.Optional[int]]]" = (
637+
CallbackList()
638+
)
624639

625640
# The second parameter means that the grid is to take
626641
# ownership of the table and will destroy it when done.
@@ -865,7 +880,7 @@ def _remove_selection(self) -> bool:
865880

866881
self.ClearSelection()
867882
self._selection = (None, None)
868-
883+
869884
idx = self._table.get_byte_idx(self.GetGridCursorRow(), self.GetGridCursorCol())
870885
if idx > len(self._editor.binary):
871886
self.SetGridCursor(0, 0)
@@ -953,17 +968,9 @@ def _paste(self, overwrite: bool = False, insert: bool = False) -> bool:
953968
clipboard = wx.TextDataObject()
954969
wx.TheClipboard.GetData(clipboard)
955970
wx.TheClipboard.Close()
956-
byts_str: str = clipboard.GetText()
957-
958-
# convert string to bytes
959-
try:
960-
byts_str = byts_str.replace(" ", "")
961-
byts = bytes.fromhex(byts_str)
962-
except Exception as e:
963-
wx.MessageBox(
964-
f"Can't convert data from clipboard to bytes.\n\n{str(e)}\n\nClipboard Data:\n{byts_str}",
965-
"Warning",
966-
)
971+
clipboard_txt: str = clipboard.GetText()
972+
byts = self.string_to_byts(clipboard_txt)
973+
if not byts:
967974
return False
968975

969976
# copy new data to the binary data
@@ -975,6 +982,51 @@ def _paste(self, overwrite: bool = False, insert: bool = False) -> bool:
975982
self.select_range(sel[0], sel[0] + len(byts) - 1)
976983
return True
977984

985+
def string_to_byts(self, byts_str: str):
986+
"""
987+
Normalize pasted string converting it to bytes (can be overridden).
988+
Return a "bytes" variable, or None in case or error.
989+
990+
Possible Strings:
991+
- 0102030405060708090a0b0c0d0e0f
992+
- 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
993+
- 01.02.03.04.05.06.07.08.09.0a.0b.0c.0d.0e.0f
994+
- 01-02-03-04-05-06-07-08-09-0a-0b-0c-0d-0e-0f
995+
- 1,2,3,4,5,6,7,8,9,a,b,c,d,e,f
996+
- 1 2 3 4 5 6 7 8 9 a b c d e f
997+
- "71 20 98 00 c0 7a 3e 6a 8d 7c c4 0d 04 10 02 f2 00"
998+
- ("71 20 98 00 c0 7a 3e 6a 8d 7c c4 0d 04 10 02 f2 00")
999+
- bytes.fromhex("71 20 98 00 c0 7a 3e 6a 8d 7c c4 0d 04 10 02 f2 00")
1000+
- '71209800c07a3e6a8d7cc40d041002f200'
1001+
- 0x12, 0x23, 0x45,
1002+
- b'\x00\x00\x00\xa4\xc18\xe1\x81_\x00\xcc#b\x0c\r\x15'
1003+
"""
1004+
byts_str = re.sub(r".*[\"'](.*)[\"'].*", r"\1", byts_str)
1005+
try:
1006+
byts = bytes.fromhex(byts_str)
1007+
return byts
1008+
except Exception:
1009+
pass
1010+
try:
1011+
byts_str_conv = re.findall(r"[0-9A-Fa-fXx]+", byts_str)
1012+
byts = bytes(map(lambda x: int(x, 16), byts_str_conv))
1013+
return byts
1014+
except Exception:
1015+
pass
1016+
try:
1017+
byts = bytes(
1018+
byts_str.encode("utf8", "backslashreplace").decode("unicode_escape"),
1019+
encoding="raw_unicode_escape",
1020+
)
1021+
return byts
1022+
except Exception as e:
1023+
wx.MessageBox(
1024+
f"Can't convert data from clipboard to bytes.\n\n{str(e)}"
1025+
f"\n\nProcessed clipboard data:\n{byts_str}",
1026+
"Warning",
1027+
)
1028+
return None
1029+
9781030
def _undo(self):
9791031
self._binary_data.command_processor.Undo()
9801032

@@ -1068,53 +1120,73 @@ def _on_cell_right_click(self, event: Grid.GridEvent):
10681120
if select_cell:
10691121
self.SetGridCursor(event.GetRow(), event.GetCol())
10701122

1071-
menus = [
1072-
(
1123+
popup_menu = wx.Menu()
1124+
for menu in self.build_context_menu():
1125+
if menu is None:
1126+
popup_menu.AppendSeparator()
1127+
continue
1128+
if menu.toggle_state != None: # checkbox boolean state
1129+
item: wx.MenuItem = popup_menu.AppendCheckItem(menu.wx_id, menu.name)
1130+
item.Check(menu.toggle_state)
1131+
else:
1132+
item: wx.MenuItem = popup_menu.Append(menu.wx_id, menu.name)
1133+
self.Bind(wx.EVT_MENU, menu.callback, id=item.Id)
1134+
item.Enable(menu.enabled)
1135+
1136+
self.PopupMenu(popup_menu, event.GetPosition())
1137+
popup_menu.Destroy()
1138+
1139+
def build_context_menu(
1140+
self,
1141+
) -> t.List[t.Optional[ContextMenuItem]]:
1142+
"""Build the context menu. Can be overridden."""
1143+
1144+
return [
1145+
ContextMenuItem(
10731146
wx.ID_CUT,
10741147
"Cut\tCtrl+X",
10751148
lambda event: self._cut_selection(),
1149+
None,
10761150
not self.read_only,
10771151
),
1078-
(wx.ID_COPY, "Copy\tCtrl+C", lambda event: self._copy_selection(), True),
1079-
(
1152+
ContextMenuItem(
1153+
wx.ID_COPY,
1154+
"Copy\tCtrl+C",
1155+
lambda event: self._copy_selection(),
1156+
None,
1157+
True,
1158+
),
1159+
ContextMenuItem(
10801160
wx.ID_PASTE,
10811161
"Paste (overwrite)\tCtrl+V",
10821162
lambda event: self._paste(overwrite=True),
1163+
None,
10831164
not self.read_only,
10841165
),
1085-
(
1166+
ContextMenuItem(
10861167
wx.ID_PASTE,
10871168
"Paste (insert)\tCtrl+Shift+V",
10881169
lambda event: self._paste(insert=True),
1170+
None,
10891171
not self.read_only,
10901172
),
10911173
None,
1092-
(
1174+
ContextMenuItem(
10931175
wx.ID_UNDO,
10941176
"Undo\tCtrl+Z",
10951177
lambda event: self._undo(),
1178+
None,
10961179
self._binary_data.command_processor.CanUndo(),
10971180
),
1098-
(
1181+
ContextMenuItem(
10991182
wx.ID_REDO,
11001183
"Redo\tCtrl+Y",
11011184
lambda event: self._redo(),
1185+
None,
11021186
self._binary_data.command_processor.CanRedo(),
11031187
),
11041188
]
11051189

1106-
popup_menu = wx.Menu()
1107-
for menu in menus:
1108-
if menu is None:
1109-
popup_menu.AppendSeparator()
1110-
continue
1111-
item: wx.MenuItem = popup_menu.Append(menu[0], menu[1])
1112-
self.Bind(wx.EVT_MENU, menu[2], id=item.Id)
1113-
item.Enable(menu[3])
1114-
1115-
self.PopupMenu(popup_menu, event.GetPosition())
1116-
popup_menu.Destroy()
1117-
11181190

11191191
# #####################################################################################################################
11201192
# ############################################## WxHexEditor ##########################################################

0 commit comments

Comments
 (0)