hotcocoa 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/bin/hotcocoa +31 -0
- data/lib/hotcocoa/application_builder.rb +320 -0
- data/lib/hotcocoa/attributed_string.rb +143 -0
- data/lib/hotcocoa/behaviors.rb +7 -0
- data/lib/hotcocoa/data_sources/combo_box_data_source.rb +44 -0
- data/lib/hotcocoa/data_sources/table_data_source.rb +18 -0
- data/lib/hotcocoa/delegate_builder.rb +85 -0
- data/lib/hotcocoa/graphics/canvas.rb +836 -0
- data/lib/hotcocoa/graphics/color.rb +781 -0
- data/lib/hotcocoa/graphics/elements/particle.rb +75 -0
- data/lib/hotcocoa/graphics/elements/rope.rb +99 -0
- data/lib/hotcocoa/graphics/elements/sandpainter.rb +71 -0
- data/lib/hotcocoa/graphics/gradient.rb +63 -0
- data/lib/hotcocoa/graphics/image.rb +488 -0
- data/lib/hotcocoa/graphics/path.rb +325 -0
- data/lib/hotcocoa/graphics/pdf.rb +71 -0
- data/lib/hotcocoa/graphics.rb +161 -0
- data/lib/hotcocoa/kernel_ext.rb +14 -0
- data/lib/hotcocoa/kvo_accessors.rb +48 -0
- data/lib/hotcocoa/layout_view.rb +448 -0
- data/lib/hotcocoa/mapper.rb +227 -0
- data/lib/hotcocoa/mapping_methods.rb +40 -0
- data/lib/hotcocoa/mappings/alert.rb +25 -0
- data/lib/hotcocoa/mappings/application.rb +112 -0
- data/lib/hotcocoa/mappings/array_controller.rb +87 -0
- data/lib/hotcocoa/mappings/box.rb +39 -0
- data/lib/hotcocoa/mappings/button.rb +92 -0
- data/lib/hotcocoa/mappings/collection_view.rb +44 -0
- data/lib/hotcocoa/mappings/color.rb +28 -0
- data/lib/hotcocoa/mappings/column.rb +21 -0
- data/lib/hotcocoa/mappings/combo_box.rb +24 -0
- data/lib/hotcocoa/mappings/control.rb +33 -0
- data/lib/hotcocoa/mappings/font.rb +44 -0
- data/lib/hotcocoa/mappings/gradient.rb +15 -0
- data/lib/hotcocoa/mappings/image.rb +15 -0
- data/lib/hotcocoa/mappings/image_view.rb +43 -0
- data/lib/hotcocoa/mappings/label.rb +25 -0
- data/lib/hotcocoa/mappings/layout_view.rb +9 -0
- data/lib/hotcocoa/mappings/menu.rb +71 -0
- data/lib/hotcocoa/mappings/menu_item.rb +47 -0
- data/lib/hotcocoa/mappings/movie.rb +13 -0
- data/lib/hotcocoa/mappings/movie_view.rb +27 -0
- data/lib/hotcocoa/mappings/notification.rb +17 -0
- data/lib/hotcocoa/mappings/popup.rb +110 -0
- data/lib/hotcocoa/mappings/progress_indicator.rb +68 -0
- data/lib/hotcocoa/mappings/scroll_view.rb +29 -0
- data/lib/hotcocoa/mappings/search_field.rb +9 -0
- data/lib/hotcocoa/mappings/secure_text_field.rb +17 -0
- data/lib/hotcocoa/mappings/segmented_control.rb +97 -0
- data/lib/hotcocoa/mappings/slider.rb +25 -0
- data/lib/hotcocoa/mappings/sort_descriptor.rb +13 -0
- data/lib/hotcocoa/mappings/sound.rb +9 -0
- data/lib/hotcocoa/mappings/speech_synthesizer.rb +25 -0
- data/lib/hotcocoa/mappings/split_view.rb +21 -0
- data/lib/hotcocoa/mappings/status_bar.rb +7 -0
- data/lib/hotcocoa/mappings/status_item.rb +9 -0
- data/lib/hotcocoa/mappings/table_view.rb +110 -0
- data/lib/hotcocoa/mappings/text_field.rb +41 -0
- data/lib/hotcocoa/mappings/text_view.rb +13 -0
- data/lib/hotcocoa/mappings/timer.rb +25 -0
- data/lib/hotcocoa/mappings/toolbar.rb +97 -0
- data/lib/hotcocoa/mappings/toolbar_item.rb +36 -0
- data/lib/hotcocoa/mappings/view.rb +67 -0
- data/lib/hotcocoa/mappings/web_view.rb +22 -0
- data/lib/hotcocoa/mappings/window.rb +118 -0
- data/lib/hotcocoa/mappings/xml_parser.rb +41 -0
- data/lib/hotcocoa/mappings.rb +109 -0
- data/lib/hotcocoa/mvc.rb +175 -0
- data/lib/hotcocoa/notification_listener.rb +62 -0
- data/lib/hotcocoa/object_ext.rb +22 -0
- data/lib/hotcocoa/plist.rb +45 -0
- data/lib/hotcocoa/standard_rake_tasks.rb +17 -0
- data/lib/hotcocoa/template.rb +27 -0
- data/lib/hotcocoa/virtual_file_system.rb +172 -0
- data/lib/hotcocoa.rb +26 -0
- data/template/Rakefile +5 -0
- data/template/config/build.yml +8 -0
- data/template/lib/application.rb +45 -0
- data/template/lib/menu.rb +32 -0
- data/template/resources/HotCocoa.icns +0 -0
- data/test/test_helper.rb +3 -0
- data/test/test_hotcocoa.rb +11 -0
- metadata +137 -0
@@ -0,0 +1,448 @@
|
|
1
|
+
module HotCocoa
|
2
|
+
|
3
|
+
module LayoutManaged
|
4
|
+
|
5
|
+
def layout=(options)
|
6
|
+
@layout = LayoutOptions.new(self, options)
|
7
|
+
@layout.update_layout_views!
|
8
|
+
end
|
9
|
+
|
10
|
+
def layout
|
11
|
+
@layout
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class LayoutOptions
|
17
|
+
|
18
|
+
VALID_EXPANSIONS = [nil, :height, :width, [:height, :width], [:width, :height]]
|
19
|
+
|
20
|
+
attr_accessor :defaults_view
|
21
|
+
attr_reader :view
|
22
|
+
|
23
|
+
# options can be
|
24
|
+
#
|
25
|
+
# :start -> bool
|
26
|
+
# Whether the view is packed at the start or the end of the packing view.
|
27
|
+
# Default value is true.
|
28
|
+
#
|
29
|
+
# :expand -> :height, :width, [:height, :width]
|
30
|
+
# Whether the view's first dimension (width for horizontal and height for vertical)
|
31
|
+
# should be expanded to the maximum possible size, and should be variable according
|
32
|
+
# to the packing view frame.
|
33
|
+
# Default value is nil.
|
34
|
+
#
|
35
|
+
# :padding -> float
|
36
|
+
# :left_padding -> float
|
37
|
+
# :right_padding -> float
|
38
|
+
# :top_padding -> float
|
39
|
+
# :bottom_padding -> float
|
40
|
+
# Controls the padding area around the view. :padding controls all areas, while
|
41
|
+
# :left_padding for example only controls the left side. If :padding is set, other
|
42
|
+
# padding flags are ignored.
|
43
|
+
# Default value is 0.0 for all flags.
|
44
|
+
#
|
45
|
+
# :align -> mode
|
46
|
+
# Controls the view's alignment if its not expanded in the other dimension
|
47
|
+
# Modes can be:
|
48
|
+
# :left
|
49
|
+
# For horizontal layouts, align left
|
50
|
+
# :center
|
51
|
+
# Align center for horizontal or vertical layouts
|
52
|
+
# :right
|
53
|
+
# For horizontal layouts, align right
|
54
|
+
# :top
|
55
|
+
# For vertical layouts, align top
|
56
|
+
# :bottom
|
57
|
+
# For vertical layouts, align bottom
|
58
|
+
def initialize(view, options={})
|
59
|
+
@view = view
|
60
|
+
@start = options[:start]
|
61
|
+
@expand = options[:expand]
|
62
|
+
@padding = options[:padding]
|
63
|
+
@left_padding = @padding || options[:left_padding]
|
64
|
+
@right_padding = @padding || options[:right_padding]
|
65
|
+
@top_padding = @padding || options[:top_padding]
|
66
|
+
@bottom_padding = @padding || options[:bottom_padding]
|
67
|
+
@align = options[:align]
|
68
|
+
@defaults_view = options[:defaults_view]
|
69
|
+
end
|
70
|
+
|
71
|
+
def start=(value)
|
72
|
+
return if value == @start
|
73
|
+
@start = value
|
74
|
+
update_layout_views!
|
75
|
+
end
|
76
|
+
|
77
|
+
def start?
|
78
|
+
return @start unless @start.nil?
|
79
|
+
if in_layout_view?
|
80
|
+
@view.superview.default_layout.start?
|
81
|
+
else
|
82
|
+
true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def expand=(value)
|
87
|
+
return if value == @expand
|
88
|
+
unless VALID_EXPANSIONS.include?(value)
|
89
|
+
raise ArgumentError, "Expand must be nil, :height, :width or [:width, :height] not #{value.inspect}"
|
90
|
+
end
|
91
|
+
@expand = value
|
92
|
+
update_layout_views!
|
93
|
+
end
|
94
|
+
|
95
|
+
def expand
|
96
|
+
return @expand unless @expand.nil?
|
97
|
+
if in_layout_view?
|
98
|
+
@view.superview.default_layout.expand
|
99
|
+
else
|
100
|
+
false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def expand_width?
|
105
|
+
e = self.expand
|
106
|
+
e == :width || (e.respond_to?(:include?) && e.include?(:width))
|
107
|
+
end
|
108
|
+
|
109
|
+
def expand_height?
|
110
|
+
e = self.expand
|
111
|
+
e == :height || (e.respond_to?(:include?) && e.include?(:height))
|
112
|
+
end
|
113
|
+
|
114
|
+
def left_padding=(value)
|
115
|
+
return if value == @left_padding
|
116
|
+
@left_padding = value
|
117
|
+
@padding = nil
|
118
|
+
update_layout_views!
|
119
|
+
end
|
120
|
+
|
121
|
+
def left_padding
|
122
|
+
return @left_padding unless @left_padding.nil?
|
123
|
+
if in_layout_view?
|
124
|
+
@view.superview.default_layout.left_padding
|
125
|
+
else
|
126
|
+
@padding || 0.0
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def right_padding=(value)
|
131
|
+
return if value == @right_padding
|
132
|
+
@right_padding = value
|
133
|
+
@padding = nil
|
134
|
+
update_layout_views!
|
135
|
+
end
|
136
|
+
|
137
|
+
def right_padding
|
138
|
+
return @right_padding unless @right_padding.nil?
|
139
|
+
if in_layout_view?
|
140
|
+
@view.superview.default_layout.right_padding
|
141
|
+
else
|
142
|
+
@padding || 0.0
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def top_padding=(value)
|
147
|
+
return if value == @top_padding
|
148
|
+
@top_padding = value
|
149
|
+
@padding = nil
|
150
|
+
update_layout_views!
|
151
|
+
end
|
152
|
+
|
153
|
+
def top_padding
|
154
|
+
return @top_padding unless @top_padding.nil?
|
155
|
+
if in_layout_view?
|
156
|
+
@view.superview.default_layout.top_padding
|
157
|
+
else
|
158
|
+
@padding || 0.0
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def bottom_padding=(value)
|
163
|
+
return if value == @bottom_padding
|
164
|
+
@bottom_padding = value
|
165
|
+
@padding = nil
|
166
|
+
update_layout_views!
|
167
|
+
end
|
168
|
+
|
169
|
+
def bottom_padding
|
170
|
+
return @bottom_padding unless @bottom_padding.nil?
|
171
|
+
if in_layout_view?
|
172
|
+
@view.superview.default_layout.bottom_padding
|
173
|
+
else
|
174
|
+
@padding || 0.0
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def align
|
179
|
+
return @align unless @align.nil?
|
180
|
+
if in_layout_view?
|
181
|
+
@view.superview.default_layout.align
|
182
|
+
else
|
183
|
+
:left
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def align=(value)
|
188
|
+
return if value == @align
|
189
|
+
@align = value
|
190
|
+
update_layout_views!
|
191
|
+
end
|
192
|
+
|
193
|
+
def padding=(value)
|
194
|
+
return if value == @padding
|
195
|
+
@right_padding = @left_padding = @top_padding = @bottom_padding = value
|
196
|
+
@padding = value
|
197
|
+
update_layout_views!
|
198
|
+
end
|
199
|
+
|
200
|
+
def padding
|
201
|
+
@padding
|
202
|
+
end
|
203
|
+
|
204
|
+
def inspect
|
205
|
+
"#<#{self.class} start=#{start?}, expand=#{expand.inspect}, left_padding=#{left_padding}, right_padding=#{right_padding}, top_padding=#{top_padding}, bottom_padding=#{bottom_padding}, align=#{align.inspect}, view=#{view.inspect}>"
|
206
|
+
end
|
207
|
+
|
208
|
+
def update_layout_views!
|
209
|
+
@view.superview.views_updated! if in_layout_view?
|
210
|
+
@defaults_view.views_updated! if @defaults_view
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
def in_layout_view?
|
216
|
+
@view && @view.superview.kind_of?(LayoutView)
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
class LayoutView < NSView
|
223
|
+
|
224
|
+
attr_accessor :frame_color
|
225
|
+
|
226
|
+
def initWithFrame(frame)
|
227
|
+
super
|
228
|
+
@mode = :vertical
|
229
|
+
@spacing = 10.0
|
230
|
+
@margin = 10.0
|
231
|
+
self
|
232
|
+
end
|
233
|
+
|
234
|
+
def vertical?
|
235
|
+
@mode == :vertical
|
236
|
+
end
|
237
|
+
|
238
|
+
def horizontal?
|
239
|
+
@mode == :horizonal
|
240
|
+
end
|
241
|
+
|
242
|
+
def mode=(mode)
|
243
|
+
if mode != :horizontal and mode != :vertical
|
244
|
+
raise ArgumentError, "invalid mode value #{mode}"
|
245
|
+
end
|
246
|
+
if mode != @mode
|
247
|
+
@mode = mode
|
248
|
+
relayout!
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def default_layout=(options)
|
253
|
+
options[:defaults_view] = self
|
254
|
+
@default_layout = LayoutOptions.new(nil, options)
|
255
|
+
relayout!
|
256
|
+
end
|
257
|
+
|
258
|
+
def default_layout
|
259
|
+
@default_layout ||= LayoutOptions.new(nil, :defaults_view => self)
|
260
|
+
end
|
261
|
+
|
262
|
+
def spacing
|
263
|
+
@spacing
|
264
|
+
end
|
265
|
+
|
266
|
+
def spacing=(spacing)
|
267
|
+
if spacing != @spacing
|
268
|
+
@spacing = spacing.to_i
|
269
|
+
relayout!
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def frame=(frame)
|
274
|
+
setFrame(frame)
|
275
|
+
end
|
276
|
+
|
277
|
+
def size=(size)
|
278
|
+
setFrameSize(size)
|
279
|
+
end
|
280
|
+
|
281
|
+
def margin
|
282
|
+
@margin
|
283
|
+
end
|
284
|
+
|
285
|
+
def margin=(margin)
|
286
|
+
if margin != @margin
|
287
|
+
@margin = margin.to_i
|
288
|
+
relayout!
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def <<(view)
|
293
|
+
addSubview(view)
|
294
|
+
end
|
295
|
+
|
296
|
+
def remove(subview, options = {})
|
297
|
+
raise ArgumentError, "#{subview} is not a subview of #{self} and cannot be removed." unless subview.superview == self
|
298
|
+
options[:needs_display] == false ? subview.removeFromSuperviewWithoutNeedingDisplay : subview.removeFromSuperview
|
299
|
+
end
|
300
|
+
|
301
|
+
def addSubview(view)
|
302
|
+
super
|
303
|
+
if view.respond_to?(:layout)
|
304
|
+
relayout!
|
305
|
+
else
|
306
|
+
raise ArgumentError, "view #{view} does not support the #layout method"
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def views_updated!
|
311
|
+
relayout!
|
312
|
+
end
|
313
|
+
|
314
|
+
def remove_view(view)
|
315
|
+
unless subviews.include?(view)
|
316
|
+
raise ArgumentError, "view #{view} not a subview of this LayoutView"
|
317
|
+
end
|
318
|
+
view.removeFromSuperview
|
319
|
+
relayout!
|
320
|
+
end
|
321
|
+
|
322
|
+
def remove_all_views
|
323
|
+
subviews.each { |view| view.removeFromSuperview }
|
324
|
+
relayout!
|
325
|
+
end
|
326
|
+
|
327
|
+
def drawRect(frame)
|
328
|
+
if @frame_color
|
329
|
+
@frame_color.set
|
330
|
+
NSFrameRect(frame)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def setFrame(frame)
|
335
|
+
super
|
336
|
+
relayout!
|
337
|
+
end
|
338
|
+
|
339
|
+
def setFrameSize(size)
|
340
|
+
super
|
341
|
+
relayout!
|
342
|
+
end
|
343
|
+
|
344
|
+
private
|
345
|
+
|
346
|
+
def relayout!
|
347
|
+
vertical = @mode == :vertical
|
348
|
+
view_size = frameSize
|
349
|
+
dimension = @margin
|
350
|
+
end_dimension = vertical ? view_size.height : view_size.width
|
351
|
+
end_dimension -= (@margin * 2)
|
352
|
+
|
353
|
+
expandable_size = end_dimension
|
354
|
+
expandable_views = 0
|
355
|
+
subviews.each do |view|
|
356
|
+
next if !view.respond_to?(:layout) || view.layout.nil?
|
357
|
+
if (vertical ? view.layout.expand_height? : view.layout.expand_width?)
|
358
|
+
expandable_views += 1
|
359
|
+
else
|
360
|
+
expandable_size -= vertical ? view.frameSize.height : view.frameSize.width
|
361
|
+
expandable_size -= @spacing
|
362
|
+
end
|
363
|
+
expandable_size -=
|
364
|
+
vertical ? view.layout.top_padding + view.layout.bottom_padding
|
365
|
+
: view.layout.left_padding + view.layout.right_padding
|
366
|
+
end
|
367
|
+
expandable_size /= expandable_views
|
368
|
+
|
369
|
+
subviews.each do |view|
|
370
|
+
next if !view.respond_to?(:layout) || view.layout.nil?
|
371
|
+
options = view.layout
|
372
|
+
subview_size = view.frameSize
|
373
|
+
view_frame = NSMakeRect(0, 0, *subview_size)
|
374
|
+
subview_dimension = vertical ? subview_size.height : subview_size.width
|
375
|
+
|
376
|
+
if vertical
|
377
|
+
view_frame.origin.x = @margin
|
378
|
+
if options.start?
|
379
|
+
view_frame.origin.y = dimension
|
380
|
+
else
|
381
|
+
view_frame.origin.y = end_dimension - subview_dimension
|
382
|
+
end
|
383
|
+
else
|
384
|
+
if options.start?
|
385
|
+
view_frame.origin.x = dimension
|
386
|
+
else
|
387
|
+
view_frame.origin.x = end_dimension - subview_dimension
|
388
|
+
end
|
389
|
+
view_frame.origin.y = @margin
|
390
|
+
end
|
391
|
+
|
392
|
+
if (vertical ? options.expand_height? : options.expand_width?)
|
393
|
+
if vertical
|
394
|
+
view_frame.size.height = expandable_size
|
395
|
+
else
|
396
|
+
view_frame.size.width = expandable_size
|
397
|
+
end
|
398
|
+
subview_dimension = expandable_size
|
399
|
+
end
|
400
|
+
|
401
|
+
if (vertical ? options.expand_width? : options.expand_height?)
|
402
|
+
if vertical
|
403
|
+
view_frame.size.width = view_size.width - (2 * @margin) - options.right_padding - options.left_padding
|
404
|
+
else
|
405
|
+
view_frame.size.height = view_size.height - (2 * @margin) - options.top_padding - options.bottom_padding
|
406
|
+
end
|
407
|
+
else
|
408
|
+
case options.align
|
409
|
+
when :left, :bottom
|
410
|
+
# Nothing to do
|
411
|
+
|
412
|
+
when :center
|
413
|
+
if vertical
|
414
|
+
view_frame.origin.x = (view_size.width / 2.0) - (subview_size.width / 2.0)
|
415
|
+
else
|
416
|
+
view_frame.origin.y = (view_size.height / 2.0) - (subview_size.height / 2.0)
|
417
|
+
end
|
418
|
+
|
419
|
+
when :right, :top
|
420
|
+
if vertical
|
421
|
+
view_frame.origin.x = view_size.width - subview_size.width - @margin
|
422
|
+
else
|
423
|
+
view_frame.origin.y = view_size.height - subview_size.height - @margin
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
puts "view #{view} options #{options} final frame #{view_frame}" if $DEBUG
|
429
|
+
|
430
|
+
view_frame.origin.x += options.left_padding
|
431
|
+
view_frame.origin.y += options.bottom_padding
|
432
|
+
|
433
|
+
if options.start?
|
434
|
+
dimension += subview_dimension + @spacing
|
435
|
+
if vertical
|
436
|
+
dimension += options.bottom_padding + options.top_padding
|
437
|
+
else
|
438
|
+
dimension += options.left_padding + options.right_padding
|
439
|
+
end
|
440
|
+
else
|
441
|
+
end_dimension -= subview_dimension + @spacing
|
442
|
+
end
|
443
|
+
|
444
|
+
view.frame = view_frame
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
module HotCocoa
|
2
|
+
module Mappings
|
3
|
+
class Mapper
|
4
|
+
|
5
|
+
attr_reader :control_class, :builder_method, :control_module
|
6
|
+
|
7
|
+
attr_accessor :map_bindings
|
8
|
+
|
9
|
+
def self.map_class(klass)
|
10
|
+
new(klass).include_in_class
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.map_instances_of(klass, builder_method, &block)
|
14
|
+
new(klass).map_method(builder_method, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.bindings_modules
|
18
|
+
@bindings_module ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.delegate_modules
|
22
|
+
@delegate_modules ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(klass)
|
26
|
+
@control_class = klass
|
27
|
+
end
|
28
|
+
|
29
|
+
def include_in_class
|
30
|
+
@extension_method = :include
|
31
|
+
customize(@control_class)
|
32
|
+
end
|
33
|
+
|
34
|
+
def map_method(builder_method, &block)
|
35
|
+
@extension_method = :extend
|
36
|
+
@builder_method = builder_method
|
37
|
+
mod = (class << self; self; end)
|
38
|
+
mod.extend MappingMethods
|
39
|
+
mod.module_eval &block
|
40
|
+
@control_module = mod
|
41
|
+
inst = self
|
42
|
+
HotCocoa.send(:define_method, builder_method) do |*args, &control_block|
|
43
|
+
map = (args.length == 1 ? args[0] : args[1]) || {}
|
44
|
+
guid = args.length == 1 ? nil : args[0]
|
45
|
+
map = inst.remap_constants(map)
|
46
|
+
inst.map_bindings = map.delete(:map_bindings)
|
47
|
+
default_empty_rect_used = (map[:frame].__id__ == DefaultEmptyRect.__id__)
|
48
|
+
control = inst.respond_to?(:init_with_options) ? inst.init_with_options(inst.control_class.alloc, map) : inst.alloc_with_options(map)
|
49
|
+
Views[guid] = control if guid
|
50
|
+
inst.customize(control)
|
51
|
+
map.each do |key, value|
|
52
|
+
if control.respond_to?("#{key}=")
|
53
|
+
eval "control.#{key} = value"
|
54
|
+
elsif control.respond_to?(key)
|
55
|
+
new_key = (key.start_with?('set') ? key : "set#{key[0].capitalize}#{key[1..(key.length - 1)]}")
|
56
|
+
if control.respond_to?(new_key)
|
57
|
+
eval "control.#{new_key}(value)"
|
58
|
+
else
|
59
|
+
control.send("#{key}")
|
60
|
+
end
|
61
|
+
elsif control.respond_to?("set#{Mapper.camel_case(key.to_s)}")
|
62
|
+
eval "control.set#{Mapper.camel_case(key.to_s)}(value)"
|
63
|
+
else
|
64
|
+
NSLog "Unable to map #{key} as a method"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
if default_empty_rect_used
|
68
|
+
control.sizeToFit if control.respondsToSelector(:sizeToFit) == true
|
69
|
+
end
|
70
|
+
if control_block
|
71
|
+
if inst.respond_to?(:handle_block)
|
72
|
+
inst.handle_block(control, &control_block)
|
73
|
+
else
|
74
|
+
control_block.call(control)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
control
|
78
|
+
end
|
79
|
+
# make the function callable using HotCocoa.xxxx
|
80
|
+
HotCocoa.send(:module_function, builder_method)
|
81
|
+
# module_function makes the instance method private, but we want it to stay public
|
82
|
+
HotCocoa.send(:public, builder_method)
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def inherited_constants
|
87
|
+
constants = {}
|
88
|
+
each_control_ancestor do |ancestor|
|
89
|
+
constants = constants.merge(ancestor.control_module.constants_map)
|
90
|
+
end
|
91
|
+
constants
|
92
|
+
end
|
93
|
+
|
94
|
+
def inherited_delegate_methods
|
95
|
+
delegate_methods = {}
|
96
|
+
each_control_ancestor do |ancestor|
|
97
|
+
delegate_methods = delegate_methods.merge(ancestor.control_module.delegate_map)
|
98
|
+
end
|
99
|
+
delegate_methods
|
100
|
+
end
|
101
|
+
|
102
|
+
def inherited_custom_methods
|
103
|
+
methods = []
|
104
|
+
each_control_ancestor do |ancestor|
|
105
|
+
methods << ancestor.control_module.custom_methods if ancestor.control_module.custom_methods
|
106
|
+
end
|
107
|
+
methods
|
108
|
+
end
|
109
|
+
|
110
|
+
def each_control_ancestor
|
111
|
+
control_class.ancestors.reverse.each do |ancestor|
|
112
|
+
Mappings.mappings.values.each do |mapper|
|
113
|
+
yield mapper if mapper.control_class == ancestor
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def customize(control)
|
119
|
+
inherited_custom_methods.each do |custom_methods|
|
120
|
+
control.send(@extension_method, custom_methods)
|
121
|
+
end
|
122
|
+
decorate_with_delegate_methods(control)
|
123
|
+
decorate_with_bindings_methods(control)
|
124
|
+
end
|
125
|
+
|
126
|
+
def decorate_with_delegate_methods(control)
|
127
|
+
control.send(@extension_method, delegate_module_for_control_class)
|
128
|
+
end
|
129
|
+
|
130
|
+
def delegate_module_for_control_class
|
131
|
+
unless Mapper.delegate_modules.has_key?(control_class)
|
132
|
+
delegate_module = Module.new
|
133
|
+
required_methods = []
|
134
|
+
delegate_methods = inherited_delegate_methods
|
135
|
+
if delegate_methods.size > 0
|
136
|
+
delegate_methods.each do |delegate_method, mapping|
|
137
|
+
required_methods << delegate_method if mapping[:required]
|
138
|
+
end
|
139
|
+
delegate_methods.each do |delegate_method, mapping|
|
140
|
+
parameters = mapping[:parameters] ? ", "+mapping[:parameters].map {|param| %{"#{param}"} }.join(",") : ""
|
141
|
+
delegate_module.module_eval %{
|
142
|
+
def #{mapping[:to]}(&block)
|
143
|
+
raise "Must pass in a block to use this delegate method" unless block_given?
|
144
|
+
@_delegate_builder ||= HotCocoa::DelegateBuilder.new(self, #{required_methods.inspect})
|
145
|
+
@_delegate_builder.add_delegated_method(block, "#{delegate_method}" #{parameters})
|
146
|
+
end
|
147
|
+
}
|
148
|
+
end
|
149
|
+
delegate_module.module_eval %{
|
150
|
+
def delegate_to(object)
|
151
|
+
@_delegate_builder ||= HotCocoa::DelegateBuilder.new(self, #{required_methods.inspect})
|
152
|
+
@_delegate_builder.delegate_to(object, #{delegate_methods.values.map {|method| ":#{method[:to]}"}.join(', ')})
|
153
|
+
end
|
154
|
+
}
|
155
|
+
end
|
156
|
+
Mapper.delegate_modules[control_class] = delegate_module
|
157
|
+
end
|
158
|
+
Mapper.delegate_modules[control_class]
|
159
|
+
end
|
160
|
+
|
161
|
+
def decorate_with_bindings_methods(control)
|
162
|
+
return if control_class == NSApplication
|
163
|
+
control.send(@extension_method, bindings_module_for_control(control)) if @map_bindings
|
164
|
+
end
|
165
|
+
|
166
|
+
def bindings_module_for_control(control)
|
167
|
+
return Mapper.bindings_modules[control_class] if Mapper.bindings_modules.has_key?(control_class)
|
168
|
+
instance = if control == control_class
|
169
|
+
control_class.alloc.init
|
170
|
+
else
|
171
|
+
control
|
172
|
+
end
|
173
|
+
bindings_module = Module.new
|
174
|
+
instance.exposedBindings.each do |exposed_binding|
|
175
|
+
bindings_module.module_eval %{
|
176
|
+
def #{Mapper.underscore(exposed_binding)}=(value)
|
177
|
+
if value.kind_of?(Hash)
|
178
|
+
options = value.delete(:options)
|
179
|
+
bind "#{exposed_binding}", toObject:value.keys.first, withKeyPath:value.values.first, options:options
|
180
|
+
else
|
181
|
+
set#{exposed_binding.capitalize}(value)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
}
|
185
|
+
end
|
186
|
+
Mapper.bindings_modules[control_class] = bindings_module
|
187
|
+
bindings_module
|
188
|
+
end
|
189
|
+
|
190
|
+
def remap_constants(tags)
|
191
|
+
constants = inherited_constants
|
192
|
+
if control_module.defaults
|
193
|
+
control_module.defaults.each do |key, value|
|
194
|
+
tags[key] = value unless tags.has_key?(key)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
result = {}
|
198
|
+
tags.each do |tag, value|
|
199
|
+
if constants[tag]
|
200
|
+
result[tag] = value.kind_of?(Array) ? value.inject(0) {|a, i| a|constants[tag][i]} : constants[tag][value]
|
201
|
+
else
|
202
|
+
result[tag] = value
|
203
|
+
end
|
204
|
+
end
|
205
|
+
result
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.underscore(string)
|
209
|
+
string.gsub(/::/, '/').
|
210
|
+
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
|
211
|
+
gsub(/([a-z\d])([A-Z])/, '\1_\2').
|
212
|
+
tr("-", "_").
|
213
|
+
downcase
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.camel_case(string)
|
217
|
+
if string !~ /_/ && string =~ /[A-Z]+.*/
|
218
|
+
string
|
219
|
+
else
|
220
|
+
string.split('_').map{ |e| e.capitalize }.join
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module HotCocoa
|
2
|
+
|
3
|
+
module MappingMethods
|
4
|
+
|
5
|
+
def defaults(defaults=nil)
|
6
|
+
if defaults
|
7
|
+
@defaults = defaults
|
8
|
+
else
|
9
|
+
@defaults
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def constant(name, constants)
|
14
|
+
constants_map[name] = constants
|
15
|
+
end
|
16
|
+
|
17
|
+
def constants_map
|
18
|
+
@constants_map ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def custom_methods(&block)
|
22
|
+
if block
|
23
|
+
@custom_methods = Module.new
|
24
|
+
@custom_methods.module_eval(&block)
|
25
|
+
else
|
26
|
+
@custom_methods
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def delegating(name, options)
|
31
|
+
delegate_map[name] = options
|
32
|
+
end
|
33
|
+
|
34
|
+
def delegate_map
|
35
|
+
@delegate_map ||= {}
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|