hotcocoa 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,120 +1,149 @@
1
- ##
2
- # Builds a delegate for a control at runtime by creating a generic object
3
- # and adding singleton methods for each given delegate method; it then
4
- # tells the control to delegate to that created object.
5
- class HotCocoa::DelegateBuilder
6
-
7
- # @return The object that needs a delegate
8
- attr_reader :control
9
-
10
- # @return [Array<>] Delegate methods which are assumed to be implemented
11
- # and therefore __MUST__ be given at least a stub
12
- attr_reader :required_methods
13
-
14
- # @return [Fixnum] Number of delegate methods that have been added
15
- attr_reader :method_count
16
-
17
- # @return [Object] The delegate object
18
- attr_reader :delegate
19
-
20
- # @param control the object which needs a delegate
21
- # @param [Array<String>] required_methods
22
- def initialize control, required_methods
23
- @control = control
24
- @required_methods = required_methods
25
- @method_count = 0
26
- @delegate = Object.new
27
- end
1
+ module HotCocoa
28
2
 
29
3
  ##
30
- # Add a delegated method for {#control} to {#delegate}
31
- def add_delegated_method block, selector_name, *parameters
32
- clear_delegate if required_methods.empty?
4
+ # Builds a delegate for a control at runtime by creating a generic object
5
+ # and adding singleton methods for each given delegate method; it then
6
+ # tells the control to delegate to that created object.
7
+ class DelegateBuilder
8
+
9
+ ##
10
+ # The object that needs a delegate
11
+ #
12
+ # @return
13
+ attr_reader :control
14
+
15
+ ##
16
+ # Delegate methods which are assumed to be implemented and therefore
17
+ # __MUST__ be given at least a stub
18
+ #
19
+ # @return [Array<SEL,String>]
20
+ attr_reader :required_methods
21
+
22
+ ##
23
+ # The delegate object
24
+ #
25
+ # @return [Object]
26
+ attr_reader :delegate
27
+
28
+ # @param control the object which needs a delegate
29
+ # @param [Array<SEL,String>] required_methods
30
+ def initialize control, required_methods
31
+ @control = control
32
+ @required_methods = required_methods
33
+ @delegate = Object.new
34
+ end
33
35
 
34
- @method_count += 1
35
- bind_block_to_delegate_instance_variable(block)
36
- create_delegate_method(selector_name, parameters)
36
+ ##
37
+ # Add a delegated method for {#control} to {#delegate}
38
+ def add_delegated_method block, selector_name, *parameters
39
+ clear_delegate if required_methods.empty?
37
40
 
38
- set_delegate if required_methods.empty?
39
- end
41
+ delegate_method_builder.add_delegated_method block, selector_name, *parameters
40
42
 
41
- def delegate_to object, *method_names
42
- method_names.each do |method_name|
43
- control.send(method_name, &object.method(method_name)) if object.respond_to?(method_name)
43
+ required_methods.delete(selector_name)
44
+ set_delegate if required_methods.empty?
44
45
  end
45
- end
46
46
 
47
- private
47
+ def delegate_to object, *method_names
48
+ method_names.each do |method_name|
49
+ control.send(method_name, &object.method(method_name)) if object.respond_to?(method_name)
50
+ end
51
+ end
48
52
 
49
- def bind_block_to_delegate_instance_variable block
50
- delegate.instance_variable_set(block_instance_variable, block)
51
- end
52
53
 
53
- ##
54
- # @todo GET RID OF EVAL
55
- def create_delegate_method selector_name, parameters
56
- required_methods.delete(selector_name)
57
- eval %{
58
- def delegate.#{parameterize_selector_name(selector_name)}
59
- #{block_instance_variable}.call(#{parameter_values_for_mapping(selector_name, parameters)})
60
- end
61
- }
62
- end
54
+ private
63
55
 
64
- ##
65
- # Reset the delegate for {#control} to `nil`.
66
- def clear_delegate
67
- control.setDelegate(nil) if control.delegate
68
- end
56
+ ##
57
+ # Reset the delegate for {#control} to `nil`.
58
+ def clear_delegate
59
+ control.setDelegate(nil) if control.delegate
60
+ end
69
61
 
