hotcocoa 0.6.0 → 0.6.1

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.
@@ -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