Class: AX::Element Abstract

Inherits:
Object
  • Object
show all
Includes:
Accessibility::PrettyPrinter
Defined in:
lib/ax/element.rb,
lib/accessibility/factory.rb

Overview

This class is abstract.

The abstract base class for all accessibility objects. AX::Element composes low level AXUIElementRef objects into a more Rubyish interface.

This abstract base class provides generic functionality that all accessibility objects require.

Attributes collapse

Parameterized Attributes collapse

Actions collapse

Search collapse

Instance Method Summary collapse

Methods included from Accessibility::PrettyPrinter

#pp_checkbox, #pp_children, #pp_enabled, #pp_focused, #pp_identifier, #pp_position

Constructor Details

#initialize(ref) ⇒ Element

Returns a new instance of Element.

Parameters:

  • ref (AXUIElementRef)


26
27
28
# File 'lib/ax/element.rb', line 26

def initialize ref
  @ref = ref
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &block) ⇒ Object

We use #method_missing to dynamically handle requests to lookup attributes or search for elements in the view hierarchy. An attribute lookup is always tried first, followed by a parameterized attribute lookup, and then finally a search.

Failing all lookups, this method calls super, which will probably raise an exception; however, most elements have children and so it is more likely that you will get an Accessibility::SearchFailure in cases where you sholud get a NoMethodError.

Examples:


mail   = Accessibility.application_with_bundle_identifier 'com.apple.mail'

# attribute lookup
window = mail.focused_window
# is equivalent to
window = mail.attribute :focused_window

# attribute setting
window.position = CGPoint.new(100, 100)
# is equivalent to
window.set :position, CGPoint.new(100, 100)

# parameterized attribute lookup
window.title_ui_element.string_for_range 1..10
# is equivalent to
title = window.attribute :title_ui_element
title.parameterized_attribute :string_for_range, 1..10

# simple single element search
window.button # => You want the first Button that is found
# is equivalent to
window.search :button, {}

# simple multi-element search
window.buttons # => You want all the Button objects found
# is equivalent to
window.search :buttons, {}

# filters for a single element search
window.button(title: 'Log In') # => First Button with a title of 'Log In'
# is equivalent to
window.search :button, title: 'Log In'

# searching from #method_missing will #raise if nothing is found
window.application # => SearchFailure is raised


353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/ax/element.rb', line 353

def method_missing method, *args, &block
  return set(method.to_s.chomp(EQUALS), args.first) if method[-1] == EQUALS

  key = TRANSLATOR.cocoaify method
  if @ref.attributes.include? key
    return attribute(method)

  elsif @ref.parameterized_attributes.include? key
    return parameterized_attribute(method, args.first)

  elsif @ref.attributes.include? KAXChildrenAttribute
    if (result = search(method, *args, &block)).blank?
      raise Accessibility::SearchFailure.new(self, method, args.first, &block)
    else
      return result
    end

  else
    super

  end
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?, equal?

Overridden so that equality testing would work.

A hack, but the only sane way I can think of to test for equivalency.



515
516
517
# File 'lib/ax/element.rb', line 515

def == other
  @ref == other.instance_variable_get(:@ref)
end

#actionsArray<Symbol>

List of available actions.

Examples:


toolbar.actions # => []
button.actions  # => [:press]
menu.actions    # => [:open, :cancel]

Returns:



207
208
209
# File 'lib/ax/element.rb', line 207

def actions
  @actions ||= TRANSLATOR.rubyize @ref.actions
end

#ancestor(kind, filters = {}) { ... } ⇒ AX::Element?

Search for an ancestor of the current element.

As the opposite of #search, this also takes filters, and can be used to find a specific ancestor for the current element.

Returns nil if no ancestor is found.

Examples:


button.ancestor :window       # => #<AX::StandardWindow>
row.ancestor    :scroll_area  # => #<AX::ScrollArea>

