| 1 |
// |
| 2 |
// MDRubyDialog.m |
| 3 |
// Alchemusica |
| 4 |
// |
| 5 |
// Created by Toshi Nagata on 08/04/13. |
| 6 |
// Copyright 2008-2011 __MyCompanyName__. All rights reserved. |
| 7 |
// |
| 8 |
|
| 9 |
#import <Cocoa/Cocoa.h> |
| 10 |
|
| 11 |
#include "MDRuby.h" |
| 12 |
|
| 13 |
static VALUE |
| 14 |
sTextSymbol, sTextFieldSymbol, sRadioSymbol, sButtonSymbol, |
| 15 |
sCheckBoxSymbol, sPopUpSymbol, sTextViewSymbol, sViewSymbol, |
| 16 |
sTagSymbol, sTypeSymbol, |
| 17 |
sTitleSymbol, sXSymbol, sYSymbol, sWidthSymbol, sHeightSymbol, |
| 18 |
sOriginSymbol, sSizeSymbol, sFrameSymbol, |
| 19 |
sEnabledSymbol, sHiddenSymbol, sValueSymbol, |
| 20 |
sBlockSymbol, sRangeSymbol; |
| 21 |
|
| 22 |
VALUE cMRDialog = Qfalse; |
| 23 |
|
| 24 |
@implementation MDRubyDialogController |
| 25 |
|
| 26 |
- (void)windowDidLoad |
| 27 |
{ |
| 28 |
[super windowDidLoad]; |
| 29 |
ditems = [[NSMutableArray array] retain]; |
| 30 |
[ditems addObject: [[[self window] contentView] viewWithTag: 0]]; /* OK button */ |
| 31 |
[ditems addObject: [[[self window] contentView] viewWithTag: 1]]; /* Cancel button */ |
| 32 |
} |
| 33 |
|
| 34 |
- (void)dealloc |
| 35 |
{ |
| 36 |
[ditems release]; |
| 37 |
[super dealloc]; |
| 38 |
} |
| 39 |
|
| 40 |
- (void)dialogItemAction: (id)sender |
| 41 |
{ |
| 42 |
int tag = [self searchDialogItem: sender]; |
| 43 |
if (tag == 0) /* OK button */ |
| 44 |
[NSApp stopModal]; |
| 45 |
else if (tag == 1) /* Cancel button */ |
| 46 |
[NSApp abortModal]; |
| 47 |
} |
| 48 |
|
| 49 |
- (void)setRubyObject: (VALUE)val |
| 50 |
{ |
| 51 |
dval = val; |
| 52 |
} |
| 53 |
|
| 54 |
- (void)addDialogItem: (id)ditem |
| 55 |
{ |
| 56 |
[[[self window] contentView] addSubview: ditem]; |
| 57 |
[ditems addObject: ditem]; |
| 58 |
} |
| 59 |
|
| 60 |
- (id)dialogItemAtIndex: (int)index |
| 61 |
{ |
| 62 |
if (index >= 0 && index < [ditems count]) |
| 63 |
return [ditems objectAtIndex: index]; |
| 64 |
else return nil; |
| 65 |
} |
| 66 |
|
| 67 |
- (int)searchDialogItem: (id)ditem |
| 68 |
{ |
| 69 |
unsigned int ui = [ditems indexOfObjectIdenticalTo: ditem]; |
| 70 |
if (ui == NSNotFound) |
| 71 |
return -1; |
| 72 |
else return ui; |
| 73 |
} |
| 74 |
|
| 75 |
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor |
| 76 |
{ |
| 77 |
VALUE items, item, val, val_min, val_max; |
| 78 |
int nitems, itag; |
| 79 |
NSString *s; |
| 80 |
|
| 81 |
if (dval == Qfalse) |
| 82 |
return NO; |
| 83 |
|
| 84 |
s = [control stringValue]; |
| 85 |
items = rb_iv_get(dval, "_items"); |
| 86 |
nitems = RARRAY_LEN(items); |
| 87 |
itag = [control tag]; |
| 88 |
if (itag < 0 || itag >= nitems) |
| 89 |
return YES; /* Accept anything */ |
| 90 |
|
| 91 |
item = (RARRAY_PTR(items))[itag]; |
| 92 |
val = rb_hash_aref(item, sRangeSymbol); |
| 93 |
if (NIL_P(val)) |
| 94 |
return YES; /* Accept anything */ |
| 95 |
|
| 96 |
val_min = MDRuby_ObjectAtIndex(val, 0); |
| 97 |
val_max = MDRuby_ObjectAtIndex(val, 1); |
| 98 |
if (FIXNUM_P(val_min) && FIXNUM_P(val_max)) { |
| 99 |
int ival = [s intValue]; |
| 100 |
int imin = NUM2INT(val_min); |
| 101 |
int imax = NUM2INT(val_max); |
| 102 |
if (ival < imin || ival > imax) |
| 103 |
return NO; |
| 104 |
[control setStringValue: [NSString stringWithFormat: @"%d", ival]]; |
| 105 |
} else { |
| 106 |
double d = [s doubleValue]; |
| 107 |
double dmin = NUM2DBL(val_min); |
| 108 |
double dmax = NUM2DBL(val_max); |
| 109 |
if (d < dmin || d > dmax) |
| 110 |
return NO; |
| 111 |
} |
| 112 |
return YES; |
| 113 |
} |
| 114 |
|
| 115 |
@end |
| 116 |
|
| 117 |
#if 0 |
| 118 |
/* Internal class for adding tags to a generic view */ |
| 119 |
@interface MDRubyTaggedView : NSView { |
| 120 |
int tag; |
| 121 |
} |
| 122 |
- (void)setTag: (int)tag; |
| 123 |
- (int)tag; |
| 124 |
@end |
| 125 |
|
| 126 |
@implementation MDRubyTaggedView |
| 127 |
- (void)setTag: (int)aTag |
| 128 |
{ |
| 129 |
tag = aTag; |
| 130 |
} |
| 131 |
- (int)tag |
| 132 |
{ |
| 133 |
return tag; |
| 134 |
} |
| 135 |
@end; |
| 136 |
#endif |
| 137 |
|
| 138 |
#pragma mark ====== MRDialog alloc/init/release ====== |
| 139 |
|
| 140 |
typedef struct MRDialogInfo { |
| 141 |
MDRubyDialogController *d; |
| 142 |
} MRDialogInfo; |
| 143 |
|
| 144 |
static MDRubyDialogController * |
| 145 |
s_MRDialog_GetController(VALUE self) |
| 146 |
{ |
| 147 |
MRDialogInfo *di; |
| 148 |
Data_Get_Struct(self, MRDialogInfo, di); |
| 149 |
if (di != NULL) |
| 150 |
return di->d; |
| 151 |
else return NULL; |
| 152 |
} |
| 153 |
|
| 154 |
static void |
| 155 |
s_MRDialog_Release(void *p) |
| 156 |
{ |
| 157 |
if (p != NULL) { |
| 158 |
MDRubyDialogController *d = ((MRDialogInfo *)p)->d; |
| 159 |
if (d != nil) { |
| 160 |
[d setRubyObject: Qfalse]; /* Stop access to the Ruby object (in case the MDRubyDialogController is not dealloc'ed in the following line) */ |
| 161 |
[d release]; |
| 162 |
((MRDialogInfo *)p)->d = nil; |
| 163 |
} |
| 164 |
free(p); |
| 165 |
} |
| 166 |
} |
| 167 |
|
| 168 |
static VALUE |
| 169 |
s_MRDialog_Alloc(VALUE klass) |
| 170 |
{ |
| 171 |
VALUE val; |
| 172 |
MRDialogInfo *di; |
| 173 |
MDRubyDialogController *d = [[MDRubyDialogController alloc] initWithWindowNibName: @"RubyDialog"]; |
| 174 |
val = Data_Make_Struct(klass, MRDialogInfo, 0, s_MRDialog_Release, di); |
| 175 |
di->d = d; |
| 176 |
[d setRubyObject: val]; |
| 177 |
return val; |
| 178 |
} |
| 179 |
|
| 180 |
static VALUE |
| 181 |
s_MRDialog_Initialize(int argc, VALUE *argv, VALUE self) |
| 182 |
{ |
| 183 |
int i, j; |
| 184 |
VALUE val1; |
| 185 |
VALUE items; |
| 186 |
MDRubyDialogController *d = s_MRDialog_GetController(self); |
| 187 |
|
| 188 |
[d window]; // Load window |
| 189 |
|
| 190 |
rb_scan_args(argc, argv, "01", &val1); |
| 191 |
if (!NIL_P(val1)) { |
| 192 |
char *p = StringValuePtr(val1); |
| 193 |
[[d window] setTitle: [NSString stringWithUTF8String: p]]; |
| 194 |
} |
| 195 |
|
| 196 |
// Array of item informations |
| 197 |
items = rb_ary_new(); |
| 198 |
|
| 199 |
// Initialize "OK" and "Cancel" buttons (may be used for enabling/disabling buttons) |
| 200 |
for (i = 0; i < 2; i++) { |
| 201 |
VALUE item, vals[14]; |
| 202 |
id button = [[[d window] contentView] viewWithTag: i]; |
| 203 |
NSString *title = [button title]; |
| 204 |
NSRect frame = [button frame]; |
| 205 |
vals[0] = sTagSymbol; |
| 206 |
vals[1] = rb_str_new2(i == 0 ? "ok" : "cancel"); |
| 207 |
vals[2] = sTypeSymbol; |
| 208 |
vals[3] = sButtonSymbol; |
| 209 |
vals[4] = sTitleSymbol; |
| 210 |
vals[5] = rb_str_new2([title UTF8String]); |
| 211 |
vals[6] = sXSymbol; |
| 212 |
vals[7] = rb_float_new(frame.origin.x); |
| 213 |
vals[8] = sYSymbol; |
| 214 |
vals[9] = rb_float_new(frame.origin.y); |
| 215 |
vals[10] = sWidthSymbol; |
| 216 |
vals[11] = rb_float_new(frame.size.width); |
| 217 |
vals[12] = sHeightSymbol; |
| 218 |
vals[13] = rb_float_new(frame.size.height); |
| 219 |
item = rb_hash_new(); |
| 220 |
for (j = 0; j < 7; j++) { |
| 221 |
rb_hash_aset(item, vals[j * 2], vals[j * 2 + 1]); |
| 222 |
} |
| 223 |
rb_ary_push(items, item); |
| 224 |
} |
| 225 |
|
| 226 |
rb_iv_set(self, "_items", items); |
| 227 |
|
| 228 |
return Qnil; |
| 229 |
} |
| 230 |
|
| 231 |
#pragma mark ====== Ruby methods ====== |
| 232 |
|
| 233 |
static int |
| 234 |
s_MRDialog_ItemIndexForTag(VALUE self, VALUE tag) |
| 235 |
{ |
| 236 |
VALUE items = rb_iv_get(self, "_items"); |
| 237 |
int len = RARRAY_LEN(items); |
| 238 |
VALUE *ptr = RARRAY_PTR(items); |
| 239 |
int i; |
| 240 |
if (FIXNUM_P(tag) && (i = NUM2INT(tag)) >= 0 && i < len) |
| 241 |
return i; |
| 242 |
for (i = 0; i < len; i++) { |
| 243 |
if (rb_equal(tag, rb_hash_aref(ptr[i], sTagSymbol)) == Qtrue) |
| 244 |
return i; |
| 245 |
} |
| 246 |
rb_raise(rb_eStandardError, "MRDialog has no item with tag %s", StringValuePtr(tag)); |
| 247 |
return -1; /* Not reached */ |
| 248 |
} |
| 249 |
|
| 250 |
static NSString * |
| 251 |
s_CleanUpNewLine(NSString *s) |
| 252 |
{ |
| 253 |
/* Convert various "newline" characters in an NSString to "\n" */ |
| 254 |
unsigned int start, end, contentsEnd; |
| 255 |
NSRange range; |
| 256 |
NSMutableString *ms = [NSMutableString stringWithString: s]; |
| 257 |
start = [ms length]; |
| 258 |
while (start > 0) { |
| 259 |
/* Get the "last line" */ |
| 260 |
range.location = start - 1; |
| 261 |
range.length = 1; |
| 262 |
[ms getLineStart: &start end: &end contentsEnd: &contentsEnd forRange: range]; |
| 263 |
if (contentsEnd < end) { |
| 264 |
/* Replace the EOL characters with @"\n" */ |
| 265 |
[ms replaceCharactersInRange: NSMakeRange(contentsEnd, end - contentsEnd) withString: @"\n"]; |
| 266 |
} |
| 267 |
} |
| 268 |
return ms; |
| 269 |
} |
| 270 |
|
| 271 |
/* |
| 272 |
* call-seq: |
| 273 |
* dialog.set_attr(tag, hash) |
| 274 |
* |
| 275 |
* Set the attributes given in the hash. |
| 276 |
*/ |
| 277 |
static VALUE |
| 278 |
s_MRDialog_SetAttr(VALUE self, VALUE tag, VALUE hash) |
| 279 |
{ |
| 280 |
int i; |
| 281 |
VALUE items = rb_iv_get(self, "_items"); |
| 282 |
VALUE *ptr = RARRAY_PTR(items); |
| 283 |
int itag = s_MRDialog_ItemIndexForTag(self, tag); |
| 284 |
VALUE item = ptr[itag]; |
| 285 |
VALUE type = rb_hash_aref(item, sTypeSymbol); |
| 286 |
MDRubyDialogController *d = s_MRDialog_GetController(self); |
| 287 |
id view = [d dialogItemAtIndex: itag]; |
| 288 |
VALUE keys = rb_funcall(hash, rb_intern("keys"), 0); |
| 289 |
int klen = RARRAY_LEN(keys); |
| 290 |
VALUE *kptr = RARRAY_PTR(keys); |
| 291 |
|
| 292 |
for (i = 0; i < klen; i++) { |
| 293 |
VALUE key = kptr[i]; |
| 294 |
VALUE val = rb_hash_aref(hash, key); |
| 295 |
BOOL flag; |
| 296 |
NSString *s; |
| 297 |
if (key == sRangeSymbol) { |
| 298 |
/* Range of value (must be an array of two integers or two floats) */ |
| 299 |
VALUE val1, val2; |
| 300 |
double d1, d2; |
| 301 |
if (TYPE(val) != T_ARRAY || RARRAY_LEN(val) != 2) |
| 302 |
rb_raise(rb_eTypeError, "the attribute 'range' should specify an array of two numbers"); |
| 303 |
val1 = RARRAY_PTR(val)[0]; |
| 304 |
val2 = RARRAY_PTR(val)[1]; |
| 305 |
d1 = NUM2DBL(val1); |
| 306 |
d2 = NUM2DBL(val2); |
| 307 |
if (!FIXNUM_P(val1) || !FIXNUM_P(val2)) { |
| 308 |
/* Convert to a range of floats */ |
| 309 |
if (TYPE(val1) != T_FLOAT || TYPE(val2) != T_FLOAT) { |
| 310 |
val1 = rb_float_new(NUM2DBL(val1)); |
| 311 |
val2 = rb_float_new(NUM2DBL(val2)); |
| 312 |
val = rb_ary_new3(2, val1, val2); |
| 313 |
} |
| 314 |
} |
| 315 |
if (d1 > d2) |
| 316 |
rb_raise(rb_eArgError, "invalid number range [%g,%g]", d1, d2); |
| 317 |
rb_hash_aset(item, key, val); |
| 318 |
} else if (key == sValueSymbol) { |
| 319 |
/* Value */ |
| 320 |
if (type == sTextFieldSymbol || type == sTextViewSymbol) { |
| 321 |
s = [NSString stringWithUTF8String: StringValuePtr(val)]; |
| 322 |
if (type == sTextFieldSymbol) |
| 323 |
[view setStringValue: s]; |
| 324 |
else |
| 325 |
[[view documentView] setString: s]; |
| 326 |
} else if (type == sCheckBoxSymbol) { |
| 327 |
[view setState: (RTEST(val) ? NSOnState : NSOffState)]; |
| 328 |
} |
| 329 |
} else if (key == sTitleSymbol) { |
| 330 |
/* Title */ |
| 331 |
s = [NSString stringWithUTF8String: StringValuePtr(val)]; |
| 332 |
if (type == sTextSymbol) |
| 333 |
[view setStringValue: s]; |
| 334 |
else [view setTitle: s]; |
| 335 |
} else if (key == sEnabledSymbol) { |
| 336 |
/* Enabled */ |
| 337 |
flag = (val != Qnil && val != Qfalse); |
| 338 |
if (type == sTextViewSymbol) |
| 339 |
[[view documentView] setEditable: flag]; |
| 340 |
else |
| 341 |
[view setEnabled: flag]; |
| 342 |
} else if (key == sHiddenSymbol) { |
| 343 |
/* Hidden */ |
| 344 |
flag = (val != Qnil && val != Qfalse); |
| 345 |
[view setHidden: flag]; |
| 346 |
} else if (key == sXSymbol || key == sYSymbol || key == sWidthSymbol || key == sHeightSymbol) { |
| 347 |
/* Frame components */ |
| 348 |
NSRect frame; |
| 349 |
float f = NUM2DBL(val); |
| 350 |
frame = [view frame]; |
| 351 |
if (key == sXSymbol) |
| 352 |
frame.origin.x = f; |
| 353 |
else if (key == sYSymbol) |
| 354 |
frame.origin.y = f; |
| 355 |
else if (key == sWidthSymbol) |
| 356 |
frame.size.width = f; |
| 357 |
else |
| 358 |
frame.size.height = f; |
| 359 |
[view setFrame: frame]; |
| 360 |
} else if (key == sOriginSymbol || key == sSizeSymbol) { |
| 361 |
/* Frame components */ |
| 362 |
NSRect frame; |
| 363 |
float f0 = NUM2DBL(MDRuby_ObjectAtIndex(val, 0)); |
| 364 |
float f1 = NUM2DBL(MDRuby_ObjectAtIndex(val, 1)); |
| 365 |
frame = [view frame]; |
| 366 |
if (key == sOriginSymbol) { |
| 367 |
frame.origin.x = f0; |
| 368 |
frame.origin.y = f1; |
| 369 |
} else { |
| 370 |
frame.size.width = f0; |
| 371 |
frame.size.height = f1; |
| 372 |
} |
| 373 |
[view setFrame: frame]; |
| 374 |
} else if (key == sFrameSymbol) { |
| 375 |
/* Frame (x, y, width, height) */ |
| 376 |
NSRect frame; |
| 377 |
frame.origin.x = NUM2DBL(MDRuby_ObjectAtIndex(val, 0)); |
| 378 |
frame.origin.y = NUM2DBL(MDRuby_ObjectAtIndex(val, 1)); |
| 379 |
frame.size.width = NUM2DBL(MDRuby_ObjectAtIndex(val, 2)); |
| 380 |
frame.size.height = NUM2DBL(MDRuby_ObjectAtIndex(val, 3)); |
| 381 |
[view setFrame: frame]; |
| 382 |
} else { |
| 383 |
rb_hash_aset(item, key, val); |
| 384 |
} |
| 385 |
} |
| 386 |
return Qnil; |
| 387 |
} |
| 388 |
|
| 389 |
/* |
| 390 |
* call-seq: |
| 391 |
* dialog.attr(tag, key) |
| 392 |
* |
| 393 |
* Get the attribute for the key. |
| 394 |
*/ |
| 395 |
static VALUE |
| 396 |
s_MRDialog_Attr(VALUE self, VALUE tag, VALUE key) |
| 397 |
{ |
| 398 |
BOOL flag; |
| 399 |
VALUE items = rb_iv_get(self, "_items"); |
| 400 |
VALUE *ptr = RARRAY_PTR(items); |
| 401 |
int itag = s_MRDialog_ItemIndexForTag(self, tag); |
| 402 |
VALUE item = ptr[itag]; |
| 403 |
VALUE type = rb_hash_aref(item, sTypeSymbol); |
| 404 |
MDRubyDialogController *d = s_MRDialog_GetController(self); |
| 405 |
id view = [d dialogItemAtIndex: itag]; |
| 406 |
|
| 407 |
VALUE val = Qnil; |
| 408 |
NSString *s; |
| 409 |
if (key == sValueSymbol) { |
| 410 |
/* Value */ |
| 411 |
if (type == sTextFieldSymbol) { |
| 412 |
/* Is range specified? */ |
| 413 |
VALUE range = rb_hash_aref(item, sRangeSymbol); |
| 414 |
s = [view stringValue]; |
| 415 |
if (TYPE(range) == T_ARRAY) { |
| 416 |
if (FIXNUM_P((RARRAY_PTR(range))[0])) |
| 417 |
val = INT2NUM([s intValue]); |
| 418 |
else |
| 419 |
val = rb_float_new([s doubleValue]); |
| 420 |
} else val = rb_str_new2([s UTF8String]); |
| 421 |
} else if (type == sTextViewSymbol) { |
| 422 |
s = [[view documentView] string]; |
| 423 |
s = s_CleanUpNewLine(s); |
| 424 |
val = rb_str_new2([s UTF8String]); |
| 425 |
} else if (type == sCheckBoxSymbol) { |
| 426 |
val = ([view state] == NSOnState ? Qtrue : Qfalse); |
| 427 |
} |
| 428 |
} else if (key == sTitleSymbol) { |
| 429 |
if (type == sTextSymbol) |
| 430 |
s = [view stringValue]; |
| 431 |
else s = [view title]; |
| 432 |
val = rb_str_new2([s UTF8String]); |
| 433 |
} else if (key == sEnabledSymbol) { |
| 434 |
/* Enabled */ |
| 435 |
if (type == sTextViewSymbol) |
| 436 |
flag = [[view documentView] isEditable]; |
| 437 |
else |
| 438 |
flag = [view isEnabled]; |
| 439 |
val = (flag ? Qtrue : Qfalse); |
| 440 |
} else if (key == sHiddenSymbol) { |
| 441 |
/* Hidden */ |
| 442 |
val = ([view isHiddenOrHasHiddenAncestor] ? Qtrue : Qfalse); |
| 443 |
} else if (key == sXSymbol || key == sYSymbol || key == sWidthSymbol || key == sHeightSymbol) { |
| 444 |
/* Frame components */ |
| 445 |
NSRect frame; |
| 446 |
float f; |
| 447 |
frame = [view frame]; |
| 448 |
if (key == sXSymbol) |
| 449 |
f = frame.origin.x; |
| 450 |
else if (key == sYSymbol) |
| 451 |
f = frame.origin.y; |
| 452 |
else if (key == sWidthSymbol) |
| 453 |
f = frame.size.width; |
| 454 |
else |
| 455 |
f = frame.size.height; |
| 456 |
val = rb_float_new(f); |
| 457 |
} else if (key == sOriginSymbol || key == sSizeSymbol) { |
| 458 |
/* Frame components */ |
| 459 |
NSRect frame; |
| 460 |
float f0, f1; |
| 461 |
frame = [view frame]; |
| 462 |
if (key == sOriginSymbol) { |
| 463 |
f0 = frame.origin.x; |
| 464 |
f1 = frame.origin.y; |
| 465 |
} else { |
| 466 |
f0 = frame.size.width; |
| 467 |
f1 = frame.size.height; |
| 468 |
} |
| 469 |
val = rb_ary_new3(2, rb_float_new(f0), rb_float_new(f1)); |
| 470 |
rb_obj_freeze(val); |
| 471 |
} else if (key == sFrameSymbol) { |
| 472 |
/* Frame (x, y, width, height) */ |
| 473 |
NSRect frame = [view frame]; |
| 474 |
val = rb_ary_new3(4, rb_float_new(frame.origin.x), rb_float_new(frame.origin.y), rb_float_new(frame.size.width), rb_float_new(frame.size.height)); |
| 475 |
rb_obj_freeze(val); |
| 476 |
} else { |
| 477 |
val = rb_hash_aref(item, key); |
| 478 |
} |
| 479 |
|
| 480 |
return val; |
| 481 |
} |
| 482 |
|
| 483 |
/* |
| 484 |
* call-seq: |
| 485 |
* dialog.run |
| 486 |
* |
| 487 |
* Run the modal session for this dialog. |
| 488 |
*/ |
| 489 |
static VALUE |
| 490 |
s_MRDialog_Run(VALUE self) |
| 491 |
{ |
| 492 |
int retval; |
| 493 |
MDRubyDialogController *d = s_MRDialog_GetController(self); |
| 494 |
|
| 495 |
retval = [NSApp runModalForWindow: [d window]]; |
| 496 |
[d close]; |
| 497 |
if (retval == NSRunStoppedResponse) { |
| 498 |
VALUE items = rb_iv_get(self, "_items"); |
| 499 |
int len = RARRAY_LEN(items); |
| 500 |
VALUE *ptr = RARRAY_PTR(items); |
| 501 |
VALUE hash = rb_hash_new(); |
| 502 |
int i; |
| 503 |
/* Get values for editable controls */ |
| 504 |
for (i = 0; i < len; i++) { |
| 505 |
// VALUE type = rb_hash_aref(ptr[i], sTypeSymbol); |
| 506 |
VALUE tag = rb_hash_aref(ptr[i], sTagSymbol); |
| 507 |
VALUE val; |
| 508 |
if (NIL_P(tag)) |
| 509 |
continue; |
| 510 |
val = s_MRDialog_Attr(self, tag, sValueSymbol); |
| 511 |
rb_hash_aset(hash, tag, val); |
| 512 |
} |
| 513 |
return hash; |
| 514 |
} else |
| 515 |
return Qfalse; |
| 516 |
} |
| 517 |
|
| 518 |
/* |
| 519 |
* call-seq: |
| 520 |
* dialog.layout(row, column, i11, ..., i1c, i21, ..., i2c, ..., ir1, ..., irc, options) => integer |
| 521 |
* |
| 522 |
* Layout items in a table. |
| 523 |
* Returns an integer that represents the NSView that wraps the items. |
| 524 |
*/ |
| 525 |
static VALUE |
| 526 |
s_MRDialog_Layout(int argc, VALUE *argv, VALUE self) |
| 527 |
{ |
| 528 |
VALUE rval, cval, nhash, items; |
| 529 |
int row, col, i, j, n, itag, nitems; |
| 530 |
MDRubyDialogController *d; |
| 531 |
float *widths, *heights; |
| 532 |
float f, fmin; |
| 533 |
NSSize *sizes; |
| 534 |
NSView *contentView; |
| 535 |
NSView *layoutView; |
| 536 |
NSSize contentMinSize; |
| 537 |
NSRect layoutFrame; |
| 538 |
float col_padding = 4.0; /* Padding between columns */ |
| 539 |
float row_padding = 4.0; /* Padding between rows */ |
| 540 |
float margin = 15.0; |
| 541 |
|
| 542 |
d = s_MRDialog_GetController(self); |
| 543 |
contentView = [[d window] contentView]; |
| 544 |
contentMinSize = [[d window] contentMinSize]; |
| 545 |
items = rb_iv_get(self, "_items"); |
| 546 |
nitems = RARRAY_LEN(items); |
| 547 |
|
| 548 |
if (argc < 2) |
| 549 |
rb_raise(rb_eArgError, "wrong number of arguments (should be at least 2 but only %d given)", argc); |
| 550 |
|
| 551 |
rval = argv[0]; |
| 552 |
cval = argv[1]; |
| 553 |
row = NUM2INT(rval); |
| 554 |
col = NUM2INT(cval); |
| 555 |
if (row <= 0) |
| 556 |
rb_raise(rb_eRangeError, "number of rows (%d) must be a positive integer", row); |
| 557 |
if (col <= 0) |
| 558 |
rb_raise(rb_eRangeError, "number of columns (%d) must be a positive integer", col); |
| 559 |
|
| 560 |
/* Allocate temporary storage */ |
| 561 |
sizes = (NSSize *)calloc(sizeof(NSSize), row * col); |
| 562 |
widths = (float *)calloc(sizeof(float), col); |
| 563 |
heights = (float *)calloc(sizeof(float), row); |
| 564 |
if (sizes == NULL || widths == NULL || heights == NULL) |
| 565 |
rb_raise(rb_eNoMemError, "out of memory during layout"); |
| 566 |
|
| 567 |
/* Get frame sizes */ |
| 568 |
for (i = 0; i < row; i++) { |
| 569 |
for (j = 0; j < col; j++) { |
| 570 |
n = 2 + i * col + j; |
| 571 |
if (n < argc && FIXNUM_P(argv[n])) { |
| 572 |
itag = FIX2INT(argv[n]); |
| 573 |
if (itag >= nitems) |
| 574 |
rb_raise(rb_eRangeError, "item tag (%d) is out of range (should be 0..%d)", itag, nitems - 1); |
| 575 |
sizes[n - 2] = [[d dialogItemAtIndex: itag] frame].size; |
| 576 |
} |
| 577 |
} |
| 578 |
} |
| 579 |
|
| 580 |
/* Calculate required widths */ |
| 581 |
fmin = 0.0; |
| 582 |
for (j = 0; j < col; j++) { |
| 583 |
for (i = 0; i < row; i++) { |
| 584 |
for (n = j; n >= 0; n--) { |
| 585 |
f = sizes[i * col + n].width; |
| 586 |
if (f > 0.0) { |
| 587 |
f += (n > 0 ? widths[n - 1] : 0.0); |
| 588 |
break; |
| 589 |
} |
| 590 |
} |
| 591 |
if (fmin < f) |
| 592 |
fmin = f; |
| 593 |
} |
| 594 |
fmin += col_padding; |
| 595 |
widths[j] = fmin; |
| 596 |
} |
| 597 |
|
| 598 |
/* Calculate required heights */ |
| 599 |
fmin = 0.0; |
| 600 |
for (i = 0; i < row; i++) { |
| 601 |
for (j = 0; j < col; j++) { |
| 602 |
for (n = i; n >= 0; n--) { |
| 603 |
f = sizes[n * col + j].height; |
| 604 |
if (f > 0.0) { |
| 605 |
f += (n > 0 ? heights[n - 1] : 0.0); |
| 606 |
break; |
| 607 |
} |
| 608 |
} |
| 609 |
if (fmin < f) |
| 610 |
fmin = f; |
| 611 |
} |
| 612 |
fmin += row_padding; |
| 613 |
heights[i] = fmin; |
| 614 |
} |
| 615 |
|
| 616 |
/* Create a layout view */ |
| 617 |
layoutFrame.size.width = widths[col - 1]; |
| 618 |
layoutFrame.size.height = heights[row - 1]; |
| 619 |
layoutFrame.origin.x = margin; |
| 620 |
layoutFrame.origin.y = contentMinSize.height; |
| 621 |
layoutView = [[[NSView alloc] initWithFrame: layoutFrame] autorelease]; |
| 622 |
|
| 623 |
/* Move the subviews into the layout view */ |
| 624 |
for (i = 0; i < row; i++) { |
| 625 |
for (j = 0; j < col; j++) { |
| 626 |
n = 2 + i * col + j; |
| 627 |
if (n < argc && FIXNUM_P(argv[n]) && (itag = FIX2INT(argv[n])) < nitems) { |
| 628 |
NSPoint pt; |
| 629 |
NSView *subview = [d dialogItemAtIndex: itag]; |
| 630 |
float offset; |
| 631 |
VALUE type = rb_hash_aref((RARRAY_PTR(items))[itag], sTypeSymbol); |
| 632 |
if (type == sTextSymbol) |
| 633 |
offset = 3.0; |
| 634 |
else offset = 0.0; |
| 635 |
pt.x = (j > 0 ? widths[j - 1] : 0.0) + col_padding * 0.5; |
| 636 |
pt.y = layoutFrame.size.height - (i > 0 ? heights[i - 1] : 0.0) - sizes[n - 2].height - row_padding * 0.5 - offset; |
| 637 |
[subview retain]; |
| 638 |
[subview removeFromSuperview]; |
| 639 |
[layoutView addSubview: subview]; |
| 640 |
[subview setFrameOrigin: pt]; |
| 641 |
[subview release]; |
| 642 |
} |
| 643 |
} |
| 644 |
} |
| 645 |
|
| 646 |
free(sizes); |
| 647 |
free(widths); |
| 648 |
free(heights); |
| 649 |
|
| 650 |
/* Create a new hash for the layout view and push to _items */ |
| 651 |
nhash = rb_hash_new(); |
| 652 |
rb_hash_aset(nhash, sTypeSymbol, sViewSymbol); |
| 653 |
rb_ary_push(items, nhash); |
| 654 |
|
| 655 |
/* Tag for the layout view */ |
| 656 |
itag = RARRAY_LEN(items) - 1; |
| 657 |
|
| 658 |
/* Resize the window */ |
| 659 |
{ |
| 660 |
NSSize winSize; |
| 661 |
winSize.width = layoutFrame.size.width + margin * 2; |
| 662 |
if (winSize.width < contentMinSize.width) |
| 663 |
winSize.width = contentMinSize.width; |
| 664 |
winSize.height = layoutFrame.size.height + contentMinSize.height + margin; |
| 665 |
[[d window] setContentSize: winSize]; |
| 666 |
} |
| 667 |
|
| 668 |
/* Add to the window */ |
| 669 |
[d addDialogItem: layoutView]; |
| 670 |
|
| 671 |
/* Returns the integer tag */ |
| 672 |
return INT2NUM(itag); |
| 673 |
} |
| 674 |
|
| 675 |
/* |
| 676 |
* call-seq: |
| 677 |
* dialog.item(type, hash) => integer |
| 678 |
* |
| 679 |
* Create a dialog item. |
| 680 |
* type: one of the following symbols; :text, :textfield, :radio, :checkbox, :popup |
| 681 |
* hash: attributes that can be set by set_attr |
| 682 |
* Returns an integer that represents the item. (0 and 1 are reserved for "OK" and "Cancel") |
| 683 |
*/ |
| 684 |
static VALUE |
| 685 |
s_MRDialog_Item(int argc, VALUE *argv, VALUE self) |
| 686 |
{ |
| 687 |
int itag; /* Integer tag for NSControl */ |
| 688 |
id control; /* A view */ |
| 689 |
NSRect rect, brect; |
| 690 |
NSString *title; |
| 691 |
NSDictionary *attr; |
| 692 |
NSFont *font; |
| 693 |
VALUE type, hash, val, nhash, items; |
| 694 |
MDRubyDialogController *d; |
| 695 |
|
| 696 |
d = s_MRDialog_GetController(self); |
| 697 |
rb_scan_args(argc, argv, "11", &type, &hash); |
| 698 |
if (NIL_P(hash)) |
| 699 |
hash = rb_hash_new(); |
| 700 |
rect.size.width = rect.size.height = 1.0; |
| 701 |
rect.origin.x = rect.origin.y = 0.0; |
| 702 |
|
| 703 |
val = rb_hash_aref(hash, sTitleSymbol); |
| 704 |
if (!NIL_P(val)) { |
| 705 |
title = [NSString stringWithUTF8String: StringValuePtr(val)]; |
| 706 |
} else { |
| 707 |
title = @""; |
| 708 |
} |
| 709 |
|
| 710 |
Check_Type(type, T_SYMBOL); |
| 711 |
|
| 712 |
if (type == sTextViewSymbol) |
| 713 |
font = [NSFont userFixedPitchFontOfSize: 0]; |
| 714 |
else |
| 715 |
font = [NSFont systemFontOfSize: [NSFont smallSystemFontSize]]; |
| 716 |
attr = [NSDictionary dictionaryWithObjectsAndKeys: font, NSFontAttributeName, nil]; |
| 717 |
brect.origin.x = brect.origin.y = 0.0; |
| 718 |
brect.size = [title sizeWithAttributes: attr]; |
| 719 |
brect.size.width += 8; |
| 720 |
|
| 721 |
/* Set rect if specified */ |
| 722 |
/* val = rb_hash_aref(hash, sXSymbol); |
| 723 |
if (!NIL_P(val) && (dval = NUM2DBL(val)) > 0.0) |
| 724 |
rect.origin.x = dval; |
| 725 |
val = rb_hash_aref(hash, sYSymbol); |
| 726 |
if (!NIL_P(val) && (dval = NUM2DBL(val)) > 0.0) |
| 727 |
rect.origin.y = dval; |
| 728 |
val = rb_hash_aref(hash, sWidthSymbol); |
| 729 |
if (!NIL_P(val) && (dval = NUM2DBL(val)) > 0.0) |
| 730 |
rect.size.width = dval; |
| 731 |
val = rb_hash_aref(hash, sHeightSymbol); |
| 732 |
if (!NIL_P(val) && (dval = NUM2DBL(val)) > 0.0) |
| 733 |
rect.size.height = dval; */ |
| 734 |
|
| 735 |
/* Create a new hash for this item */ |
| 736 |
nhash = rb_hash_new(); |
| 737 |
rb_hash_aset(nhash, sTypeSymbol, type); |
| 738 |
|
| 739 |
if (type == sTextSymbol || type == sTextFieldSymbol) { |
| 740 |
if (rect.size.height == 1.0) |
| 741 |
rect.size.height = brect.size.height; |
| 742 |
if (rect.size.width == 1.0) |
| 743 |
rect.size.width = brect.size.width; |
| 744 |
if (type == sTextFieldSymbol) |
| 745 |
rect.size.height += 5.0; |
| 746 |
control = [[[NSTextField alloc] initWithFrame: rect] autorelease]; |
| 747 |
[control setStringValue: title]; |
| 748 |
[control setFont: font]; |
| 749 |
[control setDelegate: d]; |
| 750 |
if (type == sTextSymbol) { |
| 751 |
[control setEditable: NO]; |
| 752 |
[control setBezeled: NO]; |
| 753 |
[control setBordered: NO]; |
| 754 |
[control setDrawsBackground: NO]; |
| 755 |
} else { |
| 756 |
[control setEditable: YES]; |
| 757 |
[control setBezeled: YES]; |
| 758 |
[control setDrawsBackground: YES]; |
| 759 |
} |
| 760 |
} else if (type == sTextViewSymbol) { |
| 761 |
/* An NSTextView included within an NSScrollView */ |
| 762 |
NSTextView *tv; |
| 763 |
NSSize contentSize; |
| 764 |
if (rect.size.height == 1.0) |
| 765 |
rect.size.height = brect.size.height; |
| 766 |
if (rect.size.width == 1.0) |
| 767 |
rect.size.width = 90; |
| 768 |
control = [[[NSScrollView alloc] initWithFrame: rect] autorelease]; |
| 769 |
[control setHasVerticalScroller: YES]; |
| 770 |
[control setHasHorizontalScroller: NO]; |
| 771 |
[control setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable]; |
| 772 |
[control setBorderType: NSBezelBorder]; |
| 773 |
[[control verticalScroller] setControlSize: NSSmallControlSize]; |
| 774 |
contentSize = [control contentSize]; |
| 775 |
tv = [[[NSTextView alloc] initWithFrame: NSMakeRect(0, 0, contentSize.width, contentSize.height)] autorelease]; |
| 776 |
[tv setMinSize: NSMakeSize(0.0, contentSize.height)]; |
| 777 |
[tv setMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)]; |
| 778 |
[tv setVerticallyResizable: YES]; |
| 779 |
[tv setHorizontallyResizable: NO]; |
| 780 |
[tv setAutoresizingMask: NSViewWidthSizable]; |
| 781 |
[[tv textContainer] setContainerSize: NSMakeSize(contentSize.width, FLT_MAX)]; |
| 782 |
[[tv textContainer] setWidthTracksTextView: YES]; |
| 783 |
[tv setFont: font]; |
| 784 |
// [control setDelegate: d]; |
| 785 |
[tv setRichText: NO]; |
| 786 |
[tv setSelectable: YES]; |
| 787 |
[tv setEditable: YES]; |
| 788 |
[control setDocumentView: tv]; |
| 789 |
} else if (type == sCheckBoxSymbol) { |
| 790 |
if (rect.size.height == 1.0) |
| 791 |
rect.size.height = 14; |
| 792 |
if (rect.size.width == 1.0) |
| 793 |
rect.size.width = brect.size.width + 20; |
| 794 |
control = [[[NSButton alloc] initWithFrame: rect] autorelease]; |
| 795 |
[control setButtonType: NSSwitchButton]; |
| 796 |
[[control cell] setControlSize: NSSmallControlSize]; |
| 797 |
[control setFont: font]; |
| 798 |
[control setTitle: title]; |
| 799 |
} else { |
| 800 |
rb_raise(rb_eStandardError, "item type :%s is not implemented", rb_id2name(SYM2ID(type))); |
| 801 |
} |
| 802 |
|
| 803 |
/* Push to _items */ |
| 804 |
items = rb_iv_get(self, "_items"); |
| 805 |
rb_ary_push(items, nhash); |
| 806 |
itag = RARRAY_LEN(items) - 1; |
| 807 |
|
| 808 |
/* Add to the window */ |
| 809 |
[d addDialogItem: control]; |
| 810 |
|
| 811 |
/* Tag as a Ruby integer */ |
| 812 |
val = INT2NUM(itag); |
| 813 |
|
| 814 |
/* Set attributes */ |
| 815 |
s_MRDialog_SetAttr(self, val, hash); |
| 816 |
|
| 817 |
return val; |
| 818 |
} |
| 819 |
|
| 820 |
/* |
| 821 |
* call-seq: |
| 822 |
* dialog._items => an array of hash |
| 823 |
* |
| 824 |
* Returns an internal array of items. For debugging use only. |
| 825 |
*/ |
| 826 |
static VALUE |
| 827 |
s_MRDialog_Items(VALUE self) |
| 828 |
{ |
| 829 |
return rb_iv_get(self, "_items"); |
| 830 |
} |
| 831 |
|
| 832 |
#pragma mark ====== Initialize class ====== |
| 833 |
|
| 834 |
void |
| 835 |
MRDialogInitClass(void) |
| 836 |
{ |
| 837 |
if (cMRDialog != Qfalse) |
| 838 |
return; |
| 839 |
|
| 840 |
cMRDialog = rb_define_class("RubyDialog", rb_cObject); |
| 841 |
rb_define_alloc_func(cMRDialog, s_MRDialog_Alloc); |
| 842 |
rb_define_private_method(cMRDialog, "initialize", s_MRDialog_Initialize, -1); |
| 843 |
rb_define_method(cMRDialog, "run", s_MRDialog_Run, 0); |
| 844 |
rb_define_method(cMRDialog, "item", s_MRDialog_Item, -1); |
| 845 |
rb_define_method(cMRDialog, "layout", s_MRDialog_Layout, -1); |
| 846 |
rb_define_method(cMRDialog, "_items", s_MRDialog_Items, 0); |
| 847 |
rb_define_method(cMRDialog, "set_attr", s_MRDialog_SetAttr, 2); |
| 848 |
rb_define_method(cMRDialog, "attr", s_MRDialog_Attr, 2); |
| 849 |
|
| 850 |
{ |
| 851 |
static VALUE *sTable1[] = { &sTextSymbol, &sTextFieldSymbol, &sRadioSymbol, &sButtonSymbol, &sCheckBoxSymbol, &sPopUpSymbol, &sTextViewSymbol, &sViewSymbol, &sTagSymbol, &sTypeSymbol, &sTitleSymbol, &sXSymbol, &sYSymbol, &sWidthSymbol, &sHeightSymbol, &sOriginSymbol, &sSizeSymbol, &sFrameSymbol, &sEnabledSymbol, &sHiddenSymbol, &sValueSymbol, &sBlockSymbol, &sRangeSymbol }; |
| 852 |
static const char *sTable2[] = { "text", "textfield", "radio", "button", "checkbox", "popup", "textview", "view", "tag", "type", "title", "x", "y", "width", "height", "origin", "size", "frame", "enabled", "hidden", "value", "block", "range" }; |
| 853 |
int i; |
| 854 |
for (i = 0; i < sizeof(sTable1) / sizeof(sTable1[0]); i++) |
| 855 |
*(sTable1[i]) = ID2SYM(rb_intern(sTable2[i])); |
| 856 |
} |
| 857 |
} |