AXElements 0.7.8 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/.yardopts +1 -10
  2. data/README.markdown +7 -14
  3. data/ext/accessibility/key_coder/key_coder.c +7 -0
  4. data/lib/AXElements.rb +0 -2
  5. data/lib/accessibility/core.rb +180 -123
  6. data/lib/accessibility/dsl.rb +310 -191
  7. data/lib/accessibility/enumerators.rb +9 -8
  8. data/lib/accessibility/errors.rb +7 -8
  9. data/lib/accessibility/factory.rb +16 -9
  10. data/lib/accessibility/graph.rb +68 -22
  11. data/lib/accessibility/highlighter.rb +86 -0
  12. data/lib/accessibility/pp_inspector.rb +4 -4
  13. data/lib/accessibility/qualifier.rb +11 -9
  14. data/lib/accessibility/string.rb +12 -4
  15. data/lib/accessibility/translator.rb +19 -10
  16. data/lib/accessibility/version.rb +3 -1
  17. data/lib/accessibility.rb +42 -17
  18. data/lib/ax/application.rb +90 -30
  19. data/lib/ax/button.rb +5 -2
  20. data/lib/ax/element.rb +133 -149
  21. data/lib/ax/pop_up_button.rb +12 -0
  22. data/lib/ax/radio_button.rb +5 -2
  23. data/lib/ax/row.rb +2 -2
  24. data/lib/ax/static_text.rb +5 -2
  25. data/lib/ax/systemwide.rb +24 -12
  26. data/lib/ax_elements/awesome_print.rb +13 -0
  27. data/lib/ax_elements/exception_workaround.rb +5 -0
  28. data/lib/ax_elements/nsarray_compat.rb +1 -0
  29. data/lib/ax_elements.rb +2 -1
  30. data/lib/minitest/ax_elements.rb +60 -4
  31. data/lib/mouse.rb +47 -20
  32. data/lib/rspec/expectations/ax_elements.rb +180 -88
  33. data/rakelib/doc.rake +7 -0
  34. data/test/helper.rb +2 -1
  35. data/test/integration/accessibility/test_dsl.rb +126 -18
  36. data/test/integration/accessibility/test_errors.rb +1 -1
  37. data/test/integration/ax/test_element.rb +17 -0
  38. data/test/integration/minitest/test_ax_elements.rb +33 -38
  39. data/test/integration/rspec/expectations/test_ax_elements.rb +68 -19
  40. data/test/sanity/accessibility/test_core.rb +45 -37
  41. data/test/sanity/accessibility/test_highlighter.rb +56 -0
  42. data/test/sanity/ax/test_application.rb +8 -0
  43. data/test/sanity/ax/test_element.rb +7 -3
  44. data/test/sanity/minitest/test_ax_elements.rb +2 -0
  45. data/test/sanity/rspec/expectations/test_ax_elements.rb +3 -0
  46. data/test/sanity/test_accessibility.rb +9 -0
  47. data/test/sanity/test_mouse.rb +2 -2
  48. metadata +11 -38
  49. data/docs/AccessibilityTips.markdown +0 -119
  50. data/docs/Acting.markdown +0 -340
  51. data/docs/Debugging.markdown +0 -165
  52. data/docs/Inspecting.markdown +0 -261
  53. data/docs/KeyboardEvents.markdown +0 -122
  54. data/docs/NewBehaviour.markdown +0 -151
  55. data/docs/Notifications.markdown +0 -271
  56. data/docs/Searching.markdown +0 -250
  57. data/docs/TestingExtensions.markdown +0 -52
  58. data/docs/images/all_the_buttons.jpg +0 -0
  59. data/docs/images/next_version.png +0 -0
  60. data/docs/images/ui_hierarchy.dot +0 -34
  61. data/docs/images/ui_hierarchy.png +0 -0
  62. data/lib/accessibility/debug.rb +0 -164
  63. data/test/integration/accessibility/test_debug.rb +0 -44
  64. data/test/sanity/accessibility/test_debug.rb +0 -63
@@ -88,4 +88,12 @@ class TestAXApplication < MiniTest::Unit::TestCase
88
88
  assert_equal running_app.bundleIdentifier, app.bundle_identifier
89
89
  end
90
90
 
