#!/var/lib/install/usr/bin/python2.4
# -*- coding: UTF-8 -*-

""" 
	Simple Function Graph Plotter
	Maemo version
	© Thomas Führinger, 2005-02-12
	www.fuhringer.com/thomas
	
	Code contributions by Sam Tygier - thanks!
	
	Version 0.9.0
	Requires PyGtk 2.6	
	Released under the terms of the revised BSD license
	Modified: 2006-02-26
"""

import gtk, math, sys, hildon

App = None
Actions = gtk.ActionGroup("General")
Graph = None
ConnectPoints = True

xMax = "5.0"
xMin = "-5.0"
xScale = "1.0"

yMax = "3.0"
yMin = "-3.0"
yScale = "1.0"

y1 = ""
y2 = ""
y3 = ""
	
class GraphClass:
	def __init__(self):	

		# Create backing pixmap of the appropriate size
		def ConfigureEvent(Widget, Event):
			x, y, w, h = Widget.get_allocation()
			self.PixMap = gtk.gdk.Pixmap(Widget.window, w, h)
			self.CanvasWidth = w
			self.CanvasHeight = h
			self.xMax = eval(xMax)
			self.xMin = eval(xMin)
			self.xScale = eval(xScale)
			self.yMax = eval(yMax)
			self.yMin = eval(yMin)
			self.yScale = eval(yScale)
			self.Plot()
			return True

		# Redraw the screen from the backing pixmap
		def ExposeEvent(Widget, Event):
			x, y, w, h = Event.area
			Widget.window.draw_drawable(Widget.get_style().fg_gc[gtk.STATE_NORMAL], self.PixMap, x, y, x, y, w, h)
			return False

		# Start marking selection
		def ButtonPressEvent(Widget, Event):
			global xSel, ySel
			
			if Event.button == 1:
				self.Selection[0][0], self.Selection[0][1] = int(Event.x), int(Event.y)
				self.Selection[1][0], self.Selection[1][1] = None, None

		# End of selection
		def ButtonReleaseEvent(Widget, Event):
			
			if Event.button == 1 and Event.x != self.Selection[0][0] and Event.y != self.Selection[0][1]:
				xmi, ymi = min(self.GraphX(self.Selection[0][0]), self.GraphX(Event.x)), min(self.GraphY(self.Selection[0][1]), self.GraphY(Event.y))
				xma, yma = max(self.GraphX(self.Selection[0][0]), self.GraphX(Event.x)), max(self.GraphY(self.Selection[0][1]), self.GraphY(Event.y))
				self.xMin, self.yMin, self.xMax, self.yMax = xmi, ymi, xma, yma
				ParameterEntriesRepopulate()
				Graph.Plot()
				self.Selection[1][0] = None
				self.Selection[0][0] = None

		# Draw rectangle during mouse movement
		def MotionNotifyEvent(Widget, Event):
			
			if Event.is_hint:
				x, y, State = Event.window.get_pointer()
			else:
				x = Event.x
				y = Event.y
				State = Event.state

			if State & gtk.gdk.BUTTON1_MASK and self.Selection[0][0] is not None:
				gc = self.DrawingArea.get_style().black_gc
				gc.set_function(gtk.gdk.INVERT)
				if self.Selection[1][0] is not None:
					x0 = min(self.Selection[1][0], self.Selection[0][0])
					y0 = min(self.Selection[1][1], self.Selection[0][1])
					w = abs(self.Selection[1][0] - self.Selection[0][0])
					h = abs(self.Selection[1][1] - self.Selection[0][1])
					self.PixMap.draw_rectangle(gc, False, x0, y0, w, h)
				x0 = min(self.Selection[0][0], int(x))
				y0 = min(self.Selection[0][1], int(y))
				w = abs(int(x) - self.Selection[0][0])
				h = abs(int(y) - self.Selection[0][1])
				self.PixMap.draw_rectangle(gc, False, x0, y0, w, h)
				self.Selection[1][0], self.Selection[1][1] = int(x), int(y)
				self.DrawDrawable()
				
		self.PrevY = [None, None, None]
		
		# Marked area point[0, 1][x, y]
		self.Selection = [[None, None], [None, None]]
		
		self.DrawingArea = gtk.DrawingArea()		
		self.DrawingArea.connect("expose_event", ExposeEvent)
		self.DrawingArea.connect("configure_event", ConfigureEvent)
		self.DrawingArea.connect("button_press_event", ButtonPressEvent)
		self.DrawingArea.connect("button_release_event", ButtonReleaseEvent)
		self.DrawingArea.connect("motion_notify_event", MotionNotifyEvent)
		self.DrawingArea.set_events(gtk.gdk.EXPOSURE_MASK | gtk.gdk.LEAVE_NOTIFY_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.POINTER_MOTION_MASK |gtk.gdk.POINTER_MOTION_HINT_MASK)
			
	def DrawDrawable(self):
		x, y, w, h = self.DrawingArea.get_allocation()
		self.DrawingArea.window.draw_drawable(self.DrawingArea.get_style().fg_gc[gtk.STATE_NORMAL], self.PixMap, 0, 0, 0, 0, w, h)
		
	def Plot(self):	
		self.PixMap.draw_rectangle(self.DrawingArea.get_style().white_gc, True, 0, 0, self.CanvasWidth, self.CanvasHeight)
		
		# draw cross
		self.PixMap.draw_lines(self.DrawingArea.get_style().black_gc, [self.CanvasPoint(0, self.yMin), self.CanvasPoint(0, self.yMax)])
		self.PixMap.draw_lines(self.DrawingArea.get_style().black_gc, [self.CanvasPoint(self.xMin, 0), self.CanvasPoint(self.xMax, 0)])
		
		# draw scaling x
		iv = int(self.xScale * self.CanvasWidth/(self.xMax - self.xMin))
		os = self.CanvasX(0) % iv
		for i in xrange(self.CanvasWidth / iv + 1):
			self.PixMap.draw_lines(self.DrawingArea.get_style().black_gc, [(os + i * iv, self.CanvasY(0) - 5), (os + i * iv, self.CanvasY(0) + 5)])
		# draw scaling y
		iv = int(self.yScale * self.CanvasHeight/(self.yMax - self.yMin))
		os = self.CanvasY(0) % iv
		for i in xrange(self.CanvasHeight / iv + 1):
			self.PixMap.draw_lines(self.DrawingArea.get_style().black_gc, [(self.CanvasX(0) - 5, i * iv + os), (self.CanvasX(0) + 5, i * iv + os)])

		# plot
		# (coloring of lines does not work yet)
		GC1 = self.DrawingArea.get_style().fg_gc[gtk.STATE_NORMAL]
		GC1.foreground = gtk.gdk.color_parse("blue")
		GC2 = self.DrawingArea.get_style().fg_gc[gtk.STATE_NORMAL]
		GC2.foreground = gtk.gdk.color_parse("red")
		GC3 = self.DrawingArea.get_style().fg_gc[gtk.STATE_NORMAL]
		GC3.foreground = gtk.gdk.color_parse("DarkGreen")
		
		self.PrevY = [None, None, None]
		for i in xrange(self.CanvasWidth):
			x = self.GraphX(i + 1)
			for e in ((y1, 0, GC1), (y2, 1, GC2), (y3, 2, GC3)):
				try:
					y = eval(e[0])
					yC = self.CanvasY(y)
					
					if yC < 0 or yC > self.CanvasHeight:
						raise ValueError
					
					if ConnectPoints and self.PrevY[e[1]] is not None:
						self.PixMap.draw_lines(e[2], [(i, self.PrevY[e[1]]), (i + 1, yC)])
					else:
						self.PixMap.draw_points(e[2], [(i + 1, yC)])
					self.PrevY[e[1]] = yC
				except:
					#print "Error at %d: %s" % (x, sys.exc_value)
					self.PrevY[e[1]] = None
		self.DrawDrawable()

		
	def CanvasX(self, x):
		"Calculate position on canvas to point on graph"
		return int((x - self.xMin) * self.CanvasWidth/(self.xMax - self.xMin))

	def CanvasY(self, y):
		return int((self.yMax - y) * self.CanvasHeight/(self.yMax - self.yMin))
		
	def CanvasPoint(self, x, y):
		return (self.CanvasX(x), self.CanvasY(y))
	
	def GraphX(self, x):
		"Calculate position on graph from point on canvas"
		return x  * (self.xMax - self.xMin) / self.CanvasWidth + self.xMin
		
	def GraphY(self, y):
		return self.yMax - (y * (self.yMax - self.yMin) / self.CanvasHeight)
		
		
