AXElements 0.6.0beta2 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. data/.yardopts +1 -2
  2. data/README.markdown +152 -88
  3. data/Rakefile +8 -103
  4. data/docs/Debugging.markdown +9 -2
  5. data/docs/KeyboardEvents.markdown +114 -49
  6. data/docs/Setting.markdown +1 -0
  7. data/docs/images/next_version.png +0 -0
  8. data/ext/accessibility/key_coder/extconf.rb +22 -0
  9. data/ext/accessibility/key_coder/key_coder.c +113 -0
  10. data/lib/AXElements.rb +2 -0
  11. data/lib/accessibility/core.rb +897 -0
  12. data/lib/accessibility/debug.rb +168 -0
  13. data/lib/accessibility/dsl.rb +697 -0
  14. data/lib/accessibility/enumerators.rb +104 -0
  15. data/lib/accessibility/errors.rb +32 -0
  16. data/lib/accessibility/factory.rb +153 -0
  17. data/lib/accessibility/graph.rb +150 -0
  18. data/lib/{ax_elements/inspector.rb → accessibility/pp_inspector.rb} +39 -28
  19. data/lib/accessibility/qualifier.rb +158 -0
  20. data/lib/accessibility/string.rb +494 -0
  21. data/lib/accessibility/translator.rb +178 -0
  22. data/lib/accessibility/version.rb +7 -0
  23. data/lib/accessibility.rb +79 -0
  24. data/lib/ax/application.rb +234 -0
  25. data/lib/{ax_elements/elements → ax}/button.rb +2 -0
  26. data/lib/ax/element.rb +518 -0
  27. data/lib/{ax_elements/elements → ax}/radio_button.rb +2 -0
  28. data/lib/ax/row.rb +37 -0
  29. data/lib/{ax_elements/elements → ax}/static_text.rb +2 -0
  30. data/lib/ax/systemwide.rb +86 -0
  31. data/lib/ax_elements/awesome_print.rb +25 -0
  32. data/lib/ax_elements/exception_workaround.rb +8 -0
  33. data/lib/ax_elements/nsarray_compat.rb +64 -0
  34. data/lib/ax_elements/vendor/inflection_data.rb +65 -0
  35. data/lib/ax_elements/vendor/inflections.rb +172 -0
  36. data/lib/ax_elements/vendor/inflector.rb +306 -0
  37. data/lib/ax_elements.rb +14 -25
  38. data/lib/minitest/ax_elements.rb +112 -12
  39. data/lib/mouse.rb +72 -46
  40. data/lib/rspec/expectations/ax_elements.rb +133 -6
  41. data/rakelib/doc.rake +13 -0
  42. data/rakelib/ext.rake +61 -0
  43. data/rakelib/gem.rake +28 -0
  44. data/rakelib/test.rake +53 -0
  45. data/test/helper.rb +11 -97
  46. data/test/integration/accessibility/test_core.rb +18 -0
  47. data/test/integration/accessibility/test_debug.rb +44 -0
  48. data/test/integration/accessibility/test_dsl.rb +225 -0
  49. data/test/integration/accessibility/test_enumerators.rb +122 -0
  50. data/test/integration/accessibility/test_errors.rb +38 -0
  51. data/test/integration/accessibility/test_notifications.rb +22 -0
  52. data/test/integration/accessibility/test_qualifier.rb +148 -0
  53. data/test/integration/ax/test_application.rb +56 -0
  54. data/test/integration/ax/test_element.rb +46 -0
  55. data/test/integration/ax/test_row.rb +23 -0
  56. data/test/integration/ax_elements/test_nsarray_compat.rb +43 -0
  57. data/test/integration/minitest/test_ax_elements.rb +98 -0
  58. data/test/integration/rspec/expectations/test_ax_elements.rb +58 -0
  59. data/test/integration/test_mouse.rb +35 -0
  60. data/test/sanity/accessibility/test_core.rb +553 -0
  61. data/test/sanity/accessibility/test_debug.rb +63 -0
  62. data/test/sanity/accessibility/test_dsl.rb +75 -0
  63. data/test/sanity/accessibility/test_errors.rb +10 -0
  64. data/test/sanity/accessibility/test_factory.rb +88 -0
  65. data/test/sanity/accessibility/test_pp_inspector.rb +110 -0
  66. data/test/sanity/accessibility/test_qualifier.rb +13 -0
  67. data/test/sanity/accessibility/test_string.rb +238 -0
  68. data/test/sanity/accessibility/test_translator.rb +145 -0
  69. data/test/sanity/ax/test_application.rb +90 -0
  70. data/test/sanity/ax/test_element.rb +80 -0
  71. data/test/sanity/ax/test_systemwide.rb +66 -0
  72. data/test/sanity/ax_elements/test_nsarray_compat.rb +16 -0
  73. data/test/sanity/ax_elements/test_nsobject_inspect.rb +11 -0
  74. data/test/sanity/minitest/test_ax_elements.rb +15 -0
  75. data/test/sanity/rspec/expectations/test_ax_elements.rb +12 -0
  76. data/test/sanity/test_ax_elements.rb +10 -0
  77. data/test/sanity/test_mouse.rb +19 -0
  78. metadata +111 -93
  79. data/LICENSE.txt +0 -25
  80. data/ext/key_coder/extconf.rb +0 -6
  81. data/ext/key_coder/key_coder.m +0 -77
  82. data/lib/ax_elements/accessibility/enumerators.rb +0 -104
  83. data/lib/ax_elements/accessibility/graph.rb +0 -118
  84. data/lib/ax_elements/accessibility/language.rb +0 -347
  85. data/lib/ax_elements/accessibility/qualifier.rb +0 -73
  86. data/lib/ax_elements/accessibility.rb +0 -166
  87. data/lib/ax_elements/core.rb +0 -541
  88. data/lib/ax_elements/element.rb +0 -593
  89. data/lib/ax_elements/elements/application.rb +0 -88
  90. data/lib/ax_elements/elements/row.rb +0 -30
  91. data/lib/ax_elements/elements/systemwide.rb +0 -46
  92. data/lib/ax_elements/macruby_extensions.rb +0 -255
  93. data/lib/ax_elements/notification.rb +0 -37
  94. data/lib/ax_elements/version.rb +0 -9
  95. data/test/elements/test_application.rb +0 -72
  96. data/test/elements/test_row.rb +0 -27
  97. data/test/elements/test_systemwide.rb +0 -38
  98. data/test/test_accessibility.rb +0 -127
  99. data/test/test_blankness.rb +0 -26
  100. data/test/test_core.rb +0 -448
  101. data/test/test_element.rb +0 -939
  102. data/test/test_enumerators.rb +0 -81
  103. data/test/test_inspector.rb +0 -130
  104. data/test/test_language.rb +0 -157
  105. data/test/test_macruby_extensions.rb +0 -303
  106. data/test/test_mouse.rb +0 -5
  107. data/test/test_search_semantics.rb +0 -143
