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