AXElements 0.6.0beta2 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
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