Esempio n. 1
0
    def init(self):
        '''
    Initialize the event monitor plugin.
    '''
        self.global_hotkeys = [(N_('Highlight last event entry'),
                                self._onHighlightEvent,
                                gdk.KEY_e, gdk.ModifierType.MOD1_MASK | \
                                           gdk.ModifierType.CONTROL_MASK),
                               (N_('Start/stop event recording'),
                                self._onStartStop,
                                gdk.KEY_r, gdk.ModifierType.MOD1_MASK | \
                                           gdk.ModifierType.CONTROL_MASK),
                               (N_('Clear event log'),
                                self._onClearlog,
                                gdk.KEY_t, gdk.ModifierType.MOD1_MASK | \
                                           gdk.ModifierType.CONTROL_MASK)]

        self.source_filter = None
        self.main_xml = gtk.Builder()
        self.main_xml.set_translation_domain(DOMAIN)
        self.main_xml.add_from_file(UI_FILE)
        vpaned = self.main_xml.get_object('monitor_vpaned')
        self.plugin_area.add(vpaned)
        self.events_model = self.main_xml.get_object('events_treestore')
        self._popEventsModel()
        self._initTextView()

        self.monitor_toggle = self.main_xml.get_object('monitor_toggle')

        self.source_filter = None
        self.sources_dict = { \
            self.main_xml.get_object('source_everthing') : 'source_everthing', \
            self.main_xml.get_object('source_app') : 'source_app', \
            self.main_xml.get_object('source_acc') : 'source_acc' \
        }

        self.listen_list = []

        self.node.connect('accessible-changed', self._onNodeUpdated)

        self.main_xml.connect_signals(self)
        self.show_all()
Esempio n. 2
0
class Console(ViewportPlugin):
  '''
  Plugin class for IPython console.
  '''
  plugin_name = N_('IPython Console')
  plugin_name_localized = _(plugin_name)
  plugin_description = \
      N_('Interactive console for manipulating currently selected accessible')
  def init(self):
    '''
    Initialize plugin.
    '''
    sw = gtk.ScrolledWindow()
    self.plugin_area.add(sw)
    self.ipython_view = ipython_view.IPythonView()
    self.ipython_view.updateNamespace({'acc': None})
    self.ipython_view.updateNamespace(pyatspi.__dict__)
    self.ipython_view.updateNamespace({'desktop': 
                                      pyatspi.Registry.getDesktop(0)})
    self.ipython_view.updateNamespace({'show': self._showAcc})
    sw.add(self.ipython_view)
  
  def onAccChanged(self, acc):
    '''
    Update 'acc' variable in console namespace with currently 
    selected accessible.
    
    @param acc: The currently selected accessible.
    @type acc: Accessibility.Accessible
    '''
    self.ipython_view.updateNamespace({'acc': acc})

  def _showAcc(self, acc):
    '''
    A method that is exposed in the console's namespace that allows the user 
    to show a given accessible in the main application.
    
    @param acc: Accessible to show.
    @type acc: Accessibility.Accessible
    '''
    self.node.update(acc)
  def init(self):
    '''
    Initialize plugin.
    '''
    self.global_hotkeys = [(N_('Inspect last focused accessible'),
                            self._inspectLastFocused, 
                            gdk.KEY_a, gdk.ModifierType.CONTROL_MASK | \
                                       gdk.ModifierType.MOD1_MASK),
                           (N_('Inspect accessible under mouse'),
                            self._inspectUnderMouse, 
                            gdk.KEY_question, gdk.ModifierType.CONTROL_MASK | \
                                              gdk.ModifierType.MOD1_MASK)]

    pyatspi.Registry.registerEventListener(self._accEventFocusChanged, 
                               'object:state-changed')

    pyatspi.Registry.registerEventListener(self._accEventSelectionChanged, 
                               'object:selection-changed')

    self.last_focused = None
    self.last_selected = None
Esempio n. 4
0
   def _viewNameDataFunc(self, column, cell, model, iter, foo=None):
       '''
 Function for determining the displayed data in the tree's view column.
 
 @param column: Column number.
 @type column: integer
 @param cell: Cellrender.
 @type cell: gtk.CellRendererText
 @param model: Tree's model
 @type model: gtk.ListStore
 @param iter: Tree iter of current row,
 @type iter: gtk.TreeIter
 '''
       plugin_class = model[iter][self.plugin_manager.COL_CLASS]
       if issubclass(plugin_class, gtk.Widget):
           view_name = \
               self.view_manager.getViewNameForPlugin(plugin_class.plugin_name)
           cell.set_property('sensitive', True)
       else:
           view_name = N_('No view')
           cell.set_property('sensitive', False)
       cell.set_property('text', _(view_name))
