'''
This device drives the Gnome Magnifier (magnifier).

@author: Eitan Isaacson <eitan@ascender.com>
@organization: IBM Corporation
@copyright: Copyright (c) 2006 IBM Corp
@license: The BSD License

All rights reserved. This program and the accompanying materials are made 
available under the terms of the BSD license which accompanies
this distribution, and is available at
U{http://www.opensource.org/licenses/bsd-license.php}

Current revision $Revision: 1.9 $
Latest change by $Author: eitan $ on $Date: 2006/12/14 20:57:12 $
'''
import AEOutput
from i18n import _
from AEConstants import CMD_GOTO, CMD_GET_ROI
from gtk.gdk import Display, get_display
import ORBit, bonobo
import pdb

__uie__ = dict(kind='device')

ORBit.load_typelib("GNOME_Magnifier")
import GNOME.Magnifier

class GnomeMagStyle(AEOutput.Style):
  '''
  Overrides the base L{AEOutput.Style.Style} class, filling in 
  fields for supported style properties with their appropriate values.

  FullScreen (bool):Magnify in full screen mode, this only affects magnifiers
  that are in a seperate screen.
  Zoom (float): Magnification factor.
  Invert (bool): Invert the colors displayed by the magnifier
  SmoothingType (enum): Method used to smooth the magnification
  TargetDisplayX (int): Left boundary of magnifier display
  TargetDisplayY (int): Top boundary of magnifier display
  TargetDisplayW (int): Width of magnifier display
  TargetDisplayH (int): Height of magnifier display
  TargetDisplayScreen (string): Screen magnifier should be displayed on.
  TargetDisplayConfigured (bool): If False, resort to magnifier placement defaults.
  CursorScale (float): Scale of cursor in magnified display
  CursorColor (color): Color of cursor in magnified display
  CrosswireSize (int): Size of cursor crosswire in magnified display
  CrosswireColor (color): Color of crosswire in magnified display
  CrosswireClip (bool): Clip crosswire around cursor
  ContrastRed (float): Red contrast of the magnifier
  ContrastGreen (float): Green contrast of the magnifier
  ContrastBlue (float): Blue contrast of the magnifier
  '''
  def init(self, device):
    self.newBool('FullScreen', False, _('Full screen'),
       _('Magnify in full screen mode, this only affects magnifiers that are in a seperate screen.'))

    self.newRange('Zoom', 2, _('Zoom Factor'), 1, 13, 1,
       _('Change the magnification zoom factor.'))

    self.newBool('Invert', False, _('Invert magnifier'),
       _('Invert the colors displayed by the magnifier'))

    self.newEnum('SmoothingType', 'none', _('Smoothing method'),
              {'None':'none','Bilinear':'bilinear-interpolation'},
              _('Change the method used to smooth the magnification'))

    self.newNumeric('TargetDisplayX', 0, _('Left boundary'), 0, 1600, 0,
                 _('Left boundary of magnifier display'))

    self.newNumeric('TargetDisplayY', 0, _('Top boundary'), 0, 1200, 0,
                 _('Top boundary of magnifier display'))

    self.newNumeric('TargetDisplayW', 100, _('Magnifier width'), 0, 1600, 0,
                 _('Width of magnifier display'))

    self.newNumeric('TargetDisplayH', 100, _('Magnifier height'), 0, 1200, 0,
                 _('Height of magnifier display'))

    self.newString('TargetDisplayScreen', '', _('Magnifier screen'),
                 _('Put the magnifier on a separate display'))

    self.newBool('TargetDisplayConfigured', False, 
                 _('Target Display Configured'), 
                 _('Set to True once the target display has been configured'))

    self.newRange('CursorScale', 2, _('Cursor scale'),1, 13, 1,
               _('Scale of cursor in magnified display'))

    self.newColor('CursorColor', (0x0, 0x0, 0x0), _('Cursor color'),
              _('Color of cursor in magnified display'))

    self.newNumeric('CrosswireSize', 1, _('Cursor crosswire size'), 0, 600, 0,
                 _('Size of cursor crosswire in magnified display'))

    self.newColor('CrosswireColor', (0x0, 0x0, 0x0), _('Crosswire color'),
              _('Color of crosswire in magnified display'))

    self.newBool('CrosswireClip', False, _('Clip crosswire'),
              _('Clip crosswire around cursor'))

    self.newRange('ContrastRed', 1, _('Red Contrast'), 0, 50, 1,
                _('Adjust the red contrast of the magnifier'))

    self.newRange('ContrastGreen', 1, _('Green Contrast'), 0, 50, 1,
                _('Adjust the green contrast of the magnifier'))

    self.newRange('ContrastBlue', 1, _('Blue Contrast'), 0, 50, 1,
                _('Adjust the blue contrast of the magnifier'))
    print 'Dono'


  def getGroups(self):
    '''
    Gets configurable settings for magnifier and one zoom region
    
    @return: Group of all configurable settings
    @rtype: L{AEState.Setting.Group}
    '''
    g = self.newGroup()

    d = g.newGroup('Display')    
    d.append('FullScreen')
    d.append('Zoom')
    d.append('Invert')
    d.append('SmoothingType')

    td = g.newGroup('Target Display')
    td.append('TargetDisplayX')
    td.append('TargetDisplayY')
    td.append('TargetDisplayW')
    td.append('TargetDisplayH')
    td.append('TargetDisplayScreen')

    c = g.newGroup('Cursor')
    c.append('CursorScale')
    c.append('CursorColor')
    c.append('CrosswireSize')
    c.append('CrosswireColor')
    c.append('CrosswireClip')

    co = g.newGroup('Contrast')
    co.append('ContrastRed')
    co.append('ContrastGreen')
    co.append('ContrastBlue')

    return g


