Coverage Report - orca.mag

ModuleCoverage %
orca.mag
16%
1
# Orca
2
#
3
# Copyright 2005-2007 Sun Microsystems Inc.
4
#
5
# This library is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU Library General Public
7
# License as published by the Free Software Foundation; either
8
# version 2 of the License, or (at your option) any later version.
9
#
10
# This library is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
# Library General Public License for more details.
14
#
15
# You should have received a copy of the GNU Library General Public
16
# License along with this library; if not, write to the
17
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18
# Boston, MA 02111-1307, USA.
19
20
"""Manages the magnifier for orca.  [[[TODO: WDW - this is very very
21 1
early in development.  One might even say it is pre-prototype.]]]"""
22
23 1
__id__        = "$Id: mag.py 2076 2007-03-02 23:01:44Z wwalker $"
24 1
__version__   = "$Revision: 2076 $"
25 1
__date__      = "$Date: 2007-03-02 15:01:44 -0800 (Fri, 02 Mar 2007) $"
26 1
__copyright__ = "Copyright (c) 2005-2007 Sun Microsystems Inc."
27 1
__license__   = "LGPL"
28
29 1
import bonobo
30 1
try:
31
    # This can fail due to gtk not being available.  We want to
32
    # be able to recover from that if possible.  The main driver
33
    # for this is to allow "orca --text-setup" to work even if
34
    # the desktop is not running.
35
    #
36 1
    import gtk
37 0
except:
38 0
    pass
39 1
import time
40
41 1
import atspi
42 1
import debug
43 1
import settings
44
45 1
_magnifierAvailable = False
46
47 1
try:
48 1
    atspi.ORBit.load_typelib('GNOME_Magnifier')
49 1
    import GNOME.Magnifier
50 1
    _magnifierAvailable = True
51 0
except:
52 0
    pass
53
54 1
import time
55
56
# If True, this module has been initialized.
57
#
58 1
_initialized = False
59
60
# The Magnifier
61
#
62 1
_magnifier = None
63
64
# The width and height, in unzoomed system coordinates of the rectangle that,
65
# when magnified, will fill the viewport of the magnifier - this needs to be
66
# sync'd with the magnification factors of the zoom area.
67
#
68 1
_roiWidth = 0
69 1
_roiHeight = 0
70
71
# Minimum values for the center of the ROI
72
#
73 1
_minROIX = 0
74 1
_maxROIX = 0
75 1
_minROIY = 0
76 1
_maxROIY = 0
77
78
# The current region of interest.
79
#
80 1
_roi = None
81
82
# The area we are magnifying
83
#
84 1
_sourceDisplayBounds = None
85
86
# The area where we are magnifying
87
#
88 1
_targetDisplayBounds = None
89
90
# The ZoomRegion we care about [[[TODO: WDW - we should be more careful about
91
# just what we're doing here.  The Magnifier allows more than one ZoomRegion
92
# and we're just picking up the first one.]]]
93
#
94 1
_zoomer = None
95
96
# The time of the last mouse event.
97
#
98 1
_lastMouseEventTime = time.time()
99
100
# If True, we're using gnome-mag >= 0.13.1 that allows us to control
101
# where to draw the cursor and crosswires.
102
#
103 1
_pollMouseDisabled = False
104
105 1
def __setROI(rect):
106
    """Sets the region of interest.
107
108
    Arguments:
109
    - rect: A GNOME.Magnifier.RectBounds object.
110
    """
111
112 0
    global _roi
113
114 0
    _roi = rect
115 0
    _zoomer.setROI(_roi)
116 0
    _zoomer.markDirty(_roi)  # [[[TODO: WDW - for some reason, this seems
117
                             # necessary.]]]
118 1
def __setROICenter(x, y):
119
    """Centers the region of interest around the given point.
120
121
    Arguments:
122
    - x: integer in unzoomed system coordinates representing x component
123
    - y: integer in unzoomed system coordinates representing y component
124
    """
125
126 0
    if not _initialized:
127 0
        return
128
129 0
    if x < _minROIX:
130 0
        x = _minROIX
131 0
    elif x > _maxROIX:
132 0
        x = _maxROIX
133
134 0
    if y < _minROIY:
135 0
        y = _minROIY
136 0
    elif y > _maxROIY:
137 0
        y = _maxROIY
138
139 0
    x1 = x - (_roiWidth / 2)
140 0
    y1 = y - (_roiHeight / 2)
141
142 0
    x2 = x1 + _roiWidth
143 0
    y2 = y1 + _roiHeight
