# -*- 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.
#
# Authors: Florian Boucault <florian@fluendo.com>
#          Olivier Tilloy <olivier@fluendo.com>

from elisa.core.input_event import EventValue, EventSource, UnicodeInputEvent
from elisa.plugins.pigment.graph.text import Text
from elisa.plugins.pigment.widgets.button import AbstractButton

from twisted.internet import reactor

import gobject


class TextEntry(AbstractButton):

    """
    A text entry widget, displaying a single line input.

    It emits the C{content-changed} signal when the text in the entry has
    changed.

    If the C{password_char} attribute is set to anything else than C{None}, its
    value will be used to display obfuscated contents (password mode).

    The entry can display an optional instruction text when empty to give the
    user a hint on what she is expected to enter.

    @ivar content:            the content of the entry
    @type content:            C{unicode}
    @ivar instructions:       optional text to display when the entry is empty
    @type instructions:       C{unicode}
    @ivar password_char:      an optional character to display instead of the
                              real contents, or C{None} (the default)
    @type password_char:      C{unicode}
    @ivar obfuscate_delay:    delay in seconds before obfuscating the last
                              character entered when in password mode
    @type obfuscate_delay:    C{float}
    @ivar label:              the text widget that displays the contents
    @type label:              L{elisa.plugins.pigment.graph.text.Text}
    @ivar instructions_label: the text widget that displays the instructions
    @type instructions_label: L{elisa.plugins.pigment.graph.text.Text}
    """

    __gsignals__ = {'content-changed': (gobject.SIGNAL_RUN_LAST, 
                                        gobject.TYPE_BOOLEAN,
                                        ())
                   }

    def __init__(self, password_char=None, obfuscate_delay=0.0):
        """
        @param password_char:   an optional character to display instead of the
                                real contents, or C{None} (the default)
        @type password_char:    C{unicode}
        @param obfuscate_delay: delay in seconds before obfuscating the last
                                character entered when in password mode
                                (default: C{0.0}, e.g. immediate obfuscation)
        @type obfuscate_delay:  C{float}
        """
        super(TextEntry, self).__init__()
        self.password_char = password_char
        self.obfuscate_delay = obfuscate_delay
        self._content = u''
        self._pending_obfuscation = None
        self._create_widgets()
        self.update_style_properties(self.style.get_items())

    def _create_widgets(self):
        self.label = Text()
        self.label.visible = True
        self.add(self.label)
        # FIXME: not pretty
        self._connect_mouse_events(self.label)
        # Text widget for optional instructions
        self.instructions_label = Text()
        self.instructions_label.visible = False
        self.add(self.instructions_label)

    def _set_label(self, old_content):
        self._cancel_pending_obfuscation()
        if self.password_char is not None:
            all_but_last = (self.obfuscate_delay > 0.0) and \
                           (len(old_content) == (len(self._content) - 1))
            self._obfuscate(all_but_last)
        else:
            self.label.label = self._content

    def content__get(self):
        return self._content

    def content__set(self, value):
        value = value or u''
        if self._content != value:
            old_content = self._content
            if not old_content:
                # Some content entered, hide the optional instructions
                self.label.visible = True
                self.instructions_label.visible = False
            self._content = value
            self._set_label(old_content)
            if not value:
                # Content has been cleared, display the optional instructions
                self.label.visible = False
                self.instructions_label.visible = True
            self.emit('content-changed')

    content = property(content__get, content__set)

    def instructions__get(self):
        return self.instructions_label.label

    def instructions__set(self, value):
        self.instructions_label.label = value or u''
        if not self._content:
            self.label.visible = False
            self.instructions_label.visible = True

    instructions = property(instructions__get, instructions__set)

    def _cancel_pending_obfuscation(self):
        if self._pending_obfuscation is not None:
            if not self._pending_obfuscation.called:
                self._pending_obfuscation.cancel()
            self._pending_obfuscation = None

    def _obfuscate(self, all_but_last=False):
        n = len(self._content)
        if all_but_last and n > 0:
            # Obfuscate all the contents but the last character
            obfuscated = self.password_char * (n - 1) + self._content[-1]
            # And delay the obfuscation of the last character
            self._pending_obfuscation = \
                reactor.callLater(self.obfuscate_delay, self._obfuscate)
        else:
            # Obfuscate all the contents
            obfuscated = self.password_char * n
        self.label.label = obfuscated

    def handle_input(self, manager, event):
        if event.value == EventValue.KEY_SPACE:
            self.content += u' '
            return True
        elif event.value == EventValue.KEY_MENU:
            if event.source == EventSource.KEYBOARD:
                if len(self.content) > 0:
                    self.content = self.content[0:-1]
                    return True
        elif event.value in (EventValue.KEY_ESCAPE, EventValue.KEY_TAB):
            pass
        elif isinstance(event, UnicodeInputEvent):
            self.content += event.value
            return True
        elif event.value != EventValue.KEY_OK and \
                 str(event.value).startswith('KEY_'):
            letter = unicode(event.value)[4:]
            if letter.isalnum():
                self.content += letter
                return True

        return super(TextEntry, self).handle_input(manager, event)

    @classmethod
    def _demo_widget(cls, *args, **kwargs):
        widget = cls()
        widget.size = (100.0, 10.0)
        widget.instructions = "Type in something..."
        widget.visible = True
        return widget


"""Default password character"""
DEFAULT_PASSWORD_CHAR = u'●'

"""Default obfuscation delay"""
DEFAULT_OBFUSCATE_DELAY = 0.5


class PasswordEntry(TextEntry):

    """
    A specialized text entry widget to input passwords.
    The real contents of the entry are obfuscated with ●'s.
    """

    def __init__(self):
        super(PasswordEntry, self).__init__(DEFAULT_PASSWORD_CHAR,
                                            DEFAULT_OBFUSCATE_DELAY)


if __name__ == '__main__':
    from elisa.plugins.pigment.widgets.input import TextEntry
    TextEntry.demo()
    try:
        __IPYTHON__
    except NameError:
        import pgm
        pgm.main()