91
+ def test_info_plist
92
+ assert_equal 'hmmmmm.icns', app.info_plist['CFBundleIconFile']
93
+ end
94
+
95
+ def test_version
96
+ assert_equal '1.0', app.version
97
+ end
98
+
91
99
  end
@@ -7,6 +7,10 @@ class TestAXElement < MiniTest::Unit::TestCase
7
7
  @element ||= AX::Element.new REF
8
8
  end
9
9
 
10
+ def test_ancestry
11
+ assert_equal [element], element.ancestry
12
+ end
13
+
10
14
  def test_children # not much we can do here
11
15
  assert_respond_to element, :children
12
16
  assert_kind_of Array, element.children
@@ -38,12 +42,12 @@ class TestAXElement < MiniTest::Unit::TestCase
38
42
  def center_test position, size, expected
39
43
  element.define_singleton_method :attribute do |attr|
40
44
  case attr
41
- when :position then CGPointMake(*position)
42
- when :size then CGSizeMake(*size)
45
+ when :position then CGPoint.new(*position)
46
+ when :size then CGSize.new(*size)
43
47
  else raise ArgumentError
44
48
  end
45
49
  end
46
- assert_equal CGPointMake(*expected), element.to_point
50
+ assert_equal CGPoint.new(*expected), element.to_point
47
51
  end
48
52
 
49
53
  # the nil case
@@ -7,9 +7,11 @@ class TestMiniTestAssertions < MiniTest::Unit::TestCase
7
7
  assert_respond_to self, :assert_has_child
8
8
  assert_respond_to self, :assert_has_descendent
9
9
  assert_respond_to self, :assert_has_descendant
10
+ assert_respond_to self, :assert_shortly_has
10
11
  assert_respond_to self, :refute_has_child
11
12
  assert_respond_to self, :refute_has_descendent
12
13
  assert_respond_to self, :refute_has_descendant
14
+ assert_respond_to self, :refute_shortly_has
13
15
  end
14
16
 
15
17
  end
@@ -7,6 +7,9 @@ class TestRSpecMatchers < MiniTest::Unit::TestCase
7
7
  assert TopLevel.method(:have_child)
8
8
  assert TopLevel.method(:have_descendent)
9
9
  assert TopLevel.method(:have_descendant)
10
+ assert TopLevel.method(:shortly_have_child)
11
+ assert TopLevel.method(:shortly_have_descendent)
12
+ assert TopLevel.method(:shortly_have_descendant)
10
13
  end
11
14
 
12
15
  end
@@ -0,0 +1,9 @@
1
+ require 'test/helper'
2
+ require 'accessibility'
3
+
4
+ class TestAccessibilityDebug < MiniTest::Unit::TestCase
5
+ def test_debug_setting
6
+ assert_respond_to Accessibility, :debug?
7
+ assert_respond_to Accessibility, :debug=
8
+ end
9
+ end
@@ -7,11 +7,11 @@ class TestMouseModule < MiniTest::Unit::TestCase
7
7
  end
8
8
 
9
9
  def test_move_to
10
- point = CGPointMake(100, 100)
10
+ point = CGPoint.new(100, 100)
11
11
  Mouse.move_to point
12
12
  assert_in_delta 0, distance(point,Mouse.current_position), 1.0
13
13
 
14
- point = CGPointMake(rand(700)+150, rand(500)+100)
14
+ point = CGPoint.new(rand(700)+150, rand(500)+100)
15
15
  Mouse.move_to point
16
16
  assert_in_delta 0, distance(point,Mouse.current_position), 1.0
17
17
  end
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: AXElements
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.7.8
5
+ version: 0.8.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Mark Rada
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-13 00:00:00 Z
12
+ date: 2012-04-24 00:00:00 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -70,30 +70,15 @@ email: mrada@marketcircle.com
70
70
  executables: []
71
71
  extensions:
72
72
  - ext/accessibility/key_coder/extconf.rb
73
- extra_rdoc_files:
74
- - docs/AccessibilityTips.markdown
75
- - docs/Acting.markdown
76
- - docs/Debugging.markdown
77
- - docs/images/all_the_buttons.jpg
78
- - docs/images/next_version.png
79
- - docs/images/ui_hierarchy.dot
80
- - docs/images/ui_hierarchy.png
81
- - docs/Inspecting.markdown
82
- - docs/KeyboardEvents.markdown
83
- - docs/NewBehaviour.markdown
84
- - docs/Notifications.markdown
85
- - docs/Searching.markdown
86
- - docs/TestingExtensions.markdown
87
- - .yardopts
88
- - README.markdown
73
+ extra_rdoc_files: []
89
74
  files:
