[orca] Add new methods needed for the keyboard-and-event refactor in progress.



commit 6f3f557acb446ea233fb25bc5d88cc47cb63aa02
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Mon Dec 26 15:44:16 2011 -0500

    Add new methods needed for the keyboard-and-event refactor in progress.

 src/orca/braille.py         |   10 +++
 src/orca/input_event.py     |  153 ++++++++++++++++++++++++++++++++++++++-----
 src/orca/keybindings.py     |   66 +++++++++++++++++--
 src/orca/scripts/default.py |   25 +++++++
 4 files changed, 230 insertions(+), 24 deletions(-)
---
diff --git a/src/orca/braille.py b/src/orca/braille.py
index 203b75d..d30bcaf 100644
--- a/src/orca/braille.py
+++ b/src/orca/braille.py
@@ -1569,6 +1569,16 @@ def displayMessage(message, cursor=-1, flashTime=0):
     setFocus(region)
     refresh(True, stopFlash=False)
 
+def displayKeyEvent(event):
+    """Displays a KeyboardEvent. Typically reserved for locking keys like
+    Caps Lock and Num Lock."""
+
+    lockingStateString = event.getLockingStateString()
+    if lockingStateString:
+        keyname = event.getKeyName()
+        msg = "%s %s" % (keyname, lockingStateString)
+        displayMessage(msg, flashTime=settings.brailleFlashTime)
+
 def panLeft(panAmount=0):
     """Pans the display to the left, limiting the pan to the beginning
     of the line being displayed.
diff --git a/src/orca/input_event.py b/src/orca/input_event.py
index 9a1d034..9dbc5e1 100644
--- a/src/orca/input_event.py
+++ b/src/orca/input_event.py
@@ -18,17 +18,13 @@
 # Free Software Foundation, Inc., Franklin Street, Fifth Floor,
 # Boston MA  02110-1301 USA.
 
-"""Provides support for handling input events.  This provides several classes
-to define input events (InputEvent, KeyboardEvent, BrailleEvent,
-MouseButtonEvent, MouseMotionEvent, and SpeechEvent), and also provides a
-InputEventHandler class.  It is intended that instances of InputEventHandler
-will be used which should be used to handle all input events."""
+"""Provides support for handling input events."""
 
 __id__        = "$Id$"
 __version__   = "$Revision$"
 __date__      = "$Date$"
 __copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc." \
-                "Copyright (c) 2010-2011 Igalia, S.L."
+                "Copyright (c) 2011 Igalia, S.L."
 __license__   = "LGPL"
 
 import pyatspi
@@ -36,8 +32,10 @@ import time
 import unicodedata
 
 import debug
+import keynames
 import orca_state
 import settings
+from orca_i18n import C_
 
 KEYBOARD_EVENT     = "keyboard"
 BRAILLE_EVENT      = "braille"
@@ -51,12 +49,53 @@ class InputEvent:
         """Creates a new input event of the given type.
 
         Arguments:
