AXElements 0.7.8 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +1 -10
- data/README.markdown +7 -14
- data/ext/accessibility/key_coder/key_coder.c +7 -0
- data/lib/AXElements.rb +0 -2
- data/lib/accessibility/core.rb +180 -123
- data/lib/accessibility/dsl.rb +310 -191
- data/lib/accessibility/enumerators.rb +9 -8
- data/lib/accessibility/errors.rb +7 -8
- data/lib/accessibility/factory.rb +16 -9
- data/lib/accessibility/graph.rb +68 -22
- data/lib/accessibility/highlighter.rb +86 -0
- data/lib/accessibility/pp_inspector.rb +4 -4
- data/lib/accessibility/qualifier.rb +11 -9
- data/lib/accessibility/string.rb +12 -4
- data/lib/accessibility/translator.rb +19 -10
- data/lib/accessibility/version.rb +3 -1
- data/lib/accessibility.rb +42 -17
- data/lib/ax/application.rb +90 -30
- data/lib/ax/button.rb +5 -2
- data/lib/ax/element.rb +133 -149
- data/lib/ax/pop_up_button.rb +12 -0
- data/lib/ax/radio_button.rb +5 -2
- data/lib/ax/row.rb +2 -2
- data/lib/ax/static_text.rb +5 -2
- data/lib/ax/systemwide.rb +24 -12
- data/lib/ax_elements/awesome_print.rb +13 -0
- data/lib/ax_elements/exception_workaround.rb +5 -0
- data/lib/ax_elements/nsarray_compat.rb +1 -0
- data/lib/ax_elements.rb +2 -1
- data/lib/minitest/ax_elements.rb +60 -4
- data/lib/mouse.rb +47 -20
- data/lib/rspec/expectations/ax_elements.rb +180 -88
- data/rakelib/doc.rake +7 -0
- data/test/helper.rb +2 -1
- data/test/integration/accessibility/test_dsl.rb +126 -18
- data/test/integration/accessibility/test_errors.rb +1 -1
- data/test/integration/ax/test_element.rb +17 -0
- data/test/integration/minitest/test_ax_elements.rb +33 -38
- data/test/integration/rspec/expectations/test_ax_elements.rb +68 -19
- data/test/sanity/accessibility/test_core.rb +45 -37
- data/test/sanity/accessibility/test_highlighter.rb +56 -0
- data/test/sanity/ax/test_application.rb +8 -0
- data/test/sanity/ax/test_element.rb +7 -3
- data/test/sanity/minitest/test_ax_elements.rb +2 -0
- data/test/sanity/rspec/expectations/test_ax_elements.rb +3 -0
- data/test/sanity/test_accessibility.rb +9 -0
- data/test/sanity/test_mouse.rb +2 -2
- metadata +11 -38
- data/docs/AccessibilityTips.markdown +0 -119
- data/docs/Acting.markdown +0 -340
- data/docs/Debugging.markdown +0 -165
- data/docs/Inspecting.markdown +0 -261
- data/docs/KeyboardEvents.markdown +0 -122
- data/docs/NewBehaviour.markdown +0 -151
- data/docs/Notifications.markdown +0 -271
- data/docs/Searching.markdown +0 -250
- data/docs/TestingExtensions.markdown +0 -52
- data/docs/images/all_the_buttons.jpg +0 -0
- data/docs/images/next_version.png +0 -0
- data/docs/images/ui_hierarchy.dot +0 -34
- data/docs/images/ui_hierarchy.png +0 -0
- data/lib/accessibility/debug.rb +0 -164
- data/test/integration/accessibility/test_debug.rb +0 -44
- data/test/sanity/accessibility/test_debug.rb +0 -63
data/docs/NewBehaviour.markdown
DELETED
@@ -1,151 +0,0 @@
|
|
1
|
-
# Adding Behaviour
|
2
|
-
|
3
|
-
Sometimes it is necessary to add extra methods to a UI
|
4
|
-
element. There are a few cases of this in the AXElements source code
|
5
|
-
itself, but many more opportunities exist. Unfortunately, extending UI
|
6
|
-
element classes in AXElements is not totally straightforward. Some of
|
7
|
-
the implementation details need to be understood before you can
|
8
|
-
successfully extend AXElements.
|
9
|
-
|
10
|
-
## Laziness
|
11
|
-
|
12
|
-
In a laziness contest between AXElements and Garfield, AXElements
|
13
|
-
wins (I assume). A lot of data that AXElements needs to be processed,
|
14
|
-
such as name translation, and the work for this is delayed until it
|
15
|
-
needs to be done in order to avoid a very large amount of overhead at
|
16
|
-
boot time. Not all parts of AXElements are lazy, at least not yet.
|
17
|
-
|
18
|
-
## Class Hierarchy
|
19
|
-
|
20
|
-
At run time you will have noticed that you were returned objects which
|
21
|
-
have a class like `AX::StandardWindow`, but you can never find the
|
22
|
-
definition of the class in the source code. This is because the class
|
23
|
-
hierarchy is lazily defined.
|
24
|
-
|
25
|
-
### Deciding The Class Name
|
26
|
-
|
27
|
-
The first thing to understand is the way that AXElements decides what
|
28
|
-
class to instantiate for a UI element. This is actually pretty
|
29
|
-
simple. Each UI element has a `role` attribute that is supposed to be
|
30
|
-
used by assistive applications (read: AXElements) to understand which
|
31
|
-
attributes will likely be available. This is Apple's hint as to what
|
32
|
-
kind of class structure to choose.
|
33
|
-
|
34
|
-
However, some UI elements also have a `subrole` attribute. For
|
35
|
-
instance, `AXStandardWindow` is a subrole attribute that a UI element
|
36
|
-
can have if it has a `role` of `AXWindow`. Once again, this is a hint
|
37
|
-
that has been built into the system, and AXElements follows these
|
38
|
-
hints. Put into object-oriented terms, if an object has a `subrole`,
|
39
|
-
then that `subrole` becomes the class for the object and the `role`
|
40
|
-
becomes the superclass; if the object does not have a `subrole`, then
|
41
|
-
the class of the object will be decided by the `role`.
|
42
|
-
|
43
|
-
### Abstract Base
|
44
|
-
|
45
|
-
In either case, the {AX::Element} class will be an ancestor for the
|
46
|
-
class that is chosen. A class that is its `subrole` will always have a
|
47
|
-
superclass that is its `role`, and a class that is a `role` will
|
48
|
-
always have {AX::Element} as its superclass.
|
49
|
-
|
50
|
-
The advantage to creating this hierarchy is that it becomes much
|
51
|
-
easier to implement searches that can find a "kind of" object. For
|
52
|
-
instance, you can search for `text_fields` and find `AX::TextField`
|
53
|
-
objects as well as `AX::SecureTextField` objects. This is one of the
|
54
|
-
more powerful features outlined in the
|
55
|
-
{file:docs/Searching.markdown Searching tutorial}.
|
56
|
-
|
57
|
-
### Why Lazy?
|
58
|
-
|
59
|
-
Laziness was chosen as it makes the library more resilient to custom
|
60
|
-
roles and subroles. However, it also allows the MacRuby run time to
|
61
|
-
boot faster since it avoids having to load all the different classes
|
62
|
-
that would need to be defined.
|
63
|
-
|
64
|
-
## Explicitly Defining Classes
|
65
|
-
|
66
|
-
Now that you understand how classes are structured it should be very
|
67
|
-
obvious how you should name your classes and choose your
|
68
|
-
superclass. However, you could also use this technique to customize
|
69
|
-
the hierarchy from what Apple has defined. For instance, you could force a
|
70
|
-
`AXPopUpButton` to be a subclass of `AXButton` even though Apple has
|
71
|
-
declared them to be separate roles. This may or may not be convenient
|
72
|
-
depending on what custom methods you wish to add.
|
73
|
-
|
74
|
-
## Reasons To Add A Custom Method
|
75
|
-
|
76
|
-
For this topic I can only lead by example. Fortunately AXElements has
|
77
|
-
a few examples to show off. The full list is in
|
78
|
-
`lib/ax_elements/elements`, but I'll go over a few here.
|
79
|
-
|
80
|
-
### Application Objects
|
81
|
-
|
82
|
-
{AX::Application} has been extended in a couple of ways. First off, in
|
83
|
-
order to provide an object oriented interface to sending keyboard
|
84
|
-
events I added {AX::Application#type_string}.
|
85
|
-
|
86
|
-
However, the big change with {AX::Application} is the merging of
|
87
|
-
functionality from `NSRunningApplication`. In order to provide methods
|
88
|
-
to set focus to an application I had to cache the
|
89
|
-
`NSRunningApplication` instance at initialization and forward some
|
90
|
-
method calls to that object. For instance, when you call `#set_focus`
|
91
|
-
and pass an application object, AXElements does not actually using
|
92
|
-
accessibility to set focus to the application, it uses the
|
93
|
-
`NSRunningApplication` class internally still support the
|
94
|
-
functionality in a transparent way.
|
95
|
-
|
96
|
-
### Overriding `#==`
|
97
|
-
|
98
|
-
The most popular customization to make is to overload `#==` for an
|
99
|
-
object class that provides a more natural interface, and also one that
|
100
|
-
makes search much more flexible. There is more than one example in
|
101
|
-
this case, you could look at {AX::StaticText#==} which allows you
|
102
|
-
check equality against a string that equal to the `value` attribute
|
103
|
-
for the static text object. Similarly, {AX::Button#==} was added to
|
104
|
-
check equality with buttons.
|
105
|
-
|
106
|
-
### Table Rows
|
107
|
-
|
108
|
-
When working with `AX::Table` objects you may have issues identifying
|
109
|
-
children that belong to a specific column. Children of table rows, in
|
110
|
-
my experience, do not have descriptions identifying which column they
|
111
|
-
belong to. In cases where you have no unique identifier for a child,
|
112
|
-
such as if you have multiple check boxes, there is no good way to find
|
113
|
-
a specific check box using the built in search mechanics.
|
114
|
-
|
115
|
-
You could hope that the column order never changes and just use the
|
116
|
-
index of the children array but that is fragile; or perhaps you actually
|
117
|
-
know what the order of the columns is to begin with and were able to
|
118
|
-
keep track of how they changed.
|
119
|
-
|
120
|
-
A much more sane way to identify the child is by identifying the
|
121
|
-
column that the child belongs to. For instance, a the column for a
|
122
|
-
table is an `AX::Column` object that usually has a `header` or `title`
|
123
|
-
attribute which will be unique for the table. For this case,
|
124
|
-
AXElements includes the {AX::Row#child_in_column} method which
|
125
|
-
provides something similar to a search but with the few extra steps
|
126
|
-
that would be necessary to correlate the child to the column and then
|
127
|
-
return the child that you wanted.
|
128
|
-
|
129
|
-
### More
|
130
|
-
|
131
|
-
There are likely other cases that I have not come across yet which
|
132
|
-
would be significantly simplified by a helper method or the merging of
|
133
|
-
functionality from another class. Don't be afraid to share your
|
134
|
-
extensions.
|
135
|
-
|
136
|
-
## Tests
|
137
|
-
|
138
|
-
If you are adding new features to AXElements then you should add tests
|
139
|
-
for the new features and also make sure that you don't break existing
|
140
|
-
features without realizing it.
|
141
|
-
|
142
|
-
Running the test suite is covered in the {file:README.markdown}.
|
143
|
-
|
144
|
-
Figuring out the test suite internals may not be easy, there is a bit
|
145
|
-
of duplication, and something things need better organization. The
|
146
|
-
test suite isn't well documented (on purpose) so you will have to read
|
147
|
-
some of the other code to understand how things should work before
|
148
|
-
writing your own tests. Be careful not to introduce state dependencies
|
149
|
-
between tests or else you will not have a fun time tracking down why a
|
150
|
-
certain test seems fail occassionally (which is a problem I had with
|
151
|
-
notifications tests).
|
data/docs/Notifications.markdown
DELETED
@@ -1,271 +0,0 @@
|
|
1
|
-
# Notifications
|
2
|
-
|
3
|
-
@todo This document needs to be audited for accuracy. The interface
|
4
|
-
for notifications is in flux and this document might be out of sync
|
5
|
-
with the current implementation.
|
6
|
-
|
7
|
-
Accessibility notifications are a tool that you can use to add a delay
|
8
|
-
in script that waits until a certain event happens. These events could
|
9
|
-
be a window being created, a menu being opened, or one of a plethora
|
10
|
-
of other events.
|
11
|
-
|
12
|
-
A notification is much more time efficient than simply using `sleep`
|
13
|
-
to estimate how long an action will take to complete. Sleep often
|
14
|
-
causes you to wait longer than is necessary to avoid waiting less time
|
15
|
-
than what is needed. With notifications you set a maximum amount of
|
16
|
-
time that should be waited, called the timeout, but waiting is stopped
|
17
|
-
as soon as the notification is received. This makes using
|
18
|
-
notifications preferable to simply sleeping.
|
19
|
-
|
20
|
-
An important thing to understand is that an accessibility notification
|
21
|
-
is separate from the notifications that are sent and received
|
22
|
-
internally in a Cocoa app. Accessibility notifications are not very
|
23
|
-
different in concept, they differ largely in mechanics. This is
|
24
|
-
because talking to another application,
|
25
|
-
[across processes](http://en.wikipedia.org/wiki/Inter-process_communication),
|
26
|
-
is expensive and would be impractical to do for every notification
|
27
|
-
that is sent internally in an application. This makes a bit more
|
28
|
-
difficult to track down what notification is going to be sent through
|
29
|
-
accessibility; using notifications more difficult than simply using
|
30
|
-
`sleep`, at least until you have a lot of practice.
|
31
|
-
|
32
|
-
## Simple Example
|
33
|
-
|
34
|
-
Consider a log in window. You enter some credentials and then you
|
35
|
-
press a button to log in and load up the main application window.
|
36
|
-
This procedure is most likely asynchronous, it usually takes one or
|
37
|
-
two seconds, but could take longer. To script logging in you would
|
38
|
-
have to wait until the main application window loaded. You could just
|
39
|
-
use `Kernel#sleep`, but how long should you sleep for and how reliable
|
40
|
-
is that? How much time would that waste?
|
41
|
-
|
42
|
-
Notifications are perfect for this scenario; whenever a window is
|
43
|
-
created, the application sends out a notification to any accessibility
|
44
|
-
applications that are listening letting them know a window was
|
45
|
-
created; the notification will even include the object that sent the
|
46
|
-
notification and the name of the notification. Using this system, we
|
47
|
-
can just wait for the notification to be received and continue
|
48
|
-
executing the script right away (technically, there can be few
|
49
|
-
milliseconds of delay). A typical example of using notifications looks
|
50
|
-
like this:
|
51
|
-
|
52
|
-
register_for_notification app, :window_created
|
53
|
-
login # trigger an action that will eventually send a notification
|
54
|
-
wait_for_notification
|
55
|
-
|
56
|
-
And that's it.
|
57
|
-
|
58
|
-
## One-Two-Three Combo
|
59
|
-
|
60
|
-
Using notifications is a three step process: first a left jab to set up
|
61
|
-
the notification, then you dodge the counter punch, and then you
|
62
|
-
finish with a right cross to wait for the notification. If you're left
|
63
|
-
handed then you can jab with the right and cross with the left (but
|
64
|
-
you still setup first and then wait).
|
65
|
-
|
66
|
-
### First You Set It Up
|
67
|
-
|
68
|
-
Setup is the important step and is fairly painless as far as what
|
69
|
-
you need to provide for AXElements. To register for a notification you
|
70
|
-
call {Accessibility::Language#register_for_notification}. You pass the
|
71
|
-
the {AX::Element} who will be sending the notification and then the
|
72
|
-
notification name. Another simple example would be:
|
73
|
-
|
74
|
-
register_for_notification text_field, :value_changed
|
75
|
-
|
76
|
-
In this hypothetical case, we want to wait for a `:value_changed`
|
77
|
-
notification that will be sent by `text_field`. That's it. And that
|
78
|
-
also covers 90% of the cases where you will use notifications.
|
79
|
-
|
80
|
-
#### Applications Are Special
|
81
|
-
|
82
|
-
Listening from the application, an {AX::Application}, object is a
|
83
|
-
special case. When you say that an {AX::Application} object will be
|
84
|
-
the sender, what you are saying is that any UI element in the app will
|
85
|
-
be the sender and you don't really care as long as you get the right
|
86
|
-
notification name (that's not entirely true, but a good enough lie for
|
87
|
-
now).
|
88
|
-
|
89
|
-
#### Notification Names or: How Is Babby Formed?
|
90
|
-
|
91
|
-
Like attribute and action name translation, notification names are
|
92
|
-
also translated for your convenience. However, where do you get the
|
93
|
-
notification names that Apple provides?
|
94
|
-
|
95
|
-
It turns out that there is no API for getting a list of notifications
|
96
|
-
that an element can send---Apple dropped the ball in this case, but
|
97
|
-
they did provide a
|
98
|
-
[list of notifications](http://developer.apple.com/library/mac/#documentation/UserExperience/Reference/Accessibility_RoleAttribute_Ref/Notifications.html#//apple_ref/doc/uid/TP40007870-Notifications-SW1)
|
99
|
-
which has been nicely updated for Mac OS X Lion. The list of
|
100
|
-
notifications is __required__ reading if you want to use accessibility
|
101
|
-
notifications.
|
102
|
-
|
103
|
-
For custom notifications, which I'll get to in a bit, you shouldn't
|
104
|
-
(read: don't) do a name translation unless you provide an equivalent
|
105
|
-
constant for the MacRuby run time. Instead, you should just provide
|
106
|
-
the string with the notification name:
|
107
|
-
|
108
|
-
register_for_notification app, 'customNotification'
|
109
|
-
register_for_notification app, 'Cheezburger''
|
110
|
-
|
111
|
-
### Do Something
|
112
|
-
|
113
|
-
To trigger the action, you perform an action. This was already covered
|
114
|
-
in the {file:docs/Acting.markdown Acting tutorial}, which is probably
|
115
|
-
why you are reading this tutorial.
|
116
|
-
|
117
|
-
### Waiting...
|
118
|
-
|
119
|
-
Once the action has been triggered, you simply need to wait for the
|
120
|
-
notification to be received:
|
121
|
-
|
122
|
-
wait_for_notification
|
123
|
-
|
124
|
-
And then you wait (if you are the computer or just enjoy watching your
|
125
|
-
scripts execute). If all goes well you will receive the notification,
|
126
|
-
and the script continues on to the next statement. By default,
|
127
|
-
AXElements will wait for 10 seconds, and if the notification is not
|
128
|
-
received then the script will continue anyways.
|
129
|
-
|
130
|
-
Waiting for longer, or less, time than the default can be done by
|
131
|
-
passing a parameter to `wait_for_notification`. For instance, you
|
132
|
-
could wait for a minute or a second:
|
133
|
-
|
134
|
-
wait_for_notification 60.0
|
135
|
-
wait_for_notification 1.0
|
136
|
-
|
137
|
-
## A Bit More Complicated
|
138
|
-
|
139
|
-
The above should cover most cases, but notifications can be more
|
140
|
-
complex than those examples. You might need to unregister for
|
141
|
-
notifications, or add more complex logic to a notification
|
142
|
-
registration.
|
143
|
-
|
144
|
-
### Unregister Notifications
|
145
|
-
|
146
|
-
If you need to unregister for a notifications then you can. The DSL
|
147
|
-
layer provides {Accessibility::Language#unregister_notifications}, and
|
148
|
-
hopefully more options in the future.
|
149
|
-
|
150
|
-
Using `unregister_notifications`, you can unregister _all_
|
151
|
-
notifications. This is actually done in cases where a notification is
|
152
|
-
not received, simply to clear out possible problems with lingering
|
153
|
-
registrations that might mess up future waiting.
|
154
|
-
|
155
|
-
### You Are The Decider
|
156
|
-
|
157
|
-
When you setup a notification you can optionally pass a block that
|
158
|
-
decides whether or not the received notification is the one you
|
159
|
-
wanted. You will be given the element that sent the notification,
|
160
|
-
which is not necessarily the element that you registered with, and the
|
161
|
-
name of the notification. In this case you would register for a
|
162
|
-
notification like so:
|
163
|
-
|
164
|
-
register_for_notification app, :window_created do |window, notification|
|
165
|
-
window.title == 'New Contact'
|
166
|
-
end
|
167
|
-
|
168
|
-
This gives you some extra security. Instead of continuing the script
|
169
|
-
execution after getting the first `:window_created` notification, the
|
170
|
-
script will remain paused until the element that sends the
|
171
|
-
notification has a title of `'New Contact'`.
|
172
|
-
|
173
|
-
The contract is quite simple. If you do not pass a block, then the
|
174
|
-
first notification received will cause AXElements to stop waiting; if
|
175
|
-
you want to pass a block then the block must return tuthy or falsy
|
176
|
-
to decide whether the received notification is the one that you
|
177
|
-
wanted. You will be given the sender of the notification as well as
|
178
|
-
the notification name; though the notification name will usually not
|
179
|
-
be very interesting since it is the value you registered with.
|
180
|
-
|
181
|
-
### More Than One Notification
|
182
|
-
|
183
|
-
You can register for as many notifications as you want, but I don't
|
184
|
-
recommend that you register for more than one at a time unless you
|
185
|
-
know what you are doing. The reason why is that when you call
|
186
|
-
`wait_for_notification`, you are pausing script execution until any of
|
187
|
-
the notifications you have registered for are received. The block that
|
188
|
-
you pass will only be used for notifications that are sent to the
|
189
|
-
registration you set it up with.
|
190
|
-
|
191
|
-
This is why AXElements unregisters everything if it fails to receive a
|
192
|
-
notification. This detail might also screw you over if you are trying
|
193
|
-
to work with more than one notification at once and should be fixed in
|
194
|
-
the future.
|
195
|
-
|
196
|
-
### Rainbows And Unicorns
|
197
|
-
|
198
|
-
As mentioned earlier, accessibility notifications are not all rainbows
|
199
|
-
and unicorns. If you've played with notifications while going through
|
200
|
-
this tutorial then you may have noticed a problem or two with the
|
201
|
-
design.
|
202
|
-
|
203
|
-
#### Who Sends The Notification
|
204
|
-
|
205
|
-
Documentation is the only hint you really get about who will send
|
206
|
-
particular notifications. If you are confident that a certain
|
207
|
-
notification will be sent but do not know who will be sending the
|
208
|
-
notification, then you can register with the {AX::Application} object
|
209
|
-
to be the sender of the notification. This causes you to receive
|
210
|
-
notifications from any object as long as it has the proper
|
211
|
-
notification type. If you use this feature in conjunction with passing
|
212
|
-
a block that always returns false, then you can capture all the
|
213
|
-
notifications for a period of time and find out who is sending the
|
214
|
-
notification. An example would look something like this:
|
215
|
-
|
216
|
-
register_for_notification app, :value_changed do |element, notif|
|
217
|
-
puts element.inspect
|
218
|
-
false
|
219
|
-
end
|
220
|
-
|
221
|
-
# trigger some action
|
222
|
-
|
223
|
-
wait_for_notification 30.0
|
224
|
-
|
225
|
-
Since you are returning `false` at the end of the block, the script
|
226
|
-
will pause until a timeout occurs and you will see the `#inspect`
|
227
|
-
output for each object that sends the notification.
|
228
|
-
|
229
|
-
### What Is Known To Work
|
230
|
-
|
231
|
-
- `:value_changed` is only sent by objects that have a `value`
|
232
|
-
attribute, and should be sent every time that the value is changed,
|
233
|
-
whether through accessibility or normal computer usage
|
234
|
-
- `:menu_opened` seems to only be sent by menu bar menus, not by menus
|
235
|
-
opened by pop up buttons
|
236
|
-
|
237
|
-
## Custom Notifications
|
238
|
-
|
239
|
-
Custom notifications are pretty simple. Apple recommends that you try
|
240
|
-
to use the built in notifications as much as possible, but
|
241
|
-
acknowledges that you may need to add your own notifications
|
242
|
-
sometimes.
|
243
|
-
|
244
|
-
As noted earlier, accessibility notifications use a different system
|
245
|
-
than cocoa notifications; accessibility notifications are much simpler
|
246
|
-
to send; it is simply a C function call with two parameters:
|
247
|
-
|
248
|
-
NSAccessibilityPostNotification(self, @"Cheezburger");
|
249
|
-
|
250
|
-
The first parameter is the sender of the notification; just as with
|
251
|
-
other accessibility APIs from the server (read: app) side of things,
|
252
|
-
you pass the actual object and accessibility magic will create the
|
253
|
-
token to send to the client (read: AXElements). The one caveat in this
|
254
|
-
case is that the sender of the notification cannot be an object that is
|
255
|
-
ignored by accessibility or else the notification will not be sent. If
|
256
|
-
you are unsure whether or not the object is visible to accessibility,
|
257
|
-
then you need to call an extra function to find someone who _will_ be
|
258
|
-
visible to accessibility; these functions are listed in the
|
259
|
-
[AppKit Functions](http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Miscellaneous/AppKit_Functions/Reference/reference.html#//apple_ref/doc/uid/TP40004154)
|
260
|
-
in the Accessibility section. An example would look like this:
|
261
|
-
|
262
|
-
NSAccessibilityPostNotification(NSAccessibilityUnignoredAncestor(self), @"Cheezburger");
|
263
|
-
|
264
|
-
In this case you would be looking up the hierarchy for an ancestor,
|
265
|
-
but you could also look down the hierarchy for a descendant or to the
|
266
|
-
side for a sibling.
|
267
|
-
|
268
|
-
As a rule of thumb, if you end up spending more 20minutes trying to
|
269
|
-
figure out which notification will should be listening for, and who
|
270
|
-
will be sending it, then you should just create a custom
|
271
|
-
notification. But you don't have to take my word for it.
|
data/docs/Searching.markdown
DELETED
@@ -1,250 +0,0 @@
|
|
1
|
-
# Searching
|
2
|
-
|
3
|
-
Searching the view hierarchy is the most powerful idea that this
|
4
|
-
toolkit provides by _significantly_ simplifying the process of
|
5
|
-
describing UI elements. Search works by looking at the child elements
|
6
|
-
of the current element, and possibly at the grandchildren, and so on
|
7
|
-
and so forth in a breadth first searching order. There are a few well
|
8
|
-
defined features of searching that make it powerful: pluralization,
|
9
|
-
attribute filtering, nesting, and element attribute inference.
|
10
|
-
|
11
|
-
## Search Basics
|
12
|
-
|
13
|
-
First, the basic form of a search looks like this:
|
14
|
-
|
15
|
-
$ELEMENT.$KLASS($FILTER_ATTRIBUTE1: $FILTER_VALUE1, ...)
|
16
|
-
|
17
|
-
Actually, that is just the form for an implicit search, which is a
|
18
|
-
little nicer to write than an explicit search. The only difference
|
19
|
-
between the two is that an implicit search must always find something,
|
20
|
-
since we are treating it as a description, or else a
|
21
|
-
{AX::Element::SearchFailure} error will be raised.
|
22
|
-
|
23
|
-
Looking at the pieces of the statement, the obvious piece is
|
24
|
-
`$ELEMENT`; `$ELEMENT` is the element to start searching from. Then
|
25
|
-
`$KLASS`, the method name, is the class of the object to search for,
|
26
|
-
and the parameter list is just a hash where the key,
|
27
|
-
`$FILTER_ATTRIBUTE`, is an attribute on the instance of the class and
|
28
|
-
the value, `$FILTER_VALUE`, needs to match what is returned when the
|
29
|
-
attribute is looked up.
|
30
|
-
|
31
|
-
An implicit search is written as if the class of the object you were
|
32
|
-
searching for was the method and the filters would be the parameters
|
33
|
-
for the method. If we substitute real values in, an example would like
|
34
|
-
this:
|
35
|
-
|
36
|
-
window.button(title: 'Main Window')
|
37
|
-
|
38
|
-
Which means that we want to find a `button` that is a child (or
|
39
|
-
descendant) of `window`, but only if the `button` has a `title` of
|
40
|
-
`'Main Window'` on it. You can add as many filters as you want, but
|
41
|
-
generally you will only need one.
|
42
|
-
|
43
|
-
### Pluralization
|
44
|
-
|
45
|
-
In cases where you want to find more than one object of a certain
|
46
|
-
type, you simply need to pluralize the method name. For example:
|
47
|
-
|
48
|
-
window.buttons
|
49
|
-
|
50
|
-
is translated into something like this:
|
51
|
-
|
52
|
-
![All The Buttons](images/all_the_buttons.jpg)
|
53
|
-
|
54
|
-
It's just that easy. The rules for pluralization are the same as
|
55
|
-
English (or the local language?) since we are using the
|
56
|
-
`ActiveSupport::Inflector` to do the work of translating from
|
57
|
-
pluralized form back to the singular form. Even something like 'boxes'
|
58
|
-
will get translated back to 'box' and work as you expect.
|
59
|
-
|
60
|
-
Except for the fact that you will get a collection of UI elements back
|
61
|
-
instead of a single item, pluralized search works the same as a
|
62
|
-
non-pluralized search. You can attach any filters you could use with a
|
63
|
-
non-pluralized search, and if the search is implicit then it must find
|
64
|
-
something.
|
65
|
-
|
66
|
-
Pluralized searches are useful when you want to do some custom
|
67
|
-
refinement on a search, or if you need to make sure something is not
|
68
|
-
in the UI element tree (and hopefully that means it is not on the
|
69
|
-
screen anymore either). It can also be helpful when you want to
|
70
|
-
explore the UI element tree and find out what types of UI elements are
|
71
|
-
present on the screen. The detail to remember is that a pluralized
|
72
|
-
search will have to explore the entire UI sub-tree from the starting
|
73
|
-
point and so it could be slow.
|
74
|
-
|
75
|
-
### Kind Of
|
76
|
-
|
77
|
-
Remember earlier when I said the method name should be the class of
|
78
|
-
the element being searched for? Well, that was kind of a lie. Kind of.
|
79
|
-
Get it? Have I killed the joke yet? Kind of? :D
|
80
|
-
|
81
|
-
The search class that you enter is actually matched using `#kind_of?`
|
82
|
-
instead of matching the classes exactly. And since class hierarchies
|
83
|
-
are properly setup, you can search for a base class and end up finding
|
84
|
-
subclasses.
|
85
|
-
|
86
|
-
For instance, not all buttons are of the class `AX::Button`, the
|
87
|
-
traffic light buttons are all different subclasses of `AX::Button`:
|
88
|
-
`AX::CloseButton`, `AX::MinimizeButton`, and `AX::ZoomButton`. There
|
89
|
-
are other several other subclasses of `AX::Button` as
|
90
|
-
well. `AX::Button` itself is a subclass of `AX::Element`. Actually,
|
91
|
-
all UI elements are a subclass of `AX::Element`. What this means is
|
92
|
-
that when you have code like:
|
93
|
-
|
94
|
-
app.close_button
|
95
|
-
|
96
|
-
You will only ever find something that is a `AX::CloseButton`, but
|
97
|
-
when you write something like:
|
98
|
-
|
99
|
-
app.button
|
100
|
-
|
101
|
-
Any button or subclass of button, including all the traffic light
|
102
|
-
buttons, can be found. I believe this makes search follow the
|
103
|
-
[DWIM](http://en.wikipedia.org/wiki/DWIM) principle, and allows you to
|
104
|
-
shorten the code you need to write in a number of cases. For example:
|
105
|
-
|
106
|
-
app.window
|
107
|
-
|
108
|
-
Can be substituted in place of
|
109
|
-
|
110
|
-
app.standard_window
|
111
|
-
|
112
|
-
to find the first window for `app`. This makes sense if there is only
|
113
|
-
one window for the app, which is often the case. Similarly, if you are
|
114
|
-
searching from a container, such as an `AX::Group`, which only has a
|
115
|
-
one button, which happens to be a `AX::SortButton`, then you can say:
|
116
|
-
|
117
|
-
table.button
|
118
|
-
|
119
|
-
Since it will not be ambiguous and AXElements knows what you
|
120
|
-
mean. What if we take it a step further, what if made an even broader
|
121
|
-
search. Since _all_ UI elements are a subclass of {AX::Element}, we
|
122
|
-
could just write something like:
|
123
|
-
|
124
|
-
app.element
|
125
|
-
|
126
|
-
Which would find the first child of `app`. If we combined this with
|
127
|
-
pluralization, we could do something like:
|
128
|
-
|
129
|
-
app.elements
|
130
|
-
|
131
|
-
Which will return an array with _all_ the descendants in it; so as always
|
132
|
-
you will need to have some awareness of the layout of the element tree
|
133
|
-
when you write a search. Otherwise you could end up finding something
|
134
|
-
completely different from what you wanted; consider what could happen
|
135
|
-
if you search for an `AX::Button` objects when you want an
|
136
|
-
`AX::CloseButton`. In these cases you will want to be more specific
|
137
|
-
about what you are looking for, which can often allow you to skip the
|
138
|
-
need for a search filter. Be specific when it sounds better and
|
139
|
-
generalize more when you can, it should make code read more
|
140
|
-
naturally.
|
141
|
-
|
142
|
-
### Nested Searching
|
143
|
-
|
144
|
-
Sometimes you need to describe an element on the screen, and the only
|
145
|
-
reasonable way to do so is by describing some of the descendants of the
|
146
|
-
element that you are looking for. For this requirement, nested
|
147
|
-
searching exists, and very naturally too. Pretend that you didn't
|
148
|
-
already look at the next code snippet and try to guess what a nested
|
149
|
-
search looks like; you will probably be correct. __Hint__: Searches
|
150
|
-
can be nested arbitrarily deep and mixed in with other filter
|
151
|
-
parameters. The answer, by example:
|
152
|
-
|
153
|
-
window.outline.row(text_field: { value: 'Calendar' })
|
154
|
-
|
155
|
-
This would be asking for the outline row that has a text field with
|
156
|
-
the value of `'Calendar'`. The proper form for this would be:
|
157
|
-
|
158
|
-
$ELEMENT.$KLASS($DESCENDANT: { $DESCENDANT_FILTER_ATTRIBUTE: $DESCENDANT_FILTER_VALUE1, ... }, ...)
|
159
|
-
|
160
|
-
Where `$DESCENDANT` plays the same role as `$KLASS`, but for a new
|
161
|
-
search that will be applied to descendants of `$KLASS`. Nested
|
162
|
-
searching is a feature you won't need too often if the UI hierarchy
|
163
|
-
makes good use of identifiers or other attributes that can be used to
|
164
|
-
uniquely identify an element. Nested searching is best used when you
|
165
|
-
can only identify an element by describing its children. But you don't
|
166
|
-
have to take my word for it.
|
167
|
-
|
168
|
-
### Element Attribute Inference
|
169
|
-
|
170
|
-
Element attribute inference came about because of a coding error, when
|
171
|
-
someone was trying to write some code to search for element that
|
172
|
-
matched to a title UI element. Before going over the solution I think
|
173
|
-
it would be best to explain the problem.
|
174
|
-
|
175
|
-
If an element has a title UI element attribute, then the title UI
|
176
|
-
element will end up being another UI element. The problem with this is
|
177
|
-
that you then need to know about that element before you search and
|
178
|
-
then you need to use the element as the filter value, for example:
|
179
|
-
|
180
|
-
title_field = window.text_field(value: 'Name')
|
181
|
-
window.button(title_ui_element: title_field)
|
182
|
-
|
183
|
-
While that code is legitimate, it is not the most succinct way of
|
184
|
-
writing what was meant, and maybe not as clear as it could be. Perhaps
|
185
|
-
something more like:
|
186
|
-
|
187
|
-
window.button(title_ui_element: 'Name')
|
188
|
-
|
189
|
-
In this case you are matching the `title` of the
|
190
|
-
`title_ui_element`. This works without introducing inconsistencies in
|
191
|
-
the language we have created for searching. In the example, you would
|
192
|
-
be saying that the button you are looking for will be associated with
|
193
|
-
a title UI element that says `'Name'`. You can still match against the
|
194
|
-
actual UI element if you want, but I think this is much simpler.
|
195
|
-
|
196
|
-
This is not actually implemented internally to the searching
|
197
|
-
logic, it is implemented in each class that wants to
|
198
|
-
participate. Since search works by getting the value of the search
|
199
|
-
filter and checking if it is `==` to the filter value, we just need
|
200
|
-
to implement `==` on specific classes where we want to support custom
|
201
|
-
behaviour. Examples of how this would work would be
|
202
|
-
{AX::StaticText#==} and {AX::Button#==}. The behaviour could be easily
|
203
|
-
added to other classes where it made sense. However, this type of idea
|
204
|
-
can be further expanded to cases that might not be so easily
|
205
|
-
implemented and is discussed in future prospects.
|
206
|
-
|
207
|
-
## Explicit Search
|
208
|
-
|
209
|
-
In the off chance that you need to make an explicit search, you can
|
210
|
-
trigger a search through {AX::Element#search}. In this case you give
|
211
|
-
the `$KLASS` as the first parameter of the method and the filters are
|
212
|
-
the remaining parameters. As with {AX::Element#attribute}, this is
|
213
|
-
meant for performance in cases of heavy searching; you should avoid
|
214
|
-
using it unless you know what you are doing.
|
215
|
-
|
216
|
-
## Caveats
|
217
|
-
|
218
|
-
The only caveat to note right now is that a UI element will always
|
219
|
-
return `false` when you ask if it can `#respond_to?` something that
|
220
|
-
would be an implicit search. This is because of the semantics of a
|
221
|
-
search do not make sense in the context of `#respond_to?` and would be
|
222
|
-
very expensive. You would need to perform the search in order to know
|
223
|
-
if the search would succeed.
|
224
|
-
|
225
|
-
## The Future
|
226
|
-
|
227
|
-
Right now, the only case that is not handled is filtering by
|
228
|
-
parameterized attribute. The problem with this case is that I am not
|
229
|
-
sure how to work it into the existing syntax or how to change the
|
230
|
-
existing syntax. Since you also need to encode the parameter with the
|
231
|
-
attribute it is difficult to express in terms of key-value
|
232
|
-
pairs. Perhaps the key could be an array so that the attribute can be
|
233
|
-
included with the key? That would be possible without too much work,
|
234
|
-
but how would it look? Does the syntax for search start to get too
|
235
|
-
crazy at that point? For instance, you cannot use the label syntax for
|
236
|
-
hash keys with an array (unfortunately), so code would look like:
|
237
|
-
|
238
|
-
window.button([:string_for_range, CFRange.new(0,5)] => 'AXEle')
|
239
|
-
|
240
|
-
This topic is open to debate, but I will always play the part of the
|
241
|
-
devil's advocate. :)
|
242
|
-
|
243
|
-
### Inference
|
244
|
-
|
245
|
-
There is also space for enhancement in the attribute inference
|
246
|
-
feature(s). Wouldn't it be nice to be able to specify a filter value
|
247
|
-
as a range instead of a specific value? Probably not very often, but it is
|
248
|
-
something that could be done. Search filtering is not very flexible in
|
249
|
-
that regard right now, and maybe it never needs to be, but it is an
|
250
|
-
interesting change to think about.
|