70
- ##
71
- # Set the delegate for {#control} to {#delegate}
72
- def set_delegate
73
- control.setDelegate(delegate)
74
- end
62
+ ##
63
+ # Set the delegate for {#control} to {#delegate}
64
+ def set_delegate
65
+ control.setDelegate(delegate)
66
+ end
67
+
68
+ def delegate_method_builder
69
+ @delegate_method_builder ||= DelegateMethodBuilder.new(@delegate)
70
+ end
75
71
 
76
- ##
77
- # Returns an instance variable name to be used for the delegate method
78
- # currently being built.
79
- #
80
- # @return [String]
81
- def block_instance_variable
82
- "@block#{method_count}"
83
72
  end
84
73
 
85
- ##
86
- # Take an Objective-C selector and create a parameter list to be used
87
- # in creating method's using #eval
88
- #
89
- # @example
90
- # parameterize_selector_name('myDelegateMethod') # => 'myDeletageMethod'
91
- # parameterize_selector_name('myDelegateMethod:withArgument:') # => 'myDeletageMethod p1, withArgumnet:p2'
92
- #
93
- # @param [String] selector_name
94
- # @return [String]
95
- def parameterize_selector_name selector_name
96
- return selector_name unless selector_name.include?(':')
97
-
98
- params = selector_name.split(':')
99
- result = "#{params.shift} p1"
100
- params.each_with_index do |param, i|
101
- result << ",#{param}:p#{i + 2}"
74
+
75
+ class DelegateMethodBuilder
76
+
77
+ def initialize target
78
+ @target = target
102
79
  end
103
- result
104
- end
105
80
 
106
- def parameter_values_for_mapping selector_name, parameters
107
- return if parameters.empty?
81
+ def add_delegated_method block, selector_name, *parameters
82
+ bind_block_to_delegate_instance_variable(selector_name, block)
83
+ create_delegate_method(selector_name, parameters)
84
+ end
85
+
86
+ def bind_block_to_delegate_instance_variable selector_name, block
87
+ @target.instance_variable_set(block_instance_variable_for(selector_name), block)
88
+ end
108
89
 
109
- result = []
110
- selector_params = selector_name.split(':')
111
- parameters.each do |parameter|
112
- if (dot = parameter.index('.'))
113
- result << "p#{selector_params.index(parameter[0...dot]) + 1}#{parameter[dot..-1]}"
114
- else
115
- result << "p#{selector_params.index(parameter) + 1}"
90
+ ##
91
+ # @todo GET RID OF EVAL
92
+ def create_delegate_method selector_name, parameters
93
+ eval %{
94
+ def @target.#{parameterize_selector_name(selector_name)}
95
+ #{block_instance_variable_for(selector_name)}.call(#{parameter_values_for_mapping(selector_name, parameters)})
96
+ end
97
+ }
98
+ end
99
+
100
+ ##
101
+ # Returns an instance variable name to be used for the delegate method
102
+ # currently being built.
103
+ #
104
+ # @return [String]
105
+ def block_instance_variable_for selector_name
106
+ "@block_#{selector_name.gsub(':', "_")}"
107
+ end
108
+
109
+ ##
110
+ # Take an Objective-C selector and create a parameter list to be used
111
+ # in creating method's using #eval
112
+ #
113
+ # @example
114
+ # parameterize_selector_name('myDelegateMethod') # => 'myDeletageMethod'
115
+ # parameterize_selector_name('myDelegateMethod:withArgument:') # => 'myDeletageMethod p1, withArgument:p2'
116
+ #
117
+ # @param [String] selector_name
118
+ # @return [String]
119
+ def parameterize_selector_name selector_name
120
+ return selector_name unless selector_name.include?(':')
121
+
122
+ params = selector_name.split(':')
123
+ result = "#{params.shift} p1"
124
+ params.each_with_index do |param, i|
125
+ result << ",#{param}:p#{i + 2}"
126
+ end
127
+ result
128
+ end
129
+
130
+ def parameter_values_for_mapping selector_name, parameters
131
+ return if parameters.empty?
132
+
133
+ result = []
134
+ selector_params = selector_name.split(':')
135
+ parameters.each do |parameter|
136
+ if (dot = parameter.index('.'))
137
+ result << "p#{selector_params.index(parameter[0...dot]) + 1}#{parameter[dot..-1]}"
138
+ else
139
+ parameter = parameter.to_s
140
+ raise "Error in delegate mapping: '#{parameter}' is not a valid parameter of method '#{selector_name}'" if selector_params.index(parameter).nil?
141
+ result << "p#{selector_params.index(parameter) + 1}"
142
+ end
116
143
  end
