motion-accessibility 1.1.1 → 2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a5468abd3418ca116e6c77c58f430f557944426c
4
- data.tar.gz: 2d1807d3bad63b1908071dc77ea6cca9ebfa42d6
3
+ metadata.gz: 5a8b8a68fde38c107500ee9ba7391df3cf22d7a6
4
+ data.tar.gz: e979abc551392f9dede8c3dbf88199f70b1c34ff
5
5
  SHA512:
6
- metadata.gz: c386cc1b2e01afddf5b57a9fc22101f992615804f0d828f6e66e096815b03efb86e71b9203889dce6b6682c664da7be13c800a2fa79c31763e3db3faa26dea64
7
- data.tar.gz: 2661bbe13ac9c29a0379ba2249fbda14cd202f3bbae8661475de89cdc67bb88b0cc2779cdc9e2640934556376da452ab413370c377f47fce48acdfeeecbfb0d1
6
+ metadata.gz: b97f12b278ea3522bd242c8ee9c94bcae1f67cbf5e17ba4ff87839ccd2997bc73164f3524e879b574ef6e770b42e849524276efd0399d1a85334a9104e960479
7
+ data.tar.gz: 356842305034eb08ce3a4f49aebd4166e7c9156069295d636c9371f43c237143d6a148016e2bf2579dc2fb356bb93ffdf2f31ad370d3fbdc2f16b81b14ff0e26
data/README.md CHANGED
@@ -23,6 +23,82 @@ Or install it yourself as:
23
23
  $ gem install motion-accessibility
24
24
 
25
25
  ## Usage
26
+ ### The Motion-Accessibility Console
27
+
28
+ The motion-accessibility console gives you a way to interact with a running application through a purely textual interface. This works well for blind developers and command line users.
29
+
30
+ #### Enabling the Console
31
+
32
+ To enable the console, you can do one of two things. If you would just like to try it, type `include Accessibility::Console` at a REPL prompt. If you would like to use it in your application, add `require motion-accessibility-console` to your Rakefile. You have to do this even if you use bundler.
33
+
34
+ #### `browse` or `b`
35
+
36
+ The `browse` or `b` command lets you examine the view hierarchy in a speech-friendly way. This lets you see all the relevant views displayed in your running application. It will detect if the screen has changed and refresh itself automatically.
37
+
38
+ The following examples come from the sample app included with motion-accessibility.
39
+
40
+ ```
41
+ (main)> browse
42
+ Browsing UIWindow
43
+ 1 UILabel Hello!
44
+ 2 Touchable UITextField
45
+ 3 Touchable UIRoundedRectButton Update
46
+ 4 UINavigationBar
47
+ 5 UITabBar with 2 subviews
48
+ => nil
49
+ ```
50
+
51
+ If a view has subviews, you can browse that view.
52
+
53
+ ```
54
+ (main)> b 5
55
+ Browsing UITabBar
56
+ 0 Superview UIWindow
57
+ 1 Touchable UITabBarButton Test App
58
+ 2 Touchable UITabBarButton Table
59
+ => nil
60
+ ```
61
+
62
+ You can refresh the browser by passing the `:refresh` or `:top` keyword.
63
+
64
+ You may pass the `:scroll` keyword to scroll a UIScrollView or descendants, such as a UITableView. This still has some minor issues .
65
+
66
+ #### `view` or `v`
67
+
68
+ The `view` or `v` command simply returns the current view. If you have just browsed a view, it will return that. Otherwise, you may specify the view you wish to browse. Note that for all the commands, you may either use its number or accessibility label.
69
+
70
+ ```
71
+ (main)> v 1
72
+ => #<UITabBarButton:0x9380560>
73
+ ```
74
+
75
+ #### `touch`
76
+ The `touch` command lets you interact with the various controls. It works on all standard UIControls. `touch` can accept an argument depending on the type of control. For example, you can pass a UITextField a string to set its value.
77
+
78
+ ```
79
+ (main)> touch 2,"motion-accessibility rocks!"
80
+ Browsing UIWindow
81
+ 1 UILabel Hello!
82
+ 2 Touchable UITextField motion-accessibility rocks!
83
+ 3 Touchable UIRoundedRectButton Update
84
+ 4 UINavigationBar
85
+ 5 UITabBar with 2 subviews
86
+ => nil
87
+ ```
88
+
89
+ UIButtons can take a UIControlEvent, but default to `UIControlEventTouchUpInside`. Note here the use of an accessibility label to reference the view.
90
+
91
+ ```
92
+ (main)> touch "update"
93
+ Browsing UIWindow
94
+ 1 UILabel motion-accessibility rocks!
95
+ 2 Touchable UITextField motion-accessibility rocks!
96
+ 3 Touchable UIRoundedRectButton Update
97
+ 4 UINavigationBar
98
+ 5 UITabBar with 2 subviews
99
+ => nil
100
+ ```
101
+
26
102
  ### The Accessibility Inspector