144
145 0
    __setROI(GNOME.Magnifier.RectBounds(x1, y1, x2, y2))
146
147 1
def __setROIPush(x, y):
148
    """Nudges the ROI if the pointer bumps into the edge of it.  The point
149
    given is assumed to be the point where the mouse pointer is.
150
151
    Arguments:
152
    - x: integer in unzoomed system coordinates representing x component
153
    - y: integer in unzoomed system coordinates representing y component
154
    """
155
156 0
    needNewROI = False
157 0
    newROI = GNOME.Magnifier.RectBounds(_roi.x1, _roi.y1, _roi.x2, _roi.y2)
158 0
    if x < _roi.x1:
159 0
        needNewROI = True
160 0
        newROI.x1 = x
161 0
        newROI.x2 = x + _roiWidth
162 0
    elif x > _roi.x2:
163 0
        needNewROI = True
164 0
        newROI.x2 = x
165 0
        newROI.x1 = x - _roiWidth
166 0
    if y < _roi.y1:
167 0
        needNewROI = True
168 0
        newROI.y1 = y
169 0
        newROI.y2 = y + _roiHeight
170 0
    elif y > _roi.y2:
171 0
        needNewROI = True
172 0
        newROI.y2 = y
173 0
        newROI.y1 = y - _roiHeight
174
175
    # Well...we'll always update the ROI so the new gnome-mag API
176
    # will redraw the crosswires for us.
177
    #
178
    #if needNewROI:
179 0
    if True:
180 0
        __setROI(newROI)
181
182 1
def __setROIProportional(x, y):
183
    """Positions the ROI proportionally to where the pointer is on the screen.
184
185
    Arguments:
186
    - x: integer in unzoomed system coordinates representing x component
187
    - y: integer in unzoomed system coordinates representing y component
188
    """
189
190 0
    if not _initialized:
191 0
        return
192
193 0
    if not _sourceDisplayBounds:
194 0
        __setROICenter(x, y)
195
    else:
196 0
        halfScreenWidth  = (_sourceDisplayBounds.x2 \
197
                            - _sourceDisplayBounds.x1) / 2.0
198 0
        halfScreenHeight = (_sourceDisplayBounds.y2 \
199
                            - _sourceDisplayBounds.y1) / 2.0
200
201 0
        proportionX = (halfScreenWidth  - x) / halfScreenWidth
202 0
        proportionY = (halfScreenHeight - y) / halfScreenHeight
203
204 0
        centerX = x + int(proportionX * _roiWidth  / 2.0)
205 0
        centerY = y + int(proportionY * _roiHeight / 2.0)
206
207 0
        __setROICenter(centerX, centerY)
208
209
# Used for tracking the pointer.
210
#
211 1
def __onMouseEvent(e):
212
    """
213
    Arguments:
214
    - e: at-spi event from the at-api registry
215
    """
216
217 0
    global _lastMouseEventTime
218
219 0
    _lastMouseEventTime = time.time()
220
221 0
    [x, y] = [e.detail1, e.detail2]
222
223
    # If True, we're using gnome-mag >= 0.13.1 that allows us to
224
    # control where to draw the cursor and crosswires.
225
    #
226 0
    if _pollMouseDisabled:
227 0
        _zoomer.setPointerPos(x, y)
228
229 0
    if settings.magMouseTrackingMode == settings.MAG_MOUSE_TRACKING_MODE_PUSH:
230 0
        __setROIPush(x, y)
231 0
    elif settings.magMouseTrackingMode == settings.MAG_MOUSE_TRACKING_MODE_PROPORTIONAL:
232 0
        __setROIProportional(x, y)
233 0
    elif settings.magMouseTrackingMode == settings.MAG_MOUSE_TRACKING_MODE_CENTERED:
234 0
        __setROICenter(x, y)
235
236 1
def __getValueText(slot, value):
237 0
    valueText = ""
238 0
    if slot == "cursor-hotspot":
239 0
        valueText = "(%d, %d)" % (value.x, value.y)
240 0
    elif slot == "source-display-bounds":
241 0
        valueText = "(%d, %d),(%d, %d)" \
242
                    % (value.x1, value.y1, value.x2, value.y2)
243 0
    elif slot == "target-display-bounds":
244 0
        valueText = "(%d, %d),(%d, %d)" \
245
                    % (value.x1, value.y1, value.x2, value.y2)
246 0
    elif slot == "viewport":
247 0
        valueText = "(%d, %d),(%d, %d)" \
248
                    % (value.x1, value.y1, value.x2, value.y2)
249 0
    return valueText
