# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.

from elisa.plugins.pigment.graph.text import Text
from elisa.plugins.pigment.graph.image import Image
from elisa.plugins.pigment.widgets.widget import Widget
from elisa.plugins.pigment.widgets.button import AbstractButton
from elisa.plugins.pigment.widgets.box import HBox
from elisa.plugins.pigment.widgets import const
from elisa.plugins.pigment.animation import implicit
from elisa.plugins.pigment.widgets.size_enforcer import RatioConstrainer

from elisa.plugins.poblesec.widgets.button import RelookSmoothPanel
from elisa.plugins.poblesec.widgets.artwork_box import ArtworkBox
from elisa.plugins.poblesec.widgets.background import WidgetWithBackground

from twisted.internet import reactor

import gobject

class InfoGlyph(gobject.GObject):
    """
    Simple abstract glyph object that contains all the information that is 
    needed to display small image on the right hand side of menu item.

    @param name:     The name uniquely identifying the image to be displayed. 
                     The actual image resource should be defined in styles.conf 
                     file.
    @type name:      string
    @param label:    Text which will be displayed on top of the image.
    @type label:     string
    """

    __gsignals__ = {
        'name-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
        'label-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
    }

    def __init__(self, name=None, label=None):
        super(InfoGlyph, self).__init__()
        self._name = name
        self._label = label

    def _get_name(self):
        return self._name

    def _set_name(self, new_name):
        self._name = new_name
        self.emit('name-changed')

    name = property(_get_name, _set_name)

    def _get_label(self):
        return self._label

    def _set_label(self, new_label):
        self._label = new_label
        self.emit('label-changed')

    label = property(_get_label, _set_label)


class InfoGlyphWidget(RatioConstrainer, WidgetWithBackground):
    """
    A widget to display InfoGlyph object. It displays image (glyph) in 
    background, and optionally some text in foreground. The pictures 
    corresponding to each InfoGlyph instance are defined in css styles.

    On creation each InfoGlyphWidget acquires name attribute (from InfoGlyph 
    class). This name can be used to address style of a specific glyph as in
    the following example:

        elisa.plugins.poblesec.widgets.menu_item.InfoGlyphWidget#by{
          aspect_ratio: 0.5;
          background-file: url(glyphs/00781.png);
        }

    @param info_glyph:    the glyph for which the widget will be constructed
    @type info_glyph:     L{elisa.plugins.poblesec.widgets.menu_item.InfoGlyph}
    """

    def __init__(self, info_glyph):
        super(InfoGlyphWidget, self).__init__()
        self.info_glyph = None
        self.set_info_glyph(info_glyph)
        self.update_style_properties(self.style.get_items())

    def set_info_glyph(self, info_glyph):
        """
        Change the glyph corresponding to this widget. 

        @param info_glyph: new glyph
        @type info_glyph: C{InfoGlyph}  
        """
        if self.info_glyph is not None:
            self.info_glyph.disconnect_by_func(self._on_name_changed)
            self.info_glyph.disconnect_by_func(self._on_label_changed)

        if info_glyph is not None:
            self.set_name(info_glyph.name)
            self._set_label(info_glyph.label)

            info_glyph.connect('name-changed', self._on_name_changed)
            info_glyph.connect('label-changed', self._on_label_changed)

        self.info_glyph = info_glyph

    def _set_label(self, new_label):
        """
        Set a new textual information accompanying glyph.

        @param new_label: text
        @type new_label: C{string} 
        """
        if new_label is not None:
            foreground = Text()
            foreground.label = new_label
        else:
            foreground = None

        self.set_foreground(foreground)

    def _on_name_changed(self, info_glyph):
        self.set_name(info_glyph.name)
        self.update_style_properties(self.style.get_items())

    def _on_label_changed(self, info_glyph):
        self._set_label(info_glyph.label)

    def clean(self):
        if self.info_glyph is not None:
            self.info_glyph.disconnect_by_func(self._on_name_changed)
            self.info_glyph.disconnect_by_func(self._on_label_changed)

        return super(InfoGlyphWidget, self).clean()