class GnomeMagDevice(AEOutput.AEOutput):
  '''
  A Gnome Magnifier.
  '''

  STYLE = GnomeMagStyle
  MAGNIFIER_IID   = "OAFIID:GNOME_Magnifier_Magnifier:0.9"
  MAGNIFIER_OBJ   = "GNOME/Magnifier/Magnifier"

  property_trans_zoom = {'Invert': 'inverse-video',
                         'SmoothingType': 'smoothing-type',
                         'ContrastRed': 'red-contrast',
                         'ContrastGreen': 'green-contrast',
                         'ContrastBlue': 'blue-contrast'}
  property_trans_mag = {'TargetDisplayScreen': 'target-display-screen',
                        'CursorScale': 'cursor-scale-factor',
                        'CursorColor': 'cursor-color',
                        'CrosswireSize': 'crosswire-size',
                        'CrosswireColor': 'crosswire-color',
                        'CrosswireClip': 'crosswire-clip'}

  def init(self):
    '''
    Initializes the gnome magnifier.

    @raise AEOutput.InitError: When the device can not be initialized
    '''
    print 'Initialiing magnifier'
    data = bonobo.activation.activate_from_id(self.MAGNIFIER_IID)
    if data is None:
      raise AEOutput.InitError('Could not activate:', self.MAGNIFIER_IID)
    self.mag = bonobo.get_object(self.MAGNIFIER_IID, self.MAGNIFIER_OBJ)
    self.mag_pb_proxy = PropertyBagProxy(self.mag.getProperties())
    print 'done initing'

  def postInit(self):
    '''
    Called after the L{init} method and after either L{loadStyles} or 
    L{createDistinctStyles}. Override this method to perform additional 
    initilization after the setting values are available.
    '''
    self._initMag()
    
    self._getZoom()

    for setting in self.default_style:
      setting.addObserver(self._updateSetting)
      if not setting.name.startswith('TargetDisplay'):
        self._updateSetting(self.default_style, setting)

  def _initMag(self):
    '''
    Set the magnifier object to the correct screen, position, and size.
    '''
    print 'Configuring magnifier'
    style = self.default_style
    if style.TargetDisplayScreen != '':
      self.mag_pb_proxy['target-display-screen'] = style.TargetDisplayScreen
    if style.FullScreen:
      self._setFullScreen(False)
    elif style.TargetDisplayConfigured:
      self.mag_pb_proxy['target-display-bounds'] = (style.TargetDisplayX,
                                                    style.TargetDisplayY,
                                                    style.TargetDisplayW,
                                                    style.TargetDisplayH)
    else:
      # As a default, take up right half of screen
      target_display = get_display()
      w, h = self._getScreenSize(target_display)
      style.TargetDisplayScreen = target_display
      style.TargetDisplayX = w/2
      style.TargetDisplayY = 0
      style.TargetDisplayW = w-w/2
      style.TargetDisplayH = h
      self.mag_pb_proxy['target-display-bounds'] = (style.TargetDisplayX,
                                                    style.TargetDisplayY,
                                                    style.TargetDisplayW,
                                                    style.TargetDisplayH)
      style.TargetDisplayConfigured = True
    print 'magnifier bounds:', (style.TargetDisplayX,
                                style.TargetDisplayY,
                                style.TargetDisplayW,
                                style.TargetDisplayH)
    
  def _getZoom(self):
    '''
    Get magnifier's first zoom region, if it doesn't exist create one.
    Resize viewport to magnifier's target display size.
    '''
    zoom_regions = self.mag.getZoomRegions()

    x, y, w, h = self.mag_pb_proxy['target-display-bounds']
    if len(zoom_regions) > 0:
      print 'getting existing zoom'
      self.zoom = zoom_regions[-1]
      self.zoom_pb_proxy = PropertyBagProxy(self.zoom.getProperties())
      self.zoom.setMagFactor(self.default_style.Zoom,
                             self.default_style.Zoom)
      self.zoom_pb_proxy['viewport'] = (0, 0, w, h)        
    else:
      print 'creating zoom'
      self.zoom = self.mag.createZoomRegion(
        self.default_style.Zoom,
        self.default_style.Zoom,
        GNOME.Magnifier.RectBounds(0,0,w,h),
        GNOME.Magnifier.RectBounds(0,0,w,h))
      self.zoom_pb_proxy = PropertyBagProxy(self.zoom.getProperties())
      self.mag.addZoomRegion(self.zoom)

  def close(self):
    '''
    Stop and close the magnifier device.
    '''
    self.mag.dispose()
    del self.mag

  def getCapabilities(self):
    '''
    @return: 'magnifier' as the only capability of this device.
    @type: list of string
    '''
    return ['magnifier'] 

  def getProxy(self):
    return self

  def send(self, name, value, style=None):
    '''
    Perform given command, or simply apply all dirty style properties.
    '''
    if name is CMD_GOTO:
      self._setPos(self.zoom,value)
    elif name is CMD_GET_ROI:
      return self._getPos(self.zoom)

  def _setFullScreen(self, reset_viewport=True):
    '''
    If source display is not target display, set magnifier to fullscreen.

    @param reset_viewport: Snap zoomer's viewport to new magnifier size.
    @type reset_viewport: boolean
    '''
    source_display = self.mag_pb_proxy['source-display-screen']
    target_display = self.mag_pb_proxy['target-display-screen']
    if target_display == source_display:
      return
    w, h = self._getScreenSize(target_display )
    if None not in (w, h):
      self.mag_pb_proxy['target-display-bounds'] = (0,0,w,h)
    if reset_viewport:
      self.zoom_pb_proxy['viewport'] = (0,0,w,h)

  def _getScreenSize(self, display_name):
    '''
    Get the size of a given screen.
    
    @param display_name: Name of display.
    @type display_name: string
    @return: Width and height of display, or (None, None) if there
    was trouble retrieving display size.
    @rtype: tuple
    '''
    try:
      display = Display(display_name)
    except RuntimeError:
      return None, None
    screen = display.get_default_screen()
    w, h = screen.get_width(), screen.get_height()
    return w,h

  def _isDisplay(self, display_name):
    '''
    Checks if given screen exists
    
    @param display_name: Name of display.
    @type display_name: string
    @return: True if screen exists, False if not.
    @rtype: boolean
    '''
    try:
      display = Display(display_name)
    except RuntimeError:
      return False
    return True

  def _setPos(self, zoom, roi_tuple):
    '''
    Set ROI of zoomer.
    
    @param zoom: Zoomer to adjust.
    @type zoom: GNOME.Magnifier.ZoomRegion
    @param pos: position in (x, y)
    @type pos: tuple
    '''
    self._roi = GNOME.Magnifier.RectBounds (*roi_tuple)
    try:
      zoom.setROI(self._roi)
    except:
      pass

  def _getPos(self, zoom):
    '''
    Get ROI of zoomer.
    
    @param zoom: Zoomer to adjust.
    @type zoom: GNOME.Magnifier.ZoomRegion
    @return: Position in (x, y)
    @rtype: tuple
    '''
    try:
      roi = zoom.getROI()
    except:
      return None
    x_zoom, y_zoom = self.zoom.getMagFactor()
    x,y,w,h = self.zoom_pb_proxy['viewport']
    x_center = (roi.x1 + roi.x2)/2
    y_center = (roi.y1 + roi.y2)/2
    region_width = w/x_zoom
    region_height = h/y_zoom
    rv = (x_center - region_width/2, y_center - region_height/2,
          x_center + region_width/2, y_center + region_height/2)
    return tuple(map(int,map(round,rv)))
            
  def _updateSetting(self, style, setting):
    '''
    Apply style attribute to magnifier or zoomer.
    @param style: Style object ot retrieve attribute from
    @type style: L{AEOutput.Style}
    @param name: Name of attribute
    @type style: string
    '''
    name = setting.name
    value = setting.value
    if name[:-1] == 'TargetDisplay' or (name == 'FullScreen' and not style.FullScreen):
      self.mag_pb_proxy['target-display-bounds'] = (style.TargetDisplayX,
                                                    style.TargetDisplayY,
                                                    style.TargetDisplayW,
                                                    style.TargetDisplayH)
      self.zoom_pb_proxy['viewport'] = (0,0,
                                        style.TargetDisplayW,
                                        style.TargetDisplayH)

    elif name == 'FullScreen' and value:
      self._setFullScreen()
    elif name == 'Zoom':
      self.zoom.setMagFactor(value, value)

    if self.property_trans_mag.has_key(name):
      self.mag_pb_proxy[self.property_trans_mag[name]] = value
    elif self.property_trans_zoom.has_key(name):
      self.zoom_pb_proxy[self.property_trans_zoom[name]] = value