-        - eventType: the input event type (one of KEYBOARD_EVENT, BRAILLE_EVENT,
-          MOUSE_BUTTON_EVENT, MOUSE_MOTION_EVENT, SPEECH_EVENT).
+        - eventType: one of KEYBOARD_EVENT, BRAILLE_EVENT, MOUSE_BUTTON_EVENT
         """
 
         self.type = eventType
 
+    def getClickCount(self):
+        """Return the count of the number of clicks a user has made."""
+
+        # TODO - JD: I relocated this out of script.py, because it seems
+        # to belong there even less than here. Need to revisit how this
+        # functionality is used and where.
+        return orca_state.clickCount
+
+    def setClickCount(self):
+        """Sets the count of the number of clicks a user has made to one
+        of the non-modifier keys on the keyboard.  Note that this looks at
+        the event_string (keysym) instead of hw_code (keycode) because
+        the Java platform gives us completely different keycodes for keys.
+
+        Arguments:
+        - inputEvent: the current input event.
+        """
+
+        # TODO - JD: This setter for the getter I found in script.py was
+        # in orca.py. :-/ Again, this needs sorting out. But for now it
+        # is less out of place here.
+
+        lastInputEvent = orca_state.lastNonModifierKeyEvent
+        if self.type == pyatspi.KEY_RELEASED_EVENT:
+            return
+
+        if not isinstance(self, KeyboardEvent):
+            orca_state.clickCount = 0
+            return
+
+        if not isinstance(lastInputEvent, KeyboardEvent):
+            orca_state.clickCount = 1
+            return
+
+        if self.time - lastInputEvent.time < settings.doubleClickTimeout:
+            # Cap the possible number of clicks at 3.
+            if orca_state.clickCount < 3:
+                orca_state.clickCount += 1
+                return
+
+        orca_state.clickCount = 1
+
 class KeyboardEvent(InputEvent):
 
     TYPE_UNKNOWN          = "unknown"
@@ -222,22 +261,30 @@ class KeyboardEvent(InputEvent):
         if self.keyType:
             return self.keyType == KeyboardEvent.TYPE_MODIFIER
 
-        modifierKeys = ['Alt_L', 'Alt_R', 'Control_L', 'Control_R',
-                        'Shift_L', 'Shift_R', 'Meta_L', 'Meta_R']
+        if self.isOrcaModifier():
+            return True
 
-        if not orca_state.bypassNextCommand:
-            orcaMods = settings.orcaModifierKeys
-            try:
-                orcaMods = map(lambda x: x.encode('UTF-8'), orcaMods)
-            except (UnicodeDecodeError, UnicodeEncodeError):
-                pass
-            modifierKeys.extend(orcaMods)
+        return self.event_string in \
+            ['Alt_L', 'Alt_R', 'Control_L', 'Control_R',
+             'Shift_L', 'Shift_R', 'Meta_L', 'Meta_R']
+
+    def isOrcaModifier(self):
+        """Return True if this is the Orca modifier key."""
+
+        if orca_state.bypassNextCommand:
+            return False
+
+        orcaMods = settings.orcaModifierKeys
+        try:
+            orcaMods = map(lambda x: x.encode('UTF-8'), orcaMods)
+        except (UnicodeDecodeError, UnicodeEncodeError):
+            pass
 
         string = self.event_string
         if isinstance(string, unicode):
             string = string.encode('UTF-8')
 
-        return string in modifierKeys
+        return string in orcaMods
 
     def isPrintableKey(self):
         """Return True if this is a printable key."""
@@ -257,6 +304,11 @@ class KeyboardEvent(InputEvent):
 
         return unicodedata.category(unicodeString)[0] in ('P', 'S')
 
+    def isPressedKey(self):
+        """Returns True if the key is pressed"""
+
+        return self.type == pyatspi.KEY_PRESSED_EVENT
+
     def isCharacterEchoable(self):
         """Returns True if the script will echo this event as part of
         character echo. We do this to not double-echo a given printable
@@ -282,6 +334,71 @@ class KeyboardEvent(InputEvent):
 
         return not self.modifiers & (1 << mod)
 
+    def getLockingStateString(self):
+        """Returns the string which reflects the locking state we wish to
+        include when presenting a locking key."""
+
+        locked = self.getLockingState()
+        if locked == None:
+            return ''
+
+        if not locked:
+            # Translators: This string is used to present the state of a
+            # locking key, such as Caps Lock. If Caps Lock is "off", then
+            # letters typed will appear in lowercase; if Caps Lock is "on",
+            # they will instead appear in uppercase. This string is also
+            # applied to Num Lock and potentially will be applied to similar
+            # keys in the future.
+            return C_("locking key state", "off")
+
+        # Translators: This string is used to present the state of a
+        # locking key, such as Caps Lock. If Caps Lock is "off", then
+        # letters typed will appear in lowercase; if Caps Lock is "on",
+        # they will instead appear in uppercase. This string is also
+        # applied to Num Lock and potentially will be applied to similar
+        # keys in the future.
+        return C_("locking key state", "on")
+
+    def getKeyName(self):
+        """Returns the string to be used for presenting the key to the user."""
+
+        return keynames.getKeyName(self.event_string)
+
+    def ignoreDueToTimestamp(self):
+        """Returns True if the event should be ignored due to its timestamp,
+        which might be completely absent or suggest that this input event is
+        a duplicate."""
+
+        if not self.timestamp:
+            return True
+        if self.timestamp != orca_state.lastInputEventTimestamp:
+            return False
+        if not orca_state.lastInputEvent:
+            return False
+        if self.hw_code == orca_state.lastInputEvent.hw_code \
+           and self.type == orca_state.lastInputEvent.type:
+            return True
+
+        return False
+
+    def present(self):
+        """Presents the event via the appropriate medium/media. Returns True
+        if we presented the event. False if there was some reason the event
+        was not worthy of presentation."""
+
+        if not self.shouldEcho:
+            return False
+
+        orca_state.lastKeyEchoTime = time.time()
+        debug.println(debug.LEVEL_FINEST,
+                      "KeyboardEvent.present: %s" % self.event_string)
+
+        script = orca_state.activeScript
+        if script:
+            return script.presentKeyboardEvent(self)
+
+        return False
+
 class BrailleEvent(InputEvent):
 
     def __init__(self, event):