250
251 1
def __dumpPropertyBag(obj):
252 0
    pbag = obj.getProperties()
253 0
    slots = pbag.getKeys("")
254 0
    print "  Available slots: ", pbag.getKeys("")
255 0
    for slot in slots:
256
        # These crash the magnifier since it doesn't know how to marshall
257
        # them to us.
258
        #
259 0
        if slot in ["cursor-set", "smoothing-type"]:
260 0
            continue
261 0
        print "    About '%s':" % slot
262 0
        print "    Doc Title:", pbag.getDocTitle(slot)
263 0
        print "    Type:", pbag.getType(slot)
264 0
        value = pbag.getDefault(slot).value()
265 0
        print "    Default value:", value, __getValueText(slot, value)
266 0
        value = pbag.getValue(slot).value()
267 0
        print "    Current value:", value, __getValueText(slot, value)
268 0
        print
269
270 1
def applySettings():
271
    """Looks at the user settings and applies them to the magnifier."""
272
273 0
    global _sourceDisplayBounds
274 0
    global _targetDisplayBounds
275 0
    global _zoomer
276 0
    global _roiWidth
277 0
    global _roiHeight
278 0
    global _minROIX
279 0
    global _minROIY
280 0
    global _maxROIX
281 0
    global _maxROIY
282 0
    global _pollMouseDisabled
283
284
    ########################################################################
285
    #                                                                      #
286
    # First set up the magnifier properties.                               #
287
    #                                                                      #
288
    ########################################################################
289
290 0
    _magnifier.clearAllZoomRegions()
291
292 0
    try:
293 0
        _magnifier.TargetDisplay = settings.magTargetDisplay
294 0
    except:
295 0
        pass
296
297 0
    try:
298 0
        _magnifier.SourceDisplay = settings.magSourceDisplay
299 0
    except:
300 0
        pass
301
302 0
    magnifierPBag = _magnifier.getProperties()
303
304 0
    try:
305 0
        tdb = magnifierPBag.getValue("target-display-bounds").value()
306 0
        magnifierPBag.setValue(
307
            "target-display-bounds",
308 0
            atspi.ORBit.CORBA.Any(
309
                atspi.ORBit.CORBA.TypeCode(
310
                    tdb.__typecode__.repo_id),
311 0
                GNOME.Magnifier.RectBounds(settings.magZoomerLeft,
312 0
                                           settings.magZoomerTop,
313 0
                                           settings.magZoomerRight,
314 0
                                           settings.magZoomerBottom)))
315 0
    except:
316 0
        debug.printException(debug.LEVEL_OFF)
317
318 0
    if settings.enableMagCursor:
319 0
        bonobo.pbclient_set_float(
320
            magnifierPBag, "cursor-scale-factor", 1.0 * settings.magZoomFactor)
321
    else:
322 0
        bonobo.pbclient_set_float(
323
            magnifierPBag, "cursor-scale-factor", 0.0)
324
325 0
    if settings.enableMagCursorExplicitSize:
326 0
        bonobo.pbclient_set_long(
327
            magnifierPBag, "cursor-size", settings.magCursorSize)
328
    else:
329 0
        bonobo.pbclient_set_long(
330
            magnifierPBag, "cursor-size", 0)
331
332 0
    bonobo.pbclient_set_string(magnifierPBag, "cursor-set", "default")
333
334
    # Convert the colorPreference string to something we can use.
335
    # The main issue here is that the color preferences are saved
336
    # as 4 byte values per color.  We only need 2 bytes, so we
337
    # get rid of the bottom 8 bits.
338
    #
339 0
    colorPreference = gtk.gdk.color_parse(settings.magCursorColor)
340 0
    colorPreference.red   = colorPreference.red   >> 8
341 0
    colorPreference.blue  = colorPreference.blue  >> 8
342 0
    colorPreference.green = colorPreference.green >> 8
343 0
    colorString = "0x%02X%02X%02X" \
344
                  % (colorPreference.red,
345
                     colorPreference.green,
346
                     colorPreference.blue)
347
348 0
    color = magnifierPBag.getValue("cursor-color")
349 0
    magnifierPBag.setValue(
350
        "cursor-color",
351 0
        atspi.ORBit.CORBA.Any(
352
            color.typecode(),
353 0
            long(colorString, 0)))
354
355 0
    color = magnifierPBag.getValue("crosswire-color")
356 0
    magnifierPBag.setValue(
357
        "crosswire-color",
358 0
        atspi.ORBit.CORBA.Any(
359
            color.typecode(),
360 0
            long(colorString, 0)))