def MenuToolbarCreate():

	MenuMain = hildon.AppView.get_menu(App.View)
		
	Actions.Plot = gtk.Action("Plot", "P_lot", "Plot Functions", gtk.STOCK_REFRESH)
	Actions.Plot.connect ("activate", Plot)
	Actions.add_action(Actions.Plot)
	MenuItemPlot = Actions.Plot.create_menu_item()
	MenuMain.append(MenuItemPlot)
	
	Actions.Evaluate = gtk.Action("Evaluate", "_Evaluate", "Evaluate Functions", gtk.STOCK_EXECUTE)
	Actions.Evaluate.connect ("activate", Evaluate)
	Actions.add_action(Actions.Evaluate)
	MenuItemEvaluate = Actions.Evaluate.create_menu_item()
	MenuMain.append(MenuItemEvaluate)
	
	Actions.ZoomIn = gtk.Action("ZoomIn", "Zoom _In", "Zoom In", gtk.STOCK_ZOOM_IN)
	Actions.ZoomIn.connect ("activate", ZoomIn)
	Actions.add_action(Actions.ZoomIn)
	MenuItemZoomIn = Actions.ZoomIn.create_menu_item()
	MenuMain.append(MenuItemZoomIn)
	
	Actions.ZoomOut = gtk.Action("ZoomOut", "Zoom _Out", "Zoom Out", gtk.STOCK_ZOOM_OUT)
	Actions.ZoomOut.connect ("activate", ZoomOut)
	Actions.add_action(Actions.ZoomOut)
	MenuItemZoomOut = Actions.ZoomOut.create_menu_item()
	MenuMain.append(MenuItemZoomOut)
	
	Actions.ZoomReset = gtk.Action("ZoomReset", "Zoom _Reset", "Zoom Reset", gtk.STOCK_ZOOM_100)
	Actions.ZoomReset.connect ("activate", ZoomReset)
	Actions.add_action(Actions.ZoomReset)
	MenuItemZoomReset = Actions.ZoomReset.create_menu_item()
	MenuMain.append(MenuItemZoomReset)
	
	MenuItemToggleConnect = gtk.CheckMenuItem("_Connect Points")
	MenuItemToggleConnect.set_active(True)
	MenuItemToggleConnect.connect ("toggled", ToggleConnect)
	MenuMain.append(MenuItemToggleConnect)

	Actions.About = gtk.Action("About", "_About", "About Box", gtk.STOCK_ABOUT)
	Actions.About.connect ("activate", ShowAboutDialog)
	Actions.add_action(Actions.About)
	MenuItemAbout = Actions.About.create_menu_item()
	MenuMain.append(MenuItemAbout)
	
	
	ToolBar = gtk.Toolbar()
	ToolBar.insert(Actions.Plot.create_tool_item(), -1)
	ToolBar.insert(Actions.Evaluate.create_tool_item(), -1)
	ToolBar.insert(gtk.SeparatorToolItem(), -1)
	ToolBar.insert(Actions.ZoomIn.create_tool_item(), -1)
	ToolBar.insert(Actions.ZoomOut.create_tool_item(), -1)
	ToolBar.insert(Actions.ZoomReset.create_tool_item(), -1)
	#App.View.set_toolbar(ToolBar)
	gtk.Widget.show_all(MenuMain)
	gtk.Widget.show_all(ToolBar)
	

