orca r4200 - in branches/phase2: . src/orca



Author: wwalker
Date: Fri Sep 12 22:42:00 2008
New Revision: 4200
URL: http://svn.gnome.org/viewvc/orca?rev=4200&view=rev

Log:
Work on input bindings and get the script plumbing kind of working.


Added:
   branches/phase2/src/orca/input_bindings.py
Removed:
   branches/phase2/src/orca/key_bindings.py
Modified:
   branches/phase2/   (props changed)
   branches/phase2/src/orca/Makefile.am
   branches/phase2/src/orca/input_event.py
   branches/phase2/src/orca/script.py
   branches/phase2/src/orca/script_manager.py

Modified: branches/phase2/src/orca/Makefile.am
==============================================================================
--- branches/phase2/src/orca/Makefile.am	(original)
+++ branches/phase2/src/orca/Makefile.am	Fri Sep 12 22:42:00 2008
@@ -10,8 +10,8 @@
 	braille_monitor.py \
 	braille.py \
 	default.py \
+	input_bindings.py \
 	input_event.py \
-	key_bindings.py \
 	orca_i18n.py \
 	orca.py \
 	platform.py \

Added: branches/phase2/src/orca/input_bindings.py
==============================================================================
--- (empty file)
+++ branches/phase2/src/orca/input_bindings.py	Fri Sep 12 22:42:00 2008
@@ -0,0 +1,576 @@
+# Orca
+#
+# Copyright 2005-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 support for defining braille and key bindings and matching
+them to input events."""
+
+__id__        = "$Id: keybindings.py 3971 2008-06-11 00:54:46Z joanied $"
+__version__   = "$Revision: 3971 $"
+__date__      = "$Date: 2008-06-10 20:54:46 -0400 (Tue, 10 Jun 2008) $"
+__copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc."
+__license__   = "LGPL"
+
+import logging
+log = logging.getLogger('orca.input_bindings')
+
+try:
+    # This can fail due to gtk not being available.  We want to
+    # be able to recover from that if possible.  The main driver
+    # for this is to allow "orca --text-setup" to work even if
+    # the desktop is not running.
+    #
+    import gtk
+except ImportError:
+    pass
+
+import pyatspi
+
+########################################################################
+#                                                                      #
+# METHODS FOR DEALING WITH CONVERTING X KEYSYMS TO KEYCODES            #
+#                                                                      #
+########################################################################
+
+_keysymsCache = {}
+_keycodeCache = {}
+
+def getAllKeysyms(keysym):
+    """Given a keysym, find all other keysyms associated with the key
+    that is mapped to the given keysym.  This allows us, for example,
+    to determine that the key bound to KP_Insert is also bound to KP_0.
+    """
+    if keysym not in _keysymsCache:
+        # The keysym itself is always part of the list.
+        #
+        _keysymsCache[keysym] = [keysym]
+
+        # Find the numerical value of the keysym
+        #
+        keyval = gtk.gdk.keyval_from_name(keysym)
+
+        if keyval != 0:
+            # Find the keycodes for the keysym.  Since a keysym
+            # can be associated with more than one key, we'll shoot
+            # for the keysym that's in group 0, regardless of shift
+            # level (each entry is of the form [keycode, group,
+            # level]).
+            #
+            keymap = gtk.gdk.keymap_get_default()
+            entries = keymap.get_entries_for_keyval(keyval)
+            keycode = 0
+            if entries:
+                for entry in entries:
+                    if entry[1] == 0:  # group = 0
+                        keycode = entry[0]
+                        break
+
+            # Find the keysyms bound to the keycode.  These are what
+            # we are looking for.
+            #
+            if keycode != 0:
+                entries = keymap.get_entries_for_keycode(keycode)
+                if entries:
+                    for entry in entries:
+                        keyval = entry[0]
+                        name = gtk.gdk.keyval_name(keyval)
+                        if name and (name != keysym):
+                            _keysymsCache[keysym].append(name)
+
+    return _keysymsCache[keysym]
+
+def getKeycode(keysym):
+    """Converts an XKeysym string (e.g., 'KP_Enter') to a keycode that
+    should match the event.hw_code for key events.
+
+    This whole situation is caused by the fact that Solaris chooses
+    to give us different keycodes for the same key, and the keypad
+    is the primary place where this happens: if NumLock is not on,
+    there is no telling the difference between keypad keys and the
+    other navigation keys (e.g., arrows, page up/down, etc.).  One,
+    for example, would expect to get KP_End for the '1' key on the
+    keypad if NumLock were not on.  Instead, we get 'End' and the
+    keycode for it matches the keycode for the other 'End' key.  Odd.
+    If NumLock is on, we at least get KP_* keys.
+
+    So...when setting up keybindings, we say we're interested in
+    KeySyms, but those keysyms are carefully chosen so as to result
+    in a keycode that matches the actual key on the keyboard.  This
+    is why we use KP_1 instead of KP_End and so on in our keybindings.
+
+    Arguments:
+    - keysym: a string that is a valid representation of an XKeysym.
+
+    Returns an integer representing a key code that should match the
+    event.hw_code for key events.
+    """
+    if not keysym:
+        return 0
+
+    if keysym not in _keycodeCache:
+        keymap = gtk.gdk.keymap_get_default()
+
+        # Find the numerical value of the keysym
+        #
+        keyval = gtk.gdk.keyval_from_name(keysym)
+        if keyval == 0:
+            return 0
+
+        # Now find the keycodes for the keysym.   Since a keysym can
+        # be associated with more than one key, we'll shoot for the
+        # keysym that's in group 0, regardless of shift level (each
+        # entry is of the form [keycode, group, level]).
+        #
+        _keycodeCache[keysym] = 0
+        entries = keymap.get_entries_for_keyval(keyval)
+        if entries:
+            for entry in entries:
+                if entry[1] == 0:  # group = 0
+                    _keycodeCache[keysym] = entry[0]
+                    break
+                if _keycodeCache[keysym] == 0:
+                    _keycodeCache[keysym] = entries[0][0]
+
+    return _keycodeCache[keysym]
+
+########################################################################
+#                                                                      #
+# INPUT BINDING CLASSES                                                #
+#                                                                      #
+########################################################################
+
+class Handler:
+    """Binds a function to a description."""
+    def __init__(self, function, description, learnModeEnabled=True):
+        """Creates a new Handler instance.  All bindings
+        (e.g., key bindings and braille bindings) will be handled
+        by an instance of an Handler.
+
+        Arguments:
+        - function: the function to call with an InputEvent instance as its
+                    sole argument.  The function is expected to return True
+                    if it consumes the event; otherwise it should return
+                    False
+        - description: a localized string describing what this InputEvent
+                       does
+        - learnModeEnabled: if True, the description will be spoken and
+                            brailled if learn mode is enabled.  If False,
+                            the function will be called no matter what.
+        """
+        self.function = function
+        self.description = description
+        self.learnModeEnabled = learnModeEnabled
+
+    def __str__(self):
+        return "%s (learnModeEnabled=%s)" \
+               % (self.description, self.learnModeEnabled)
+
+    def __eq__(self, other):
+        """Compares one input handler to another."""
+        return (self.function == other.function)
+
+    def processInputEvent(self, script, inputEvent, modifiers):
+        """Processes an input event.  If settings.learnModeEnabled is True,
+        this will merely report the description of the input event to braille
+        and speech.  If settings.learnModeEnabled is False, this will call the
+        function bound to this Handler instance, passing the
+        inputEvent as the sole argument to the function.
+
+        This function is expected to return True if it consumes the
+        event; otherwise it is expected to return False.
+
+        Arguments:
+        - script:     the script (if any) associated with this event
+        - inputEvent: the input event to pass to the function bound
+                      to this Handler instance.
+        - modifiers:  any modifiers associated with the event
+        """
+        log.debug("process %s for %s" % (inputEvent, script))
+        try:
+            consumed = self.function(script, inputEvent, modifiers)
+        except:
+            log.exception("while attempting to process input event")
+            consumed = False
+        return consumed
+
+class Binding:
+    """A single input event binding, consisting of a command, a keyboard
+    modifier mask, and the Handler.
+    """
+
+    # We really do want this many arguments.
+    #
+    # pylint: disable-msg=R0913
+
+    def __init__(self, 
+                 command, 
+                 modifierMask, 
+                 modifiers, 
+                 handler,
+                 clickCount = 1):
+        """Creates a new input binding.
+
+        Arguments:
+        - command: a command that uniquely identifies this.  For
+                   braille is can be something like KEY_CMD_ROUTE,
+                   KEY_CMD_FWINLT, etc.  For keyboard, we want a
+                   keysymString - this is typically a string from
+                   /usr/include/X11/keysymdef.h with the preceding
+                   'XK_' removed (e.g., XK_KP_Enter becomes the string
+                   'KP_Enter').
+        - modifierMask: bit mask where a set bit tells us what modifiers
+          we care about (see pyatspi.MODIFIER_*)
+        - modifiers: the state the modifiers we care about must be in for
+          this key binding to match an input event (see also
+          pyatspi.MODIFIER_*)
+        - handler: the Handler for this key binding
+        - clickCount: how many times the command should be pressed in a row
+        """
+        self.command = command
+        self.modifierMask = modifierMask
+        self.modifiers = modifiers
+        self.handler = handler
+        self.clickCount = clickCount
+        self._commandCode = None
+
+    def __str__(self):
+        return "[%x %x %s %d %s]" % \
+            (self.modifierMask,
+             self.modifiers,
+             self.command,
+             self.clickCount,
+             self.handler.description)
+
+    def _getCommandCode(self):
+        """Converts the command into a command code.  For example,
+        a keysym into a keycode.  By default, the command code is 
+        the same as the command.
+        """
+        if not self._commandCode:
+            self._commandCode = self.command
+        return self._commandCode
+
+    def matches(self, commandCode, modifiers):
+        """Returns true if this binding matches the given commandCode and
+        modifier state.
+        """
+        if commandCode == self._getCommandCode():
+            result = modifiers & self.modifierMask
+            return result == self.modifiers
+        else:
+            return False
+
+class Bindings(list):
+    """Structure that maintains a set of Binding instances.
+    """
+    def __init__(self):
+        list.__init__(self)
+
+    def __str__(self):
+        result = "[\n"
+        for binding in self:
+            result += "  %s\n" % binding
+        result += "]"
+        return result
+    
+    def removeByHandler(self, handler):
+        """Removes the binding associated with the handler.
+        """
+        i = len(self)
+        while i > 0:
+            if self[i - 1].handler == handler:
+                del self[i - 1]
+            i = i - 1
+
+    def hasBinding(self, binding, typeOfSearch="strict"):
+        """Return True if keyBinding is already in self.keyBindings.
+
+           The typeOfSearch can be:
+              "strict":      matches description, modifiers, key, and
+                             click count
+              "description": matches only description.
+              "command":     matches the modifiers, command, and modifier mask,
+                             and click count
+              "commandNoMask":  matches the modifiers, command, and click count
+        """
+        hasIt = False
+
+        for myBinding in self:
+            if typeOfSearch == "strict":
+                if (binding.handler.description \
+                    == myBinding.handler.description) \
+                    and (binding.command \
+                         == myBinding.command) \
+                    and (binding.modifierMask \
+                         == myBinding.modifierMask) \
+                    and (binding.modifiers \
+                         == myBinding.modifiers) \
+                    and (binding.clickCount \
+                         == myBinding.clickCount):
+                    hasIt = True
+            elif typeOfSearch == "description":
+                if binding.handler.description \
+                    == myBinding.handler.description:
+                    hasIt = True
+            elif typeOfSearch == "command":
+                if (binding.command \
+                    == myBinding.command) \
+                    and (binding.modifierMask \
+                         == myBinding.modifierMask) \
+                    and (binding.modifiers \
+                         == myBinding.modifiers) \
+                    and (binding.clickCount \
+                         == myBinding.clickCount):
+                    hasIt = True
+            elif typeOfSearch == "commandNoMask":
+                if (binding.command \
+                        == myBinding.command) \
+                    and (binding.modifiers \
+                         == myBinding.modifiers) \
+                    and (binding.clickCount \
+                         == myBinding.clickCount):
+                    hasIt = True
+
+        return hasIt
+
+    def getHandler(self, commandCode, modifiers, clickCount):
+        """Returns the input handler of the key binding that matches the
+        given inputEvent and clickCount, or None if no match exists.
+        """
+        candidates = []
+        for binding in self:
+            if binding.matches(commandCode, modifiers):
+                if binding.modifierMask == modifiers \
+                   and binding.clickCount == clickCount:
+                    return binding.handler
+                candidates.append(binding)
+
+        # If we're still here, we don't have an exact match. Prefer
+        # the one whose click count is closest to, but does not exceed,
+        # the actual click count.
+        #
+        candidates.sort(cmp=lambda x, y: x.clickCount - y.clickCount,
+                        reverse=True)
+        for candidate in candidates:
+            if candidate.clickCount <= clickCount:
+                return candidate.handler
+
+        return None
+
+    def consumeInputEvent(self, 
+                          script, 
+                          inputEvent, 
+                          commandCode, 
+                          modifiers, 
+                          process=True):
+        """Attempts to consume the given input event.
+        """
+        consumed = False
+        handler = self.getHandler(commandCode,
+                                  modifiers,
+                                  script.getClickCount())
+        if handler:
+            consumed = True
+            if process:
+                try:
+                    handler.processInputEvent(script,
+                                              inputEvent,
+                                              modifiers)
+                except:
+                    log.exception("while processing input event")
+
+        return consumed
+
+########################################################################
+#                                                                      #
+# KEY BINDING CLASSES                                                  #
+#                                                                      #
+########################################################################
+
+class KeyBinding(Binding):
+    """A single binding, consisting of a keycode, a modifier mask,
+    and the Handler.
+    """
+
+    # We really do want this many arguments.
+    #
+    # pylint: disable-msg=R0913
+
+    def __init__(self, 
+                 command, 
+                 modifierMask, 
+                 modifiers, 
+                 handler,
+                 clickCount = 1):
+        """Creates a new key binding.
+
+        Arguments:
+        - command: the keysymString - this is typically a string
+          from /usr/include/X11/keysymdef.h with the preceding 'XK_'
+          removed (e.g., XK_KP_Enter becomes the string 'KP_Enter').
+        - modifierMask: bit mask where a set bit tells us what modifiers
+          we care about (see pyatspi.MODIFIER_*)
+        - modifiers: the state the modifiers we care about must be in for
+          this key binding to match an input event (see also
+          pyatspi.MODIFIER_*)
+        - handler: the Handler for this key binding
+        - clickCount: how many times the key should be pressed in a row
+        """
+        Binding.__init__(self, 
+                         command, 
+                         modifierMask,
+                         modifiers,
+                         handler,
+                         clickCount)
+
+    def _getCommandCode(self):
+        """Converts the command into a command code"""
+        if not self._commandCode:
+            self._commandCode = getKeycode(self.command)
+        return self._commandCode
+
+class KeyBindings(Bindings):
+    """Structure that maintains a set of KeyBinding instances.
+    """
+    def __init__(self):
+        Bindings.__init__(self)
+
+    def consumeInputEvent(self, script, keyboardEvent, modifiers):
+        """Attempts to consume the given keyboard event.  If these
+        keybindings have a handler for the given keyboardEvent, it is
+        assumed the event will always be consumed (e.g., we want to
+        consume key presses as well, but not really process them).
+        """
+        return Bindings.consumeInputEvent(
+            self, 
+            script,
+            keyboardEvent,
+            keyboardEvent.hw_code,
+            modifiers,
+            keyboardEvent.type == pyatspi.KEY_PRESSED_EVENT)
+
+########################################################################
+#                                                                      #
+# BRAILLE BINDING CLASSES                                              #
+#                                                                      #
+########################################################################
+
+class BrailleBinding(Binding):
+    # We really do want this many arguments.
+    #
+    # pylint: disable-msg=R0913
+
+    def __init__(self, 
+                 command, 
+                 modifierMask, 
+                 modifiers, 
+                 handler,
+                 clickCount = 1):
+        """Creates a new braille binding.
+
+        Arguments:
+        - command: A BrlAPI KEY_CMD_* command.
+        - modifierMask: bit mask where a set bit tells us what modifiers
+          we care about (see pyatspi.MODIFIER_*)
+        - modifiers: the state the modifiers we care about must be in for
+          this key binding to match an input event (see also
+          pyatspi.MODIFIER_*)
+        - handler: the Handler for this key binding
+        - clickCount: how many times the key should be pressed in a row
+        """
+        Binding.__init__(self, 
+                         command, 
+                         modifierMask,
+                         modifiers,
+                         handler,
+                         clickCount)
+
+class BrailleBindings(Bindings):
+    """Structure that maintains a set of BrailleBinding instances.
+    """
+    def __init__(self):
+        Bindings.__init__(self)
+
+    def consumeInputEvent(self, script, brailleEvent, modifiers):
+        """Attempts to consume the given braille event.
+        """
+        return Bindings.consumeInputEvent(
+            self,
+            script,
+            brailleEvent,
+            brailleEvent["command"],
+            modifiers,
+            True)
+
+if __name__ == "__main__":
+    logging.basicConfig(format="%(name)s %(message)s")
+    log.setLevel(logging.DEBUG)
+
+    print getAllKeysyms("Insert")
+    print getKeycode("Insert")
+
+    print "Keyboard tests:\n"
+    bindings = KeyBindings()
+    handler1 = Handler(None, "Does something")
+    print handler1
+    binding1 = KeyBinding("Insert", 0x1ff, 0, handler1, 2)
+    bindings.append(binding1)
+    handler2 = Handler(None, "Does something else")
+    binding2 = KeyBinding("Return", 0x1ff, 0, handler2, 2)
+    bindings.append(binding2)
+    print "All bindings:"
+    print bindings
+
+    print "Bindings has %s: %s" \
+          % (binding2, bindings.hasBinding(binding2))
+
+    bindings.remove(binding2)
+    print "All bindings after remove:"
+    print bindings
+    print "Bindings has %s: %s" \
+          % (binding2, bindings.hasBinding(binding2))
+
+    bindings.removeByHandler(handler1)
+    print "All bindings after removeByHandler:"
+    print bindings
+
+    print "\n\nBraille tests:\n"
+    import brlapi
+    bindings = BrailleBindings()
+    handler1 = Handler(None, "Does something")
+    print handler1
+    binding1 = BrailleBinding(brlapi.KEY_CMD_ROUTE, 0x1ff, 0, handler1, 2)
+    bindings.append(binding1)
+    handler2 = Handler(None, "Does something else")
+    binding2 = BrailleBinding(brlapi.KEY_CMD_FWINRT, 0x1ff, 0, handler2, 2)
+    bindings.append(binding2)
+    print "All bindings:"
+    print bindings
+
+    print "Bindings has %s: %s" \
+          % (binding2, bindings.hasBinding(binding2))
+
+    bindings.remove(binding2)
+    print "All bindings after remove:"
+    print bindings
+    print "Bindings has %s: %s" \
+          % (binding2, bindings.hasBinding(binding2))
+
+    bindings.removeByHandler(handler1)
+    print "All bindings after removeByHandler:"
+    print bindings

Modified: branches/phase2/src/orca/input_event.py
==============================================================================
--- branches/phase2/src/orca/input_event.py	(original)
+++ branches/phase2/src/orca/input_event.py	Fri Sep 12 22:42:00 2008
@@ -32,10 +32,108 @@
 import logging
 log = logging.getLogger('orca.input_event')
 
-import key_bindings
 import pyatspi
 import time
 
+from orca_i18n import _ # for gettext support
+
+# A new modifier to use (set by the press of any key in the
+# orcaModifierKeys list) to represent the Orca modifier.
+#
+MODIFIER_ORCA = 8
+
+# The different modifiers/modifier masks associated with key bindings
+#
+NO_MODIFIER_MASK              =  0
+ALT_MODIFIER_MASK             =  1 << pyatspi.MODIFIER_ALT
+CTRL_MODIFIER_MASK            =  1 << pyatspi.MODIFIER_CONTROL
+ORCA_MODIFIER_MASK            =  1 << MODIFIER_ORCA
+ORCA_ALT_MODIFIER_MASK        = (1 << MODIFIER_ORCA |
+                                 1 << pyatspi.MODIFIER_ALT)
+ORCA_CTRL_MODIFIER_MASK       = (1 << MODIFIER_ORCA |
+                                 1 << pyatspi.MODIFIER_CONTROL)
+ORCA_CTRL_ALT_MODIFIER_MASK   = (1 << MODIFIER_ORCA |
+                                 1 << pyatspi.MODIFIER_CONTROL |
+                                 1 << pyatspi.MODIFIER_ALT)
+ORCA_SHIFT_MODIFIER_MASK      = (1 << MODIFIER_ORCA |
+                                 1 << pyatspi.MODIFIER_SHIFT)
+SHIFT_MODIFIER_MASK           =  1 << pyatspi.MODIFIER_SHIFT
+SHIFT_ALT_MODIFIER_MASK       = (1 << pyatspi.MODIFIER_SHIFT |
+                                 1 << pyatspi.MODIFIER_ALT)
+COMMAND_MODIFIER_MASK         = (1 << pyatspi.MODIFIER_ALT |
+                                 1 << pyatspi.MODIFIER_CONTROL |
+                                 1 << pyatspi.MODIFIER_META2 |
+                                 1 << pyatspi.MODIFIER_META3)
+NON_LOCKING_MODIFIER_MASK     = (1 << pyatspi.MODIFIER_SHIFT |
+                                 1 << pyatspi.MODIFIER_ALT |
+                                 1 << pyatspi.MODIFIER_CONTROL |
+                                 1 << pyatspi.MODIFIER_META2 |
+                                 1 << pyatspi.MODIFIER_META3 |
+                                 1 << MODIFIER_ORCA)
+ALL_BUT_NUMLOCK_MODIFIER_MASK = (1 << MODIFIER_ORCA |
+                                 1 << pyatspi.MODIFIER_SHIFT |
+                                 1 << pyatspi.MODIFIER_SHIFTLOCK |
+                                 1 << pyatspi.MODIFIER_CONTROL |
+                                 1 << pyatspi.MODIFIER_ALT |
+                                 1 << pyatspi.MODIFIER_META2 |
+                                 1 << pyatspi.MODIFIER_META3)
+
+# The 2nd parameter used when creating a key binding refers to the
+# modifiers "we care about."  We typically care about all of the
+# modifiers which are not locking keys because we want to avoid
+# conflicts with other commands, such as Orca's command for moving
+# among headings (H) and the Help menu (Alt+H).
+#
+DEFAULT_MODIFIER_MASK = NON_LOCKING_MODIFIER_MASK
+
+def getModifierNames(mods):
+    """Gets the modifier names of a numeric modifier mask as a human
+    consumable string that can be prefixed ahead of a character name.
+    """
+    text = ""
+    if mods & ORCA_MODIFIER_MASK:
+        text += _("Orca") + "+"
+    #if mods & (1 << pyatspi.MODIFIER_NUMLOCK):
+    #    text += _("Num_Lock") + "+"
+    if mods & 128:
+        # Translators: this is presented in a GUI to represent the
+        # "right alt" modifier.
+        #
+        text += _("Alt_R") + "+"
+    if mods & (1 << pyatspi.MODIFIER_META3):
+        # Translators: this is presented in a GUI to represent the
+        # "super" modifier.
+        #
+        text += _("Super") + "+"
+    if mods & (1 << pyatspi.MODIFIER_META2):
+        # Translators: this is presented in a GUI to represent the
+        # "meta 2" modifier.
+        #
+        text += _("Meta2") + "+"
+    #if mods & (1 << pyatspi.MODIFIER_META):
+    #    text += _("Meta") + "+"
+    if mods & ALT_MODIFIER_MASK:
+        # Translators: this is presented in a GUI to represent the
+        # "left alt" modifier.
+        #
+        text += _("Alt_L") + "+"
+    if mods & CTRL_MODIFIER_MASK:
+        # Translators: this is presented in a GUI to represent the
+        # "control" modifier.
+        #
+        text += _("Ctrl") + "+"
+    if mods & (1 << pyatspi.MODIFIER_SHIFTLOCK):
+        # Translators: this is presented in a GUI to represent the
+        # "caps lock" modifier.
+        #
+        text += _("Caps_Lock") + "+"
+    if mods & SHIFT_MODIFIER_MASK:
+        # Translators: this is presented in a GUI to represent the
+        # "shift " modifier.
+        #
+        text += _("Shift") + "+"
+    return text
+
 class InputEvent:
     """Super class for all input events."""
     def __init__(self):
@@ -62,7 +160,7 @@
         # mapping ASCII control characters to UTF-8.]]]
         #
         event_string = event.event_string
-        if (event.modifiers & key_bindings.CTRL_MODIFIER_MASK) \
+        if (event.modifiers & CTRL_MODIFIER_MASK) \
             and (not event.is_text) and (len(event_string) == 1):
             value = ord(event.event_string[0])
             if value < 32:
@@ -70,8 +168,7 @@
 
         # Filter out the NUMLOCK modifier -- it always causes problems.
         #
-        event.modifiers = event.modifiers \
-                          & key_bindings.ALL_BUT_NUMLOCK_MODIFIER_MASK
+        event.modifiers = event.modifiers & ALL_BUT_NUMLOCK_MODIFIER_MASK
 
         self.type = event.type
         self.hw_code = event.hw_code
@@ -124,62 +221,8 @@
                 % (self.button, self.x, self.y)
         return s
 
-class InputEventHandler:
-    """Binds a function to a description."""
-    def __init__(self, function, description, learnModeEnabled=True):
-        """Creates a new InputEventHandler instance.  All bindings
-        (e.g., key bindings and braille bindings) will be handled
-        by an instance of an InputEventHandler.
-
-        Arguments:
-        - function: the function to call with an InputEvent instance as its
-                    sole argument.  The function is expected to return True
-                    if it consumes the event; otherwise it should return
-                    False
-        - description: a localized string describing what this InputEvent
-                       does
-        - learnModeEnabled: if True, the description will be spoken and
-                            brailled if learn mode is enabled.  If False,
-                            the function will be called no matter what.
-        """
-        self.function = function
-        self.description = description
-        self.learnModeEnabled = learnModeEnabled
-
-    def __str__(self):
-        return "%s (learnModeEnabled=%s)" \
-               % (self.description, self.learnModeEnabled)
-
-    def __eq__(self, other):
-        """Compares one input handler to another."""
-        return (self.function == other.function)
-
-    def processInputEvent(self, script, inputEvent):
-        """Processes an input event.  If settings.learnModeEnabled is True,
-        this will merely report the description of the input event to braille
-        and speech.  If settings.learnModeEnabled is False, this will call the
-        function bound to this InputEventHandler instance, passing the
-        inputEvent as the sole argument to the function.
-
-        This function is expected to return True if it consumes the
-        event; otherwise it is expected to return False.
-
-        Arguments:
-        - script:     the script (if any) associated with this event
-        - inputEvent: the input event to pass to the function bound
-                      to this InputEventHandler instance.
-        """
-        log.debug("process %s for %s" % (inputEvent, script))
-        try:
-            consumed = self.function(script, inputEvent)
-        except:
-            log.exception("while attempting to process input event")
-            consumed = False
-        return consumed
-
 if __name__ == "__main__":
     logging.basicConfig(format="%(name)s %(message)s")
     log.setLevel(logging.DEBUG)
 
-    handler = InputEventHandler(None, "Does something")
-    print handler
+    print getModifierNames(0x1ff)

Modified: branches/phase2/src/orca/script.py
==============================================================================
--- branches/phase2/src/orca/script.py	(original)
+++ branches/phase2/src/orca/script.py	Fri Sep 12 22:42:00 2008
@@ -40,7 +40,7 @@
 import logging
 log = logging.getLogger('orca.script')
 
-import key_bindings
+import braille
 
 # We want several methods here to be abstract methods so subclasses
 # can refer to self.
@@ -58,30 +58,30 @@
         Arguments:
         - app: the Python Accessible application to create a script for
         """
