#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4

import random
import sys
import wx
import wx.grid

LIGHT_RED = "#ddaaaa"
LIGHT_YELLOW = "#ffffa6"
SPACER_TOP = 0.1
SPACER_MIDDLE = 0.2
SPACER_BOTTOM = 0.1
BAR_SIZE = 0.3
NUM_DATA = 40
ROW_SIZE = 30
MAGIC_LIST_HEIGHT = 5


class CustomDataTable(wx.grid.PyGridTableBase):
	def __init__(self):
		wx.grid.PyGridTableBase.__init__(self)

		self.colLabels = ['January', 'February', "March", "April",
			"May", "June", "July", "August", "September", "October",
			"November", "December"]

		self.dataTypes = [wx.grid.GRID_VALUE_FLOAT] * 12

		self.data = []
		for row in range(NUM_DATA):
			self.data.append([0] * 12)
			for f in range(12):
				self.data[row][f] = (random.random(), random.random())

	# Required methods for the wxPyGridTableBase interface
	def GetNumberRows(self):
		return len(self.data)

	def GetNumberCols(self):
		return len(self.data[0])

	def IsEmptyCell(self, row, col):
		try:
			return not self.data[row][col]
		except IndexError:
			return True

	# Get/Set values in the table.  The Python version of these
	# methods can handle any data-type, (as long as the Editor and
	# Renderer understands the type too,) not just strings as in the
	# C++ version.
	def GetValue(self, row, col):
		try:
			return self.data[row][col]
		except IndexError:
			return ''

	def SetValue(self, row, col, value):
		try:
			self.data[row][col] = value
		except IndexError:
			# add a new row
			self.data.append([''] * self.GetNumberCols())
			self.SetValue(row, col, value)

			# tell the grid we've added a row
			msg = wx.grid.GridTableMessage(self,            # The table
				wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, # what we did to it
				1                                       # how many
				)

			self.GetView().ProcessTableMessage(msg)


	#--------------------------------------------------
	# Some optional methods

	# Called when the grid needs to display labels
	def GetColLabelValue(self, col):
		return self.colLabels[col]

	# Called to determine the kind of editor/renderer to use by
	# default, doesn't necessarily have to be the same type used
	# natively by the editor/renderer if they know how to convert.
	def GetTypeName(self, row, col):
		return self.dataTypes[col]

	# Called to determine how the data can be fetched and stored by the
	# editor and renderer.  This allows you to enforce some type-safety
	# in the grid.
	def CanGetValueAs(self, row, col, typeName):
		colType = self.dataTypes[col].split(':')[0]
		if typeName == colType:
			return True
		else:
			return False

	def CanSetValueAs(self, row, col, typeName):
		return self.CanGetValueAs(row, col, typeName)


class CustomTableGrid(wx.grid.Grid):
	def __init__(self, parent, bound_widget = None):
		self.bound_widget = bound_widget
		wx.grid.Grid.__init__(self, parent)

		self.table = CustomDataTable()

		# The second parameter means that the grid is to take ownership of the
		# table and will destroy it when done.  Otherwise you would need to
		# keep a reference to it and call it's Destroy method later.
		self.SetTable(self.table, True)

		#self.SetRowLabelSize(0)
		self.SetMargins(0, 0)
		self.AutoSizeColumns(True)
		# Disallow rows stretching vertically and set a fixed height.
		self.DisableDragRowSize()
		self.SetRowMinimalAcceptableHeight(ROW_SIZE)
		self.SetDefaultRowSize(ROW_SIZE, True)
		self.SetScrollRate(self.GetScrollLineX(), ROW_SIZE)
		# Don't let the user change the values.
		self.EnableEditing(False)

		# Create a scrollbar handler.
		self.Bind(wx.EVT_SCROLLWIN, self.did_scroll)
		self.v = 0
		self.locked = False


	def did_scroll(self, event):
		try:
			if event.GetOrientation() != wx.VERTICAL or self.locked:
				return

			print "%4d, grid pos was %d, will be %d" % (self.v,
				self.GetScrollPos(wx.VERTICAL), event.GetPosition())
			self.bound_widget.scroll_to(event.GetPosition())
			self.v += 1
		finally:
			event.Skip()


	def scroll_to(self, position):
		self.locked = True
		self.Scroll(-1, position)
		self.locked = False


class List_control(wx.ListCtrl):
	def fill(self, bound_widget = None):
		self.bound_widget = bound_widget
		# Create the images with the desired height.
		self.image_list = wx.ImageList(1, ROW_SIZE - 1)
		self.SetImageList(self.image_list, wx.IMAGE_LIST_SMALL)

		self.InsertColumn(0, "Col 1")
		self.InsertColumn(1, "Col 2")
		self.InsertColumn(2, "Col 3")
		for f in range(NUM_DATA):
			self.Append(("id%4d" % (f + 1), "Ref %d" % f, "total"))

		self.Bind(wx.EVT_SCROLLWIN, self.did_scroll)
		self.v = 0
		self.locked = False


	def did_scroll(self, event):
		try:
			if event.GetOrientation() != wx.VERTICAL or self.locked:
				return

			print "%4d, list pos was %d, will be %d" % (self.v,
				self.GetScrollPos(wx.VERTICAL), event.GetPosition())
			self.bound_widget.scroll_to(event.GetPosition())
			self.v += 1
		finally:
			event.Skip()


	def scroll_to(self, position):
		self.locked = True
		dif = position - self.GetScrollPos(wx.VERTICAL)
		self.ScrollList(-1, dif * ROW_SIZE)
		#self.Scroll(-1, position)
		self.locked = False



class App(wx.App):
	"""Application class."""

	def OnInit(self):
		self.frame = wx.Frame(None, title = "My title", size = (700, 500))
		panel = wx.Panel(self.frame)
		self.SetTopWindow(self.frame)

		hbox = wx.BoxSizer(wx.HORIZONTAL)
		vbox1 = wx.BoxSizer(wx.VERTICAL)
		vbox2 = wx.BoxSizer(wx.VERTICAL)

		# The filler panel allows us to set the 'height' of the list.
		self.filler = wx.Panel(panel)
		self.list = List_control(panel, size = (300, -1),
			style=wx.LC_REPORT | wx.ALWAYS_SHOW_SB | wx.VSCROLL)
		self.list.fill()
		vbox1.Add(self.filler, 0, wx.EXPAND | wx.ALL, 0)
		vbox1.Add(self.list, 1, wx.EXPAND | wx.ALL, 5)

		self.grid = CustomTableGrid(panel)
		vbox2.Add(self.grid, 1, wx.EXPAND | wx.ALL, 5)
		hbox.Add(vbox1, 0, wx.EXPAND)
		hbox.Add(vbox2, 1, wx.EXPAND)

		# Bind the scrollbars of the widgets.
		self.grid.bound_widget = self.list
		self.list.bound_widget = self.grid

		self.resize_filler()
		panel.SetSizer(hbox)
		self.frame.Show()
		return True


	def resize_filler(self):
		"""f() -> None

		Resizes the filler panel to adapt the list control height. The position
		of the list control is moved in such a way that its entries correspond
		to the height of the grids entries.
		"""
		# Get the sizes of the list and grid top labels.
		grid_size = ROW_SIZE
		# Unfortunately the height of the list labels is not possible to get,
		# hence the use of the magic 4 number.
		list_size = self.list.GetCharHeight() + MAGIC_LIST_HEIGHT
		self.filler.SetSize((-1, grid_size - list_size))


def main():
	"""Main entry point."""
	app = App(redirect = False)
	app.MainLoop()
	print "main() finished running."


if __name__ == "__main__":
	main()