def Plot(Widget, Event=None):
	global xMax, xMin, xScale, yMax, yMin, yScale, y1, y2, y3
	
	xMax = App.xMaxEntry.get_text()
	xMin = App.xMinEntry.get_text()
	xScale = App.xScaleEntry.get_text()

	yMax = App.yMaxEntry.get_text()
	yMin = App.yMinEntry.get_text()
	yScale = App.yScaleEntry.get_text()
	
	Graph.xMax = eval(xMax)
	Graph.xMin = eval(xMin)
	Graph.xScale = eval(xScale)

	Graph.yMax = eval(yMax)
	Graph.yMin = eval(yMin)
	Graph.yScale = eval(yScale)

	y1 = App.Y1Entry.get_text()
	y2 = App.Y2Entry.get_text()
	y3 = App.Y3Entry.get_text()
	
	Graph.Plot()
	

def Evaluate(Widget, Event=None):
	"Evaluate a given x for the three functions"
	
	def EntryChanged(self):
		for e in ((y1, DlgWin.Y1Entry), (y2, DlgWin.Y2Entry), (y3, DlgWin.Y3Entry)):
			try:
				x = float(DlgWin.XEntry.get_text())
				e[1].set_text(str(eval(e[0])))
			except:
				if len(e[0]) > 0:
					e[1].set_text("Error: %s" % sys.exc_value)
				else:
					e[1].set_text("")
				
	def Close(self):
		DlgWin.destroy()
		
	DlgWin = gtk.Window(gtk.WINDOW_TOPLEVEL)
	DlgWin.set_title("Evaluate")
	DlgWin.connect("destroy", Close)
	
	DlgWin.XEntry = gtk.Entry()
	DlgWin.XEntry.set_size_request(200, 24)
	DlgWin.XEntry.connect("changed", EntryChanged)
	DlgWin.Y1Entry = gtk.Entry()
	DlgWin.Y1Entry.set_size_request(200, 24)
	DlgWin.Y1Entry.set_sensitive(False)
	DlgWin.Y2Entry = gtk.Entry()
	DlgWin.Y2Entry.set_size_request(200, 24)
	DlgWin.Y2Entry.set_sensitive(False)
	DlgWin.Y3Entry = gtk.Entry()
	DlgWin.Y3Entry.set_size_request(200, 24)
	DlgWin.Y3Entry.set_sensitive(False)
	
	Table = gtk.Table(2, 5)
	l = gtk.Label("x = ")
	l.set_alignment(0, .5)
	Table.attach(l, 0, 1, 0, 1, xpadding=1, ypadding=1, xoptions=gtk.FILL)
	Table.attach(DlgWin.XEntry, 1, 2, 0, 1)
	l = gtk.Label("y1 = ")
	l.set_alignment(0, .5)
	l.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("blue"))
	Table.attach(l, 0, 1, 1, 2, xpadding=1, ypadding=1, xoptions=gtk.FILL)
	Table.attach(DlgWin.Y1Entry, 1, 2, 1, 2)
	l = gtk.Label("y2 = ")
	l.set_alignment(0, .5)
	l.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("red"))
	Table.attach(l, 0, 1, 2, 3, xpadding=1, ypadding=1, xoptions=gtk.FILL)
	Table.attach(DlgWin.Y2Entry, 1, 2, 2, 3)
	l = gtk.Label("y3 = ")
	l.set_alignment(0, .5)
	l.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("DarkGreen"))
	Table.attach(l, 0, 1, 3, 4, xpadding=1, ypadding=1, xoptions=gtk.FILL)
	Table.attach(DlgWin.Y3Entry, 1, 2, 3, 4)
	
	Table.set_border_width(2)	
	DlgWin.add(Table)	
	DlgWin.show_all()


