import sys
import bdb
import types
import os

import W
import WASTEconst
import PyBrowser
from Carbon import Qd
from Carbon import Evt
from Carbon import Lists
import MacOS
_filenames = {}

SIMPLE_TYPES = (
        types.NoneType,
        types.IntType,
        types.LongType,
        types.FloatType,
        types.ComplexType,
        types.StringType
)


class Debugger(bdb.Bdb):

    def __init__(self, title = 'Debugger'):
        bdb.Bdb.__init__(self)
        self.closed = 1
        self.title = title
        self.breaksviewer = None
        self.reset()
        self.tracing = 0
        self.tracingmonitortime = Evt.TickCount()
        self.editors = {}

        prefs = W.getapplication().getprefs()
        if prefs.debugger:
            for file, breaks in prefs.debugger.breaks.items():
                for b in breaks:
                    self.set_break(file, b)
            self.bounds, self.horpanes, self.verpanes = prefs.debugger.windowsettings
            self.tracemagic = prefs.debugger.tracemagic
        else:
            self.breaks = {}
            self.horpanes = (0.4, 0.6)
            self.verpanes = (0.3, 0.35, 0.35)
            self.bounds = (600, 400)
            self.tracemagic = 0
        self.laststacksel = None

    def canonic(self, filename):
        # override: the provided canonic() method breaks our
        # file-less Untitled windows
        return filename

    def reset(self):
        self.currentframe = None
        self.file = None
        self.laststack = None
        self.reason = 'Not running'
        self.continuewithoutdebugger = 0
        bdb.Bdb.reset(self)
        self.forget()

    def start(self, bottomframe = None, running = 0):
        W.getapplication().DebuggerQuit = bdb.BdbQuit
        from Carbon import Menu
        Menu.HiliteMenu(0)
        if self.closed:
            self.setupwidgets(self.title)
            self.closed = 0
        if not self.w.parent.debugger_quitting:
            self.w.select()
            raise W.AlertError, 'There is another debugger session busy.'
        self.reset()
        self.botframe = bottomframe
        if running:
            self.set_continue()
            self.reason = 'Running\xc9'
            self.setstate('running')
        else:
            self.set_step()
            self.reason = 'stopped'
            self.setstate('stopped')
        sys.settrace(self.trace_dispatch)

    def stop(self):
        self.set_quit()
        if self.w.parent:
            self.exit_mainloop()
            self.resetwidgets()

    def set_continue_without_debugger(self):
        sys.settrace(None)
        self.set_quit()
        self.clear_tracefuncs()
        self.continuewithoutdebugger = 1
        if hasattr(self, "w") and self.w.parent:
            self.exit_mainloop()
            self.resetwidgets()

    def clear_tracefuncs(self):
        try:
            raise 'spam'
        except:
            pass
        frame = sys.exc_traceback.tb_frame
        while frame is not None:
            del frame.f_trace
            frame = frame.f_back

    def postmortem(self, exc_type, exc_value, traceback):
        if self.closed:
            self.setupwidgets(self.title)
            self.closed = 0
        if not self.w.parent.debugger_quitting:
            raise W.AlertError, 'There is another debugger session busy.'
        self.reset()
        if traceback:
            self.botframe = traceback.tb_frame
            while traceback.tb_next <> None:
                traceback = traceback.tb_next
            frame = traceback.tb_frame
        else:
            self.botframe = None
            frame = None
        self.w.panes.bottom.buttons.killbutton.enable(1)
        self.reason = '(dead) ' + self.formatexception(exc_type, exc_value)
        self.w.select()
        self.setup(frame, traceback)
        self.setstate('dead')
        self.showstack(self.curindex)
        self.showframe(self.curindex)

    def setupwidgets(self, title):
        self.w = w = W.Window(self.bounds, title, minsize = (500, 300))

        w.panes = W.HorizontalPanes((8, 4, -8, -8), self.horpanes)

        w.panes.browserpanes = browserpanes = W.VerticalPanes(None, self.verpanes)

        browserpanes.stacklist = W.Group(None)
        browserpanes.stacklist.title = W.TextBox((4, 0, 0, 12), 'Stack')
        browserpanes.stacklist.stack = W.List((0, 16, 0, 0), callback = self.do_stack, flags = Lists.lOnlyOne)

        browserpanes.locals = W.Group(None)
        browserpanes.locals.title = W.TextBox((4, 0, 0, 12), 'Local variables')
        browserpanes.locals.browser = PyBrowser.BrowserWidget((0, 16, 0, 0))

        browserpanes.globals = W.Group(None)
        browserpanes.globals.title = W.TextBox((4, 0, 0, 12), 'Global variables')
        browserpanes.globals.browser = PyBrowser.BrowserWidget((0, 16, 0, 0))

        w.panes.bottom = bottom = W.Group(None)
        bottom.src = src = W.Group((0, 64, 0, 0))
        source = SourceViewer((1, 1, -15, -15), readonly = 1, debugger = self)
        src.optionsmenu = W.PopupMenu((-16, 0, 16, 16), [])
        src.optionsmenu.bind('<click>', self.makeoptionsmenu)

        src._barx = W.Scrollbar((0, -16, -15, 16), source.hscroll, max = 32767)
        src._bary = W.Scrollbar((-16, 15, 16, -15), source.vscroll, max = 32767)
        src.source = source
        src.frame = W.Frame((0, 0, -15, -15))

        bottom.tracingmonitor = TracingMonitor((0, 23, 6, 6))
        bottom.state = W.TextBox((12, 24, 0, 16), self.reason)

        bottom.srctitle = W.TextBox((12, 44, 0, 16))
        bottom.buttons = buttons = W.Group((12, 0, 0, 20))

        buttons.runbutton = W.Button((0, 0, 50, 16), "Run", self.do_run)
        buttons.stopbutton = W.Button((58, 0, 50, 16), "Stop", self.do_stop)
        buttons.killbutton = W.Button((116, 0, 50, 16), "Kill", self.do_kill)
        buttons.line = W.VerticalLine((173, 0, 0, 0))
        buttons.stepbutton = W.Button((181, 0, 60, 16), "Step", self.do_step)
        buttons.stepinbutton = W.Button((249, 0, 60, 16), "Step in", self.do_stepin)
        buttons.stepoutbutton = W.Button((317, 0, 60, 16), "Step out", self.do_stepout)

        w.bind('cmdr', buttons.runbutton.push)
        w.bind('cmd.', buttons.stopbutton.push)
        w.bind('cmdk', buttons.killbutton.push)
        w.bind('cmds', buttons.stepbutton.push)
        w.bind('cmdt', buttons.stepinbutton.push)
        w.bind('cmdu', buttons.stepoutbutton.push)

        w.bind('<close>', self.close)

        w.open()
        w.xxx___select(w.panes.bottom.src.source)

    def makeoptionsmenu(self):
        options = [('Clear breakpoints', self.w.panes.bottom.src.source.clearbreakpoints),
                        ('Clear all breakpoints', self.clear_all_breaks),
                        ('Edit breakpoints\xc9', self.edit_breaks), '-',
                        (self.tracemagic and
                                'Disable __magic__ tracing' or 'Enable __magic__ tracing', self.togglemagic)]
        self.w.panes.bottom.src.optionsmenu.set(options)

    def edit_breaks(self):
        if self.breaksviewer:
            self.breaksviewer.select()
        else:
            self.breaksviewer = BreakpointsViewer(self)

    def togglemagic(self):
        self.tracemagic = not self.tracemagic

    def setstate(self, state):
        self.w.panes.bottom.tracingmonitor.reset()
        self.w.panes.bottom.state.set(self.reason)
        buttons = self.w.panes.bottom.buttons
        if state == 'stopped':
            buttons.runbutton.enable(1)
            buttons.stopbutton.enable(0)
            buttons.killbutton.enable(1)
            buttons.stepbutton.enable(1)
            buttons.stepinbutton.enable(1)
            buttons.stepoutbutton.enable(1)
        elif state == 'running':
            buttons.runbutton.enable(0)
            buttons.stopbutton.enable(1)
            buttons.killbutton.enable(1)
            buttons.stepbutton.enable(0)
            buttons.stepinbutton.enable(0)
            buttons.stepoutbutton.enable(0)
        elif state == 'idle':
            buttons.runbutton.enable(0)
            buttons.stopbutton.enable(0)
            buttons.killbutton.enable(0)
            buttons.stepbutton.enable(0)
            buttons.stepinbutton.enable(0)
            buttons.stepoutbutton.enable(0)
        elif state == 'dead':
            buttons.runbutton.enable(0)
            buttons.stopbutton.enable(0)
            buttons.killbutton.enable(1)
            buttons.stepbutton.enable(0)
            buttons.stepinbutton.enable(0)
            buttons.stepoutbutton.enable(0)
        else:
            print 'unknown state:', state

    def resetwidgets(self):
        self.reason = ''
        self.w.panes.bottom.srctitle.set('')
        self.w.panes.bottom.src.source.set('')
        self.w.panes.browserpanes.stacklist.stack.set([])
        self.w.panes.browserpanes.locals.browser.set({})
        self.w.panes.browserpanes.globals.browser.set({})
        self.setstate('idle')

    # W callbacks

    def close(self):
        self.set_quit()
        self.exit_mainloop()
        self.closed = 1

        self.unregister_editor(self.w.panes.bottom.src.source,
                        self.w.panes.bottom.src.source.file)
        self.horpanes = self.w.panes.getpanesizes()
        self.verpanes = self.w.panes.browserpanes.getpanesizes()
        self.bounds = self.w.getbounds()
        prefs = W.getapplication().getprefs()
        prefs.debugger.breaks = self.breaks
        prefs.debugger.windowsettings = self.bounds, self.horpanes, self.verpanes
        prefs.debugger.tracemagic = self.tracemagic
        prefs.save()

    # stack list callback

    def do_stack(self, isdbl):
        sel = self.w.panes.browserpanes.stacklist.stack.getselection()
        if isdbl:
            if sel:
                frame, lineno = self.stack[sel[0] + 1]
                filename = frame.f_code.co_filename
                editor = self.w._parentwindow.parent.openscript(filename, lineno)
                if self.breaks.has_key(filename):
                    editor.showbreakpoints(1)
        else:
            if sel and sel <> self.laststacksel:
                self.showframe(sel[0] + 1)
            self.laststacksel = sel

    def geteditor(self, filename):
        if filename[:1] == '<' and filename[-1:] == '>':
            editor = W.getapplication().getscript(filename[1:-1])
        else:
            editor = W.getapplication().getscript(filename)
        return editor

    # button callbacks

    def do_run(self):
        self.running()
        self.set_continue()
        self.exit_mainloop()

    def do_stop(self):
        self.set_step()

    def do_kill(self):
        self.set_quit()
        self.exit_mainloop()
        self.resetwidgets()

    def do_step(self):
        self.running()
        self.set_next(self.curframe)
        self.exit_mainloop()

    def do_stepin(self):
        self.running()
        self.set_step()
        self.exit_mainloop()

    def do_stepout(self):
        self.running()
        self.set_return(self.curframe)
        self.exit_mainloop()

    def running(self):
        W.SetCursor('watch')
        self.reason = 'Running\xc9'
        self.setstate('running')
        #self.w.panes.bottom.src.source.set('')
        #self.w.panes.browserpanes.stacklist.stack.set([])
        #self.w.panes.browserpanes.locals.browser.set({})
        #self.w.panes.browserpanes.globals.browser.set({})

    def exit_mainloop(self):
        self.w.parent.debugger_quitting = 1

    #

    def showframe(self, stackindex):
        (frame, lineno) = self.stack[stackindex]
        W.SetCursor('watch')
        filename = frame.f_code.co_filename
        if filename <> self.file:
            editor = self.geteditor(filename)
            if editor:
                self.w.panes.bottom.src.source.set(editor.get(), filename)
            else:
                try:
                    f = open(filename, 'rU')
                    data = f.read()
                    f.close()
                except IOError:
                    if filename[-3:] == '.py':
                        import imp
                        modname = os.path.basename(filename)[:-3]
                        try:
                            f, filename, (suff, mode, dummy) = imp.find_module(modname)
                        except ImportError:
                            self.w.panes.bottom.src.source.set("can't find file")
                        else:
                            if f:
                                f.close()
                            if f and suff == '.py':
                                f = open(filename, 'rU')
                                data = f.read()
                                f.close()
                                self.w.panes.bottom.src.source.set(data, filename)
                            else:
                                self.w.panes.bottom.src.source.set("can't find file")
                    else:
                        self.w.panes.bottom.src.source.set("can't find file")
                else:
                    data = data.replace('\n', '\r')
                    self.w.panes.bottom.src.source.set(data, filename)
            self.file = filename
        self.w.panes.bottom.srctitle.set('Source: ' + filename + ((lineno > 0) and (' (line %d)' % lineno) or ' '))
        self.goto_line(lineno)
        self.lineno = lineno
        self.showvars((frame, lineno))

    def showvars(self, (frame, lineno)):
        if frame.f_locals is not frame.f_globals:
            locals = frame.f_locals
        else:
            locals = {'Same as Globals':''}
        filteredlocals = {}
        for key, value in locals.items():
            # empty key is magic for Python 1.4; '.' is magic for 1.5...
            if not key or key[0] <> '.':
                filteredlocals[key] = value
        self.w.panes.browserpanes.locals.browser.set(filteredlocals)
        self.w.panes.browserpanes.globals.browser.set(frame.f_globals)

    def showstack(self, stackindex):
        stack = []
        for frame, lineno in self.stack[1:]:
            filename = frame.f_code.co_filename
            try:
                filename = _filenames[filename]
            except KeyError:
                if filename[:1] + filename[-1:] <> '<>':
                    filename = os.path.basename(filename)
                _filenames[frame.f_code.co_filename] = filename
            funcname = frame.f_code.co_name
            if funcname == '?':
                funcname = '<toplevel>'
            stack.append(filename + ': ' + funcname)
        if stack <> self.laststack:
            self.w.panes.browserpanes.stacklist.stack.set(stack)
            self.laststack = stack
        sel = [stackindex - 1]
        self.w.panes.browserpanes.stacklist.stack.setselection(sel)
        self.laststacksel = sel

    def goto_line(self, lineno):
        if lineno > 0:
            self.w.panes.bottom.src.source.selectline(lineno - 1)
        else:
            self.w.panes.bottom.src.source.setselection(0, 0)

    # bdb entry points

