hotcocoa 0.5
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.
- 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
|