accerciser r413 - in trunk: . plugindata plugindata/validate plugins



Author: parente
Date: Fri Jan 11 01:46:08 2008
New Revision: 413
URL: http://svn.gnome.org/viewvc/accerciser?rev=413&view=rev

Log:
2008-01-10  Peter Parente  <parente cs unc edu>

	* plugins/validate.py
	* plugins/validate.glade
	* plugindata/validate/basic.py: Old code for validate plugin



Added:
   trunk/plugindata/
   trunk/plugindata/validate/
   trunk/plugindata/validate/basic.py
   trunk/plugins/validate.glade
   trunk/plugins/validate.py
Modified:
   trunk/ChangeLog

Added: trunk/plugindata/validate/basic.py
==============================================================================
--- (empty file)
+++ trunk/plugindata/validate/basic.py	Fri Jan 11 01:46:08 2008
@@ -0,0 +1,277 @@
+from accerciser.i18n import _
+from pyatspi import *
+from pyatspi.constants import *
+from validate import Validator
+import random
+
+__metadata__ = {
+  'name': _('Basic'),
+  'description': _('Tests fundamental GUI application accessibility')}
+
+class ActionIsInteractive(Validator):
+  URL = 'http://live.gnome.org/Accerciser/Validate#1'
+  def condition(self, acc):
+    return acc.queryAction()
+
+  def before(self, acc, state, view):
+    s = acc.getState()
+    if (not (s.contains(STATE_FOCUSABLE) or
+             s.contains(STATE_SELECTABLE))):
+      view.error(_('actionable %s is not focusable or selectable') %
+                 acc.getLocalizedRoleName(), acc, self.URL)
+
+class WidgetHasAction(Validator):
+  URL = 'http://live.gnome.org/Accerciser/Validate#2'
+  def condition(self, acc):
+    return acc.getRole() in [ROLE_PUSH_BUTTON, ROLE_MENU, ROLE_MENU_ITEM,
+                             ROLE_CHECK_MENU_ITEM, ROLE_RADIO_MENU_ITEM,
+                             ROLE_TOGGLE_BUTTON, ROLE_RADIO_BUTTON]
+
+  def before(self, acc, state, view):
+    try:
+      acc.queryAction()
+    except NotImplementedError:
+      view.error(_('interactive %s is not actionable') %
+                 acc.getLocalizedRoleName(), acc, self.URL)
+
+class OneFocus(Validator):  
+  def before(self, acc, state, view):
+    s = acc.getState()
+    if s.contains(STATE_FOCUSED):
+      if not state.has_key('focus'):
+        state['focus'] = acc
+      else:
+        view.error(_('more than one focused widget'), acc)
+
+class WidgetHasText(Validator):
+  def condition(self, acc):
+    return acc.getRole() in [ROLE_PUSH_BUTTON, ROLE_MENU, ROLE_MENU_ITEM,
+                             ROLE_CHECK_MENU_ITEM, ROLE_RADIO_MENU_ITEM,
+                             ROLE_TOGGLE_BUTTON, ROLE_STATUS_BAR,
+                             ROLE_TABLE_COLUMN_HEADER,
+                             ROLE_TABLE_ROW_HEADER, ROLE_SPIN_BUTTON,
+                             ROLE_SLIDER, ROLE_ROW_HEADER, ROLE_COLUMN_HEADER,
+                             ROLE_RADIO_BUTTON, ROLE_PASSWORD_TEXT,
+                             ROLE_TEXT, ROLE_ENTRY, ROLE_PARAGRAPH,
+                             ROLE_PAGE_TAB, ROLE_LIST_ITEM, ROLE_LINK,
+                             ROLE_HEADING, ROLE_HEADER,
+                             ROLE_FOOTER, ROLE_CHECK_BOX, ROLE_CAPTION,
+                             ROLE_TERMINAL]
+
+  def before(self, acc, state, view):
+    try:
+      acc.queryText()
+    except NotImplementedError:
+      view.error(_('%s has no text interface') % acc.getLocalizedRoleName(), acc)
+
+class ParentChildIndexMatch(Validator):
+  def condition(self, acc):
+    # don't test applications
+    acc.queryApplication()
+    return False
+  
+  def before(self, acc, state, view):
+    pi = acc.getIndexInParent()
+    child = acc.parent.getChildAtIndex(pi)
+    if acc != child:
+      view.error(_('%s index in parent does not match child index') %
+                 acc.getLocalizedRoleName(), acc)
+
+class ReciprocalRelations(Validator):
+  REL_MAP = {RELATION_LABEL_FOR : RELATION_LABELLED_BY,
+             RELATION_CONTROLLER_FOR : RELATION_CONTROLLED_BY,
+             RELATION_MEMBER_OF : RELATION_MEMBER_OF,
+             RELATION_FLOWS_TO : RELATION_FLOWS_FROM,
+             RELATION_EMBEDS : RELATION_EMBEDDED_BY,
+             RELATION_POPUP_FOR : RELATION_PARENT_WINDOW_OF,
+             RELATION_DESCRIPTION_FOR : RELATION_DESCRIBED_BY}
+  
+  def condition(self, acc):
+    s = acc.getRelationSet()
+    return len(s) > 0
+
+  def _getReciprocal(self, kind):
+    return self.REL_MAP.get(kind)
+
+  def _hasRelationTarget(self, s, kind, acc):
+    if kind is None:
+      return True
+    
+    for rel in s:
+      rec = rel.getRelationType()
+      if kind != rec:
+        continue
+      for i in xrange(rel.getNTargets()):
+        if rel.getTarget(i) == acc:
+          return True
+    return False
+
+  def before(self, acc, state, view):
+    s = acc.getRelationSet()
+    for rel in s:
+      kind = rel.getRelationType()
+      for i in xrange(rel.getNTargets()):
+        target = rel.getTarget(i)
+        ts = target.getRelationSet()
+        rec = self._getReciprocal(kind)
+        if not self._hasRelationTarget(ts, rec, acc):
+          view.error(_('Missing reciprocal for %s relation') %
+                     rel.getRelationTypeName(), acc)
+    
+class HasLabelName(Validator):
+  URL = 'http://live.gnome.org/Accerciser/Validate#4'
+  TEXT_CANNOT_LABEL = [ROLE_SPIN_BUTTON, ROLE_SLIDER, ROLE_PASSWORD_TEXT,
+                       ROLE_TEXT, ROLE_ENTRY, ROLE_TERMINAL]
+                   
+  TEXT_CAN_LABEL = [ROLE_PUSH_BUTTON, ROLE_MENU, ROLE_MENU_ITEM,
+                    ROLE_CHECK_MENU_ITEM, ROLE_RADIO_MENU_ITEM,
+                    ROLE_TOGGLE_BUTTON, ROLE_TABLE_COLUMN_HEADER,
+                    ROLE_TABLE_ROW_HEADER, ROLE_ROW_HEADER,
+                    ROLE_COLUMN_HEADER, ROLE_RADIO_BUTTON, ROLE_PAGE_TAB,
+                    ROLE_LIST_ITEM, ROLE_LINK, ROLE_LABEL, ROLE_HEADING,
+                    ROLE_HEADER, ROLE_FOOTER, ROLE_CHECK_BOX, ROLE_CAPTION,
+                    ]
+
+  def condition(self, acc):
+    return acc.getRole() in (self.TEXT_CANNOT_LABEL + self.TEXT_CAN_LABEL)
+
+  def _checkForReadable(self, acc):
+    if acc.name and acc.name.strip():
+      return True
+    if acc in self.TEXT_CAN_LABEL:
+      try:
+        t = acc.queryText()
+      except NotImplementedError:
+        return False
+      if t.getText(0, -1).strip():
+        return True
+    return False
+
+  def before(self, acc, state, view):
+    if self._checkForReadable(acc):
+      return
+    for rel in acc.getRelationSet():
+      if rel.getRelationType() != RELATION_LABELLED_BY:
+        continue
+      for i in xrange(rel.getNTargets()):
+        target = rel.getTarget(i)
+        if self._checkForReadable(target):
+          return
+    view.error(_('%s missing name or label') % acc.getLocalizedRoleName(), acc,
+               self.URL)
+    
+class TableHasSelection(Validator):
+  def condition(self, acc):
+    acc.queryTable()
+    return acc.getState().contains(STATE_FOCUSABLE)
+
+  def before(self, acc, state, view):
+    try:
+      acc.querySelection()
+    except NotImplementedError:
+      view.error(_('focusable %s has table interface, no selection interface') %
+                 acc.getLocalizedRoleName(), acc)
+                 
+class StateWithAbility(Validator):
+  STATE_MAP = {STATE_EXPANDED : STATE_EXPANDABLE,
+               STATE_COLLAPSED : STATE_EXPANDABLE,
+               STATE_FOCUSED : STATE_FOCUSABLE,
+               STATE_SELECTED: STATE_SELECTABLE}
+  def condition(self, acc):
+    ss = acc.getState()
+    for s in self.STATE_MAP:
+      if ss.contains(s):
+        self.test_state = s
+        return True
+
+  def before(self, acc, state, view):
+    ss = acc.getState()
+    able_state = self.STATE_MAP[self.test_state]
+    if not ss.contains(able_state):
+      view.error(_('%s has %s state without %s state') % (
+        acc.getLocalizedRoleName(),
+        stateToString(self.test_state),
+        stateToString(able_state)), acc)
+
+class RadioInSet(Validator):
+  def condition(self, acc):
+    return self.getRole() in [ROLE_RADIO_BUTTON, ROLE_RADIO_MENU_ITEM]
+
+  def before(self, acc, state, view):
+    attrs = acc.getAttributes()
+    m = dict([attr.split(':', 1) for attr in attrs])
+    if m.has_key('posinset'):
+      return
+    rels = acc.getRelationSet()
+    for rel in rels:
+      if rel.getRelationType() == RELATION_MEMBER_OF:
+        return
+    view.error(_('%s does not belong to a set') % acc.getLocalizedRoleName(),
+               acc)
+
+def _randomRowCol(table):
+  rows, cols = table.nRows, table.nColumns
+  r = random.randint(0, rows-1)
+  c = random.randint(0, cols-1)
+  return r, c
+    
+class TableRowColIndex(Validator):
+  MAX_SAMPLES = 100
+  def condition(self, acc):
+    t = acc.queryTable()
+    # must not be empty to test
+    return (t.nRows and t.nColumns)
+
+  def before(self, acc, state, view):
+    t = acc.queryTable()
+    samples = max(t.nRows * t.nColumns, self.MAX_SAMPLES)
+    for i in xrange(samples):
+      r, c = _randomRowCol(t)
+      i = t.getIndexAt(r, c)
+      ir = t.getRowAtIndex(i)
+      ic = t.getColumnAtIndex(i)
+      if r != ir or c != ic:
+        view.error(_('%s index %d does not match row and column') %
+                   (acc.getLocalizedRoleName(), i), acc)
+        return
+
+class TableRowColParentIndex(Validator):
+  MAX_SAMPLES = 100
+  def condition(self, acc):
+    t = acc.queryTable()
+    # must not be empty to test
+    return (t.nRows and t.nColumns)
+
+  def before(self, acc, state, view):
+    t = acc.queryTable()
+    samples = max(t.nRows * t.nColumns, self.MAX_SAMPLES)
+    for i in xrange(samples):
+      r, c = _randomRowCol(t)
+      child = t.getAccessibleAt(r, c)
+      ip = child.getIndexInParent()
+      i = t.getIndexAt(r, c)
+      if i != ip:
+        view.error(_('%s parent index %d does not match row and column index %d') %
+                   (acc.getLocalizedRoleName(), ip, i), acc)
+        return
+
+class ImageHasName(Validator):
+  def condition(self, acc):
+    if acc.getRole() in [ROLE_DESKTOP_ICON, ROLE_ICON, ROLE_ANIMATION,
+                         ROLE_IMAGE]:
+      return True
+    acc.queryImage()
+    return True
+
+  def before(self, acc, state, view):
+    if ((acc.name and acc.name.strip()) or
+        (acc.description and acc.description.strip())):
+      return
+    ni = False
+    try:
+      im = acc.queryImage()
+    except NotImplementedError:
+      ni = True
+    if ni or im.imageDescription is None or not im.imageDescription.strip():
+      view.error(_('%s has no name or description') % 
+                 acc.getLocalizedRoleName(), acc)