-        self.app = app
+        self._app = app
 
         if app:
-            self.name = self.app.name
+            self._name = self._app.name
         else:
-            self.name = "default"
+            self._name = "default"
 
-        self.name += " (module=" + self.__module__ + ")"
+        self._name += " (module=" + self.__module__ + ")"
         
-        self.listeners = self._getListeners()
+        self._isActive = False
 
-        # By default, handle events for non-active applications.
-        #
-        self.presentIfInactive = True
-        self.locusOfFocus = {}
+        self.presentIfInactive = False
+        self.listeners = self._createListeners()
+        self.keyBindings = self._createKeyBindings()
+        self.brailleBindings = self._createBrailleBindings()
 
         log.debug("NEW SCRIPT: %s" % self)
 
     def __str__(self):
         """Returns a human readable representation of the script.
         """
-        return self.name
+        return self._name
 
-    def _getListeners(self):
+    def _createListeners(self):
         """Sets up the AT-SPI event listeners for this script.
 
         Returns a dictionary where the keys are AT-SPI event names
@@ -89,20 +89,19 @@
         """
         return {}
 
-    def _getKeyBindings(self):
+    def _createKeyBindings(self):
         """Defines the key bindings for this script.
 
-        Returns an instance of keybindings.KeyBindings.
+        Returns an instance of input_bindings.KeyBindings.
         """