class QuickSelect(Plugin):
  '''
  Plugin class for quick select.
  '''
  plugin_name = N_('Quick Select')
  plugin_name_localized = _(plugin_name)
  plugin_description = \
      N_('Plugin with various methods of selecting accessibles quickly.')

  def init(self):
    '''
    Initialize plugin.
    '''
    self.global_hotkeys = [(N_('Inspect last focused accessible'),
                            self._inspectLastFocused, 
                            gdk.KEY_a, gdk.ModifierType.CONTROL_MASK | \
                                       gdk.ModifierType.MOD1_MASK),
                           (N_('Inspect accessible under mouse'),
                            self._inspectUnderMouse, 
                            gdk.KEY_question, gdk.ModifierType.CONTROL_MASK | \
                                              gdk.ModifierType.MOD1_MASK)]

    pyatspi.Registry.registerEventListener(self._accEventFocusChanged, 
                               'object:state-changed')

    pyatspi.Registry.registerEventListener(self._accEventSelectionChanged, 
                               'object:selection-changed')

    self.last_focused = None
    self.last_selected = None

  def _accEventFocusChanged(self, event):
    '''
    Hold a reference for the last focused accessible. This is used when a certain 
    global hotkey is pressed to select this accessible.

    @param event: The event that is being handled.
    @type event: L{pyatspi.event.Event}
    '''
    if not self.isMyApp(event.source):
      self.last_focused = event.source      

  def _accEventSelectionChanged(self, event):
    '''
    Hold a reference for the last parent of a selected accessible. 
    This will be useful if we want to find an accessible at certain coords.

    @param event: The event that is being handled.
    @type event: L{pyatspi.event.Event}
    '''
    if not self.isMyApp(event.source):
      self.last_selected = event.source

  def _inspectLastFocused(self):
    '''
    Inspect the last focused widget's accessible.
    '''
    if self.last_focused:
      self.node.update(self.last_focused)

  def _inspectUnderMouse(self):
    '''
    Inspect accessible of widget under mouse.
    '''
    display = gdk.Display(gdk.get_display())
    screen, x, y, flags =  display.get_pointer()
    del screen # A workaround http://bugzilla.gnome.org/show_bug.cgi?id=593732

    # First check if the currently selected accessible has the pointer over it.
    # This is an optimization: Instead of searching for 
    # STATE_SELECTED and ROLE_MENU and LAYER_POPUP in the entire tree.
    item = self._getPopupItem(x,y)
    if item:
      self.node.update(item)
      return
          
    # Inspect accessible under mouse
    desktop = pyatspi.Registry.getDesktop(0)
    wnck_screen = wnck.Screen.get_default()
    window_order = [w.get_name() for w in wnck_screen.get_windows_stacked()]
    top_window = (None, -1)
    for app in desktop:
      if not app or self.isMyApp(app):
        continue
      for frame in app:
        if not frame:
          continue
        acc = self._getComponentAtCoords(frame, x, y)
        if acc:
          try:
            z_order = window_order.index(frame.name)
          except ValueError:
            # It's possibly a popup menu, so it would not be in our frame name
            # list. And if it is, it is probably the top-most component.
            try:
              if acc.queryComponent().getLayer() == pyatspi.LAYER_POPUP:
                self.node.update(acc)
                return
            except:
              pass
          else:
            if z_order > top_window[1]:
              top_window = (acc, z_order)

    if top_window[0]:
      self.node.update(top_window[0])

  def _getPopupItem(self, x, y):
    suspect_children = []
    # First check if the currently selected accessible has the pointer over it.
    # This is an optimization: Instead of searching for 
    # STATE_SELECTED and ROLE_MENU and LAYER_POPUP in the entire tree.
    if self.last_selected and \
          self.last_selected.getRole() == pyatspi.ROLE_MENU and \
          self.last_selected.getState().contains(pyatspi.STATE_SELECTED):
      try:
        si = self.last_selected.querySelection()
      except NotImplementedError:
        return None

      if si.nSelectedChildren > 0:
        suspect_children = [si.getSelectedChild(0)]
      else:
        suspect_children = self.last_selected

      for child in suspect_children:
        try:
          ci = child.queryComponent()
        except NotImplementedError:
          continue

        if ci.contains(x,y, pyatspi.DESKTOP_COORDS) and \
              ci.getLayer() == pyatspi.LAYER_POPUP:
          return child

      return None

  def _getComponentAtCoords(self, parent, x, y):
    '''
    Gets any child accessible that resides under given desktop coordinates.

    @param parent: Top-level accessible.
    @type parent: L{Accessibility.Accessible}
    @param x: X coordinate.
    @type x: integer
    @param y: Y coordinate.
    @type y: integer

    @return: Child accessible at given coordinates, or None.
    @rtype: L{Accessibility.Accessible}
    '''
    container = parent
    while True:
      container_role = container.getRole()
      if container_role == pyatspi.ROLE_PAGE_TAB_LIST:
        try:
          si = container.querySelection()
          container = si.getSelectedChild(0)[0]
        except NotImplementedError:
          pass
      try:
        ci = container.queryComponent()
      except:
        break
      else:
        inner_container = container
      container =  ci.getAccessibleAtPoint(x, y, pyatspi.DESKTOP_COORDS)
      if not container or container.queryComponent() == ci:
        # The gecko bridge simply has getAccessibleAtPoint return itself
        # if there are no further children
        break
    if inner_container == parent:
      return None
    else:
      return inner_container