def ZoomIn(Widget, Event=None):
	"Narrow the plotted section by half"
	
	Graph.xMin /= 2
	Graph.yMin /= 2
	Graph.xMax /= 2
	Graph.yMax /= 2
	ParameterEntriesRepopulate()
	Graph.Plot()


def ZoomOut(Widget, Event=None):
	"Double the plotted section"
	
	Graph.xMin *= 2
	Graph.yMin *= 2
	Graph.xMax *= 2
	Graph.yMax *= 2
	ParameterEntriesRepopulate()
	Graph.Plot()


def ZoomReset(Widget, Event=None):
	"Set the range back to the user's input"
   
	Graph.xMin = eval(xMin)
	Graph.yMin = eval(yMin)
	Graph.xMax = eval(xMax)
	Graph.yMax = eval(yMax)
	ParameterEntriesPopulate()
	Graph.Plot()


def ToggleConnect(Widget, Event=None):
	"Toggle between a graph that connects points with lines and one that does not"
	
	global ConnectPoints
	ConnectPoints = not ConnectPoints
	Graph.Plot()
	

def ShowAboutDialog(Widget):
	AboutDialog = gtk.AboutDialog()
	AboutDialog.set_name("Lybniz")
	AboutDialog.set_version("0.9.0")
	#AboutDialog.set_copyright(u"© 2005 by Thomas Führinger")
	AboutDialog.set_authors([u"Thomas Führinger"])
	AboutDialog.set_comments("Function Graph Plotter")
	AboutDialog.set_license("Revised BSD")
	#AboutDialog.set_website("http://www.fuhringer.com/thomas/lybniz")
	AboutDialog.show()


