#!/usr/bin/env python

# tutorials.creating_clients.timeusergui

#    Copyright (c) 2005 Simon Yuill.
#
#    This file is part of 'Social Versioning System' (SVS).
#
#    'Social Versioning System' is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    'Social Versioning System' is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with 'Social Versioning System'; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


"""
'Creating Clients' tutorial source code.

Custom interface components for time user client.

@author:	Simon Yuill
@copyright:	Simon Yuill
@license:	GNU GPL version 2 or any later version
@contact:	simon@lipparosa.org
"""

# svs imports
from svs_core.network.clientuser import GraphicalClient
from svs_core.gui.clientgui import ClientGUI
from timeuser import TimeUser, daysOfWeek

# external imports
from Tkinter import *
from twisted.internet import tksupport
import math

#############################
# CONSTANTS
#############################
deg2rad = math.pi/180.0
rad2deg = 180.0/math.pi
secondsAngle = 360.0 / 60
minutesAngle = 360.0 / 60
hoursAngle = 360.0 / 12
STATE_ACTIVE = 1
STATE_INACTIVE = 0


#############################
# CLIENT
#############################
class GraphicTimeUser(TimeUser):
	"""
	Client accessing time service.
	"""
	def __init__(self, name, passwd):
		TimeUser.__init__(self, name, passwd, guiClass=TimeUserGui)

	#######################
	# NETWORK
	#######################
	def haveJoinedClusterGroup(self):
		"""
		Automatically called after client has joined a cluster group.
		"""
		self.getGroupMembers()

	#######################
	# TIME
	#######################
	def handleTime(self, timeData):
		"""
		Displays time returned from service.
		"""
		TimeUser.handleTime(self, timeData) # call parent method
		self.gui.displayTime(timeData[3], timeData[4], timeData[5])
		self.getTimeLog()

	def handleTimeLog(self, logData):
		"""
		Displays log returned from service.
		"""
		TimeUser.handleTimeLog(self, logData) # call parent method
		self.gui.displayLog(logData)

	#######################
	# CLIENTS
	#######################
	def handleClientJoined(self, data):
		"""
		Responds to new client joining the network.
		"""
		TimeUser.handleClientJoined(self, data)
		self.gui.setStateForClient(data['client_name'], STATE_ACTIVE)
		self.gui.displayLog()

	def handleClientDeparted(self, data):
		"""
		Responds to new client leaving the network.
		"""
		TimeUser.handleClientDeparted(self, data)
		self.gui.setStateForClient(data['client_name'], STATE_INACTIVE)
		self.gui.displayLog()

	def handleGroupMemberList(self, groupList):
		"""
		Deals with received list of group members.
		"""
		TimeUser.handleGroupMemberList(self, groupList)
		for name in groupList:
			self.gui.setStateForClient(name, STATE_ACTIVE)
		self.getTimeLog()


#############################
# GUI
#############################
class TimeUserGui(ClientGUI):
	"""
	Customised graphical interface for TimeUser.
	"""
	def __init__(self, client):
		ClientGUI.__init__(self, client)

	def build(self):
		"""
		Creates interface components.
		"""
		# make root window
		self.root = Tk()
		self.root.title('TimeUser: %s' % self.client.profile.name)
		self.root.geometry("%dx%d%+d%+d" % (400, 400, 0, 0))
		self.root.bind('<Destroy>', self.destroy)
		# register with twisted
		tksupport.install(self.root)
		# add time components
		self.timeFrame = Frame(self.root, height=180)
		self.timeFrame.pack(side=TOP, expand=YES, fill=BOTH)
		self.timeCanvas = TimeCanvas(self.timeFrame)
		self.timeCanvas.place(x=0, y=0, relwidth=0.5, relheight=1.0)
		self.logCanvas = LogCanvas(self.timeFrame)
		self.logCanvas.place(relx=0.5, y=0, relwidth=0.5, relheight=1.0)
		self.timeFrame.rowconfigure(0, weight=1)
		self.timeFrame.columnconfigure(0, weight=1)
		self.timeFrame.columnconfigure(1, weight=1)
		# add standard components
		self.buildStandardComponents()


	def displayTime(self, hours, mins, secs):
		"""
		Draw graphical display of time.
		"""
		self.timeCanvas.drawTime(hours, mins, secs)

	def displayLog(self, logData=None):
		"""
		Draw graphical display of log.
		"""
		self.logCanvas.drawLogData(logData)

	def setStateForClient(self, clientName, state):
		"""
		Sets state for client.  This is used in
		displaying client names.
		"""
		self.logCanvas.clientStates[clientName] = state


