Fix hang in exception reporting dialog
[rox-lib/lack.git] / ROX-Lib2 / python / rox / debug.py
blob7f02a60a81f697d5bd61e00f53d98c403838455d
1 """This module provides features to help with debugging ROX applications."""
3 import sys, os
4 import traceback
5 import gobject
6 import linecache
8 from rox import g, ButtonMixed, toplevel_ref, toplevel_unref, _
9 from rox import info, alert
11 savebox = None
13 def _show_debug_help():
14 from os.path import join, dirname
15 help = join(dirname(dirname(dirname(__file__))), 'Help', 'Errors')
16 from rox import filer
17 filer.spawn_rox((help,))
19 def show_exception(type, value, tb, auto_details = False):
20 """Display this exception in an error box. The user has the options
21 of ignoring the error, quitting the application and examining the
22 exception in more detail. See also rox.report_exception()."""
24 QUIT = 1
25 DETAILS = 2
26 SAVE = 3
28 brief = ''.join(traceback.format_exception_only(type, value))
30 toplevel_ref()
31 box = g.MessageDialog(None, 0, g.MESSAGE_ERROR, g.BUTTONS_NONE, brief)
33 if not auto_details:
34 button = ButtonMixed(g.STOCK_ZOOM_IN, _('_Details'))
35 button.set_flags(g.CAN_DEFAULT)
36 button.show()
37 box.add_action_widget(button, DETAILS)
39 box.add_button(g.STOCK_HELP, g.RESPONSE_HELP)
40 box.add_button(g.STOCK_OK, g.RESPONSE_OK)
41 box.set_default_response(g.RESPONSE_OK)
43 box.set_position(g.WIN_POS_CENTER)
44 box.set_title(_('Error'))
45 box.show()
47 if tb:
48 bug_report = 'Traceback (most recent call last):\n' + \
49 ''.join(traceback.format_stack(tb.tb_frame.f_back) +
50 traceback.format_tb(tb) +
51 traceback.format_exception_only(type, value))
52 else:
53 bug_report = 'No stack trace.'
55 while 1:
56 if auto_details:
57 resp = DETAILS
58 auto_details = False
59 else:
60 resp = box.run()
61 if resp == int(g.RESPONSE_OK) or resp == int(g.RESPONSE_DELETE_EVENT):
62 break
63 if resp == SAVE:
64 global savebox
65 if savebox:
66 savebox.destroy()
67 def destroy(box):
68 global savebox # For pychecker
69 savebox = None
70 from saving import StringSaver
71 savebox = StringSaver(bug_report, 'BugReport')
72 savebox.connect('destroy', destroy)
73 savebox.show()
74 continue
75 if resp == QUIT:
76 sys.exit(1)
77 elif resp == int(g.RESPONSE_HELP):
78 _show_debug_help()
79 continue
80 assert resp == DETAILS
81 box.set_response_sensitive(DETAILS, False)
83 button = ButtonMixed(g.STOCK_SAVE, _('_Bug Report'))
84 button.set_flags(g.CAN_DEFAULT)
85 button.show()
86 box.add_action_widget(button, SAVE)
87 box.action_area.set_child_secondary(button, True)
89 button = ButtonMixed(g.STOCK_QUIT, _('Forced Quit'))
90 button.set_flags(g.CAN_DEFAULT)
91 button.show()
92 box.add_action_widget(button, QUIT)
93 box.action_area.set_child_secondary(button, True)
95 if tb:
96 ee = ExceptionExplorer(tb)
97 box.vbox.pack_start(ee)
98 ee.show()
99 else:
100 no_trace = g.Label('No traceback object!')
101 box.vbox.pack_start(no_trace)
102 no_trace.show()
103 box.destroy()
104 toplevel_unref()
106 class ExceptionExplorer(g.Frame):
107 """Displays details from a traceback object."""
108 LEAF = 0
109 LINE = 1
110 FUNC = 2
111 CODE = 3
112 FILE = 4
113 def __init__(self, tb):
114 g.Frame.__init__(self, _('Stack trace (innermost last)'))
116 vbox = g.VBox(False, 0)
117 self.add(vbox)
119 inner = g.Frame()
120 inner.set_shadow_type(g.SHADOW_IN)
121 vbox.pack_start(inner, False, True, 0)
123 self.savebox = None
125 self.tb = tb
127 self.model = g.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT,
128 gobject.TYPE_STRING, gobject.TYPE_STRING,
129 gobject.TYPE_STRING)
130 tree = g.TreeView(self.model)
131 inner.add(tree)
133 cell = g.CellRendererText()
135 column = g.TreeViewColumn('File', cell, text = ExceptionExplorer.LEAF)
136 cell.set_property('xalign', 1)
137 tree.append_column(column)
139 cell = g.CellRendererText()
140 column = g.TreeViewColumn('Line', cell, text = ExceptionExplorer.LINE)
141 tree.append_column(column)
142 column = g.TreeViewColumn('Func', cell, text = ExceptionExplorer.FUNC)
143 tree.append_column(column)
144 column = g.TreeViewColumn('Code', cell, text = ExceptionExplorer.CODE)
145 tree.append_column(column)
147 inner.set_border_width(5)
149 frames = []
150 while tb is not None:
151 frames.insert(0, (tb.tb_frame, traceback.tb_lineno(tb)))
152 tb = tb.tb_next
153 f = self.tb.tb_frame
154 if f:
155 f = f.f_back # Skip the reporting frame
156 while f is not None:
157 frames.append((f, f.f_lineno))
158 f = f.f_back
160 frames.reverse()
162 new = None
163 for f, lineno in frames:
164 co = f.f_code
165 filename = co.co_filename
166 name = co.co_name
167 line = linecache.getline(filename, lineno).strip()
169 leafname = os.path.basename(filename)
171 new = self.model.append()
172 self.model.set(new, ExceptionExplorer.LEAF, leafname,
173 ExceptionExplorer.LINE, lineno,
174 ExceptionExplorer.FUNC, name,
175 ExceptionExplorer.CODE, line,
176 ExceptionExplorer.FILE, filename)
178 def selected_frame():
179 selected = sel.get_selected()
180 assert selected
181 model, titer = selected
182 frame, = model.get_path(titer)
183 return frames[frame][0]
185 vars = g.ListStore(str, str)
186 sel = tree.get_selection()
187 sel.set_mode(g.SELECTION_BROWSE)
188 def select_frame(tree):
189 vars.clear()
190 for n, v in selected_frame().f_locals.iteritems():
191 value = `v`
192 if len(value) > 500:
193 value = value[:500] + ' ...'
194 new = vars.append()
195 vars.set(new, 0, str(n), 1, value)
196 sel.connect('changed', select_frame)
197 def show_source(tree, path, column):
198 line = self.model[path][ExceptionExplorer.LINE]
199 file = self.model[path][ExceptionExplorer.FILE]
200 import launch
201 launch.launch('http://rox.sourceforge.net/2005/interfaces/Edit',
202 '-l%d' % line, file)
204 tree.connect('row-activated', show_source)
206 # Area to show the local variables
207 tree = g.TreeView(vars)
209 vbox.pack_start(g.Label(_('Local variables in selected frame:')),
210 False, True, 0)
212 cell = g.CellRendererText()
213 column = g.TreeViewColumn('Name', cell, text = 0)
214 cell.set_property('xalign', 1)
215 tree.append_column(column)
216 cell = g.CellRendererText()
217 column = g.TreeViewColumn('Value', cell, text = 1)
218 tree.append_column(column)
220 inner = g.ScrolledWindow()
221 inner.set_size_request(-1, 200)
222 inner.set_policy(g.POLICY_AUTOMATIC, g.POLICY_ALWAYS)
223 inner.set_shadow_type(g.SHADOW_IN)
224 inner.add(tree)
225 inner.set_border_width(5)
226 vbox.pack_start(inner, True, True, 0)
228 if new:
229 sel.select_iter(new)
231 hbox = g.HBox(False, 4)
232 hbox.set_border_width(5)
233 vbox.pack_start(hbox, False, True, 0)
234 hbox.pack_start(g.Label('>>>'), False, True, 0)
236 expr = g.Entry()
237 hbox.pack_start(expr, True, True, 0)
238 def activate(entry):
239 expr = entry.get_text()
240 frame = selected_frame()
241 try:
242 info(`eval(expr, frame.f_locals, frame.f_globals)`)
243 except:
244 extype, value = sys.exc_info()[:2]
245 brief = ''.join(traceback.format_exception_only(extype, value))
246 alert(brief)
247 entry.grab_focus()
248 expr.connect('activate', activate)
250 vbox.show_all()