Skip to content

Commit 6d0a0cd

Browse files
committed
Added exception dialog to show the complete parse/build exception.
1 parent 5ae18e0 commit 6d0a0cd

5 files changed

Lines changed: 140 additions & 79 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Enhanced ConstructEditor:
1313
- Implemented "Copy" / Ctrl+C (#18)
1414
- Added "Copy path to clipboard" button in the context menu (#18)
1515
- Fixed a bug, when a struct has multiple times the same `Enum` construct. Then the metadata (eg. byte position) of the last parsed enum value is used for all enum values.
16+
- Added exception dialog to show the complete parse/build exception.
1617

1718
Enhanced HexEditor:
1819
- fix crash when selecting or extending selection before the beginning of the hex editor using the shift LEFT and UP arrow keys (#20)

construct_editor/core/construct_editor.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ def reload(self):
3030
"""
3131

3232
@abc.abstractmethod
33-
def show_parse_error_message(self, msg: t.Optional[str]):
33+
def show_parse_error_message(self, msg: t.Optional[str], ex: t.Optional[Exception]):
3434
"""
3535
Show an parse error message to the user.
3636
3737
This has to be implemented by the derived class.
3838
"""
3939

4040
@abc.abstractmethod
41-
def show_build_error_message(self, msg: t.Optional[str]):
41+
def show_build_error_message(self, msg: t.Optional[str], ex: t.Optional[Exception]):
4242
"""
4343
Show an build error message to the user.
4444
@@ -118,8 +118,8 @@ def change_construct(self, constr: cs.Construct) -> None:
118118
Change the construct format, that is used for building/parsing.
119119
"""
120120
# reset error messages
121-
self.show_build_error_message(None)
122-
self.show_parse_error_message(None)
121+
self.show_build_error_message(None, None)
122+
self.show_parse_error_message(None, None)
123123

124124
# add root name, is none is available
125125
if constr.name is None:
@@ -149,10 +149,10 @@ def parse(self, binary: bytes, **contextkw: t.Any):
149149
"""
150150
try:
151151
self._model.root_obj = self._construct.parse(binary, **contextkw)
152-
self.show_parse_error_message(None)
152+
self.show_parse_error_message(None, None)
153153
except Exception as e:
154154
self.show_parse_error_message(
155-
f"Error while parsing binary data: {type(e).__name__}\n{str(e)}"
155+
f"Error while parsing binary data: {type(e).__name__}\n{str(e)}", e
156156
)
157157
self._model.root_obj = None
158158

@@ -166,10 +166,10 @@ def build(self, **contextkw: t.Any) -> bytes:
166166
"""
167167
try:
168168
binary = self._construct.build(self._model.root_obj, **contextkw)
169-
self.show_build_error_message(None)
169+
self.show_build_error_message(None, None)
170170
except Exception as e:
171171
self.show_build_error_message(
172-
f"Error while building binary data: {type(e).__name__}\n{str(e)}"
172+
f"Error while building binary data: {type(e).__name__}\n{str(e)}", e
173173
)
174174
raise e
175175

construct_editor/main.py

Lines changed: 7 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import sys
2-
import traceback
32
import typing as t
43
from pathlib import Path
54
from types import TracebackType
@@ -45,6 +44,10 @@
4544
import construct_editor.gallery.test_tflagsenum
4645
import construct_editor.gallery.test_timestamp
4746
from construct_editor.wx_widgets import WxConstructHexEditor
47+
from construct_editor.wx_widgets.wx_exception_dialog import (
48+
ExceptionInfo,
49+
WxExceptionDialog,
50+
)
4851

4952

5053
class ConstructGalleryFrame(wx.Frame):
@@ -75,7 +78,9 @@ def on_uncaught_exception(
7578
:param string `trace`: the traceback header, if any (otherwise, it prints the
7679
standard Python header: ``Traceback (most recent call last)``.
7780
"""
78-
dial = ExceptionDialog(None, etype, value, trace)
81+
dial = WxExceptionDialog(
82+
None, "Uncaught Exception...", ExceptionInfo(etype, value, trace)
83+
)
7984
dial.ShowModal()
8085

8186

@@ -396,73 +401,6 @@ def on_load_binary_file_clicked(self, event):
396401
)
397402

398403

399-
class ExceptionDialog(wx.Dialog):
400-
def __init__(
401-
self,
402-
parent,
403-
etype: t.Type[BaseException],
404-
value: BaseException,
405-
trace: TracebackType,
406-
):
407-
wx.Dialog.__init__(
408-
self,
409-
parent,
410-
id=wx.ID_ANY,
411-
title="Uncaught Exception...",
412-
pos=wx.DefaultPosition,
413-
size=wx.Size(800, 600),
414-
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
415-
)
416-
417-
self._init_gui()
418-
419-
self.exception_txt.SetValue(
420-
"".join(traceback.format_exception_only(etype, value))
421-
)
422-
self.traceback_txt.SetValue("".join(traceback.format_tb(trace)))
423-
424-
def _init_gui(self):
425-
self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
426-
427-
sizer = wx.BoxSizer(wx.VERTICAL)
428-
429-
self.ok_btn = wx.Button(
430-
self, wx.ID_ANY, "OK", wx.DefaultPosition, wx.DefaultSize, 0
431-
)
432-
sizer.Add(self.ok_btn, 0, wx.ALL | wx.EXPAND, 5)
433-
434-
self.exception_txt = wx.TextCtrl(
435-
self,
436-
wx.ID_ANY,
437-
wx.EmptyString,
438-
wx.DefaultPosition,
439-
wx.Size(-1, -1),
440-
wx.TE_MULTILINE | wx.TE_READONLY,
441-
)
442-
sizer.Add(self.exception_txt, 1, wx.ALL | wx.EXPAND, 5)
443-
444-
self.traceback_txt = wx.TextCtrl(
445-
self,
446-
wx.ID_ANY,
447-
wx.EmptyString,
448-
wx.DefaultPosition,
449-
wx.Size(-1, -1),
450-
wx.TE_MULTILINE | wx.TE_READONLY,
451-
)
452-
sizer.Add(self.traceback_txt, 2, wx.ALL | wx.EXPAND, 5)
453-
454-
self.SetSizer(sizer)
455-
self.Layout()
456-
457-
self.Centre(wx.BOTH)
458-
459-
# Connect Events
460-
self.ok_btn.Bind(wx.EVT_BUTTON, self.on_ok_clicked)
461-
462-
def on_ok_clicked(self, event):
463-
self.Close()
464-
465-
466404
def main():
467405
if sys.platform == "win32":
468406
# Windows Icon fix: https://stackoverflow.com/a/1552105

construct_editor/wx_widgets/wx_construct_editor.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
create_obj_editor,
1818
create_obj_renderer_helper,
1919
)
20+
from construct_editor.wx_widgets.wx_exception_dialog import WxExceptionDialog
2021

2122

2223
@dataclasses.dataclass
@@ -306,9 +307,21 @@ def _init_gui(self):
306307

307308
# Create InfoBars
308309
self._parse_error_info_bar = wx.InfoBar(self)
310+
btn_id = wx.NewIdRef()
311+
self._parse_error_info_bar.AddButton(btn_id, "Exception Infos")
312+
self._parse_error_info_bar.Bind(
313+
wx.EVT_BUTTON, self._parse_error_info_bar_btn_clicked, id=btn_id
314+
)
315+
self._parse_error_ex: t.Optional[Exception] = None
309316
vsizer.Add(self._parse_error_info_bar, 0, wx.EXPAND)
310317

311318
self._build_error_info_bar = wx.InfoBar(self)
319+
btn_id = wx.NewIdRef()
320+
self._build_error_info_bar.AddButton(btn_id, "Exception Infos")
321+
self._build_error_info_bar.Bind(
322+
wx.EVT_BUTTON, self._build_error_info_bar_btn_clicked, id=btn_id
323+
)
324+
self._build_error_ex: t.Optional[Exception] = None
312325
vsizer.Add(self._build_error_info_bar, 0, wx.EXPAND)
313326

314327
# create status bar
@@ -373,22 +386,24 @@ def reload(self):
373386
finally:
374387
self.Thaw()
375388

376-
def show_parse_error_message(self, msg: t.Optional[str]):
389+
def show_parse_error_message(self, msg: t.Optional[str], ex: t.Optional[Exception]):
377390
"""
378391
Show an message to the user.
379392
"""
380393
if msg is None:
381394
self._parse_error_info_bar.Dismiss()
382395
else:
396+
self._parse_error_ex = ex
383397
self._parse_error_info_bar.ShowMessage(msg, wx.ICON_WARNING)
384398

385-
def show_build_error_message(self, msg: t.Optional[str]):
399+
def show_build_error_message(self, msg: t.Optional[str], ex: t.Optional[Exception]):
386400
"""
387401
Show an build error message to the user.
388402
"""
389403
if msg is None:
390404
self._build_error_info_bar.Dismiss()
391405
else:
406+
self._build_error_ex = ex
392407
self._build_error_info_bar.ShowMessage(msg, wx.ICON_WARNING)
393408

394409
def show_status(self, path_info: str, bytes_info: str):
@@ -664,3 +679,17 @@ def _get_from_clipboard(self):
664679
wx.TheClipboard.Close()
665680
txt: str = clipboard.GetText()
666681
return txt
682+
683+
def _parse_error_info_bar_btn_clicked(self, event):
684+
if self._parse_error_ex is None:
685+
return
686+
687+
dial = WxExceptionDialog(None, "Parse error", self._parse_error_ex)
688+
dial.ShowModal()
689+
690+
def _build_error_info_bar_btn_clicked(self, event):
691+
if self._build_error_ex is None:
692+
return
693+
694+
dial = WxExceptionDialog(None, "Build error", self._build_error_ex)
695+
dial.ShowModal()
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import dataclasses
2+
import traceback
3+
import typing as t
4+
from types import TracebackType
5+
6+
import wx
7+
8+
9+
@dataclasses.dataclass
10+
class ExceptionInfo:
11+
etype: t.Type[BaseException]
12+
value: BaseException
13+
trace: t.Optional[TracebackType]
14+
15+
16+
class WxExceptionDialog(wx.Dialog):
17+
def __init__(
18+
self, parent, title: str, exception: t.Union[ExceptionInfo, BaseException]
19+
):
20+
wx.Dialog.__init__(
21+
self,
22+
parent,
23+
id=wx.ID_ANY,
24+
title=title,
25+
pos=wx.DefaultPosition,
26+
size=wx.Size(800, 600),
27+
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
28+
)
29+
30+
self._init_gui()
31+
32+
if isinstance(exception, ExceptionInfo):
33+
exception_info = exception
34+
else:
35+
exception_info = ExceptionInfo(
36+
type(exception), exception, exception.__traceback__
37+
)
38+
39+
self.exception_txt.SetValue(
40+
"".join(
41+
traceback.format_exception_only(
42+
exception_info.etype, exception_info.value
43+
)
44+
)
45+
)
46+
47+
if exception_info.trace is None:
48+
self.traceback_txt.SetValue("")
49+
else:
50+
self.traceback_txt.SetValue(
51+
"".join(traceback.format_tb(exception_info.trace))
52+
)
53+
54+
def _init_gui(self):
55+
self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
56+
57+
sizer = wx.BoxSizer(wx.VERTICAL)
58+
59+
self.ok_btn = wx.Button(
60+
self, wx.ID_ANY, "OK", wx.DefaultPosition, wx.DefaultSize, 0
61+
)
62+
sizer.Add(self.ok_btn, 0, wx.ALL | wx.EXPAND, 5)
63+
64+
self.exception_txt = wx.TextCtrl(
65+
self,
66+
wx.ID_ANY,
67+
wx.EmptyString,
68+
wx.DefaultPosition,
69+
wx.Size(-1, -1),
70+
wx.TE_MULTILINE | wx.TE_READONLY,
71+
)
72+
sizer.Add(self.exception_txt, 1, wx.ALL | wx.EXPAND, 5)
73+
74+
self.traceback_txt = wx.TextCtrl(
75+
self,
76+
wx.ID_ANY,
77+
wx.EmptyString,
78+
wx.DefaultPosition,
79+
wx.Size(-1, -1),
80+
wx.TE_MULTILINE | wx.TE_READONLY,
81+
)
82+
sizer.Add(self.traceback_txt, 2, wx.ALL | wx.EXPAND, 5)
83+
84+
self.SetSizer(sizer)
85+
self.Layout()
86+
87+
self.Centre(wx.BOTH)
88+
89+
# Connect Events
90+
self.ok_btn.Bind(wx.EVT_BUTTON, self.on_ok_clicked)
91+
92+
def on_ok_clicked(self, event):
93+
self.Close()

0 commit comments

Comments
 (0)