-        return key_bindings.KeyBindings()
+        return []
 
-    def _getBrailleBindings(self):
+    def _createBrailleBindings(self):
         """Defines the braille bindings for this script.
 
-        Returns a dictionary where the keys are BrlTTY commands and the
-        values are InputEventHandler instances.
+        Returns an instance of input_bindings.BrailleBindings
         """
-        return {}
+        return []
 
     def _getPronunciations(self):
         """Defines the application specific pronunciations for this script.
@@ -110,14 +109,50 @@
         Returns a dictionary where the keys are the actual text strings and
         the values are the replacement strings that are spoken instead.
         """
-
         return {}
 
+    def consumesKeyboardEvent(self, keyboardEvent):
+        """Called when a key is pressed on the keyboard.  If we care
+        about it, our processKeyboardEvent method will get called a
+        bit later.
+
+        Arguments:
+        - keyboardEvent: an instance of input_event.KeyboardEvent
+
+        Returns True if the event is of interest.
+        """
+        return False
+
+    def processKeyboardEvent(self, keyboardEvent):
+        """Called whenever a key is pressed on the Braille display
+        and we have an interest in it.
+        """
+        log.debug("processKeyboardEvent %s" % keyboardEvent)
+
+    def consumesBrailleEvent(self, brailleEvent):
+        """Called when a key is pressed on the braille display.  If we
+        care about it, our processBrailleEvent method will get called a
+        bit later.
+
+        Arguments:
+        - brailleEvent: an instance of input_event.KeyboardEvent
+
+        Returns True if the event is of interest.
+        """
+        return False
+
+    def processBrailleEvent(self, brailleEvent):
+        """Called whenever a key is pressed on the Braille display
+        and we have an interest in it.
+
+        Arguments:
+        - brailleEvent: an instance of input_event.BrailleEvent
+        """
+        log.debug("processBrailleEvent %s" % brailleEvent)
+
     def processObjectEvent(self, event):
         """Processes all AT-SPI object events of interest to this
-        script.  The interest in events is specified via the
-        'listeners' field that was defined during the construction of
-        this script.
+        script.
 
         In general, the primary purpose of handling object events is to
         keep track of changes to the locus of focus.
@@ -130,7 +165,8 @@
         """
         # Check to see if we really want to process this event.
         #
