accerciser r413 - in trunk: . plugindata plugindata/validate plugins
- From: parente svn gnome org
- To: svn-commits-list gnome org
- Subject: accerciser r413 - in trunk: . plugindata plugindata/validate plugins
- Date: Fri, 11 Jan 2008 01:46:08 +0000 (GMT)
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]