1 '''
2 This device drives the Gnome Magnifier (magnifier).
3
4 @author: Eitan Isaacson <eitan@ascender.com>
5 @organization: IBM Corporation
6 @copyright: Copyright (c) 2006 IBM Corp
7 @license: The BSD License
8
9 @author: Frank Zenker
10 @organization: IT Science Center Ruegen gGmbH, Germany
11 @copyright: Copyright (c) 2007, 2008 ITSC Ruegen
12 @license: The BSD License
13
14 All rights reserved. This program and the accompanying materials are made
15 available under the terms of the BSD license which accompanies
16 this distribution, and is available at
17 U{http://www.opensource.org/licenses/bsd-license.php}
18 '''
19 from AccessEngine import AEOutput
20 from Tools.i18n import _
21 from AccessEngine.AEConstants import CMD_GOTO, CMD_GET_ROI
22 from gtk.gdk import Display, get_display
23 import ORBit, bonobo
24
25 __uie__ = dict(kind='device')
26
27 ORBit.load_typelib("GNOME_Magnifier")
28 import GNOME.Magnifier
29
31 '''
32 Overrides the base L{AccessEngine.AEOutput.AEOutput.Style.Style} class,
33 filling in fields for supported style properties with their appropriate values.
34
35 FullScreen (bool): Magnify in full screen mode, this only affects magnifiers
36 that are in a seperate screen.
37
38 Zoom (float): Magnification factor.
39
40 Invert (bool): Invert the colors displayed by the magnifier
41
42 SmoothingType (enum): Method used to smooth the magnification
43
44 TargetDisplayX (int): Left boundary of magnifier display
45
46 TargetDisplayY (int): Top boundary of magnifier display
47
48 TargetDisplayW (int): Width of magnifier display
49
50 TargetDisplayH (int): Height of magnifier display
51
52 TargetDisplayScreen (string): Screen magnifier should be displayed on.
53
54 TargetDisplayConfigured (bool): If False, resort to magnifier placement
55 defaults.
56
57 CursorScale (float): Scale of cursor in magnified display
58
59 CursorColor (color): Color of cursor in magnified display
60
61 CrosswireSize (int): Size of cursor crosswire in magnified display
62
63 CrosswireColor (color): Color of crosswire in magnified display
64
65 CrosswireClip (bool): Clip crosswire around cursor
66
67 ContrastRed (float): Red contrast of the magnifier
68
69 ContrastGreen (float): Green contrast of the magnifier
70
71 ContrastBlue (float): Blue contrast of the magnifier
72 '''
73 - def init(self, device):
74 self.newBool('FullScreen', False, _('Full screen'),
75 _('Magnify in full screen mode, this only affects magnifiers that are '
76 'in a seperate screen.'))
77
78 self.newRange('Zoom', 2, _('Zoom Factor'), 1, 13, 1,
79 _('Change the magnification zoom factor.'))
80
81 self.newBool('Invert', False, _('Invert magnifier'),
82 _('Invert the colors displayed by the magnifier'))
83
84 self.newEnum('SmoothingType', 'none', _('Smoothing method'),
85 {'None':'none','Bilinear':'bilinear-interpolation'},
86 _('Change the method used to smooth the magnification'))
87
88 self.newNumeric('TargetDisplayX', 0, _('Left boundary'), 0, 1600, 0,
89 _('Left boundary of magnifier display'))
90
91 self.newNumeric('TargetDisplayY', 0, _('Top boundary'), 0, 1200, 0,
92 _('Top boundary of magnifier display'))
93
94 self.newNumeric('TargetDisplayW', 100, _('Magnifier width'), 0, 1600, 0,
95 _('Width of magnifier display'))
96
97 self.newNumeric('TargetDisplayH', 100, _('Magnifier height'), 0, 1200, 0,
98 _('Height of magnifier display'))
99
100 self.newString('TargetDisplayScreen', '', _('Magnifier screen'),
101 _('Put the magnifier on a separate display'))
102
103 self.newBool('TargetDisplayConfigured', False,
104 _('Target Display Configured'),
105 _('Set to True once the target display has been configured'))
106
107 self.newRange('CursorScale', 2, _('Cursor scale'),1, 13, 1,
108 _('Scale of cursor in magnified display'))
109
110 self.newColor('CursorColor', (0x0, 0x0, 0x0), _('Cursor color'),
111 _('Color of cursor in magnified display'))
112
113 self.newNumeric('CrosswireSize', 1, _('Cursor crosswire size'), 0, 600, 0,
114 _('Size of cursor crosswire in magnified display'))
115
116 self.newColor('CrosswireColor', (0x0, 0x0, 0x0), _('Crosswire color'),
117 _('Color of crosswire in magnified display'))
118
119 self.newBool('CrosswireClip', False, _('Clip crosswire'),
120 _('Clip crosswire around cursor'))
121
122 self.newRange('ContrastRed', 0, _('Red Contrast'), -1, 1, 2,
123 _('Adjust the red contrast of the magnifier'))
124
125 self.newRange('ContrastGreen', 0, _('Green Contrast'), -1, 1, 2,
126 _('Adjust the green contrast of the magnifier'))
127
128 self.newRange('ContrastBlue', 0, _('Blue Contrast'), -1, 1, 2,
129 _('Adjust the blue contrast of the magnifier'))
130
131
132
133 self.newString('MagVersion', '', '')
134
136 '''
137 Gets configurable settings for magnifier and one zoom region
138
139 @return: Group of all configurable settings
140 @rtype: L{AEState.Setting.Group}
141 '''
142 g = self.newGroup()
143
144 d = g.newGroup('Display')
145 d.append('FullScreen')
146 d.append('Zoom')
147 d.append('Invert')
148 d.append('SmoothingType')
149
150 td = g.newGroup('Target Display')
151 td.append('TargetDisplayX')
152 td.append('TargetDisplayY')
153 td.append('TargetDisplayW')
154 td.append('TargetDisplayH')
155 td.append('TargetDisplayScreen')
156
157 c = g.newGroup('Cursor')
158 c.append('CursorScale')
159 c.append('CursorColor')
160 c.append('CrosswireSize')
161 c.append('CrosswireColor')
162 c.append('CrosswireClip')
163
164 co = g.newGroup('Contrast')
165 co.append('ContrastRed')
166 co.append('ContrastGreen')
167 co.append('ContrastBlue')
168
169 return g
170
172 '''
173 GNOME Magnifier device. Currently manages a single zoom region, while
174 exposing all of its properties for configuration.
175
176 @ivar mag: Magnifier instance
177 @type mag: GNOME.Magnifier.Magnifier
178 @ivar mag_pb_proxy: Proxy for the property bag on the magnifier
179 @type mag_pb_proxy: L{PropertyBagProxy}
180 @ivar zoom: Zoom region
181 @type zoom: GNOME.Magnifier.ZoomRegion
182 @ivar zoom_pb_proxy: Proxy for property bag on the zoomer
183 @type zoom_pb_proxy: L{PropertyBagProxy}
184 '''
185 STYLE = GnomeMagStyle
186 MAGNIFIER_IID = "OAFIID:GNOME_Magnifier_Magnifier:0.9"
187 MAGNIFIER_OBJ = "GNOME/Magnifier/Magnifier"
188
189 property_trans_zoom = {'Invert': 'inverse-video',
190 'SmoothingType': 'smoothing-type',
191 'ContrastRed': 'red-contrast',
192 'ContrastGreen': 'green-contrast',
193 'ContrastBlue': 'blue-contrast'}
194 property_trans_mag = {'TargetDisplayScreen': 'target-display-screen',
195 'CursorScale': 'cursor-scale-factor',
196 'CursorColor': 'cursor-color',
197 'CrosswireSize': 'crosswire-size',
198 'CrosswireColor': 'crosswire-color',
199 'CrosswireClip': 'crosswire-clip'}
200
202 '''
203 Initializes the gnome magnifier.
204
205 @raise AEOutput.InitError: When the device can not be initialized
206 '''
207 try:
208
209 self.mag = bonobo.get_object(self.MAGNIFIER_IID, self.MAGNIFIER_OBJ)
210 except Exception:
211
212 raise AEOutput.InitError('Could not activate:', self.MAGNIFIER_IID)
213 self.mag_pb_proxy = PropertyBagProxy(self.mag.getProperties())
214
215 - def postInit(self):
216 '''
217 Called after the L{init} method and after either L{AEOutput.loadStyles
218 <AccessEngine.AEDevice.AEOutput.Base.AEOutput.loadStyles>} or
219 L{AEOutput.createDistinctStyles
220 <AccessEngine.AEDevice.AEOutput.Base.AEOutput.createDistinctStyles>}.
221 Override this method to perform additional initilization after the setting
222 values are available.
223 '''
224 self._initMag(self.default_style)
225 self._getZoom()
226 self._correctContrast(self.default_style)
227
228 for setting in self.default_style:
229 setting.addObserver(self._updateSetting)
230 if not setting.name.startswith('TargetDisplay'):
231 self._updateSetting(self.default_style, setting)
232
234 '''
235 Uses a bug in versions of the GNOME magnifier to detect what version of
236 the magnifier is running so that the scale of the color constrast values
237 can be corrected.
238
239 @param style: Default style object
240 @type style: L{GnomeMagStyle}
241 '''
242
243
244 test = -10
245 old = '0.13'
246 new = '0.14'
247
248 key = self.property_trans_zoom['ContrastRed']
249 orig = self.zoom_pb_proxy[key]
250 self.zoom_pb_proxy[key] = test
251
252 if self.zoom_pb_proxy[key] == test:
253 cv = old
254 else:
255 cv = new
256 self.zoom_pb_proxy[key] = orig
257
258
259 v = style.MagVersion
260
261 if not v:
262
263 if cv == old:
264 self._setContrastValues(style, 0, 1, lambda x: 1)
265
266 elif v == old:
267 if cv == new:
268
269 self._setContrastValues(style, -1, 1, lambda x: x - 1)
270 elif v == new:
271 if cv == old:
272
273 self._setContrastValues(style, 0, 1, lambda x: min(x,0)+1)
274
275
276 style.MagVersion = cv
277
279 '''
280 Sets all color contrast values at once.
281
282 @param style: Style object
283 @type style: L{GnomeMagStyle}
284 @param min: Minimum value
285 @type min: float
286 @param max: Maximum value
287 @type max: float
288 @param convert: Conversion equation
289 @type convert: callable
290 '''
291 for name in ('ContrastRed', 'ContrastGreen', 'ContrastBlue'):
292 sett = style.getSettingObj(name)
293 sett.min = min
294 sett.max = max
295 sett.value = convert(sett.value)
296
298 '''
299 Set the magnifier object to the correct screen, position, and size.
300
301 @param style: Default style object
302 @type style: L{GnomeMagStyle}
303 '''
304 if style.TargetDisplayScreen != '':
305 self.mag_pb_proxy['target-display-screen'] = style.TargetDisplayScreen
306 if style.FullScreen:
307 self._setFullScreen(False)
308 elif style.TargetDisplayConfigured:
309 self.mag_pb_proxy['target-display-bounds'] = (style.TargetDisplayX,
310 style.TargetDisplayY,
311 style.TargetDisplayW,
312 style.TargetDisplayH)
313 else:
314
315 target_display = get_display()
316 w, h = self._getScreenSize(target_display)
317 style.TargetDisplayScreen = target_display
318 style.TargetDisplayX = w/2
319 style.TargetDisplayY = 0
320 style.TargetDisplayW = w-w/2
321 style.TargetDisplayH = h
322 self.mag_pb_proxy['target-display-bounds'] = (style.TargetDisplayX,
323 style.TargetDisplayY,
324 style.TargetDisplayW,
325 style.TargetDisplayH)
326 style.TargetDisplayConfigured = True
327
329 '''
330 Get magnifier's first zoom region, if it doesn't exist create one.
331 Resize viewport to magnifier's target display size.
332 '''
333 zoom_regions = self.mag.getZoomRegions()
334
335 x, y, w, h = self.mag_pb_proxy['target-display-bounds']
336 if len(zoom_regions) > 0:
337 self.zoom = zoom_regions[-1]
338 self.zoom_pb_proxy = PropertyBagProxy(self.zoom.getProperties())
339 self.zoom.setMagFactor(self.default_style.Zoom,
340 self.default_style.Zoom)
341 self.zoom_pb_proxy['viewport'] = (0, 0, w, h)
342 else:
343 self.zoom = self.mag.createZoomRegion(
344 self.default_style.Zoom,
345 self.default_style.Zoom,
346 GNOME.Magnifier.RectBounds(0,0,w,h),
347 GNOME.Magnifier.RectBounds(0,0,w,h))
348 self.zoom_pb_proxy = PropertyBagProxy(self.zoom.getProperties())
349 self.mag.addZoomRegion(self.zoom)
350
352 '''
353 Stop and close the magnifier device.
354 '''
355 self.mag.dispose()
356 del self.mag
357
359 '''
360 @return: 'magnifier' as the only capability of this device.
361 @rtype: list of string
362 '''
363 return ['magnifier']
364
367
368 - def send(self, name, value, style=None):
369 '''
370 Perform given command, or simply apply all dirty style properties.
371 '''
372 if name is CMD_GOTO:
373 self._setPos(self.zoom,value)
374 elif name is CMD_GET_ROI:
375 return self._getPos(self.zoom)
376
378 '''
379 If source display is not target display, set magnifier to fullscreen.
380
381 @param reset_viewport: Snap zoomer's viewport to new magnifier size.
382 @type reset_viewport: boolean
383 '''
384 source_display = self.mag_pb_proxy['source-display-screen']
385 target_display = self.mag_pb_proxy['target-display-screen']
386 if target_display == source_display:
387 return
388 w, h = self._getScreenSize(target_display )
389 if None not in (w, h):
390 self.mag_pb_proxy['target-display-bounds'] = (0,0,w,h)
391 if reset_viewport:
392 self.zoom_pb_proxy['viewport'] = (0,0,w,h)
393
395 '''
396 Get the size of a given screen.
397
398 @param display_name: Name of display.
399 @type display_name: string
400 @return: Width and height of display, or (None, None) if there
401 was trouble retrieving display size.
402 @rtype: tuple
403 '''
404 try:
405 display = Display(display_name)
406 except RuntimeError:
407 return None, None
408 screen = display.get_default_screen()
409 w, h = screen.get_width(), screen.get_height()
410 return w,h
411
413 '''
414 Checks if given screen exists
415
416 @param display_name: Name of display.
417 @type display_name: string
418 @return: True if screen exists, False if not.
419 @rtype: boolean
420 '''
421 try:
422 display = Display(display_name)
423 except RuntimeError:
424 return False
425 return True
426
427 - def _setPos(self, zoom, roi_tuple):
428 '''
429 Set ROI of zoomer.
430
431 @param zoom: Zoomer to adjust.
432 @type zoom: GNOME.Magnifier.ZoomRegion
433 @param roi_tuple: Region of interest in x,y,w,h
434 @type roi_tuple: 4-tuple of integer
435 '''
436 self._roi = GNOME.Magnifier.RectBounds(*roi_tuple)
437 try:
438 zoom.setROI(self._roi)
439 except Exception:
440 pass
441
443 '''
444 Get ROI of the zoomer including left, top, right, bottom coordinates.
445
446 @param zoom: Zoomer to adjust.
447 @type zoom: GNOME.Magnifier.ZoomRegion
448 @return: Bounds of the zoomer region
449 @rtype: 4-tuple of int
450 '''
451 try:
452 roi = zoom.getROI()
453 except Exception:
454 return None
455 x_zoom, y_zoom = self.zoom.getMagFactor()
456 x,y,w,h = self.zoom_pb_proxy['viewport']
457 x_center = (roi.x1 + roi.x2)/2
458 y_center = (roi.y1 + roi.y2)/2
459 region_width = w/x_zoom
460 region_height = h/y_zoom
461 rv = (x_center - region_width/2, y_center - region_height/2,
462 x_center + region_width/2, y_center + region_height/2)
463 return tuple(map(int,map(round,rv)))
464
466 '''
467 Apply style attribute to magnifier or zoomer.
468
469 @param style: Style object ot retrieve attribute from
470 @type style: L{AccessEngine.AEOutput.AEOutput.Style}
471 @param setting: Name of attribute
472 @type setting: string
473 '''
474 name = setting.name
475 value = setting.value
476 if (name[:-1] == 'TargetDisplay' or
477 (name == 'FullScreen' and not style.FullScreen)):
478 self.mag_pb_proxy['target-display-bounds'] = (style.TargetDisplayX,
479 style.TargetDisplayY,
480 style.TargetDisplayW,
481 style.TargetDisplayH)
482 self.zoom_pb_proxy['viewport'] = (0,0,
483 style.TargetDisplayW,
484 style.TargetDisplayH)
485
486 elif name == 'FullScreen' and value:
487 self._setFullScreen()
488 elif name == 'Zoom':
489
490 roi = self._getPos(self.zoom)
491 self.zoom.setMagFactor(value, value)
492
493 self._setPos(self.zoom, roi)
494
495 if self.property_trans_mag.has_key(name):
496 self.mag_pb_proxy[self.property_trans_mag[name]] = value
497 elif self.property_trans_zoom.has_key(name):
498 self.zoom_pb_proxy[self.property_trans_zoom[name]] = value
499
501 '''
502 Proxy class for bonobo.PropertyBag that provides a dictionary interface
503 for the propery bag. In addition it also converts (x, y, w, h) tuples to
504 GNOME.Magnifier.RectBounds and (r, g, b) tuples to 0xrrggbb.
505
506 @ivar _property_bag: Last style object to be applied to output
507 @type _property_bag: bonobo.PropertyBag
508 '''
510 self._property_bag = property_bag
512 return len(self.keys())
514 try:
515 property = self._property_bag.getValue(key)
516 except self._property_bag.NotFound:
517 raise KeyError, key
518 typecode = property.typecode()
519 if typecode.name == 'RectBounds':
520 rb = property.value()
521 return (rb.x1, rb.y1,
522 rb.x2 - rb.x1,
523 rb.y2 - rb.y1)
524 elif typecode.name == 'unsigned_long' and key.endswith('-color'):
525 color_long = property.value()
526 return (color_long >> 16,
527 color_long >> 8 & 0xff,
528 color_long & 0xff)
529 else:
530 return property.value()
532 try:
533 property = self._property_bag.getValue(key)
534 except self._property_bag.NotFound:
535 raise KeyError, key
536 typecode = property.typecode()
537 if typecode.name == 'RectBounds' and isinstance(value, tuple):
538 x, y, w, h = value
539 value = GNOME.Magnifier.RectBounds(x,y,x+w,y+h)
540 elif key.endswith('-color') and isinstance(value, tuple):
541 r, g, b = value
542 value = (r >> 8) << 16
543 value |= (g >> 8) << 8
544 value |= (b >> 8)
545 self._property_bag.setValue(key,ORBit.CORBA.Any(typecode, value))
547 return iter(self.keys())
551 return iter(self.values())
553 return self._property_bag.getKeys('')
555 pairs = self._property_bag.getValues('')
556 return [pair.value.value() for pair in pairs]
557