# -*- coding: UTF-8 -*-
""" Graph module - plot function graphs
	Thomas Führinger, 2005-10-28
"""

import gtk, sys, __main__
	
class Graph2D:
	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.Refresh()
			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

		def ButtonPressEvent(Widget, Event):
			global xSel, ySel
			
			# Start marking selection
			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
			
			# Show popup menu allowing to split window
			if Event.button == 3 and self.WorkbenchWindow is not None:
				m = self.WorkbenchWindow.PopupMenuCreate()
				MenuItemSave = self.ActionSave.create_menu_item()
				m.append(MenuItemSave)
				m.popup(None, None, None, 3, Event.time)

		# 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
				self.GtkSpinButton.set_value(1)
				self.Refresh()
				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.xMax = 10.0
		self.xMin = -10.0
		self.xScale = 1
		self.yMax = 10.0
		self.yMin = -10.0
		self.yScale = 1
		
		self.y = []
		
		self.Zoom = 1
		self.ConnectPoints = True
		
		self.GtkWidget = gtk.VBox()
		self.WorkbenchWindow = None
	
		self.ToolBar = gtk.Toolbar()	
		ti = gtk.ToolItem()
		ti.add(gtk.Label(" Zoom Factor  "))		
		self.ToolBar.insert(ti, -1)
		
		self.GtkSpinButton = gtk.SpinButton(None, 1, 1)
		self.GtkSpinButton.set_numeric(True)
		self.GtkSpinButton.set_range(0.1, 100.0)
		self.GtkSpinButton.set_increments(.2, .2)
		self.GtkSpinButton.set_value(1)		
		ti = gtk.ToolItem()
		ti.add(self.GtkSpinButton)		
		self.ToolBar.insert(ti, -1)
		
		self.ActionRefresh = gtk.Action("Refresh", "_Refresh", "Refresh graphs", gtk.STOCK_REFRESH)
		self.ActionRefresh.connect ("activate", self.Refresh)
		self.ToolBar.insert(self.ActionRefresh.create_tool_item(), -1)
		self.HandleBox = gtk.HandleBox()
		self.HandleBox.add(self.ToolBar)
		self.GtkWidget.pack_start(self.HandleBox, False, False, 0)
		
		self.ActionSave = gtk.Action("Save", "_Save", "Save graph", gtk.STOCK_SAVE)
		self.ActionSave.connect ("activate", self.Save)
				
		# Marked area point[0, 1][x, y]
		self.Selection = [[None, None], [None, None]]
		
		self.DrawingArea = gtk.DrawingArea()
		self.DrawingArea.set_size_request(60, 60)
		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)
		
		self.GtkWidget.pack_start(self.DrawingArea, True, True, 0)
			
	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 Refresh(self, Widget=None, Event=None):
		
		self.GtkSpinButton.update()
		self.Zoom = self.GtkSpinButton.get_value()
		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.Zoom), self.CanvasPoint(0, self.yMax / self.Zoom)])
		self.PixMap.draw_lines(self.DrawingArea.get_style().black_gc, [self.CanvasPoint(self.xMin * self.Zoom, 0), self.CanvasPoint(self.xMax / self.Zoom, 0)])
		
		# draw scaling x
		iv = int(self.xScale * self.CanvasWidth/(self.xMax - self.xMin) * self.Zoom)
		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 x
		iv = int(self.yScale * self.CanvasHeight/(self.yMax - self.yMin) * self.Zoom)
		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
		GC1 = self.DrawingArea.get_style().fg_gc[gtk.STATE_NORMAL]
		GC1.foreground = gtk.gdk.color_parse("blue")
		
		PrevY = []
		for i in  xrange(len(self.y)): PrevY.append(None)
		for i in xrange(self.CanvasWidth):
			__main__.x = self.GraphX(i + 1)
			for ii in xrange(len(self.y)):
				try:
					y = self.y[ii]()
					yC = self.CanvasY(y)
					
					if yC < 0 or yC > self.CanvasHeight:
						raise ValueError
					
					if self.ConnectPoints and PrevY[ii] is not None:
						self.PixMap.draw_lines(GC1, [(i, PrevY[ii]), (i + 1, yC)])
					else:
						self.PixMap.draw_points(GC1, [(i + 1, yC)])
					PrevY[ii] = yC
				except:
					#print "Error at %d: %s" % (__main__.x, sys.exc_value)
					PrevY[ii] = None
		self.DrawDrawable()

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

	def CanvasY(self, y):
		return int((self.yMax / self.Zoom - y) * self.CanvasHeight/(self.yMax - self.yMin) * self.Zoom)
		
	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.Zoom / self.CanvasWidth + self.xMin / self.Zoom
		
	def GraphY(self, y):
		return self.yMax / self.Zoom - (y * (self.yMax * self.Zoom - self.yMin / self.Zoom) / self.CanvasHeight)
		
	def ConnectWindow(self, WorkbenchWindow):
		"make widget appear in WorkbenchWindow"
		
		self.WorkbenchWindow = WorkbenchWindow
		self.WorkbenchWindow.Connect(self.GtkWidget)
		self.GtkWidget.show_all()

	def Save(self, Widget, Event=None):
		"Save graph as .png"

		FileDialog = gtk.FileChooserDialog("Save as..", None, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK))
		FileDialog.set_default_response(gtk.RESPONSE_OK)
		Filter = gtk.FileFilter()
		Filter.add_mime_type("image/png")
		Filter.add_pattern("*.png")
		FileDialog.add_filter(Filter)
		FileDialog.set_filename("FunctionGraph.png")
		
		Response = FileDialog.run()
		FileDialog.destroy()
		if Response == gtk.RESPONSE_OK:
			x, y, w, h = self.DrawingArea.get_allocation()
			PixBuffer = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, w, h)
			PixBuffer.get_from_drawable(self.PixMap, self.PixMap.get_colormap(), 0, 0, 0, 0, w, h)
			PixBuffer.save(FileDialog.get_filename(), "png")
	


syntax highlighted by Code2HTML, v. 0.9.1