# Orca
#
# Copyright 2004-2008 Sun Microsystems Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
# Boston MA  02110-1301 USA.

"""Provides a custom script for gcalctool."""

__id__        = "$Id$"
__version__   = "$Revision$"
__date__      = "$Date$"
__copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc."
__license__   = "LGPL"

import orca.default as default
import orca.input_event as input_event
import orca.orca_state as orca_state
import orca.speech as speech
import pyatspi

from speech_generator import SpeechGenerator

from orca.orca_i18n import _ # for gettext support

########################################################################
#                                                                      #
# The GCalcTool script class.                                          #
#                                                                      #
########################################################################

class Script(default.Script):

    def __init__(self, app):
        """Creates a new script for the given application.  Callers
        should use the getScript factory method instead of calling
        this constructor directly.

        Arguments:
        - app: the application to create a script for.
        """

        default.Script.__init__(self, app)

        self._lastProcessedKeyEvent = None
        self._lastSpokenContents = None
        self._resultsDisplay = None
        self._statusLine = None

    def getSpeechGenerator(self):
        """Returns the speech generator for this script.
        """
        return SpeechGenerator(self)

    def onWindowActivated(self, event):
        """Called whenever one of gcalctool's toplevel windows is activated.

        Arguments:
        - event: the window activated Event
        """

        # Locate the results display and the status line if we've not yet
        # done so.
        #
        if not (self._resultsDisplay and self._statusLine) \
           and event.source.getRole() == pyatspi.ROLE_FRAME:
            objs = self.utilities.descendantsWithRole(
                event.source, pyatspi.ROLE_EDITBAR)
            if len(objs) == 0:
                # Translators: this is an indication that Orca is unable to
                # obtain the display of the gcalctool calculator, which is
                # the area where calculation results are presented.
                #
                contents = _("Unable to get calculator display")
                speech.speak(contents)
                self.displayBrailleMessage(contents)
            else:
                self._resultsDisplay = objs[0]
                contents = self.utilities.substring(self._resultsDisplay, 0, -1)
                self.displayBrailleMessage(contents)
                # The status line in gcalctool 5.29 is a sibling of the
                # edit bar.
                #
                objs = self.utilities.descendantsWithRole(
                    self._resultsDisplay.parent, pyatspi.ROLE_TEXT)
                for obj in objs:
                    if not obj.getState().contains(pyatspi.STATE_EDITABLE):
                        self._statusLine = obj
                        break
                else:
                    # The status line in gcalctool 5.28 is a label in the
                    # status bar in which text is inserted as need be.
                    #
                    statusBar = self.utilities.statusBar(event.source)
                    if statusBar:
                        objs = self.utilities.descendantsWithRole(
                            statusBar, pyatspi.ROLE_LABEL)
                        if len(objs):
                            self._statusLine = objs[0]

        default.Script.onWindowActivated(self, event)

    def onTextInserted(self, event):
        """Called whenever text is inserted into gcalctool's text display.

        Arguments:
        - event: the text inserted Event
        """

        # Always update the Braille display but only speak if the last
        # key pressed was Return, space, or equals.
        #
        if self.utilities.isSameObject(event.source, self._resultsDisplay):
            contents = self.utilities.substring(self._resultsDisplay, 0, -1)
            self.displayBrailleMessage(contents)
            lastKey, mods = self.utilities.lastKeyAndModifiers()
            if lastKey in ["space", "Return", "="]:
                # gcalctool issues several identical text inserted events
                # for a single press of keys such as enter, space, or equals.
                # In fact, it's usually about 4, but we cannot depend upon
                # that.  In addition, keyboard events are decoupled from
                # text insertion events, so we may get key press/release
                # events before an insertion occurs, or we might get a
                # press, an insertion, and then a release.
                #
                # So, what we do is try to infer that an insertion event
                # was the cause of a single key event.
                #
                speakIt = True
                if self._lastProcessedKeyEvent:
                    # This catches text insertion events where the last
                    # keyboard event we looked at was the key release event.
                    #
                    if self._lastProcessedKeyEvent.timestamp \
                       == orca_state.lastNonModifierKeyEvent.timestamp:
                        speakIt = False
                    # This catches text insertion events where the last
                    # keyboard event we looked at was the key press event,
                    # but the current one is now the associated key release
                    # event.  We infer they are the same by looking at the
                    # hardware code, the time delta between the press and
                    # the release, and that we are on the verge of repeating
                    # something we just spoke.
                    #
                    # It would be tempting to look at just the contents that
                    # we previous spoke, but a use case we need to handle is
                    # when the user enters a number (e.g., "666") and then
                    # presses Return several times.  In this case, we get
                    # the text insertion events and they are all "666".
                    #
                    elif (self._lastProcessedKeyEvent.type \
                          == pyatspi.KEY_PRESSED_EVENT) \
                          and (orca_state.lastNonModifierKeyEvent.type \
                               == pyatspi.KEY_RELEASED_EVENT) \
                          and (orca_state.lastNonModifierKeyEvent.hw_code \
                               == self._lastProcessedKeyEvent.hw_code) \
                          and (orca_state.lastNonModifierKeyEvent.timestamp \
                               - self._lastProcessedKeyEvent.timestamp) < 1000 \
                          and (contents == self._lastSpokenContents):
                        speakIt = False
                if speakIt:
                    speech.speak(contents)
                    self._lastSpokenContents = contents

            if not lastKey:
                return

            self._lastProcessedKeyEvent = \
                input_event.KeyboardEvent(orca_state.lastNonModifierKeyEvent)

        elif self.utilities.isSameObject(event.source, self._statusLine):
            contents = self.utilities.substring(self._statusLine, 0, -1)
            speech.speak(contents)
            return