-        if not self.presentIfInactive:
+        if not (self._isActive or self.presentIfInactive):
+            log.debug("not processing event because script is not active")
             return
 
         # This calls the first listener it finds whose key *begins with* or is
@@ -148,8 +184,34 @@
 
     def activate(self):
         """Called when this script is activated."""
-        pass
+        log.debug("%s has been activated" % self)
+
+        self._isActive = True
+
+        # Tell BrlTTY which commands we care about.
+        # [[[TODO: WDW - do this.]]]
+        #
+        braille.setupKeyRanges([])
 
     def deactivate(self):
         """Called when this script is deactivated."""
+        log.debug("%s has been deactivated" % self)
+
+        self._isActive = False
+
+if __name__ == "__main__":
+    logging.basicConfig(format="%(name)s %(message)s")
+    log.setLevel(logging.DEBUG)
+
+    script = Script(None)
+    print script
+
+    script.processObjectEvent(None)
+
+    script.activate()
+    try:
+        script.processObjectEvent(None)
+    except:
+        # Expected since no event was passed in
+        #
         pass

Modified: branches/phase2/src/orca/script_manager.py
==============================================================================
--- branches/phase2/src/orca/script_manager.py	(original)
+++ branches/phase2/src/orca/script_manager.py	Fri Sep 12 22:42:00 2008
@@ -58,9 +58,19 @@
 #
 ignoredEventsList = ['object:bounds-changed']
 