144
+ result.join(', ')
117
145
  end
118
- result.join(', ')
146
+
119
147
  end
148
+
120
149
  end
@@ -203,8 +203,8 @@ module HotCocoa
203
203
  end
204
204
 
205
205
  def update_layout_views!
206
- @view.superview.views_updated! if in_layout_view?
207
- @defaults_view.views_updated! if @defaults_view
206
+ @view.superview.relayout! if in_layout_view?
207
+ @defaults_view.relayout! if @defaults_view
208
208
  end
209
209
 
210
210
  private
@@ -214,36 +214,62 @@ module HotCocoa
214
214
  end
215
215
  end
216
216
 
217
+ ##
218
+ # @todo Why aren't we mixing in {HotCocoa::Behaviors}?
219
+ #
220
+ # HotCocoa layout managing class. This class is responsible for keeping
221
+ # track of your UI layout, including adding, removing, and updating
222
+ # subviews.
217
223
  class LayoutView < NSView
218
- attr_accessor :frame_color
219
224
 
225
+ ##
226
+ # Set some default values and call the super class initializer.
227
+ #
228
+ # @param [CGRect, Array<Number, Number, Number, Number>]
220
229
  def initWithFrame frame
221
230
  super
222
- @mode = :vertical
223
- @spacing = 10.0
224
- @margin = 10.0
231
+ @mode = :vertical
232
+ @spacing = 10
233
+ @margin = 10
225
234
  self
226
235
  end
227
236
 
237
+ # @return [NSColor]
238
+ attr_accessor :frame_color
239
+
240
+ ##
241
+ # Whether or not the layout mode is vertical.
228
242
  def vertical?
229
243
  @mode == :vertical
230
244
  end
231
245
 
246
+ ##
247
+ # Whether or not the layout mode is horizontal.
232
248
  def horizontal?
233
249
  @mode == :horizonal
234
250
  end
235
251
 
236
- def mode= mode
237
- unless [:horizontal, :vertical].include?(mode)
238
- raise ArgumentError, "invalid mode value #{mode}"
252
+ ##
253
+ # Set the layout mode. The default value is `:vertical`, you can
254
+ # change it to be `:horizontal` if you want.
255
+ #
256
+ # @param [Symbol]
257
+ def mode= new_mode
258
+ unless [:horizontal, :vertical].include?(new_mode)
259
+ raise ArgumentError, "invalid mode value #{new_mode}"
239
260
  end
240
261
 
241
- if mode != @mode
242
- @mode = mode
262
+ if new_mode != @mode
263
+ @mode = new_mode
243
264
  relayout!
244
265
  end
245
266
  end
246
267
 
268
+ ##
269
+ # Set the default layout options. The options should follow the format
270
+ # that would be given to {HotCocoa::LayoutOptions}.
271
+ #
272
+ # @param [Hash]
247
273
  def default_layout= options
248
274
  options[:defaults_view] = self
249
275
  @default_layout = LayoutOptions.new(nil, options)
@@ -251,74 +277,73 @@ module HotCocoa
251
277
  end
252
278
 
253
279
  def default_layout
254
- @default_layout ||= LayoutOptions.new(nil, :defaults_view => self)
280
+ @default_layout ||= LayoutOptions.new(nil, defaults_view: self)
255
281
  end
