Source

gexcept / gexcept.py

"""
    gexcept
    ~~~~~~~

    an exception handler for pygtk with nice ui

    :copyright: 2009 by Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
    :license: LGPL2 or later
"""

import sys
import traceback
import linecache
from cgi import escape
import gtk


def scrolled(widget, shadow=gtk.SHADOW_NONE):
    window = gtk.ScrolledWindow()
    window.set_shadow_type(shadow)
    window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
    if widget.set_scroll_adjustments(window.get_hadjustment(),
                                      window.get_vadjustment()):
        window.add(widget)
    else:
        window.add_with_viewport(widget)
    return window


class SimpleExceptionDialog(gtk.MessageDialog):
    def __init__(self, exc, tb, parent=None, **extra):

        gtk.MessageDialog.__init__(self,
                buttons=gtk.BUTTONS_CLOSE,
                type=gtk.MESSAGE_ERROR,
                parent=parent
                )

        self.extra = extra
        self.set_resizable(True)
        text = 'An exception Occured\n%s: %s\n'
        self.set_markup(text%(exc.__class__.__name__, exc))
        expander = gtk.Expander("Exception Details")
        self.vbox.pack_start(expander)
        expander.add(self.get_trace_view(exc, tb))
        self.show_all()


    def get_trace_view(self, exc, tb):
        text = traceback.format_exception(type(exc), exc, tb)
        textview = gtk.TextView()
        textview.get_buffer().set_text(''.join(text))
        return scrolled(textview)




def extract_tb(tb, limit=None):
    if limit is None and hasattr(sys, 'tracebacklimit'):
        limit = sys.tracebacklimit
    assert limit is None or limit>0

    while tb is not None and (limit is None or limit >0):
        frame = tb.tb_frame
        lineno = tb.tb_lineno
        code = frame.f_code
        filename = code.co_filename
        name = code.co_name
        linecache.checkcache(filename)
        line = linecache.getline(filename, lineno, frame.f_globals)
        line = line.strip() if line else None
        yield filename, lineno, name, line, frame
        tb = tb.tb_next


class ExtendedExceptionDialog(SimpleExceptionDialog):
    def get_trace_view(self, exc, tb):
        store = gtk.ListStore(str, int, str, str, object)
        for item in extract_tb(tb):
            store.append(item)

        view = gtk.TreeView(model=store)
        cell = gtk.CellRendererText()
        column = gtk.TreeViewColumn('Pango Markup', cell, markup=0)
        view.append_column(column)
        format = ('File <span color="darkgreen">%r</span>,'
                  ' line <span color="blue"><i>%d</i></span> in <i>%s</i>\n'
                  '  %s')
        def data_func(column, cell, model, iter):
            filename = escape(model.get_value(iter, 0))
            lineno = model.get_value(iter, 1)
            name = escape(model.get_value(iter, 2))
            line = escape(model.get_value(iter, 3))

            text = format%(filename, lineno, name, line)
            cell.set_property('markup', text)
        column.set_cell_data_func(cell, data_func)
        return view




def install_hook(dialog=SimpleExceptionDialog, **extra):
    """
    install the configured exception hook wrapping the old exception hook

    don't use it twice
    """
    old_hook = sys.excepthook

    def new_hook(etype, eval, trace):
        def handler(etype, eval, trace):
            if etype not in (KeyboardInterrupt, SystemExit):
                d = dialog(eval, trace, **extra)
                d.run()
                d.destroy()
        gtk.idle_add(handler, etype, eval, trace)
        old_hook(etype, eval, trace)
    sys.excepthook = new_hook



def _test2(*a):
    1/0

def _test(*a):
    x = lambda: _test2()
    x()

if __name__=='__main__':
    install_hook(ExtendedExceptionDialog)
    w = gtk.Window()
    w.set_title('test')
    w.set_size_request(100,100)
    w.connect('destroy', gtk.main_quit)
    b = gtk.Button("click")
    b.connect("clicked", _test)
    w.add(b)
    w.show_all()
    
    gtk.main()