Class: HotCocoa::Mappings::Mapper
Overview
Does most of the heavy lifiting when it comes to HotCocoa mappings.
Constant Summary collapse
- SET =
Performance hack. Put mutable objects that are constant into a constant to avoid having to
#dup
. 'set'
Class Attribute Summary collapse
-
.bindings_modules ⇒ Hash{Symbol=>Module}
readonly
Cached bindings modules.
-
.delegate_modules ⇒ Hash{Symbol=>Module}
readonly
Cached delegate modules.
Instance Attribute Summary collapse
-
#builder_method ⇒ Symbol
readonly
The name which the mapping goes by (e.g. :window for NSWindow).
- #control_class ⇒ Class readonly
-
#control_module ⇒ Class
readonly
Singleton class for the mapper instance.
-
#map_bindings ⇒ Boolean
Whether or not bindings should be mapped for an instance of the mapped class.
Class Method Summary collapse
-
.map_class(klass) ⇒ nil
Add mappings to a class so instances of the class can benefit from HotCocoa features.
-
.map_instances_of(klass, builder_method, &block) ⇒ HotCocoa::Mappings::Mapper
Create a mapper for the given
klass
and assign it to the givenbuilder_method
.
Instance Method Summary collapse
-
#bindings_module_for_control(control) ⇒ Module
Create a module to hold all bindings setters.
-
#customize(control) ⇒ Object
Apply customizations to defined in a mapping to the control.
-
#decorate_with_bindings_methods(control) ⇒ nil
Do not count on a return value.
-
#decorate_with_delegate_methods(control) ⇒ Object
Add the delegate method hooks.
-
#delegate_module_for_control_class ⇒ Module
Create a module to hold the delegate object.
-
#each_control_ancestor {|a| ... } ⇒ Object
Iterates over the ancestor chain for the class being mapped and yields for each ancestor that also has a mapping.
-
#include_in_class ⇒ Object
Add HotCocoa features to a class.
-
#inherited_constants ⇒ Hash{Hash}
Returns a hash of constant hashes that were inherited from ancestors that have also been mapped.
-
#inherited_custom_methods ⇒ Array<Module>
Return the
custom_methods
module for the class we are instantiating, as well as all of its ancestors. - #inherited_delegate_methods ⇒ Object
-
#initialize(klass) ⇒ Mapper
constructor
A new instance of Mapper.
-
#map_method(builder_method) { ... } ⇒ HotCocoa::Mappings::Mapper
Create the mapping method named
builder_method
. -
#remap_constants(tags) ⇒ Hash
Takes a hash and processes symbols, if the symbol is a mapped constant then it will be swapped with the value of the constant.
Constructor Details
#initialize(klass) ⇒ Mapper
Returns a new instance of Mapper.
77 78 79 |
# File 'lib/hotcocoa/mapper.rb', line 77 def initialize klass @control_class = klass end |
Class Attribute Details
.bindings_modules ⇒ Hash{Symbol=>Module} (readonly)
Cached bindings modules.
31 32 33 |
# File 'lib/hotcocoa/mapper.rb', line 31 def bindings_modules @bindings_modules end |
.delegate_modules ⇒ Hash{Symbol=>Module} (readonly)
Cached delegate modules.
37 38 39 |
# File 'lib/hotcocoa/mapper.rb', line 37 def delegate_modules @delegate_modules end |
Instance Attribute Details
#builder_method ⇒ Symbol (readonly)
We do not use the cached builder_method
attribute unless
you count tests. So maybe we should get rid of it?
The name which the mapping goes by (e.g. :window for NSWindow)
61 62 63 |
# File 'lib/hotcocoa/mapper.rb', line 61 def builder_method @builder_method end |
#control_class ⇒ Class (readonly)
52 53 54 |
# File 'lib/hotcocoa/mapper.rb', line 52 def control_class @control_class end |
#control_module ⇒ Class (readonly)
Singleton class for the mapper instance
67 68 69 |
# File 'lib/hotcocoa/mapper.rb', line 67 def control_module @control_module end |
#map_bindings ⇒ Boolean
Whether or not bindings should be mapped for an instance of the mapped class.
74 75 76 |
# File 'lib/hotcocoa/mapper.rb', line 74 def map_bindings @map_bindings end |
Class Method Details
.map_class(klass) ⇒ nil
Add mappings to a class so instances of the class can benefit from HotCocoa features. Usually called by Behaviors.included.
12 13 14 |
# File 'lib/hotcocoa/mapper.rb', line 12 def map_class klass new(klass).include_in_class end |
.map_instances_of(klass, builder_method, &block) ⇒ HotCocoa::Mappings::Mapper
Create a mapper for the given klass
and assign it to the
given builder_method
.
23 24 25 |
# File 'lib/hotcocoa/mapper.rb', line 23 def map_instances_of klass, builder_method, &block new(klass).map_method(builder_method, &block) end |
Instance Method Details
#bindings_module_for_control(control) ⇒ Module
Create a module to hold all bindings setters. The bindings module is meant to assist with setting up Cocoa Bindings by providing a simplified and more Ruby-ish interface.
Read more about Key-Value Binding.
If the control has no exposed bindings, then an empty module will be generated.
In either case, once a module is generated, it is cached for later use.
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/hotcocoa/mapper.rb', line 309 def bindings_module_for_control control bindings_module = HotCocoa::Mappings::Mapper.bindings_modules[control_class] return bindings_module if bindings_module instance = if control == control_class control_class.alloc.init else control end bindings_module = Module.new instance.exposedBindings.each do |exposed_binding| p = Proc.new do |value| if value.kind_of? Hash = value.delete :options bind "#{exposed_binding}", toObject: value.keys.first, withKeyPath: value.values.first, options: else send "set#{exposed_binding.camel_case}", value end end bindings_module.send :define_method, "#{exposed_binding.underscore}=", p end HotCocoa::Mappings::Mapper.bindings_modules[control_class] = bindings_module end |
#customize(control) ⇒ Object
Apply customizations to defined in a mapping to the control. The control is either an instance of the class or the class itself, depending on how things were setup.
225 226 227 228 229 230 231 |
# File 'lib/hotcocoa/mapper.rb', line 225 def customize control inherited_custom_methods.each do |custom_methods| control.send @extension_method, custom_methods end decorate_with_delegate_methods control decorate_with_bindings_methods control end |
#decorate_with_bindings_methods(control) ⇒ nil
Returns do not count on a return value.
289 290 291 292 293 294 |
# File 'lib/hotcocoa/mapper.rb', line 289 def decorate_with_bindings_methods control return if control_class == NSApplication if @map_bindings control.send @extension_method, bindings_module_for_control(control) end end |
#decorate_with_delegate_methods(control) ⇒ Object
Add the delegate method hooks. For #include they become instance methods and for #extend they become singleton methods.
236 237 238 |
# File 'lib/hotcocoa/mapper.rb', line 236 def decorate_with_delegate_methods control control.send @extension_method, delegate_module_for_control_class end |
#delegate_module_for_control_class ⇒ Module
Create a module to hold the delegate object. The module can then be mixed in so that a control instance can use HotCocoa style delegation.
The style of delegation that HotCocoa supports works by creating an Object instance and then defining delegate methods as singleton methods on that object. Then the object is set to be the delegate of the control.
The generated module is cached for later reuse.
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/hotcocoa/mapper.rb', line 252 def delegate_module_for_control_class delegate_module = HotCocoa::Mappings::Mapper.delegate_modules[control_class] return delegate_module if delegate_module delegate_module = Module.new required_methods = [] delegate_methods = inherited_delegate_methods if delegate_methods.size > 0 delegate_methods.each do |delegate_method, mapping| required_methods << delegate_method if mapping[:required] end delegate_methods.each do |delegate_method, mapping| parameters = mapping[:parameters] ? mapping[:parameters] : [] # kind of a hack, giving a block directly to define_method is not working # for some odd reason, possibly a bug in MacRuby callback = Proc.new do |&block| raise 'Must pass in a block to use this delegate method' unless block @_delegate_builder ||= HotCocoa::DelegateBuilder.new(self, required_methods) @_delegate_builder.add_delegated_method(block, delegate_method, *parameters) end delegate_module.send :define_method, mapping[:to], callback end delegate_module.send :define_method, :delegate_to do |object| @_delegate_builder ||= HotCocoa::DelegateBuilder.new(self, required_methods) @_delegate_builder.delegate_to(object, *delegate_methods.values.map { |method| method[:to].to_sym }) end end HotCocoa::Mappings::Mapper.delegate_modules[control_class] = delegate_module end |
#each_control_ancestor {|a| ... } ⇒ Object
Iterates over the ancestor chain for the class being mapped and yields for each ancestor that also has a mapping.
Classes are yielded from the descending order (from the super class to the sub class).
211 212 213 214 215 216 217 |
# File 'lib/hotcocoa/mapper.rb', line 211 def each_control_ancestor control_class.ancestors.reverse.each do |ancestor| HotCocoa::Mappings.mappings.values.each do |mapper| yield mapper if mapper.control_class == ancestor end end end |
#include_in_class ⇒ Object
Add HotCocoa features to a class. The control_class
that the mapper
was initialized with will receive features for all ancestors that
have mappings.
85 86 87 88 |
# File 'lib/hotcocoa/mapper.rb', line 85 def include_in_class @extension_method = :include customize @control_class end |
#inherited_constants ⇒ Hash{Hash}
Returns a hash of constant hashes that were inherited from ancestors that have also been mapped.
171 172 173 174 175 176 177 |
# File 'lib/hotcocoa/mapper.rb', line 171 def inherited_constants constants = {} each_control_ancestor do |ancestor| constants.merge! ancestor.control_module.constants_map end constants end |
#inherited_custom_methods ⇒ Array<Module>
Return the custom_methods
module for the class we are instantiating,
as well as all of its ancestors.
192 193 194 195 196 197 198 199 200 |
# File 'lib/hotcocoa/mapper.rb', line 192 def inherited_custom_methods methods = [] each_control_ancestor do |ancestor| if ancestor.control_module.custom_methods methods << ancestor.control_module.custom_methods end end methods end |
#inherited_delegate_methods ⇒ Object
179 180 181 182 183 184 185 |
# File 'lib/hotcocoa/mapper.rb', line 179 def inherited_delegate_methods delegate_methods = {} each_control_ancestor do |ancestor| delegate_methods.merge! ancestor.control_module.delegate_map end delegate_methods end |
#map_method(builder_method) { ... } ⇒ HotCocoa::Mappings::Mapper
Create the mapping method named builder_method
.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/hotcocoa/mapper.rb', line 96 def map_method builder_method, &block @extension_method = :extend @builder_method = builder_method # @todo use self.singleton_class instead (not implemented in MacRuby yet) mod = (class << self; self; end) mod.extend HotCocoa::MappingMethods mod.module_eval &block @control_module = mod # put self in a variable, because context of self changes inside the define_method block inst = self HotCocoa.send :define_method, builder_method do |args = {}, &control_block| map = inst.remap_constants args inst.map_bindings = map.delete :map_bindings default_empty_rect_used = (CGRectZero == map[:frame]) control = if inst.respond_to? :init_with_options inst.(inst.control_class.alloc, map) else inst.(map) end inst.customize control map.each do |key, value| if control.respond_to? "#{key}=" control.send "#{key}=", value elsif control.respond_to? key new_key = (key.start_with?(SET) ? key : "set#{key[0].capitalize}#{key[1..-1]}") if control.respond_to? new_key control.send new_key, value else control.send key end elsif control.respond_to? "set#{key.camel_case}" control.send "set#{key.camel_case}", value else NSLog("Unable to map #{key} as a method") end end if default_empty_rect_used control.sizeToFit if control.respondsToSelector :sizeToFit end if control_block if inst.respond_to? :handle_block inst.handle_block control, &control_block else control_block.call control end end control end # make the function callable using HotCocoa.xxxx HotCocoa.send :module_function, builder_method # module_function makes the instance method private, but we want it to stay public HotCocoa.send :public, builder_method self end |
#remap_constants(tags) ⇒ Hash
Takes a hash and processes symbols, if the symbol is a mapped constant then it will be swapped with the value of the constant.
This is how constant mappings are used in Hot Cocoa.
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'lib/hotcocoa/mapper.rb', line 345 def remap_constants constants = inherited_constants if control_module.defaults control_module.defaults.each do |key, value| [key] = value unless .has_key? key end end result = {} .each do |tag, value| if constants[tag] result[tag] = value.kind_of?(Array) ? value.inject(0) { |a, i| a|constants[tag][i] } : constants[tag][value] else result[tag] = value end end result end |