27
103
 
28
104
  You can easily see the state of any of the following attributes and methods by using the accessibility inspector. Just call the `inspect_accessibility` method on any object.
@@ -0,0 +1,16 @@
1
+ class NSObject
2
+
3
+ def browse(*args)
4
+ A11y::Console.browse(*args)
5
+ end
6
+ def view(*args)
7
+ A11y::Console.view(*args)
8
+ end
9
+ def touch(*args)
10
+ A11y::Console.touch(*args)
11
+ end
12
+
13
+ alias :b :browse
14
+ alias :v :view
15
+
16
+ end
@@ -0,0 +1,8 @@
1
+ unless defined?(Motion::Project::Config)
2
+ raise "The motion-accessibility browser must be required within a RubyMotion project Rakefile."
3
+ end
4
+
5
+ lib_dir_path = File.dirname(File.expand_path(__FILE__))
6
+ Motion::Project::App.setup do |app|
7
+ app.files.unshift(Dir.glob(File.join(lib_dir_path, "motion-accessibility-console/**/*.rb")))
8
+ end
@@ -187,7 +187,16 @@ attributes.member?(attribute)
187
187
  end
188
188
 
189
189
  Touchable_Types = ["UITextField", "UIButton", "UIPickerView", "UIDatePicker",
190
- "UISegmentedControl", "UISlider", "UIStepper", "UISwitch"]
190
+ "UISegmentedControl", "UISlider", "UIStepper", "UISwitch",
191
+ "UITableViewCell", "UITabBarButton","UINavigationItemButtonView"]
192
+
193
+ Ignored_Views = ["UIView", "UILayoutContainerView", "UITransitionView", "UINavigationTransitionView", "UIViewControllerWrapperView", "UITableViewCellContentView", "UINavigationItemView", "UITableViewWrapperView"]
194
+
195
+ Ignored_ImageViews = ["UINavigationBar", "UITabBar", "UITableView"]
196
+
197
+ Reverse_Views = ["UITableView", "UINavigationBar", "UITabBar"]
198
+
199
+ View_Names = {"UINavigationItemButtonView" => "Back Button"}
191
200
 
192
201
  private
193
202
  def compile_attributes