data/lib/ax/element.rb ADDED
@@ -0,0 +1,518 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'ax_elements/vendor/inflector'
4
+ require 'accessibility/enumerators'
5
+ require 'accessibility/qualifier'
6
+ require 'accessibility/errors'
7
+ require 'accessibility/pp_inspector'
8
+ require 'accessibility/factory'
9
+ require 'accessibility/core'
10
+
11
+ ##
12
+ # @abstract
13
+ #
14
+ # The abstract base class for all accessibility objects. This class
15
+ # provides generic functionality that all accessibility objects require.
16
+ class AX::Element
17
+ include Accessibility::PPInspector
18
+ include Accessibility::Factory
19
+
20
+ # @param [AXUIElementRef]
21
+ def initialize ref
22
+ @ref = ref
23
+ end
24
+
25
+
26
+ # @group Attributes
27
+
28
+ ##
29
+ # Cache of available attributes.
30
+ #
31
+ # @example
32
+ #
33
+ # window.attributes # => [:size, :position, :title, ...]
34
+ #
35
+ # @return [Array<Symbol>]
36
+ def attributes
37
+ @attributes ||= TRANSLATOR.rubyize @ref.attributes
38
+ end
39
+
40
+ ##
41
+ # @todo Consider returning `nil` if the element does not have
42
+ # the given attribute.
43
+ #
44
+ # Get the value of an attribute.
45
+ #
46
+ # @example
47
+ #
48
+ # element.attribute :position # => #<CGPoint x=123.0 y=456.0>
49
+ #
50
+ # @param [#to_sym]
51
+ def attribute name
52
+ if rattr = lookup(name, @ref.attributes)
53
+ process @ref.attribute(rattr)
54
+ end
55
+ end
56
+
57
+ ##
58
+ # Needed to override inherited `NSObject#description`. If you want a
59
+ # description of the object then you should use {#inspect} instead.
60
+ def description
61
+ attribute :description
62
+ end
63
+
64
+ ##
65
+ # Return the `#size` of an attribute. This only works for attributes
66
+ # that are a collection. This exists because it is _much_ more
67
+ # efficient to find out how many `children` exist using this API
68
+ # instead of getting the children array and asking for the size.
69
+ #
70
+ # @example
71
+ #
72
+ # table.size_of :rows # => 111
73
+ # window.size_of :children # => 16
74
+ #
75
+ # @param [#to_sym]
76
+ # @return [Number]
77
+ def size_of attr
78
+ if rattr = lookup(attr, @ref.attributes)
79
+ @ref.size_of rattr
80
+ else
81
+ 0
82
+ end
83
+ end
84
+
85
+ ##
86
+ # Get the process identifier for the application that the element
87
+ # belongs to.
88
+ #
89
+ # @example
90
+ #
91
+ # element.pid # => 12345
92
+ #
93
+ # @return [Fixnum]
94
+ def pid
95
+ @ref.pid
96
+ end
97
+
98
+ ##
99
+ # Check whether or not an attribute is writable.
100
+ #
101
+ # @example
102
+ #
103
+ # element.writable? :size # => true
104
+ # element.writable? :value # => false
105
+ #
106
+ # @param [#to_sym]
107
+ def writable? attr
108
+ if rattr = lookup(attr, @ref.attributes)
109
+ @ref.writable? rattr
110
+ else
111
+ false
112
+ end
113
+ end
114
+
115
+ ##
116
+ # Set a writable attribute on the element to the given value.
117
+ #
118
+ # @example
119
+ #
120
+ # element.set :value, 'Hello, world!'
121
+ # element.set :size, [100, 200].to_size
122
+ #
123
+ # @param [#to_sym]
124
+ # @return the value that you were setting is returned
125
+ def set attr, value
126
+ unless writable? attr
127
+ raise NoMethodError, "#{attr} is read-only for #{inspect}"
128
+ end
129
+ value = value.relative_to(@ref.value.size) if value.kind_of? Range
130
+ rattr = lookup(attr, @ref.attributes)
131
+ @ref.set rattr, value
132
+ value
133
+ end
134
+
135
+
136
+ # @group Parameterized Attributes
137
+
138
+ ##
139
+ # List of available parameterized attributes. Most elements have no
140
+ # parameterized attributes, but the ones that do have many.
141
+ #
142
+ # @example
143
+ #
144
+ # window.parameterized_attributes # => []
145
+ # text_field.parameterized_attributes # => [:string_for_range, :attributed_string, ...]
146
+ #
147
+ # @return [Array<Symbol>]
148
+ def parameterized_attributes
149
+ TRANSLATOR.rubyize @ref.parameterized_attributes
150
+ end
151
+
152
+ ##
153
+ # Get the value for a parameterized attribute.
154
+ #
155
+ # @example
156
+ #
157
+ # text_field.attribute :string_for_range, for_param: (2..8).relative_to(10)
158
+ #
159
+ # @param [#to_sym]
160
+ def attribute attr, for_parameter: param
161
+ if rattr = lookup(attr, @ref.parameterized_attributes)
162
+ param = param.relative_to(@ref.value.size) if value.kind_of? Range
163
+ process @ref.attribute(rattr, for_parameter: param)
164
+ end
165
+ end
166
+
167
+
168
+ # @group Actions
169
+
170
+ ##
171
+ # List of available actions.
172
+ #
173
+ # @example
174
+ #
175
+ # toolbar.actions # => []
176
+ # button.actions # => [:press]
177
+ # menu.actions # => [:open, :cancel]
178
+ #
179
+ # @return [Array<Symbol>]
180
+ def actions
181
+ TRANSLATOR.rubyize @ref.actions
182
+ end
183
+
184
+ ##
185
+ # Tell an object to trigger an action.
186
+ #
187
+ # For instance, you can tell a button to call the same method that
188
+ # would be called when pressing a button, except that the mouse will
189
+ # not move over to the button to press it, nor will the keyboard be
190
+ # used.
191
+ #
192
+ # @example
193
+ #
194
+ # button.perform :press # => true
195
+ #
196
+ # @param [#to_sym]
197
+ # @return [Boolean] true if successful
198
+ def perform action
199
+ if raction = lookup(action, @ref.actions)
200
+ @ref.perform raction
201
+ else
202
+ false
203
+ end
204
+ end
205
+
206
+
207
+ # @group Search
208
+
209
+ ##
210
+ # Perform a breadth first search through the view hierarchy rooted at
211
+ # the current element.
212
+ #
213
+ # See the {file:docs/Searching.markdown Searching} tutorial for the
214
+ # details on searching.
215
+ #
216
+ # @example Find the dock icon for the Finder app
217
+ #
218
+ # AX::DOCK.search( :application_dock_item, title:'Finder' )
219
+ #
220
+ # @param [#to_s]
221
+ # @param [Hash{Symbol=>Object}]
222
+ # @return [AX::Element,nil,Array<AX::Element>,Array<>]
223
+ def search kind, filters = {}, &block
224
+ kind = kind.to_s
225
+ qualifier = Accessibility::Qualifier.new(kind, filters, &block)
226
+ tree = Accessibility::Enumerators::BreadthFirst.new(self)
227
+
228
+ if TRANSLATOR.singularize(kind) == kind
229
+ tree.find { |element| qualifier.qualifies? element }
230
+ else
231
+ tree.find_all { |element| qualifier.qualifies? element }
232
+ end
233
+ end
234
+
235
+ ##
236
+ # Search for an ancestor of the current elemenet.
237
+ #
238
+ # As the opposite of {#search}, this also takes filters, and can
239
+ # be used to find a specific ancestor for the current element.
240
+ #
241
+ # @example
242
+ #
243
+ # button.ancestor :window # => #<AX::StandardWindow>
244
+ # row.ancestor :scroll_area # => #<AX::ScrollArea>
245
+ #
246
+ # @param [#to_s]
247
+ # @param [Hash{Symbol=>Object}]
248
+ # @return [AX::Element]
249
+ def ancestor kind, filters = {}, &block
250
+ qualifier = Accessibility::Qualifier.new(kind, filters, &block)
251
+ element = attribute :parent
252
+ until qualifier.qualifies? element
253
+ element = element.attribute :parent
254
+ end
255
+ element
256
+ end
257
+
258
+ ##
259
+ # We use {#method_missing} to dynamically handle requests to lookup
260
+ # attributes or search for elements in the view hierarchy. An attribute
261
+ # lookup is always tried first, followed by a parameterized attribute
262
+ # lookup, and then finally a search.
263
+ #
264
+ # Failing all lookups, this method calls `super`.
265
+ #
266
+ # @example
267
+ #
268
+ # mail = AX::Application.application_with_bundle_identifier 'com.apple.mail'
269
+ #
270
+ # # attribute lookup
271
+ # window = mail.focused_window
272
+ # # is equivalent to
273
+ # window = mail.attribute :focused_window
274
+ #
275
+ # # parameterized attribute lookup
276
+ # window.title_ui_element.string_for_range (1..10).relative_to(100)
277
+ # # is equivalent to
278
+ # title = window.attribute :title_ui_element
279
+ # title.param_attribute :string_for_range, for_param: (1..10).relative_to(100)
280
+ #
281
+ # # simple single element search
282
+ # window.button # => You want the first Button that is found
283
+ # # is equivalent to
284
+ # window.search :button, {}
285
+ #
286
+ # # simple multi-element search
287
+ # window.buttons # => You want all the Button objects found
288
+ # # is equivalent to
289
+ # window.search :buttons, {}
290
+ #
291
+ # # filters for a single element search
292
+ # window.button(title: 'Log In') # => First Button with a title of 'Log In'
293
+ # # is equivalent to
294
+ # window.search :button, title: 'Log In'
295
+ #
296
+ # # attribute and element search failure
297
+ # window.application # => SearchFailure is raised
298
+ #
299
+ def method_missing method, *args, &block
300
+ if method[-1] == EQUALS
301
+ return set(method.chomp(EQUALS), args.first)
302
+
303
+ elsif attr = lookup(method, @ref.attributes)
304
+ return process @ref.attribute(attr)
305
+
306
+ elsif attr = lookup(method, @ref.parameterized_attributes)
307
+ return process @ref.attribute(attr, for_parameter: args.first)
308
+
309
+ elsif @ref.attributes.include? KAXChildrenAttribute
310
+ result = search method, *args, &block
311
+ return result unless result.blank?
312
+ raise Accessibility::SearchFailure.new(self, method, args.first)
313
+
314
+ else
315
+ super
316
+
317
+ end
318
+ end
319
+
320
+
321
+ # @group Notifications
322
+
323
+ def notifs
324
+ @notifs ||= {}
325
+ end
326
+
327
+ ##
328
+ # Register to receive notification of the given event being completed
329
+ # by the given element.
330
+ #
331
+ # {file:docs/Notifications.markdown Notifications} are a way to put
332
+ # non-polling delays into your scripts.
333
+ #
334
+ # Use this method to register to be notified of the specified event in
335
+ # an application.
336
+ #
337
+ # The block is optional. The block will be given the sender of the
338
+ # notification, which will almost always be `self`, and also the name
339
+ # of the notification being received. The block should return a
340
+ # boolean value that decides if the notification received is the
341
+ # expected one.
342
+ #
343
+ # @example
344
+ #
345
+ # on_notification(:window_created) { |sender|
346
+ # puts "#{sender.inspect} sent the ':window_created' notification"
347
+ # true
348
+ # }
349
+ #
350
+ # @param [#to_s] notif the name of the notification
351
+ # @yield Validate the notification; the block should return truthy if
352
+ # the notification received is the expected one and the script can
353
+ # stop waiting, otherwise should return falsy.
354
+ # @yieldparam [String] notif the name of the notification
355
+ # @yieldparam [AXUIElementRef] element the element that sent the notification
356
+ # @yieldreturn [Boolean]
357
+ # @return [Array(Observer, String, CFRunLoopSource)]
358
+ def on_notification name, &block
359
+ notif = TRANSLATOR.guess_notification_for name
360
+ observer = @ref.observer &notif_callback_for(&block)
361
+ source = @ref.run_loop_source_for observer
362
+ @ref.register observer, to_receive: notif
363
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), source, KCFRunLoopDefaultMode)
364
+ notifs[name] = [observer, notif, source]
365
+ end
366
+
367
+ def unregister_notification name
368
+ unless notifs.has_key? name
369
+ raise ArgumentError, "You have no registrations for #{name}"
370
+ end
371
+ _unregister_notification *notifs.delete(name)
372
+ end
373
+
374
+ ##
375
+ # Cancel _all_ notification registrations for this object. Simple and
376
+ # clean, but a blunt tool at best. This will have to do for the time
377
+ # being...
378
+ #
379
+ # @return [nil]
380
+ def unregister_all
381
+ notifs.keys.each do |notif|
382
+ unregister_notification notif
383
+ end
384
+ nil
385
+ end
386
+
387
+ # @endgroup
388
+
389
+
390
+ ##
391
+ # Overriden to produce cleaner output.
392
+ #
393
+ # @return [String]
394
+ def inspect
395
+ msg = "#<#{self.class}" << pp_identifier
396
+ attrs = @ref.attributes
397
+ msg << pp_position if attrs.include? KAXPositionAttribute
398
+ msg << pp_children if attrs.include? KAXChildrenAttribute
399
+ msg << pp_checkbox(:enabled) if attrs.include? KAXEnabledAttribute
400
+ msg << pp_checkbox(:focused) if attrs.include? KAXFocusedAttribute
401
+ msg << '>'
402
+ end
403
+
404
+ ##
405
+ # @note Since `#inspect` is often overridden by subclasses, this cannot
406
+ # be an alias.
407
+ #
408
+ # An "alias" for {#inspect}.
409
+ #
410
+ # @return [String]
411
+ def to_s
412
+ inspect
413
+ end
414
+
415
+ ##
416
+ # Overriden to respond properly with regards to dynamic attribute
417
+ # lookups, but will return false for potential implicit searches.
418
+ def respond_to? name
419
+ lookup(name.chomp(EQUALS), @ref.attributes) ||
420
+ lookup(name, @ref.parameterized_attributes) ||
421
+ super
422
+ end
423
+
424
+ ##
425
+ # Get the center point of the element.
426
+ #
427
+ # @return [CGPoint]
428
+ def to_point
429
+ size = attribute :size
430
+ point = attribute :position
431
+ point.x += size.width / 2
432
+ point.y += size.height / 2
433
+ point
434
+ end
435
+
436
+ ##
437
+ # Get the bounding rectangle for the element.
438
+ #
439
+ # @return [CGRect]
440
+ def bounds
441
+ point = attribute :position
442
+ size = attribute :size
443
+ CGRectMake(*point, *size)
444
+ end
445
+
446
+ ##
447
+ # Get the application object for the element.
448
+ #
449
+ # @return [AX::Application]
450
+ def application
451
+ process @ref.application
452
+ end
453
+
454
+ ##
455
+ # Concept borrowed from `Active Support`. It is used during implicit
456
+ # searches to determine if searches yielded responses.
457
+ def blank?
458
+ false
459
+ end
460
+
461
+ ##
462
+ # @todo Need to add '?' to predicate methods, but how?
463
+ #
464
+ # Like {#respond_to?}, this is overriden to include attribute methods.
465
+ def methods include_super = true, include_objc_super = false
466
+ super.concat(attributes).concat(parameterized_attributes)
467
+ end
468
+
469
+ ##
470
+ # Overridden so that equality testing would work. A hack, but the only
471
+ # sane way I can think of to test for equivalency.
472
+ def == other
473
+ @ref == other.instance_variable_get(:@ref)
474
+ end
475
+ alias_method :eql?, :==
476
+ alias_method :equal?, :==
477
+
478
+
479
+ private
480
+
481
+ ##
482
+ # @private
483
+ #
484
+ # Performance hack.
485
+ #
486
+ # @return [String]
487
+ EQUALS = '='
488
+
489
+ def lookup key, values
490
+ value = TRANSLATOR.lookup key, values
491
+ return value if values.include? value
492
+ end
493
+
494
+ def notif_callback_for
495
+ # we are ignoring the context pointer since this is OO
496
+ Proc.new do |observer, sender, notif, _|
497
+ break unless yield(process sender) if block_given?
498
+ _unregister_notification observer, notif, run_loop_source_for(observer)
499
+ CFRunLoopStop(CFRunLoopGetCurrent())
500
+ end
501
+ end
502
+
503
+ ##
504
+ # @todo What are the implications of removing the run loop source?
505
+ # Taking it out would clobber other notifications that are using
506
+ # the same source, so we would have to check if we can remove it.
507
+ #
508
+ def _unregister_notification observer, notif, source
509
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, KCFRunLoopDefaultMode)
510
+ @ref.unregister observer, from_receiving: notif
511
+ end
512
+
513
+ end
514
+
515
+
516
+ # Extensions so checking #blank? on search result "just works".
517
+ class NSArray; alias_method :blank?, :empty? end
518
+ class NilClass; def blank?; true end end
@@ -1,3 +1,5 @@
1
+ require 'ax/element'
2
+
1
3
  ##
