#
#		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.")
    
    tabbable = overridable_property('tabbable', "True if component can be tabbed into.")
    
    _is_scrollable = False  #  Overridden by scrollable subclasses
    _tabbable = False
    
    #
    #   Class variables defined by implementations:
    #
    #  _has_local_coords   bool   True if component has a local coordinate system
    #

    visible = overridable_property('visible')	

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

    def destroy(self):
        self.container = None

    #
    #		Properties
    #
    
    #   Default implementations of position and size properties
    #   in terms of the bounds property. A minimal implementation
    #   need only implement get_bounds and set_bounds.

    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)

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

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

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

    def set_y(self, v):
        x, y = self.position
        self.position = (x, v)
    
    def get_position(self):
        l, t, r, b = self.bounds
        return (l, t)
    
    def set_position(self, (x, y)):
        l, t, r, b = self.bounds
        self.bounds = (x, y, x + r - l, y + b - t)

    def get_width(self):
        return self.size[0]
    
    def set_width(self, v):
        w, h = self.size
        self.size = v, h

    def get_height(self):
        return self.size[1]
    
    def set_height(self, v):
        w, h = self.size
        self.size = w, v
    
    def get_size(self):
        l, t, r, b = self.bounds
        return (r - l, b - t)
    
    def set_size(self, (w, h)):
        l, t, r, b = self.bounds
        self.bounds = (l, t, l + w, t + h)
    
    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 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."""
        return self.window and self.window.target is self
    
    #
    #		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 as a result of the
        resize() method being called."""
        pass

    def container_resized(self, delta):
        """Called whenever the component's container changes size as a
        result of its resize() method being called. 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

    #
    #		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):
        if self._has_local_coords:
            return add_pt(p, self.local_to_container_offset())
        else:
            return p
    
    def container_to_local(self, p):
        if self._has_local_coords:
            return sub_pt(p, self.local_to_container_offset())
        else:
            return p

    def local_to_container_offset(self):
        if self._has_local_coords:
            return self.position
        else:
            return (0, 0)

    #
    #   Placement specification support
    #
    
    def __add__(self, offset):
        return (self, offset)
    
    def __sub__(self, offset):
        return (self, -offset)
    
    #
    #   Tabbing
    #
    
    def get_tabbable(self):
        return self._tabbable
    
    def set_tabbable(self, value):
        if self._tabbable <> value:
            self._tabbable = value
            self._invalidate_tab_chain()
    
    def _tab_out(self):
        pass
    
    def _tab_in(self):
        self.become_target()
    
    def _build_tab_chain(self, chain):
        if self._tabbable:
            chain.append(self)
    
    def _invalidate_tab_chain(self):
        window = self.window
        if window:
            window._invalidate_tab_chain()

    #
    #		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)