+# Assists with dealing with CORBA COMM_FAILURES.  A failure doesn't
+# always mean an object disappeared - there just might be a network
+# glitch.  So, on COMM_FAILURES, we might retry a few times before
+# giving up on an object.  This might need to be overridden by the
+# script.
+#
+commFailureWaitTime = 0.1
+commFailureAttemptLimit = 5
+
 import logging
 log = logging.getLogger('orca.script_manager')
 
+import gobject
 import Queue
 
 import braille
@@ -81,9 +91,11 @@
         self._knownScripts   = {}
         self._activeScript   = None
         self._defaultScript  = None
-        self._eventQueue     = Queue.Queue(0)
         self._listenerCounts = {}
 
+        self._gidleId        = 0
+        self._eventQueue     = Queue.Queue(0)
+
         self._registerEventListener("window:activate")
         self._registerEventListener("window:deactivate")
         self._registerEventListener("object:children-changed:remove")
@@ -343,9 +355,9 @@
                     del app
                     del script
         except:
-            log.exception("while reclaiming scripts")
+            log.exception("exception while reclaiming scripts:")
 
-    def getScript(self, app):
+    def _getScript(self, app):
         """Get a script for an app (and make it if necessary).  This is used
         instead of a simple call to Script's constructor.
 
@@ -377,7 +389,14 @@
         Arguments:
         - newScript: the new script to be made active.
         """
