1/*
2 * Copyright (c) 2006-2008, The RubyCocoa Project.
3 * Copyright (c) 2001-2006, FUJIMOTO Hisakuni.
4 * All Rights Reserved.
5 *
6 * RubyCocoa is free software, covered under either the Ruby's license or the
7 * LGPL. See the COPYRIGHT file for more information.
8 */
9
10#import "mdl_osxobjc.h"
11#import "osx_ruby.h"
12#import <Foundation/Foundation.h>
13#import "RubyCocoa.h"
14#import "Version.h"
15#import "RBThreadSwitcher.h"
16#import "RBObject.h"
17#import "RBClassUtils.h"
18#import "ocdata_conv.h"
19#import <mach-o/dyld.h>
20#import <string.h>
21#import "BridgeSupport.h"
22#import <objc/objc-runtime.h>
23#import "cls_objcid.h"
24#import "objc_compat.h"
25#import "OverrideMixin.h"
26#import "internal_macros.h"
27
28#define OSX_MODULE_NAME "OSX"
29
30static VALUE _cOCObject = Qnil;
31ID _relaxed_syntax_ID;
32
33static VALUE init_module_OSX()
34{
35  VALUE module;
36  RB_ID id_osx = rb_intern(OSX_MODULE_NAME);
37
38  if (rb_const_defined(rb_cObject, id_osx))
39    module = rb_const_get(rb_cObject, id_osx);
40  else
41    module = rb_define_module(OSX_MODULE_NAME);
42  return module;
43}
44
45static VALUE init_cls_OCObject(VALUE mOSX)
46{
47  VALUE kObjcID;
48  VALUE kOCObject;
49  VALUE mOCObjWrapper;
50
51  kObjcID = rb_const_get(mOSX, rb_intern("ObjcID"));
52  kOCObject = rb_define_class_under(mOSX, "OCObject", kObjcID);
53  mOCObjWrapper = rb_const_get(mOSX, rb_intern("OCObjWrapper"));
54  rb_include_module(kOCObject, mOCObjWrapper);
55
56  return kOCObject;
57}
58
59// def OSX.objc_proxy_class_new (kls, kls_name)
60// ex1.  OSX.objc_proxy_class_new (AA::BB::AppController, "AppController")
61static VALUE
62osx_mf_objc_proxy_class_new(VALUE mdl, VALUE kls, VALUE kls_name)
63{
64  kls_name = rb_obj_as_string(kls_name);
65  RBObjcClassNew(kls, StringValuePtr(kls_name), [RBObject class]);
66  return Qnil;
67}
68
69// def OSX.objc_derived_class_new (kls, kls_name, super_name)
70// ex1.  OSX.objc_derived_class_new (AA::BB::CustomView, "CustomView", "NSView")
71static VALUE
72osx_mf_objc_derived_class_new(VALUE mdl, VALUE kls, VALUE kls_name, VALUE super_name)
73{
74  Class super_class;
75  Class new_cls = nil;
76
77  kls_name = rb_obj_as_string(kls_name);
78  super_name = rb_obj_as_string(super_name);
79  super_class = objc_getClass(StringValuePtr(super_name));
80  if (super_class)
81    new_cls = RBObjcDerivedClassNew(kls, StringValuePtr(kls_name), super_class);
82
83  if (new_cls)
84    return ocobj_s_new(new_cls);
85  return Qnil;
86}
87
88// def OSX.objc_class_method_add (kls, method_name)
89// ex1.  OSX.objc_class_method_add (AA::BB::CustomView, "drawRect:")
90static VALUE
91osx_mf_objc_class_method_add(VALUE mdl, VALUE kls, VALUE method_name, VALUE class_method, VALUE types)
92{
93  Class a_class;
94  SEL a_sel;
95  const char *kls_name;
96  BOOL direct_override;
97
98  method_name = rb_obj_as_string(method_name);
99  a_sel = sel_registerName(StringValuePtr(method_name));
100  if (a_sel == NULL)
101    return Qnil;
102  kls_name = rb_class2name(kls);
103  if (strncmp(kls_name, "OSX::", 5) == 0
104      && (a_class = objc_lookUpClass(kls_name + 5)) != NULL
105      && !is_objc_derived_class(kls)) {
106    // override in the current class
107    direct_override = YES;
108  }
109  else {
110    // override in the super class
111    a_class = RBObjcClassFromRubyClass(kls);
112    direct_override = NO;
113  }
114  if (a_class != NULL) {
115    id rcv;
116
117    rcv = RTEST(class_method) ? a_class->isa : a_class;
118    if (NIL_P(types))
119      ovmix_register_ruby_method(rcv, a_sel, direct_override);
120    else
121      [rcv addRubyMethod:a_sel withType:StringValuePtr(types)];
122  }
123  return Qnil;
124}
125
126static VALUE
127osx_mf_ruby_thread_switcher_start(int argc, VALUE* argv, VALUE mdl)
128{
129  volatile VALUE arg_interval, arg_wait;
130  double interval, wait;
131
132  rb_scan_args(argc, argv, "02", &arg_interval, &arg_wait);
133
134  if (arg_interval == Qnil) {
135    [RBThreadSwitcher start];
136  }
137  else {
138    Check_Type(arg_interval, T_FLOAT);
139    interval = RFLOAT(arg_interval)->value;
140
141    if (arg_wait == Qnil) {
142      [RBThreadSwitcher start: interval];
143    }
144    else {
145      Check_Type(arg_wait, T_FLOAT);
146      wait = RFLOAT(arg_wait)->value;
147      [RBThreadSwitcher start: interval wait: wait];
148    }
149  }
150  return Qnil;
151}
152
153static VALUE
154osx_mf_ruby_thread_switcher_stop(VALUE mdl)
155{
156  [RBThreadSwitcher stop];
157  return Qnil;
158}
159
160static VALUE
161ns_autorelease_pool(VALUE mdl)
162{
163  id pool = [[NSAutoreleasePool alloc] init];
164  rb_yield(Qnil);
165  [pool release];
166  return Qnil;
167}
168
169static void
170thread_switcher_start()
171{
172  [RBThreadSwitcher start];
173}
174
175/******************/
176
177VALUE
178rb_osx_class_const (const char* name)
179{
180  VALUE mOSX;
181  VALUE constant;
182  ID name_id;
183
184  if (strlen(name) == 0)
185    return Qnil;
186
187  mOSX = osx_s_module();
188  if (NIL_P(mOSX))
189    return Qnil;
190
191  name_id = rb_intern(name);
192  if (!rb_is_const_id(name_id)) {
193    // If the class name can't be a constant, let's use the superclass name.
194    Class klass = objc_getClass(name);
195    if (klass != NULL) {
196      Class superklass = class_getSuperclass(klass);
197      if (superklass != NULL)
198        return rb_osx_class_const(class_getName(superklass));
199    }
200
201    return Qnil;
202  }
203
204  // Get the class constant, triggering an import if necessary.
205  // Don't import the class if we are called within NSClassFromString, just return the constant
206  // if it exists (otherwise it would cause an infinite loop).
207  if (rb_const_defined(mOSX, name_id)) {
208    constant = rb_const_get(mOSX, name_id);
209  }
210  else if (current_function == NULL || strcmp(current_function->name, "NSClassFromString") != 0) {
211    constant = rb_funcall(mOSX, rb_intern("ns_import"), 1, rb_str_new2(name));
212  }
213  else {
214    constant = Qnil;
215  }
216
217  return constant;
218}
219
220VALUE
221ocobj_s_class (void)
222{
223  return _cOCObject;
224}
225
226VALUE
227rb_cls_ocobj (const char* name)
228{
229  VALUE cls = rb_osx_class_const(name);
230  if (cls == Qnil)
231    cls = _cOCObject;
232  return cls;
233}
234
235static id
236rb_obj_ocid(VALUE rcv)
237{
238  VALUE val = rb_funcall(rcv, rb_intern("__ocid__"), 0);
239  return NUM2OCID(val);
240}
241
242static VALUE
243osx_mf_objc_symbol_to_obj(VALUE mdl, VALUE const_name, VALUE const_type)
244{
245  rb_raise(rb_eRuntimeError, "#objc_symbol_to_obj has been obsoleted");
246  return Qnil;
247}
248
249/***/
250
251VALUE
252osx_s_module()
253{
254  RB_ID rid;
255
256  rid = rb_intern("OSX");
257  if (! rb_const_defined(rb_cObject, rid))
258    return rb_define_module("OSX");
259  return rb_const_get(rb_cObject, rid);
260}
261
262VALUE
263ocobj_s_new_with_class_name(id ocid, const char *cls_name)
264{
265  // Try to determine from the metadata if a given NSCFType object cannot be promoted to a better class.
266  if (strcmp(cls_name, "NSCFType") == 0) {
267    struct bsCFType *bs_cf_type;
268
269    bs_cf_type = find_bs_cf_type_by_type_id(CFGetTypeID((CFTypeRef)ocid));
270    if (bs_cf_type != NULL)
271      cls_name = bs_cf_type->bridged_class_name;
272  }
273
274  return objcid_new_with_ocid(rb_cls_ocobj(cls_name), ocid);
275}
276
277VALUE
278ocobj_s_new(id ocid)
279{
280  return ocobj_s_new_with_class_name(ocid, object_getClassName(ocid));
281}
282
283id
284rbobj_get_ocid (VALUE obj)
285{
286  RB_ID mtd;
287
288  if (rb_obj_is_kind_of(obj, objid_s_class()) == Qtrue)
289    return OBJCID_ID(obj);
290
291  mtd = rb_intern("__ocid__");
292  if (rb_respond_to(obj, mtd))
293    return rb_obj_ocid(obj);
294
295#if 0
296  if (rb_respond_to(obj, rb_intern("to_nsobj"))) {
297    VALUE nso = rb_funcall(obj, rb_intern("to_nsobj"), 0);
298    return rb_obj_ocid(nso);
299  }
300#endif
301
302  return nil;
303}
304
305VALUE
306ocid_get_rbobj (id ocid)
307{
308  VALUE result = Qnil;
309
310  @try {
311    if (!IS_UNDOPROXY(ocid)
312        && (([ocid isProxy] && [ocid isRBObject])
313        || [ocid respondsToSelector:@selector(__rbobj__)]))
314      result = [ocid __rbobj__];
315  }
316  @catch (id exception) {}
317
318  return result;
319}
320
321// FIXME: this is a silly hack.
322
323struct RB_METHOD {
324  VALUE klass, rklass;
325  // ...
326};
327
328static VALUE
329osx_mf_rebind_umethod(VALUE rcv, VALUE klass, VALUE umethod)
330{
331  struct RB_METHOD *data;
332
333  Data_Get_Struct(umethod, struct RB_METHOD, data);
334  data->rklass = klass;
335
336  return Qnil;
337}
338
339static VALUE
340osx_rbobj_to_nsobj (VALUE rcv, VALUE obj)
341{
342  id ocid, pool;
343  VALUE val;
344
345  pool = [[NSAutoreleasePool alloc] init];
346  if (!rbobj_to_nsobj(obj, &ocid) || ocid == nil) {
347    [pool release];
348    return Qnil;
349  }
350
351  val = ocid_to_rbobj(Qnil, ocid);
352  [ocid retain];
353  OBJCID_DATA_PTR(val)->retained = YES;
354  OBJCID_DATA_PTR(val)->can_be_released = YES;
355
356  [pool release];
357
358  return val;
359}
360
361NSThread *rubycocoaThread;
362NSRunLoop *rubycocoaRunLoop;
363
364/******************/
365
366void initialize_mdl_osxobjc()
367{
368  char* framework_resources_path();
369  VALUE mOSX;
370
371  mOSX = init_module_OSX();
372  init_cls_ObjcPtr(mOSX);
373  init_cls_ObjcID(mOSX);
374  init_mdl_OCObjWrapper(mOSX);
375  _cOCObject = init_cls_OCObject(mOSX);
376
377  _relaxed_syntax_ID = rb_intern("@relaxed_syntax");
378  rb_ivar_set(mOSX, _relaxed_syntax_ID, Qtrue);
379
380  rb_define_module_function(mOSX, "objc_proxy_class_new",
381			    osx_mf_objc_proxy_class_new, 2);
382  rb_define_module_function(mOSX, "objc_derived_class_new",
383			    osx_mf_objc_derived_class_new, 3);
384  rb_define_module_function(mOSX, "objc_class_method_add",
385			    osx_mf_objc_class_method_add, 4);
386
387  rb_define_module_function(mOSX, "ruby_thread_switcher_start",
388			    osx_mf_ruby_thread_switcher_start, -1);
389  rb_define_module_function(mOSX, "ruby_thread_switcher_stop",
390			    osx_mf_ruby_thread_switcher_stop, 0);
391
392  rb_define_module_function(mOSX, "ns_autorelease_pool",
393			    ns_autorelease_pool, 0);
394
395  rb_define_const(mOSX, "RUBYCOCOA_VERSION",
396		  rb_obj_freeze(rb_str_new2(RUBYCOCOA_VERSION)));
397  rb_define_const(mOSX, "RUBYCOCOA_RELEASE_DATE",
398		  rb_obj_freeze(rb_str_new2(RUBYCOCOA_RELEASE_DATE)));
399  rb_define_const(mOSX, "RUBYCOCOA_SVN_REVISION",
400		  rb_obj_freeze(rb_str_new2(RUBYCOCOA_SVN_REVISION)));
401#if __LP64__
402  rb_define_const(mOSX, "RUBYCOCOA_BUILD_LP64", Qtrue);
403#else
404  rb_define_const(mOSX, "RUBYCOCOA_BUILD_LP64", Qfalse);
405#endif
406
407  char *p = framework_resources_path();
408  rb_define_const(mOSX, "RUBYCOCOA_RESOURCES_PATH",
409		  rb_obj_freeze(rb_str_new2(p)));
410  free(p);
411
412  rb_define_const(mOSX, "RUBYCOCOA_SIGN_PATHS", rb_ary_new());
413  rb_define_const(mOSX, "RUBYCOCOA_FRAMEWORK_PATHS", rb_ary_new());
414
415  rb_define_module_function(mOSX, "objc_symbol_to_obj", osx_mf_objc_symbol_to_obj, 2);
416
417  rb_define_module_function(mOSX, "__rebind_umethod__", osx_mf_rebind_umethod, 2);
418
419  rb_define_module_function(mOSX, "rbobj_to_nsobj", osx_rbobj_to_nsobj, 1);
420
421  thread_switcher_start();
422
423  initialize_bridge_support(mOSX);
424
425  rubycocoaThread = [NSThread currentThread];
426  rubycocoaRunLoop = [NSRunLoop currentRunLoop];
427}
428