Index: src/Perks/FirefoxPerk.py
===================================================================
--- src/Perks/FirefoxPerk.py	(revision 728)
+++ src/Perks/FirefoxPerk.py	(working copy)
@@ -162,10 +162,47 @@
     self.registerTask(ConvReviewUserReg('firefox conv keys user reg'))
     
     # Register user triggered move/announcement events
-    self.registerTask(MoveToNextHeader('firefox move next header'))
     self.registerTask(MoveToActiveDoc('firefox move active doc'))
     self.registerTask(ReadDocumentAddress('firefox read document address'))
     
+    # navigation by element
+    self.registerTask(MoveToElement('firefox move next anchor'))
+    self.registerTask(MoveToElement('firefox move next embed'))
+    self.registerTask(MoveToElement('firefox move next button'))
+    self.registerTask(MoveToElement('firefox move next checkbox'))
+    self.registerTask(MoveToElement('firefox move next combobox'))
+    self.registerTask(MoveToElement('firefox move next entry'))
+    self.registerTask(MoveToElement('firefox move next form'))
+    self.registerTask(MoveToElement('firefox move next formcontrol'))
+    self.registerTask(MoveToElement('firefox move next image'))
+    self.registerTask(MoveToElement('firefox move next heading'))
+    self.registerTask(MoveToElement('firefox move next list'))
+    self.registerTask(MoveToElement('firefox move next listitem'))
+    self.registerTask(MoveToElement('firefox move next section'))
+    self.registerTask(MoveToElement('firefox move next radiobutton'))
+    self.registerTask(MoveToElement('firefox move next table'))
+    self.registerTask(MoveToElement('firefox move next unvisitedlink'))
+    self.registerTask(MoveToElement('firefox move next visitedlink'))
+    
+    self.registerTask(MoveToElement('firefox move prev anchor'))
+    self.registerTask(MoveToElement('firefox move prev embed'))
+    self.registerTask(MoveToElement('firefox move prev button'))
+    self.registerTask(MoveToElement('firefox move prev checkbox'))
+    self.registerTask(MoveToElement('firefox move prev combobox'))
+    self.registerTask(MoveToElement('firefox move prev entry'))
+    self.registerTask(MoveToElement('firefox move prev form'))
+    self.registerTask(MoveToElement('firefox move prev formcontrol'))
+    self.registerTask(MoveToElement('firefox move prev image'))
+    self.registerTask(MoveToElement('firefox move prev heading'))
+    self.registerTask(MoveToElement('firefox move prev list'))
+    self.registerTask(MoveToElement('firefox move prev listitem'))
+    self.registerTask(MoveToElement('firefox move prev section'))
+    self.registerTask(MoveToElement('firefox move prev radiobutton'))
+    self.registerTask(MoveToElement('firefox move prev table'))
+    self.registerTask(MoveToElement('firefox move prev unvisitedlink'))
+    self.registerTask(MoveToElement('firefox move prev visitedlink'))
+    
+    
     # Register state watching tasks
     self.registerTask(HandleAutocomplete('firefox say autocomplete', 
                                          tier=True))
@@ -175,18 +212,57 @@
     self.addInputModifiers(kbd, kbd.AEK_CAPS_LOCK)
     self.addInputModifiers(kbd, kbd.AEK_CONTROL_R)
     self.addInputModifiers(kbd, kbd.AEK_CONTROL_L)
+    self.addInputModifiers(kbd, kbd.AEK_SHIFT_R)
+    self.addInputModifiers(kbd, kbd.AEK_SHIFT_L)
     
     # Register keyboard commands for reading document address, move to next
     # header, and move to active doc frame.
     self.registerCommand(kbd, 'firefox read document address', False, 
                          [kbd.AEK_CAPS_LOCK, kbd.AEK_A])
-    self.registerCommand(kbd, 'firefox move next header', False, 
-                         [kbd.AEK_CAPS_LOCK, kbd.AEK_H])
     self.registerCommand(kbd, 'firefox move active doc', False, 
                          [kbd.AEK_CAPS_LOCK, kbd.AEK_HOME])
     self.registerCommand(kbd, 'firefox conv keys user reg', False, 
                          [kbd.AEK_CAPS_LOCK, kbd.AEK_DOWN])