90
75
  - lib/accessibility/core.rb
91
- - lib/accessibility/debug.rb
92
76
  - lib/accessibility/dsl.rb
93
77
  - lib/accessibility/enumerators.rb
94
78
  - lib/accessibility/errors.rb
95
79
  - lib/accessibility/factory.rb
96
80
  - lib/accessibility/graph.rb
81
+ - lib/accessibility/highlighter.rb
97
82
  - lib/accessibility/pp_inspector.rb
98
83
  - lib/accessibility/qualifier.rb
99
84
  - lib/accessibility/string.rb
@@ -103,6 +88,7 @@ files:
103
88
  - lib/ax/application.rb
104
89
  - lib/ax/button.rb
105
90
  - lib/ax/element.rb
91
+ - lib/ax/pop_up_button.rb
106
92
  - lib/ax/radio_button.rb
107
93
  - lib/ax/row.rb
108
94
  - lib/ax/static_text.rb
@@ -125,8 +111,9 @@ files:
125
111
  - rakelib/gem.rake
126
112
  - rakelib/test.rake
127
113
  - Rakefile
114
+ - README.markdown
115
+ - .yardopts
128
116
  - test/integration/accessibility/test_core.rb
129
- - test/integration/accessibility/test_debug.rb
130
117
  - test/integration/accessibility/test_dsl.rb
131
118
  - test/integration/accessibility/test_enumerators.rb
132
119
  - test/integration/accessibility/test_errors.rb
@@ -138,10 +125,10 @@ files:
138
125
  - test/integration/minitest/test_ax_elements.rb
139
126
  - test/integration/rspec/expectations/test_ax_elements.rb
140
127
  - test/sanity/accessibility/test_core.rb
141
- - test/sanity/accessibility/test_debug.rb
142
128
  - test/sanity/accessibility/test_dsl.rb
143
129
  - test/sanity/accessibility/test_errors.rb
144
130
  - test/sanity/accessibility/test_factory.rb
131
+ - test/sanity/accessibility/test_highlighter.rb
145
132
  - test/sanity/accessibility/test_pp_inspector.rb
146
133
  - test/sanity/accessibility/test_qualifier.rb
147
134
  - test/sanity/accessibility/test_string.rb
@@ -153,24 +140,10 @@ files:
153
140
  - test/sanity/ax_elements/test_nsobject_inspect.rb
154
141
  - test/sanity/minitest/test_ax_elements.rb
155
142
  - test/sanity/rspec/expectations/test_ax_elements.rb
143
+ - test/sanity/test_accessibility.rb
156
144
  - test/sanity/test_ax_elements.rb
157
145
  - test/sanity/test_mouse.rb
158
146
  - test/helper.rb
159
- - docs/AccessibilityTips.markdown
160
- - docs/Acting.markdown
161
- - docs/Debugging.markdown
162
- - docs/images/all_the_buttons.jpg
163
- - docs/images/next_version.png
164
- - docs/images/ui_hierarchy.dot
165
- - docs/images/ui_hierarchy.png
166
- - docs/Inspecting.markdown
167
- - docs/KeyboardEvents.markdown
168
- - docs/NewBehaviour.markdown
169
- - docs/Notifications.markdown
170
- - docs/Searching.markdown
171
- - docs/TestingExtensions.markdown
172
- - .yardopts
173
- - README.markdown
174
147
  homepage: http://github.com/Marketcircle/AXElements
175
148
  licenses:
176
149
  - BSD 3-clause
@@ -198,7 +171,6 @@ specification_version: 3
198
171
  summary: A DSL for automating GUI manipulation
199
172
  test_files:
200
173
  - test/integration/accessibility/test_core.rb
201
- - test/integration/accessibility/test_debug.rb
202
174
  - test/integration/accessibility/test_dsl.rb
203
175
  - test/integration/accessibility/test_enumerators.rb
204
176
  - test/integration/accessibility/test_errors.rb
@@ -210,10 +182,10 @@ test_files:
210
182
  - test/integration/minitest/test_ax_elements.rb
211
183
  - test/integration/rspec/expectations/test_ax_elements.rb
