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
@param ref [AXUIElementRef]
# File lib/ax/element.rb, line 25 def initialize ref @ref = ref end
Public Instance Methods
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
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
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
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
Get the application object for the element.
@return [AX::Application]
# File lib/ax/element.rb, line 483 def application @ref.application.to_ruby end
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
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
(see NilClass#blank?)
# File lib/ax/element.rb, line 488 def blank? false end
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
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
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
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
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
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
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
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
Like {#respond_to?}, this is overriden to include attribute methods. Though, it does include dynamic predicate methods at the moment.
# File lib/ax/element.rb, line 506 def methods include_super = true super.concat(attributes).concat(parameterized_attributes) end
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
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
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
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
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.
# 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
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
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](github.com/Marketcircle/AXElements/wiki/Searching) for the details on search semantics.
@example Find the dock icon for the Finder app
AX::DOCK.search(:application_dock_item, title:'Finder')
@param kind [#to_s] @param filters [Hash{Symbol=>Object}] @yield Optional block used for filtering @return [AX::Element,nil,Array<AX::Element>,Array<>]
# File lib/ax/element.rb, line 264 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 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
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
@return [Hash{Symbol=>Object}]
# File lib/ax/element.rb, line 402 def to_h Hash[attributes.zip attributes.map { |attr| attribute(attr) }] end
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
@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
@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
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