256
282
 
257
- def spacing
258
- @spacing
259
- end
283
+ # @return [Fixnum]
284
+ attr_reader :spacing
260
285
 
261
- def spacing= spacing
262
- if spacing != @spacing
263
- @spacing = spacing.to_i
286
+ ##
287
+ # Change the spacing between subviews.
288
+ #
289
+ # @param [Number]
290
+ def spacing= new_spacing
291
+ if new_spacing != @spacing
292
+ @spacing = new_spacing.to_i
264
293
  relayout!
265
294
  end
266
295
  end
267
296
 
268
- def frame= frame
269
- setFrame(frame)
270
- end
271
-
272
- def size= size
273
- setFrameSize(size)
274
- end
275
-
276
- def margin
277
- @margin
278
- end
297
+ # @return [Fixnum]
298
+ attr_reader :margin
279
299
 
280
- def margin= margin
281
- if margin != @margin
282
- @margin = margin.to_i
300
+ ##
301
+ # Change the margin size for the view.
302
+ #
303
+ # @param [Fixnum]
304
+ def margin= new_margin
305
+ if new_margin != @margin
306
+ @margin = new_margin.to_i
283
307
  relayout!
284
308
  end
285
309
  end
286
310
 
287
- def << view
288
- addSubview(view)
289
- end
290
-
291
- def remove subview, options = {}
292
- raise ArgumentError, "#{subview} is not a subview of #{self} and cannot be removed." unless subview.superview == self
293
- options[:needs_display] == false ? subview.removeFromSuperviewWithoutNeedingDisplay : subview.removeFromSuperview
294
- end
295
-
296
- def addSubview(view)
311
+ ##
312
+ # Add a new subview to the layout view.
313
+ #
314
+ # @param [NSView]
315
+ def addSubview view
297
316
  super
298
- if view.respond_to?(:layout)
317
+ if view.respond_to? :layout
299
318
  relayout!
300
319
  else
301
320
  raise ArgumentError, "view #{view} does not support the #layout method"
302
321
  end
303
322
  end
323
+ alias_method :<<, :addSubview
304
324
 
305
- def views_updated!
306
- relayout!
307
- end
308
-
325
+ ##
326
+ # Remove a subview from the layout.
327
+ #
328
+ # @param [NSView]
309
329
  def remove_view view
310
- unless subviews.include?(view)
330
+ unless subviews.include? view
311
331
  raise ArgumentError, "view #{view} not a subview of this LayoutView"
312
332
  end
313
333
  view.removeFromSuperview
314
334
  relayout!
315
335
  end
336
+ alias_method :remove, :remove_view
316
337
 
338
+ ##
339
+ # Remove all the subviews from the layout view.
317
340
  def remove_all_views
318
341
  subviews.each { |view| view.removeFromSuperview }
319
342
  relayout!
320
343
  end
321
344
 
345
+ ##
346
+ # This is a callback, you don't need to worry about it.
322
347
  def drawRect frame
323
348
  if frame_color
324
349
  frame_color.set
@@ -326,59 +351,37 @@ module HotCocoa
326
351
  end
327
352
  end
328
353
 
354
+ ##
355
+ # This is a callback, you don't need to worry about it.
329
356
  def setFrame frame
330
357
  super(frame, &nil)
331
358
  relayout!
332
359
  end
360
+ alias_method :frame=, :setFrame
333
361
 
362
+ ##
363
+ # This is a callback, you don't need to worry about it.
334
364
  def setFrameSize size
335
365
  super(size, &nil)
336
366
  relayout!
337
367
  end
368
+ alias_method :size=, :setFrameSize
338
369
 