#       def user_call(self, frame, argument_list):
#               self.reason = 'Calling'
#               self.interaction(frame, None)

    def user_line(self, frame):
        # This function is called when we stop or break at this line
        self.reason = 'Stopped'
        self.interaction(frame, None)

    def user_return(self, frame, return_value):
        # This function is called when a return trap is set here
        fname = frame.f_code.co_name
        if fname <> '?':
            self.reason = 'Returning from %s()' % frame.f_code.co_name
            frame.f_locals['__return__'] = return_value
        elif frame.f_back is self.botframe:
            self.reason = 'Done'
        else:
            self.reason = 'Returning'
        self.interaction(frame, None, 1)

    def user_exception(self, frame, (exc_type, exc_value, exc_traceback)):
        # This function is called when we stop or break at this line
        self.reason = self.formatexception(exc_type, exc_value)
        self.interaction(frame, exc_traceback)

    def formatexception(self, exc_type, exc_value):
        if exc_type == SyntaxError:
            try:
                value, (filename, lineno, charno, line) = exc_value
            except:
                pass
            else:
                return str(exc_type) + ': ' + str(value)
        if type(exc_type) == types.ClassType:
            nice = exc_type.__name__
        else:
            nice = str(exc_type)
        value = str(exc_value)
        if exc_value and value:
            nice = nice + ": " + value
        return nice

    def forget(self):
        self.stack = []
        self.curindex = 0
        self.curframe = None

    def setup(self, f, t, isreturning = 0):
        self.forget()
        self.stack, self.curindex = self.get_stack(f, t)
        self.curframe = self.stack[self.curindex - isreturning][0]

    def interaction(self, frame, traceback, isreturning = 0):
        saveport = Qd.GetPort()
        self.w.select()
        try:
            self.setup(frame, traceback, isreturning)
            self.setstate('stopped')
            stackindex = self.curindex
            if isreturning:
                if frame.f_back is not self.botframe:
                    stackindex = stackindex - 1
            self.showstack(stackindex)
            self.showframe(stackindex)
            self.w.parent.debugger_mainloop()
            self.forget()
        finally:
            Qd.SetPort(saveport)

    # bdb customization

    def trace_dispatch(self, frame, event, arg, TickCount = Evt.TickCount):
        if TickCount() - self.tracingmonitortime > 15:
            self.tracingmonitortime = TickCount()
            self.w.panes.bottom.tracingmonitor.toggle()
        try:
            try:
                if hasattr(MacOS, 'EnableAppswitch'):
                    MacOS.EnableAppswitch(0)
                if self.quitting:
                    # returning None is not enough, a former BdbQuit exception
                    # might have been eaten by the print statement
                    raise bdb.BdbQuit
                if event == 'line':
                    return self.dispatch_line(frame)
                if event == 'call':
                    return self.dispatch_call(frame, arg)
                if event == 'return':
                    return self.dispatch_return(frame, arg)
                if event == 'exception':
                    return self.dispatch_exception(frame, arg)
                print 'bdb.Bdb.dispatch: unknown debugging event:', repr(event)
                return self.trace_dispatch
            finally:
                if hasattr(MacOS, 'EnableAppswitch'):
                    MacOS.EnableAppswitch(-1)
        except KeyboardInterrupt:
            self.set_step()
            return self.trace_dispatch
        except bdb.BdbQuit:
            if self.continuewithoutdebugger:
                self.clear_tracefuncs()
                return
            else:
                raise bdb.BdbQuit
        except:
            print 'XXX Exception during debugger interaction.', \
                            self.formatexception(sys.exc_type, sys.exc_value)
            import traceback
            traceback.print_exc()
            return self.trace_dispatch

    def dispatch_call(self, frame, arg):
        if not self.tracemagic and \
                        frame.f_code.co_name[:2] == '__' == frame.f_code.co_name[-2:] and \
                        frame.f_code.co_name <> '__init__':
            return
        if self.botframe is None:
            # First call of dispatch since reset()
            self.botframe = frame.f_back    # xxx !!! added f_back
            return self.trace_dispatch
        if not (self.stop_here(frame) or self.break_anywhere(frame)):
            # No need to trace this function
            return # None
        self.user_call(frame, arg)
        if self.quitting:
            raise bdb.BdbQuit
        return self.trace_dispatch

    def set_continue(self):
        # Don't stop except at breakpoints or when finished
        self.stopframe = self.botframe
        self.returnframe = None
        self.quitting = 0
        # unlike in bdb/pdb, there's a chance that breakpoints change
        # *while* a program (this program ;-) is running. It's actually quite likely.
        # So we don't delete frame.f_trace until the bottom frame if there are no breakpoints.

    def set_break(self, filename, lineno):
        if not self.breaks.has_key(filename):
            self.breaks[filename] = []
        list = self.breaks[filename]
        if lineno in list:
            return 'There is already a breakpoint there!'
        list.append(lineno)
        list.sort()     # I want to keep them neatly sorted; easier for drawing
        if hasattr(bdb, "Breakpoint"):
            # 1.5.2b1 specific
            bp = bdb.Breakpoint(filename, lineno, 0, None)
        self.update_breaks(filename)

    def clear_break(self, filename, lineno):
        bdb.Bdb.clear_break(self, filename, lineno)
        self.update_breaks(filename)

    def clear_all_file_breaks(self, filename):
        bdb.Bdb.clear_all_file_breaks(self, filename)
        self.update_breaks(filename)

    def clear_all_breaks(self):
        bdb.Bdb.clear_all_breaks(self)
        for editors in self.editors.values():
            for editor in editors:
                editor.drawbreakpoints()

    # special

    def toggle_break(self, filename, lineno):
        if self.get_break(filename, lineno):
            self.clear_break(filename, lineno)
        else:
            self.set_break(filename, lineno)

    def clear_breaks_above(self, filename, above):
        if not self.breaks.has_key(filename):
            return 'There are no breakpoints in that file!'
        for lineno in self.breaks[filename][:]:
            if lineno > above:
                self.breaks[filename].remove(lineno)
        if not self.breaks[filename]:
            del self.breaks[filename]

    # editor stuff

    def update_breaks(self, filename):
        if self.breaksviewer:
            self.breaksviewer.update()
        if self.editors.has_key(filename):
            for editor in self.editors[filename]:
                if editor._debugger:    # XXX
                    editor.drawbreakpoints()
                else:
                    print 'xxx dead editor!'

    def update_allbreaks(self):
        if self.breaksviewer:
            self.breaksviewer.update()
        for filename in self.breaks.keys():
            if self.editors.has_key(filename):
                for editor in self.editors[filename]:
                    if editor._debugger:    # XXX
                        editor.drawbreakpoints()
                    else:
                        print 'xxx dead editor!'

    def register_editor(self, editor, filename):
        if not filename:
            return
        if not self.editors.has_key(filename):
            self.editors[filename] = [editor]
        elif editor not in self.editors[filename]:
            self.editors[filename].append(editor)

    def unregister_editor(self, editor, filename):
        if not filename:
            return
        try:
            self.editors[filename].remove(editor)
            if not self.editors[filename]:
                del self.editors[filename]
                # if this was an untitled window, clear the breaks.
                if filename[:1] == '<' and filename[-1:] == '>' and \
                                self.breaks.has_key(filename):
                    self.clear_all_file_breaks(filename)
        except (KeyError, ValueError):
            pass