2
4
  # Radio buttons are not the same as a generic button, radio buttons work
3
5
  # in mutually exclusive groups (you can only select one at a time). You
data/lib/ax/row.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'ax/element'
2
+ require 'accessibility/qualifier'
3
+
4
+ ##
5
+ # UI Element for the row in a table, outline, etc.
6
+ class AX::Row < AX::Element
7
+
8
+ ##
9
+ # Retrieve the child in a row that corresponds to a specific column.
10
+ # You must pass filters here in the same way that you would for a
11
+ # search.
12
+ #
13
+ # This is useful for tables where it is difficult to identify which
14
+ # row item is the one you want based on the row items themselves.
15
+ # Often times the columns in the table will have identifying attributes,
16
+ # such as a header, and so you can use this method to figure out what
17
+ # column your row item is in and then the method will return the row
18
+ # item you wanted.
19
+ #
20
+ # @example
21
+ #
22
+ # rows = table.rows
23
+ # total = rows.inject(0) { |sum, row|
24
+ # sum += row.child_in_column(header: 'Price').value.to_i
25
+ # }
26
+ # puts "The average price is $ #{total / rows.size}"
27
+ #
28
+ # @param [Hash]
29
+ # @return [AX::Element]
30
+ def child_in_column filters, &block
31
+ qualifier = Accessibility::Qualifier.new(:Column, filters, &block)
32
+ column = self.parent.columns.index { |x| qualifier.qualifies? x }
33
+ return self.children.at(column) if column
34
+ raise Accessibility::SearchFailure.new(self.parent, 'column', filters)
35
+ end
36
+
37
+ end
@@ -1,3 +1,5 @@
1
+ require 'ax/element'
2
+
1
3
  ##