+        if self._activeScript:
+            self._activeScript.deactivate()
+
         self._activeScript = newScript
+
+        if self._activeScript:
+            self._activeScript.activate()
+
         log.debug("ACTIVE SCRIPT: %s (reason=%s)"
                   % (self._activeScript, reason))
 
@@ -394,11 +413,10 @@
         Arguments:
         - keyboardEvent: an instance of input_event.KeyboardEvent
         """
-        if self._activeScript:
-            try:
-                self._activeScript.processKeyboardEvent(keyboardEvent)
-            except:
-                log.exception("while processing keyboard event")
+        try:
+            self._activeScript.processKeyboardEvent(keyboardEvent)
+        except:
+            log.exception("exception while processing keyboard event:")
 
     def _dispatchBrailleEvent(self, brailleEvent):
         """Called whenever we get a braille input event.
@@ -406,11 +424,10 @@
         Arguments:
         - brailleEvent: an instance of input_event.BrailleEvent
         """
-        if self._activeScript:
-            try:
-                self._activeScript.processBrailleEvent(brailleEvent)
-            except:
-                log.exception("while processing braille event")
+        try:
+            self._activeScript.processBrailleEvent(brailleEvent)
+        except:
+            log.exception("exception while processing braille event:")
 
     def _dispatchObjectEvent(self, event):
         """Handles all events destined for scripts.