class SourceViewer(W.PyEditor):

    def __init__(self, *args, **kwargs):
        apply(W.PyEditor.__init__, (self,) + args, kwargs)
        self.bind('<click>', self.clickintercept)

    def clickintercept(self, point, modifiers):
        if self._parentwindow._currentwidget <> self and not self.pt_in_breaks(point):
            self._parentwindow.xxx___select(self)
            return 1

    def _getviewrect(self):
        l, t, r, b = self._bounds
        if self._debugger:
            return (l + 12, t + 2, r - 1, b - 2)
        else:
            return (l + 5, t + 2, r - 1, b - 2)

    def select(self, onoff, isclick = 0):
        if W.SelectableWidget.select(self, onoff):
            return
        self.SetPort()
        #if onoff:
        #       self.ted.WEActivate()
        #else:
        #       self.ted.WEDeactivate()
        self.drawselframe(onoff)

    def drawselframe(self, onoff):
        pass


class BreakpointsViewer:

    def __init__(self, debugger):
        self.debugger = debugger
        self.w = W.Window((300, 250), 'Breakpoints', minsize = (200, 200))
        self.w.panes = W.HorizontalPanes((8, 8, -8, -32), (0.3, 0.7))
        self.w.panes.files = W.List(None, callback = self.filehit)              #, flags = Lists.lOnlyOne)
        self.w.panes.gr = W.Group(None)
        self.w.panes.gr.breaks = W.List((0, 0, -130, 0), callback = self.linehit)       #, flags = Lists.lOnlyOne)
        self.w.panes.gr.openbutton = W.Button((-80, 4, 0, 16), 'View\xc9', self.openbuttonhit)
        self.w.panes.gr.deletebutton = W.Button((-80, 28, 0, 16), 'Delete', self.deletebuttonhit)

        self.w.bind('<close>', self.close)
        self.w.bind('backspace', self.w.panes.gr.deletebutton.push)

        self.setup()
        self.w.open()
        self.w.panes.gr.openbutton.enable(0)
        self.w.panes.gr.deletebutton.enable(0)
        self.curfile = None

    def deletebuttonhit(self):
        if self.w._currentwidget == self.w.panes.files:
            self.del_filename()
        else:
            self.del_number()
        self.checkbuttons()

    def del_number(self):
        if self.curfile is None:
            return
        sel = self.w.panes.gr.breaks.getselectedobjects()
        for lineno in sel:
            self.debugger.clear_break(self.curfile, lineno)

    def del_filename(self):
        sel = self.w.panes.files.getselectedobjects()
        for filename in sel:
            self.debugger.clear_all_file_breaks(filename)
        self.debugger.update_allbreaks()

    def setup(self):
        files = self.debugger.breaks.keys()
        files.sort()
        self.w.panes.files.set(files)

    def close(self):
        self.debugger.breaksviewer = None
        self.debugger = None

    def update(self):
        sel = self.w.panes.files.getselectedobjects()
        self.setup()
        self.w.panes.files.setselectedobjects(sel)
        sel = self.w.panes.files.getselection()
        if len(sel) == 0 and self.curfile:
            self.w.panes.files.setselectedobjects([self.curfile])
        self.filehit(0)

    def select(self):
        self.w.select()

    def selectfile(self, file):
        self.w.panes.files.setselectedobjects([file])
        self.filehit(0)

    def openbuttonhit(self):
        self.filehit(1)

    def filehit(self, isdbl):
        sel = self.w.panes.files.getselectedobjects()
        if isdbl:
            for filename in sel:
                lineno = None
                if filename == self.curfile:
                    linesel = self.w.panes.gr.breaks.getselectedobjects()
                    if linesel:
                        lineno = linesel[-1]
                    elif self.w.panes.gr.breaks:
                        lineno = self.w.panes.gr.breaks[0]
                editor = self.w._parentwindow.parent.openscript(filename, lineno)
                editor.showbreakpoints(1)
            return
        if len(sel) == 1:
            file = sel[0]
            filebreaks = self.debugger.breaks[file][:]
            if self.curfile == file:
                linesel = self.w.panes.gr.breaks.getselectedobjects()
            self.w.panes.gr.breaks.set(filebreaks)
            if self.curfile == file:
                self.w.panes.gr.breaks.setselectedobjects(linesel)
            self.curfile = file
        else:
            if len(sel) <> 0:
                self.curfile = None
            self.w.panes.gr.breaks.set([])
        self.checkbuttons()

    def linehit(self, isdbl):
        if isdbl:
            files = self.w.panes.files.getselectedobjects()
            if len(files) <> 1:
                return
            filename = files[0]
            linenos = self.w.panes.gr.breaks.getselectedobjects()
            if not linenos:
                return
            lineno = linenos[-1]
            editor = self.w._parentwindow.parent.openscript(filename, lineno)
            editor.showbreakpoints(1)
        self.checkbuttons()

    def checkbuttons(self):
        if self.w.panes.files.getselection():
            self.w.panes.gr.openbutton.enable(1)
            self.w._parentwindow.setdefaultbutton(self.w.panes.gr.openbutton)
            if self.w._currentwidget == self.w.panes.files:
                if self.w.panes.files.getselection():
                    self.w.panes.gr.deletebutton.enable(1)
                else:
                    self.w.panes.gr.deletebutton.enable(0)
            else:
                if self.w.panes.gr.breaks.getselection():
                    self.w.panes.gr.deletebutton.enable(1)
                else:
                    self.w.panes.gr.deletebutton.enable(0)
        else:
            self.w.panes.gr.openbutton.enable(0)
            self.w.panes.gr.deletebutton.enable(0)