361
362 0
    if settings.enableMagCrossHair:
363 0
        bonobo.pbclient_set_long(
364
            magnifierPBag, "crosswire-size", settings.magCrossHairSize)
365
    else:
366 0
        bonobo.pbclient_set_long(
367
            magnifierPBag, "crosswire-size", 0)
368
369 0
    bonobo.pbclient_set_boolean(
370
        magnifierPBag, "crosswire-clip", settings.enableMagCrossHairClip)
371
372
    ########################################################################
373
    #                                                                      #
374
    # Now set up the zoomer properties.                                    #
375
    #                                                                      #
376
    ########################################################################
377
378 0
    _sourceDisplayBounds = magnifierPBag.getValue(
379
        "source-display-bounds").value()
380
381 0
    _targetDisplayBounds = magnifierPBag.getValue(
382
        "target-display-bounds").value()
383
384 0
    _zoomer = _magnifier.createZoomRegion(
385
        settings.magZoomFactor, settings.magZoomFactor,
386 0
        GNOME.Magnifier.RectBounds(0,
387 0
                                   0,
388 0
                                   -1,
389 0
                                   -1),
390 0
        GNOME.Magnifier.RectBounds(0,
391 0
                                   0,
392 0
                                   _targetDisplayBounds.x2 \
393
                                   - _targetDisplayBounds.x1,
394 0
                                   _targetDisplayBounds.y2 \
395
                                   - _targetDisplayBounds.y1))
396
397 0
    zoomerPBag = _zoomer.getProperties()
398 0
    bonobo.pbclient_set_boolean(zoomerPBag, "is-managed", True)
399
400
    # Try to use gnome-mag >= 0.13.1 to allow us to control where to
401
    # draw the cursor and crosswires.
402
    #
403 0
    try:
404 0
        bonobo.pbclient_set_boolean(zoomerPBag, "poll-mouse", False)
405 0
        _pollMouseDisabled = True
406 0
    except:
407 0
        _pollMouseDisabled = False
408
409 0
    _zoomer.setMagFactor(settings.magZoomFactor, settings.magZoomFactor)
410
411 0
    bonobo.pbclient_set_boolean(
412
        zoomerPBag, "inverse-video", settings.enableMagZoomerColorInversion)
413
414 0
    if settings.magSmoothingMode == settings.MAG_SMOOTHING_MODE_BILINEAR:
415 0
        try:
416 0
            bonobo.pbclient_set_string(
417
                zoomerPBag, "smoothing-type", "bilinear")
418 0
        except:
419 0
            pass
420
421 0
    viewport = zoomerPBag.getValue("viewport").value()
422
423 0
    magx = zoomerPBag.getValue("mag-factor-x").value()
424 0
    magy = zoomerPBag.getValue("mag-factor-y").value()
425
426 0
    _roiWidth = (viewport.x2 - viewport.x1) / magx
427 0
    _roiHeight = (viewport.y2 - viewport.y1) / magy
428
429 0
    _minROIX = _sourceDisplayBounds.x1 + (_roiWidth / 2)
430 0
    _minROIY = _sourceDisplayBounds.y1 + (_roiHeight / 2)
431
432 0
    _maxROIX = _sourceDisplayBounds.x2 - (_roiWidth / 2)
433 0
    _maxROIY = _sourceDisplayBounds.y2 - (_roiHeight / 2)
434
435 0
    _magnifier.addZoomRegion(_zoomer)
436
437
    #print "MAGNIFIER PROPERTIES:", _magnifier
438
    #__dumpPropertyBag(_magnifier)
439
    #print "ZOOMER PROPERTIES:", _zoomer
440
    #__dumpPropertyBag(_zoomer)
441
442 1
def magnifyAccessible(event, obj, extents=None):
443
    """Sets the region of interest to the upper left of the given
444
    accessible, if it implements the Component interface.  Otherwise,
445
    does nothing.
446
447
    Arguments:
448
    - event: the Event that caused this to be called
449
    - obj: the accessible
450
    """
451
452 1732
    if not _initialized:
453 1732
        return
454
455 0
    if extents:
456 0
        [x, y, width, height] = extents
457 0
    elif event and (event.type == "object:text-caret-moved") \
458
       and obj.text and (obj.text.caretOffset >= 0):
459 0
        offset = obj.text.caretOffset
460 0
        [x, y, width, height] = obj.text.getCharacterExtents(offset,
461 0
                                                             0) # coord type screen
462 0
    elif obj.extents:
463 0
        extents = obj.extents