@@ -453,6 +470,134 @@
             self._enqueueEvent(brailleEvent)
         return consume
 
+    def _processObjectEvent(self, event):
+        """Handles all AT-SPI events.
+        
+        Arguments:
+        - event: an AT-SPI event
+        """
+        # Reclaim (delete) any scripts when desktop children go away.
+        # The idea here is that a desktop child is an app. We also
+        # generally do not like object:children-changed:remove events,
+        # either.
+        #
+        if event.type.startswith("object:children-changed:remove") \
+            and (event.source == self.registry.getDesktop(0)):
+            self._reclaimScripts()
+
+        # We don't want to touch a defunct object.  It's useless and it
+        # can cause hangs.
+        #
+        try:
+            if event.source.getState().contains(pyatspi.STATE_DEFUNCT):
+                log.debug("IGNORING DEFUNCT OBJECT")
+                return
+        except:
+            log.exception("exception while checking object state for defunct:")
+            return
+
+        try:
+            # If we've received a mouse event, then don't try to get
+            # event.source.getApplication() because the top most parent
+            # is of role unknown, which will cause an ERROR message to be
+            # displayed. See Orca bug #409731 for more details.
+            #
+            if not event.type.startswith("mouse:"):
+                s = self._getScript(event.host_application \
+                                    or event.source.getApplication())
+            else:
+                s = self._activeScript
+        except:
+            s = None
+            log.exception("exception while trying to find script:")
+            return
+
+        if not s:
+            return
+
+        # We can sometimes get COMM_FAILURES even if the object has not
+        # gone away.  This happens a lot with the Java access bridge.
+        # So...we will try a few times before giving up.
+        #
+        retryCount = 0
+        while retryCount <= commFailureAttemptLimit:
+            try:
+                state = event.source.getState()
+                if not state.contains(pyatspi.STATE_ICONIFIED):
+                    eType = event.type
+                    setNewActiveScript = eType == "window:activate"
+
+                    reason = None
+                    if not reason and setNewActiveScript:
+                        reason = "window:activate event"
+
+                    # [[[TODO: WDW - HACK we look for frame that get
+                    # focus: as a means to detect active scripts
+                    # because yelp does this.  Actually, yelp is a bit
+                    # odd in that it calls itself 'yelp' then changes
+                    # its application name and id to the Gecko toolkit
+                    # in use, and then issues a focus: event on the
+                    # main window, which is a frame.]]]
+                    #
+                    setNewActiveScript = setNewActiveScript \
+                        or (eType.startswith("focus") \
+                            and (event.source.getRole() == pyatspi.ROLE_FRAME))
+
+                    if not reason and setNewActiveScript:
+                        reason = "frame received focus"
+
+                    # Added in a further check. We look for modal panels
+                    # that are now showing (such as gnome-screensaver-dialog).
+                    # See bug #530368 for more details.
+                    #
+                    setNewActiveScript = setNewActiveScript \
+                        or (eType.startswith("object:state-changed:showing")
+                            and (event.source.getRole() == pyatspi.ROLE_PANEL)
+                            and state.contains(pyatspi.STATE_MODAL))
+
+                    if not reason and setNewActiveScript:
+                        reason = "modal panel is showing"
+
+                    # Or, we might just be getting a focus event.  In this
+                    # case, assume the window has focus and we missed an
+                    # event for it somehow.
+                    #
+                    if not setNewActiveScript:
+                        if eType.startswith("focus") \
+                           or (eType.startswith("object:state-changed:focused")\
+                               and event.detail1):
+                            setNewActiveScript = \
+                                orca_state.activeScript \
+                                and event.host_application \
+                                and (orca_state.activeScript.app \
+                                     != event.host_application)
+
+                    if not reason and setNewActiveScript:
+                        reason = "object received focus"
+
+                    if setNewActiveScript:
+                        self._setActiveScript(
+                            self._getScript(event.host_application \
+                                            or event.source.getApplication()),
+                            reason)
+
+                    s.processObjectEvent(event)
+
+                    if retryCount:
+                        log.warning("  SUCCEEDED AFTER %d TRIES" % retryCount)
+                break
+            except LookupError:
+                log.exception("exception while processing %s" % event.type)
+                retryCount += 1
+                if retryCount <= commFailureAttemptLimit:
+                    log.warning("  TRYING AGAIN (%d)" % retryCount)
+                    time.sleep(s.commFailureWaitTime)
+                else:
+                    log.warning("  GIVING UP AFTER %d TRIES" % (retryCount - 1))
+            except:
+                log.exception("exception while processing %s" % event.type)
+                break
+
     def _enqueueEvent(self, e):
         """Handles all events destined for scripts.
 
@@ -512,6 +657,8 @@
             self._eventQueue.put(event)
             if processSynchronously:
                 self._dequeueEvent()
+            else:
+                self._gidleId = gobject.idle_add(self._dequeueEvent)
 
     def _dequeueEvent(self):
         """Handles all events destined for scripts.  Called by the GTK
@@ -522,17 +669,16 @@
         try:
             event = self._eventQueue.get_nowait()
             log.debug("DEQUEUED %s <----------" % str(event).replace("\n"," "))
-            log.debug("\nvvvvv PROCESS %s vvvvv" % event)
+            log.debug("\nvvvvv PROCESS %s vvvvv" % str(event).replace("\n"," "))
             if isinstance(event, input_event.KeyboardEvent):
                 self._dispatchKeyboardEvent(event)
             elif isinstance(event, input_event.BrailleEvent):
                 self._dispatchBrailleEvent(event)
             else:
-                self._dispatchObjectEvent(event)
+                self._processObjectEvent(event)
             log.debug("\n^^^^^ PROCESS %s ^^^^^" % str(event).replace("\n"," "))
         except Queue.Empty:
-            log.exception("event queue is empty!")
-            rerun = False # destroy and don't call again
+            rerun = False
         except:
             log.exception("while processing event")
 
@@ -544,4 +690,4 @@
 
     import utils
     manager = ScriptManager()
-    print manager.getScript(utils.getKnownApplications()[0])
+    print manager._getScript(utils.getKnownApplications()[0])



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