1# Copyright (c) 2006-2008, The RubyCocoa Project. 2# Copyright (c) 2001-2006, FUJIMOTO Hisakuni. 3# All Rights Reserved. 4# 5# RubyCocoa is free software, covered under either the Ruby's license or the 6# LGPL. See the COPYRIGHT file for more information. 7 8require 'osx/objc/oc_wrapper' 9 10module OSX 11 12 FRAMEWORK_PATHS = [ 13 '/System/Library/Frameworks', 14 '/Library/Frameworks' 15 ] 16 17 SIGN_PATHS = [ 18 '/System/Library/BridgeSupport', 19 '/Library/BridgeSupport' 20 ] 21 22 PRE_SIGN_PATHS = 23 if path = ENV['BRIDGE_SUPPORT_PATH'] 24 path.split(':') 25 else 26 [] 27 end 28 29 FRAMEWORK_PATHS.concat(RUBYCOCOA_FRAMEWORK_PATHS) 30 31 if path = ENV['HOME'] 32 FRAMEWORK_PATHS << File.join(ENV['HOME'], 'Library', 'Frameworks') 33 SIGN_PATHS << File.join(ENV['HOME'], 'Library', 'BridgeSupport') 34 end 35 36 # A name-to-path cache for the frameworks we support that are buried into umbrella frameworks. 37 QUICK_FRAMEWORKS = { 38 'CoreGraphics' => '/System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework', 39 'PDFKit' => '/System/Library/Frameworks/Quartz.framework/Frameworks/PDFKit.framework', 40 'QuartzComposer' => '/System/Library/Frameworks/Quartz.framework/Frameworks/QuartzComposer.framework', 41 'ImageKit' => '/System/Library/Frameworks/Quartz.framework/Frameworks/ImageKit.framework' 42 } 43 44 def _bundle_path_for_framework(framework) 45 if framework[0] == ?/ 46 [OSX::NSBundle.bundleWithPath(framework), framework] 47 elsif path = QUICK_FRAMEWORKS[framework] 48 [OSX::NSBundle.bundleWithPath(path), path] 49 else 50 path = FRAMEWORK_PATHS.map { |dir| 51 File.join(dir, "#{framework}.framework") 52 }.find { |path| 53 File.exist?(path) 54 } 55 if path 56 [OSX::NSBundle.bundleWithPath(path), path] 57 end 58 end 59 end 60 module_function :_bundle_path_for_framework 61 62 # The OSX::require_framework method imports Mac OS X frameworks and uses the 63 # BridgeSupport metadata to add Ruby entry points for the framework's Classes, 64 # methods, and Constants into the OSX module. 65 # 66 # The framework parameter is a reference to the framework that should be 67 # imported. This may be a full path name to a particular framework, a shortcut, 68 # or a framework name. The shortcuts are the keys listed in the 69 # <tt>QUICK_FRAMEWORKS</tt> hash. 70 # 71 # If a framework name (with no path) is given, then the method searches a number 72 # of directories. Those directories (in search order) are: 73 # 1. /System/Library/Frameworks 74 # 2. /Library/Frameworks 75 # 3. Any directories in the RUBYCOCOA_FRAMEWORK_PATHS array, if defined 76 # 4. ENV['HOME']/Library/Frameworks, if the HOME environment variable is defined 77 # 78 # When using the search paths, the <tt>.framework</tt> file type extension should 79 # be omitted from the framework name passed to the method. 80 # 81 # If the method loads the framework successfully, it returns <tt>true</tt>. 82 # If the framework was already loaded the method returns <tt>false</tt>. 83 # If the method is unable to locate, or unable to load the framework then it 84 # raises an <tt>ArgumentError</tt>. 85 def require_framework(framework) 86 return false if framework_loaded?(framework) 87 bundle, path = _bundle_path_for_framework(framework) 88 bundle.oc_load 89 if not bundle.isLoaded? then 90 raise ArgumentError, "Can't load framework '#{framework}'" 91 end 92 load_bridge_support_signatures(path) 93 return true 94 end 95 module_function :require_framework 96 97 def framework_loaded?(framework) 98 bundle, path = _bundle_path_for_framework(framework) 99 unless bundle.nil? 100 loaded = bundle.isLoaded 101 if loaded then 102 load_bridge_support_signatures(path) 103 else 104 # CoreFoundation/Foundation are linked at built-time. 105 id = bundle.bundleIdentifier 106 loaded = (id.isEqualToString('com.apple.CoreFoundation') or 107 id.isEqualToString('com.apple.Foundation')) 108 end 109 loaded 110 else 111 raise ArgumentError, "Can't locate framework '#{framework}'" 112 end 113 end 114 module_function :framework_loaded? 115 116 def __load_bridge_support_file__(dir, framework_name) 117 @bridge_support_signatures_loaded_marks ||= {} 118 return true if @bridge_support_signatures_loaded_marks[framework_name] 119 bs = File.join(dir, framework_name + '.bridgesupport') 120 pbs = File.join(dir, framework_name + 'Private.bridgesupport') 121 if File.exist?(bs) or File.exist?(pbs) 122 # Load the .dylib first (if any). 123 dylib = File.join(dir, framework_name + '.dylib') 124 OSX.load_bridge_support_dylib(dylib) if File.exist?(dylib) 125 126 # Then the regular metadata file. 127 OSX.load_bridge_support_file(bs) if File.exist?(bs) 128 129 # And finally the private metadata file (if any). 130 OSX.load_bridge_support_file(pbs) if File.exist?(pbs) 131 return @bridge_support_signatures_loaded_marks[framework_name] = true 132 end 133 return false 134 end 135 module_function :__load_bridge_support_file__ 136 137 def load_bridge_support_signatures(framework) 138 # First, look into the pre paths. 139 fname = framework[0] == ?/ ? File.basename(framework, '.framework') : framework 140 PRE_SIGN_PATHS.each { |dir| return true if __load_bridge_support_file__(dir, fname) } 141 142 # A path to a framework, let's search for a BridgeSupport file inside the Resources folder. 143 if framework[0] == ?/ 144 path = File.join(framework, 'Resources', 'BridgeSupport') 145 return true if __load_bridge_support_file__(path, fname) 146 framework = fname 147 end 148 149 # Let's try to localize the framework and see if it contains the metadata. 150 FRAMEWORK_PATHS.each do |dir| 151 path = File.join(dir, "#{framework}.framework") 152 if File.exist?(path) 153 path = File.join(path, 'Resources', 'BridgeSupport') 154 return true if __load_bridge_support_file__(path, fname) 155 end 156 end 157 158 # Try the app/bundle specific and RubyCocoa.framework metadata directories. 159 RUBYCOCOA_SIGN_PATHS.each do |path| 160 if File.exist?(path) then 161 return true if __load_bridge_support_file__(path, fname) 162 end 163 end 164 165 # We can still look into the general metadata directories. 166 SIGN_PATHS.each { |dir| return true if __load_bridge_support_file__(dir, fname) } 167 168 # Damnit! 169 warn "Can't find signatures file for #{framework}" if OSX._debug? 170 return false 171 end 172 module_function :load_bridge_support_signatures 173 174 # Load C constants/classes lazily. 175 def self.const_missing(c) 176 begin 177 OSX::import_c_constant(c) 178 rescue LoadError 179 (OSX::ns_import(c) or raise NameError, "uninitialized constant #{c}") 180 end 181 end 182 183 def self.included(m) 184 if m.respond_to? :const_missing 185 m.module_eval do 186 class <<self 187 alias_method :_osx_const_missing_prev, :const_missing 188 def const_missing(c) 189 begin 190 OSX.const_missing(c) 191 rescue NameError 192 _osx_const_missing_prev(c) 193 end 194 end 195 end 196 end 197 else 198 m.module_eval do 199 def self.const_missing(c) 200 OSX.const_missing(c) 201 end 202 end 203 end 204 end 205 206 # Load the foundation frameworks. 207 OSX.load_bridge_support_signatures('CoreFoundation') 208 OSX.load_bridge_support_signatures('Foundation') 209 210 # create Ruby's class for Cocoa class, 211 # then define Constant under module 'OSX'. 212 def ns_import(sym) 213 if not OSX.const_defined?(sym) 214 NSLog("importing #{sym}...") if OSX._debug? 215 klass = if clsobj = NSClassFromString(sym) 216 if rbcls = class_new_for_occlass(clsobj) 217 OSX.const_set(sym, rbcls) 218 end 219 end 220 NSLog("importing #{sym}... done (#{klass.ancestors.join(' -> ')})") if (klass and OSX._debug?) 221 return klass 222 end 223 end 224 module_function :ns_import 225 226 # create Ruby's class for Cocoa class 227 def class_new_for_occlass(occls) 228 superclass = _objc_lookup_superclass(occls) 229 klass = Class.new(superclass) 230 klass.class_eval do 231 if superclass == OSX::ObjcID 232 include OCObjWrapper 233 self.extend OCClsWrapper 234 end 235 @ocid = occls.__ocid__.to_i 236 end 237 if superclass == OSX::ObjcID 238 def klass.__ocid__() @ocid end 239 def klass.to_s() name end 240 def klass.inherited(subklass) subklass.ns_inherited() end 241 end 242 return klass 243 end 244 module_function :class_new_for_occlass 245 246 def _objc_lookup_superclass(occls) 247 occls_superclass = occls.oc_superclass 248 if occls_superclass.nil? or occls_superclass.__ocid__ == occls.__ocid__ 249 OSX::ObjcID 250 elsif occls_superclass.is_a?(OSX::NSProxy) or occls_superclass.__ocid__ == OSX::NSProxy.__ocid__ 251 OSX::NSProxy 252 else 253 begin 254 OSX.const_get(occls_superclass.to_s.to_sym) 255 rescue NameError 256 # some ObjC internal class cannot become Ruby constant 257 # because of prefix '%' or '_' 258 if occls.__ocid__ != occls_superclass.__ocid__ 259 OSX._objc_lookup_superclass(occls_superclass) 260 else 261 OSX::ObjcID # root class of ObjC 262 end 263 end 264 end 265 end 266 module_function :_objc_lookup_superclass 267 268 module NSBehaviorAttachment 269 270 ERRMSG_FOR_RESTRICT_NEW = "use 'alloc.initXXX' to instantiate Cocoa Object" 271 272 # restrict creating an instance by Class#new, unless the Objective-C class 273 # really responds to the new selector. 274 def new 275 if ocm_responds?(:new) 276 objc_send(:new) 277 else 278 raise ERRMSG_FOR_RESTRICT_NEW 279 end 280 end 281 282 # initializer for definition of a derived class of a class on 283 # Objective-C World. 284 def ns_inherited() 285 return if ns_inherited? 286 kls_name = self.name.split('::')[-1] 287 if kls_name 288 spr_name = superclass.name.split('::')[-1] 289 occls = OSX.objc_derived_class_new(self, kls_name, spr_name) 290 self.instance_eval { @ocid = occls.__ocid__.to_i } 291 OSX::BundleSupport.bind_class_with_current_bundle(self) 292 end 293 @inherited = true 294 end 295 296 def ns_inherited? 297 return defined?(@inherited) && @inherited 298 end 299 300 # declare to override instance methods of super class which is 301 # defined by Objective-C. 302 def ns_overrides(*args) 303 warn "#{caller[0]}: ns_overrides is no longer necessary, should not be called anymore and will be removed in a next release. Please update your code to not use it." 304 end 305 alias_method :ns_override, :ns_overrides 306 alias_method :ib_override, :ns_overrides 307 alias_method :ib_overrides, :ns_overrides 308 309 # declare write-only attribute accessors which are named IBOutlet 310 # in the Objective-C world. 311 def ib_outlets(*args) 312 attr_writer(*args) 313 end 314 alias_method :ib_outlet, :ib_outlets 315 316 def ns_outlets(*args) 317 warn "#{caller[0]}:: ns_outlet(s) is deprecated, and will be removed in a next release. Please use ib_outlet(s) instead." 318 ib_outlets(*args) 319 end 320 alias_method :ns_outlet, :ns_outlets 321 322 # declare a IBAction method. if given a block, it mean the 323 # implementation of the action. 324 def ib_action(name, &blk) 325 define_method(name, blk) if block_given? 326 end 327 328 def _ns_behavior_method_added(sym, class_method) 329 return if OSX._ignore_ns_override 330 sel = sym.to_s.gsub(/([^_])_/, '\1:') 331 arity = if defined?(@__imported_arity) and @__imported_arity != nil \ 332 and RUBY_VERSION < "1.8.5" 333 # This is a workaround for a Ruby 1.8.2 issue, the real arity is 334 # provided by _register_method. 335 @__imported_arity 336 else 337 m = class_method ? method(sym) : instance_method(sym) 338 m.arity 339 end 340 sel << ':' if arity > 0 and /[^:]\z/ =~ sel 341 mtype = nil 342 if _ns_enable_override?(sel, class_method) or 343 mtype = OSX.lookup_informal_protocol_method_type(sel, class_method) 344 expected_arity = sel.scan(/:/).length 345 if expected_arity != arity 346 raise RuntimeError, "Cannot override Objective-C method '#{sel}' with Ruby method ##{sym}, they should both have the same number of arguments. (expected arity #{expected_arity}, got #{arity})" 347 end 348 OSX.objc_class_method_add(self, sel, class_method, mtype) 349 end 350 end 351 352 def _ns_enable_override?(sel, class_method) 353 ns_inherited? and (class_method ? self.objc_method_type(sel) : self.objc_instance_method_type(sel)) 354 end 355 356 def _no_param_method?(typefmt) 357 if typefmt[0] == ?{ 358 count = 1 359 i = 0 360 while count > 0 and i = typefmt.index(/[{}]/, i + 1) 361 case typefmt[i] 362 when ?{; count += 1 363 when ?}; count -= 1 364 end 365 end 366 raise ArgumentError, "illegal type encodings" unless i 367 typefmt[i+1..-1] == '@:' 368 else 369 typefmt.index('@:') == typefmt.length - 2 370 end 371 end 372 373 def _objc_export(name, types, class_method) 374 typefmt = _types_to_typefmt(types) 375 name = name.to_s 376 name = name[0].chr << name[1..-1].gsub(/_/, ':') 377 name << ':' if name[-1] != ?: and not _no_param_method?(typefmt) 378 OSX.objc_class_method_add(self, name, class_method, typefmt) 379 end 380 381 def def_objc_method(name, types, &blk) 382 if block_given? then 383 objc_method(name, types, &blk) 384 else 385 raise ArgumentError, "block for method implementation expected" 386 end 387 end 388 389 def objc_method(name, types=['id'], &blk) 390 define_method(name, blk) if block_given? 391 _objc_export(name, types, false) 392 end 393 394 def objc_class_method(name, types=['id']) 395 _objc_export(name, types, true) 396 end 397 398 def objc_export(name, types) 399 warn "#{caller[0]}: objc_export is deprecated, and will be removed in a next release. please use objc_method instead." 400 objc_method(name, types) 401 end 402 403 def objc_alias_method(new, old) 404 new_sel = new.to_s.gsub(/([^_])_/, '\1:') 405 old_sel = old.to_s.gsub(/([^_])_/, '\1:') 406 _objc_alias_method(new, old) 407 end 408 409 def objc_alias_class_method(new, old) 410 new_sel = new.to_s.gsub(/([^_])_/, '\1:') 411 old_sel = old.to_s.gsub(/([^_])_/, '\1:') 412 _objc_alias_class_method(new, old) 413 end 414 415 # TODO: support more types such as pointers... 416 OCTYPES = { 417 :id => '@', 418 :class => '#', 419 :BOOL => 'c', 420 :char => 'c', 421 :uchar => 'C', 422 :short => 's', 423 :ushort => 'S', 424 :int => 'i', 425 :uint => 'I', 426 :long => 'l', 427 :ulong => 'L', 428 :float => 'f', 429 :double => 'd', 430 :bool => 'B', 431 :void => 'v', 432 :selector => ':', 433 :sel => ':', 434 :longlong => 'q', 435 :ulonglong => 'Q', 436 :cstr => '*', 437 } 438 def _types_to_typefmt(types) 439 return types.strip if types.is_a?(String) 440 raise ArgumentError, "Array or String (as type format) expected (got #{types.klass} instead)" unless types.is_a?(Array) 441 raise ArgumentError, "Given types array should have at least an element" unless types.size > 0 442 octypes = types.map do |type| 443 if type.is_a?(Class) and type.ancestors.include?(OSX::Boxed) 444 type.encoding 445 else 446 type = type.strip.intern unless type.is_a?(Symbol) 447 octype = OCTYPES[type] 448 raise "Invalid type (got '#{type}', expected one of : #{OCTYPES.keys.join(', ')}, or a boxed class)" if octype.nil? 449 octype 450 end 451 end 452 octypes[0] + '@:' + octypes[1..-1].join 453 end 454 455 end # module OSX::NSBehaviorAttachment 456 457 module NSKVCAccessorUtil 458 private 459 460 def kvc_internal_setter(key) 461 return '_kvc_internal_' + key.to_s + '=' 462 end 463 464 def kvc_setter_wrapper(key) 465 return '_kvc_wrapper_' + key.to_s + '=' 466 end 467 end # module OSX::NSKVCAccessorUtil 468 469 module NSKeyValueCodingAttachment 470 include NSKVCAccessorUtil 471 472 # invoked from valueForUndefinedKey: of a Cocoa object 473 def rbValueForKey(key) 474 if m = kvc_getter_method(key.to_s) 475 return send(m) 476 else 477 kvc_accessor_notfound(key) 478 end 479 end 480 481 # invoked from setValue:forUndefinedKey: of a Cocoa object 482 def rbSetValue_forKey(value, key) 483 if m = kvc_setter_method(key.to_s) 484 send(m, value) 485 else 486 kvc_accessor_notfound(key) 487 end 488 end 489 490 private 491 492 # find accesor for key-value coding 493 # "key" must be a ruby string 494 495 def kvc_getter_method(key) 496 [key, key + '?'].each do |m| 497 return m if respond_to? m 498 end 499 return nil # accessor not found 500 end 501 502 def kvc_setter_method(key) 503 [kvc_internal_setter(key), key + '='].each do |m| 504 return m if respond_to? m 505 end 506 return nil 507 end 508 509 def kvc_accessor_notfound(key) 510 fmt = '%s: this class is not key value coding-compliant for the key "%s"' 511 raise sprintf(fmt, self.class, key.to_s) 512 end 513 514 end # module OSX::NSKeyValueCodingAttachment 515 516 module NSKVCBehaviorAttachment 517 include NSKVCAccessorUtil 518 519 def kvc_reader(*args) 520 attr_reader(*args) 521 end 522 523 def kvc_writer(*args) 524 args.flatten.each do |key| 525 next if method_defined?(kvc_setter_wrapper(key)) 526 setter = key.to_s + '=' 527 attr_writer(key) unless method_defined?(setter) 528 alias_method kvc_internal_setter(key), setter 529 self.class_eval <<-EOE_KVC_WRITER,__FILE__,__LINE__+1 530 def #{kvc_setter_wrapper(key)}(value) 531 if self.class.automaticallyNotifiesObserversForKey('#{key.to_s}') 532 willChangeValueForKey('#{key.to_s}') 533 send('#{kvc_internal_setter(key)}', value) 534 didChangeValueForKey('#{key.to_s}') 535 else 536 send('#{kvc_internal_setter(key)}', value) 537 end 538 end 539 EOE_KVC_WRITER 540 alias_method setter, kvc_setter_wrapper(key) 541 end 542 end 543 544 def kvc_accessor(*args) 545 kvc_reader(*args) 546 kvc_writer(*args) 547 end 548 549 def kvc_depends_on(keys, *dependencies) 550 dependencies.flatten.each do |dependentKey| 551 setKeys_triggerChangeNotificationsForDependentKey(Array(keys), dependentKey) 552 end 553 end 554 555 # define accesor for keys defined in Cocoa, 556 # such as NSUserDefaultsController and NSManagedObject 557 def kvc_wrapper(*keys) 558 kvc_wrapper_reader(*keys) 559 kvc_wrapper_writer(*keys) 560 end 561 562 def kvc_wrapper_reader(*keys) 563 keys.flatten.compact.each do |key| 564 class_eval <<-EOE_KVC_WRAPPER,__FILE__,__LINE__+1 565 def #{key} 566 valueForKey("#{key}") 567 end 568 EOE_KVC_WRAPPER 569 end 570 end 571 572 def kvc_wrapper_writer(*keys) 573 keys.flatten.compact.each do |key| 574 class_eval <<-EOE_KVC_WRAPPER,__FILE__,__LINE__+1 575 def #{key}=(val) 576 setValue_forKey(val, "#{key}") 577 end 578 EOE_KVC_WRAPPER 579 end 580 end 581 582 # Define accessors that send change notifications for an array. 583 # The array instance variable must respond to the following methods: 584 # 585 # length 586 # [index] 587 # [index]= 588 # insert(index,obj) 589 # delete_at(index) 590 # 591 # Notifications are only sent for accesses through the Cocoa methods: 592 # countOfKey, objectInKeyAtIndex_, insertObject_inKeyAtIndex_, 593 # removeObjectFromKeyAtIndex_, replaceObjectInKeyAtIndex_withObject_ 594 # 595 def kvc_array_accessor(*args) 596 args.each do |key| 597 keyname = key.to_s 598 keyname[0..0] = keyname[0..0].upcase 599 self.addRubyMethod_withType("countOf#{keyname}".to_sym, "i4@8:12") 600 self.addRubyMethod_withType("objectIn#{keyname}AtIndex:".to_sym, "@4@8:12i16") 601 self.addRubyMethod_withType("insertObject:in#{keyname}AtIndex:".to_sym, "@4@8:12@16i20") 602 self.addRubyMethod_withType("removeObjectFrom#{keyname}AtIndex:".to_sym, "@4@8:12i16") 603 self.addRubyMethod_withType("replaceObjectIn#{keyname}AtIndex:withObject:".to_sym, "@4@8:12i16@20") 604 # get%s:range: - unimplemented. You can implement this method for performance improvements. 605 self.class_eval <<-EOT,__FILE__,__LINE__+1 606 def countOf#{keyname}() 607 @#{key.to_s}.length 608 end 609 610 def objectIn#{keyname}AtIndex(index) 611 @#{key.to_s}[index] 612 end 613 614 def insertObject_in#{keyname}AtIndex(obj, index) 615 indexes = OSX::NSIndexSet.indexSetWithIndex(index) 616 if self.class.automaticallyNotifiesObserversForKey('#{key.to_s}') 617 willChange_valuesAtIndexes_forKey(OSX::NSKeyValueChangeInsertion, indexes, #{key.inspect}) 618 @#{key.to_s}.insert(index, obj) 619 didChange_valuesAtIndexes_forKey(OSX::NSKeyValueChangeInsertion, indexes, #{key.inspect}) 620 else 621 @#{key.to_s}.insert(index, obj) 622 end 623 nil 624 end 625 626 def removeObjectFrom#{keyname}AtIndex(index) 627 indexes = OSX::NSIndexSet.indexSetWithIndex(index) 628 if self.class.automaticallyNotifiesObserversForKey('#{key.to_s}') 629 willChange_valuesAtIndexes_forKey(OSX::NSKeyValueChangeRemoval, indexes, #{key.inspect}) 630 @#{key.to_s}.delete_at(index) 631 didChange_valuesAtIndexes_forKey(OSX::NSKeyValueChangeRemoval, indexes, #{key.inspect}) 632 else 633 @#{key.to_s}.delete_at(index) 634 end 635 nil 636 end 637 638 def replaceObjectIn#{keyname}AtIndex_withObject(index, obj) 639 indexes = OSX::NSIndexSet.indexSetWithIndex(index) 640 if self.class.automaticallyNotifiesObserversForKey('#{key.to_s}') 641 willChange_valuesAtIndexes_forKey(OSX::NSKeyValueChangeReplacement, indexes, #{key.inspect}) 642 @#{key.to_s}[index] = obj 643 didChange_valuesAtIndexes_forKey(OSX::NSKeyValueChangeReplacement, indexes, #{key.inspect}) 644 else 645 @#{key.to_s}[index] = obj 646 end 647 nil 648 end 649 EOT 650 end 651 end 652 653 # re-wrap at overriding setter method 654 def _kvc_behavior_method_added(sym) 655 return unless sym.to_s =~ /\A([^=]+)=\z/ 656 key = $1 657 setter = kvc_internal_setter(key) 658 wrapper = kvc_setter_wrapper(key) 659 return unless method_defined?(setter) && method_defined?(wrapper) 660 return if instance_method(wrapper) == instance_method(sym) 661 alias_method setter, sym 662 alias_method sym, wrapper 663 end 664 665 end # module OSX::NSKVCBehaviorAttachment 666 667 module OCObjWrapper 668 669 include NSKeyValueCodingAttachment 670 671 end 672 673 module OCClsWrapper 674 675 include OCObjWrapper 676 include NSBehaviorAttachment 677 include NSKVCBehaviorAttachment 678 679 def singleton_method_added(sym) 680 _ns_behavior_method_added(sym, true) 681 end 682 683 def method_added(sym) 684 _ns_behavior_method_added(sym, false) 685 _kvc_behavior_method_added(sym) 686 end 687 688 end 689 690end # module OSX 691 692# The following code defines a new subclass of Object (Ruby's). 693# 694# module OSX 695# class NSCocoaClass end 696# end 697# 698# This Object.inherited() replaces the subclass of Object class by 699# a Cocoa class from # OSX.ns_import. 700# 701class Object 702 class <<self 703 def _real_class_and_mod(klass) 704 unless klass.ancestors.include?(OSX::Boxed) 705 klassname = klass.name.to_s 706 unless klassname.nil? || klassname.empty? 707 if Object.included_modules.include?(OSX) and /::/.match(klassname).nil? 708 [klassname, Object] 709 elsif klassname[0..4] == 'OSX::' and (tokens = klassname.split(/::/)).size == 2 and klass.superclass != OSX::Boxed 710 [tokens[1], OSX] 711 end 712 end 713 end 714 end 715 716 alias _before_osx_inherited inherited 717 def inherited(subklass) 718 nsklassname, mod = _real_class_and_mod(subklass) 719 if nsklassname and (first_char = nsklassname[0]) >= ?A and first_char <= ?Z 720 # remove Ruby's class 721 mod.instance_eval { remove_const nsklassname.intern } 722 begin 723 klass = OSX.ns_import nsklassname.intern 724 raise NameError if klass.nil? 725 subklass = klass 726 rescue NameError 727 # redefine subclass (looks not a Cocoa class) 728 mod.const_set(nsklassname, subklass) 729 end 730 end 731 _before_osx_inherited(subklass) 732 end 733 734 def _register_method(sym, class_method) 735 if self != Object 736 nsklassname, mod = _real_class_and_mod(self) 737 if nsklassname 738 begin 739 nsklass = OSX.const_get(nsklassname) 740 raise NameError unless nsklass.ancestors.include?(OSX::NSObject) 741 if class_method 742 method = self.method(sym).unbind 743 OSX.__rebind_umethod__(nsklass.class, method) 744 nsklass.module_eval do 745 @__imported_arity = method.arity 746 (class << self; self; end).instance_eval do 747 define_method(sym, method) 748 end 749 @__imported_arity = nil 750 end 751 else 752 method = self.instance_method(sym) 753 OSX.__rebind_umethod__(nsklass, method) 754 nsklass.module_eval do 755 @__imported_arity = method.arity 756 define_method(sym, method) 757 @__imported_arity = nil 758 end 759 end 760 rescue NameError 761 end 762 end 763 end 764 end 765 766 alias _before_method_added method_added 767 def method_added(sym) 768 _register_method(sym, false) 769 _before_method_added(sym) 770 end 771 772 alias _before_singleton_method_added singleton_method_added 773 def singleton_method_added(sym) 774 _register_method(sym, true) 775 _before_singleton_method_added(sym) 776 end 777 778 def method_missing(symbol, *args) 779 nsklassname, mod = _real_class_and_mod(self) 780 if nsklassname 781 begin 782 nsklass = OSX.const_get(nsklassname) 783 if nsklass.respond_to?(symbol) 784 return nsklass.send(symbol, *args) 785 end 786 rescue NameError 787 end 788 end 789 raise NoMethodError, "undefined method `#{symbol.to_s}' for #{self}" 790 end 791 end 792end 793