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.
- data/.yardopts +1 -2
- data/README.markdown +152 -88
- data/Rakefile +8 -103
- data/docs/Debugging.markdown +9 -2
- data/docs/KeyboardEvents.markdown +114 -49
- data/docs/Setting.markdown +1 -0
- data/docs/images/next_version.png +0 -0
- data/ext/accessibility/key_coder/extconf.rb +22 -0
- data/ext/accessibility/key_coder/key_coder.c +113 -0
- data/lib/AXElements.rb +2 -0
- data/lib/accessibility/core.rb +897 -0
- data/lib/accessibility/debug.rb +168 -0
- data/lib/accessibility/dsl.rb +697 -0
- data/lib/accessibility/enumerators.rb +104 -0
- data/lib/accessibility/errors.rb +32 -0
- data/lib/accessibility/factory.rb +153 -0
- data/lib/accessibility/graph.rb +150 -0
- data/lib/{ax_elements/inspector.rb → accessibility/pp_inspector.rb} +39 -28
- data/lib/accessibility/qualifier.rb +158 -0
- data/lib/accessibility/string.rb +494 -0
- data/lib/accessibility/translator.rb +178 -0
- data/lib/accessibility/version.rb +7 -0
- data/lib/accessibility.rb +79 -0
- data/lib/ax/application.rb +234 -0
- data/lib/{ax_elements/elements → ax}/button.rb +2 -0
- data/lib/ax/element.rb +518 -0
- data/lib/{ax_elements/elements → ax}/radio_button.rb +2 -0
- data/lib/ax/row.rb +37 -0
- data/lib/{ax_elements/elements → ax}/static_text.rb +2 -0
- data/lib/ax/systemwide.rb +86 -0
- data/lib/ax_elements/awesome_print.rb +25 -0
- data/lib/ax_elements/exception_workaround.rb +8 -0
- data/lib/ax_elements/nsarray_compat.rb +64 -0
- data/lib/ax_elements/vendor/inflection_data.rb +65 -0
- data/lib/ax_elements/vendor/inflections.rb +172 -0
- data/lib/ax_elements/vendor/inflector.rb +306 -0
- data/lib/ax_elements.rb +14 -25
- data/lib/minitest/ax_elements.rb +112 -12
- data/lib/mouse.rb +72 -46
- data/lib/rspec/expectations/ax_elements.rb +133 -6
- data/rakelib/doc.rake +13 -0
- data/rakelib/ext.rake +61 -0
- data/rakelib/gem.rake +28 -0
- data/rakelib/test.rake +53 -0
- data/test/helper.rb +11 -97
- data/test/integration/accessibility/test_core.rb +18 -0
- data/test/integration/accessibility/test_debug.rb +44 -0
- data/test/integration/accessibility/test_dsl.rb +225 -0
- data/test/integration/accessibility/test_enumerators.rb +122 -0
- data/test/integration/accessibility/test_errors.rb +38 -0
- data/test/integration/accessibility/test_notifications.rb +22 -0
- data/test/integration/accessibility/test_qualifier.rb +148 -0
- data/test/integration/ax/test_application.rb +56 -0
- data/test/integration/ax/test_element.rb +46 -0
- data/test/integration/ax/test_row.rb +23 -0
- data/test/integration/ax_elements/test_nsarray_compat.rb +43 -0
- data/test/integration/minitest/test_ax_elements.rb +98 -0
- data/test/integration/rspec/expectations/test_ax_elements.rb +58 -0
- data/test/integration/test_mouse.rb +35 -0
- data/test/sanity/accessibility/test_core.rb +553 -0
- data/test/sanity/accessibility/test_debug.rb +63 -0
- data/test/sanity/accessibility/test_dsl.rb +75 -0
- data/test/sanity/accessibility/test_errors.rb +10 -0
- data/test/sanity/accessibility/test_factory.rb +88 -0
- data/test/sanity/accessibility/test_pp_inspector.rb +110 -0
- data/test/sanity/accessibility/test_qualifier.rb +13 -0
- data/test/sanity/accessibility/test_string.rb +238 -0
- data/test/sanity/accessibility/test_translator.rb +145 -0
- data/test/sanity/ax/test_application.rb +90 -0
- data/test/sanity/ax/test_element.rb +80 -0
- data/test/sanity/ax/test_systemwide.rb +66 -0
- data/test/sanity/ax_elements/test_nsarray_compat.rb +16 -0
- data/test/sanity/ax_elements/test_nsobject_inspect.rb +11 -0
- data/test/sanity/minitest/test_ax_elements.rb +15 -0
- data/test/sanity/rspec/expectations/test_ax_elements.rb +12 -0
- data/test/sanity/test_ax_elements.rb +10 -0
- data/test/sanity/test_mouse.rb +19 -0
- metadata +111 -93
- data/LICENSE.txt +0 -25
- data/ext/key_coder/extconf.rb +0 -6
- data/ext/key_coder/key_coder.m +0 -77
- data/lib/ax_elements/accessibility/enumerators.rb +0 -104
- data/lib/ax_elements/accessibility/graph.rb +0 -118
- data/lib/ax_elements/accessibility/language.rb +0 -347
- data/lib/ax_elements/accessibility/qualifier.rb +0 -73
- data/lib/ax_elements/accessibility.rb +0 -166
- data/lib/ax_elements/core.rb +0 -541
- data/lib/ax_elements/element.rb +0 -593
- data/lib/ax_elements/elements/application.rb +0 -88
- data/lib/ax_elements/elements/row.rb +0 -30
- data/lib/ax_elements/elements/systemwide.rb +0 -46
- data/lib/ax_elements/macruby_extensions.rb +0 -255
- data/lib/ax_elements/notification.rb +0 -37
- data/lib/ax_elements/version.rb +0 -9
- data/test/elements/test_application.rb +0 -72
- data/test/elements/test_row.rb +0 -27
- data/test/elements/test_systemwide.rb +0 -38
- data/test/test_accessibility.rb +0 -127
- data/test/test_blankness.rb +0 -26
- data/test/test_core.rb +0 -448
- data/test/test_element.rb +0 -939
- data/test/test_enumerators.rb +0 -81
- data/test/test_inspector.rb +0 -130
- data/test/test_language.rb +0 -157
- data/test/test_macruby_extensions.rb +0 -303
- data/test/test_mouse.rb +0 -5
- data/test/test_search_semantics.rb +0 -143
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Namespace for enumerators used to navigate accessibility hierarchies.
|
|
3
|
+
module Accessibility::Enumerators
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# Enumerator for visiting each element in a UI hierarchy in breadth
|
|
7
|
+
# first order.
|
|
8
|
+
class BreadthFirst
|
|
9
|
+
include Enumerable
|
|
10
|
+
|
|
11
|
+
# @param [AX::Element]
|
|
12
|
+
def initialize root
|
|
13
|
+
@root = root
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# Semi-lazily iterate through the tree.
|
|
18
|
+
#
|
|
19
|
+
# @yieldparam [AX::Element]
|
|
20
|
+
def each
|
|
21
|
+
# @todo Lazy-wrap element refs, might make things a bit faster
|
|
22
|
+
# for fat trees; what is impact on thin trees?
|
|
23
|
+
# @todo See if we can implement the method in a single loop
|
|
24
|
+
queue = [@root]
|
|
25
|
+
until queue.empty?
|
|
26
|
+
queue.shift.attribute(:children).each do |x|
|
|
27
|
+
queue << x if x.attributes.include? :children
|
|
28
|
+
yield x
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# @note Explicitly defined so that escaping at the first found element
|
|
35
|
+
# actually works. Since only a single `break` is called when an
|
|
36
|
+
# item is found it does not fully escape the method. Technically,
|
|
37
|
+
# we need to do this with other 'escape-early' iteraters, but
|
|
38
|
+
# they aren't being used...yet.
|
|
39
|
+
#
|
|
40
|
+
# Override `Enumerable#find` for performance reasons.
|
|
41
|
+
def find
|
|
42
|
+
each { |x| return x if yield x }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Enumerator for visitng each element in a UI hierarchy in
|
|
49
|
+
# depth first order.
|
|
50
|
+
class DepthFirst
|
|
51
|
+
include Enumerable
|
|
52
|
+
|
|
53
|
+
# @param [AX::Element]
|
|
54
|
+
def initialize root
|
|
55
|
+
@root = root
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @yieldparam [AX::Element]
|
|
59
|
+
def each
|
|
60
|
+
stack = @root.attribute(:children)
|
|
61
|
+
until stack.empty?
|
|
62
|
+
current = stack.shift
|
|
63
|
+
yield current
|
|
64
|
+
if current.attributes.include? :children
|
|
65
|
+
# need to reverse it since child ordering seems to matter in practice
|
|
66
|
+
stack.unshift *current.attribute(:children)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# Walk the UI element tree and yield both the element and the level
|
|
73
|
+
# that the element is at relative to the root.
|
|
74
|
+
#
|
|
75
|
+
# @yieldparam [AX::Element]
|
|
76
|
+
# @yieldparam [Number]
|
|
77
|
+
def each_with_level &block
|
|
78
|
+
# @todo A bit of a hack that I would like to fix one day...
|
|
79
|
+
@root.attribute(:children).each do |element|
|
|
80
|
+
recursive_each_with_level element, 1, block
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# Recursive implementation of a depth first iterator.
|
|
89
|
+
#
|
|
90
|
+
# @param [AX::Element]
|
|
91
|
+
# @param [Number]
|
|
92
|
+
# @param [#call]
|
|
93
|
+
def recursive_each_with_level element, depth, block
|
|
94
|
+
block.call element, depth
|
|
95
|
+
if element.respond_to? :children
|
|
96
|
+
element.attribute(:children).each do |x|
|
|
97
|
+
recursive_each_with_level x, depth + 1, block
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'accessibility/debug'
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Error raised when an implicit search fails to return a result.
|
|
5
|
+
class Accessibility::SearchFailure < NoMethodError
|
|
6
|
+
|
|
7
|
+
def initialize searcher, searchee, filters
|
|
8
|
+
filters = {} unless filters.kind_of? Hash
|
|
9
|
+
msg = "Could not find `#{pp_searchee searchee, filters}` "
|
|
10
|
+
msg << "as a child of #{searcher.class}\n"
|
|
11
|
+
msg << "Element Path:\n\t" << path_to(searcher)
|
|
12
|
+
# @todo Consider turning this on by default
|
|
13
|
+
msg << "\nSubtree:\n\t" << debug(searcher) if Accessibility::Debug.on?
|
|
14
|
+
super msg
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def pp_searchee searchee, filters
|
|
21
|
+
Accessibility::Qualifier.new(searchee, filters).describe
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def path_to element
|
|
25
|
+
Accessibility::Debug.path(element).map! { |x| x.inspect }.join("\n\t")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def debug searcher
|
|
29
|
+
Accessibility::Debug.text_subtree(searcher)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
require 'accessibility/core'
|
|
2
|
+
require 'accessibility/translator'
|
|
3
|
+
|
|
4
|
+
##
|
|
5
|
+
# Namespace container for all the accessibility objects.
|
|
6
|
+
module AX; end
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Mixin made for processing low level data from AXAPI methods.
|
|
10
|
+
module Accessibility::Factory
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# Processes any given data from an AXAPI method and wraps it if
|
|
14
|
+
# needed. Meant for taking a return value from {Accessibility::Core#attr:for:}
|
|
15
|
+
# and friends.
|
|
16
|
+
#
|
|
17
|
+
# Generally, used to process an `AXValue` into a `CGPoint` or an
|
|
18
|
+
# `AXUIElementRef` into some kind of {AX::Element} object.
|
|
19
|
+
def process value
|
|
20
|
+
return nil if value.nil? # CFGetTypeID(nil) crashes runtime
|
|
21
|
+
case CFGetTypeID(value)
|
|
22
|
+
when ARRAY_TYPE then process_array value
|
|
23
|
+
when REF_TYPE then process_element value
|
|
24
|
+
else
|
|
25
|
+
value
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# @private
|
|
34
|
+
#
|
|
35
|
+
# Reference to the singleton instance of the translator.
|
|
36
|
+
#
|
|
37
|
+
# @return [Accessibility::Translator]
|
|
38
|
+
TRANSLATOR = Accessibility::Translator.instance
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
# @private
|
|
42
|
+
#
|
|
43
|
+
# Type ID for `AXUIElementRef` objects.
|
|
44
|
+
#
|
|
45
|
+
# @return [Number]
|
|
46
|
+
REF_TYPE = AXUIElementGetTypeID()
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# @private
|
|
50
|
+
#
|
|
51
|
+
# Type ID for `CFArrayRef` objects.
|
|
52
|
+
#
|
|
53
|
+
# @return [Number]
|
|
54
|
+
ARRAY_TYPE = CFArrayGetTypeID()
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# @todo Should we handle cases where a subrole has a value of
|
|
58
|
+
# 'Unknown'? What is the performance impact?
|
|
59
|
+
#
|
|
60
|
+
# Takes an `AXUIElementRef` and gives you some kind of wrapped
|
|
61
|
+
# accessibility object.
|
|
62
|
+
#
|
|
63
|
+
# Some code paths have been unrolled for efficiency. Don't hate player,
|
|
64
|
+
# hate the game.
|
|
65
|
+
#
|
|
66
|
+
# @param [AXUIElementRef]
|
|
67
|
+
# @return [AX::Element]
|
|
68
|
+
def process_element ref
|
|
69
|
+
role = TRANSLATOR.unprefix ref.role
|
|
70
|
+
attrs = ref.attributes
|
|
71
|
+
klass = if attrs.include? KAXSubroleAttribute
|
|
72
|
+
subrole = ref.subrole
|
|
73
|
+
# Some objects claim to have a subrole but return nil
|
|
74
|
+
if subrole
|
|
75
|
+
class_for TRANSLATOR.unprefix(subrole), and: role
|
|
76
|
+
else
|
|
77
|
+
class_for role
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
class_for role
|
|
81
|
+
end
|
|
82
|
+
klass.new ref
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
##
|
|
86
|
+
# We assume a homogeneous array and only wrap element arrays right now.
|
|
87
|
+
#
|
|
88
|
+
# @return [Array]
|
|
89
|
+
def process_array vals
|
|
90
|
+
return vals if vals.empty?
|
|
91
|
+
return vals if CFGetTypeID(vals.first) != REF_TYPE
|
|
92
|
+
return vals.map { |val| process_element val }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
##
|
|
96
|
+
# @todo Consider using {AX.const_missing} instead.
|
|
97
|
+
#
|
|
98
|
+
# Find the class for a given role. If the class does not exist it will
|
|
99
|
+
# be created on demand.
|
|
100
|
+
#
|
|
101
|
+
# @param [#to_s]
|
|
102
|
+
# @return [Class]
|
|
103
|
+
def class_for role
|
|
104
|
+
if AX.const_defined? role, false
|
|
105
|
+
AX.const_get role
|
|
106
|
+
else
|
|
107
|
+
create_class role
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
##
|
|
112
|
+
# Find the class for a given subrole and role. If the class does not
|
|
113
|
+
# exist it will be created on demand.
|
|
114
|
+
#
|
|
115
|
+
# @param [#to_s]
|
|
116
|
+
# @param [#to_s]
|
|
117
|
+
# @return [Class]
|
|
118
|
+
def class_for subrole, and: role
|
|
119
|
+
# @todo it would be nice if we didn't have to lookup twice
|
|
120
|
+
if AX.const_defined? subrole, false
|
|
121
|
+
AX.const_get subrole
|
|
122
|
+
else
|
|
123
|
+
create_class subrole, with_superclass: role
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
##
|
|
128
|
+
# Create a new class in the {AX} namespace that has {AX::Element}
|
|
129
|
+
# as the superclass.
|
|
130
|
+
#
|
|
131
|
+
# @param [#to_s]
|
|
132
|
+
# @return [Class]
|
|
133
|
+
def create_class name
|
|
134
|
+
klass = Class.new AX::Element
|
|
135
|
+
AX.const_set name, klass
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
##
|
|
139
|
+
# Create a new class in the {AX} namesapce that has the given
|
|
140
|
+
# `superklass` as the superclass..
|
|
141
|
+
#
|
|
142
|
+
# @param [#to_s] name
|
|
143
|
+
# @param [#to_s] superklass
|
|
144
|
+
# @return [Class]
|
|
145
|
+
def create_class name, with_superclass: superklass
|
|
146
|
+
unless AX.const_defined? superklass, false
|
|
147
|
+
create_class superklass
|
|
148
|
+
end
|
|
149
|
+
klass = Class.new AX.const_get(superklass)
|
|
150
|
+
AX.const_set name, klass
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
##
|
|
2
|
+
# DOT graph generator for AXElements. It can generate the digraph code
|
|
3
|
+
# for a UI subtree. That code can then be given to GraphViz to generate
|
|
4
|
+
# an image for the graph.
|
|
5
|
+
#
|
|
6
|
+
# You can learn more about generating graphs in the
|
|
7
|
+
# {file:docs/Debugging.markdown Debugging} tutorial.
|
|
8
|
+
class Accessibility::Graph
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
# @todo Graphs could be a lot nicer looking. That is, nodes could be much
|
|
12
|
+
# more easily identifiable, by allowing different classes to tell
|
|
13
|
+
# the node more about itself. A mixin module/protocol should
|
|
14
|
+
# probably be created, just as with the inspector mixin, and added
|
|
15
|
+
# to abstract base and overridden as needed in subclasses. In this
|
|
16
|
+
# way, an object can be more specific about what shape it is, how
|
|
17
|
+
# it is coloured, etc.
|
|
18
|
+
# Reference: http://www.graphviz.org/doc/info/attrs.html
|
|
19
|
+
#
|
|
20
|
+
# A node in the UI hierarchy. Used by {Accessibility::Graph} in order
|
|
21
|
+
# to build Graphviz DOT graphs.
|
|
22
|
+
class Node
|
|
23
|
+
|
|
24
|
+
# @return [String]
|
|
25
|
+
attr_reader :id
|
|
26
|
+
|
|
27
|
+
# @return [AX::Element]
|
|
28
|
+
attr_reader :element
|
|
29
|
+
|
|
30
|
+
# @param [AX::Element]
|
|
31
|
+
def initialize element
|
|
32
|
+
@element = element
|
|
33
|
+
@id = "element_#{element.object_id}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [String]
|
|
37
|
+
def to_dot
|
|
38
|
+
"#{@id} #{identifier} #{shape}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
EMPTY_STRING = ''
|
|
45
|
+
NAMESPACE = '::'
|
|
46
|
+
|
|
47
|
+
def identifier
|
|
48
|
+
klass = @element.class.to_s.split(NAMESPACE).last
|
|
49
|
+
ident = @element.pp_identifier
|
|
50
|
+
ident.gsub! /"/, '\"'
|
|
51
|
+
"[label = \"#{klass}#{ident}\"]"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def shape
|
|
55
|
+
@element.actions.empty? ? OVAL : BOX
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def enabled
|
|
59
|
+
FILL if @element.respond_to?(:enabled) && !@element.enabled?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def focus
|
|
63
|
+
BOLD if @element.respond_to?(:focused) && @element.focused?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
OVAL = '[shape = oval]'
|
|
67
|
+
BOX = '[shape = box]'
|
|
68
|
+
BOLD = '[style = bold]'
|
|
69
|
+
FILL = '[style = filled] [color = "grey"]'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# An edge in the UI hierarchy. Used by {Accessibility::Graph} in order
|
|
74
|
+
# to build Graphviz DOT graphs.
|
|
75
|
+
class Edge
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# The style of arrowhead to use
|
|
79
|
+
#
|
|
80
|
+
# @return [String]
|
|
81
|
+
attr_accessor :style
|
|
82
|
+
|
|
83
|
+
# @param [Accessibility::Graph::Node]
|
|
84
|
+
# @param [Accessibility::Graph::Node]
|
|
85
|
+
def initialize head, tail
|
|
86
|
+
@head = head
|
|
87
|
+
@tail = tail
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @return [String]
|
|
91
|
+
def to_dot
|
|
92
|
+
arrow = style ? style : 'normal'
|
|
93
|
+
"#{@head.id} -> #{@tail.id} [arrowhead = #{arrow}]"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
##
|
|
100
|
+
# List of nodes in the UI hierarchy.
|
|
101
|
+
#
|
|
102
|
+
# @return [Array<Accessibility::Graph::Node>]
|
|
103
|
+
attr_reader :nodes
|
|
104
|
+
|
|
105
|
+
##
|
|
106
|
+
# List of edges in the graph.
|
|
107
|
+
#
|
|
108
|
+
# @return [Array<Accessibility::Graph::Edge>]
|
|
109
|
+
attr_reader :edges
|
|
110
|
+
|
|
111
|
+
# @param [AX::Element]
|
|
112
|
+
def initialize root
|
|
113
|
+
root_node = Node.new(root)
|
|
114
|
+
@nodes = [root_node]
|
|
115
|
+
@edges = []
|
|
116
|
+
|
|
117
|
+
# exploit the ordering of a breadth-first enumeration to simplify
|
|
118
|
+
# the creation of edges for the graph. This only works because
|
|
119
|
+
# the UI hiearchy is a simple tree.
|
|
120
|
+
@edge_queue = Array.new(root.size_of(:children), root_node)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
##
|
|
124
|
+
# Construct the list of nodes and edges for the graph.
|
|
125
|
+
#
|
|
126
|
+
# The secret sauce is that we create an edge queue to exploit the
|
|
127
|
+
# breadth first ordering of the enumerator, which makes building the
|
|
128
|
+
# edges very easy.
|
|
129
|
+
def build!
|
|
130
|
+
Accessibility::Enumerators::BreadthFirst.new(nodes.last.element).each do |element|
|
|
131
|
+
nodes << node = Node.new(element)
|
|
132
|
+
edges << Edge.new(node, @edge_queue.shift)
|
|
133
|
+
@edge_queue.concat Array.new(element.size_of(:children), node)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
##
|
|
138
|
+
# Generate the `dot` graph code. You should take this string and
|
|
139
|
+
# feed it to the `dot` program to have it generate the graph.
|
|
140
|
+
#
|
|
141
|
+
# @return [String]
|
|
142
|
+
def to_dot
|
|
143
|
+
graph = "digraph {\n"
|
|
144
|
+
graph << nodes.map(&:to_dot).join("\n")
|
|
145
|
+
graph << "\n\n"
|
|
146
|
+
graph << edges.map(&:to_dot).join("\n")
|
|
147
|
+
graph << "\n}\n"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
end
|
|
@@ -12,27 +12,16 @@
|
|
|
12
12
|
#
|
|
13
13
|
module Accessibility::PPInspector
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
protected
|
|
17
|
-
|
|
18
|
-
##
|
|
19
|
-
# Added for backwards compatability with Snow Leopard.
|
|
20
|
-
#
|
|
21
|
-
# @return [String]
|
|
22
|
-
KAXIdentifierAttribute = 'AXIdentifier'.freeze
|
|
23
|
-
|
|
24
15
|
##
|
|
25
|
-
# @todo I feel a bit bad about having such a large method that has
|
|
26
|
-
# some inefficiencies.
|
|
27
|
-
#
|
|
28
16
|
# Create an identifier for `self` using various attributes that should
|
|
29
17
|
# make it very easy to identify the element.
|
|
30
18
|
#
|
|
31
19
|
# @return [String]
|
|
32
20
|
def pp_identifier
|
|
33
|
-
#
|
|
21
|
+
# @todo Break this method up into chunks
|
|
22
|
+
# @note use, or lack of use, of #inspect is intentional for visual effect
|
|
34
23
|
|
|
35
|
-
if attributes.include?
|
|
24
|
+
if attributes.include? :value
|
|
36
25
|
val = attribute :value
|
|
37
26
|
if val.kind_of? NSString
|
|
38
27
|
return " #{val.inspect}" unless val.empty?
|
|
@@ -42,27 +31,27 @@ module Accessibility::PPInspector
|
|
|
42
31
|
end
|
|
43
32
|
end
|
|
44
33
|
|
|
45
|
-
if attributes.include?
|
|
34
|
+
if attributes.include? :title
|
|
46
35
|
val = attribute(:title)
|
|
47
36
|
return " #{val.inspect}" if val && !val.empty?
|
|
48
37
|
end
|
|
49
38
|
|
|
50
|
-
if attributes.include?
|
|
39
|
+
if attributes.include? :title_ui_element
|
|
51
40
|
val = attribute :title_ui_element
|
|
52
|
-
return
|
|
41
|
+
return " #{val.inspect}" if val
|
|
53
42
|
end
|
|
54
43
|
|
|
55
|
-
if attributes.include?
|
|
44
|
+
if attributes.include? :description
|
|
56
45
|
val = attribute(:description).to_s
|
|
57
|
-
return
|
|
46
|
+
return " #{val}" unless val.empty?
|
|
58
47
|
end
|
|
59
48
|
|
|
60
|
-
if attributes.include?
|
|
49
|
+
if attributes.include? :identifier
|
|
61
50
|
return " id=#{attribute(:identifier)}"
|
|
62
51
|
end
|
|
63
52
|
|
|
64
53
|
# @todo should we have other fallbacks?
|
|
65
|
-
return
|
|
54
|
+
return EMPTY_STRING
|
|
66
55
|
end
|
|
67
56
|
|
|
68
57
|
##
|
|
@@ -75,7 +64,7 @@ module Accessibility::PPInspector
|
|
|
75
64
|
if position
|
|
76
65
|
" (#{position.x}, #{position.y})"
|
|
77
66
|
else
|
|
78
|
-
|
|
67
|
+
EMPTY_STRING
|
|
79
68
|
end
|
|
80
69
|
end
|
|
81
70
|
|
|
@@ -89,9 +78,9 @@ module Accessibility::PPInspector
|
|
|
89
78
|
if child_count > 1
|
|
90
79
|
" #{child_count} children"
|
|
91
80
|
elsif child_count == 1
|
|
92
|
-
|
|
81
|
+
ONE_CHILD
|
|
93
82
|
else # there are some odd edge cases
|
|
94
|
-
|
|
83
|
+
EMPTY_STRING
|
|
95
84
|
end
|
|
96
85
|
end
|
|
97
86
|
|
|
@@ -103,7 +92,7 @@ module Accessibility::PPInspector
|
|
|
103
92
|
# @param [Symbol]
|
|
104
93
|
# @return [String]
|
|
105
94
|
def pp_checkbox attr
|
|
106
|
-
" #{attr}[#{attribute(attr) ?
|
|
95
|
+
" #{attr}[#{attribute(attr) ? CHECKMARK : CROSS }]"
|
|
107
96
|
end
|
|
108
97
|
|
|
109
98
|
|
|
@@ -112,10 +101,32 @@ module Accessibility::PPInspector
|
|
|
112
101
|
##
|
|
113
102
|
# @private
|
|
114
103
|
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
104
|
+
# Constant string used by {#pp_checkbox}.
|
|
105
|
+
#
|
|
106
|
+
# @return [String]
|
|
107
|
+
CHECKMARK = '✔'
|
|
108
|
+
|
|
109
|
+
##
|
|
110
|
+
# @private
|
|
111
|
+
#
|
|
112
|
+
# Constant string used by {#pp_checkbox}.
|
|
113
|
+
#
|
|
114
|
+
# @return [String]
|
|
115
|
+
CROSS = '✘'
|
|
116
|
+
|
|
117
|
+
##
|
|
118
|
+
# @private
|
|
119
|
+
#
|
|
120
|
+
# Constant string used by {#pp_children}.
|
|
117
121
|
#
|
|
118
122
|
# @return [String]
|
|
119
|
-
|
|
123
|
+
ONE_CHILD = ' 1 child'
|
|
120
124
|
|
|
125
|
+
##
|
|
126
|
+
# @private
|
|
127
|
+
#
|
|
128
|
+
# Constant used all over the place.
|
|
129
|
+
#
|
|
130
|
+
# @return [String]
|
|
131
|
+
EMPTY_STRING = ''
|
|
121
132
|
end
|