#
#		Python GUI - Components - Generic
#

from Properties import Properties, overridable_property
from MessageHandlers import MessageHandler
from Geometry import add_pt, sub_pt, rect_size, rect_sized, rect_topleft
from Applications import application

class Component(Properties, MessageHandler):
    """Component is an abstract class representing a user
    interface component."""

    left = overridable_property('left', "Position of left edge relative to container.")
    top = overridable_property('top', "Position of top edge relative to container.")
    right = overridable_property('right', "Position of right edge relative to container.")
    bottom = overridable_property('bottom', "Position of bottom edge relative to container.")
    
    x = overridable_property('x', "Horizontal position relative to container.")
    y = overridable_property('y', "Vertical position relative to container.")
    width = overridable_property('width')
    height = overridable_property('height')
    
    position = overridable_property('position', "Position relative to container.")
    size = overridable_property('size')
    
    bounds = overridable_property('bounds', "Bounding rectangle in container's coordinates.")
    
    container = overridable_property('container',
        "Container which contains this Component. Setting this property has the "
        "effect of removing the component from its previous container, if any, "
        "and adding it to the new one, if any.")

    visible = overridable_property('visible')	

    _container = None
    hmove = 0
    vmove = 0
    hstretch = 0
    vstretch = 0

    def destroy(self):
        self.container = None

    #
    #		Properties
    #

    def get_left(self):
        return self.position[0]

    def set_left(self, v):
        l, t, r, b = self.bounds
        self.bounds = (v, t, r, b)

    def get_top(self):
        return self.position[1]

    def set_top(self, v):
        l, t, r, b = self.bounds
        self.bounds = (l, v, r, b)

    def get_right(self):
        return self.bounds[2]

    def set_right(self, v):
        l, t, r, b = self.bounds
        self.bounds = (l, t, v, b)

    def get_bottom(self):
        return self.bounds[3]
    
    def set_bottom(self, v):
        l, t, r, b = self.bounds
        self.bounds = (l, t, r, v)

    get_x = get_left

    def set_x(self, v):
        x, y = self.position
        self.position = (v, y)

    get_y = get_top

    def set_y(self, v):
        x, y = self.position
        self.position = (x, v)

    def get_width(self):
        return self.size[0]

    def get_height(self):
        return self.size[1]
    
    def get_container(self):
        return self._container
     
    def set_container(self, new_container):
        if self._container != new_container:
            self._change_container(new_container)
    
    def _change_container(self, new_container):
        if self._container:
            self._container._remove(self)
        self._container = new_container
        if self._container:
            self._container._add(self)
    
    #
    #   Message dispatching
    #

    def become_target(self):
        """Arrange for this object to be the first to handle messages
        dispatched to the containing Window. If the component is not
        contained in a Window, the effect is undefined."""
        raise NotImplementedError

    def current_target(self):
        """Find the current target object within the Window containing
        this component. If the component is not contained in a Window,
        the result is undefined."""
        raise NotImplementedError

    def is_target(self):
        """Return true if this is the current target within the containing
        Window. If the component is not contained in a Window, the result
        is undefined."""
        raise NotImplementedError
    
    #
    #		Message handling
    #
    
    def next_handler(self):
        return self._container

    #
    #		Visibility control
    #

    def show(self):
        """Make the Component visible (provided its container is visible)."""
        self.visible = 1

    def hide(self):
        """Make the Component invisible."""
        self.visible = 0
    
    #
    #		Resizing
    #
    
    def resize(self, **kwds):	
        """Change the geometry of the component as specified, and if this
        causes its size to change, update the positions and sizes of its
        subcomponents according to their moving and stretching options.
        Keyword arguments to this method may be any of the properties
        affecting position and size (i.e. left, top, right, bottom, x, y,
        width, height, position, size, bounds)."""
        old_size = self.size
        self.set(**kwds)
        new_size = self.size
        if old_size <> new_size:
            self.resized(sub_pt(new_size, old_size))
    
    def resized(self, delta):
        """Called whenever the component changes size."""
        pass

    def container_resized(self, delta):
        """Called whenever the component's container changes size.
        Repositions and resizes this component according to its moving
        and stretching options."""
        dw, dh = delta
        left, top, right, bottom = self.bounds
        if self.hmove:
            left += dw
            right += dw
        elif self.hstretch:
            right += dw
        if self.vmove:
            top += dh
            bottom += dh
        elif self.vstretch:
            bottom += dh
        self.resize(bounds = (left, top, right, bottom))
    
    #
    #		Event handlers
    #

    def mouse_down(self, event):
        """Called in response to a mouse click."""
        pass

#	def key_down(self, event):
#		"""Called in response to a key press."""
#		pass
#
#	def key_up(self, event):
#		"""Called in response to a key release."""
#		pass

    #
    #		Update region maintenance
    #

    def invalidate(self):
        """Mark the whole Component as needing to be redrawn."""
        self.invalidate_rect(self.viewed_rect())
    
    def invalidate_rect(self, r):
        container = self._container
        if container:
            container.invalidate_rect(r)
    
    def _invalidate_in_container(self):
        container = self._container
        if container:
            container._invalidate_subcomponent(self)

    #
    #		Coordinate transformation
    #

    def local_to_global(self, p):
        p = self.local_to_container(p)
        parent = self._container
        if parent:
            return parent.local_to_global(p)
        else:
            return p

    def global_to_local(self, p):
        parent = self._container
        if parent:
            p = parent.global_to_local(p)
        return self.container_to_local(p)
    
    def local_to_container(self, p):
        return p
    
    def container_to_local(self, p):
        return p
    
    #
    #   Placement specification support
    #
    
    def __add__(self, offset):
        return (self, offset)
    
    def __sub__(self, offset):
        return (self, -offset)

    #
    #		Other
    #
    
    window = overridable_property('window', """The Window ultimately containing
        this Component, or None.""")
    
    def get_window(self):
        container = self._container
        if container:
            return container.window
        else:
            return None

    def reset_blink(self):
        application().reset_blink()
    
    def viewed_rect(self):
        return self.bounds
    
    def broadcast(self, message, *args):
        """Traverse the component hierarchy, calling each component's handler for
        the given message, if any."""
        method = getattr(self, message, None)
        if method:
            method(*args)

    def _dispatch_mouse_event(self, event):
        self._handle_mouse_event(event)
    
    def _handle_mouse_event(self, event):
        self.handle(event.kind, event)