Added: trunk/plugins/validate.glade
==============================================================================
--- (empty file)
+++ trunk/plugins/validate.glade	Fri Jan 11 01:46:08 2008
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.4.0 on Wed Jan  9 21:49:47 2008 -->
+<glade-interface>
+  <widget class="GtkWindow" id="window1">
+    <child>
+      <widget class="GtkVBox" id="main vbox">
+        <property name="visible">True</property>
+        <property name="border_width">5</property>
+        <property name="spacing">5</property>
+        <child>
+          <widget class="GtkScrolledWindow" id="scrolledwindow1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+            <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+            <child>
+              <widget class="GtkTreeView" id="report table">
+                <property name="visible">True</property>
+                <property name="headers_clickable">True</property>
+                <property name="reorderable">True</property>
+                <property name="rules_hint">True</property>
+                <property name="search_column">1</property>
+                <property name="show_expanders">False</property>
+                <property name="enable_grid_lines">GTK_TREE_VIEW_GRID_LINES_BOTH</property>
+                <signal name="row_activated" handler="_onActivateRow"/>
+                <signal name="cursor_changed" handler="_onCursorChanged"/>
+              </widget>
+            </child>
+          </widget>
+        </child>
+        <child>
+          <widget class="GtkHBox" id="hbox1">
+            <property name="visible">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+            <property name="spacing">10</property>
+            <child>
+              <widget class="GtkLabel" id="label1">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">Sche_ma:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">schema combo</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkComboBox" id="schema combo">
+                <property name="visible">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkButton" id="save button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">gtk-save-as</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">0</property>
+                <signal name="clicked" handler="_onSaveAs"/>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkButton" id="help button">
+                <property name="visible">True</property>
+                <property name="sensitive">False</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">gtk-help</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">0</property>
+                <signal name="clicked" handler="_onHelp"/>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkToggleButton" id="validate button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="response_id">0</property>
+                <signal name="toggled" handler="_onValidate"/>
+                <child>
+                  <widget class="GtkHBox" id="hbox2">
+                    <property name="visible">True</property>
+                    <property name="spacing">5</property>
+                    <child>
+                      <widget class="GtkImage" id="image1">
+                        <property name="visible">True</property>
+                        <property name="stock">gtk-apply</property>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkLabel" id="button label">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">V_alidate</property>
+                        <property name="use_underline">True</property>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">4</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <widget class="GtkProgressBar" id="progress bar">
+            <property name="visible">True</property>
+            <property name="activity_mode">True</property>
+            <property name="show_text">True</property>
+            <property name="pulse_step">0.0099999997764825821</property>
+            <property name="text" translatable="yes">Idle</property>
+            <property name="discrete_blocks">50</property>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>

