'''
Defines a L{Perk} for the GNOME Terminal.

@author: Peter Parente
@author: Brett Clippingdale
@author: Eitan Isaacson
@author: Scott Haeger
@author: Luiz Rocha
@organization: IBM Corporation
@copyright: Copyright (c) 2005, 2007 IBM Corporation
@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}
'''
import Perk, Task
import AEConstants
from POR import POR
from i18n import bind, _ # so far, this is unused

__uie__ = dict(kind='perk', tier='gnome-terminal', all_tiers=False)

class GTerminalPerk(Perk.Perk):
  '''
  Corrects caret problems in in gnome-terminal.

  @ivar actions: Maps all possible 'added' values to strings
  @type actions: dictionary
  @ivar move_keys: 
  @type move_keys: tuple
  @ivar delete_keys: 
  @type delete_keys: tuple
  @ivar ret_key:
  @type ret_key: string
  @ivar last_caret: Stores the previous caret L{POR}
  @type last_caret: L{POR}
  @ivar last_key: Stores the last key pressed for future comparision
  @type last_key: tuple
  '''
  def init(self):
    '''
    Register L{Task}s and chain it around the default L{Task} for caret change
    events. Initialize perk variables.
    '''
    # set an audio device as the default output
    self.setPerkIdealOutput('audio')

    # registering tasks
    self.registerTask(HandleTerminalCaret('gterm caret'))

    # chain to other tasks
    self.chainTask('gterm caret', AEConstants.CHAIN_AROUND, 'read caret')

    # initialize little nice cool perk variables
    self.actions = {None: 'move', False: 'delete', True: 'insert'}
    self.move_keys = ('Left', 'Right')
    self.delete_keys = ('Delete', 'BackSpace')
    self.ret_key = 'Return'
    self.last_caret = POR(item_offset=0)
    self.last_key = None

  def getName(self):
    return _('GNOME Terminal')

  def getDescription(self):
    return _('Improve general accessibility of the GNOME Terminal.')

class HandleTerminalCaret(Task.Task):
  '''
  L{Task} that handles messy L{AEEvent.CaretChange} sent from gnome-terminal.
  This one is chained around the basic event handling L{Task}. The {execute}
  method identifies the event, fix, propagate or ignore it althogether.
  '''
  def execute(self, por, text, text_offset, added, **kwargs):
    '''
    TODO: write a nice description for execute.

    @param por: Point of regard where the caret event occurred
    @type por: L{POR}
    @param text: The text inserted, deleted or the line of the caret
    @type text: string
    @param text_offset: The offset of the inserted/deleted text or the line 
      offset when movement only
    @type text_offset: integer
    @param added: True when text added, False when text deleted, and None 
      (the default) when event is for caret movement only
    @type added: boolean
    '''
    # TODO: write a nice description for execute.
    # TODO: would it be a good idea to refactor this and move the conditional
    #       blocks for each 'added' to separate methods inside this task?
    # TODO: check long cmdlines (call a random prog with a long line of args)
    # TODO: check multiline insertions (using \ at the end of a cmdline) output
    # TODO: check reverse/incremental search (Ctrl+R) output
    # TODO: say the whole word (instead of just the ending) on tab-completion?

    # handling only terminal events
    if not self.hasAccRole('terminal') or None in self.getLastKey():
      return False

    # last key pressed in gterm is our current key
    current_key = self.getLastKey()

    # TODO: remove the 'else' from the action conditional loop and use this as
    #       default condition setting?
    # propagate to chained tasks by default
    # propagate = True

    if self.perk.actions[added] == 'insert':
      # if the current key is a Return, the terminal is probably responding to
      # some user input. the avalanche of events gterm sends might break the
      # speech output. inhibit the next stop to prevent this.
      if current_key[1] == self.perk.ret_key:
        self.inhibitMayStop()
        propagate = True
      elif current_key[1] in self.perk.delete_keys:
        # ignore inserts bundled with valid delete events
        propagate = False
      else:
        # TODO: is there any other insert events to repair? if not, kill this
        propagate = True
    elif self.perk.actions[added] == 'move':
      # ignore "bundled" move events from the terminal. we'll react only when
      # a move key is pressed
      if (not current_key[1] in self.perk.move_keys or
          self.perk.last_key[1] == self.perk.ret_key):     
        # not an actual move event, just garbage. move along.
        # TODO: validate the conditions to ignore move events.
        propagate = False
      else:
        # move key pressed, great! let BasicSpeech handle it
        # pass the terminal last caret along with the event chain
        # TODO: somehow, at the end of a line, the first move to the left do 
        #       not announce the character. all subsequent events goes smoothly
        #       check this out and fix it.
        # TODO: check the output when moving to the right
        propagate = True
    elif self.perk.actions[added] == 'delete':
      # ignore "bundled" delete events received from gterm. react only when a
      # delete (delete/backspace) key was pressed
      if (not current_key[1] in self.perk.delete_keys or
          self.perk.last_key[1] == self.perk.ret_key):
        # TODO: validates the condition to ignore delete events
        # do not propagate
        #
        # not a valid delete event, ignore it
        propagate = False
      else:
        # delete key pressed, let BasicSpeech handle it
        # TODO: after some input, the first backspace is announced as a delete,
        #       only at the end of the word. if a move key was pressed, it 
        #       works correctly. move events shows similar problem. issues 
        #       with the POR?
        #
        # the text param on delete events contains the whole text after the
        # deleted character. trim the text to announce only the deleted char
        text = text[:1]
        propagate = True
    else:
      # propagate and let BasicSpeechPerk handle anything we missed here
      propagate = True

    # if propagate is True, call BasicSpeechPerk.HandleCaretChange task
    if propagate:
      self.doTask('read caret', chain=False, por=por, text=text,
          text_offset=text_offset, added=added)

    # store keyboard input and last caret position for future comparisons
    self.perk.last_key = current_key
    self.perk.last_caret = por # needed?

    return propagate
