# $Id: StatusBar.py,v 1.16 2006/07/02 14:04: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 class, which can display information of any kind using a
stack technique.
"""

from datetime import datetime
from Container import Container
from Constants import *
from StyleInformation import StyleInformation
import base

_TIMER = 50

class StatusBar (Container):
    """StatusBar () -> StatusBar

    A widget class, which can display information using a stack.

    The StatusBar widget class can display the current date and info
    messages using a message stack.
    
    Info messages can be enabled or disabled using the 'tips' attribute
    or show_tips() method. If the tips are enabled, only the topmost
    (at last added) tip will be displayed.

    statusbar.tips = True
    statusbar.show_tips (False)

    The push_tip() method will put the message on the top of the stack,
    while the pop_tip() method will remove the topmost message. If there
    are several messages on the stack, pop_tip() will reveal previously
    added messages, which will be displayed then.

    statusbar.push_tip ('This is a message') # 'This is a message' is shown
    statusbar.push_tip ('Another message')   # 'Another message' is shown
    statusbar.pop_tip ()                     # 'This is a message' is shown

    To get the currently displayed message, the 'current_tip' attribute
    or get_current_tip() method can be used.

    current = statusbar.current_tip
    current = statusbar.get_current_tip ()

    The width of the area, that displays the tips can be adjusted using
    the 'tip_width' attribute or set_tip_width () method.
    If the rendered text exceeds the width of the area, it will be cut down
    to its width.

    statusbar.tip_width = 50
    statusbar.set_tip_width (50)

    Displaying the actual date can be enabled or disabled with the
    'date' attribute or show_date() method.

    statusbar.date = True
    statusbar.show_date (False)

    The display format of it can be adjusted through the 'date_format'
    attribute or set_date_format() method. The format has to be a string
    in a format, which can be understood by the strftime() method.

    statusbar.date_format = '%x'
    statusbar.set_date_format ('%H: %M')

    The current date (which can differ from the displayed one) can be
    fetched through the 'current_date' attribute. Usually the update resolution
    for the date is set to one second. Due to the internal drawing and update
    code it can happen that the the displayed and current date differ by one
    second.

    The width of the area, that displays the date can be adjusted using
    the 'date_width' attribute or set_date_width () method.
    If the rendered date exceeds the width of the area, it will be cut
    down to its width.

    statusbar.date_width = 50
    statusbar.set_date_width (50)

    Additionally you are able to place widgets on the StatusBar through
    the usual Container methods. The widgets will be placed between the
    info message text and the date.

    Note:
    The StatusBar updates itself every 500 ms. Thus you will not have a
    real time clock in the date display, which updates itself correctly
    every second.
    
    Default action (invoked by activate()):
    None
    
    Mnemonic action (invoked by activate_mnemonic()):
    None
    
    Attributes:
    tips         - Indicates, whether the info messages should be shown.
    current_tip  - The current tooltip to display.
    tip_width    - The width of the tip area. Default is 80.
    date         - Indicates, whether the date should be shown.
    current_date - The current date to display.
    date_format  - The display format of the date. Default is '%x %H:%M'
    date_width   - The width of the date area. Default is 90.
    """
    def __init__ (self):
        Container.__init__ (self)

        # Info values.
        self._showtips = True
        self._tips = []
        self._tipwidth = 80

        self._timer = _TIMER
        
        # Date.
        self._showdate = True
        self._dateformat = "%x %H:%M:%S"
        self._datewidth = 90
        self._signals[SIG_TICK] = None

        # Default size.
        self.minsize = 204, 24

    def set_focus (self, focus=True):
        """S.set_focus (focus=True) -> None

        Overrides the set_focus() behaviour for the StatusBar.

        The StatusBar class is not focusable by default.
        """
        return False

    def set_tip_width (self, width):
        """S.set_tip_width (...) -> None

        Sets the width of the tip area.

        Raises a TypeError, if the passed argument is not a positive
        integer.
        """
        if (type (width) != int) or (width < 0):
            raise TypeError ("width must be a positive integer")
        self._tipwidth = width
        self.dirty = True

    def set_date_width (self, width):
        """S.set_date_width (...) -> None

        Sets the width of the date area.

        Raises a TypeError, if the passed argument is not a positive
        integer.
        """
        if (type (width) != int) or (width < 0):
            raise TypeError ("width must be a positive integer")
        self._datewidth = width
        self.dirty = True

    def show_tips (self, show):
        """S.show_tips (...) -> None

        Shows or hides the info message display of the StatusBar.
        """
        if show != self._showtips:
            self._showtips = show
            self.dirty = True

    def show_date (self, show):
        """S.show_date (...) -> None

        Shows or hides the date display of the StatusBar.
        """
        if show != self._showdate:
            self._showdate = show
            self.dirty = True
    
    def set_date_format (self, format):
        """S.set_date_format (...) -> None

        Sets the display format for the date.

        The passed format string has to be in a format, which can be
        understood by the strftime() method.
        """
        self._dateformat = format
        self.dirty = True
    
    def get_current_tip (self):
        """S.get_current_tip () -> string or unicode

        Gets the current info message to display.
        """
        length = len (self._tips)
        if length > 0:
            return self._tips[length - 1]
        return ""
    
    def push_tip (self, tip):
        """S.push_tip (...) -> None

        Puts a info message on the queue of the StatusBar.

        Raises a TypeError, if the passed argument is not a string or
        unicode.
        """
        if type (tip) not in (str, unicode):
            raise TypeError ("tip must be a string or unicode")
        self._tips.append (tip)
        self.dirty = True
    
    def pop_tip (self):
        """S.pop_tip () -> None

        Removes the last info message from the StatusBar queue.
        """
        if len (self._tips) > 0:
            self._tips.pop ()
            self.dirty = True

    def calculate_size (self):
        """S.calculate_size () -> int, int.

        Calculates the size needed by the children.

        Calculates the size needed by the children and returns the
        resulting width and height.
        """
        border = base.GlobalStyle.get_border_size \
                 (self.__class__, self.style,
                  StyleInformation.get ("STATUSBAR_BORDER"))
        width = 2 * (border + self.padding)
        height = 0
        
        for widget in self.children:
            width += widget.width + self.spacing
            if widget.height > height:
                height = widget.height
        height += 2 * (border + self.padding)

        # Add width for the tips and date.
        if self.tips:
            width += self.tip_width
            if self.date:
                width += self.spacing
        if self.date:
            width += self.date_width
        if not self.tips and not self.date:
            width -= self.spacing

        return width, height
    
    def dispose_widgets (self, startx, height):
        """S.dispose_widgets (...) -> None
        
        Moves the children of the StatusBar to their correct positions.
        """
        border = base.GlobalStyle.get_border_size \
                 (self.__class__, self.style,
                  StyleInformation.get ("STATUSBAR_BORDER"))
        
        x = startx
        addy = border + self.padding + \
               (height - 2 * (border + self.padding)) / 2
        
        for widget in self.children:
            y = border + self.padding + \
                (height - 2 * (border + self.padding)) / 2 - widget.height / 2
            y = addy - widget.height / 2
            widget.topleft = (x, y)
            x += widget.width + self.spacing
    
    def notify (self, event):
        """S.notify (...) -> None

        Notifies the StatusBar about an event.
        """
        if event.signal == SIG_TICK:
            if self._timer == 0:
                self._timer = _TIMER
                if self.date:
                    self.dirty = True
            self._timer -= 1
    
    def draw_bg (self):
        """S.draw_bg () -> Surface

        Draws the background surface of the StatusBar and returns it.

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

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

        Draws the StatusBar surface and places its children on it.
        """
        Container.draw (self)

        cls = self.__class__
        style = base.GlobalStyle
        border = style.get_border_size \
                 (cls, self.style, StyleInformation.get ("STATUSBAR_BORDER"))
        blit = self.image.blit
        rect = self.image.get_rect()
        
        x = self.padding + border
        # Draw the current tip.
        if self.tips:
            surface_tip = style.engine.draw_string (self.current_tip,
                                                    self.state, cls,
                                                    self.style)
            r = surface_tip.get_rect ()
            r.x = x
            r.centery = rect.centery
            blit (surface_tip, r, (0, 0, self.tip_width, r.height))
            x += self.tip_width + self.spacing

        # Place the children of the StatusBar and blit them.
        self.dispose_widgets (x, self.height)
        for widget in self.children:
            blit (widget.image, widget.rect)

        # Draw the date.
        if self.date:
            surface_date = style.engine.draw_string (self.current_date,
                                                     self.state, cls,
                                                     self.style)
            r = surface_date.get_rect ()
            r.right = rect.right - border - self.padding
            r.centery = rect.centery
            blit (surface_date, r, (0, 0, self._datewidth, r.height))

    tips = property (lambda self: self._showtips,
                     lambda self, var: self.show_tips (var),
                     doc = "Indicates, whether the tooltips should be shown.")
    tip_width = property (lambda self: self._tipwidth,
                          lambda self, var: self.set_tip_width (var),
                          doc = "The width of the tip area.")
    date = property (lambda self: self._showdate,
                     lambda self, var: self.show_date (var),
                     doc = "Indicates, whether the date should be shown.")
    date_width = property (lambda self: self._datewidth,
                           lambda self, var: self.set_date_width (var),
                           doc = "The width of the date area.")
    current_tip = property (lambda self: self.get_current_tip (),
                            doc = "The current tooltip to display.")
    current_date = property (lambda self: datetime.now ().strftime \
                             (self._dateformat),
                             doc = "The current date to display.")
    date_format = property (lambda self: self._dateformat,
                            lambda self, var: self.set_date_format (var),
                            doc = "Sets the display format of the date.")
