#!/usr/bin/env python
#############################################################################
# Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1997, 1998, 1999
# All Rights Reserved.
#
# The software contained on this media is the property of the DSTC Pty
# Ltd.  Use of this software is strictly in accordance with the
# license agreement in the accompanying LICENSE.HTML file.  If your
# distribution of this software does not contain a LICENSE.HTML file
# then you have no rights to use this software in any manner and
# should contact DSTC at the address below to determine an appropriate
# licensing arrangement.
# 
#      DSTC Pty Ltd
#      Level 7, GP South
#      Staff House Road
#      University of Queensland
#      St Lucia, 4072
#      Australia
#      Tel: +61 7 3365 4310
#      Fax: +61 7 3365 4311
#      Email: enquiries@dstc.edu.au
# 
# This software is being provided "AS IS" without warranty of any
# kind.  In no event shall DSTC Pty Ltd be liable for damage of any
# kind arising out of or in connection with the use or performance of
# this software.
#
# Project:      Fnorb
# File:         $Source: /projects/fnorb/Fnorb-1.1/orb/RCS/TkReactor.py,v $
# Version:      @(#)$RCSfile: TkReactor.py,v $ $Revision: 1.15 $
#
#############################################################################
""" A Reactor for use with 'tkinter'. 

'tkinter' (and 'Tk' itself) are NOT thread-safe, and therefore this module
assumes a single-threaded environment.

"""


# The Python module for Tk 4.1 and upwards is "_tkinter", for Tk 4.0 it is
# "tkinter" 8^(
try:
    import _tkinter

except ImportError:
    import tkinter; _tkinter = tkinter
    
# Fnorb modules.
import Reactor


def TkReactor_init():
    """ Initialise the TkReactor.

    There can only be one instance of any concrete reactor class per process.

    """
    try:
	reactor = TkReactor()

    except Reactor.Reactor, reactor:
	pass

    return reactor


class TkReactor(Reactor.Reactor):
    """ The TkReactor.

    The TkReactor uses the Tk event loop to dispatch Fnorb events.

    """

    def __init__(self):
	""" Constructor. """

	# There can be at most one instance of any concrete reactor class per
	# process.
	if Reactor.Reactor._instance is not None:
	    # A reactor already exists.
	    raise Reactor.Reactor._instance

	Reactor.Reactor._instance = self

	# A dictionary of all registered event handlers. All handles in the
	# TkReactor are actually just file descriptors.
	self.__handlers = {} # {Handle: (EventHandler, Mask)}

	return

    #########################################################################
    # Reactor interface.
    #########################################################################

    def register_handler(self, handler, mask):
	""" Register an event handler. """

	# Get the event handler's underlying I/O handle.
	handle = handler.handle()

	# If the handler is already registered then update the mask.
	try:
	    # Find the event handler associated with the handle.
	    (handler, handler_mask) = self.__handlers[handle]

	    # Update the mask.
	    handler_mask = handler_mask | mask

	# Otherwise, add the new handler.
	except KeyError:
	    handler_mask = mask

	# Do the add/update.
	self.__handlers[handle] = (handler, handler_mask)

	# Register with Tk.
	self.__register_Tk_handler(handle, handler_mask)

	return

    def unregister_handler(self, handler, mask):
	""" Withdraw a handler's registration. """

	# Get the event handler's underlying I/O handle.
	handle = handler.handle()

	try:
	    # Find the event handler associated with the handle.
	    (handler, handler_mask) = self.__handlers[handle]

	    # Update the mask.
	    handler_mask = handler_mask & ~mask

	    # If the mask is now zero (ie. the handler is no longer interested
	    # in *any* events) then delete the handler.
	    if handler_mask == 0:
		# Remove the handler from Tk.
		_tkinter.deletefilehandler(handle)

		# Delete it!
		del self.__handlers[handle]

	    # Otherwise, update the handler's mask and re-register with Tk.
	    else:
		# Update the handler's mask.
		self.__handlers[handle] = (handler, handler_mask)

		# Re-register with Tk.
		self.__register_Tk_handler(handle, handler_mask)

	# Ignore any attempts to un-register something that wasn't registerd!
	except KeyError:
	    pass

	return

    def start_event_loop(self, timeout=None):
	""" Start the event loop. """

	# Enter the Tk mainloop (ie. Tk will dispatch the events).
	_tkinter.mainloop()

	# Close all registered handlers.
	self.__close_all_handlers()

	return

    def stop_event_loop(self):
	""" Stop the event loop. """

	# Quit the Tk mainloop.
	_tkinter.quit()

	return

    def do_one_event(self):
	""" Dispatch a single event. """

	_tkinter.dooneevent()

	return

    # The following two methods are provided to allow Fnorb events to be
    # handled in other event loops.
    def handles(self):
	""" Return the read/write/exception handles for registered handlers.

	The return value is a tuple in the form:-

	([ReadHandles], [WriteHandles], [ExceptionHandles])

	"""
	return self.__get_handles()

    def handle_one_event(self, handle, tk_mask):
	""" Handle a single Tk event. """

	self.__handle_Tk_event(handle, tk_mask)

	return

    #########################################################################
    # Private interface.
    #########################################################################

    def __get_handles(self):
	""" Return the read/write/exception handles for registered handlers.

	The return value is a tuple in the form:-

	([ReadHandles], [WriteHandles], [ExceptionHandles])

	"""
	iwtd = {}
	owtd = {}
	ewtd = {}

	for handle in self.__handlers.keys():
	    # Find the event handler associated with the handle.
	    (handler, mask) = self.__handlers[handle]

	    if mask & Reactor.READ:
		iwtd[handle] = None

	    if mask & Reactor.WRITE:
		owtd[handle] = None

	    # Listen for errors on all handles.
	    ewtd[handle] = None

	return (iwtd.keys(), owtd.keys(), ewtd.keys())

    def __handle_Tk_event(self, handle, tk_mask):
	""" Handle an event. """

	# Translate between Tk and Fnorb event masks.
	mask = 0x0
	if tk_mask & _tkinter.READABLE:
	    mask = mask | Reactor.READ

	if tk_mask & _tkinter.WRITABLE:
	    mask = mask | Reactor.WRITE

	if tk_mask & _tkinter.EXCEPTION:
	    mask = mask | Reactor.EXCEPTION

	# Find the event handler associated with the handle.
	(handler, handler_mask) = self.__handlers[handle]

	# Handler callback.
	handler.handle_event(mask)
	
	return

    def __register_Tk_handler(self, handle, mask):
	""" Register a file handler with Tk. """

	# Translate between Fnorb and Tk event masks (we are always interested
	# in exceptions!).
	tk_mask = _tkinter.EXCEPTION

	if mask & Reactor.READ:
	    tk_mask = tk_mask | _tkinter.READABLE

	if mask & Reactor.WRITE:
	    tk_mask = tk_mask | _tkinter.WRITABLE

	# Register with Tk.
	_tkinter.createfilehandler(handle, tk_mask, self.__handle_Tk_event)
	
	return

    def __close_all_handlers(self):
	""" Close all registered handlers. """

	# We work on a copy of the list, 'cos the handlers will most likely
	# call 'unregister_handler' in their 'close' method.
	for (handler, mask) in self.__handlers.values():
	    # Handler callback.
	    handler.handle_close()

	return

#############################################################################