def ParameterEntriesCreate():
	# create text entries for parameters	
	Table = gtk.Table(6, 3)
	
	App.Y1Entry = gtk.Entry()
	App.Y1Entry.set_size_request(300, 24)
	App.Y2Entry = gtk.Entry()
	App.Y3Entry = gtk.Entry()
	App.xMinEntry = gtk.Entry()
	App.xMinEntry.set_size_request(90, 24)
	App.xMinEntry.set_alignment(1)
	App.xMaxEntry = gtk.Entry()
	App.xMaxEntry.set_size_request(90, 24)
	App.xMaxEntry.set_alignment(1)
	App.xScaleEntry = gtk.Entry()
	App.xScaleEntry.set_size_request(90, 24)
	App.xScaleEntry.set_alignment(1)
	App.yMinEntry = gtk.Entry()
	App.yMinEntry.set_size_request(90, 24)
	App.yMinEntry.set_alignment(1)
	App.yMaxEntry = gtk.Entry()
	App.yMaxEntry.set_size_request(90, 24)
	App.yMaxEntry.set_alignment(1)
	App.yScaleEntry = gtk.Entry()
	App.yScaleEntry.set_size_request(90, 24)
	App.yScaleEntry.set_alignment(1)
	
	ParameterEntriesPopulate()
	
	l = gtk.Label("y1 = ")
	l.set_alignment(0, .5)
	l.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("blue"))
	Table.attach(l, 0, 1, 0, 1, xpadding=5, ypadding=5, xoptions=gtk.FILL)
	Table.attach(App.Y1Entry, 1, 2, 0, 1)
	l = gtk.Label("xMin")
	l.set_alignment(1, .5)
	Table.attach(l, 2, 3, 0, 1, xpadding=5, ypadding=7, xoptions=gtk.FILL)
	Table.attach(App.xMinEntry, 3, 4, 0, 1, xoptions=gtk.FILL)
	l = gtk.Label("yMin")
	l.set_alignment(1, .5)
	Table.attach(l, 4, 5, 0, 1, xpadding=5, ypadding=5, xoptions=gtk.FILL)
	Table.attach(App.yMinEntry, 5, 6, 0, 1, xpadding=5, xoptions=gtk.FILL)
	l = gtk.Label("y2 = ")
	l.set_alignment(0, .5)
	l.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("red"))
	Table.attach(l, 0, 1, 1, 2, xpadding=5, ypadding=5, xoptions=gtk.FILL)
	Table.attach(App.Y2Entry, 1, 2, 1, 2)
	l = gtk.Label("xMax")
	l.set_alignment(1, .5)
	Table.attach(l, 2, 3, 1, 2, xpadding=5, ypadding=7, xoptions=gtk.FILL)
	Table.attach(App.xMaxEntry, 3, 4, 1, 2, xoptions=gtk.FILL)
	l = gtk.Label("yMax")
	l.set_alignment(1, .5)
	Table.attach(l, 4, 5, 1, 2, xpadding=5, ypadding=5, xoptions=gtk.FILL)
	Table.attach(App.yMaxEntry, 5, 6, 1, 2, xpadding=5, xoptions=gtk.FILL)
	l = gtk.Label("y3 = ")
	l.set_alignment(0, .5)
	l.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("DarkGreen"))
	Table.attach(l, 0, 1, 2, 3, xpadding=5, ypadding=5, xoptions=gtk.FILL)
	Table.attach(App.Y3Entry, 1, 2, 2, 3)
	l = gtk.Label("xScale")
	l.set_alignment(0, .5)
	Table.attach(l, 2, 3, 2, 3, xpadding=5, ypadding=7, xoptions=gtk.FILL)
	Table.attach(App.xScaleEntry, 3, 4, 2, 3, xoptions=gtk.FILL)
	l = gtk.Label("yScale")
	l.set_alignment(0, .5)
	Table.attach(l, 4, 5, 2, 3, xpadding=5, ypadding=5, xoptions=gtk.FILL)
	Table.attach(App.yScaleEntry, 5, 6, 2, 3, xpadding=5, xoptions=gtk.FILL)
	return Table
	
	
