# $Id: ImageMap.py,v 1.5.2.1 2006/08/16 09:30:31 marcusva Exp $
#
# Copyright (c) 2004-2006, Marcus von Appen
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#  * Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""A widget, which can display images and reacts upon mouse events."""

from pygame import Surface
from ocempgui.draw import Image
from BaseWidget import BaseWidget
from Constants import *
from StyleInformation import StyleInformation
import base

class ImageMap (BaseWidget):
    """ImageMap (image) -> ImageMap

    A widget class, which displays images and acts upon mouse events.

    The ImageMap widget can - as the ImageButton - load and display
    images, but additionally keeps track of mouse events and their
    relative position on the widget.

    The image to display can be set with the 'picture' attribute or
    set_picture() method. The image can be either a file name from which
    the image should be loaded or a pygame.Surface object to display.

    imagemap.image = './image.png'
    imagemap.set_image (image_surface)

    If the displayed image is loaded from a file, its file path will be
    saved in the 'path' attribute. This also can be used to determine,
    whether the image was loaded from a file ('path' contains a file
    path) or not ('path' is None).

    To keep track of mouse events, the ImageMap stores the last event,
    which occured on it in the 'last_event' attribute.

    if imagemap.last_event.signal == SIG_MOUSEDOWN:
        ...

    To allow easy acces of the real coordinates on the displayed image,
    the ImageMap contains a 'relative_position' attribute, which returns
    the absolute position of the mouse on the ImageMap widget.

    if imagemap.relative_position == (10, 40):
        ...
    
    Default action (invoked by activate()):
    None
    
    Mnemonic action (invoked by activate_mnemonic()):
    None
    
    Signals:
    SIG_MOUSEDOWN - Invoked, when a mouse button is pressed on the
                    ImageMap.
    SIG_MOUSEUP   - Invoked, when a mouse button is released on the
                    ImageMap.
    SIG_MOUSEMOVE - Invoked, when the mouse moves over the ImageMap.
    SIG_CLICKED   - Invoked, when the left mouse button is pressed AND
                    released over the ImageMap.

    Attributes:
    image             - A pygame.Surface of the set image.
    path              - The path of the set image (if it is loaded from a
                        file).
    last_event        - The last event, which occured on the ImageMap.
    relative_position - The last relative position of the event on the
                        ImageMap.
    """
    def __init__ (self, image):
        BaseWidget.__init__ (self)
        self._picture = None
        self._path = None

        self.__click = False
        self._lastevent = None
        
        self._signals[SIG_MOUSEDOWN] = []
        self._signals[SIG_MOUSEUP] = []
        self._signals[SIG_CLICKED] = []
        self._signals[SIG_MOUSEMOVE] = []

        self.set_picture (image)

    def set_picture (self, image):
        """I.set_picture (...) -> None

        Sets the image to be displayed on the ImageMap.

        The image can be either a valid pygame.Surface object or the
        path to an image file. If the argument is a file, the 'path'
        attribute will be set to the file path, otherwise it will be
        None.

        Raises a TypeError, if the passed argument is not a string,
        unicode or pygame.Surface.
        """
        if image:
            if type (image) in (str, unicode):
                self._path = image
                self._picture = Image.load_image (image)
            elif isinstance (image, Surface):
                self._path = None
                self._picture = image
            else:
                raise TypeError ("image must be a string, unicode or a " \
                                 "pygame.Surface")
        else:
            self._path = None
            self._picture = None
        self.dirty = True

    def _get_relative_position (self):
        """I._get_relative_position () -> int, int

        Gets the last relative position of the mouse on the ImageMap
        (absolute position on it).

        If the last position could not be determined -1, -1 will be
        returned instead.
        """
        if self.last_event:
            border = base.GlobalStyle.get_border_size \
                     (self.__class__, self.style,
                      StyleInformation.get ("IMAGEMAP_BORDER"))
            area = self.rect_to_client ()
            
            return self.last_event.data.pos[0] - border - area.x, \
                   self.last_event.data.pos[1] - border - area.y
        return -1, -1
    
    def notify (self, event):
        """I.notify (...) -> None

        Notifies the ImageMap about an event.
        """
        if not self.sensitive:
            return

        if event.signal in SIGNALS_MOUSE:
            eventarea = self.rect_to_client ()
            if event.signal == SIG_MOUSEDOWN:
                if eventarea.collidepoint (event.data.pos):
                    self._lastevent = event
                    if base.debug: print "ImageMap.MOUSEDOWN"
                    if event.data.button == 1:
                        self.state = STATE_ACTIVE
                        self.__click = True
                    self.run_signal_handlers (SIG_MOUSEDOWN, event.data)

            elif event.signal == SIG_MOUSEUP:
                if eventarea.collidepoint (event.data.pos):
                    self._lastevent = event
                    if base.debug: print "ImageMap.MOUSEUP"
                    self.run_signal_handlers (SIG_MOUSEUP, event.data)
                    if event.data.button == 1:
                        if self.state == STATE_ACTIVE:
                            self.state = STATE_ENTERED
                        else:
                            self.state = STATE_NORMAL
                        if self.__click:
                            self.__click = False
                            self.run_signal_handlers (SIG_CLICKED)
                elif (event.data.button == 1) and (self.state == STATE_ACTIVE):
                    self.__click = False
                    self.state = STATE_NORMAL

            elif event.signal == SIG_MOUSEMOVE:
                if eventarea.collidepoint (event.data.pos):
                    self._lastevent = event
                    if base.debug: print "ImageMap.MOUSEMOVE (inner)"
                    if self.state == STATE_NORMAL:
                        self.state = STATE_ENTERED
                    self.run_signal_handlers (SIG_MOUSEMOVE, event.data)
                elif self.state == STATE_ENTERED:
                    if base.debug: print "ImageMap.MOUSEMOVE (outer)"
                    self.state = STATE_NORMAL

        BaseWidget.notify (self, event)

    def draw_bg (self):
        """I.draw_bg () -> Surface
 
        Draws the background surface of the ImageMap and returns it.

        Creates the visible surface of the ImageMap and returns it to
        the caller.
        """
        return base.GlobalStyle.engine.draw_imagemap (self)

    def draw (self):
        """I.draw () -> None

        Draws the ImageMap surface and places its picture on it.
        """
        BaseWidget.draw (self)
        rect = self.picture.get_rect ()
        rect.center = self.image.get_rect ().center
        self.image.blit (self.picture, rect)

    path = property (lambda self: self._path,
                     doc = "The file path of the image.")
    picture = property (lambda self: self._picture,
                        lambda self, var: self.set_picture (var),
                        doc = "The image to display on the ImageMap.")
    last_event = property (lambda self: self._lastevent,
                           doc = "The last event occured on the ImageMap.")
    relative_position = property (lambda self: self._get_relative_position (),
                                  doc = "The last relative position of the " \
                                  "mouse.")