Esempio n. 6
0
class EventMonitor(ViewportPlugin):
    '''
  Class for the monitor viewer.

  @ivar source_filter: Determines what events from what sources could be shown.
  Either source_app and source_acc for selected applications and accessibles 
  respectively. Or everything.
  @type source_filter: string
  @ivar main_xml: The main event monitor gtkbuilder file.
  @type main_xml: gtk.GtkBuilder
  @ivar monitor_toggle: Toggle button for turining monitoring on and off.
  @type monitor_toggle: gtk.ToggleButton
  @ivar listen_list: List of at-spi events the monitor is currently listening
  to.
  @type listen_list: list
  @ivar events_model: Data model of all at-spi event types.
  @type events_model: gtk.TreeStore
  @ivar textview_monitor: Text view of eent monitor.
  @type textview_monitor: gtk.TextView
  @ivar monitor_buffer: Text buffer for event monitor.
  @type monitor_buffer: gtk.TextBuffer
  '''
    plugin_name = N_('Event Monitor')
    plugin_name_localized = _(plugin_name)
    plugin_description = \
        N_('Shows events as they occur from selected types and sources')
    COL_NAME = 0
    COL_FULL_NAME = 1
    COL_TOGGLE = 2
    COL_INCONSISTENT = 3

    def init(self):
        '''
    Initialize the event monitor plugin.
    '''
        self.global_hotkeys = [(N_('Highlight last event entry'),
                                self._onHighlightEvent,
                                gdk.KEY_e, gdk.ModifierType.MOD1_MASK | \
                                           gdk.ModifierType.CONTROL_MASK),
                               (N_('Start/stop event recording'),
                                self._onStartStop,
                                gdk.KEY_r, gdk.ModifierType.MOD1_MASK | \
                                           gdk.ModifierType.CONTROL_MASK),
                               (N_('Clear event log'),
                                self._onClearlog,
                                gdk.KEY_t, gdk.ModifierType.MOD1_MASK | \
                                           gdk.ModifierType.CONTROL_MASK)]

        self.source_filter = None
        self.main_xml = gtk.Builder()
        self.main_xml.set_translation_domain(DOMAIN)
        self.main_xml.add_from_file(UI_FILE)
        vpaned = self.main_xml.get_object('monitor_vpaned')
        self.plugin_area.add(vpaned)
        self.events_model = self.main_xml.get_object('events_treestore')
        self._popEventsModel()
        self._initTextView()

        self.monitor_toggle = self.main_xml.get_object('monitor_toggle')

        self.source_filter = None
        self.sources_dict = { \
            self.main_xml.get_object('source_everthing') : 'source_everthing', \
            self.main_xml.get_object('source_app') : 'source_app', \
            self.main_xml.get_object('source_acc') : 'source_acc' \
        }

        self.listen_list = []

        self.node.connect('accessible-changed', self._onNodeUpdated)

        self.main_xml.connect_signals(self)
        self.show_all()

    def _onStartStop(self):
        active = self.monitor_toggle.get_active()
        self.monitor_toggle.set_active(not active)

    def _onClearlog(self):
        self.monitor_buffer.set_text('')

    def _onNodeUpdated(self, node, acc):
        if acc == node.desktop and \
              self.source_filter in ('source_app', 'source_acc'):
            self.monitor_toggle.set_active(False)

    def _popEventsModel(self):
        '''
    Populate the model for the event types tree view. Uses a constant
    from pyatspi for the listing of all event types.
    '''
        events = list(pyatspi.EVENT_TREE.keys())
        for sub_events in pyatspi.EVENT_TREE.values():
            events.extend(sub_events)
        events = list(set([event.strip(':') for event in events]))
        events.sort()
        GLib.idle_add(self._appendChildren, None, '', 0, events)

    def _initTextView(self):
        '''
    Initialize text view in monitor plugin.
    '''
        self.textview_monitor = self.main_xml.get_object('textview_monitor')

        self.monitor_buffer = self.textview_monitor.get_buffer()
        self.monitor_mark = \
            self.monitor_buffer.create_mark('scroll_mark',
                                            self.monitor_buffer.get_end_iter(),
                                            False)
        self.monitor_buffer.create_mark('mark_last_log',
                                        self.monitor_buffer.get_end_iter(),
                                        True)
        self.monitor_buffer.create_tag('last_log', weight=700)

    def _appendChildren(self, parent_iter, parent, level, events):
        '''
    Append child events to a parent event's iter.
    
    @param parent_iter: The tree iter of the parent.
    @type parent_iter: gtk.TreeIter
    @param parent: The parent event.
    @type parent: string
    @param level: The generation of the children.
    @type level: integer
    @param events: A list of children
    @type events: list
    
    @return: Return false to that this won't be called again in the mainloop.
    @rtype: boolean
    '''
        for event in events:
            if event.count(':') == level and event.startswith(parent):
                iter = self.events_model.append(
                    parent_iter, [event.split(':')[-1], event, False, False])
                GLib.idle_add(self._appendChildren, iter, event, level + 1,
                              events)
        return False

    def _onToggled(self, renderer_toggle, path):
        '''
    Callback for toggled events in the treeview.
    
    @param renderer_toggle: The toggle cell renderer.
    @type renderer_toggle: gtk.CellRendererToggle
    @param path: The path of the toggled node.
    @type path: tuple
    '''
        iter = self.events_model.get_iter(path)
        val = not self.events_model.get_value(iter, self.COL_TOGGLE)
        self._iterToggle(iter, val)
        self._resetClient()

    def _resetClient(self):
        '''
    De-registers the client from the currently monitored events. 
    If the monitor is still enabled it get's a list of enabled events 
    and re-registers the client.
    '''
        pyatspi.Registry.deregisterEventListener(self._handleAccEvent,
                                                 *self.listen_list)
        self.listen_list = self._getEnabledEvents(
            self.events_model.get_iter_first())
        if self.monitor_toggle.get_active():
            pyatspi.Registry.registerEventListener(self._handleAccEvent,
                                                   *self.listen_list)

    def _getEnabledEvents(self, iter):
        '''
    Recursively walks through the events model and collects all enabled 
    events in a list.
    
    @param iter: Iter of root node to check under.
    @type iter: gtk.TreeIter
    
    @return: A list of enabled events.
    @rtype: list
    '''
        listen_for = []
        while iter:
            toggled = self.events_model.get_value(iter, self.COL_TOGGLE)
            inconsistent = self.events_model.get_value(iter,
                                                       self.COL_INCONSISTENT)
            if toggled and not inconsistent:
                listen_for.append(
                    self.events_model.get_value(iter, self.COL_FULL_NAME))
            elif inconsistent:
                listen_for_child = self._getEnabledEvents(
                    self.events_model.iter_children(iter))
                listen_for.extend(listen_for_child)
            iter = self.events_model.iter_next(iter)
        return listen_for

    def _iterToggle(self, iter, val):
        '''
    Toggle the given node. If the node has children toggle them accordingly 
    too. Toggle all anchester nodes too, either true, false or inconsistent,
    sepending on the value of their children.
    
    @param iter: Iter of node to toggle.
    @type iter: gtk.TreeIter
    @param val: Toggle value.
    @type val: boolean
    '''
        self.events_model.set_value(iter, self.COL_INCONSISTENT, False)
        self.events_model.set_value(iter, self.COL_TOGGLE, val)
        self._setAllDescendants(iter, val)
        parent = self.events_model.iter_parent(iter)
        while parent:
            is_consistent = self._descendantsConsistent(parent)
            self.events_model.set_value(parent, self.COL_INCONSISTENT,
                                        not is_consistent)
            self.events_model.set_value(parent, self.COL_TOGGLE, val)
            parent = self.events_model.iter_parent(parent)

    def _setAllDescendants(self, iter, val):
        '''
    Set all descendants of a given node to a certain toggle value.
    
    @param iter: Parent node's iter.
    @type iter: gtk.TreeIter
    @param val: Toggle value.
    @type val: boolean
    '''
        child = self.events_model.iter_children(iter)
        while child:
            self.events_model.set_value(child, self.COL_TOGGLE, val)
            self._setAllDescendants(child, val)
            child = self.events_model.iter_next(child)

    def _descendantsConsistent(self, iter):
        '''
    Determine if all of a node's descendants are consistently toggled.
    
    @param iter: Parent node's iter.
    @type iter: gtk.TreeIter
    
    @return: True if descendants nodes are consistent.
    @rtype: boolean
    '''
        child = self.events_model.iter_children(iter)
        if child:
            first_val = self.events_model.get_value(child, self.COL_TOGGLE)
        while child:
            child_val = self.events_model.get_value(child, self.COL_TOGGLE)
            is_consistent = self._descendantsConsistent(child)
            if (first_val != child_val or not is_consistent):
                return False
            child = self.events_model.iter_next(child)
        return True

    def _onSelectAll(self, button):
        '''
    Callback for "select all" button. Select all event types.
    
    @param button: Button that was clicked
    @type button: gtk.Button
    '''
        iter = self.events_model.get_iter_first()
        while iter:
            self._iterToggle(iter, True)
            iter = self.events_model.iter_next(iter)
        self._resetClient()

    def _onClearSelection(self, button):
        '''
    Callback for "clear selection" button. Clear all selected events.
    
    @param button: Button that was clicked.
    @type button: gtk.Button
    '''
        iter = self.events_model.get_iter_first()
        while iter:
            self._iterToggle(iter, False)
            iter = self.events_model.iter_next(iter)
        self._resetClient()

    def _logEvent(self, event):
        '''
    Log the given event.
    
    @param event: The event to log.
    @type event: Accessibility.Event
    '''
        iter = self.monitor_buffer.get_iter_at_mark(self.monitor_mark)
        self.monitor_buffer.move_mark_by_name(
            'mark_last_log',
            self.monitor_buffer.get_iter_at_mark(self.monitor_mark))
        self._insertEventIntoBuffer(event)
        self.textview_monitor.scroll_mark_onscreen(self.monitor_mark)

    def _insertEventIntoBuffer(self, event):
        '''
    Inserts given event in to text buffer. Creates hyperlinks for
    the events context accessibles.
    
    @param event: The at-spi event we are inserting.
    @type event: Accessibility.Event
    '''
        ts = int((os.times()[-1] * 10) % 1000)
        self._writeText('%02.1f %s(%s, %s, %s)\n\tsource: ' % \
                          (ts / 10, event.type, event.detail1,
                            event.detail2, event.any_data))
        hyperlink = self._createHyperlink(event.source)
        self._writeText(str(event.source), hyperlink)
        self._writeText('\n\tapplication: ')
        hyperlink = self._createHyperlink(event.host_application)
        self._writeText(str(event.host_application), hyperlink)
        if hasattr(event, "sender") and event.sender != event.host_application:
            self._writeText('\n\tsender: ')
            hyperlink = self._createHyperlink(event.sender)
            self._writeText(str(event.sender), hyperlink)
        self._writeText('\n')
        if event.type == "screen-reader:region-changed":
            try:
                text = event.source.queryText()
                (x, y, width,
                 height) = text.getRangeExtents(event.detail1, event.detail2,
                                                pyatspi.DESKTOP_COORDS)
                if width > 0 and height > 0:
                    ah = node._HighLight(x, y, width, height, node.FILL_COLOR,
                                         node.FILL_ALPHA, node.BORDER_COLOR,
                                         node.BORDER_ALPHA, 2.0, 0)
                    ah.highlight(node.HL_DURATION)
            except:
                pass

    def _writeText(self, text, *tags):
        '''
    Convinience function for inserting text in to the text buffer.
    If tags are provided they are applied to the inserted text.
    
    @param text: Text to insert
    @type text: string
    @param *tags: List of optional tags to insert with text
    @type *tags: list of gtk.TextTag
    '''
        if tags:
            self.monitor_buffer.insert_with_tags(
                self.monitor_buffer.get_iter_at_mark(self.monitor_mark), text,
                *tags)
        else:
            self.monitor_buffer.insert(
                self.monitor_buffer.get_iter_at_mark(self.monitor_mark), text)

    def _createHyperlink(self, acc):
        '''
    Create a hyperlink tag for a given accessible. When the link is clicked
    the accessible is selected in the main program.
    
    @param acc: The accessible to create the tag for.
    @type acc: Accessibility.Accessible
    
    @return: The new hyperlink tag
    @rtype: gtk.TextTag
    '''
        hyperlink = self.monitor_buffer.create_tag(
            None, foreground='blue', underline=Pango.Underline.SINGLE)
        hyperlink.connect('event', self._onLinkClicked)
        setattr(hyperlink, 'acc', acc)
        setattr(hyperlink, 'islink', True)
        return hyperlink

    def _onLinkClicked(self, tag, widget, event, iter):
        '''
    Callback for clicked link. Select links accessible in main application.
    
    @param tag: Tag that was clicked.
    @type tag: gtk.TextTag
    @param widget: The widget that received event.
    @type widget: gtk.Widget
    @param event: The event object.
    @type event: gdk.Event
    @param iter: The text iter that was clicked.
    @type iter: gtk.TextIter
    '''
        if event.type == gdk.EventType.BUTTON_RELEASE and \
               event.button == 1 and not self.monitor_buffer.get_has_selection():
            self.node.update(getattr(tag, 'acc'))

    def _onLinkKeyPress(self, textview, event):
        '''
    Callback for a keypress in the text view. If the keypress is enter or 
    space, and the cursor is above a link, follow it.
    
    @param textview: Textview that was pressed.
    @type textview: gtk.TextView
    @param event: Event object.
    @type event: gdk.Event
    '''
        if event.keyval in (gdk.KEY_Return, gdk.KEY_KP_Enter, gdk.KEY_space):
            buffer = textview.get_buffer()
            iter = buffer.get_iter_at_mark(buffer.get_insert())
            acc = None
            for tag in iter.get_tags():
                acc = getattr(tag, 'acc')
                if acc:
                    self.node.update(acc)
                    break

    def _onLinkMotion(self, textview, event):
        '''
    Change mouse cursor shape when hovering over a link.
    
    @param textview: Monitors text view.
    @type textview: gtk.TextView
    @param event: Event object
    @type event: gdk.Event
    
    @return: Return False so event continues in callback chain.
    @rtype: boolean
    '''
        x, y = textview.window_to_buffer_coords(gtk.TextWindowType.WIDGET,
                                                int(event.x), int(event.y))
        isText = True
        iter = textview.get_iter_at_location(x, y)
        if isinstance(iter, tuple):
            (isText, iter) = iter
        cursor = gdk.Cursor(gdk.CursorType.XTERM)
        if isText:
            for tag in iter.get_tags():
                if getattr(tag, 'islink'):
                    cursor = gdk.Cursor(gdk.CursorType.HAND2)
                    break
        window = textview.get_window(gtk.TextWindowType.TEXT)
        window.set_cursor(cursor)
        window.get_pointer()
        return False

    def _handleAccEvent(self, event):
        '''
    Main at-spi event client. If event passes filtering requirements, log it.
    
    @param event: The at-spi event recieved.
    @type event: Accessibility.Event
    '''
        if self.isMyApp(event.source) or not self._eventFilter(event):
            return
        self._logEvent(event)

    def _onSave(self, button):
        '''
    Callback for 'save' button clicked. Saves the buffer in to the given 
    filename.
    
    @param button: Button that was clicked.
    @type button: gtk.Button
    '''
        save_dialog = gtk.FileChooserDialog(
            'Save monitor output',
            action=gtk.FileChooserAction.SAVE,
            buttons=(gtk.ButtonsType.CANCEL, gtk.ResponseType.CANCEL,
                     gtk.ButtonsType.OK, gtk.ResponseType.OK))
        save_dialog.set_do_overwrite_confirmation(True)
        save_dialog.set_default_response(gtk.ResponseType.OK)
        response = save_dialog.run()
        save_dialog.show_all()
        if response == gtk.ResponseType.OK:
            save_to = open(save_dialog.get_filename(), 'w')
            save_to.write(
                self.monitor_buffer.get_text(
                    self.monitor_buffer.get_start_iter(),
                    self.monitor_buffer.get_end_iter()))
            save_to.close()
        save_dialog.destroy()

    def _onClear(self, button):
        '''
    Callback for 'clear' button. Clears monitor's text buffer.
    
    @param button: Button that was clicked.
    @type button: gtk.Button
    '''
        self.monitor_buffer.set_text('')

    def _onMonitorToggled(self, monitor_toggle):
        '''
    Callback for monitor toggle button. Activates or deactivates monitoring.
    
    @param monitor_toggle: The toggle button that was toggled.
    @type monitor_toggle: gtk.ToggleButton
    '''
        if monitor_toggle.get_active():
            pyatspi.Registry.registerEventListener(self._handleAccEvent,
                                                   *self.listen_list)
        else:
            pyatspi.Registry.deregisterEventListener(self._handleAccEvent,
                                                     *self.listen_list)

    def _onSourceToggled(self, radio_button):
        '''
    Callback for radio button selection for choosing source filters.
    
    @param radio_button: Radio button that was selected.
    @type radio_button: gtk.RadioButton
    '''
        self.source_filter = self.sources_dict[radio_button]

    def _eventFilter(self, event):
        '''
    Determine if an event's source should make the event filtered.
    
    @param event: The given at-spi event.
    @type event: Accessibility.Event
    
    @return: False if the event should be filtered.
    @rtype: boolean
    '''
        if self.source_filter == 'source_app':
            try:
                return event.source.getApplication(
                ) == self.acc.getApplication()
            except:
                return False
        elif self.source_filter == 'source_acc':
            return event.source == self.acc
        else:
            return True

    def _onHighlightEvent(self):
        '''
    A callback fom a global key binding. Makes the last event in the textview
    bold.
    '''
        start_iter = self.monitor_buffer.get_iter_at_mark(
            self.monitor_buffer.get_mark('mark_last_log'))
        end_iter = self.monitor_buffer.get_end_iter()
        self.monitor_buffer.apply_tag_by_name('last_log', start_iter, end_iter)