464 0
        [x, y, width, height] = [extents.x, extents.y, extents.width, extents.height]
465
    else:
466 0
        return
467
468
    # Avoid jerking the display around if the mouse is what ended up causing
469
    # this event.  We guess this by seeing if this request has come in within
470
    # a close period of time.  [[[TODO: WDW - this is a hack and really
471
    # doesn't belong here.  Plus, the delta probably should be adjustable.]]]
472
    #
473 0
    currentTime = time.time()
474 0
    if (currentTime - _lastMouseEventTime) < 0.2: # 200 milliseconds
475 0
        return
476
477
    # Determine if the accessible is partially to the left, right,
478
    # above, or below the current region of interest (ROI).
479
    #
480 0
    leftOfROI = x < _roi.x1
481 0
    rightOfROI = (x + width) > _roi.x2
482 0
    aboveROI = y < _roi.y1
483 0
    belowROI = (y + height) > _roi.y2
484
485
    # If it is already completely showing, do nothing.
486
    #
487 0
    visibleX = not(leftOfROI or rightOfROI)
488 0
    visibleY = not(aboveROI or belowROI)
489
490 0
    if visibleX and visibleY:
491 0
        _zoomer.markDirty(_roi)
492
493
    # The algorithm is devised to move the ROI as little as possible, yet
494
    # favor the top left side of the object [[[TODO: WDW - the left/right
495
    # top/bottom favoring should probably depend upon the locale.  Also,
496
    # I had the notion of including a floating point snap factor between
497
    # 0.0 and 1.0 that would determine how to position the object in the
498
    # window relative to the ROI edges.  A snap factor of -1 would mean to
499
    # snap to the closest edge.  A snap factor of 0.0 would snap to the
500
    # left most or top most edge, a snap factor of 1.0 would snap to the
501
    # right most or bottom most edge.  Any number in between would divide
502
    # the two.]]]
503
    #
504 0
    x1 = _roi.x1
505 0
    x2 = _roi.x2
506 0
    y1 = _roi.y1
507 0
    y2 = _roi.y2
508
509 0
    if leftOfROI:
510 0
        x1 = x
511 0
        x2 = x1 + _roiWidth
512 0
    elif rightOfROI:
513 0
        if width > _roiWidth:
514 0
            x1 = x
515 0
            x2 = x1 + _roiWidth
516
        else:
517 0
            x2 = x + width
518 0
            x1 = x2 - _roiWidth
519
520 0
    if aboveROI:
521 0
        y1 = y
522 0
        y2 = y1 + _roiHeight
523 0
    elif belowROI:
524 0
        if height > _roiHeight:
525 0
            y1 = y
526 0
            y2 = y1 + _roiHeight
527
        else:
528 0
            y2 = y + height
529 0
            y1 = y2 - _roiHeight
530
531 0
    __setROI(GNOME.Magnifier.RectBounds(x1, y1, x2, y2))
532
533 1
def init():
534
    """Initializes the magnifier, bringing the magnifier up on the
535
    display.
536
537
    Returns True if the initialization procedure was run or False if this
538
    module has already been initialized.
539
    """
540
541 0
    global _initialized
542 0
    global _magnifier
543
544 0
    if not _magnifierAvailable:
545 0
        return False
546
547 0
    if _initialized:
548 0
        return False
549
550 0
    _magnifier = bonobo.get_object("OAFIID:GNOME_Magnifier_Magnifier:0.9",
551 0
                                   "GNOME/Magnifier/Magnifier")
552
553 0
    try:
554 0
        applySettings()
555 0
    except:
556 0
        debug.printException(debug.LEVEL_SEVERE)
557
558 0
    atspi.Registry().registerEventListener(__onMouseEvent, "mouse:abs")
559
560 0
    _initialized = True
561
562
    # Zoom to the upper left corner of the display for now.
563
    #
564 0
    __setROICenter(0, 0)
565
566 0
    return True
567
568 1
def shutdown():
569
    """Shuts down the magnifier module.
570
    Returns True if the shutdown procedure was run or False if this
571
    module has not been initialized.
572
    """
573
574 0
    global _initialized
575 0
    global _magnifier
576
577 1
    if not _magnifierAvailable:
578 0
        return False
579
580 1
    if not _initialized:
581 1
        return False
582
583 0
    atspi.Registry().deregisterEventListener(__onMouseEvent,"mouse:abs")
584
585 0
    _magnifier.clearAllZoomRegions()
586 0
    _magnifier.dispose()
587 0
    _magnifier = None
588
589 0
    _initialized = False
590
591 0
    return True