+                         
+    # navigation by element
+    self.registerCommand(kbd, 'firefox move next anchor', False, [kbd.AEK_A])
+    self.registerCommand(kbd, 'firefox move next embed', False, [kbd.AEK_D])
+    self.registerCommand(kbd, 'firefox move next button', False, [kbd.AEK_B])
+    self.registerCommand(kbd, 'firefox move next checkbox', False, [kbd.AEK_X])
+    self.registerCommand(kbd, 'firefox move next combobox', False, [kbd.AEK_C])
+    self.registerCommand(kbd, 'firefox move next entry', False, [kbd.AEK_E])
+    self.registerCommand(kbd, 'firefox move next form', False, [kbd.AEK_F])
+    self.registerCommand(kbd, 'firefox move next formcontrol', False, [kbd.AEK_G])
+    self.registerCommand(kbd, 'firefox move next image', False, [kbd.AEK_I])
+    self.registerCommand(kbd, 'firefox move next heading', False, [kbd.AEK_H])
+    self.registerCommand(kbd, 'firefox move next list', False, [kbd.AEK_L])
+    self.registerCommand(kbd, 'firefox move next listitem', False, [kbd.AEK_SEMICOLON])
+    self.registerCommand(kbd, 'firefox move next section', False, [kbd.AEK_S])
+    self.registerCommand(kbd, 'firefox move next radiobutton', False, [kbd.AEK_R])
+    self.registerCommand(kbd, 'firefox move next table', False, [kbd.AEK_T])
+    self.registerCommand(kbd, 'firefox move next unvisitedlink', False, [kbd.AEK_U])
+    self.registerCommand(kbd, 'firefox move next visitedlink', False, [kbd.AEK_V])
     