class TimeCanvas:
	"""
	Custom Tk canvas for displaying time data.
	"""
	def __init__(self, root):
		self.canvas = Canvas(root, bg='#ccccaa', borderwidth=0, highlightthickness=0)
		self.canvas.bind('<Configure>', self.canvasAdjusted)
		self.hours = 0
		self.mins = 0
		self.secs = 0

	#############################
	# LAYOUT
	#############################
	def pack(self, **kwargs):
		"""
		Provides wrapper for pack method on self.canvas.
		"""
		self.canvas.pack(kwargs)

	def place(self, **kwargs):
		"""
		Provides wrapper for place method on self.canvas.
		"""
		self.canvas.place(kwargs)

	#############################
	# DRAWING
	#############################
	def canvasAdjusted(self, args=None):
		"""
		Responds to main window being adjusted in size.
		"""
		self.canvas.update_idletasks()
		self.width = self.canvas.winfo_width()
		self.height = self.canvas.winfo_height()
		self.centreX = self.width / 2
		self.centreY = self.height / 2
		# clock drawing
		if self.height > self.width:
			self.clockRadius = self.centreX * 0.6
		else:
			self.clockRadius = self.centreY * 0.6
		self.bigHandRadius = self.clockRadius * 0.9
		self.smallHandRadius = self.clockRadius * 0.6
		self.hourTickInnerRadius = self.clockRadius * 0.8
		self.hourTickOuterRadius = self.clockRadius
		# redraw
		self.drawClockFace()
		self.drawClockHands()


	def drawTime(self, hours, mins, secs):
		"""
		Draws time display.
		"""
		self.hours = hours
		self.mins = mins
		self.secs = secs
		#self.drawClockFace()
		self.drawClockHands()

	def drawClockFace(self):
		"""
		Draws clock face.
		"""
		self.canvas.delete('CLOCK_FACE')
		self.canvas.delete('CLOCK_TICK')
		# background
		self.canvas.create_oval(self.centreX - self.clockRadius, 
			self.centreY - self.clockRadius, 
			self.centreX + self.clockRadius, 
			self.centreY + self.clockRadius, 
			outline='white', fill=None, width=1, tag='CLOCK_FACE')
		# hour ticks
		# 12 o'clock
		self.canvas.create_line(self.centreX, 
			self.centreY - self.hourTickInnerRadius, 
			self.centreX, 
			self.centreY - self.hourTickOuterRadius, 
			fill='white', tag='CLOCK_TICK')
		# 3 o'clock
		self.canvas.create_line(self.centreX + self.hourTickInnerRadius, 
			self.centreY, 
			self.centreX + self.hourTickOuterRadius, 
			self.centreY, 
			fill='white', tag='CLOCK_TICK')
		# 6 o'clock
		self.canvas.create_line(self.centreX, 
			self.centreY + self.hourTickInnerRadius, 
			self.centreX, 
			self.centreY + self.hourTickOuterRadius, 
			fill='white', tag='CLOCK_TICK')
		# 9 o'clock
		self.canvas.create_line(self.centreX - self.hourTickInnerRadius, 
			self.centreY, 
			self.centreX - self.hourTickOuterRadius, 
			self.centreY, 
			fill='white', tag='CLOCK_TICK')
		self.canvas.lower('CLOCK_FACE')
		self.canvas.lower('CLOCK_TICK')

	def drawClockHands(self):
		"""
		Draws clock hands.
		"""
		self.canvas.delete('CLOCK_HAND')
		# seconds hand
		drawAngle = ((secondsAngle * self.secs) - 90) % 360
		endX = self.centreX + (self.bigHandRadius * math.cos(deg2rad * drawAngle))
		endY = self.centreY + (self.bigHandRadius * math.sin(deg2rad * drawAngle))
		self.canvas.create_line(self.centreX, 
			self.centreY, 
			endX, 
			endY, 
			fill='red', tag='CLOCK_HAND')
		# hour hand
		drawAngle = ((hoursAngle * self.hours) - 90) % 360
		endX = self.centreX + (self.smallHandRadius * math.cos(deg2rad * drawAngle))
		endY = self.centreY + (self.smallHandRadius * math.sin(deg2rad * drawAngle))
		self.canvas.create_line(self.centreX, 
			self.centreY, 
			endX, 
			endY, 
			fill='black', tag='CLOCK_HAND')
		# minute hand
		drawAngle = ((minutesAngle * self.mins) - 90) % 360
		endX = self.centreX + (self.bigHandRadius * math.cos(deg2rad * drawAngle))
		endY = self.centreY + (self.bigHandRadius * math.sin(deg2rad * drawAngle))
		self.canvas.create_line(self.centreX, 
			self.centreY, 
			endX, 
			endY, 
			fill='black', tag='CLOCK_HAND')
		self.canvas.lift('CLOCK_HAND')



