| Server IP : 182.53.201.61 / Your IP : 216.73.217.175 Web Server : Apache/2.2.15 (Fedora) System : Linux km10.dyndns.org 2.6.31.5-127.fc12.i686.PAE #1 SMP Sat Nov 7 21:25:57 EST 2009 i686 User : apache ( 48) PHP Version : 5.3.3 Disable Function : NONE MySQL : ON | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /usr/lib/python2.6/site-packages/orca/ |
Upload File : |
# Mouse reviewer for Orca
#
# Copyright 2008 Eitan Isaacson
#
# 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.
"""Mouse review mode."""
__id__ = "$Id$"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2008 Eitan Isaacson"
__license__ = "LGPL"
import debug
try:
import wnck
_mouseReviewCapable = True
except:
debug.println(debug.LEVEL_WARNING, \
"Python module wnck not found, mouse review not available.")
_mouseReviewCapable = False
import gtk
import gobject
import orca
import pyatspi
import speech
import braille
import settings
class BoundingBox:
"""A bounding box, currently it is used to test if a given point is
inside the bounds of the box.
"""
# TODO: Find if we pygtk or something already has this,
# if not, maybe this needs to be in a different Orca file.
def __init__(self, x, y, width, height):
"""Initialize a bounding box.
Arguments:
- x: Left border of box.
- y: Top border of box.
- width: Width of box.
- height: Height of box.
"""
self.x, self.y, self.width, self.height = x, y, width, height
def isInBox(self, x, y):
"""Test if a given point is inside a box.
Arguments:
- x: X coordinate.
- y: Y coordinate.
Returns True if point is inside box.
"""
return (self.x <= x <= self.x + self.width) and \
(self.y <= y <= self.y + self.height)
class _WordContext:
"""A word on which the mouse id hovering above. This class should have
enough info to make it unique, so we know when we have left the word.
"""
def __init__(self, word, acc, start, end):
"""Initialize a word context.
Arguments:
- word: The string of the word we are on.
- acc: The accessible object that contains the word.
- start: The start offset of the word in the text.
- end: The end offset of the word in the text.
"""
self.word = word
self.acc = acc
self.start = start
self.end = end
def __cmp__(self, other):
"""Compare two word contexts, if they refer to the same word, return 0.
Otherwise return 1
"""
if other is None:
return 1
return int(not(self.word == other.word and self.acc == other.acc and
self.start == other.start and self.end == other.end))
class _ItemContext:
"""An _ItemContext holds all the information of the item we are currently
hovering above. If the accessible supports word speaking, we also store
a word context here.
"""
def __init__(self, x=0, y=0, acc=None, frame=None, app=None, script=None):
"""Initialize an _ItemContext with all the information we have.
Arguments:
- x: The X coordinate of the pointer.
- y: The Y coordinate of the pointer.
- acc: The end-node accessible at that coordinate.
- frame: The top-level frame below the pointer.
- app: The application the pointer is hovering above.
- script: The script for the context's application.
"""
self.acc = acc
self.frame = frame
self.app = app
self.script = script
self.word_ctx = self._getWordContext(x, y)
def _getWordContext(self, x, y):
"""If the context's accessible supports it, retrieve the word we are
currently hovering above.
Arguments:
- x: The X coordinate of the pointer.
- y: The Y coordinate of the pointer.
Returns a _WordContext of the current word, or None.
"""
if not self.script or not self.script.speakWordUnderMouse(self.acc):
return None
word, start, end = self.script.getWordAtCoords(self.acc, x, y)
return _WordContext(word, self.acc, start, end)
class MouseReviewer:
"""Main class for the mouse-review feature.
"""
def __init__(self):
"""Initalize a mouse reviewer class.
"""
if not _mouseReviewCapable:
return
# Need to do this and allow the main loop to cycle once to get any info
wnck_screen = wnck.screen_get_default()
self.active = False
self._currentMouseOver = _ItemContext()
self._oldMouseOver = _ItemContext()
self._lastReportedCoord = None
def toggle(self, on=None):
"""Toggle mouse reviewing on or off.
Arguments:
- on: If set to True or False, explicitly toggles reviewing on or off.
"""
if not _mouseReviewCapable:
return
if on is None:
on = not self.active
if on and not self.active:
pyatspi.Registry.registerEventListener(self._onMouseMoved,
"mouse:abs")
elif not on and self.active:
pyatspi.Registry.deregisterEventListener(self._onMouseMoved,
"mouse:abs")
self.active = on
def _onMouseMoved(self, event):
"""Callback for "mouse:abs" AT-SPI event. We will check after the dwell
delay if the mouse moved away, if it didn't we will review the
component under it.
Arguments:
- event: The event we recieved.
"""
if settings.mouseDwellDelay:
gobject.timeout_add(settings.mouseDwellDelay,
self._mouseDwellTimeout,
event.detail1,
event.detail2)
else:
self._mouseDwellTimeout(event.detail1, event.detail2)
def _mouseDwellTimeout(self, prev_x, prev_y):
"""Dwell timout callback. If we are still dwelling, review the
component.
Arguments:
- prev_x: Previuos X coordinate of mouse pointer.
- prev_y: Previuos Y coordinate of mouse pointer.
"""
display = gtk.gdk.Display(gtk.gdk.get_display())
screen, x, y, flags = display.get_pointer()
if abs(prev_x - x) <= settings.mouseDwellMaxDrift \
and abs(prev_y - y) <= settings.mouseDwellMaxDrift \
and not (x, y) == self._lastReportedCoord:
self._lastReportedCoord = (x, y)
self._reportUnderMouse(x, y)
return False
def _reportUnderMouse(self, x, y):
"""Report the element under the given coordinates:
Arguments:
- x: X coordinate.
- y: Y coordinate.
"""
current_element = self._getContextUnderMouse(x, y)
if not current_element:
return
self._currentMouseOver, self._oldMouseOver = \
current_element, self._currentMouseOver
output_obj = []
if current_element.acc.getRole() in (pyatspi.ROLE_MENU_ITEM,
pyatspi.ROLE_COMBO_BOX) and \
current_element.acc.getState().contains(
pyatspi.STATE_SELECTED):
# If it is selected, we are probably doing that by hovering over it
# Orca will report this in any case.
return
if self._currentMouseOver.frame != self._oldMouseOver.frame and \
settings.mouseDwellDelay == 0:
output_obj.append(self._currentMouseOver.frame)
if self._currentMouseOver.acc != self._oldMouseOver.acc \
or (settings.mouseDwellDelay > 0 and \
not self._currentMouseOver.word_ctx):
output_obj.append(self._currentMouseOver.acc)
if self._currentMouseOver.word_ctx:
if self._currentMouseOver.word_ctx != self._oldMouseOver.word_ctx:
output_obj.append(self._currentMouseOver.word_ctx.word)
self._outputElements(output_obj)
return False
def _outputElements(self, output_obj):
"""Output the given elements.
TODO: Now we are mainly using WhereAmI, we might need to find out a
better, less verbose output method.
Arguments:
- output_obj: A list of objects to output, could be accessibles and
text.
"""
if output_obj:
speech.stop()
for obj in output_obj:
if obj is None:
continue
if isinstance(obj, str):
speech.speak(obj)
# TODO: There is probably something more useful that we could
# display.
braille.displayMessage(obj)
else:
speech.speak(
self._currentMouseOver.script.speechGenerator.\
generateSpeech(obj))
self._currentMouseOver.script.updateBraille(obj)
def _getZOrder(self, frame_name):
"""Determine the stack position of a given window.
Arguments:
- frame_name: The name of the window.
Returns position of given window in window-managers stack.
"""
# This is neccesary because z-order is still broken in AT-SPI.
wnck_screen = wnck.screen_get_default()
window_order = \
[w.get_name() for w in wnck_screen.get_windows_stacked()]
return window_order.index(frame_name)
def _getContextUnderMouse(self, x, y):
"""Get the context under the mouse.
Arguments:
- x: X coordinate.
- y: Y coordinate.
Returns _ItemContext of the component under the mouse.
"""
# Inspect accessible under mouse
desktop = pyatspi.Registry.getDesktop(0)
top_window = [None, -1]
for app in desktop:
if not app:
continue
script = orca.getScriptForApp(app)
if not script:
continue
for frame in app:
if not frame:
continue
acc = script.getComponentAtDesktopCoords(frame, x, y)
if acc:
try:
z_order = self._getZOrder(frame.name)
except ValueError:
# It's possibly a popup menu, so it would not be in
# our frame name list.
# And if it is, it is probably the top-most
# component.
try:
if acc.queryComponent().getLayer() == \
pyatspi.LAYER_POPUP:
return _ItemContext(x, y, acc, frame,
app, script)
except:
pass
else:
if z_order > top_window[-1]:
top_window = \
[_ItemContext(x, y, acc, frame, app, script),
z_order]
return top_window[0]
# Initialize a singleton reviewer.
if gtk.gdk.display_get_default():
mouse_reviewer = MouseReviewer()
else:
raise RuntimeError('Cannot initialize mouse review, no display')
def toggle(script=None, event=None):
"""
Toggle the reviewer on or off.
Arguments:
- script: Given script if this was called as a keybinding callback.
- event: Given event if this was called as a keybinding callback.
"""
mouse_reviewer.toggle()