+    modifiers = [kbd.AEK_SHIFT_R, kbd.AEK_SHIFT_L]
+    for m in modifiers:
+      self.registerCommand(kbd, 'firefox move prev anchor', False, [m,kbd.AEK_A])
+      self.registerCommand(kbd, 'firefox move prev embed', False, [m,kbd.AEK_D])
+      self.registerCommand(kbd, 'firefox move prev button', False, [m,kbd.AEK_B])
+      self.registerCommand(kbd, 'firefox move prev checkbox', False, [m,kbd.AEK_X])
+      self.registerCommand(kbd, 'firefox move prev combobox', False, [m,kbd.AEK_C])
+      self.registerCommand(kbd, 'firefox move prev entry', False, [m,kbd.AEK_E])
+      self.registerCommand(kbd, 'firefox move prev form', False, [m,kbd.AEK_F])
+      self.registerCommand(kbd, 'firefox move prev formcontrol', False, [m,kbd.AEK_G])
+      self.registerCommand(kbd, 'firefox move prev image', False, [m,kbd.AEK_I])
+      self.registerCommand(kbd, 'firefox move prev heading', False, [m,kbd.AEK_H])
+      self.registerCommand(kbd, 'firefox move prev list', False, [m,kbd.AEK_L])
+      self.registerCommand(kbd, 'firefox move prev listitem', False, [m,kbd.AEK_SEMICOLON])
+      self.registerCommand(kbd, 'firefox move prev section', False, [m,kbd.AEK_S])
+      self.registerCommand(kbd, 'firefox move prev radiobutton', False, [m,kbd.AEK_R])
+      self.registerCommand(kbd, 'firefox move prev table', False, [m,kbd.AEK_T])
+      self.registerCommand(kbd, 'firefox move prev unvisitedlink', False, [m,kbd.AEK_U])
+      self.registerCommand(kbd, 'firefox move prev visitedlink', False, [m,kbd.AEK_V])
+    
     # chain to other tasks.  See Perk docstring for task flowcharts.
     self.chainTask('firefox review role', CHAIN_AROUND, 'read new role')
     self.chainTask('firefox review should skip', CHAIN_AROUND, 
@@ -676,7 +752,7 @@
     role = self.getAccRole(por) 
     if role == 'heading':
       attrs = self.getAccAttrs()
-      if attrs.has_key('level'):
+      if attrs is not None and attrs.has_key('level'):
         # i18n: string is translated word 'heading', int is value 1 through 6
         outtext = _("%s level %s") % (rolename, attrs['level'])
       else:
@@ -732,44 +808,6 @@
       self.setTempVal('should skip', True)
 
     
-class MoveToNextHeader(Task.InputTask):
-  '''
-  Moves pointer to the next header.
-    
-  @param por: Point of regard to test
-  @type por: L{POR}
-  '''
-  def execute(self, por=None, **kwargs):
-    # Search from active doc frame  if user is in chrome
-    if not self.perk.getDocFrame(self.task_por):
-      subtreeroot = self.perk.getActiveDocFrame()
-    else: # otherwise search from current position
-      subtreeroot = self.task_por
-      
-    self.stopNow()
-    for nextpor in self.iterNextItems(wrap=True, por=subtreeroot):
-      try:
-        attrs = self.getAccAttrs(nextpor)
-        # is it a header?  If so, move pointer, announcement is automatic.
-        if attrs.has_key('tag') and len(attrs['tag']) == 2 and attrs['tag'][0] == 'H':
-          # header may be an empty container so look for next item
-          if self.getItemText(nextpor) == '':
-            startpor = nextpor
-            nextpor = self.getNextItem(por=nextpor, wrap=True)
-            # if the next item is empty just revert back to header
-            if self.getItemText(nextpor) == '':
-              nextpor = startpor
-          self.doTask('pointer to por', por=nextpor)
-          # Register convenience review keys
-          self.perk.registerConvKeys()
-          # consider this a review step so we must update restoration por.
-          docframe = self.perk.getActiveDocFrame()
-          self.perk.doc_pors[docframe] = docframe
-          return
-      except ValueError:
-        pass
-    self.sayInfo(text=_('header not found'))
-    
 class MoveToActiveDoc(Task.InputTask):
   '''
   Moves pointer to the active document frame.
@@ -809,3 +847,131 @@
         self.perk.unregisterConvKeys()
         self.doTask('read message', 
                      text=_('arrows review off'))
+
+
+class MoveToElement(Task.InputTask):
+  '''
+  
+    
+  @param por: Point of regard to test
+  @type por: L{POR}
+  '''
+  def execute(self, **kwargs):
+    next_el_name = None
+    pred = None
+    
+    taskid = self.getIdentity()
+    if taskid.endswith('anchor'):
+      pred = self._anchorpred
+      next_el_name = 'link'
+    elif taskid.endswith('embed'):
+      print 'MoveToNextElement embed not implemented'
+    elif taskid.endswith('button'):
+      pred = lambda por: self.hasAccRole('push button', por=por)
+      next_el_name = 'push button'
+    elif taskid.endswith('checkbox'):
+      pred = lambda por: self.hasAccRole('check box', por=por)
+      next_el_name = 'check box'
+    elif taskid.endswith('combobox'):
+      pred = lambda por: self.hasAccRole('combo box', por=por)
+      next_el_name = 'combo box'
+    elif taskid.endswith('entry'):
+      pred = self._entrypred
+      next_el_name = 'entry'
+    elif taskid.endswith('form'):
+      pred = lambda por: self.hasAccRole('form', por=por)
+      next_el_name = 'form'
+    elif taskid.endswith('formcontrol'):
+      print 'MoveToNextElement formcontrol not implemented'
+    elif taskid.endswith('image'):
+      pred = lambda por: self.hasAccRole('image', por=por)
+      next_el_name = 'image'
+    elif taskid.endswith('heading'):
+      pred = lambda por: self.hasAccRole('heading', por=por)
+      next_el_name = 'heading'
+    elif taskid.endswith('list'):
+      pred = lambda por: self.hasAccRole('list', por=por)
+      next_el_name = 'list'
+    elif taskid.endswith('listitem'):
+      pred = self._listitempred
+      if self.getAccRole() == 'menu item':
+        next_el_name = 'menu item'
+      else:
+        next_el_name = 'list item'
+    elif taskid.endswith('section'):
+      pred = lambda por: self.hasAccRole('section', por=por)
+      next_el_name = 'section'
+    elif taskid.endswith('radiobutton'):
+      pred = lambda por: self.hasAccRole('radio button', por=por)
+      next_el_name = 'radio button'
+    elif taskid.endswith('table'):
+      pred = lambda por: self.hasAccRole('table', por=por)
+      next_el_name = 'table'
+    elif taskid.endswith('unvisitedlink'):
+      pred = self._unvisitedlinkpred
+      next_el_name = 'unvisited link'
+    elif taskid.endswith('visitedlink'):
+      pred = self._visitedlinkpred 
+      next_el_name = 'visited link'       
+    else:
+      # output task identity in this error state
+      next_el_name = taskid
+    
+    if pred is None:
+      self.sayInfo(text=_('unrecognized task name %s' %next_el_name))
+    else:
+      if taskid.find('next') > 0:
+        # iterate through tree and apply predicate  
+        for nextpor in self.iterNextItems(wrap=True):
+          if pred(nextpor):
+            # pointer to por will make announcements
+            self.doTask('pointer to por', por=nextpor)
+            return
+      elif taskid.find('prev'):
+        for nextpor in self.iterPrevItems(wrap=True):
+          if pred(nextpor):
+            # pointer to por will make announcements
+            self.doTask('pointer to por', por=nextpor)
+            return
+      else:
+        # todo: is this correct?
+        raise NameError
+      # didn't find it
+      self.sayInfo(text=_('could not find %s' %next_el_name))
+      
+  def _anchorpred(self, por):
+    attrs = self.getAccAttrs(por=por)
+    try:
+      return attrs['tag'] == 'A'
+    except:
+      return False
+  
+  def _entrypred(self, por):
+    attrs = self.getAccAttrs(por=por)
+    try:
+      return attrs['tag'] == 'INPUT'
+    except:
+      return False
+  
+  def _listitempred(self, por):
+    attrs = self.getAccAttrs(por=por)
+    try:
+      return attrs['tag'] == 'OPTION'
+    except:
+      return False
+  
+  def _unvisitedlinkpred(self, por):
+    attrs = self.getAccAttrs(por=por)
+    try:
+      return (attrs['tag'] == 'A'  \
+              and not self.hasOneAccState('visited', por=por)) 
+    except:
+      return False
+  
+  def _visitedlinkpred(self, por):
+    attrs = self.getAccAttrs(por=por)
+    try:
+      return (attrs['tag'] == 'A' \
+              and self.hasOneAccState('visited', por=por)) 
+    except:
+      return False