class MenuItemWidget(AbstractButton):
    """
    A Menu item widget that contains (counting from the left) artwork_box, 
    label(s), and glyphs (small icons) to display extra information.
    Glyphs can be either static - these are persistent and visible all the time, 
    or dynamic - these appear only when the item is selected.  
    The glyphs (both static and dynamic) can be accessed through properties: 
    static_glyphs and dynamic_glyphs. Both can be assigned a list of InfoGlyph 
    instances.  

    @cvar show_glyph_delay:   delay (in secondes) after which dynamic glyphs 
                              will start showing up
    @type                     C{float}
    @ivar with_artwork_box:   boolean indicating if widget will contain
                              artwork_box to display cover
    @type with_artwork_box:   C{bool}
    @ivar static_glyphs:      list of glyphs that will be visible 
                              independently of the state of item widget
    @type static_glyphs:      L{elisa.plugins.poblesec.widgets.menu_item.InfoGlyph}
    @ivar dynamic_glyphs:     list of glyphs that will be visible when item
                              widget is selected
    @type dynamic_glyphs:     L{elisa.plugins.poblesec.widgets.menu_item.InfoGlyph}
    """

    show_glyph_delay = 0.5

    def __init__(self, with_artwork_box=True):
        super(MenuItemWidget, self).__init__()
        self._with_artwork_box = with_artwork_box

        self._static_glyphs = []
        self._static_glyphs_widgets = []
        self._dynamic_glyphs = []
        self._dynamic_glyphs_widgets = []

        self._show_glyph_delayed_call = None

        self._create_widgets()
        self.update_style_properties(self.style.get_items())

    def _create_labels(self):
        labels = Widget()
        labels.visible = True

        self.label = Text()
        self.label.visible = True
        labels.add(self.label)

        return labels

    def _create_artwork_box(self):
        artwork_box = ArtworkBox()
        artwork_box.visible = True
        return artwork_box

    def _create_glyphs_box(self):
        glyphs_box = HBox()
        glyphs_box.visible = True
        return glyphs_box

    def _create_widgets(self):
        self.foreground = HBox()
        self.foreground.visible = True
        self.add(self.foreground, forward_signals=False)

        self.artwork_box = self._create_artwork_box()
        if self._with_artwork_box:
            self.foreground.pack_start(self.artwork_box, expand=False)

        self.labels = self._create_labels()
        self.foreground.pack_start(self.labels, expand=False)
        
        self.glyphs_box = self._create_glyphs_box()

        # background drawable taking the full size of the widget; used to
        # catch mouse events
        self.background = Image()
        self.add(self.background, forward_signals=False)
        self.background.visible = True
        self._connect_mouse_events(self.background)

        self.panel = RelookSmoothPanel()
        self.add(self.panel, forward_signals=False)
        self.panel.visible = True

    def _set_with_artwork_box(self, value):
        if self._with_artwork_box != value:
            self._with_artwork_box = value
            if value:
                self.foreground.pack_start(self.artwork_box, expand=False)
            else:
                self.foreground.remove(self.artwork_box)

    def _get_with_artwork_box(self):
        return self._with_artwork_box

    with_artwork_box = property(fget=_get_with_artwork_box, \
                                fset=_set_with_artwork_box)

    def _create_static_glyphs(self):
        for glyph in self._static_glyphs:
            glyph_widget = InfoGlyphWidget(glyph)
            glyph_widget.visible = True
            self.glyphs_box.pack_end(glyph_widget, expand=False)
            self._static_glyphs_widgets.append(glyph_widget)

    def _get_static_glyphs(self):
        return self._static_glyphs

    def _set_static_glyphs(self, glyphs):
        if self._static_glyphs == glyphs:
            return

        # remove old static glyphs from glyhps_box
        for widget in self._static_glyphs_widgets:
            self.glyphs_box.remove(widget)
        self._static_glyphs_widgets = []

        # create static glyphs
        self._static_glyphs = glyphs
        self._create_static_glyphs()

        self._update_glyph_box_visibility()
        self.update_style_properties(self.style.get_items())

    static_glyphs = property(fget=_get_static_glyphs, fset=_set_static_glyphs)

    def _update_glyph_box_visibility(self):
        # insert glyphbox to the widget if neccessary
        if self.glyphs_box not in self.foreground and \
                    len(self._dynamic_glyphs)+len(self._static_glyphs) > 0:
            self.foreground.pack_end(self.glyphs_box, expand=False)

        # remove glyphbox from the widget if neccessary
        if self.glyphs_box in self.foreground and \
                    len(self._dynamic_glyphs)+len(self._static_glyphs) == 0:
            self.foreground.remove(self.glyphs_box)

    def _get_dynamic_glyphs(self):
        return self._dynamic_glyphs

    def _set_dynamic_glyphs(self, glyphs):
        if self._dynamic_glyphs == glyphs:
            return

        for widget in self._dynamic_glyphs_widgets:
             self.glyphs_box.remove(widget)
        self._dynamic_glyphs_widgets = []   

        self._dynamic_glyphs = glyphs
        if self.state == const.STATE_SELECTED:
            self._schedule_dynamic_glyphs()

    dynamic_glyphs = property(fget=_get_dynamic_glyphs, 
                              fset=_set_dynamic_glyphs)

    def do_state_changed(self, previous_state):
        super(MenuItemWidget, self).do_state_changed(previous_state)
        self.panel.state = self.state

        self._schedule_dynamic_glyphs()

    def _schedule_dynamic_glyphs(self):
        if len(self._dynamic_glyphs) == 0:
            return

        if self.state == const.STATE_SELECTED:
            # create and show animation of selected_item_info_glyphs
            if len(self._dynamic_glyphs_widgets)>0:
                # If we're here, that means that the item was selected (again) 
                # while the glyph-widgets were still there. In such a case, we 
                # just cancel their destruction at the end of animation 
                # and show them again. 
                for widget in self._dynamic_glyphs_widgets:
                    widget.animated.update_animation_settings(end_callback=None)
                    self._display_glyph(widget)
            elif not self._show_glyph_delayed_call or \
                        not self._show_glyph_delayed_call.active():
                # If we're here then the glyphs do not exist and have not yet 
                # been scheduled for creation, so we do that.
                self._show_glyph_delayed_call = \
                        reactor.callLater(self.show_glyph_delay, 
                                          self._create_and_show_dynamic_glyphs)
        elif self.state == const.STATE_NORMAL:
            # destroy dynamic_glyphs
            if self._show_glyph_delayed_call and \
                        self._show_glyph_delayed_call.active():
                self._show_glyph_delayed_call.cancel()
            else:
                self._hide_and_destroy_dynamic_glyphs()

    def _create_and_show_dynamic_glyphs(self):        
        for glyph in self._dynamic_glyphs:
            glyph_widget = InfoGlyphWidget(glyph)
            glyph_widget.visible = True
            self._display_glyph(glyph_widget)
            self._dynamic_glyphs_widgets.append(glyph_widget)
            self.glyphs_box.pack_start(glyph_widget, expand=False)

        self._update_glyph_box_visibility()            

    def _display_glyph(self, widget):
        widget.opacity = 0
        settings = {'duration': 400,
                    'transformation': implicit.SMOOTH}
        widget.animated.setup_next_animations(**settings)
        widget.animated.opacity = 255

    def _hide_and_destroy_dynamic_glyphs(self):
        for widget in self._dynamic_glyphs_widgets:
            self._hide_glyph(widget)

    def _end_callback_wrapper(self, widget):
        def callback(result):
            self._destroy_dynamic_glyph(widget)
        return callback

    def _hide_glyph(self, widget):
        widget.opacity = 255
        settings = {'duration': 200,
                    'transformation': implicit.DECELERATE,
                    'end_callback': self._end_callback_wrapper(widget)}
        widget.animated.setup_next_animations(**settings)
        widget.animated.opacity = 0

    def _destroy_dynamic_glyph(self, widget):
        self._dynamic_glyphs_widgets.remove(widget)
        try:
            self.glyphs_box.remove(widget)
        except NodeNotInGroup:
            pass

        widget.clean()
        widget = None
        self._update_glyph_box_visibility()

    def clean(self):
        if self._show_glyph_delayed_call and \
                                self._show_glyph_delayed_call.active():
            self._show_glyph_delayed_call.cancel()

        for widget in self._dynamic_glyphs_widgets:
            widget.animated.update_animation_settings(end_callback=None)
            widget.animated.stop_animations()
            self._destroy_dynamic_glyph(widget)

        for widget in self._static_glyphs_widgets:
            self._static_glyphs_widgets.remove(widget)
            self.glyphs_box.remove(widget)
            widget.clean()

        return super(MenuItemWidget, self).clean()

class DoubleLineMenuItemWidget(MenuItemWidget):

    def _create_labels(self):
        labels = super(DoubleLineMenuItemWidget, self)._create_labels()

        sublabel = Text()
        sublabel.visible = True
        labels.add(sublabel)
        self.sublabel = sublabel

        return labels

class ThreeLineMenuItemWidget(DoubleLineMenuItemWidget):

    # A menu item widget with an icon and three lines of text.

    def _create_labels(self):
        labels = super(ThreeLineMenuItemWidget, self)._create_labels()

        sublabel2 = Text()
        labels.add(sublabel2)
        sublabel2.visible = True
        self.sublabel2 = sublabel2

        return labels
