#!/usr/bin/python

import sys
import bdb
import repr
import string
import linecache # for linecache.getlines(filename)
from Gtkinter import *
import GtkExtra

class GtkDb(GtkWindow, bdb.Bdb):
	def __init__(self):
		GtkWindow.__init__(self)
		bdb.Bdb.__init__(self)
		self.realize()

		self.set_title("GtkDb")
		self.connect("destroy", self.do_quit)
		self.connect("delete_event", self.do_quit)

		self.box = GtkVBox()
		self.add(self.box)
		self.box.show()

		toolbar = GtkToolbar(ORIENTATION_HORIZONTAL, TOOLBAR_BOTH)
		toolbar.set_space_size(10)
		self.box.pack_start(toolbar, expand=FALSE)
		toolbar.show()

		toolbar.append_item("next", "Next statement", None,
				    GtkPixmap(self, "next.xpm"), self.do_next)
		toolbar.append_item("step", "Step into function", None,
				    GtkPixmap(self, "step.xpm"), self.do_step)
		toolbar.append_space()
		toolbar.append_item("return",
				    "Continue execution to end of function",
				    None, GtkPixmap(self, "return.xpm"),
				    self.do_return)
		toolbar.append_space()
		toolbar.append_item("continue",
				    "Continue execution to next break point",
				    None, GtkPixmap(self, "continue.xpm"),
				    self.do_continue)
		toolbar.append_item("break",
				    "toggle break point at selected line",None,
				    GtkPixmap(self,"break.xpm"),self.do_break)
		toolbar.append_space()
		toolbar.append_item("edit",
				    "edit the value of the selected variable",
				    None, GtkPixmap(self, "edit.xpm"),
				    self.do_edit)
		toolbar.append_item("run",
			      "Execute some code in the current stack context",
			      None, GtkPixmap(self, "run.xpm"),
			      self.do_run)
		toolbar.append_space()
		toolbar.append_item("quit", "Quit the debugger", None,
				    GtkPixmap(self, "quit.xpm"), self.do_quit)

		sep = GtkHSeparator()
		self.box.pack_start(sep, expand=FALSE)
		sep.show()

		vpane = GtkVPaned()
		self.box.pack_start(vpane)
		vpane.show()

		hpane = GtkHPaned()
		vpane.add1(hpane)
		hpane.show()

		self.stackdisp = GtkCList(1, ['Stack Frame'])
		self.stackdisp.connect("select_row", self.update_curstack)
		self.stackdisp.set_usize(280, 125)
		self.stackdisp.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
		self.stackdisp.set_selection_mode(SELECTION_BROWSE)
		self.stackdisp.border_width(2)
		hpane.add1(self.stackdisp)
		self.stackdisp.show()

		self.vardisp = GtkCList(3, ['local var', 'type', 'value'])
		self.vardisp.connect("select_row", self.update_selectedvar)
		self.vardisp.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
		self.vardisp.set_selection_mode(SELECTION_BROWSE)
		self.vardisp.set_column_width(0, 75)
		self.vardisp.set_column_width(1, 30)
		self.vardisp.border_width(2)
		hpane.add2(self.vardisp)
		self.vardisp.show()
		self.vardisp.selected = 0
		self.vardisp.varnames = []

		self.filedisp = GtkCList(3, ['break', 'lineno', 'line'])
		self.minibreak = GtkPixmap(self, "minibreak.xpm")
		# This is giving a warning -- probably a bug in GTK
		self.filedisp.set_column_widget(0, self.minibreak)
		self.filedisp.column_titles_show()
		self.filedisp.set_column_width(0, 14)
		self.filedisp.set_column_width(1, 35)
		self.filedisp.connect("select_row", self.update_selection)
		self.filedisp.set_column_justification(1, JUSTIFY_RIGHT)
		self.filedisp.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
		self.filedisp.set_selection_mode(SELECTION_BROWSE)
		self.filedisp.border_width(2)
		self.filedisp.set_usize(600, 200)
		vpane.add2(self.filedisp)
		self.filedisp.show()

		separator = GtkHSeparator()
		self.box.pack_start(separator, expand=FALSE)
		separator.show()

		align = GtkAlignment(0.0, 0.5, 0.0, 0.0)
		self.box.pack_start(align, expand=FALSE)
		align.show()
		self.status = GtkLabel()
		self.status.set_padding(4, 1)
		align.add(self.status)
		self.status.show()

		self.filename = None
		self.selected = 0
		self.blockupdate = 0

	def set_status(self, str):
		self.status.set(str)

	def update_selection(self, clist, r, c, event):
		if self.blockupdate: return
		self.selected = r + 1
		if c == 0: # break point column
			self.do_break()

	def update_curstack(self, clist, r, c, event):
		if self.blockupdate: return
		self.curindex = r
		self.curframe = self.stack[self.curindex][0]
		self.lineno = None
		self.update_code_listing()
		self.update_var_listing()

	def update_selectedvar(self, clist, r, c, event):
		self.vardisp.selected = r
		
	def set_quit(self):
		self.hide()
		self.destroy()
		bdb.Bdb.set_quit(self)

	def reset(self):
		bdb.Bdb.reset(self)
		self.forget()
	def forget(self):
		self.lineno = None
		self.stack = []
		self.curindex = 0
		self.curframe = None
	def setup(self, f, t):
		self.forget()
		self.stack, self.curindex = self.get_stack(f, t)
		self.curframe = self.stack[self.curindex][0]

	# interaction functions -- overriden from bdb
	def user_line(self, frame):
		# called when we stop or break at this line
		self.interaction(frame, None)
	def user_return(self, frame, return_value):
		# called when a return trap is set here
		frame.f_locals['__return__'] = return_value
		if frame.f_code.co_name:
			func = frame.f_code.co_name
		else:
			func = "<lambda>"
		self.set_status(func + " rerturned " + repr.repr(return_value))
		self.interaction(frame, None)
	def user_exception(self, frame, (exc_type, exc_value, exc_traceback)):
		frame.f_locals['__exception__'] = exc_type, exc_value
		if type(exc_type) == type(''):
			exc_type_name = exc_type
		else: exc_type_name = exc_type.__name__
		self.set_status(exc_type_name + ':' + repr.repr(exc_value))
		self.interaction(frame, exc_traceback)

	def interaction(self, frame, traceback):
		self.setup(frame, traceback)
		self.update_stack_listing(self.curindex)
		mainloop()
		self.forget()

	def update_stack_listing(self, curindex):
		self.stackdisp.freeze()
		self.blockupdate = 1
		self.stackdisp.clear()
		for i in range(len(self.stack)):
			frame_lineno = self.stack[i]
			row = self.format_stack_entry(frame_lineno, "##!##")
			row = string.split(row, "##!##")[0]
			self.stackdisp.append([row])
		self.blockupdate = 0
		self.stackdisp.moveto(curindex, 0, 1.0, 0.0)
		self.stackdisp.select_row(curindex, 0)
		self.stackdisp.thaw()

	def update_var_listing(self):
		self.vardisp.freeze()
		self.vardisp.clear()
		locals = self.curframe.f_locals
		self.vardisp.varnames = locals.keys()
		self.vardisp.varnames.sort()
		for var in self.vardisp.varnames:
			row = [var, type(locals[var]).__name__, repr.repr(
				locals[var])]
			self.vardisp.append(row)
		self.vardisp.thaw()

	def update_code_listing(self):
		frame = self.curframe
		newfile = frame.f_code.co_filename
		if newfile != self.filename:
			lines = linecache.getlines(newfile)
			self.filename = newfile
			self.filedisp.freeze()
			self.blockupdate = 1
			self.filedisp.clear()
			breaks = self.get_file_breaks(newfile)
			for line in range(len(lines)):
				self.filedisp.append(['', repr.repr(line+1),
						      lines[line]])
				if line+1 in breaks:
					self.filedisp.set_pixmap(line, 0,
							 self.minibreak)
			self.blockupdate = 0
			self.filedisp.thaw()
		self.selected = frame.f_lineno
		lineno = self.selected
		self.filedisp.moveto(lineno - 1, 2)
		self.filedisp.select_row(lineno - 1, 2)

	def do_next(self, _b=None):
		self.set_next(self.curframe)
		mainquit()
	def do_step(self, _b=None):
		self.set_step()
		mainquit()
	def do_return(self, _b=None):
		self.set_return(self.curframe)
		mainquit()
	def do_continue(self, _b=None):
		self.set_continue()
		mainquit()
	def do_quit(self, _b=None, _e=None):
		self.set_quit()
		mainquit()
	def do_break(self, _b=None):
		breaks = self.get_file_breaks(self.filename)
		if self.selected in breaks:
			err = self.clear_break(self.filename, self.selected)
			if err:
				self.set_status(err)
				return
			self.filedisp.set_text(self.selected-1, 0, ' ')
		else:
			err = self.set_break(self.filename, self.selected)
			if err:
				self.set_status(err)
				return
			self.filedisp.set_pixmap(self.selected-1, 0,
						 self.minibreak)
	def do_run(self, _b=None):
		line = GtkExtra.input_box("Execute Code",
					  "Enter code to execute:")
		if line == None: return
		locals = self.curframe.f_locals
		globals = self.curframe.f_globals
		globals['__privileged__'] = 1
		try:
			code = compile(line + '\n', '<stdin>', 'single')
			exec code in globals, locals
		except:
			if type(sys.exc_type) == type(''):
				exc_type_name = sys.exc_type
			else: exc_type_name = sys.exc_type.__name__
			self.set_status('*** ' + exc_type_name + ': ' +
					str(sys.exc_value))
			return
		self.update_var_listing()

	def do_edit(self, _b=None):
		locals = self.curframe.f_locals
		varname = self.vardisp.varnames[self.vardisp.selected]
		val = repr.repr(locals[varname])
		value = GtkExtra.input_box("Edit Variable",
				"Enter new value for" +	varname + ":", val)
		if value == None: return
		globals = self.curframe.f_globals
		globals['__privileged__'] = 1
		try:
			val = eval(value, globals, locals)
			self.curframe.f_locals[varname] = val
		except:
			if type(sys.exc_type) == type(''):
				exc_type_name = sys.exc_type
			else: exc_type_name = sys.exc_type.__name__
			self.set_status('*** ' + exc_type_name + ': ' +
					str(sys.exc_value))
			return
		row = self.vardisp.selected
		self.vardisp.set_text(row, 1, type(val).__name__)
		self.vardisp.set_text(row, 2, repr.repr(val))

# this makes up the interface that is compatible with pdb.
def run(statement, globals=None, locals=None):
	win = GtkDb()
	win.show()
	win.run(statement, globals, locals)

def runeval(expression, globals=None, locals=None):
	win = GtkDb()
	win.show()
	return win.runeval(expression, globals, locals)

def runcall(*args):
	win = GtkDb()
	win.show()
	return apply(win.runcall, args)

def set_trace():
	win = GtkDb()
	win.show()
	win.set_trace()

def post_mortem(traceback):
	win = GtkDb()
	win.show()
	win.reset()
	win.interaction(None, traceback)

def pm():
	post_mortem(sys.last_traceback)

if __name__ == '__main__':
	import os
	if not sys.argv[1:]:
		print "usage: gtkdb.py scriptfile [args ...]"
		sys.exit(2)
	filename = sys.argv[1]
	del sys.argv[0] # delete gtkdb.py
	sys.path.insert(0, os.path.dirname(filename))

	run('execfile("' + filename + '")', {'__name__': '__main__'})