Added: trunk/plugins/validate.py
==============================================================================
--- (empty file)
+++ trunk/plugins/validate.py	Fri Jan 11 01:46:08 2008
@@ -0,0 +1,439 @@
+'''
+AT-SPI validation plugin.
+
+ author: Peter Parente
+ organization: IBM Corporation
+ copyright: Copyright (c) 2007 IBM Corporation
+ license: BSD
+
+All rights reserved. This program and the accompanying materials are made 
+available under the terms of the BSD which accompanies this distribution, and 
+is available at U{http://www.opensource.org/licenses/bsd-license.php}
+'''
+import gtk
+import gobject
+import os
+import traceback
+import sys
+import glob
+import imp
+import webbrowser
+from accerciser.plugin import ViewportPlugin
+from accerciser.i18n import _, N_
+import pyatspi
+
+GLADE_FILE = os.path.join(os.path.dirname(__file__), 'validate.glade')
+USER_SCHEMA_PATH = os.path.join(os.environ['HOME'], '.accerciser', 
+                                'plugindata', 'validate')
+SYS_SCHEMA_PATH = os.path.join(sys.prefix, 'accerciser', 
+                               'plugindata', 'validate')
+VALIDATORS = {}
+SCHEMA_METADATA = {}
+
+class ValidatorManager(type):
+  '''
+  Metaclass that tracks all validator subclasses imported by the plug-in.
+  Used to update a list of validator schemas at runtime.
+  '''
+  def __init__(cls, name, bases, dct):
+    '''
+    Build the class as usual, but keep track of its name and one instance.
+    '''
+    super(ValidatorManager, cls).__init__(name, bases, dct)
+    if name != 'Validator':
+      # don't count the base class
+      names = cls.__module__.split('.')
+      VALIDATORS.setdefault(names[-1], []).append(cls())
+
+  @staticmethod
+  def loadSchemas():
+    '''
+    Loads all schema files from well known locations.
+    '''
+    for path in [USER_SCHEMA_PATH, SYS_SCHEMA_PATH]:
+      for fn in glob.glob(os.path.join(path, '*.py')):
+        module = os.path.basename(fn)[:-3]
+        params = imp.find_module(module, [path])
+        schema = imp.load_module(module, *params)
+        try:
+          # try to get descriptive fields from the module
+          SCHEMA_METADATA[module] = schema.__metadata__
+        except AttributeError:
+          # default to usinf file name as description
+          SCHEMA_METADATA[module] = {'name' : module,
+                                        'description' : _('No description')}
+      
+  @staticmethod
+  def getValidators(name):
+    '''
+    Gets all validator classes within a schema.
+    
+    @param name: Name of a schema
+    @return: List of Validator objects
+    @raise KeyError: When the schema is not known
+    '''
+    return VALIDATORS[name]
+
+  @staticmethod
+  def listSchemas():
+    '''
+    Gets a list of all available schema names.
+
+    @return: List of string names
+    '''
+    return VALIDATORS.keys()
+
+  @staticmethod
+  def getSchemaMetadata(name):
+    '''
+    Gets information about a schema.
+
+    @param name: Name of the schema
+    @return: Dictionary of 'name', 'description', etc.
+    '''
+    return SCHEMA_METADATA[name]
+
+class Validator(object):
+  '''
+  Base class for all validators. Defines the public interface used by the 
+  plug-in controller/view to generate validation reports.
+  '''
+  __metaclass__ = ValidatorManager
+  def __init__(self):
+    pass
+  
+  def condition(self, acc):
+    '''
+    Checks if this validator is fit to test the given accessible. For instance,
+    a test for table properties is not applicable to buttons.
+
+    @param acc: Accessible to test
+    @return: True to validate, False to avoid testing
+    @raise Exception: Same as returning False
+    '''
+    return True
+  
+  def before(self, acc, state, view):
+    '''
+    Tests the accessible before testing its descendants.
+
+    @param acc: Accessible to test
+    @param state: Dictionary containing arbitrary state that will be passed
+      among validators and be made available in both before and after passes
+    @param view: View object to use to log test results
+    '''
+    pass
+  
+  def after(self, acc, state, view):
+    '''
+    Tests the accessible after testing its descendants.
+
+    @param acc: Accessible to test
+    @param state: Dictionary containing arbitrary state that will be passed
+      among validators and be made available in both before and after passes
+    @param view: View object to use to log test results
+    '''
+    pass
+
+class ValidatorViewport(ViewportPlugin):
+  '''
+  Validator UI. Key feature is a table showing the results of a validation
+  run on some accessible and its descendants.
+
+  @ivar main_xml: glade parsed XML definition
+  @ivar report: Report table
+  @ivar progress: Activity bar
+  @ivar validate: Validation button
+  @ivar save: Save report button
+  @ivar schema: Schema combobox
+  @ivar vals: All validators for the selected schema
+  @ivar walk: Generator for the current validation walk through the hierarchy
+  '''
+  plugin_name = N_('AT-SPI Validator')
+  plugin_description = N_('Validates application accessibility')
+  
+  def init(self):
+    '''
+    Loads the glade UI definition and initializes it.
+    '''
+    # load all schemas
+    ValidatorManager.loadSchemas()
+
+    # validators and walk generator
+    self.vals = None
+    self.walk = None
+    # help url for last selected
+    self.url = None
+
+    self.main_xml = gtk.glade.XML(GLADE_FILE, 'main vbox')
+    frame = self.main_xml.get_widget('main vbox')
+    self.plugin_area.add(frame)
+    self.report = self.main_xml.get_widget('report table')
+    self.progress = self.main_xml.get_widget('progress bar')
+    self.validate = self.main_xml.get_widget('validate button')
+    self.save = self.main_xml.get_widget('save button')
+    self.help = self.main_xml.get_widget('help button')
+    self.schema = self.main_xml.get_widget('schema combo')
+
+    # model for the combobox
+    model = gtk.ListStore(gobject.TYPE_STRING)
+    self.schema.set_model(model)
+
+    # append all schema names/descriptions
+    vm = ValidatorManager
+    for d in [vm.getSchemaMetadata(name) for name in vm.listSchemas()]:
+      model.append(['%s - %s' % (d['name'], d['description'])])
+    self.schema.set_active(0)
+    
+    # model for the report
+    model = gtk.ListStore(str, str, object, str)
+    self.report.set_model(model)
+
+    # schema cell renderer
+    cell = gtk.CellRendererText()
+    self.schema.pack_start(cell, True)
+    self.schema.add_attribute(cell, 'text', 0)
+    
+    # log level column
+    col = gtk.TreeViewColumn(_('Level'))
+    rend = gtk.CellRendererText()
+    col.pack_start(rend, True)
+    col.set_attributes(rend, text=0)
+    self.report.append_column(col)
+    # description column
+    rend = gtk.CellRendererText()
+    col = gtk.TreeViewColumn(_('Description'), rend, text=1)
+    self.report.append_column(col)
+
+    # set progress bar to zero initially
+    self.progress.set_fraction(0.0)
+        
+    self.main_xml.signal_autoconnect(self)
+    self.show_all()
+    
+  def onAccChanged(self, acc):
+    '''
+    Stops validation if the accessible hierarchy changes.
+
+    @param acc: The accessible that changed
+    '''
+    if self.walk is not None:
+      self._stopValidate()
+    
+  def _onValidate(self, widget):
+    '''
+    Starts or stops a validation run.
+
+    @param widget: The validate button
+    '''
+    if widget.get_active():
+      self._startValidate()
+    else:
+      self._stopValidate()
+
+  def _onSaveAs(self, button):
+    pass
+
+  def _onHelp(self, button):
+    '''
+    Open a help URL in the system web browser.
+
+    @param button: Help button
+    '''
+    webbrowser.open(self.url)
+
+  def _startValidate(self):
+    '''
+    Starts a validation by settting up an idle callback after initializing the
+    report table and progress bar. Gets all validators for the selected schema.
+    '''
+    # clear the report
+    self.report.get_model().clear()
+    # get the validators
+    self.vals = ValidatorManager.getValidators('basic')
+    # build a new state dict
+    state = {}
+    # build our walk generator
+    self.walk = self._traverse(self.acc, state)
+    # register an idle callback
+    self.idle_id = gobject.idle_add(self._onIdle)
+    self.progress.set_text(_('Validating'))
+    # disable controls
+    self.schema.set_sensitive(False)
+    self.save.set_sensitive(False)
+    self.help.set_sensitive(False)
+
+  def _stopValidate(self):
+    '''
+    Stops a validation run by disabling the idle callback and restoring the
+    various UI components to their enabled states.
+    '''
+    # stop callbacks
+    gobject.source_remove(self.idle_id)
+    # destroy generator
+    self.walk = None
+    # reset progress
+    self.progress.set_fraction(0.0)
+    self.progress.set_text(_('Idle'))
+    # depress validate
+    self.validate.set_active(False)
+    # enable other controls
+    self.schema.set_sensitive(True)
+    self.save.set_sensitive(True)
+     
+  def _onIdle(self):
+    '''
+    Tests one accessible at a time on each idle callback by advancing the
+    walk generator.
+    '''
+    try:
+      # generate the next accessible to validate
+      self.walk.next()
+    except StopIteration:
+      # nothing left to validate, so stop
+      self._stopValidate()
+      return False
+    # pule the progress bar
+    self.progress.pulse()
+    # yes, we want more callbacks
+    return True
+
+  def _traverse(self, acc, state):
+    '''
+    Generates accessibles in a two-pass traversal of the subtree rooted at
+    the accessible selected at the time the validation starts. Accessibles are
+    tested first before their descendants (before pass) and then after all of
+    their descendants (after pass).
+
+    @param acc: Accessible root of some subtree in the walk
+    @param state: State object used by validators to share information
+    '''
+    # start the walk generator
+    gen_child = self._genAccessible(acc, state)
+    while 1:
+      try:
+        # get one child
+        child = gen_child.next()
+      except StopIteration, e:
+        break
+      # recurse
+      gen_traverse = self._traverse(child, state)
+      while 1:
+        # yield before continuing processing 
+        yield None
+        try:
+          # get one descendant
+          gen_traverse.next()
+        except StopIteration:
+          break
+    
+  def _genAccessible(self, acc, state):
+    '''
+    Tests the given accessible in the before pass if its test condition is
+    satisfied. Then generates all of its children. Finally, tests the original
+    accessible in the after pass if its test condition is satisfied.
+
+    @param acc: Accessible to test
+    @param state: State object used by validators to share information
+    '''
+    # run before() methods on all validators
+    self._runValidators(acc, state, True)
+    # generate all children, but only if acc doesn't manage descendants
+    if not acc.getState().contains(pyatspi.constants.STATE_MANAGES_DESCENDANTS):
+      for i in xrange(acc.childCount):
+        child = acc.getChildAtIndex(i)
+        yield child
+    # run after methods on all validators
+    self._runValidators(acc, state, False)
+
+  def _runValidators(self, acc, state, before):
+    '''
+    Runs all validators on a single accessible. If the validator condition is
+    true, either executes the before() or after() method on the validator
+    depending on the param 'before' passed to this method.
+
+    @param acc: Accessible to test
+    @param state: State object used by validators to share information
+    @param before: True to execute before method, false to execute after
+    '''
+    for val in self.vals:
+      try:
+        ok = val.condition(acc)
+      except Exception:
+        pass
+      else:
+        if ok:
+          try:
+            if before:
+              val.before(acc, state, self)
+            else:
+              val.after(acc, state, self)
+          except Exception, e:
+            self._exceptionError(acc, e)
+
+  def _onCursorChanged(self, report):
+    '''
+    Enables or disables the help button based on whether an item has help or
+    not.
+
+    @param report: Report table
+    '''
+    selection = report.get_selection()
+    model, iter = selection.get_selected()
+    if iter:
+      url = model[iter][3]
+      self.help.set_sensitive(len(url))
+      self.url = url
+
+  def _onActivateRow(self, report, iter, col):
+    '''
+    Updates the Accerciser tree to show an accessible when a report entry is
+    selected.
+
+    @param report: Report table
+    @param iter: Tree table iterator
+    @param col: Tree table column
+    '''
+    selection = report.get_selection()
+    model, iter = selection.get_selected()
+    if iter:
+      acc = model[iter][2]
+      if acc:
+        self.node.update(acc)
+                             
+  def _exceptionError(self, acc, ex):
+    '''
+    Logs an unexpected exception that occurred during execution of a validator.
+    
+    @param acc: Accessible under test when the exception occurred
+    @param ex: The exception
+    '''
+    info = traceback.extract_tb(sys.exc_info()[2])
+    text = '%s (%d): %s' % (os.path.basename(info[-1][0]), info[-1][1], ex)
+    self.report.get_model().append([_('EXCEPT'), text, acc])
+    
+  def error(self, text, acc, url=''):
+    '''
+    Used by validators to log messages for accessibility problems that have to
+    be fixed.
+    '''
+    self.report.get_model().append([_('ERROR'), text, acc, url])
+    
+  def warn(self, text, acc, url=''):
+    '''
+    Used by validators to log warning messages for accessibility problems that
+    should be fixed, but are not critical.
+    '''
+    self.report.get_model().append([_('WARN'), text, acc, url])
+  
+  def info(self, text, acc, url=''):
+    '''
+    Used by validators to log informational messages.
+    '''
+    self.report.get_model().append([_('INFO'), text, acc, url])
+    
+  def debug(self, text, acc, url=''):
+    '''
+    Used by validators to log debug messages.
+    '''
+    self.report.get_model().append([_('DEBUG'), text, acc, url])



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]