diff --git a/src/orca/keybindings.py b/src/orca/keybindings.py
index a57a9dc..b2bdb1d 100644
--- a/src/orca/keybindings.py
+++ b/src/orca/keybindings.py
@@ -31,7 +31,6 @@ from gi.repository import Gdk
 import pyatspi
 import debug
 import settings
-import orca_state
 
 from orca_i18n import _           # for gettext support
 
@@ -191,6 +190,24 @@ def getModifierNames(mods):
         text += _("Shift") + "+"
     return text
 
+def getClickCountString(count):
+    """Returns a human-consumable string representing the number of
+    clicks, such as 'double click' and 'triple click'."""
+
+    if count == 2:
+        # Translators: Orca keybindings support double
+        # and triple "clicks" or key presses, similar to
+        # using a mouse.
+        #
+        return _("double click")
+    if count == 3:
+        # Translators: Orca keybindings support double
+        # and triple "clicks" or key presses, similar to
+        # using a mouse.
+        #
+        return _("triple click")
+    return ""
+
 class KeyBinding:
     """A single key binding, consisting of a keycode, a modifier mask,
     and the InputEventHandler.
@@ -237,6 +254,26 @@ class KeyBinding:
         else:
             return False
 
+    def description(self):
+        """Returns the description of this binding's functionality."""
+
+        try:
+            return self.handler.description
+        except:
+            return ''
+
+    def asString(self, convertKeysym=False):
+        """Returns a more human-consumable string representing this binding."""
+
+        mods = getModifierNames(self.modifiers)
+        clickCount = getClickCountString(self.click_count)
+        keysym = self.keysymstring
+        if convertKeysym:
+            keysym = keysym.replace('KP_', _('keypad ')).title()
+        string = '%s%s %s' % (mods, keysym, clickCount)
+
+        return string.strip()
+
 class KeyBindings:
     """Structure that maintains a set of KeyBinding instances.
     """
@@ -331,18 +368,35 @@ class KeyBindings:
 
         return hasIt
 
+    def getBoundBindings(self, uniqueOnly=False):
+        """Returns the KeyBinding instances which are bound to a keystroke.
+
+        Arguments:
+        - uniqueOnly: Should alternative bindings for the same handler be
+          filtered out (default: False)
+        """
+
+        bound = filter(lambda kb: kb.keysymstring, self.keyBindings)
+        if uniqueOnly:
+            handlers = [kb.handler.description for kb in bound]
+            bound = [bound[i] for i in map(handlers.index, set(handlers))]
+
+        return bound
+
+    def getBindingsForHandler(self, handler):
+        """Returns the KeyBinding instances associated with handler."""
+
+        return filter(lambda kb: kb.handler == handler, self.keyBindings)
+
     def getInputHandler(self, keyboardEvent):
         """Returns the input handler of the key binding that matches the
         given keycode and modifiers, or None if no match exists.
         """
 
-        if not orca_state.activeScript:
-            return None
-
         candidates = []
-        clickCount = orca_state.activeScript.getClickCount()
+        clickCount = keyboardEvent.getClickCount()
         for keyBinding in self.keyBindings:
-            if keyBinding.matches(keyboardEvent.hw_code, \
+            if keyBinding.matches(keyboardEvent.hw_code,
                                   keyboardEvent.modifiers):
                 if keyBinding.modifier_mask == keyboardEvent.modifiers and \
                    keyBinding.click_count == clickCount:
diff --git a/src/orca/scripts/default.py b/src/orca/scripts/default.py
index 91c5227..72cac51 100644
--- a/src/orca/scripts/default.py
+++ b/src/orca/scripts/default.py
@@ -5359,6 +5359,31 @@ class Script(script.Script):
     #                                                                          #
     ############################################################################
 
+    def presentationInterrupt(self):
+        """Convenience method to interrupt presentation of whatever is being
+        presented at the moment."""
+
+        speech.stop()
+        braille.killFlash()
+
+    def presentKeyboardEvent(self, event):
+        """Convenience method to present the KeyboardEvent event. Returns True
+        if we fully present the event; False otherwise."""
+
+        braille.displayKeyEvent(event)
+
+        orcaModifierPressed = event.isOrcaModifier() and event.isPressedKey()
+        if event.isCharacterEchoable() and not orcaModifierPressed:
+            return False
+
+        if orca_state.learnModeEnabled:
+            if event.isPrintableKey() and event.getClickCount() == 2:
+                self.phoneticSpellCurrentItem(event.event_string)
+                return True
+
+        speech.speakKeyEvent(event)
+        return True
+
     def presentMessage(self, fullMessage, briefMessage=None, voice=None):
         """Convenience method to speak a message and 'flash' it in braille.
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]