bubble-wrap 1.2.0 → 1.3.0.osx
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 +8 -8
- data/CHANGELOG.md +4 -2
- data/Gemfile.lock +1 -1
- data/README.md +217 -7
- data/Rakefile +23 -2
- data/lib/bubble-wrap/camera.rb +10 -6
- data/lib/bubble-wrap/core.rb +14 -1
- data/lib/bubble-wrap/ext/motion_project_app.rb +8 -0
- data/lib/bubble-wrap/font.rb +3 -1
- data/lib/bubble-wrap/http.rb +2 -0
- data/lib/bubble-wrap/loader.rb +17 -2
- data/lib/bubble-wrap/location.rb +9 -6
- data/lib/bubble-wrap/media.rb +10 -6
- data/lib/bubble-wrap/test.rb +6 -1
- data/lib/bubble-wrap/ui.rb +5 -2
- data/lib/bubble-wrap/version.rb +2 -7
- data/motion/core.rb +6 -1
- data/motion/core/app.rb +3 -64
- data/motion/core/device.rb +0 -55
- data/motion/core/device/{camera.rb → ios/camera.rb} +0 -0
- data/motion/core/device/{camera_wrapper.rb → ios/camera_wrapper.rb} +0 -0
- data/motion/core/device/ios/screen.rb +75 -0
- data/motion/core/device/osx/screen.rb +18 -0
- data/motion/core/device/screen.rb +1 -69
- data/motion/core/ios/app.rb +71 -0
- data/motion/core/ios/device.rb +59 -0
- data/motion/core/osx/app.rb +15 -0
- data/motion/core/osx/device.rb +6 -0
- data/motion/core/string.rb +3 -2
- data/motion/http.rb +0 -364
- data/motion/http/query.rb +367 -0
- data/motion/http/response.rb +32 -0
- data/motion/test_suite_delegate.rb +58 -0
- data/motion/ui/ui_alert_view.rb +169 -0
- data/motion/ui/ui_bar_button_item.rb +55 -53
- data/motion/util/constants.rb +34 -32
- data/samples/alert/.gitignore +16 -0
- data/samples/alert/Gemfile +3 -0
- data/samples/alert/Rakefile +10 -0
- data/samples/alert/app/app_delegate.rb +8 -0
- data/samples/alert/app/controllers/alert_view_controller.rb +74 -0
- data/samples/alert/resources/Default-568h@2x.png +0 -0
- data/samples/alert/spec/main_spec.rb +9 -0
- data/samples/media/.gitignore +16 -0
- data/samples/media/Rakefile +11 -0
- data/samples/media/app/app_delegate.rb +8 -0
- data/samples/media/app/controllers/play_controller.rb +46 -0
- data/samples/media/resources/Default-568h@2x.png +0 -0
- data/samples/media/resources/test.mp3 +0 -0
- data/samples/media/spec/main_spec.rb +9 -0
- data/samples/osx/Gemfile +3 -0
- data/samples/osx/Gemfile.lock +10 -0
- data/samples/osx/Rakefile +11 -0
- data/samples/osx/app/app_delegate.rb +69 -0
- data/samples/osx/app/menu.rb +108 -0
- data/samples/osx/resources/Credits.rtf +29 -0
- data/samples/osx/spec/main_spec.rb +9 -0
- data/spec/motion/core/app_spec.rb +5 -164
- data/spec/motion/core/device/{camera_spec.rb → ios/camera_spec.rb} +0 -0
- data/spec/motion/core/device/{camera_wrapper_spec.rb → ios/camera_wrapper_spec.rb} +0 -0
- data/spec/motion/core/device/ios/device_spec.rb +74 -0
- data/spec/motion/core/device/{screen_spec.rb → ios/screen_spec.rb} +2 -1
- data/spec/motion/core/device/osx/screen_spec.rb +26 -0
- data/spec/motion/core/device_spec.rb +0 -71
- data/spec/motion/core/ios/app_spec.rb +180 -0
- data/spec/motion/core/kvo_spec.rb +23 -7
- data/spec/motion/core/ns_index_path_spec.rb +10 -2
- data/spec/motion/core/osx/app_spec.rb +15 -0
- data/spec/motion/core/string_spec.rb +11 -5
- data/spec/motion/core_spec.rb +13 -2
- data/spec/motion/http/query_spec.rb +731 -0
- data/spec/motion/http/response_spec.rb +44 -0
- data/spec/motion/http_spec.rb +0 -722
- data/spec/motion/{core → ui}/gestures_spec.rb +0 -0
- data/spec/motion/ui/ui_alert_view_spec.rb +1188 -0
- data/spec/motion/{core → ui}/ui_bar_button_item_spec.rb +80 -24
- data/spec/motion/{core → ui}/ui_control_spec.rb +0 -0
- data/spec/motion/util/constants_spec.rb +4 -4
- metadata +86 -26
@@ -0,0 +1,367 @@
|
|
1
|
+
# Class wrapping NSConnection and often used indirectly by the BubbleWrap::HTTP module methods.
|
2
|
+
class BubbleWrap::HTTP::Query
|
3
|
+
attr_accessor :request
|
4
|
+
attr_accessor :connection
|
5
|
+
attr_accessor :credentials # username & password has a hash
|
6
|
+
attr_accessor :proxy_credential # credential supplied to proxy servers
|
7
|
+
attr_accessor :post_data
|
8
|
+
attr_reader :method
|
9
|
+
|
10
|
+
attr_reader :response
|
11
|
+
attr_reader :status_code
|
12
|
+
attr_reader :response_headers
|
13
|
+
attr_reader :response_size
|
14
|
+
attr_reader :options
|
15
|
+
CLRF = "\r\n"
|
16
|
+
# ==== Parameters
|
17
|
+
# url<String>:: url of the resource to download
|
18
|
+
# http_method<Symbol>:: Value representing the HTTP method to use
|
19
|
+
# options<Hash>:: optional options used for the query
|
20
|
+
#
|
21
|
+
# ==== Options
|
22
|
+
# :payload<String> - data to pass to a POST, PUT, DELETE query.
|
23
|
+
# :delegator - Proc, class or object to call when the file is downloaded.
|
24
|
+
# a proc will receive a Response object while the passed object
|
25
|
+
# will receive the handle_query_response method
|
26
|
+
# :headers<Hash> - headers send with the request
|
27
|
+
# :cookies<Boolean> - Set whether cookies should be sent with request or not (Default: true)
|
28
|
+
# Anything else will be available via the options attribute reader.
|
29
|
+
#
|
30
|
+
def initialize(url_string, http_method = :get, options={})
|
31
|
+
@method = http_method.upcase.to_s
|
32
|
+
@delegator = options.delete(:action) || self
|
33
|
+
@payload = options.delete(:payload)
|
34
|
+
@files = options.delete(:files)
|
35
|
+
@boundary = options.delete(:boundary) || BW.create_uuid
|
36
|
+
@credentials = options.delete(:credentials) || {}
|
37
|
+
@credentials = {:username => nil, :password => nil}.merge(@credentials)
|
38
|
+
@timeout = options.delete(:timeout) || 30.0
|
39
|
+
@headers = escape_line_feeds(options.delete :headers)
|
40
|
+
@format = options.delete(:format)
|
41
|
+
@cache_policy = options.delete(:cache_policy) || NSURLRequestUseProtocolCachePolicy
|
42
|
+
@credential_persistence = options.delete(:credential_persistence) || NSURLCredentialPersistenceForSession
|
43
|
+
@cookies = options.key?(:cookies) ? options.delete(:cookies) : true
|
44
|
+
@options = options
|
45
|
+
@response = BubbleWrap::HTTP::Response.new
|
46
|
+
@follow_urls = options[:follow_urls] || true
|
47
|
+
@present_credentials = options[:present_credentials] == nil ? true : options.delete(:present_credentials)
|
48
|
+
|
49
|
+
@url = create_url(url_string)
|
50
|
+
@body = create_request_body
|
51
|
+
@request = create_request
|
52
|
+
@original_url = @url.copy
|
53
|
+
|
54
|
+
@connection = create_connection(request, self)
|
55
|
+
@connection.start
|
56
|
+
|
57
|
+
show_status_indicator true
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
"#<#{self.class}:#{self.object_id} - Method: #{@method}, url: #{@url.description}, body: #{@body.description}, Payload: #{@payload}, Headers: #{@headers} Credentials: #{@credentials}, Timeout: #{@timeout}, \
|
62
|
+
Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
63
|
+
end
|
64
|
+
alias description to_s
|
65
|
+
|
66
|
+
def connection(connection, didReceiveResponse:response)
|
67
|
+
# On OSX, if using an FTP connection, this method will fire *immediately* after creating an
|
68
|
+
# NSURLConnection, even if the connection has not yet started. The `response`
|
69
|
+
# object will be a NSURLResponse, *not* an `NSHTTPURLResponse`, and so will start to crash.
|
70
|
+
if App.osx? && !response.is_a?(NSHTTPURLResponse)
|
71
|
+
return
|
72
|
+
end
|
73
|
+
@status_code = response.statusCode
|
74
|
+
@response_headers = response.allHeaderFields
|
75
|
+
@response_size = response.expectedContentLength.to_f
|
76
|
+
end
|
77
|
+
|
78
|
+
# This delegate method get called every time a chunk of data is being received
|
79
|
+
def connection(connection, didReceiveData:received_data)
|
80
|
+
@received_data ||= NSMutableData.new
|
81
|
+
@received_data.appendData(received_data)
|
82
|
+
|
83
|
+
if download_progress = options[:download_progress]
|
84
|
+
download_progress.call(@received_data.length.to_f, response_size)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def connection(connection, willSendRequest:request, redirectResponse:redirect_response)
|
89
|
+
# abort early if the user has explicitly disabled redirects
|
90
|
+
if @options[:no_redirect] and redirect_response then
|
91
|
+
return nil
|
92
|
+
end
|
93
|
+
@redirect_count ||= 0
|
94
|
+
@redirect_count += 1
|
95
|
+
log "##{@redirect_count} HTTP redirect_count: #{request.inspect} - #{self.description}"
|
96
|
+
|
97
|
+
if @redirect_count >= 30
|
98
|
+
@response.error_message = "Too many redirections"
|
99
|
+
@request.done_loading!
|
100
|
+
call_delegator_with_response
|
101
|
+
nil
|
102
|
+
else
|
103
|
+
@url = request.URL if @follow_urls
|
104
|
+
request
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def connection(connection, didFailWithError: error)
|
109
|
+
log "HTTP Connection to #{@url.absoluteString} failed #{error.localizedDescription}"
|
110
|
+
show_status_indicator false
|
111
|
+
@request.done_loading!
|
112
|
+
@response.error_message = error.localizedDescription
|
113
|
+
call_delegator_with_response
|
114
|
+
end
|
115
|
+
|
116
|
+
def connection(connection, didSendBodyData:sending, totalBytesWritten:written, totalBytesExpectedToWrite:expected)
|
117
|
+
if upload_progress = options[:upload_progress]
|
118
|
+
upload_progress.call(sending, written, expected)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def connectionDidFinishLoading(connection)
|
123
|
+
show_status_indicator false
|
124
|
+
@request.done_loading!
|
125
|
+
response_body = NSData.dataWithData(@received_data) if @received_data
|
126
|
+
@response.update(status_code: status_code, body: response_body, headers: response_headers, url: @url, original_url: @original_url)
|
127
|
+
|
128
|
+
call_delegator_with_response
|
129
|
+
end
|
130
|
+
|
131
|
+
def connection(connection, didReceiveAuthenticationChallenge:challenge)
|
132
|
+
if (challenge.previousFailureCount == 0)
|
133
|
+
if credentials[:username].to_s.empty? && credentials[:password].to_s.empty?
|
134
|
+
challenge.sender.continueWithoutCredentialForAuthenticationChallenge(challenge)
|
135
|
+
log 'Continue without credentials to get 401 status in response'
|
136
|
+
else
|
137
|
+
new_credential = NSURLCredential.credentialWithUser(credentials[:username], password:credentials[:password], persistence:@credential_persistence)
|
138
|
+
challenge.sender.useCredential(new_credential, forAuthenticationChallenge:challenge)
|
139
|
+
log "auth challenged, answered with credentials: #{credentials.inspect}"
|
140
|
+
end
|
141
|
+
else
|
142
|
+
challenge.sender.cancelAuthenticationChallenge(challenge)
|
143
|
+
log 'Auth Failed :('
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
private
|
151
|
+
def show_status_indicator(show)
|
152
|
+
if !App.osx?
|
153
|
+
UIApplication.sharedApplication.networkActivityIndicatorVisible = show
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def create_request
|
158
|
+
log "BubbleWrap::HTTP building a NSRequest for #{@url.description}"
|
159
|
+
|
160
|
+
request = NSMutableURLRequest.requestWithURL(@url,
|
161
|
+
cachePolicy:@cache_policy,
|
162
|
+
timeoutInterval:@timeout)
|
163
|
+
request.setHTTPMethod(@method)
|
164
|
+
set_content_type
|
165
|
+
append_auth_header
|
166
|
+
request.setAllHTTPHeaderFields(@headers)
|
167
|
+
request.setHTTPBody(@body)
|
168
|
+
request.setHTTPShouldHandleCookies(@cookies)
|
169
|
+
patch_nsurl_request(request)
|
170
|
+
|
171
|
+
request
|
172
|
+
end
|
173
|
+
|
174
|
+
def set_content_type
|
175
|
+
return if headers_provided?
|
176
|
+
return if (@method == "GET" || @method == "HEAD")
|
177
|
+
@headers ||= {}
|
178
|
+
@headers["Content-Type"] = case @format
|
179
|
+
when :json
|
180
|
+
"application/json"
|
181
|
+
when :xml
|
182
|
+
"application/xml"
|
183
|
+
when :text
|
184
|
+
"text/plain"
|
185
|
+
else
|
186
|
+
if @format == :form_data || @payload_or_files_were_appended
|
187
|
+
"multipart/form-data; boundary=#{@boundary}"
|
188
|
+
else
|
189
|
+
"application/x-www-form-urlencoded"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def headers_provided?
|
195
|
+
@headers && @headers.keys.find {|k| k.downcase == 'content-type'}
|
196
|
+
end
|
197
|
+
|
198
|
+
def credentials_provided?
|
199
|
+
@credentials[:username] && @credentials[:password]
|
200
|
+
end
|
201
|
+
|
202
|
+
def create_request_body
|
203
|
+
return nil if (@method == "GET" || @method == "HEAD")
|
204
|
+
return nil unless (@payload || @files)
|
205
|
+
|
206
|
+
body = NSMutableData.data
|
207
|
+
|
208
|
+
append_payload(body) if @payload
|
209
|
+
append_files(body) if @files
|
210
|
+
append_body_boundary(body) if @payload_or_files_were_appended
|
211
|
+
|
212
|
+
log "Built HTTP body: \n #{body.to_str}"
|
213
|
+
body
|
214
|
+
end
|
215
|
+
|
216
|
+
def append_payload(body)
|
217
|
+
if @payload.is_a?(NSData)
|
218
|
+
body.appendData(@payload)
|
219
|
+
elsif @payload.is_a?(String)
|
220
|
+
body.appendData(@payload.dataUsingEncoding NSUTF8StringEncoding)
|
221
|
+
else
|
222
|
+
append_form_params(body)
|
223
|
+
end
|
224
|
+
body
|
225
|
+
end
|
226
|
+
|
227
|
+
def append_form_params(body)
|
228
|
+
list = process_payload_hash(@payload)
|
229
|
+
list.each do |key, value|
|
230
|
+
s = "--#{@boundary}\r\n"
|
231
|
+
s += "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n"
|
232
|
+
s += value.to_s
|
233
|
+
s += "\r\n"
|
234
|
+
body.appendData(s.dataUsingEncoding NSUTF8StringEncoding)
|
235
|
+
end
|
236
|
+
@payload_or_files_were_appended = true
|
237
|
+
body
|
238
|
+
end
|
239
|
+
|
240
|
+
def append_auth_header
|
241
|
+
return if @headers && @headers["Authorization"]
|
242
|
+
|
243
|
+
if credentials_provided? && @present_credentials
|
244
|
+
mock_request = CFHTTPMessageCreateRequest(nil, nil, nil, nil)
|
245
|
+
CFHTTPMessageAddAuthentication(mock_request, nil, @credentials[:username], @credentials[:password], KCFHTTPAuthenticationSchemeBasic, false)
|
246
|
+
|
247
|
+
@headers ||= {}
|
248
|
+
@headers["Authorization"] = CFHTTPMessageCopyHeaderFieldValue(mock_request, "Authorization")
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def parse_file(key, value)
|
253
|
+
if value.is_a?(Hash)
|
254
|
+
raise InvalidFileError if value[:data].nil?
|
255
|
+
{data: value[:data], filename: value[:filename] || key}
|
256
|
+
else
|
257
|
+
{data: value, filename: key}
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def append_files(body)
|
262
|
+
@files.each do |key, value|
|
263
|
+
file = parse_file(key, value)
|
264
|
+
s = "--#{@boundary}\r\n"
|
265
|
+
s += "Content-Disposition: form-data; name=\"#{key}\"; filename=\"#{file[:filename]}\"\r\n"
|
266
|
+
s += "Content-Type: application/octet-stream\r\n\r\n"
|
267
|
+
file_data = NSMutableData.new
|
268
|
+
file_data.appendData(s.dataUsingEncoding NSUTF8StringEncoding)
|
269
|
+
file_data.appendData(file[:data])
|
270
|
+
file_data.appendData("\r\n".dataUsingEncoding NSUTF8StringEncoding)
|
271
|
+
body.appendData(file_data)
|
272
|
+
end
|
273
|
+
@payload_or_files_were_appended = true
|
274
|
+
body
|
275
|
+
end
|
276
|
+
|
277
|
+
def append_body_boundary(body)
|
278
|
+
body.appendData("--#{@boundary}--\r\n".dataUsingEncoding NSUTF8StringEncoding)
|
279
|
+
end
|
280
|
+
|
281
|
+
def create_url(url_string)
|
282
|
+
url_string = url_string.stringByAddingPercentEscapesUsingEncoding NSUTF8StringEncoding
|
283
|
+
if (@method == "GET" || @method == "HEAD") && @payload
|
284
|
+
unless @payload.empty?
|
285
|
+
convert_payload_to_url if @payload.is_a?(Hash)
|
286
|
+
url_string += "?#{@payload}"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
url = NSURL.URLWithString(url_string)
|
290
|
+
|
291
|
+
validate_url(url)
|
292
|
+
url
|
293
|
+
end
|
294
|
+
|
295
|
+
def validate_url(url)
|
296
|
+
if !NSURLConnection.canHandleRequest(NSURLRequest.requestWithURL(url))
|
297
|
+
raise InvalidURLError, "Invalid URL provided (Make sure you include a valid URL scheme, e.g. http:// or similar)."
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def escape(string)
|
302
|
+
if string
|
303
|
+
CFURLCreateStringByAddingPercentEscapes nil, string.to_s, nil, "!*'();:@&=+$,/?%#[]", KCFStringEncodingUTF8
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def convert_payload_to_url
|
308
|
+
params_array = process_payload_hash(@payload)
|
309
|
+
params_array.map! { |key, value| "#{escape key}=#{escape value}" }
|
310
|
+
@payload = params_array.join("&")
|
311
|
+
end
|
312
|
+
|
313
|
+
def process_payload_hash(payload, prefix=nil)
|
314
|
+
list = []
|
315
|
+
payload.each do |k,v|
|
316
|
+
if v.is_a?(Hash)
|
317
|
+
new_prefix = prefix ? "#{prefix}[#{k.to_s}]" : k.to_s
|
318
|
+
param = process_payload_hash(v, new_prefix)
|
319
|
+
list += param
|
320
|
+
elsif v.is_a?(Array)
|
321
|
+
v.each do |val|
|
322
|
+
param = prefix ? "#{prefix}[#{k.to_s}][]" : "#{k.to_s}[]"
|
323
|
+
if val.is_a?(Hash)
|
324
|
+
list += process_payload_hash(val, param)
|
325
|
+
else
|
326
|
+
list << [param, val]
|
327
|
+
end
|
328
|
+
end
|
329
|
+
else
|
330
|
+
param = prefix ? "#{prefix}[#{k.to_s}]" : k.to_s
|
331
|
+
list << [param, v]
|
332
|
+
end
|
333
|
+
end
|
334
|
+
list
|
335
|
+
end
|
336
|
+
|
337
|
+
def log(message)
|
338
|
+
NSLog message if BubbleWrap.debug?
|
339
|
+
end
|
340
|
+
|
341
|
+
def escape_line_feeds(hash)
|
342
|
+
return nil if hash.nil?
|
343
|
+
escaped_hash = {}
|
344
|
+
|
345
|
+
hash.each{|k,v| escaped_hash[k] = v.gsub("\n", CLRF) if v }
|
346
|
+
escaped_hash
|
347
|
+
end
|
348
|
+
|
349
|
+
def patch_nsurl_request(request)
|
350
|
+
request.instance_variable_set("@done_loading", false)
|
351
|
+
|
352
|
+
def request.done_loading?; @done_loading; end
|
353
|
+
def request.done_loading!; @done_loading = true; end
|
354
|
+
end
|
355
|
+
|
356
|
+
def call_delegator_with_response
|
357
|
+
if @delegator.respond_to?(:call)
|
358
|
+
@delegator.call( @response, self )
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# This is a temporary method used for mocking.
|
363
|
+
def create_connection(request, delegate)
|
364
|
+
NSURLConnection.connectionWithRequest(request, delegate:delegate)
|
365
|
+
end
|
366
|
+
|
367
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Response class wrapping the results of a Query's response
|
2
|
+
class BubbleWrap::HTTP::Response
|
3
|
+
attr_reader :body
|
4
|
+
attr_reader :headers
|
5
|
+
attr_accessor :status_code, :status_description, :error_message
|
6
|
+
attr_reader :url
|
7
|
+
attr_reader :original_url
|
8
|
+
|
9
|
+
def initialize(values={})
|
10
|
+
self.update(values)
|
11
|
+
end
|
12
|
+
|
13
|
+
def update(values)
|
14
|
+
values.each do |k,v|
|
15
|
+
self.instance_variable_set("@#{k}", v)
|
16
|
+
end
|
17
|
+
update_status_description
|
18
|
+
end
|
19
|
+
|
20
|
+
def ok?
|
21
|
+
status_code.to_s =~ /20\d/ ? true : false
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"#<#{self.class}:#{self.object_id} - url: #{self.url}, body: #{self.body}, headers: #{self.headers}, status code: #{self.status_code}, error message: #{self.error_message} >"
|
26
|
+
end
|
27
|
+
alias description to_s
|
28
|
+
|
29
|
+
def update_status_description
|
30
|
+
@status_description = status_code.nil? ? nil : NSHTTPURLResponse.localizedStringForStatusCode(status_code)
|
31
|
+
end
|
32
|
+
end
|
@@ -8,3 +8,61 @@ class TestSuiteDelegate
|
|
8
8
|
true
|
9
9
|
end
|
10
10
|
end
|
11
|
+
|
12
|
+
class TestSuiteOSXDelegate
|
13
|
+
def applicationDidFinishLaunching(notification)
|
14
|
+
buildMenu
|
15
|
+
buildWindow
|
16
|
+
end
|
17
|
+
|
18
|
+
def buildWindow
|
19
|
+
@mainWindow = NSWindow.alloc.initWithContentRect([[240, 180], [480, 360]],
|
20
|
+
styleMask: NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask,
|
21
|
+
backing: NSBackingStoreBuffered,
|
22
|
+
defer: false)
|
23
|
+
@mainWindow.title = "BubbleWrap Tests"
|
24
|
+
end
|
25
|
+
|
26
|
+
def buildMenu
|
27
|
+
@mainMenu = NSMenu.new
|
28
|
+
|
29
|
+
appName = "BubbleWrap Tests"
|
30
|
+
addMenu(appName) do
|
31
|
+
addItemWithTitle("About #{appName}", action: 'orderFrontStandardAboutPanel:', keyEquivalent: '')
|
32
|
+
addItem(NSMenuItem.separatorItem)
|
33
|
+
addItemWithTitle('Preferences', action: 'openPreferences:', keyEquivalent: ',')
|
34
|
+
addItem(NSMenuItem.separatorItem)
|
35
|
+
servicesItem = addItemWithTitle('Services', action: nil, keyEquivalent: '')
|
36
|
+
NSApp.servicesMenu = servicesItem.submenu = NSMenu.new
|
37
|
+
addItem(NSMenuItem.separatorItem)
|
38
|
+
addItemWithTitle("Hide #{appName}", action: 'hide:', keyEquivalent: 'h')
|
39
|
+
item = addItemWithTitle('Hide Others', action: 'hideOtherApplications:', keyEquivalent: 'H')
|
40
|
+
item.keyEquivalentModifierMask = NSCommandKeyMask|NSAlternateKeyMask
|
41
|
+
addItemWithTitle('Show All', action: 'unhideAllApplications:', keyEquivalent: '')
|
42
|
+
addItem(NSMenuItem.separatorItem)
|
43
|
+
addItemWithTitle("Quit #{appName}", action: 'terminate:', keyEquivalent: 'q')
|
44
|
+
end
|
45
|
+
|
46
|
+
NSApp.helpMenu = addMenu('Help') do
|
47
|
+
addItemWithTitle("#{appName} Help", action: 'showHelp:', keyEquivalent: '?')
|
48
|
+
end.menu
|
49
|
+
|
50
|
+
NSApp.mainMenu = @mainMenu
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def addMenu(title, &b)
|
56
|
+
item = createMenu(title, &b)
|
57
|
+
@mainMenu.addItem item
|
58
|
+
item
|
59
|
+
end
|
60
|
+
|
61
|
+
def createMenu(title, &b)
|
62
|
+
menu = NSMenu.alloc.initWithTitle(title)
|
63
|
+
menu.instance_eval(&b) if b
|
64
|
+
item = NSMenuItem.alloc.initWithTitle(title, action: nil, keyEquivalent: '')
|
65
|
+
item.submenu = menu
|
66
|
+
item
|
67
|
+
end
|
68
|
+
end
|