339
- private
340
-
341
- def calc_expandable_size end_dimension
342
- expandable_size = end_dimension
343
- expandable_views = 0
344
-
345
- subviews.each do |view|
346
- next unless can_layout?(view)
347
-
348
- if vertical?
349
- size = view.frameSize.height
350
- expand = view.layout.expand_height?
351
- padding = view.layout.top_padding + view.layout.bottom_padding
352
- else
353
- size = view.frameSize.width
354
- expand = view.layout.expand_width?
355
- padding = view.layout.left_padding + view.layout.right_padding
356
- end
357
-
358
- if expand
359
- expandable_views += 1
360
- else
361
- expandable_size -= size
362
- expandable_size -= @spacing
363
- end
364
-
365
- expandable_size -= padding
366
- end
367
-
368
- expandable_size /= expandable_views
369
- expandable_size
370
- end
371
-
370
+ ##
371
+ # @todo This method could be optimized quite a bit, I think.
372
+ #
373
+ # Figure out how to layout all the subviews. This is the meat of the
374
+ # class.
372
375
  def relayout!
373
- view_size = frameSize
374
- end_dimension = vertical? ? view_size.height : view_size.width
376
+ view_size = frameSize
377
+ end_dimension = vertical? ? view_size.height : view_size.width
375
378
  end_dimension -= (@margin * 2)
376
- dimension = @margin
379
+ dimension = @margin
377
380
 
378
381
  expandable_size = calc_expandable_size(end_dimension)
379
382
 
380
383
  subviews.each do |view|
381
- next unless can_layout?(view)
384
+ next unless can_layout? view
382
385
 
383
386
  options = view.layout
384
387
  subview_size = view.frameSize
@@ -386,27 +389,27 @@ module HotCocoa
386
389
  subview_dimension = vertical? ? subview_size.height : subview_size.width
387
390
 
388
391
  if vertical?
389
- primary_dimension = 'height'
390
- secondary_dimension = 'width'
391
- primary_axis = 'x'
392
- secondary_axis = 'y'
393
- expand_primary = 'expand_height?'
394
- expand_secondary = 'expand_width?'
395
- padding_first = 'left_padding'
396
- padding_second = 'right_padding'
397
- padding_third = 'bottom_padding'
398
- padding_fourth = 'top_padding'
392
+ primary_dimension = HEIGHT
393
+ secondary_dimension = WIDTH
394
+ primary_axis = X
395
+ secondary_axis = Y
396
+ expand_primary = EXPAND_HEIGHT
397
+ expand_secondary = EXPAND_WIDTH
398
+ padding_first = LEFT_PADDING
399
+ padding_second = RIGHT_PADDING
400
+ padding_third = BOTTOM_PADDING
401
+ padding_fourth = TOP_PADDING
399
402
  else
400
- primary_dimension = 'width'
401
- secondary_dimension = 'height'
402
- primary_axis = 'y'
403
- secondary_axis = 'x'
404
- expand_primary = 'expand_width?'
405
- expand_secondary = 'expand_height?'
406
- padding_first = 'top_padding'
407
- padding_second = 'bottom_padding'
408
- padding_third = 'left_padding'
409
- padding_fourth = 'right_padding'
403
+ primary_dimension = WIDTH
404
+ secondary_dimension = HEIGHT
405
+ primary_axis = Y
406
+ secondary_axis = X
407
+ expand_primary = EXPAND_WIDTH
408
+ expand_secondary = EXPAND_HEIGHT
409
+ padding_first = TOP_PADDING
410
+ padding_second = BOTTOM_PADDING
411
+ padding_third = LEFT_PADDING
412
+ padding_fourth = RIGHT_PADDING
410
413
  end
411
414
 
412
415
  view_frame.origin.send("#{primary_axis}=", @margin)
@@ -419,14 +422,13 @@ module HotCocoa
419
422
 
420
423
  if options.send(expand_secondary)
421
424
  view_frame.size.send("#{secondary_dimension}=",
422
- view_size.send("#{secondary_dimension}") - (2 * @margin) -
425
+ view_size.send(secondary_dimension) - (2 * @margin) -
423
426
  options.send(padding_first) - options.send(padding_second))
424
427
  else
425
428
 
426
429
  case options.align
427
430
  when :left, :bottom
428
431
  # Nothing to do