class TracingMonitor(W.Widget):

    def __init__(self, *args, **kwargs):
        apply(W.Widget.__init__, (self,) + args, kwargs)
        self.state = 0

    def toggle(self):
        if hasattr(self, "_parentwindow") and self._parentwindow is not None:
            self.state = self.state % 2 + 1
            port = Qd.GetPort()
            self.SetPort()
            self.draw()
            Qd.SetPort(port)

    def reset(self):
        if self._parentwindow:
            self.state = 0
            port = Qd.GetPort()
            self.SetPort()
            self.draw()
            Qd.SetPort(port)

    def draw(self, visRgn = None):
        if self.state == 2:
            Qd.PaintOval(self._bounds)
        else:
            Qd.EraseOval(self._bounds)


# convenience funcs

def postmortem(exc_type, exc_value, tb):
    d = getdebugger()
    d.postmortem(exc_type, exc_value, tb)

def start(bottomframe = None):
    d = getdebugger()
    d.start(bottomframe)

def startfromhere():
    d = getdebugger()
    try:
        raise 'spam'
    except:
        frame = sys.exc_traceback.tb_frame.f_back
    d.start(frame)

def startfrombottom():
    d = getdebugger()
    d.start(_getbottomframe(), 1)

def stop():
    d = getdebugger()
    d.stop()

def cont():
    sys.settrace(None)
    d = getdebugger()
    d.set_continue_without_debugger()

def _getbottomframe():
    try:
        raise 'spam'
    except:
        pass
    frame = sys.exc_traceback.tb_frame
    while 1:
        if frame.f_code.co_name == 'mainloop' or frame.f_back is None:
            break
        frame = frame.f_back
    return frame

_debugger = None

def getdebugger():
    if not __debug__:
        raise W.AlertError, "Can't debug in \"Optimize bytecode\" mode.\r(see \"Default startup options\" in EditPythonPreferences)"
    global _debugger
    if _debugger is None:
        _debugger = Debugger()
    return _debugger