Esempio n. 7
0
class APIBrowser(ViewportPlugin):
    '''
  Plugin class for API Browser.

  @ivar iface_combo: Combobox that shows available interfaces.
  @type iface_combo: gtk.ComboBox
  @ivar method_tree: Tree view with available methods from chosen interface.
  @type method_tree: gtk.TreeView
  @ivar property_tree: Tree view with available properties from chosen 
  interface.
  @type property_tree: gtk.TreeView
  @ivar private_toggle: Toggles visibility of private attributes.
  @type private_toggle: gtk.CheckButton
  '''
    plugin_name = N_('API Browser')
    plugin_name_localized = _(plugin_name)
    plugin_description = \
        N_('Browse the various methods of the current accessible')

    def init(self):
        '''
    Initialize the API browser plugin.
    '''
        self._buildUI()
        self._initTreeViews()
        self.iface_combo.connect('changed', self._refreshAttribs)
        self.private_toggle.connect('toggled', self._refreshAttribs)
        self.curr_iface = None

    def _buildUI(self):
        '''
    Manually build the plugin's UI.
    '''
        vbox = gtk.VBox()
        self.plugin_area.add(vbox)
        top_hbox = gtk.HBox()
        bottom_hbox = gtk.HBox()
        vbox.pack_start(top_hbox, False, True, 0)
        vbox.pack_start(bottom_hbox, True, True, 0)
        self.method_tree = gtk.TreeView()
        scrolled_window = gtk.ScrolledWindow()
        scrolled_window.add(self.method_tree)
        bottom_hbox.pack_start(scrolled_window, True, True, 0)
        self.property_tree = gtk.TreeView()
        scrolled_window = gtk.ScrolledWindow()
        scrolled_window.add(self.property_tree)
        bottom_hbox.pack_start(scrolled_window, True, True, 0)
        self.iface_combo = gtk.ComboBoxText.new()
        top_hbox.pack_start(self.iface_combo, False, True, 0)
        self.private_toggle = gtk.CheckButton(_('Hide private attributes'))
        self.private_toggle.set_active(True)
        top_hbox.pack_end(self.private_toggle, False, True, 0)
        self.show_all()

    def _initTreeViews(self):
        '''
    Initialize the properties and methods tree views and models.
    '''
        # method view
        model = gtk.ListStore(str, str)
        self.method_tree.set_model(model)
        crt = gtk.CellRendererText()
        tvc = gtk.TreeViewColumn(_('Method'))
        tvc.pack_start(crt, True)
        tvc.add_attribute(crt, 'text', 0)
        self.method_tree.append_column(tvc)

        # property view
        model = gtk.ListStore(str, str)
        self.property_tree.set_model(model)
        crt = gtk.CellRendererText()
        tvc = gtk.TreeViewColumn(_('Property'))
        tvc.pack_start(crt, True)
        tvc.add_attribute(crt, 'text', 0)
        self.property_tree.append_column(tvc)
        crt = gtk.CellRendererText()
        tvc = gtk.TreeViewColumn(_('Value'))
        tvc.pack_start(crt, True)
        tvc.add_attribute(crt, 'text', 1)
        self.property_tree.append_column(tvc)

    def onAccChanged(self, acc):
        '''
    Update the UI when the selected accessible changes.
    
    @param acc: The applications-wide selected accessible.
    @type acc: Accessibility.Accessible
    '''
        self.acc = acc
        ints = pyatspi.listInterfaces(acc)
        model = self.iface_combo.get_model()
        model.clear()
        for iface in ints:
            self.iface_combo.append_text(iface)
        self.iface_combo.set_active(0)

    def _refreshAttribs(self, widget):
        '''
    Refresh the attributes in the tree views. Could be used as a callback.
    
    @param widget: The widget that may have triggered this callback.
    @type widget: gtk.Widget
    '''
        iface = self.iface_combo.get_active_text()

        try:
            query_func = getattr(self.acc, 'query%s' % iface)
        except AttributeError:
            pass
        else:
            self.curr_iface = query_func()
            self._popAttribViews()

    def _popAttribViews(self):
        '''
    Populate the attribute views with information from currently selected 
    accessible and interface.
    '''
        prop_model = self.property_tree.get_model()
        method_model = self.method_tree.get_model()
        prop_model.clear()
        method_model.clear()
        for attr in dir(self.curr_iface):
            if self.private_toggle.get_active() and attr[0] == '_':
                continue
            try:
                obj = getattr(self.curr_iface, attr)
            except AttributeError:
                # Slots seem to raise AttributeError if they were not assigned.
                continue
            if callable(obj):
                method_model.append([attr, obj.__doc__])
            else:
                prop_model.append([attr, str(obj)])
