from tkinter import *
from time import time, localtime, strftime

class ToolTip( Toplevel ):
	"""
	Provides a ToolTip widget for Tkinter.
	To apply a ToolTip to any Tkinter widget, simply pass the widget to the
	ToolTip constructor
	Created by https://github.com/jeffkt95/MorningRoutine
	""" 
	def __init__( self, wdgt, msg=None, msgFunc=None, delay=1, follow=True ):
		"""
		Initialize the ToolTip
		
		Arguments:
		  wdgt: The widget this ToolTip is assigned to
		  msg:  A static string message assigned to the ToolTip
		  msgFunc: A function that retrieves a string to use as the ToolTip text
		  delay:   The delay in seconds before the ToolTip appears(may be float)
		  follow:  If True, the ToolTip follows motion, otherwise hides
		"""
		self.wdgt = wdgt
		self.parent = self.wdgt.master										  # The parent of the ToolTip is the parent of the ToolTips widget
		Toplevel.__init__( self, self.parent, bg='black', padx=1, pady=1 )	  # Initalise the Toplevel
		self.withdraw()														 # Hide initially
		self.overrideredirect( True )										   # The ToolTip Toplevel should have no frame or title bar
		
		self.msgVar = StringVar()											   # The msgVar will contain the text displayed by the ToolTip		
		if msg == None:														 
			self.msgVar.set( 'No message provided' )
		else:
			self.msgVar.set( msg )
		self.msgFunc = msgFunc
		self.delay = delay
		self.follow = follow
		self.visible = 0
		self.lastMotion = 0
		Message( self, textvariable=self.msgVar, bg='#FFFFDD',
				 aspect=1000 ).grid()										   # The test of the ToolTip is displayed in a Message widget
		self.wdgt.bind( '<Enter>', self.spawn, '+' )							# Add bindings to the widget.  This will NOT override bindings that the widget already has
		self.wdgt.bind( '<Leave>', self.hide, '+' )
		self.wdgt.bind( '<Motion>', self.move, '+' )
		
	def spawn( self, event=None ):
		"""
		Spawn the ToolTip.  This simply makes the ToolTip eligible for display.
		Usually this is caused by entering the widget
		
		Arguments:
		  event: The event that called this funciton
		"""
		self.visible = 1
		self.after( int( self.delay * 1000 ), self.show )					   # The after function takes a time argument in miliseconds
		
	def show( self ):
		"""
		Displays the ToolTip if the time delay has been long enough
		"""
		if self.visible == 1 and time() - self.lastMotion > self.delay:
			self.visible = 2
		if self.visible == 2:
			self.deiconify()
			
	def move( self, event ):
		"""
		Processes motion within the widget.
		
		Arguments:
		  event: The event that called this function
		"""
		self.lastMotion = time()
		if self.follow == False:												# If the follow flag is not set, motion within the widget will make the ToolTip dissapear
			self.withdraw()
			self.visible = 1
		self.geometry( '+%i+%i' % ( event.x_root+10, event.y_root+10 ) )		# Offset the ToolTip 10x10 pixes southwest of the pointer
		try:
			self.msgVar.set( self.msgFunc() )								   # Try to call the message function.  Will not change the message if the message function is None or the message function fails
		except:
			pass
		self.after( int( self.delay * 1000 ), self.show )
			
	def hide( self, event=None ):
		"""
		Hides the ToolTip.  Usually this is caused by leaving the widget
		
		Arguments:
		  event: The event that called this function
		"""
		self.visible = 0
		self.withdraw()

def xrange2d( n,m ):
	"""
	Returns a generator of values in a 2d range
	
	Arguments:
	  n: The number of rows in the 2d range
	  m: The number of columns in the 2d range
	Returns:
	  A generator of values in a 2d range
	"""
	return ( (i,j) for i in xrange(n) for j in xrange(m) )

def range2d( n,m ):
	"""
	Returns a list of values in a 2d range
	
	Arguments:
	  n: The number of rows in the 2d range
	  m: The number of columns in the 2d range
	Returns:
	  A list of values in a 2d range
	"""
	return [(i,j) for i in range(n) for j in range(m) ]

def print_time():
	"""
	Prints the current time in the following format:
	HH:MM:SS.00
	"""
	t = time()
	timeString = 'time='
	timeString += strftime( '%H:%M:', localtime(t) )
	timeString += '%.2f' % ( t%60, )
	return timeString
	
def main():
	root = Tk()
	btnList = []
	for (i,j) in range2d( 6, 4 ):
		text = 'delay=%i\n' % i
		delay = i
		if j >= 2:
			follow=True
			text += '+follow\n'
		else:
			follow = False
			text += '-follow\n'
		if j % 2 == 0:
			msg = None
			msgFunc = print_time
			text += 'Message Function'
		else:
			msg = 'Button at %s' % str( (i,j) )
			msgFunc = None
			text += 'Static Message'
		btnList.append( Button( root, text=text ) )
		ToolTip( btnList[-1], msg=msg, msgFunc=msgFunc, follow=follow, delay=delay)
		btnList[-1].grid( row=i, column=j, sticky=N+S+E+W )
	root.mainloop()
	
if __name__ == '__main__':
	main()