class PropertyBagProxy(object):
  '''
  Proxy class for bonobo.PropertyBag that provides a dictionary interface
  for the propery bag. In addition it also converts (x, y, w, h) tuples to
  GNOME.Magnifier.RectBounds and (r, g, b) tuples to 0xrrggbb.

  @ivar _property_bag: Last style object to be applied to output
  @type _property_bag: bonobo.PropertyBag
  '''
  def __init__(self, property_bag):
    self._property_bag = property_bag
  def __len__(self):
    return len(self.keys())
  def __getitem__(self,key):
    try:
      property = self._property_bag.getValue(key)
    except self._property_bag.NotFound:
      raise KeyError, key
    typecode = property.typecode()
    if typecode.name == 'RectBounds':
      rb = property.value()
      return (rb.x1, rb.y1,
              rb.x2 - rb.x1,
              rb.y2 - rb.y1)
    elif typecode.name == 'unsigned_long' and key.endswith('-color'):
      color_long = property.value()
      return (color_long >> 16,
              color_long >> 8 & 0xff,
              color_long & 0xff)
    else:
      return property.value()
  def __setitem__(self,key,value):
    try:
      property = self._property_bag.getValue(key)
    except self._property_bag.NotFound:
      raise KeyError, key
    typecode = property.typecode()
    if typecode.name == 'RectBounds' and isinstance(value, tuple):
      x, y, w, h = value
      value = GNOME.Magnifier.RectBounds(x,y,x+w,y+h)
    elif key.endswith('-color') and isinstance(value, tuple):
      r, g, b = value
      value = (r >> 8) << 16
      value |= (g >> 8) << 8
      value |= (b >> 8)
    self._property_bag.setValue(key,ORBit.CORBA.Any(typecode, value))
  def __iter__(self):
    return iter(self.keys())
  def iterkeys(self):
    return self.__iter__()
  def itervalues(self):
    return iter(self.values())
  def keys(self):
    return self._property_bag.getKeys('')
  def values(self):
    pairs = self._property_bag.getValues('')
    return [pair.value.value() for pair in pairs]