Parameters:

  • kind (#to_s)
  • filters (Hash{Symbol=>Object}) (defaults to: {})

Yields:

  • Optional block used for search filtering

Returns:



294
295
296
297
298
299
300
301
302
# File 'lib/ax/element.rb', line 294

def ancestor kind, filters = {}, &block
  qualifier = Accessibility::Qualifier.new(kind, filters, &block)
  element   = self
  until qualifier.qualifies? element
    element = element.attribute :parent
    break unless element
  end
  element
end

#ancestry(elements = self) ⇒ Array<AX::Element> Also known as: lineage

Get a list of elements, starting with the receiver and riding the hierarchy up to the top level object (i.e. the Application)

Examples:


element = AX::DOCK.list.application_dock_item
element.ancestry
  # => [#<AX::ApplicationDockItem...>, #<AX::List...>, #<AX::Application...>]

Returns:



91
92
93
94
95
96
97
98
99
# File 'lib/ax/element.rb', line 91

def ancestry elements = self
  elements = Array(elements)
  element  = elements.last
  if element.attributes.include? :parent
    ancestry(elements << element.attribute(:parent))
  else
    elements
  end
end

#applicationAX::Application

Get the application object for the element.

Returns:



484
485
486
# File 'lib/ax/element.rb', line 484

def application
  @ref.application.to_ruby
end

#attribute(attr) ⇒ Object

Get the value of an attribute. This method will return nil if the attribute does not have a value or if the element is dead. The execption to the rule is that the :children attribute will always return an array unless the element does not have the :children attribute.

Examples:


element.attribute :position # => #<CGPoint x=123.0 y=456.0>

Parameters:

  • attr (#to_sym)


57
58
59
# File 'lib/ax/element.rb', line 57

def attribute attr
  @ref.attribute(TRANSLATOR.cocoaify(attr)).to_ruby
end

#attributesArray<Symbol>

Cache of available attributes.

Examples:


window.attributes # => [:size, :position, :title, ...]

Returns:



41
42
43
# File 'lib/ax/element.rb', line 41

def attributes
  @attrs ||= TRANSLATOR.rubyize @ref.attributes
end

#blank?Boolean

Returns:

  • (Boolean)


489
490
491
# File 'lib/ax/element.rb', line 489

def blank?
  false
end

#boundsCGRect Also known as: to_rect

Get the bounding rectangle for the element.

Returns:

  • (CGRect)


475
476
477
# File 'lib/ax/element.rb', line 475

def bounds
  CGRect.new(attribute(:position), attribute(:size))
end

#childrenArray<AX::Element>

Fetch the children elements for the current element.

Returns:



76
77
78
# File 'lib/ax/element.rb', line 76

def children
  @ref.children.to_ruby
end

#descriptionString

Get the accessibility description for the element.

This overrides the inherited NSObject#description. If you want a description of the object then you should use #inspect instead.

Returns:

  • (String)


68
69
70
# File 'lib/ax/element.rb', line 68

def description
  attribute(:description).to_ruby
end

#inspectString

Get relevant details about the current object.

Returns:

  • (String)


383
384
385
386
387
# File 'lib/ax/element.rb', line 383

def inspect
  "#<#{self.class}" << pp_identifier.to_s <<
                       pp_position << pp_children <<
                       pp_enabled << pp_focused << '>'
end

#inspect_subtreeString

Get the relevant details about the receiver and also the children and further descendents of the receiver. Each generation down the tree will be indented one level further.

Examples:


puts app.inspect_subtree

Returns:

  • (String)


417
418
419
420
421
422
423
424
# File 'lib/ax/element.rb', line 417

def inspect_subtree
  output = self.inspect + "\n"
  enum   = Accessibility::Enumerators::DepthFirst.new self
  enum.each_with_level do |element, depth|
    output << "\t"*depth + element.inspect + "\n"
  end
  output
end

#invalid?Boolean

Return whether or not the receiver is "dead".

A dead element is one that is no longer in the app's view hierarchy. This is not directly related to visibility, but an element that is invalid will not be visible, but an invisible element might not be invalid.

Returns:

  • (Boolean)


500
501
502
# File 'lib/ax/element.rb', line 500

def invalid?
  @ref.invalid?
end

#methods(include_super = true) ⇒ Object

Like #respond_to?, this is overriden to include attribute methods. Though, it does include dynamic predicate methods at the moment.



507
508
509
# File 'lib/ax/element.rb', line 507

def methods include_super = true
  super.concat(attributes).concat(parameterized_attributes)
end

#parameterized_attribute(attr, param) ⇒ Object

Get the value for a parameterized attribute.

Examples:


text_field.parameterized_attribute :string_for_range, 2..8

Parameters:

  • attr (#to_sym)
  • param (Object)


189
190
191
192
# File 'lib/ax/element.rb', line 189

def parameterized_attribute attr, param
  param = param.relative_to(@ref.value.size) if param.kind_of? Range
  @ref.parameterized_attribute(TRANSLATOR.cocoaify(attr), param).to_ruby
end

#parameterized_attributesArray<Symbol>

List of available parameterized attributes. Most elements have no parameterized attributes, but the ones that do have many.

Examples:


window.parameterized_attributes     # => []
text_field.parameterized_attributes # => [:string_for_range, :attributed_string, ...]

Returns:



176
177
178
# File 'lib/ax/element.rb', line 176

def parameterized_attributes
  @param_attrs ||= TRANSLATOR.rubyize @ref.parameterized_attributes
end

#perform(action) ⇒ Boolean

Tell an object to trigger an action.

For instance, you can tell a button to call the same method that would be called when pressing a button, except that the mouse will not move over to the button to press it, nor will the keyboard be used.

Examples:


button.perform :press    # => true
button.perform :make_pie # => false

Parameters:

  • action (#to_sym)

Returns:

  • (Boolean)

    true if successful



226
227
228
# File 'lib/ax/element.rb', line 226

def perform action
  @ref.perform TRANSLATOR.cocoaify action
end

#pidFixnum

Get the process identifier for the application that the element belongs to.

Examples:


element.pid # => 12345

Returns:

  • (Fixnum)


111
112
113
# File 'lib/ax/element.rb', line 111

def pid
  @ref.pid
end

#respond_to?(name, priv = false) ⇒ Boolean

Overriden to respond properly with regards to dynamic attribute lookups, but will return false for potential implicit searches.

This does not work for predicate methods at the moment.

Returns:

  • (Boolean)


451
452
453
454
455
456
# File 'lib/ax/element.rb', line 451

def respond_to? name, priv = false
  key = TRANSLATOR.cocoaify name.to_s.chomp(EQUALS)
  @ref.attributes.include?(key)               ||
  @ref.parameterized_attributes.include?(key) ||
  super
end

#screenshot(path = '~/Desktop') ⇒ String

Take a screen shot of the receiving element and save it to disk. If a file path is not given then the default value will put it on the desktop. The actual file name will automatically generated with a timestamp.

Examples:


app.main_window.screenshot
  # => "~/Desktop/AXElements-ScreenShot-20120422184650.png"

app.main_window.screenshot "/Volumes/SecretStash"
  # => "/Volumes/SecretStash/AXElements-ScreenShot-20150622032250.png"

Parameters:

  • path (#to_s) (defaults to: '~/Desktop')

Returns:

  • (String)

    path to the screenshot



442
443
444
# File 'lib/ax/element.rb', line 442

def screenshot path = '~/Desktop'
  capture_screen self, path
end

#search(kind, filters = {}) { ... } ⇒ AX::Element, ...

Perform a breadth first search through the view hierarchy rooted at the current element. If you are concerned about the return value of this method, you can call #blank? on the return object.

See the Searching wiki for the details on search semantics.

Examples:

Find the dock icon for the Finder app


AX::DOCK.search(:application_dock_item, title:'Finder')

Parameters:

  • kind (#to_s)
  • filters (Hash{Symbol=>Object}) (defaults to: {})

Yields:

  • Optional block used for filtering

Returns:



265
266
267
268
269
270
271
272
273
274
275
# File 'lib/ax/element.rb', line 265

def search kind, filters = {}, &block
  kind      = kind.to_s
  qualifier = Accessibility::Qualifier.new(kind, filters, &block)
  tree      = Accessibility::Enumerators::BreadthFirst.new(self)

  if TRANSLATOR.singularize(kind) == kind
    tree.find     { |element| qualifier.qualifies? element }
  else
    tree.find_all { |element| qualifier.qualifies? element }
  end
end

#set(attr, value) ⇒ Object

Set a writable attribute on the element to the given value.

Examples:


element.set :value, 'Hello, world!'
element.set :size,  [100, 200].to_size

Parameters:

  • attr (#to_sym)

Returns:

  • the value that you were setting is returned



155
156
157
158
159
160
161
# File 'lib/ax/element.rb', line 155

def set attr, value
  unless writable? attr
    raise ArgumentError, "#{attr} is read-only for #{inspect}"
  end
  value = value.relative_to(@ref.value.size) if value.kind_of? Range
  @ref.set TRANSLATOR.cocoaify(attr), value
end

#size_of(attr) ⇒ Number

Return the #size of an attribute. This only works for attributes that are a collection. This exists because it is much more efficient to find out how many children exist using this API instead of getting the children array and asking for the size.

Examples:


table.size_of  :rows     # => 111
window.size_of :children # => 16

Parameters:

  • attr (#to_sym)

Returns:

  • (Number)


128
129
130
# File 'lib/ax/element.rb', line 128

def size_of attr
  @ref.size_of TRANSLATOR.cocoaify attr
end

#to_hHash{Symbol=>Object}

Returns:

  • (Hash{Symbol=>Object})


403
404
405
# File 'lib/ax/element.rb', line 403

def to_h
  Hash[attributes.zip attributes.map { |attr| attribute(attr) }]
end

#to_pointCGPoint Also known as: hitpoint

Get the center point of the element.

Returns:

  • (CGPoint)


462
463
464
465
466
467
468
# File 'lib/ax/element.rb', line 462

def to_point
  size  = attribute :size
  point = attribute :position
  point.x += size.width  / 2
  point.y += size.height / 2
  point
end

#to_sString

Note:

Since #inspect is often overridden by subclasses, this cannot be an alias.

An "alias" for #inspect.

Returns:

  • (String)


396
397
398
# File 'lib/ax/element.rb', line 396

def to_s
  inspect
end

#type(string) ⇒ Boolean

Note:

As of OS X 10.9 (Sea Lion), it is no longer possible to send keyboard events directly to an element. What we have here is only an approximation.

Send keyboard events to the receiver.

Parameters:

  • string (String)

Returns:

  • (Boolean)


239
240
241
242
243
244
# File 'lib/ax/element.rb', line 239

def type string
  set_focus_to self unless focused?
  keyboard_events_for(string).each do |event|
    KeyCoder.post_event event
  end
end

#writable?(attr) ⇒ Boolean

Check whether or not an attribute is writable.

Examples:


element.writable? :size  # => true
element.writable? :value # => false

Parameters:

  • attr (#to_sym)

Returns:

  • (Boolean)


141
142
143
# File 'lib/ax/element.rb', line 141

def writable? attr
  @ref.writable? TRANSLATOR.cocoaify attr
end