motion-accessibility 1.1.1 → 2.0

Sign up to get free protection for your applications and to get access to all the features.
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