class LogCanvas:
	"""
	Custom Tk canvas for display log data.
	"""
	def __init__(self, root):
		self.canvas = Canvas(root, bg='#ccccaa', borderwidth=0, highlightthickness=0)
		self.canvas.bind('<Configure>', self.canvasAdjusted)
		self.logData = None
		self.clientStates = {}

	#############################
	# LAYOUT
	#############################
	def pack(self, **kwargs):
		"""
		Provides wrapper for pack method on self.canvas.
		"""
		self.canvas.pack(kwargs)

	def place(self, **kwargs):
		"""
		Provides wrapper for place method on self.canvas.
		"""
		self.canvas.place(kwargs)

	#############################
	# DRAWING
	#############################
	def canvasAdjusted(self, args=None):
		"""
		Responds to main window being adjusted in size.
		"""
		self.canvas.update_idletasks()
		self.width = self.canvas.winfo_width()
		self.height = self.canvas.winfo_height()
		self.centreX = self.width / 2
		self.centreY = self.height / 2
		# log drawing
		self.logWidth = self.width * 0.8
		self.logHeight = self.height * 0.8
		self.logOffsetX = (self.width - self.logWidth) / 2
		self.logOffsetY = (self.height - self.logHeight) / 2
		# redraw
		self.drawLog()


	def drawLogData(self, logData):
		"""
		Receives and draws log data.
		"""
		self.logData = logData
		self.drawLog()

	def drawLog(self):
		"""
		Draws log display.
		"""
		# clear canvas elements
		self.canvas.delete('LOG_NAME')
		self.canvas.delete('LOG_BAR')
		# if there is no data, don't draw
		if not self.logData:return
		# prepare to draw log
		logSize = len(self.logData)
		barWidth = 12
		barCount = 0
		# sort client names
		clients = self.logData.keys()
		clients.sort()
		# draw bars
		for clientname in clients:
			minX = 0
			minY = self.logOffsetY + (barWidth * barCount)
			maxX = minX + (self.logData[clientname] * 10)
			maxY = minY + barWidth
			self.canvas.create_rectangle(minX, minY, maxX, maxY, outline='#ffffff', fill='#ffffff', width=1, tag='LOG_BAR')
			clientState = self.clientStates.get(clientname, STATE_INACTIVE)
			if clientState == STATE_ACTIVE:
				labelColour = 'black'
			else:
				labelColour = 'dark grey'
			self.canvas.create_text(minX, minY + 6, fill=labelColour, text=clientname, anchor='w' , tag='LOG_NAME')
			barCount += 1

		



#########################
# MAIN
#########################
if __name__ == '__main__':
	import sys
	timeuser = GraphicTimeUser(sys.argv[1], sys.argv[2])
	timeuser.timeService = 'time_service'
	timeuser.connect("time_group", "localhost", 9797)


syntax highlighted by Code2HTML, v. 0.9.1