212
184
  - test/sanity/accessibility/test_core.rb
213
- - test/sanity/accessibility/test_debug.rb
214
185
  - test/sanity/accessibility/test_dsl.rb
215
186
  - test/sanity/accessibility/test_errors.rb
216
187
  - test/sanity/accessibility/test_factory.rb
188
+ - test/sanity/accessibility/test_highlighter.rb
217
189
  - test/sanity/accessibility/test_pp_inspector.rb
218
190
  - test/sanity/accessibility/test_qualifier.rb
219
191
  - test/sanity/accessibility/test_string.rb
@@ -225,6 +197,7 @@ test_files:
225
197
  - test/sanity/ax_elements/test_nsobject_inspect.rb
226
198
  - test/sanity/minitest/test_ax_elements.rb
227
199
  - test/sanity/rspec/expectations/test_ax_elements.rb
200
+ - test/sanity/test_accessibility.rb
228
201
  - test/sanity/test_ax_elements.rb
229
202
  - test/sanity/test_mouse.rb
230
203
  - test/helper.rb
@@ -1,119 +0,0 @@
1
- # Accessibility Tips
2
-
3
- This document includes tips for customizing accessibility in your own
4
- applications. The goal is to inform you of pitfalls that are not
5
- mentioned in Apple's documentation. It also includes notes on how to
6
- avoid making decsions that will make an application incompatible with
7
- AXElements.
8
-
9
- @todo This document is under construction and is currently just a set
10
- of notes and unorganized sections ripped from other documents.
11
-
12
- ## Guidelines
13
-
14
- When implementing custom accessibility roles, you should never use a
15
- pluralized name for the role. A role that is already pluralized will
16
- break search.
17
-
18
-
19
- ## Adding new stuff
20
-
21
- Adding new attributes to an object is very simple...usually. Most of
22
- the time it is as simple as overriding two methods:
23
-
24
- - `accessibilityAttributeNames`
25
- - `accessibilityAttributeValue:`
26
-
27
- `accessibilityAttributeNames` needs to return an array with the names
28
- of available attributes. You should call `super` to get the existing
29
- array first, and then append any custom attributes you want to
30
- provide.
31
-
32
- An attribute name must follow the convention of being a camel cased
33
- string with the "AX" prefix, such as `AXTitle`. You can optionally
34
- include an additional prefix before "AX", such as "MCAX" for
35
- Marketcircle custom attributes. If you do not follow these rules then
36
- attribute names will not be translated properly by AXElements.
37
-
38
- `accessibilityAttributeValue:` is how you actually provide the value
39
- for the attribute; the parameter for this method is the name of the
40
- attribute fetched from calling `accessibilityAttributeNames`. You
41
- should return the value without doing any extra work to the data; the
42
- Accessibility interface will do any wrapping or translating for
43
- you (e.g. CGPoint objects will have co-ordinates flipped). In the case
44
- of a C structs you should leave them wrapped in an NSValue object.
45
-
46
- Similarly, parameterized attributes are added using methods with
47
- `Parameter` in the name. The method for getting the value of a
48
- parameterized attribute will of course take an extra parameter for the
49
- parameterized attributes parameter.
50
-
51
- ### Where To Implement The Methods
52
-
53
- The difficulty in adding attributes lies in finding out where you need
54
- to add the accessibility methods. Often, the class that implements
55
- accessibility is the class that draws the user interface element. For
56
- example, the accessibility information for a button is implemented on
57
- the button cell and not the button itself. Since Apple is under the
58
- delusion that you will never need to add any custom accessibility
59
- information, they really don't document these customizations enough,
60
- and so this document only includes caveats that have been discovered
61
- so far.
62
-
63
- It is often inconvenient to subclass the cell for an object just for
64
- accessibility. In these cases Apple has provided something similar to
65
- singleton methods, but less useful, with the
66
- `accessibilitySetOverrideValue:forAttribute:` method. You can use the
67
- method to override an attribute or even add a new one. The main issue
68
- with this method is that any attribute that is overridden, or added,
69
- will not be writable using the accessibility APIs. Another issue is
70
- that you have to calculate the value when you override instead of when
71
- the attribute value is queried by the client.
72
-
73
- ### Writability
74
-
75
- In the case where it is convenient to be able to change an attribute's
76
- value through accessibility, like the size of a window, you will also
77
- need to implement two more methods for the attribute:
78
-
79
- - `accessibilityIsAttributeSettable:`
80
- - `accessibilitySetValue:forAttribute:`
81
-
82
- `accessibilityIsAttributeSettable:` simply responds with whether or
83
- not the attribute is writable, and
84
- `accessibilitySetValue:forAttribute:` will be called to actually write
85
- to the attribute.
86
-
87
- ### Remember `super`
88
-
89
- It is important to remember that these methods are already implemented
90
- for the built in features. When overriding, you should only implement
91
- the custom behaviour and call `super` to handle everything else.
92
-
93
- ### Existing Definitions
94
-
95
- When implementing custom behaviour, you should try and use
96
- pseudoclasses that have already been defined by Apple, as well as
97
- attributes, parameterized attributes, and other features that have
98
- already been defined. Apple maintains the documentation for all their
99
- definitions on
100
- [here](http://developer.apple.com/library/mac/#documentation/UserExperience/Reference/Accessibility_RoleAttribute_Ref/Introduction.html#//apple_ref/doc/uid/TP40007870).
101
- The documentation is fairly detailed now (moreso than before), but
102
- still misses a few things.
103
-
104
-
105
- ## Adding Accessibility Actions
106
-
107
- If you have access to the source code for an app, you can add more
108
- accessibility actions. You need to override two methods, just like
109
- with adding attributes:
110
-
111
- – `accessibilityActionNames`
112
- – `accessibilityPerformAction:`
113
-
114
- These methods should be implemented in the same way that you would
115
- implement new attributes, just as detailed in the
116
- {file:docs/Inspecting.markdown Inspecting tutorial}. The one
117
- difference is that `accessibilityPerformAction:` should not return
118
- anything after it performs the action.
119
-
data/docs/Acting.markdown DELETED
@@ -1,340 +0,0 @@
1
- # Actions (a.k.a. The Fun Part)
2
-
3
- Actions are how you drive the user interface. They can be anything
4
- from showing a menu to dragging a window, but are often simple things
5
- like pressing a button. Most actions are subtly different cases
6
- of the same thing, but it is important to understand the differences
7
- and when to use different actions.
8
-
9
- There are three types of actions, those provided by the accessibility
10
- API as direct calls, those provided by accessibility APIs as keyboard
11
- simulation, and those that are provided by the CGEvents API. In most
12
- cases you can use at least two types of actions to achieve the desired
13
- effect and each type of action has its own pros and cons to consider.
14
-
15
- ## Interface
16
-
17
- Though there are three different APIs under the hood, they are exposed
18
- through a common interface via the DSL methods in
19
- {Accessibility::Language}.
20
-
21
- app = Accessibility.application_with_name 'Terminal'
22
- press app.main_window.minimize_button
23
- type "\\CMD+M", app
24
- click app.main_window.minimize_button
25
-
26
- The above example demonstrates performing actions using the DSL
27
- methods. The DSL methods are always some kind of action, such as
28
- {Accessibility::Language#drag_mouse_to drag\_mouse\_to}, or
29
- {Accessibility::Language#set_focus set\_focus} to name two.
30
-
31
- The point of the DSL is to put the actions at the front of statements
32
- in order to separate verbs from nouns. In this way, scripts should
33
- resemble instructions that you might give for reproduction steps in a
34
- bug report---they should read like they were instructions for
35
- a person. This style should make it easier to think about writing
36
- scripts and it should also give better hints at when to refactor
37
- code into helper methods like `log_in_as:with_password:`.
38
-
39
- If you tried out the code snippet above you might notice the
40
- difference between the APIs (you also might notice that the second
41
- command does not work, it is a work in progress :(). In the first
42
- case, `press` was using the actions provided by the accessibility APIs
43
- to call the press action on the minimize button for the window, in the
44
- second case we used `type` to type in the hot key for minimizing a
45
- window, and in the third case we used `click` from the CGEvents APIs
46
- to actually move the mouse cursor to the button and generate a click
47
- event.
48
-
49
- ## Accessibility Actions
50
-
51
- Actions like `press` use the accessibility APIs and show up in the
52
- list of actions returned by {AX::Element#actions}. These actions will
53
- also show up in the Accessibility Inspector in the Actions section.
54
-
55
- You may have noticed that {Accessibility::Language#press press}
56
- shows up as `AXPress` for buttons when you view them with the
57
- Accessibility Inspector or by calling {AX::Element#actions}. This is
58
- because we do name translation with accessibility actions just like
59
- when accessing attributes as shown in the
60
- {file:docs/Inspecting.markdown Inspection tutorial}.
61
-
62
- Accessibility actions are the simplest and fastest way to trigger
63
- actions. They use the accessibility APIs to talk directly to the
64
- application to call the same methods that would be called when
65
- you interact with the application with the mouse or keyboard, but
66
- without having to use the mouse or keyboard.
67
-
68
- ## Setting Attributes
69
-
70
- Though it is not really an action, some attributes can have their
71
- values changed through the accessibility APIs. The
72
- {Accessibility::Language} module has two methods, `set` and
73
- `set_focus`, which allow you to change the value for certain
74
- attributes.
75
-
76
- In the Accessibility Inspector, a writable attribute will have `(W)`
77
- next to the attribute name, and the programmatic way to tell if an
78
- attribute is writable is to call {AX::Element#attribute_writable?} and
79
- pass the name of the attribute as a parameter:
80
-
81
- app.main_window.attribute_writable? :size
82
- app.main_window.attribute_writable? :title
83
-
84
- You can only set an attribute if it is writable. When you have an
85
- attribute that is writable, and you would like to change it, then you
86
- simply need to call `set`:
87
-
88
- set app.main_window, size: [500, 500].to_size
89
-
90
- The first parameter is the element, and the second parameter is a
91
- key-value pair with the attribute as the key and the value as the
92
- new value.
93
-
94
- `set_focus` is just syntactic sugar for `set` where the key-value is
95
- set for you and the only parameter you provide is the element you want
96
- to set focus to:
97
-
98
- set_focus app.main_window.text_field
99
-
100
- Though, `set` itself has a special case. The second most frequently
101
- changed attribute for a element is the `value`, it is used almost as
102
- much as `focused`. For this case, you can pass anything that is not a
103
- key-value pair to `set` and it will be assumed that you want to change
104
- the value of the `value` attribute:
105
-
106
- set app.main_window.text_field, 'Mark Rada'
107
-
108
- Another important detail that you might be curious about is that we
109
- called `to_size` on the first snippet showing how to set an
110
- attribute. The developers of MacRuby have added a feature that allows
111
- you to pass an array as an argument when you would normally be
112
- expected to pass a structure such as a `CGPoint`. Since AXElements
113
- APIs inherently need to handle any type of object, it is not sane to try
114
- and do the same type of run time type analysis. In its place, I have
115
- provided some convenience methods for {NSArray arrays} to let you
116
- quickly transform them to the appropriate type of object. In my
117
- opinion, you should just use the proper type of object in the first
118
- place and avoid the overhead.
119
-
120
- ### You Want To Use Actions
121
-
122
- The advantage to using accessibility actions is that they are usually
123
- much easier to use than the other APIs. Accessibility actions are
124
- usually synchronous and so your scripts will not need to worry about
125
- waiting for animations to complete. The only complication is that
126
- sometimes an action is not synchronous and there is no good way to
127
- tell if an action is synchronous without looking at the underlying
128
- code.
129
-
130
- ### Asynchronous Problems
131
-
132
- Problems with synchronous behaviour will occur with all the types of
133
- actions, but less often with accessibility actions, so you should
134
- always try thing out first in the console to make sure. A good rule of
135
- thumb is that anything that looks like it would take a noticeable
136
- amount of time, and is not an animation, will probably be
137
- asynchronous. An example of this would be having to load data from a
138
- database (read: separate process, across a network, etc.) such as in
139
- the Marketcircle server admin apps or the Billings Pro preferences.
140
-
141
- Asynchronous behaviour can often be worked around by using
142
- accessibility {file:docs/Notifications.markdown notifications} or by
143
- simply calling
144
- [`sleep`](http://rdoc.info/stdlib/core/1.9.2/Kernel#sleep-instance_method).
145
- Notifications have some interesting caveats that are covered in their
146
- own tutorial and so it is much easier to `sleep`.
147
-
148
- However, using `sleep` to solve the problem of asynchronous waiting is
149
- like using a steak knife for brain surgery. Unless you can control the
150
- environment when the script is running, you will need to sleep for
151
- longer periods of time than you really need to; even when you have
152
- quite a bit of control you might still have the occasional instance
153
- where a database fetch takes longer than expected. I would suggest you
154
- use notifications when you can, and sleep when you cannot.
155
-
156
- ## CGEvents Actions
157
-
158
- CGEvents based actions never directly use the accessibility APIs and
159
- are implemented at a different level of the GUI stack in OS X (I
160
- assume). An action that uses CGEvents will actually move the mouse
161
- cursor around and simulate mouse input at a relatively low level of
162
- abstraction. However, accessibility information is still used to find
163
- the point on the screen to move to and click. These types of actions
164
- are more realistic, more awesome looking, and also more difficult to
165
- write.
166
-
167
- The difficulty in writing the scripts comes from the fact that it does
168
- not directly communicate with the accessibility APIs. This implicitly
169
- means that all CGEvents actions are asynchronous, which is the only
170
- non-trivial complication with using CGEvents APIs.
171
-
172
- ### CGEvents Goes Where Accessibility Cannot
173
-
174
- The benefit of actually moving the mouse will often outweigh the
175
- downside here. Moving the mouse cursor and generating click events
176
- allows things like dragging and scrolling which cannot be done using
177
- the accessibility APIs.
178
-
179
- As mentioned earlier, CGEvents doesn't talk to applications in the
180
- same way that accessibility APIs do, which means that you can use
181
- CGEvents actions on elements that do not fully support
182
- accessibility. For instance, text might appear on the UI and have a
183
- `click`-able link but may not provide an equivalent action to clicking
184
- on the link; using CGEvents APIs you will only need to move to the
185
- position of the text and then generate a click event.
186
-
187
- ### Realism
188
-
189
- Since CGEvents actually manipulates the mouse cursor, any script that
190
- uses the APIs will be more realistic than their equivalent
191
- accessibility actions. This can make a difference if you are testing
192
- an app and you are expecting other side effects to occur. For instance,
193
- what if you implemented a sort of UI element that requires the mouse
194
- cursor to be near the element for an action to trigger, such as an
195
- expanding drawer or a folder in the finder. Depending on what you are
196
- using AXElements for, this may or may not be important to you.
197
-
198
- ### Underpinnings
199
-
200
- An important thing to note is that AXElements always works with
201
- flipped co-ordinates. The origin, `(0, 0)` is always located in the
202
- top left corner of the main display. Displays to the left have a
203
- negative `x` co-ordinate and displays above the main display will have
204
- negative `y` co-ordinates.
205
-
206
- Though the CGEvents APIs are exposed to AXElements through the
207
- {Accessibility::Language Language} module, there is another thin
208
- layer between AXElements and CGEvents that abstracts everything that
209
- AXElements uses.
210
-
211
- The between layer is the {Mouse} module, and it is responsible for
212
- generating events and animating the mouse movements on screen. The API
213
- that it makes available to AXElements is more fine grained than what
214
- AXElements makes available to you. There is a lot of room for the
215
- {Mouse} module to grow and add new features, some of which are noted
216
- as `@todo` items in the documentation. It may also be necessary in
217
- the future to make available from AXElements certain options that are
218
- normally hidden in {Mouse}.
219
-
220
- ### Where The Mouse Moves
221
-
222
- Consider the following:
223
-
224
- move_mouse_to app.main_window.close_button
225
-
226
- If you try out that code snippet then you will notice that the mouse
227
- cursor moves to the button, specifically the center of the button. If
228
- you use the method to move to another object, such as a button, you
229
- will again notice that it moved to the center point of the element.
230
-
231
- You can call `move_mouse_to` and give it a UI element, but you could
232
- also be more exact and give it a CGPoint or even an array with two
233
- numbers. In fact, you could pass just any object as long it implements
234
- a method named `to_point` which returns a CGPoint object. If you were
235
- to look at the code for {Accessibility::Language#move_mouse_to} you
236
- would see that all it does is call `#to_point` on the argument and
237
- then pass the returned value to the {Mouse} module to actually do the
238
- work. You could also look at {NSArray#to_point} and find out that it just
239
- returns a new CGPoint using the first two objects in the array, and
240
- `CGPoint#to_point` just returns itself (you can't see the
241
- documentation for the method because of limitations in
242
- YARD). Similarly, {AX::Element} implements
243
- {AX::Element#to_point to_point} which not only gets the position for
244
- the element, but also gets the size and then calculates the center
245
- point for the element. This is important because you probably never
246
- want the mouse cursor moving to the top left of the UI element, but
247
- maybe you don't want the cursor moving to the center either.
248
-
249
- If you want to have the mouse move to a location over a UI element
250
- that is not the center point, then you will need to implement
251
- `to_point` for the appropriate class. Just remember to follow the
252
- {file:docs/NewBehaviour.markdown rules} for choosing the proper
253
- superclasss. For instance, on Snow Leopard you could resize a window
254
- by clicking and dragging an `AX::GrowArea` object in the lower right
255
- corner of a window; moving to the center of the element may not
256
- actually allow the mouse to respond and so you would have to implement
257
- `to_point` to move closer to the bottom right of the window
258
- (__NOTE__: I don't know if that is actually true for `AX::GrowArea`,
259
- it was just meant as an example).
260
-
261
- ### Dragging
262
-
263
- Sometimes you just need to be able to click and drag an element. To do
264
- this you can only use the CGEvents APIs. Fortunately, click and drag
265
- is relatively painless with AXElements, the simplest example would be
266
- something like this:
267
-
268
- # move to the mouse to the starting point
269
- move_mouse_to app.main_window.title_ui_element
270
-
271
- # start the click and drag event
272
- drag_mouse_to [0, 0]
273
-
274
- Pretty cool, eh? The general pattern is to move the mouse to the
275
- starting point, and then to call `drag_mouse_to` to start the click
276
- and drag events. In the example, I gave co-ordinates using an array
277
- with two numbers but you can pass a CGPoint or anything else that
278
- responds to `to_point` just like with the other CGEvents actions.
279
-
280
- ## Keyboard Actions
281
-
282
- The final type of action is the keyboard action. Keyboard actions
283
- live in a world between the accessibility APIs and CGEvent APIs; that
284
- is, Apple has already done the work of unifying them for me and in a
285
- way that I would not have been able to do myself.
286
-
287
- The keyboard action is a single method,
288
- {Accessibility::Language#type}, that requires one parameter and
289
- takes an optional second parameter. The first parameter is simply a
290
- string that includes which characters you wish to have typed out and
291
- the second parameter is the UI element for the application where you
292
- would like to send the keyboard events. Since the second parameter is
293
- optional, the currently focused app will receive the events if the
294
- parameter is not used.
295
-
296
- # to a specific app
297
- app = Accessibility.application_with_name 'Terminal'
298
- type "Hello, world!", app
299
-
300
- # to the focused app
301
- type "Hello, world!"
302
-
303
- The string that you pass can have some special escape sequences to
304
- indicate that you want to press command characters or a combination of
305
- keys at once for a hot key. Details on this are contained in their own
306
- tutorial, the
307
- {file:docs/KeyboardEvents.markdown Keyboard Events Tutorial}.
308
-
309
- ## Mixing In
310
-
311
- By now you will have noticed that all the actions have been defined in
312
- the {Accessibility::Language} name space, which is just a simple
313
- module. Though, you have been able to use the methods anywhere and in
314
- any context.
315
-
316
- When you load AXElements, by using `require 'ax_elements'`, not only
317
- will all the code be loaded, but the extra step of mixing
318
- {Accessibility::Language} into the top level name space will also be
319
- done for you. The only way to override this behaviour is to load the
320
- components of AXElements yourself, the details on how to do this are
321
- left as an exercise to interested parties. :P
322
-
323
- ## A Note About Caching
324
-
325
- You need to be careful when you cache UI element objects. Every time
326
- that you trigger an action you are intrinsically changing the state of
327
- an application. State changes will often cause new UI elements to be
328
- created, recreated, or removed.
329
-
330
- For example consider pressing the close button for a window; in this
331
- case, an entire window and all of its children will disappear and
332
- become invalid objects. Another case might be pressing the `+` button
333
- for a table; in this case you have created a new row for the table and
334
- any cache of the existing rows for the table will not include the new
335
- element.
336
-
337
- The real problem with caching is with the invalid objects. An invalid
338
- object is poisonous to the MacRuby run time. If you try to access an
339
- attribute or trigger a search from an invalid then you will cause
340
- MacRuby to crash.