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()
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
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
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)
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)])
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
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)