429
-
430
432
  when :center
431
433
  view_frame.origin.send("#{primary_axis}=", (view_size.send(secondary_dimension) / 2.0) - (subview_size.send(secondary_dimension) / 2.0))
432
434
 
@@ -456,8 +458,72 @@ module HotCocoa
456
458
  end
457
459
  end
458
460
 
461
+
462
+ private
463
+
464
+ # @private
465
+ HEIGHT = 'height'
466
+ # @private
467
+ WIDTH = 'width'
468
+ # @private
469
+ X = 'x'
470
+ # @private
471
+ Y = 'y'
472
+ # @private
473
+ EXPAND_HEIGHT = 'expand_height?'
474
+ # @private
475
+ EXPAND_WIDTH = 'expand_width?'
476
+ # @private
477
+ LEFT_PADDING = 'left_padding'
478
+ # @private
479
+ RIGHT_PADDING = 'right_padding'
480
+ # @private
481
+ BOTTOM_PADDING = 'bottom_padding'
482
+ # @private
483
+ TOP_PADDING = 'top_padding'
484
+
485
+ ##
486
+ # Calculate the maximum size that a subview can take up in the layout.
487
+ def calc_expandable_size end_dimension
488
+ expandable_size = end_dimension
489
+ expandable_views = 0
490
+
491
+ subviews.each do |view|
492
+ next unless can_layout? view
493
+
494
+ if vertical?
495
+ size = view.frameSize.height
496
+ expand = view.layout.expand_height?
497
+ padding = view.layout.top_padding + view.layout.bottom_padding
498
+ else
499
+ size = view.frameSize.width
500
+ expand = view.layout.expand_width?
501
+ padding = view.layout.left_padding + view.layout.right_padding
502
+ end
503
+
504
+ if expand
505
+ expandable_views += 1
506
+ else
507
+ expandable_size -= size
508
+ expandable_size -= @spacing
509
+ end
510
+
511
+ expandable_size -= padding
512
+ end
513
+
514
+ expandable_size /= expandable_views
515
+ expandable_size
516
+ end
517
+
518
+ ##
519
+ # @note NSView defines `#layout` in Lion for AutoLayout, and so this
520
+ # will almost always return true on Lion, even if it should
521
+ # not. THIS IS A BUG.
522
+ #
523
+ # Whether or not the view can be used
459
524
  def can_layout? view
460
525
  view.respond_to?(:layout) && !view.layout.nil?
461
526
  end
527
+
462
528
  end
463
529
  end
@@ -1,3 +1,3 @@
1
1
  module HotCocoa
2
- VERSION = '0.6.0'
2
+ VERSION = '0.6.1'
3
3
  end
@@ -12,6 +12,13 @@ Application::Specification.new do |s|
12
12
  s.resources = Dir.glob('resources/**/*.*')
13
13
  s.sources = Dir.glob('lib/**/*.rb')
14
14
 
15
+ # BridgeSupport is required if you need to run your app under OS X 10.6 (Snow Leopard)
16
+ # set this option to false if you do not wish to embed the BridgeSupport files during deployment
17
+ # (i.e. you are targeting Lion only)
18
+ # Information about BridgeSupport: http://www.macruby.org/documentation/tutorial.html
19
+ # Latest Preview of BridgeSupport: http://www.macruby.org/files/BridgeSupport%20Preview%203.zip
20
+ s.embed_bridgesupport = true
21
+
15
22
  # optional copyright
16
23
  # s.copyright = "2011, Your Company"
17
24
 
@@ -27,10 +34,6 @@ Application::Specification.new do |s|
27
34
  # hotcocoa is automatically bundled and doesn't need to be specified here
28
35
  # s.gems = ['rest-client']
29
36
 
30
- # uncomment if you wish to embed the BridgeSupport files during deployment
31
- # useful if you need to deploy the app to OS X 10.6.
32
- # s.embed_bs = true
33
-
34
37
  # uncomment to always make a clean build of the app
35
38
  # s.overwrite = true
36
39