Module GnomeMagDevice
[hide private]
[frames] | no frames]

Source Code for Module GnomeMagDevice

  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   
30 -class GnomeMagStyle(AEOutput.Style):
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 # private setting used to store the gnome mag version number to correct for 132 # differences in color contrast ranges 133 self.newString('MagVersion', '', '')
134
135 - def getGroups(self):
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
171 -class GnomeMagDevice(AEOutput.AEOutput):
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
201 - def init(self):
202 ''' 203 Initializes the gnome magnifier. 204 205 @raise AEOutput.InitError: When the device can not be initialized 206 ''' 207 try: 208 # try to get a reference to a magnifier object 209 self.mag = bonobo.get_object(self.MAGNIFIER_IID, self.MAGNIFIER_OBJ) 210 except Exception: 211 # can't initialize, so don't do anything else with this device 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
233 - def _correctContrast(self, style):
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 # use a bug in 0.13.x versions of the magnifier to detect what the range 243 # of the color saturation values is 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 # set the current version string 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 # get version SUE last ran on 259 v = style.MagVersion 260 261 if not v: 262 # no prior version stored yet, so set for the proper version 263 if cv == old: 264 self._setContrastValues(style, 0, 1, lambda x: 1) 265 # do nothing for new, it's our default from above 266 elif v == old: 267 if cv == new: 268 # version mismatch, went from 0.13 to 0.14 269 self._setContrastValues(style, -1, 1, lambda x: x - 1) 270 elif v == new: 271 if cv == old: 272 # version mismatch, went from 0.14 to 0.13 273 self._setContrastValues(style, 0, 1, lambda x: min(x,0)+1) 274 275 # store the version we detected 276 style.MagVersion = cv
277
278 - def _setContrastValues(self, style, min, max, convert):
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
297 - def _initMag(self, style):
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 # As a default, take up right half of screen 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
328 - def _getZoom(self):
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
351 - def close(self):
352 ''' 353 Stop and close the magnifier device. 354 ''' 355 self.mag.dispose() 356 del self.mag
357
358 - def getCapabilities(self):
359 ''' 360 @return: 'magnifier' as the only capability of this device. 361 @rtype: list of string 362 ''' 363 return ['magnifier']
364
365 - def getProxy(self):
366 return self
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
377 - def _setFullScreen(self, reset_viewport=True):
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
394 - def _getScreenSize(self, display_name):
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
412 - def _isDisplay(self, display_name):
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
442 - def _getPos(self, zoom):
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
465 - def _updateSetting(self, style, setting):
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 # store original coordinates 490 roi = self._getPos(self.zoom) 491 self.zoom.setMagFactor(value, value) 492 # correct for new coordinates 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
500 -class PropertyBagProxy(object):
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 '''
509 - def __init__(self, property_bag):
510 self._property_bag = property_bag
511 - def __len__(self):
512 return len(self.keys())
513 - def __getitem__(self,key):
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()
531 - def __setitem__(self,key,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))
546 - def __iter__(self):
547 return iter(self.keys())
548 - def iterkeys(self):
549 return self.__iter__()
550 - def itervalues(self):
551 return iter(self.values())
552 - def keys(self):
553 return self._property_bag.getKeys('')
554 - def values(self):
555 pairs = self._property_bag.getValues('') 556 return [pair.value.value() for pair in pairs]
557