@@ -0,0 +1,111 @@
1
+ module Accessibility
2
+ module Console
3
+
4
+ $browser_path=[]
5
+ Update_Delay=1.0
6
+
7
+ def self.touchable_type(view)
8
+ control=view.class
9
+ until A11y::Touchable_Types.member?(control.to_s)||control.nil?
10
+ control=control.superclass
11
+ end
12
+ control
13
+ end
14
+
15
+ def self.scrollable_view?(view)
16
+ control=view.class
17
+ until control==UIScrollView||control.nil?
18
+ control=control.superclass
19
+ end
20
+ control==UIScrollView
21
+ end
22
+
23
+ def self.init(view=nil)
24
+ view=UIApplication.sharedApplication.keyWindow if view.nil?
25
+ $browser_tree=A11y::Console::Tree.build(view)
26
+ $browser_path<<$browser_tree if $browser_path.empty?
27
+ nil
28
+ end
29
+
30
+ def self.display_views
31
+ $browser_current=$browser_tree unless $browser_current
32
+ puts "Browsing "+$browser_current.display_view
33
+ $browser_current.browsable_nodes.each_with_index do |node, index|
34
+ next if node.nil?
35
+ output=node.display_view( index)
36
+ puts output unless output.nil?
37
+ end
38
+ end
39
+
40
+ def self.start_refreshing
41
+ if !A11y::Data[:refresh]&&RUBYMOTION_ENV!='test'
42
+ NSTimer.scheduledTimerWithTimeInterval(Update_Delay, target: self, selector: 'refresh', userInfo: nil, repeats: true)
43
+ A11y::Data[:refresh]=true
44
+ self.init
45
+ end
46
+ end
47
+
48
+ def self.browse(request=nil)
49
+ self.init unless $browser_current
50
+ self.start_refreshing
51
+ request=0 if request==:back
52
+ if request.nil?
53
+ elsif request==:top||request==:refresh
54
+ self.init
55
+ $browser_current=$browser_tree
56
+ $browser_path.clear
57
+ elsif request==0
58
+ raise "You cannot go back any further" if $browser_path.length<2
59
+ $browser_path.pop
60
+ $browser_current=$browser_path.last
61
+ self.init unless A11y::Data[:refresh]
62
+ elsif request==:scroll
63
+ raise "This view cannot scroll" unless A11y::Console.scrollable_view?($browser_current.view)
64
+ below=CGRect.new([0, $browser_current.view.size.height], $browser_current.view.size)
65
+ $browser_current.view.scrollRectToVisible(below, animated: false)
66
+ else
67
+ self.init unless $browser_tree
68
+ $browser_current=$browser_tree unless $browser_current
69
+ found=$browser_current.find(request)
70
+ if found
71
+ if found.subviews.empty?
72
+ $browser_cursor=found
73
+ return found.view.inspect_a11y
74
+ end
75
+ self.init unless A11y::Data[:refresh]
76
+ $browser_current=found
77
+ $browser_path<<found
78
+ end
79
+ end
80
+ $browser_cursor=$browser_current
81
+ self.display_views
82
+ nil
83
+ end
84
+
85
+ def self.refresh
86
+ self.init
87
+ $before=$browser_tree.copy unless $before
88
+ unless $browser_tree==$before
89
+ puts "The screen has changed."
90
+ self.browse :top
91
+ puts "(Main)> "
92
+ end
93
+ $before=$browser_tree.copy
94
+ end
95
+
96
+ def self.view(request=nil)
97
+ self.init unless A11y::Data[:refresh]
98
+ $browser_current=$browser_tree unless $browser_current
99
+ $browser_cursor=$browser_tree unless $browser_cursor
100
+ return $browser_cursor.view unless request
101
+ result=$browser_current.find(request)
102
+ raise "Unknown view" unless result
103
+ $browser_cursor=result
104
+ result.view
105
+ end
106
+
107
+ alias :b :browse
108
+ alias :v :view
109
+
110
+ end
111
+ end
@@ -0,0 +1,104 @@
1
+ module Accessibility
2
+ module Console
3
+
4
+ def self.touch(view, arg=nil, options={})
5
+ self.start_refreshing
6
+ $browser_current=$browser_tree unless $browser_current
7
+ unless RUBYMOTION_ENV=='test'
8
+ found=$browser_current.find(view)
9
+ raise "Could not find the view" unless found
10
+ view=found.view
11
+ end
12
+ control=A11y::Console.touchable_type(view)
13
+ raise "I don't know how to touch a #{view.class}" if control.nil?
14
+ if found
15
+ sv=options[:superview]||found.superview.view
16
+ else
17
+ sv=options[:superview]
18
+ end
19
+ case control.to_s
20
+ when "UIButton"
21
+ arg||=UIControlEventTouchUpInside
22
+ view.sendActionsForControlEvents(arg)
23
+ when "UITabBarButton"
24
+ arg||=UIControlEventTouchUpInside
25
+ view.sendActionsForControlEvents(arg)
26
+ when "UITextField"
27
+ view.text=arg
28
+ when "UIPickerView"
29
+ self.touch_pickerview(view, arg)
30
+ when "UIDatePicker"
31
+ view.date=arg
32
+ when "UISegmentedControl"
33
+ self.touch_segmented(view, arg)
34
+ when "UISlider"
35
+ view.value=arg
36
+ when "UIStepper"
37
+ view.value=arg
38
+ when "UISwitch"
39
+ arg||=!view.arg
40
+ view.on=arg
41
+ when "UITableViewCell"
42
+ raise "Could not get the UITableView" unless sv.kind_of?(UITableView)
43
+ index=options[:index]||sv.indexPathForCell(view)
44
+ raise "Could not get the index" unless index
45
+ sv.delegate.tableView(self, didSelectRowAtIndexPath: index)
46
+ when "UINavigationItemButtonView"
47
+ view.superview.delegate.popViewControllerAnimated(true)
48
+ else
49
+ raise "I don't know what to do with a #{control}"
50
+ end
51
+ self.browse unless RUBYMOTION_ENV=='test'
52
+ end
53
+
54
+ def self.touch_pickerview(view, arg)
55
+ raise "You must pass a hash with the row and component keywords" unless arg.kind_of?(Hash)&&arg[:row]&&arg[:component]
56
+ arg[:animated]||=false
57
+ if arg[:row].kind_of?(String)
58
+ results=[]
59
+ view.numberOfRowsInComponent(arg[:component]).times do |row_index|
60
+ title=view.delegate.pickerView(view, titleForRow: row_index, forComponent: arg[:component])
61
+ if title.casecmp(arg[:row])==0
62
+ results=[row_index]
63
+ break
64
+ end
65
+ if title=~Regexp.new(arg[:row],true)
66
+ results<<row_index
67
+ end
68
+ end
69
+ raise "Unknown value" if results.empty?
70
+ raise "That could refer to more than one value." if results.length>1
71
+ view.selectRow(results.first, inComponent: arg[:component], animated: false)
72
+ elsif arg[:row].kind_of?(Fixnum)
73
+ view.selectRow(arg[:row], inComponent: arg[:component], animated: arg[:animated])
74
+ else
75
+ raise "Unknown row #{arg[:row]}"
76
+ end
77
+ end
78
+
79
+ def self.touch_segmented(view, arg)
80
+ if arg.kind_of?(Fixnum)
81
+ view.selectedSegmentIndex=arg
82
+ elsif arg.kind_of?(String)
83
+ results=[]
84
+ view.numberOfSegments.times do |index|
85
+ title=view.titleForSegmentAtIndex(index)
86
+ if title.casecmp(arg)==0
87
+ results=[index]
88
+ break
89
+ end
90
+ if title=~Regexp.new(arg,true)
91
+ results<<index
92
+ end
93
+ end
94
+ raise "Unknown segment" if results.empty?
95
+ raise "That could refer to more than one segment" if results.length>1
96
+ view.selectedSegmentIndex=results.first
97
+ else
98
+ raise "Invalid segment"
99
+ end
100
+ end
101
+
102
+
103
+ end
104
+ end
@@ -0,0 +1,149 @@
1
+ module Accessibility
2
+ module Console
3
+ class Tree
4
+
5
+ attr_accessor :view, :subviews, :superview
6
+
7
+ def initialize(options={})
8
+ @view=options[:view]
9
+ @subviews=options[:subviews]||[]
10
+ @superview=options[:superview]
11
+ end
12
+
13
+ def copy
14
+ other=A11y::Console::Tree.new
15
+ other.superview=self.superview if superview
16
+ other.view=self.view if view
17
+ self.subviews.each {|subview| other.subviews<<subview.clone}
18
+ other
19
+ end
20
+
21
+ def ==(other)
22
+ return false unless self.superview.view==other.superview.view
23
+ return false unless self.view==other.view
24
+ return false unless self.subviews.size==other.subviews.size
25
+ self.subviews.each_index {|index| return false unless self.subviews[index]==other.subviews[index]}
26
+ return true
27
+ end
28
+
29
+ def browsable_nodes
30
+ nodes=[@superview]
31
+ if @subviews
32
+ if A11y::Reverse_Views.member?(@view.class.to_s)
33
+ nodes+=@subviews.reverse
34
+ else
35
+ nodes+=@subviews
36
+ end
37
+ end
38
+ nodes
39
+ end
40
+
41
+ def inspect
42
+ nodes=@subviews.collect {|tree| tree.inspect}
43
+ return @view.class.to_s if @subviews.empty?
44
+ result="[#{@view.class}"
45
+ result+=" #{nodes.join(" ")}" unless nodes.empty?
46
+ result+="]"
47
+ result
48
+ end
49
+
50
+ def display_view(index=nil)
51
+ display=Array.new
52
+ control=@view.class.to_s
53
+ control=nil if A11y::View_Names[control]
54
+ control="Superview #{control}" if index==0
55
+ if @view.class==UITableViewCell
56
+ label=@view.subviews.first
57
+ while label&&!label.kind_of?(UILabel)
58
+ label=label.subviews.first
59
+ end
60
+ raise "Could not find the UITableViewCell's label" unless label.kind_of?(UILabel)
61
+ name=label.text
62
+ elsif @view.class==UITextField
63
+ name=@view.text
64
+ else
65
+ name=A11y::View_Names[@view.class.to_s]||@view.accessibility_value||@view.accessibility_label if @view.accessibility_element?||view.superclass==UIControl
66
+ end
67
+ display<<index.to_s
68
+ display<<"Touchable" if A11y::Console.touchable_type(@view)
69
+ display<<control if control
70
+ display<<name if name
71
+ if index
72
+ if index>0 and not(@subviews.empty?)
73
+ indicator="with #{@subviews.length} subview"
74
+ indicator+="s" if @subviews.length>1
75
+ end
76
+ display<<indicator
77
+ end
78
+ display.join(" ")
79
+ end
80
+
81
+ def self.accessible_view?(view)
82
+ return view.accessibility_element?||view.accessibility_label||view.accessibility_value||view.accessibility_traits
83
+ end
84
+
85
+ def self.ignore_view?(view)
86
+ return true if view.subviews.empty?&&!self.accessible_view?(view)
87
+ if view.superview
88
+ sv=view.superview
89
+ while sv&&self.ignore_view?(sv)
90
+ sv=sv.superview
91
+ end
92
+ return true if A11y::Console.touchable_type(sv)
93
+ return true if view.class==UIImageView&&A11y::Ignored_ImageViews.member?(sv.class.to_s)
94
+ end
95
+ class_name=view.class.to_s
96
+ return true if class_name=~/^_/
97
+ A11y::Ignored_Views.member?(class_name)
98
+ end
99
+
100
+ def self.build(view=nil, superview=nil)
101
+ tree=self.new
102
+ view=UIApplication.sharedApplication.keyWindow if view.nil?
103
+ subviews=[]
104
+ view.subviews.each do |subview|
105
+ subview_tree=self.build(subview, tree)
106
+ if self.ignore_view?(subview)
107
+ subview_tree.subviews.each {|v| v.superview=tree}
108
+ subviews=subviews+subview_tree.subviews
109
+ else
110
+ subviews<<subview_tree
111
+ end
112
+ end
113
+ tree.view=view
114
+ tree.subviews=subviews
115
+ tree.superview=superview
116
+ tree
117
+ end
118
+
119
+ def find(request)
120
+ found=nil
121
+ if request.kind_of?(Fixnum)
122
+ raise "Invalid number" unless request>=0&&request<browsable_nodes.length
123
+ found=browsable_nodes[request]
124
+ elsif request.kind_of?(String)
125
+ results=[]
126
+ browsable_nodes.each do |node|
127
+ next if node.nil?
128
+ next unless node.view.accessibility_label
129
+ pattern=Regexp.new(request,true)
130
+ compare=node.view.accessibility_label=~pattern
131
+ next if compare.nil?
132
+ if node.view.accessibility_label.downcase==request.downcase
133
+ return node
134
+ else
135
+ results<<node
136
+ end
137
+ end
138
+ raise "\"#{request}\" could refer to more than one view." if results.length>1
139
+ found=results.first
140
+ else
141
+ raise "Unknown request: #{request}: #{request.class}" unless request.respond_to?(:superview)
142
+ found=request
143
+ end
144
+ found
145
+ end
146
+
147
+ end
148
+ end
149
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion-accessibility
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: '2.0'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Austin Seraphin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-09 00:00:00.000000000 Z
11
+ date: 2013-09-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -32,10 +32,15 @@ extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
34
  - README.md
35
+ - lib/motion-accessibility-console/object.rb
36
+ - lib/motion-accessibility-console.rb
35
37
  - lib/motion-accessibility.rb
36
38
  - lib/project/constants.rb
37
39
  - lib/project/element.rb
38
40
  - lib/project/inspector.rb
41
+ - lib/project/motion-accessibility-console/browser.rb
42
+ - lib/project/motion-accessibility-console/touch.rb
43
+ - lib/project/motion-accessibility-console/tree.rb
39
44
  - lib/project/object.rb
40
45
  - lib/project/picker.rb
41
46
  - lib/project/status.rb
@@ -59,7 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
59
64
  version: '0'
60
65
  requirements: []
61
66
  rubyforge_project:
62
- rubygems_version: 2.0.3
67
+ rubygems_version: 2.0.6
63
68
  signing_key:
64
69
  specification_version: 4
65
70
  summary: This gem provides easy ruby-like wrappers around the protocols which interact