class AX::Element

@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.

Constants

EQUALS

@private @return [String]

TRANSLATOR

@private @return [Accessibility::Translator]

Public Class Methods

new(ref) click to toggle source

@param ref [AXUIElementRef]

# File lib/ax/element.rb, line 25
def initialize ref
  @ref = ref
end

Public Instance Methods

==(other) click to toggle source

Overridden so that equality testing would work.

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

# File lib/ax/element.rb, line 514
def == other
  @ref == other.instance_variable_get(:@ref)
end
Also aliased as: eql?, equal?
actions() click to toggle source

List of available actions.

@example

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

@return [Array<Symbol>]

# File lib/ax/element.rb, line 206
def actions
  @actions ||= TRANSLATOR.rubyize @ref.actions
end
ancestor(kind, filters = {}) click to toggle source

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.

@example

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

@param kind [#to_s] @param filters [Hash{Symbol=>Object}] @yield Optional block used for search filtering @return [AX::Element,nil]

# File lib/ax/element.rb, line 293
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) click to toggle source

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

@example

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

@return [Array<AX::Element>]

# File lib/ax/element.rb, line 90
def ancestry elements = self
  elements = Array(elements)
  element  = elements.last
  if element.attributes.include? :parent
    ancestry(elements << element.attribute(:parent))
  else
    elements
  end
end
Also aliased as: lineage
application() click to toggle source

Get the application object for the element.

@return [AX::Application]

# File lib/ax/element.rb, line 483
def application
  @ref.application.to_ruby
end
attribute(attr) click to toggle source

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.

@example

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

@param attr [#to_sym]

# File lib/ax/element.rb, line 56
def attribute attr
  @ref.attribute(TRANSLATOR.cocoaify(attr)).to_ruby
end
attributes() click to toggle source

Cache of available attributes.

@example

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

@return [Array<Symbol>]

# File lib/ax/element.rb, line 40
def attributes
  @attrs ||= TRANSLATOR.rubyize @ref.attributes
end
blank?() click to toggle source

(see NilClass#blank?)

# File lib/ax/element.rb, line 488
def blank?
  false
end
bounds() click to toggle source

Get the bounding rectangle for the element.

@return [CGRect]

# File lib/ax/element.rb, line 474
def bounds
  CGRect.new(attribute(:position), attribute(:size))
end
Also aliased as: to_rect
children() click to toggle source

Fetch the children elements for the current element.

@return [Array<AX::Element>]

# File lib/ax/element.rb, line 75
def children
  @ref.children.to_ruby
end
description() click to toggle source

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.

@return [String]

# File lib/ax/element.rb, line 67
def description
  attribute(:description).to_ruby
end
eql?(other)
Alias for: ==
equal?(other)
Alias for: ==
hitpoint()
Alias for: to_point
inspect() click to toggle source

Get relevant details about the current object.

@return [String]

# File lib/ax/element.rb, line 382
def inspect
  "#<#{self.class}" << pp_identifier.to_s <<
                       pp_position << pp_children <<
                       pp_enabled << pp_focused << '>'
end
inspect_subtree() click to toggle source

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.

@example

puts app.inspect_subtree

@return [String]

# File lib/ax/element.rb, line 416
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?() click to toggle source

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.

# File lib/ax/element.rb, line 499
def invalid?
  @ref.invalid?
end
lineage(elements = self)
Alias for: ancestry
method_missing(method, *args, &block) click to toggle source

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`.

@example

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
Calls superclass method Accessibility::DSL#method_missing
# File lib/ax/element.rb, line 352
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
methods(include_super = true) click to toggle source

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

Calls superclass method
# File lib/ax/element.rb, line 506
def methods include_super = true
  super.concat(attributes).concat(parameterized_attributes)
end
parameterized_attribute(attr, param) click to toggle source

Get the value for a parameterized attribute.

@example

text_field.parameterized_attribute :string_for_range, 2..8

@param attr [#to_sym] @param param [Object]

# File lib/ax/element.rb, line 188
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_attributes() click to toggle source

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

@example

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

@return [Array<Symbol>]

# File lib/ax/element.rb, line 175
def parameterized_attributes
  @param_attrs ||= TRANSLATOR.rubyize @ref.parameterized_attributes
end
perform(action) click to toggle source

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.

@example

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

@param action [#to_sym] @return [Boolean] true if successful

# File lib/ax/element.rb, line 225
def perform action
  @ref.perform TRANSLATOR.cocoaify action
end
pid() click to toggle source

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

@example

element.pid # => 12345

@return [Fixnum]

# File lib/ax/element.rb, line 110
def pid
  @ref.pid
end
respond_to?(name, priv = false) click to toggle source

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.

Calls superclass method
# File lib/ax/element.rb, line 450
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') click to toggle source

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.

@example

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

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

@param path [#to_s] @return [String] path to the screenshot

# File lib/ax/element.rb, line 441
def screenshot path = '~/Desktop'
  capture_screen self, path
end
set(attr, value) click to toggle source

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

@example

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

@param attr [#to_sym] @return the value that you were setting is returned

# File lib/ax/element.rb, line 154
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) click to toggle source

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.

@example

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

@param attr [#to_sym] @return [Number]

# File lib/ax/element.rb, line 127
def size_of attr
  @ref.size_of TRANSLATOR.cocoaify attr
end
to_h() click to toggle source

@return [Hash{Symbol=>Object}]

# File lib/ax/element.rb, line 402
def to_h
  Hash[attributes.zip attributes.map { |attr| attribute(attr) }]
end
to_point() click to toggle source

Get the center point of the element.

@return [CGPoint]

# File lib/ax/element.rb, line 461
def to_point
  size  = attribute :size
  point = attribute :position
  point.x += size.width  / 2
  point.y += size.height / 2
  point
end
Also aliased as: hitpoint
to_rect()
Alias for: bounds
to_s() click to toggle source

@note Since ‘#inspect` is often overridden by subclasses, this cannot

be an alias.

An “alias” for {#inspect}.

@return [String]

# File lib/ax/element.rb, line 395
def to_s
  inspect
end
type(string) click to toggle source

@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.

@param string [String] @return [Boolean]

# File lib/ax/element.rb, line 238
def type string
  set_focus_to self unless focused?
  keyboard_events_for(string).each do |event|
    KeyCoder.post_event event
  end
end
writable?(attr) click to toggle source

Check whether or not an attribute is writable.

@example

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

@param attr [#to_sym]

# File lib/ax/element.rb, line 140
def writable? attr
  @ref.writable? TRANSLATOR.cocoaify attr
end