quick_select.py 5.98 KB
Newer Older
1 2 3 4 5 6
import gi

from gi.repository import Gtk as gtk
from gi.repository import Gdk as gdk
from gi.repository import Wnck as wnck

7 8
from accerciser.plugin import Plugin
from accerciser.i18n import N_, _
9

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
import pyatspi

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, 
27 28
                            gdk.KEY_a, gdk.ModifierType.CONTROL_MASK | \
                                       gdk.ModifierType.MOD1_MASK),
29 30
                           (N_('Inspect accessible under mouse'),
                            self._inspectUnderMouse, 
31 32
                            gdk.KEY_question, gdk.ModifierType.CONTROL_MASK | \
                                              gdk.ModifierType.MOD1_MASK)]
33 34

    pyatspi.Registry.registerEventListener(self._accEventFocusChanged, 
35 36 37 38
                               'object:state-changed')

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

    self.last_focused = None
41
    self.last_selected = None
42 43 44 45 46 47 48 49 50 51 52 53

  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      

54 55 56 57 58 59 60 61 62 63 64
  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

65 66 67 68 69 70 71 72 73 74 75
  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.
    '''
76
    display = gdk.Display(gdk.get_display())
77
    screen, x, y, flags =  display.get_pointer()
78
    del screen # A workaround http://bugzilla.gnome.org/show_bug.cgi?id=593732
79 80 81 82

    # 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.
Joanmarie Diggs's avatar
Joanmarie Diggs committed
83
    item = self._getPopupItem(x, y)
84 85 86 87 88
    if item:
      self.node.update(item)
      return
          
    # Inspect accessible under mouse
89
    desktop = pyatspi.Registry.getDesktop(0)
90
    wnck_screen = wnck.Screen.get_default()
91 92 93 94 95 96 97 98
    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
99
        acc = self._getComponentAtCoords(frame, x, y)
100 101 102 103
        if acc:
          try:
            z_order = window_order.index(frame.name)
          except ValueError:
104 105 106 107 108 109 110 111 112 113 114
            # 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)
115

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

119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
  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

Joanmarie Diggs's avatar
Joanmarie Diggs committed
143
        if ci.contains(x, y, pyatspi.DESKTOP_COORDS) and \
144 145 146 147 148 149
              ci.getLayer() == pyatspi.LAYER_POPUP:
          return child

      return None

  def _getComponentAtCoords(self, parent, x, y):
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
    '''
    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:
165 166 167 168 169 170 171
      container_role = container.getRole()
      if container_role == pyatspi.ROLE_PAGE_TAB_LIST:
        try:
          si = container.querySelection()
          container = si.getSelectedChild(0)[0]
        except NotImplementedError:
          pass
172 173 174
      try:
        ci = container.queryComponent()
      except:
175
        break
176 177 178 179 180 181 182 183 184 185 186
      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
187