Moved into a sub-dir, so that a svn checkout has the same structure as
[rox-lib/lack.git] / ROX-Lib2 / python / rox / debug.py
blobc7faa70d05f78cd65e5abdaeb711a2d96bb6af16
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 reply = []
46 def response(box, resp):
47 reply.append(resp)
48 g.main_quit()
49 box.connect('response', response)
50 box.show()
52 bug_report = 'Traceback (most recent call last):\n' + \
53 ''.join(traceback.format_stack(tb.tb_frame.f_back) +
54 traceback.format_tb(tb) +
55 traceback.format_exception_only(type, value))
57 while 1:
58 if auto_details:
59 resp = DETAILS
60 auto_details = False
61 else:
62 g.main()
63 resp = reply.pop()
64 if resp == int(g.RESPONSE_OK) or resp == int(g.RESPONSE_DELETE_EVENT):
65 break
66 if resp == SAVE:
67 global savebox
68 if savebox:
69 savebox.destroy()
70 def destroy(box):
71 global savebox # For pychecker
72 savebox = None
73 from saving import StringSaver
74 savebox = StringSaver(bug_report, 'BugReport')
75 savebox.connect('destroy', destroy)
76 savebox.show()
77 continue
78 if resp == QUIT:
79 sys.exit(1)
80 elif resp == int(g.RESPONSE_HELP):
81 _show_debug_help()
82 continue
83 assert resp == DETAILS
84 box.set_response_sensitive(DETAILS, False)
86 button = ButtonMixed(g.STOCK_SAVE, _('_Bug Report'))
87 button.set_flags(g.CAN_DEFAULT)
88 button.show()
89 box.add_action_widget(button, SAVE)
90 box.action_area.set_child_secondary(button, True)
92 button = ButtonMixed(g.STOCK_QUIT, _('Forced Quit'))
93 button.set_flags(g.CAN_DEFAULT)
94 button.show()
95 box.add_action_widget(button, QUIT)
96 box.action_area.set_child_secondary(button, True)
98 ee = ExceptionExplorer(tb)
99 box.vbox.pack_start(ee)
100 ee.show()
101 box.destroy()
102 toplevel_unref()
104 class ExceptionExplorer(g.Frame):
105 """Displays details from a traceback object."""
106 LEAF = 0
107 LINE = 1
108 FUNC = 2
109 CODE = 3
110 FILE = 4
111 def __init__(self, tb):
112 g.Frame.__init__(self, _('Stack trace (innermost last)'))
114 vbox = g.VBox(False, 0)
115 self.add(vbox)
117 inner = g.Frame()
118 inner.set_shadow_type(g.SHADOW_IN)
119 vbox.pack_start(inner, False, True, 0)
121 self.savebox = None
123 self.tb = tb
125 self.model = g.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT,
126 gobject.TYPE_STRING, gobject.TYPE_STRING,
127 gobject.TYPE_STRING)
128 tree = g.TreeView(self.model)
129 inner.add(tree)
131 cell = g.CellRendererText()
133 column = g.TreeViewColumn('File', cell, text = ExceptionExplorer.LEAF)
134 cell.set_property('xalign', 1)
135 tree.append_column(column)
137 cell = g.CellRendererText()
138 column = g.TreeViewColumn('Line', cell, text = ExceptionExplorer.LINE)
139 tree.append_column(column)
140 column = g.TreeViewColumn('Func', cell, text = ExceptionExplorer.FUNC)
141 tree.append_column(column)
142 column = g.TreeViewColumn('Code', cell, text = ExceptionExplorer.CODE)
143 tree.append_column(column)
145 inner.set_border_width(5)
147 frames = []
148 while tb is not None:
149 frames.insert(0, (tb.tb_frame, traceback.tb_lineno(tb)))
150 tb = tb.tb_next
151 f = self.tb.tb_frame
152 if f:
153 f = f.f_back # Skip the reporting frame
154 while f is not None:
155 frames.append((f, f.f_lineno))
156 f = f.f_back
158 frames.reverse()
160 new = None
161 for f, lineno in frames:
162 co = f.f_code
163 filename = co.co_filename
164 name = co.co_name
165 line = linecache.getline(filename, lineno).strip()
167 leafname = os.path.basename(filename)
169 new = self.model.append()
170 self.model.set(new, ExceptionExplorer.LEAF, leafname,
171 ExceptionExplorer.LINE, lineno,
172 ExceptionExplorer.FUNC, name,
173 ExceptionExplorer.CODE, line,
174 ExceptionExplorer.FILE, filename)
176 def selected_frame():
177 selected = sel.get_selected()
178 assert selected
179 model, titer = selected
180 frame, = model.get_path(titer)
181 return frames[frame][0]
183 vars = g.ListStore(str, str)
184 sel = tree.get_selection()
185 sel.set_mode(g.SELECTION_BROWSE)
186 def select_frame(tree):
187 vars.clear()
188 for n, v in selected_frame().f_locals.iteritems():
189 value = `v`
190 if len(value) > 500:
191 value = value[:500] + ' ...'
192 new = vars.append()
193 vars.set(new, 0, str(n), 1, value)
194 sel.connect('changed', select_frame)
195 def show_source(tree, path, column):
196 line = self.model[path][ExceptionExplorer.LINE]
197 file = self.model[path][ExceptionExplorer.FILE]
198 import launch
199 launch.launch('http://rox.sourceforge.net/2005/interfaces/Edit',
200 '-l%d' % line, file)
202 tree.connect('row-activated', show_source)
204 # Area to show the local variables
205 tree = g.TreeView(vars)
207 vbox.pack_start(g.Label(_('Local variables in selected frame:')),
208 False, True, 0)
210 cell = g.CellRendererText()
211 column = g.TreeViewColumn('Name', cell, text = 0)
212 cell.set_property('xalign', 1)
213 tree.append_column(column)
214 cell = g.CellRendererText()
215 column = g.TreeViewColumn('Value', cell, text = 1)
216 tree.append_column(column)
218 inner = g.ScrolledWindow()
219 inner.set_size_request(-1, 200)
220 inner.set_policy(g.POLICY_AUTOMATIC, g.POLICY_ALWAYS)
221 inner.set_shadow_type(g.SHADOW_IN)
222 inner.add(tree)
223 inner.set_border_width(5)
224 vbox.pack_start(inner, True, True, 0)
226 if new:
227 sel.select_iter(new)
229 hbox = g.HBox(False, 4)
230 hbox.set_border_width(5)
231 vbox.pack_start(hbox, False, True, 0)
232 hbox.pack_start(g.Label('>>>'), False, True, 0)
234 expr = g.Entry()
235 hbox.pack_start(expr, True, True, 0)
236 def activate(entry):
237 expr = entry.get_text()
238 frame = selected_frame()
239 try:
240 info(`eval(expr, frame.f_locals, frame.f_globals)`)
241 except:
242 extype, value = sys.exc_info()[:2]
243 brief = ''.join(traceback.format_exception_only(extype, value))
244 alert(brief)
245 entry.grab_focus()
246 expr.connect('activate', activate)
248 vbox.show_all()