File: quick_select.py

package info (click to toggle)
accerciser 3.48.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,944 kB
  • sloc: python: 10,674; javascript: 163; xml: 94; makefile: 49; sh: 2
file content (204 lines) | stat: -rw-r--r-- 6,608 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
import gi

from gi.repository import Gdk as gdk

from accerciser.plugin import Plugin
from accerciser.i18n import N_, _
from accerciser import window_manager

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,
                            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
    self.window_manager = window_manager.get_window_manager()

  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 event.type != "object:state-changed:focused" and \
       event.type != "object:state-changed:selected":
      return

    if event.detail1 != 1:
      return

    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.
    '''
    x, y = self.window_manager.getMousePosition()

    # 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
    window_order = self.window_manager.getCurrentWorkspaceWindowOrder()
    top_window = (None, -1)
    desktop = pyatspi.Registry.getDesktop(0)
    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

      if self.window_manager.supportsScreenCoords(self.last_selected):
        coord_type = pyatspi.DESKTOP_COORDS
      else:
        coord_type = pyatspi.WINDOW_COORDS
        x, y = self.window_manager.convertScreenToWindowCoords(x, y, self.last_selected)

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

        if ci.contains(x, y, coord_type) 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}
    '''
    if self.window_manager.supportsScreenCoords(parent):
      coord_type = pyatspi.DESKTOP_COORDS
    else:
      coord_type = pyatspi.WINDOW_COORDS
      x, y = self.window_manager.convertScreenToWindowCoords(x, y, parent)

    container = parent
    inner_container = None
    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, coord_type)
      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