2
4
  # Represents text on the screen that cannot directly be changed by
3
5
  # a user, usually a label or an instructional text block.
@@ -0,0 +1,86 @@
1
+ require 'ax/element'
2
+ require 'accessibility/string'
3
+
4
+ ##
5
+ # Represents the special `SystemWide` accessibility object.
6
+ #
7
+ # Previously, this object was a singleton, but that apparently causes
8
+ # problems with the AXAPIs. So you should always create a new instance
9
+ # of the system wide object when you need to use it (even though they
10
+ # are all the same thing).
11
+ class AX::SystemWide < AX::Element
12
+ include Accessibility::String
13
+
14
+ ##
15
+ # Overridden since there is only one way to get the element ref.
16
+ def initialize
17
+ super AXUIElementCreateSystemWide()
18
+ end
19
+
20
+ ##
21
+ # @note With the `SystemWide` class, using {#type_string} will send the
22
+ # events to which ever app has focus.
23
+ #
24
+ # Generate keyboard events by simulating keyboard input.
25
+ #
26
+ # See the {file:docs/KeyboardEvents.markdown Keyboard} documentation for
27
+ # more information on how to format strings.
28
+ #
29
+ # @return [Boolean]
30
+ def type_string string
31
+ @ref.post keyboard_events_for string
32
+ true
33
+ end
34
+
35
+ # @todo doc and cleanup
36
+ def keydown modifier
37
+ @ref.post [[EventGenerator::CUSTOM[modifier], true]]
38
+ true
39
+ end
40
+
41
+ # @todo doc and cleanup
42
+ def keyup modifier
43
+ @ref.post [[EventGenerator::CUSTOM[modifier], false]]
44
+ true
45
+ end
46
+
47
+ ##
48
+ # The system wide object cannot be used to perform searches. This method
49
+ # is just an override to avoid a difficult to understand error messages.
50
+ def search *args
51
+ raise NoMethodError, 'AX::SystemWide cannot search'
52
+ end
53
+
54
+ ##
55
+ # Raises an `NoMethodError` instead of (possibly) silently failing to
56
+ # register for a notification.
57
+ #
58
+ # @raise [NoMethodError]
59
+ def on_notification *args
60
+ raise NoMethodError, 'AX::SystemWide cannot register for notifications'
61
+ end
62
+
63
+ ##
64
+ # Find the element in at the given point for the topmost appilcation
65
+ # window.
66
+ #
67
+ # `nil` will be returned if there was nothing at that point.
68
+ #
69
+ # @param [#to_point]
70
+ # @return [AX::Element,nil]
71
+ def element_at point
72
+ process @ref.element_at point
73
+ end
74
+
75
+ ##
76
+ # Set the global messaging timeout. Searching through another interface
77
+ # and looking up attributes incurs a lot of IPC calls and sometimes an
78
+ # app is slow to respond.
79
+ #
80
+ # @param [Number]
81
+ # @return [Number]
82
+ def set_global_timeout seconds
83
+ @ref.set_timeout_to seconds
84
+ end
85
+
86
+ end
@@ -0,0 +1,25 @@
1
+ require 'awesome_print'
2
+
3
+ module AwesomePrint::AXElements
4
+
5
+ def self.included base
6
+ base.send :alias_method, :cast_without_ax_elements, :cast
7
+ base.send :alias_method, :cast, :cast_with_ax_elements
8
+ end
9
+
10
+ def cast_with_ax_elements object, type
11
+ cast = cast_without_ax_elements object, type
12
+ cast = :ax_element if object.kind_of? ::AX::Element
13
+ cast
14
+ end
15
+
16
+
17
+ private
18
+
19
+ def awesome_ax_element object
20
+ object.inspect
21
+ end
22
+
23
+ end
24
+
25
+ AwesomePrint::Formatter.send :include, AwesomePrint::AXElements
@@ -0,0 +1,8 @@
1
+ ##
2
+ # Workaround for MacRuby ticket #1334
3
+ class Exception
4
+ alias_method :original_message, :message
5
+ def message
6
+ "#{original_message}\n\t#{backtrace.join("\n\t")}"
7
+ end
8
+ end