Esempio n. 8
0
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: gtk builder 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_name_localized = _(plugin_name)
    plugin_description = N_('Validates application accessibility')

    # keep track of when a file is being written
    write_in_progress = False

    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.Builder()
        self.main_xml.set_translation_domain(DOMAIN)
        self.main_xml.add_from_file(UI_FILE)
        frame = self.main_xml.get_object('main vbox')
        self.plugin_area.add(frame)
        self.report = self.main_xml.get_object('report table')
        self.progress = self.main_xml.get_object('progress bar')
        self.validate = self.main_xml.get_object('validate button')
        self.help = self.main_xml.get_object('help button')
        self.save = self.main_xml.get_object('save button')
        self.clear = self.main_xml.get_object('clear button')
        self.schema = self.main_xml.get_object('schema combo')
        self.validator_buffer = gtk.TextBuffer()

        # model for the combobox
        model = gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING)
        self.schema.set_model(model)

        # append all schema names/descriptions
        vm = ValidatorManager
        for name in vm.listSchemas():
            d = vm.getSchemaMetadata(name)
            model.append(['%s - %s' % (d['name'], d['description']), name])
        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.add_attribute(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.connect_signals(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 _writeFile(self):
        '''
    Save the report from the report model to disk in a temporary location.
    Close the file when finished.
    '''
        if self.write_in_progress:
            # if we have finished writing to the file
            if self.curr_file_row == self.n_report_rows:
                self.save_to.close()
                self._stopSave()
                return False
        else:
            # set up the file to be written
            self._startSave()
            self.save.set_sensitive(False)
            report_store = self.report.get_model()
            # create list of lists containing column values
            self.row_values = [[row[0], row[1], row[2], row[3]]
                               for row in report_store]
            self.n_report_rows = len(self.row_values)
            return True

        remaining_rows = self.n_report_rows - self.curr_file_row
        n_rows_to_write = 5
        if n_rows_to_write > remaining_rows:
            n_rows_to_write = remaining_rows

        file_str_list = []  # list to store strings to be written to file
        start = self.curr_file_row
        end = (self.curr_file_row + n_rows_to_write)
        for i in range(start, end):
            val = self.row_values[i]
            # add level to buffer
            file_str_list.append("%s: %s\n" % (_('Level'), val[0]))
            # add description to buffer
            file_str_list.append("%s: %s\n" % (_('Description'), val[1]))
            # add accessible's name to buffer
            file_str_list.append("%s: %s\n" % (_('Name'), val[2].name))
            # add accessible's role to buffer
            file_str_list.append("%s: %s\n" %
                                 (_('Role'), val[2].getRoleName()))
            # add url role to buffer
            file_str_list.append("%s: %s\n\n" % (_('Hyperlink'), val[3]))
            self.curr_file_row += 1

        self.save_to.write(''.join(file_str_list))

        return True

    def _onSave(self, button):
        '''
    Save the report from the report model to disk

    @param button: Save button
    '''
        save_dialog = gtk.FileChooserDialog(
            'Save validator output',
            action=gtk.FileChooserAction.SAVE,
            buttons=(gtk.STOCK_CANCEL, gtk.ResponseType.CANCEL, gtk.STOCK_OK,
                     gtk.ResponseType.OK))
        #save_dialog.connect("response", self._savedDiagResponse)
        save_dialog.set_do_overwrite_confirmation(True)
        save_dialog.set_default_response(gtk.ResponseType.OK)
        response = save_dialog.run()
        if response == gtk.ResponseType.OK:
            self.save_to = open(save_dialog.get_filename(), 'w')
            GObject.idle_add(self._writeFile)
        save_dialog.destroy()

    def _onClear(self, button):
        '''
    Clear the report from the report model

    @param button: Clear button
    '''
        self.report.get_model().clear()
        self.validator_buffer.set_text('')
        self.save.set_sensitive(False)
        self.clear.set_sensitive(False)

    def _onHelp(self, button):
        '''
    Open a help URL in the system web browser.

    @param button: Help button
    '''
        webbrowser.open(self.url)

    def _onSaveIdle(self):
        '''
    Move the progress bar
    '''
        self.progress.pulse()
        return True

    def _startSave(self):
        '''
    Starts a save by settting up an idle callback after initializing progress
    bar.
    '''
        # set variables for writing report to file
        self.write_in_progress = True
        self._setDefaultSaveVars()
        # register an idle callback
        self.idle_save_id = GObject.idle_add(self._onSaveIdle)
        self.progress.set_text(_('Saving'))
        # disable controls
        self.validate.set_sensitive(False)
        self.save.set_sensitive(False)

    def _stopSave(self):
        '''
    Stops a save by disabling the idle callback and restoring the various UI
    components to their enabled states.
    '''
        # stop callbacks
        GObject.source_remove(self.idle_save_id)
        # reset progress
        self.progress.set_fraction(0.0)
        self.progress.set_text(_('Idle'))
        # enable other controls
        self.validate.set_sensitive(True)
        self.save.set_sensitive(True)
        self.save.set_sensitive(True)
        # reset variables for writing report to file
        self._setDefaultSaveVars()
        self.write_in_progress = False

    def _setDefaultSaveVars(self):
        '''
    Ready appropriate variables for a save
    '''
        self.curr_file_row = 0
        self.n_report_rows = 0
        self.row_values = []

    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
        index = self.schema.get_active()
        row = self.schema.get_model()[index]
        self.vals = ValidatorManager.getValidators(row[1])
        # build a new state dict
        state = {}
        # build our walk generator
        self.walk = self._traverse(self.acc, state)
        # register an idle callback
        self.idle_validate_id = GObject.idle_add(self._onValidateIdle)
        self.progress.set_text(_('Validating'))
        # disable controls
        self.schema.set_sensitive(False)
        self.help.set_sensitive(False)
        self.save.set_sensitive(False)
        self.clear.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_validate_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.help.set_sensitive(True)
        self.save.set_sensitive(True)
        self.clear.set_sensitive(True)

    def _onValidateIdle(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
Esempio n. 9
0
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: gtk builder 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_name_localized = _(plugin_name)
    plugin_description = N_('Validates application accessibility')

    # keep track of when a file is being written
    write_in_progress = False

    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.Builder()
        self.main_xml.set_translation_domain(DOMAIN)
        self.main_xml.add_from_file(UI_FILE)
        frame = self.main_xml.get_object('main vbox')
        self.plugin_area.add(frame)
        self.report = self.main_xml.get_object('report table')
        self.progress = self.main_xml.get_object('progress bar')
        self.validate = self.main_xml.get_object('validate button')
        self.help = self.main_xml.get_object('help button')
        self.save = self.main_xml.get_object('save button')
        self.clear = self.main_xml.get_object('clear button')
        self.schema = self.main_xml.get_object('schema combo')
        self.validator_buffer = gtk.TextBuffer()
        self.idle_validate_id = None

        # model for the combobox
        model = gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING)
        self.schema.set_model(model)

        # append all schema names/descriptions
        vm = ValidatorManager
        for name in vm.listSchemas():
            d = vm.getSchemaMetadata(name)
            model.append(['%s - %s' % (d['name'], d['description']), name])
        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.add_attribute(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.connect_signals(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 _writeFile(self):
        '''
    Save the report from the report model to disk in a temporary location.
    Close the file when finished.
    '''
        if self.write_in_progress:
            # if we have finished writing to the file
            if self.curr_file_row == self.n_report_rows:
                self.save_to.close()
                self._stopSave()
                return False
        else:
            # set up the file to be written
            self._startSave()
            self.save.set_sensitive(False)
            report_store = self.report.get_model()
            # create list of lists containing column values
            self.row_values = [[row[0], row[1], row[2], row[3]]
                               for row in report_store]
            self.n_report_rows = len(self.row_values)
            return True

        remaining_rows = self.n_report_rows - self.curr_file_row
        n_rows_to_write = 5
        if n_rows_to_write > remaining_rows:
            n_rows_to_write = remaining_rows

        file_str_list = []  # list to store strings to be written to file
        start = self.curr_file_row
        end = (self.curr_file_row + n_rows_to_write)
        for i in range(start, end):
            val = self.row_values[i]
            # add level to buffer
            file_str_list.append("%s: %s\n" % (_('Level'), val[0]))
            # add description to buffer
            file_str_list.append("%s: %s\n" % (_('Description'), val[1]))
            # add accessible's name to buffer
            file_str_list.append("%s: %s\n" % (_('Name'), val[2].name))
            # add accessible's role to buffer
            file_str_list.append("%s: %s\n" %
                                 (_('Role'), val[2].getRoleName()))
            # add url role to buffer
            file_str_list.append("%s: %s\n\n" % (_('Hyperlink'), val[3]))
            self.curr_file_row += 1

        self.save_to.write(''.join(file_str_list))

        return True

    def _onSave(self, button):
        '''
    Save the report from the report model to disk

    @param button: Save button
    '''
        save_dialog = gtk.FileChooserDialog(
            'Save validator output',
            action=gtk.FileChooserAction.SAVE,
            buttons=(gtk.STOCK_CANCEL, gtk.ResponseType.CANCEL, gtk.STOCK_OK,
                     gtk.ResponseType.OK))
        #save_dialog.connect("response", self._savedDiagResponse)
        save_dialog.set_do_overwrite_confirmation(True)
        save_dialog.set_default_response(gtk.ResponseType.OK)
        response = save_dialog.run()
        if response == gtk.ResponseType.OK:
            self.save_to = open(save_dialog.get_filename(), 'w')
            GLib.idle_add(self._writeFile)
        save_dialog.destroy()

    def _onClear(self, button):
        '''
    Clear the report from the report model

    @param button: Clear button
    '''
        self.report.get_model().clear()
        self.validator_buffer.set_text('')
        self.save.set_sensitive(False)
        self.clear.set_sensitive(False)

    def _onHelp(self, button):
        '''
    Open a help URL in the system web browser.

    @param button: Help button
    '''
        webbrowser.open(self.url)

    def _onSaveIdle(self):
        '''
    Move the progress bar
    '''
        self.progress.pulse()
        return True

    def _startSave(self):
        '''
    Starts a save by settting up an idle callback after initializing progress
    bar.
    '''
        # set variables for writing report to file
        self.write_in_progress = True
        self._setDefaultSaveVars()
        # register an idle callback
        self.idle_save_id = GLib.idle_add(self._onSaveIdle)
        self.progress.set_text(_('Saving'))
        # disable controls
        self.validate.set_sensitive(False)
        self.save.set_sensitive(False)

    def _stopSave(self):
        '''
    Stops a save by disabling the idle callback and restoring the various UI
    components to their enabled states.
    '''
        # stop callbacks
        GLib.source_remove(self.idle_save_id)
        # reset progress
        self.progress.set_fraction(0.0)
        self.progress.set_text(_('Idle'))
        # enable other controls
        self.validate.set_sensitive(True)
        self.save.set_sensitive(True)
        self.save.set_sensitive(True)
        # reset variables for writing report to file
        self._setDefaultSaveVars()
        self.write_in_progress = False

    def _setDefaultSaveVars(self):
        '''
    Ready appropriate variables for a save
    '''
        self.curr_file_row = 0
        self.n_report_rows = 0
        self.row_values = []

    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
        index = self.schema.get_active()
        if index == -1:
            self.validate.set_active(False)
            return
        row = self.schema.get_model()[index]
        self.vals = ValidatorManager.getValidators(row[1])
        # build a new state dict
        state = {}
        # build our walk generator
        self.walk = self._traverse(self.acc, state)
        # register an idle callback
        self.idle_validate_id = GLib.idle_add(self._onValidateIdle)
        self.progress.set_text(_('Validating'))
        # disable controls
        self.schema.set_sensitive(False)
        self.help.set_sensitive(False)
        self.save.set_sensitive(False)
        self.clear.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.
    '''
        if self.idle_validate_id == None:
            return
        # stop callbacks
        GLib.source_remove(self.idle_validate_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.help.set_sensitive(True)
        self.save.set_sensitive(True)
        self.clear.set_sensitive(True)

    def _onValidateIdle(self):
        '''
    Tests one accessible at a time on each idle callback by advancing the
    walk generator.
    '''
        try:
            # generate the next accessible to validate
            next(self.walk)
        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 = next(gen_child)
            except StopIteration as e:
                break
            # recurse
            gen_traverse = self._traverse(child, state)
            while 1:
                # yield before continuing processing
                yield None
                try:
                    # get one descendant
                    next(gen_traverse)
                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 range(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 as 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.
    '''
        level = _('ERROR')
        self.report.get_model().append([level, 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.
    '''
        level = _('WARN')
        self.report.get_model().append([level, text, acc, url])

    def info(self, text, acc, url=''):
        '''
    Used by validators to log informational messages.
    '''
        level = _('INFO')
        self.report.get_model().append([level, text, acc, url])

    def debug(self, text, acc, url=''):
        '''
    Used by validators to log debug messages.
    '''
        level = _('DEBUG')
        self.report.get_model().append([level, text, acc, url])

    def close(self):
        '''
    Things to do before the plugin closes.
    '''
        # don't close the plugin until we have finished writing
        while True:
            if not self.write_in_progress:
                break
            gtk.main_iteration_do(True)