def ParameterEntriesPopulate():
	# set text in entries for parameters with user's input
	
	App.Y1Entry.set_text(y1)
	App.Y2Entry.set_text(y2)
	App.Y3Entry.set_text(y3)
	App.xMinEntry.set_text(xMin)
	App.xMaxEntry.set_text(xMax)
	App.xScaleEntry.set_text(xScale)
	App.yMinEntry.set_text(yMin)
	App.yMaxEntry.set_text(yMax)
	App.yScaleEntry.set_text(yScale)
	
	
def ParameterEntriesRepopulate():
	# set text in entries for parameters
	
	App.Y1Entry.set_text(y1)
	App.Y2Entry.set_text(y2)
	App.Y3Entry.set_text(y3)
	App.xMinEntry.set_text(str(Graph.xMin))
	App.xMaxEntry.set_text(str(Graph.xMax))
	App.xScaleEntry.set_text(str(Graph.xScale))
	App.yMinEntry.set_text(str(Graph.yMin))
	App.yMaxEntry.set_text(str(Graph.yMax))
	App.yScaleEntry.set_text(str(Graph.yScale))
	
	
class Lybniz:

	def __init__(self):
		global App, Graph
		
		App = hildon.App()
		App.View = hildon.AppView("Lybniz")

		App.set_title("Lybniz")
		App.set_two_part_title(False)
		App.set_appview(App.View)
		
		MenuToolbarCreate()
		
		App.View.connect("destroy", self.destroy)
		
		App.VBox = gtk.VBox(False, 1)
		App.VBox.set_border_width(1)
		gtk.Container.add(App.View, App.VBox)
	
		App.VBox.pack_start(ParameterEntriesCreate(), False, True, 4)
		
		Graph = GraphClass()
		App.VBox.pack_start(Graph.DrawingArea, True, True, 0)
	
		App.VBox.show_all()
		App.show()
		gtk.main()
		
	def destroy(self, widget, data=None):
		gtk.main_quit()
		
		
# Start it all
if __name__ == "__main__":
	l = Lybniz()
