1// ADPersonView.m (this is -*- ObjC -*-) 2// 3// \author: Bj�rn Giesler <giesler@ira.uka.de> 4// 5// Address View Framework for GNUstep 6// 7// $Author: rmottola $ 8// $Locker: $ 9// $Revision: 1.7 $ 10// $Date: 2012/03/20 12:03:16 $ 11 12#import "ADPersonView.h" 13#import "ADPersonPropertyView.h" 14#import "ADImageView.h" 15 16NSString * const ADPersonNameChangedNotification = @"ADPersonNameChangedNotification"; 17 18NSString * const ADPeoplePboardType = @"ADPeoplePboardType"; 19 20// redefine _(@"...") so that it looks into our bundle, not the main bundle 21#undef _ 22#define _(x) [[NSBundle bundleForClass: [ADImageView class]] \ 23 localizedStringForKey: x \ 24 value: x \ 25 table: nil] 26 27static NSDictionary *_labelDict, *_isoCodeDict, *_addressLayoutDict; 28static NSImage *_vcfImage; 29 30static NSString *__defaultCountryCode = nil; 31 32@implementation ADPersonView 33+ (void) loadRessources 34{ 35 NSBundle *b; NSString *filename; 36 37 b = [NSBundle bundleForClass: self]; 38 39 filename = [b pathForResource: @"Labels" ofType: @"dict"]; 40 _labelDict = [[NSString stringWithContentsOfFile: filename] propertyList]; 41 NSAssert(_labelDict && [_labelDict isKindOfClass: [NSDictionary class]], 42 @"Labels.dict could not be loaded!\n"); 43 [_labelDict retain]; 44 45 filename = [b pathForResource: @"ISOCodeMapping" ofType: @"dict"]; 46 _isoCodeDict = [[NSString stringWithContentsOfFile: filename] propertyList]; 47 NSAssert(_isoCodeDict && [_isoCodeDict isKindOfClass: [NSDictionary class]], 48 @"ISOCodeMapping.dict could not be loaded!\n"); 49 [_isoCodeDict retain]; 50 51 filename = [b pathForResource: @"AddressLayouts" ofType: @"dict"]; 52 _addressLayoutDict = [[NSString stringWithContentsOfFile: filename] 53 propertyList]; 54 NSAssert(_addressLayoutDict && 55 [_addressLayoutDict isKindOfClass: [NSDictionary class]], 56 @"AddressLayouts.dict could not be loaded!\n"); 57 [_addressLayoutDict retain]; 58 59 filename = [b pathForResource: @"VCFImage" ofType: @"tiff"]; 60 _vcfImage = [[NSImage alloc] initWithContentsOfFile: filename]; 61 NSAssert(_vcfImage && 62 [_vcfImage isKindOfClass: [NSImage class]], 63 @"VCFImage.tiff could not be loaded!\n"); 64} 65 66- initWithFrame: (NSRect) frameRect 67{ 68 NSBundle *b; NSString *filename; 69 [super initWithFrame: frameRect]; 70 71 if(!_labelDict) [[self class] loadRessources]; 72 73 _person = nil; 74 _delegate = nil; 75 _editable = NO; 76 _acceptsDrop = NO; 77 _fontSize = [NSFont systemFontSize]; 78 _displaysImage = YES; 79 _forceImage = NO; 80 81 // load images 82 b = [NSBundle bundleForClass: [self class]]; 83 filename = [b pathForImageResource: @"Lock.tiff"]; 84 _lockImg = [[NSImage alloc] initWithContentsOfFile: filename]; 85 NSAssert(_lockImg, @"Image \"Lock.tiff\" could not be loaded!\n"); 86 filename = [b pathForImageResource: @"Share.tiff"]; 87 _shareImg = [[NSImage alloc] initWithContentsOfFile: filename]; 88 NSAssert(_lockImg, @"Image \"Share.tiff\" could not be loaded!\n"); 89 90 [[NSNotificationCenter defaultCenter] 91 addObserver: self 92 selector: @selector(superviewFrameChanged:) 93 name: NSViewFrameDidChangeNotification 94 object: nil]; 95 96 [self registerForDraggedTypes: [NSArray arrayWithObjects: 97 @"NSVCardPboardType", 98 NSTIFFPboardType, 99 NSFilenamesPboardType, 100 nil]]; 101 return self; 102} 103 104- (void) dealloc 105{ 106 [_person release]; 107 108 [[NSNotificationCenter defaultCenter] removeObserver: self]; 109 [super dealloc]; 110} 111 112- (BOOL) isFlipped 113{ 114 return YES; 115} 116 117- (int) layoutHeaderAndReturnNextY 118{ 119 ADPersonPropertyView *v; 120 NSSize sizeNeeded; 121 float IMGWIDTH = _fontSize*5; 122 float IMGHEIGHT = IMGWIDTH*(3.0/4.0); 123 124 int x = 5; 125 int y = 5; 126 127 if(_forceImage || (_displaysImage && [_person imageDataFile])) 128 { 129 // Image 130 _imageView = [[ADImageView alloc] 131 initWithFrame: NSMakeRect(x, y, IMGWIDTH, IMGHEIGHT)]; 132 [self addSubview: _imageView]; 133 [_imageView setTarget: self]; 134 [_imageView setAction: @selector(imageClicked:)]; 135 [_imageView setPerson: _person]; 136 [_imageView setDelegate: self]; 137 138 x += IMGWIDTH + 10; 139 } 140 else 141 _imageView = nil; 142 143 // First name 144 v = [[ADPersonPropertyView alloc] initWithFrame: NSMakeRect(x, y, 0, 0)]; 145 [v setEditable: _editable]; 146 [v setDelegate: self]; 147 [v setFontSize: _fontSize*1.5]; 148 [v setFont: [v boldFont]]; 149 [v setPerson: _person]; 150 [v setProperty: ADFirstNameProperty]; 151 [self addSubview: v]; 152 sizeNeeded = [v frame].size; 153 sizeNeeded.width += [[v font] widthOfString: @"f"]; 154 sizeNeeded.height += 5; 155 156 // Last name 157 v = [[ADPersonPropertyView alloc] 158 initWithFrame: NSMakeRect(x+sizeNeeded.width, y, 0, 0)]; 159 [v setEditable: _editable]; 160 [v setDelegate: self]; 161 [v setFontSize: _fontSize*1.5]; 162 [v setFont: [v boldFont]]; 163 [v setPerson: _person]; 164 [v setProperty: ADLastNameProperty]; 165 [self addSubview: v]; 166 sizeNeeded.width += [v frame].size.width; 167 sizeNeeded.height = MAX(sizeNeeded.height, [v frame].size.height); 168 169 y = sizeNeeded.height; 170 v = [[ADPersonPropertyView alloc] initWithFrame: NSMakeRect(x, y, 0, 0)]; 171 [v setEditable: _editable]; 172 [v setDelegate: self]; 173 [v setPerson: _person]; 174 [v setProperty: ADOrganizationProperty]; 175 [v setFontSize: _fontSize]; 176 [self addSubview: v]; 177 if([v frame].size.height) 178 sizeNeeded.height += [v frame].size.height; 179 180 // Job title 181 y = sizeNeeded.height; 182 v = [[ADPersonPropertyView alloc] initWithFrame: NSMakeRect(x, y, 0, 0)]; 183 [v setEditable: _editable]; 184 [v setDelegate: self]; 185 [v setPerson: _person]; 186 [v setProperty: ADJobTitleProperty]; 187 [v setFontSize: _fontSize]; 188 [self addSubview: v]; 189 if([v frame].size.height) 190 sizeNeeded.height += [v frame].size.height; 191 192 if(_imageView) 193 _iconY = ([_imageView frame].origin.y + 194 [_imageView frame].size.height + 15); 195 else 196 _iconY = 0; 197 198 return MAX(sizeNeeded.height, _iconY); 199} 200 201- (void) layout 202{ 203 NSEnumerator *e; 204 NSString *property; 205 NSArray *properties; 206 int y; 207 NSRect noteRect; 208 id label; 209 NSString *note; 210 211 properties = [NSArray arrayWithObjects: ADBirthdayProperty, 212 ADHomePageProperty, 213 ADPhoneProperty, 214 ADEmailProperty, 215 ADAddressProperty, 216 ADAIMInstantProperty, 217 nil]; 218 219 if(_person) 220 [self cleanupEmptyProperties]; 221 222 while([[self subviews] count]) 223 [[[self subviews] objectAtIndex: 0] removeFromSuperview]; 224 225 if(!_person) 226 { 227 [self calcSize]; 228 return; 229 } 230 231 y = [self layoutHeaderAndReturnNextY]; 232 233 _headerLineY = y + 7; 234 y += 15; 235 236 e = [properties objectEnumerator]; 237 while((property = [e nextObject])) 238 { 239 ADPersonPropertyView *v; 240 241 v = [[ADPersonPropertyView alloc] initWithFrame: NSMakeRect(5, y, 0, 0)]; 242 [v setEditable: _editable]; 243 [v setDelegate: self]; 244 [v setDisplaysLabel: YES]; 245 [v setPerson: _person]; 246 [v setProperty: property]; 247 [v setFontSize: _fontSize]; 248 [self addSubview: v]; 249 250 if([v frame].size.height) 251 y += [v frame].size.height + 15; 252 } 253 254 _footerLineY = y - 8; 255 256 label = [[[NSTextField alloc] initWithFrame: NSMakeRect(5, y, 100, 100)] 257 autorelease]; 258 [label setStringValue: _(@"Notes:")]; 259 [label setEditable: NO]; [label setSelectable: NO]; 260 [label setBordered: NO]; [label setBezeled: NO]; 261 [label setDrawsBackground: NO]; 262 [label setFont: [NSFont boldSystemFontOfSize: _fontSize]]; 263 264 [label sizeToFit]; 265 [self addSubview: label]; 266 267 noteRect = NSMakeRect(NSMaxX([label frame]) + 5, y, 268 400, 400); 269 _noteView = [[NSTextView alloc] initWithFrame: noteRect]; 270 271 [_noteView setMinSize: NSMakeSize(50, 50)]; 272 [_noteView setMaxSize: NSMakeSize(400, 400)]; 273 [_noteView setVerticallyResizable: YES]; 274 [_noteView setHorizontallyResizable: YES]; 275 [_noteView setDelegate: self]; 276 note = [_person valueForProperty: ADNoteProperty]; 277 if (note != nil) 278 [_noteView setString: note]; 279 [_noteView setFont: [NSFont systemFontOfSize: _fontSize]]; 280 _noteTextChanged = NO; 281 282 if(_editable) 283 { 284 [_noteView setBackgroundColor: 285 [NSColor colorWithDeviceRed: 1.0 green: 1.0 286 blue: .9 alpha: 1.0]]; 287 [_noteView setEditable: YES]; 288 } 289 else 290 [_noteView setEditable: NO]; 291 292 [self addSubview: _noteView]; 293 294 [self calcSize]; 295} 296 297- (BOOL) fillsSuperview 298{ 299 return _fillsSuperview; 300} 301 302- (void) setFillsSuperview: (BOOL) yesno 303{ 304 _fillsSuperview = yesno; 305 [self calcSize]; 306} 307 308- (void) calcSize 309{ 310 NSEnumerator *e; 311 NSView *view; 312 NSSize sizeNeeded; 313 NSSize nvSize, nvMaxSize, nvMinSize; 314 315 if(_fillsSuperview) 316 { 317 sizeNeeded = [[self superview] frame].size; 318 if (sizeNeeded.width > 10) 319 sizeNeeded.width -= 10; 320 if (sizeNeeded.height > 15) 321 sizeNeeded.height -= 15; 322 } 323 else 324 sizeNeeded = NSMakeSize(0, 0); 325 e = [[self subviews] objectEnumerator]; 326 while((view = [e nextObject])) 327 { 328 NSRect r; 329 330 r = [view frame]; 331 332 sizeNeeded.height = r.origin.y + r.size.height; 333 if(view != _noteView) 334 sizeNeeded.width = MAX(sizeNeeded.width, 335 r.origin.x + r.size.width); 336 } 337 sizeNeeded.width += 10; 338 sizeNeeded.height += 15; 339 340 if(_fillsSuperview && [self superview]) 341 { 342 NSSize superSize = [[self superview] frame].size; 343 if(sizeNeeded.height < superSize.height) 344 sizeNeeded.height = superSize.height; 345 if(sizeNeeded.width < superSize.width) 346 sizeNeeded.width = superSize.width; 347 } 348 349 if(_noteView) 350 { 351 nvSize = NSMakeSize(sizeNeeded.width - [_noteView frame].origin.x - 5, 352 [_noteView frame].size.height); 353 nvMinSize = NSMakeSize(nvSize.width, [_noteView minSize].height); 354 nvMaxSize = NSMakeSize(nvSize.width, [_noteView maxSize].height); 355 [_noteView setFrameSize: nvSize]; 356 [_noteView setMinSize: nvMinSize]; 357 [_noteView setMaxSize: nvMaxSize]; 358 } 359 360 [self setFrameSize: sizeNeeded]; 361} 362 363- (void) setPerson: (ADPerson*) aPerson 364{ 365 if(aPerson == _person || aPerson == nil) 366 return; 367 368 [_person release]; 369 _person = [aPerson retain]; 370 371 [self setFrame: NSZeroRect]; 372 [self layout]; 373} 374 375- (ADPerson*) person 376{ 377 return _person; 378} 379 380- (void) setDisplaysImage: (BOOL) yesno 381{ 382 if(yesno == _displaysImage) 383 return; 384 _displaysImage = yesno; 385 if([_person imageData]) 386 [self layout]; 387} 388 389- (BOOL) displaysImage 390{ 391 return _displaysImage; 392} 393 394- (void) setForceImage: (BOOL) yesno 395{ 396 if(yesno == _forceImage) 397 return; 398 _forceImage = yesno; 399 [self layout]; 400} 401 402- (BOOL) forceImage 403{ 404 return _forceImage; 405} 406 407- (void) drawRect: (NSRect) rect 408{ 409 NSBezierPath *p; ADPersonPropertyView *v; NSEnumerator *e; 410 NSRect r; float x; 411 412 [self calcSize]; 413 414 [self lockFocus]; 415 416 if(![self isEditable]) 417 [[NSColor whiteColor] set]; 418 else 419 [[NSColor colorWithDeviceRed: 1.0 green: 1.0 blue: 0.65 alpha: 1.0] set]; 420 NSRectFill(rect); 421 422 if(!_person) 423 { 424 NSPoint p; NSSize s1, s2; 425 426 NSAttributedString *str = [[[NSAttributedString alloc] 427 initWithString: _(@"No Person Selected")] 428 autorelease]; 429 [[NSColor blackColor] set]; 430 431 s1 = [str size]; 432 s2 = [self frame].size; 433 p.x = (s2.width-s1.width)/2; 434 p.y = (s2.height-s1.height)/2; 435 [str drawAtPoint: p]; 436 437 [self unlockFocus]; 438 return; 439 } 440 441 x = 5; 442 443 if([_person readOnly]) 444 { 445 [_lockImg compositeToPoint: NSMakePoint(x, _iconY) 446 operation: NSCompositeCopy]; 447 x += [_lockImg size].width + 2; 448 } 449 450 if([_person shared]) 451 { 452 [_shareImg compositeToPoint: NSMakePoint(x, _iconY) 453 operation: NSCompositeCopy]; 454 x += [_shareImg size].width + 2; 455 } 456 457 if([[_person uniqueId] 458 isEqualToString: [[[_person addressBook] me] uniqueId]]) 459 { 460 NSFont *meFont; 461 NSMutableAttributedString *str; 462 float y; 463 464 meFont = [NSFont boldSystemFontOfSize: 8]; 465 str = [[[NSMutableAttributedString alloc] initWithString: _(@"Me")] 466 autorelease]; 467 468 [str addAttribute: NSFontAttributeName 469 value: meFont 470 range: NSMakeRange(0, [str length])]; 471 472 y = _iconY - [meFont boundingRectForFont].size.height; 473 r = NSMakeRect(x, y, [meFont widthOfString: _(@"Me")], 474 [meFont boundingRectForFont].size.height); 475 476 [str drawInRect: r]; 477 x += r.size.width + 2; 478 } 479 480 481 [[NSColor blackColor] set]; 482 p = [NSBezierPath bezierPath]; 483 [p moveToPoint: NSMakePoint(5, _headerLineY)]; 484 [p lineToPoint: NSMakePoint([self frame].size.width-5, _headerLineY)]; 485 [p stroke]; 486 487 // find last subview that actually displays anything and draw the 488 // line directly underneath it 489 e = [[self subviews] reverseObjectEnumerator]; 490 while((v = [e nextObject])) 491 { 492 if([v respondsToSelector: @selector(hasCells)]) 493 { 494 if([v hasCells]) break; 495 } 496 else 497 break; 498 } 499 r = [_noteView frame]; 500 _footerLineY = r.origin.y - 7; 501 if(_footerLineY > _headerLineY) 502 { 503 p = [NSBezierPath bezierPath]; 504 [p moveToPoint: NSMakePoint(5, _footerLineY)]; 505 [p lineToPoint: NSMakePoint([self frame].size.width-5, _footerLineY)]; 506 [p stroke]; 507 } 508 509 [self unlockFocus]; 510} 511 512- (BOOL) isEditable 513{ 514 return _editable; 515} 516 517- (void) setEditable: (BOOL) yn 518{ 519 if(yn == _editable) 520 return; 521 _editable = yn; 522 523 if(_noteTextChanged) 524 { 525 NSString *note = [_person valueForProperty: ADNoteProperty]; 526 if(note) 527 { 528 if([[_noteView string] isEqualToString: @""]) 529 [_person removeValueForProperty: ADNoteProperty]; 530 else 531 [_person setValue: [_noteView string] forProperty: ADNoteProperty]; 532 } 533 else if(![[_noteView string] isEqualToString: @""]) 534 [_person setValue: [_noteView string] forProperty: ADNoteProperty]; 535 } 536 537 [self layout]; 538} 539 540- (void) beginEditingInFirstCell 541{ 542 NSArray *subs; 543 544 if(!_editable) [self setEditable: YES]; 545 546 subs = [self subviews]; 547 _editingViewIndex = 0; 548 549 while(![[subs objectAtIndex: _editingViewIndex] 550 respondsToSelector: @selector(hasEditableCells)] || 551 ![[subs objectAtIndex: _editingViewIndex] hasEditableCells]) 552 _editingViewIndex++; 553 [[subs objectAtIndex: _editingViewIndex] beginEditingInFirstCell]; 554} 555 556- (void) superviewFrameChanged: (NSNotification*) note 557{ 558 if([self isDescendantOf: [note object]] && self != [note object]) 559 [self layout]; 560} 561 562- (void) imageClicked: (id) sender 563{ 564 NSOpenPanel *panel; 565 NSArray *types; 566 int retval; 567 568 if(!_editable) return; 569 570 panel = [NSOpenPanel openPanel]; 571 types = [NSArray arrayWithObjects: @"jpg", @"JPG", @"jpeg", @"JPEG", 572 @"tiff", @"TIFF", @"tif", @"TIF", @"png", @"PNG", nil]; 573 [panel setCanChooseFiles: YES]; 574 [panel setCanChooseDirectories: NO]; 575 [panel setAllowsMultipleSelection: NO]; 576 retval = [panel runModalForTypes: types]; 577 578 if(retval == NSCancelButton) return; 579 580 if([[panel filenames] count] != 1) 581 { 582 NSLog(@"Argh! %d filenames; 1 expected\n", [[panel filenames] count]); 583 return; 584 } 585 if(![_person setImageDataWithFile: [[panel filenames] objectAtIndex: 0]]) 586 NSRunAlertPanel(_(@"Error Loading Image"), 587 [NSString stringWithFormat: _(@"The image file %@ could " 588 @"not be loaded.")], 589 _(@"OK"), nil, nil, nil); 590 else 591 [self layout]; 592} 593 594- (void) cleanupEmptyProperty: (NSString*) property 595{ 596 ADPropertyType type; 597 598 type = [ADPerson typeOfProperty: property]; 599 600 if(type == ADStringProperty) 601 { 602 if([[_person valueForProperty: property] isEqualToString: @""] || 603 [[_person valueForProperty: property] 604 isEqualToString: [[self class] emptyValueForProperty: property]]) 605 [_person removeValueForProperty: property]; 606 return; 607 } 608 else if(type == ADMultiStringProperty) 609 { 610 ADMutableMultiValue *mv; int i; BOOL didSomeWork, didSomethingAtAll; 611 612 mv = [_person valueForProperty: property]; 613 if(![mv count]) return; 614 615 didSomeWork = YES; didSomethingAtAll = NO; 616 while(didSomeWork) 617 { 618 didSomeWork = NO; 619 for(i=0; i<[mv count]; i++) 620 if([[mv valueAtIndex: i] 621 isEqualToString: [[self class] 622 emptyValueForProperty: property]]) 623 { 624 [mv removeValueAndLabelAtIndex: i]; 625 didSomeWork = YES; 626 didSomethingAtAll = NO; 627 i = 0; 628 break; 629 } 630 } 631 if(didSomethingAtAll) 632 [_person setValue: mv forProperty: property]; 633 } 634 else if(type == ADMultiDictionaryProperty) 635 { 636 ADMutableMultiValue *mv; int i; BOOL didSomeWork, didSomethingAtAll; 637 638 mv = [[[ADMutableMultiValue alloc] 639 initWithMultiValue: [_person valueForProperty: property]] 640 autorelease]; 641 if(![mv count]) return; 642 643 didSomeWork = YES; didSomethingAtAll = NO; 644 while(didSomeWork) 645 { 646 didSomeWork = NO; 647 for(i=0; i<[mv count]; i++) 648 if(![[mv valueAtIndex: i] count]) 649 { 650 [mv removeValueAndLabelAtIndex: i]; 651 didSomeWork = YES; 652 didSomethingAtAll = NO; 653 i = 0; 654 break; 655 } 656 } 657 if(didSomethingAtAll) 658 [_person setValue: mv forProperty: property]; 659 } 660} 661 662- (void) cleanupEmptyProperties 663{ 664 NSEnumerator *e; NSString *prop; 665 666 e = [[ADPerson properties] objectEnumerator]; 667 while((prop = [e nextObject])) 668 [self cleanupEmptyProperty: prop]; 669} 670 671- (void) setDelegate: (id) delegate 672{ 673 _delegate = delegate; 674} 675 676- (id) delegate 677{ 678 return _delegate; 679} 680 681- (void) setAcceptsDrop: (BOOL) yesno 682{ 683 _acceptsDrop = yesno; 684} 685 686- (BOOL) acceptsDrop 687{ 688 return _acceptsDrop; 689} 690 691- (void) setFontSize: (float) fontSize 692{ 693 if(_fontSize == fontSize) 694 return; 695 _fontSize = fontSize; 696 [self layout]; 697} 698 699- (float) fontSize 700{ 701 return _fontSize; 702} 703 704/* 705 * Delegate methods 706 */ 707 708- (BOOL) canPerformClickForProperty: (id) property 709{ 710 if([property isEqualToString: ADEmailProperty] || 711 [property isEqualToString: ADHomePageProperty]) 712 return YES; 713 return NO; 714} 715 716- (void) clickedOnProperty: (id) property 717 withValue: (id) value 718 inView: (id) sender 719{ 720 if([property isEqualToString: ADEmailProperty]) 721 { 722 NSPasteboard *pb = [NSPasteboard generalPasteboard]; 723 [pb declareTypes: [NSArray arrayWithObjects: NSStringPboardType, nil] 724 owner: self]; 725 [pb setString: value forType: NSStringPboardType]; 726 727 NSPerformService(@"GNUMail/New Mail with recipient", pb); 728 } 729 else if([property isEqualToString: ADHomePageProperty]) 730 { 731 NSPasteboard *pb = [NSPasteboard generalPasteboard]; 732 [pb declareTypes: [NSArray arrayWithObjects: NSStringPboardType, nil] 733 owner: self]; 734 [pb setString: value forType: NSStringPboardType]; 735 736 NSPerformService(@"Open URL", pb); 737 } 738} 739 740- (void) valueForProperty: (id) property 741 changedToValue: (id) value 742 inView: (id) sender 743{ 744} 745 746- (void) viewWillBeginEditing: (id) view 747{ 748 int i; 749 750 for(i=0; i<[[self subviews] count]; i++) 751 { 752 id v = [[self subviews] objectAtIndex: i]; 753 if(v == view) 754 _editingViewIndex = i; 755 else if([v isKindOfClass: [ADPersonPropertyView class]]) 756 [v endEditing]; 757 } 758} 759 760- (void) view: (id) view 761changedWidthFrom: (float) w1 762 to: (float) w2 763{ 764 NSPoint o; 765 NSEnumerator *e; 766 ADPersonPropertyView *v; 767 768 if(!view) return; 769 770 o = [view frame].origin; 771 e = [[self subviews] objectEnumerator]; 772 while((v = [e nextObject])) 773 { 774 NSPoint p; 775 if(v == view) continue; 776 p = [v frame].origin; 777 if(p.y == o.y && p.x > o.x) 778 { 779 p.x += (w2-w1); 780 [v setFrameOrigin: p]; 781 } 782 } 783 [self setNeedsDisplay: YES]; 784} 785 786- (void) view: (id) view 787changedHeightFrom: (float) oldH 788 to: (float) newH 789{ 790 NSPoint o; 791 NSEnumerator *e; 792 ADPersonPropertyView *v; 793 794 if(!view) return; 795 796 o = [view frame].origin; 797 e = [[self subviews] objectEnumerator]; 798 while((v = [e nextObject])) 799 { 800 NSPoint p; 801 if(v == view) continue; 802 p = [v frame].origin; 803 if(p.y > o.y) 804 { 805 p.y += (newH-oldH); 806 [v setFrameOrigin: p]; 807 } 808 } 809 [self setNeedsDisplay: YES]; 810} 811 812- (void) beginEditingInNextViewWithTextMovement: (int) movement 813{ 814 NSArray *subs; 815 816 [self layout]; 817 818 subs = [self subviews]; 819 if(![subs count]) return; 820 821 switch(movement) 822 { 823 case NSReturnTextMovement: 824 return; 825 826 case NSTabTextMovement: 827 do { 828 _editingViewIndex++; 829 if(_editingViewIndex >= [subs count]) 830 _editingViewIndex = 0; 831 if([[subs objectAtIndex: _editingViewIndex] 832 respondsToSelector: @selector(hasEditableCells)] && 833 [[subs objectAtIndex: _editingViewIndex] 834 hasEditableCells]) 835 break; 836 } while(YES); 837 [[subs objectAtIndex: _editingViewIndex] beginEditingInFirstCell]; 838 break; 839 840 case NSBacktabTextMovement: 841 do { 842 _editingViewIndex--; 843 if(_editingViewIndex < 0) 844 _editingViewIndex = [subs count]-1; 845 if([[subs objectAtIndex: _editingViewIndex] 846 respondsToSelector: @selector(hasEditableCells)] && 847 [[subs objectAtIndex: _editingViewIndex] 848 hasEditableCells]) 849 break; 850 } while(YES); 851 [[subs objectAtIndex: _editingViewIndex] beginEditingInLastCell]; 852 break; 853 default: 854 break; 855 } 856} 857 858- (BOOL) personPropertyView: (ADPersonPropertyView*) view 859 willDragValue: (NSString*) value 860 forProperty: (NSString*) aProperty 861{ 862 if(!_delegate || 863 ![_delegate 864 respondsToSelector: @selector(personView:willDragProperty:)] || 865 ![_delegate personView: self willDragProperty: aProperty]) 866 return NO; 867 return YES; 868} 869 870- (BOOL) personPropertyView: (ADPersonPropertyView*) view 871 willDragPerson: (ADPerson*) aPerson 872{ 873 if(!_delegate || 874 ![_delegate 875 respondsToSelector: @selector(personView:willDragPerson:)] || 876 ![_delegate personView: self willDragPerson: aPerson]) 877 return NO; 878 return YES; 879} 880 881- (BOOL) imageView: (ADImageView*) view 882 willDragImage: (NSImage*) image 883{ 884 if(!_delegate || 885 ![_delegate 886 respondsToSelector: @selector(personView:willDragProperty:)] || 887 ![_delegate personView: self willDragProperty: ADImageProperty]) 888 return NO; 889 return YES; 890} 891 892- (BOOL) imageView: (ADImageView*) view 893 willDragPerson: (ADPerson*) aPerson 894{ 895 if(!_delegate || 896 ![_delegate 897 respondsToSelector: @selector(personView:willDragPerson:)] || 898 ![_delegate personView: self willDragPerson: aPerson]) 899 return NO; 900 return YES; 901} 902 903- (NSImage*) draggingImage 904{ 905 return _vcfImage; 906} 907 908/* 909 * NoteView delegate methods 910 */ 911 912- (void) textDidChange: (NSNotification*) aNotification 913{ 914 id view; 915 916 view = [aNotification object]; 917 if(view != _noteView) 918 return; 919 920 _noteTextChanged = YES; 921 [view sizeToFit]; 922 [self calcSize]; 923} 924 925- (void) textDidEndEditing: (NSNotification*) aNotification 926{ 927 id view; NSString *note; 928 929 view = [aNotification object]; 930 if(view != _noteView) 931 return; 932 933 note = [_person valueForProperty: ADNoteProperty]; 934 if(note) 935 { 936 if([[view string] isEqualToString: @""]) 937 [_person removeValueForProperty: ADNoteProperty]; 938 else 939 [_person setValue: [view string] forProperty: ADNoteProperty]; 940 } 941 else if(![[view string] isEqualToString: @""]) 942 [_person setValue: [view string] forProperty: ADNoteProperty]; 943 944 _noteTextChanged = NO; 945} 946 947/* 948 * action methods 949 */ 950 951- (void) mouseDown: (NSEvent*) event 952{ 953 NSEnumerator *e; 954 id v; 955 956 e = [[self subviews] objectEnumerator]; 957 while((v = [e nextObject])) 958 if([v isKindOfClass: [ADPersonPropertyView class]]) 959 [v endEditing]; 960 [self layout]; 961 [super mouseDown: event]; 962 963 _mouseDownOnSelf = YES; 964} 965 966- (void) mouseDragged: (NSEvent*) event 967{ 968 NSPasteboard *pb; 969 NSString *str; 970 NSMutableDictionary *dict; 971 972 if(!_mouseDownOnSelf || _editable) 973 return; 974 975 if(!_delegate || 976 ![_delegate respondsToSelector: @selector(personView:willDragPerson:)] || 977 ![_delegate personView: self willDragPerson: _person]) 978 return; 979 980 pb = [NSPasteboard pasteboardWithName: NSDragPboard]; 981 [pb declareTypes: [NSArray arrayWithObjects: @"NSVCardPboardType", 982 @"NSFilesPromisePboardType", 983 NSStringPboardType, 984 ADPeoplePboardType, 985 nil] 986 owner: self]; 987 988 [pb setData: [_person vCardRepresentation] forType: @"NSVCardPboardType"]; 989 990 dict = [NSMutableDictionary dictionary]; 991 [dict setObject: [NSString stringWithFormat: @"%d", 992 [[NSProcessInfo processInfo] processIdentifier]] 993 forKey: @"PID"]; 994 if([_person uniqueId]) 995 [dict setObject: [_person uniqueId] 996 forKey: @"UID"]; 997 if([_person addressBook]) 998 [dict setObject: [[_person addressBook] addressBookDescription] 999 forKey: @"AB"]; 1000 [pb setPropertyList: [NSArray arrayWithObject: dict] 1001 forType: ADPeoplePboardType]; 1002 1003 if([[_person valueForProperty: ADEmailProperty] count]) 1004 str = [NSString stringWithFormat: @"%@ <%@>", 1005 [_person screenNameWithFormat: ADScreenNameFirstNameFirst], 1006 [[_person valueForProperty: ADEmailProperty] 1007 valueAtIndex: 0]]; 1008 else 1009 str = [_person screenName]; 1010 [pb setString: str forType: NSStringPboardType]; 1011 1012 [self dragImage: _vcfImage 1013 at: NSZeroPoint 1014 offset: NSZeroSize 1015 event: event 1016 pasteboard: pb 1017 source: self 1018 slideBack: YES]; 1019} 1020 1021- (void) mouseUp: (NSEvent*) event 1022{ 1023 _mouseDownOnSelf = NO; 1024} 1025 1026/* 1027 * Drag and drop 1028 */ 1029 1030- (NSDragOperation) draggingEntered: (id<NSDraggingInfo>) sender 1031{ 1032 BOOL ok; NSPasteboard *pb; NSArray *types; 1033 1034 if([sender draggingSource] == self || 1035 ([[sender draggingSource] isKindOfClass: [NSView class]] && 1036 [[sender draggingSource] isDescendantOf: self])) 1037 return NO; 1038 1039 ok = NO; 1040 pb = [sender draggingPasteboard]; 1041 types = [pb types]; 1042 1043 if([types containsObject: NSFilenamesPboardType]) 1044 { 1045 NSArray *arr; NSString *fname, *ext; 1046 NSArray *imgExts; 1047 1048 arr = [pb propertyListForType: NSFilenamesPboardType]; 1049 1050 if(![arr isKindOfClass: [NSArray class]] || 1051 [arr count] != 1) 1052 return NSDragOperationNone; 1053 1054 fname = [arr objectAtIndex: 0]; 1055 ext = [[fname pathExtension] lowercaseString]; 1056 imgExts = [NSArray arrayWithObjects: @"vcf", @"jpg", @"jpeg", @"tif", 1057 @"tiff", nil]; 1058 1059 // accept image only if we have a person 1060 if([imgExts containsObject: ext] && !_person) 1061 return NSDragOperationNone; 1062 // don't accept anything besides images and VCFs 1063 else if(![imgExts containsObject: ext] && 1064 ![ext isEqualToString: @"vcf"]) 1065 return NSDragOperationNone; 1066 } 1067 1068 if(_delegate && 1069 [_delegate respondsToSelector: @selector(personView:shouldAcceptDrop:)]) 1070 { 1071 if([_delegate personView: self shouldAcceptDrop: sender]) 1072 ok = YES; 1073 else 1074 ok = NO; 1075 } 1076 else 1077 { 1078 if(_acceptsDrop) ok = YES; 1079 else ok = NO; 1080 } 1081 1082 if(ok) 1083 return NSDragOperationCopy; 1084 else 1085 return NSDragOperationNone; 1086} 1087 1088- (BOOL) prepareForDragOperation: (id<NSDraggingInfo>) sender 1089{ 1090 BOOL ok; NSPasteboard *pb; NSArray *types; 1091 1092 if([sender draggingSource] == self || 1093 ([[sender draggingSource] isKindOfClass: [NSView class]] && 1094 [[sender draggingSource] isDescendantOf: self])) 1095 return NO; 1096 1097 ok = NO; 1098 pb = [sender draggingPasteboard]; 1099 types = [pb types]; 1100 1101 if(_delegate && 1102 [_delegate respondsToSelector: @selector(personView:shouldAcceptDrop:)]) 1103 { 1104 if(![_delegate personView: self shouldAcceptDrop: sender]) 1105 return NO; 1106 } 1107 else 1108 { 1109 if(!_acceptsDrop) 1110 return NO; 1111 } 1112 1113 return YES; 1114} 1115 1116- (BOOL) performDragOperation: (id<NSDraggingInfo>) sender 1117{ 1118 BOOL ok; NSPasteboard *pb; NSArray *types; 1119 1120 ok = NO; 1121 pb = [sender draggingPasteboard]; 1122 types = [pb types]; 1123 1124 if([types containsObject: NSFilenamesPboardType]) 1125 { 1126 NSArray *arr; NSString *fname, *ext; 1127 1128 arr = [pb propertyListForType: NSFilenamesPboardType]; 1129 1130 if(![arr isKindOfClass: [NSArray class]] || 1131 [arr count] != 1) 1132 return NSDragOperationNone; 1133 1134 fname = [arr objectAtIndex: 0]; 1135 ext = [[fname pathExtension] lowercaseString]; 1136 1137 // convert vcf file 1138 if([ext isEqualToString: @"vcf"]) 1139 { 1140 NSMutableArray *ppl; 1141 id conv; ADRecord *r; 1142 1143 conv = [[ADConverterManager sharedManager] 1144 inputConverterWithFile: fname]; 1145 ppl = [NSMutableArray array]; 1146 while((r = [conv nextRecord])) 1147 { 1148 if(![r isKindOfClass: [ADPerson class]]) 1149 continue; 1150 [ppl addObject: r]; 1151 } 1152 if(![ppl count]) return NO; 1153 1154 if(_delegate && 1155 [_delegate respondsToSelector: 1156 @selector(personView:receivedDroppedPersons:)]) 1157 { 1158 if(![_delegate personView: self receivedDroppedPersons: ppl]) 1159 return NO; 1160 } 1161 else 1162 [self setPerson: [ppl objectAtIndex: 0]]; 1163 return YES; 1164 } 1165 1166 else if([[NSArray arrayWithObjects: @"jpg", @"jpeg", @"tif", @"tiff", 1167 nil] containsObject: ext]) 1168 { 1169 if(!_person) return NO; 1170 if(![_person setImageDataWithFile: fname]) return NO; 1171 [self layout]; 1172 return YES; 1173 } 1174 } 1175 1176 else if([types containsObject: NSTIFFPboardType]) 1177 { 1178 NSData *data = [pb dataForType: NSTIFFPboardType]; 1179 if(![_person setImageData: data]) return NO; 1180 if(![_person setImageDataType: @"tiff"]) return NO; 1181 [self layout]; 1182 return YES; 1183 } 1184 1185 else if([types containsObject: @"NSVCardPboardType"]) 1186 { 1187 ADPerson *p; 1188 NSData *data; 1189 1190 data = [pb dataForType: @"NSVCardPboardType"]; 1191 p = [[[ADPerson alloc] initWithVCardRepresentation: data] autorelease]; 1192 if(!p) 1193 return NO; 1194 1195 if(_delegate && 1196 [_delegate respondsToSelector: 1197 @selector(personView:receivedDroppedPersons:)]) 1198 { 1199 if(![_delegate personView: self 1200 receivedDroppedPersons: [NSArray arrayWithObject: p]]) 1201 return NO; 1202 } 1203 else 1204 [self setPerson: p]; 1205 1206 return YES; 1207 } 1208 1209 return NO; 1210} 1211 1212- (unsigned int) draggingSourceOperationMaskForLocal: (BOOL) isLocal 1213{ 1214 return NSDragOperationCopy|NSDragOperationLink; 1215} 1216@end 1217 1218@implementation ADPersonView (PropertyMangling) 1219+ (NSString*) nextLabelAfter: (NSString*) previous 1220 forProperty: (NSString*) property 1221{ 1222 NSArray *arr; NSInteger index; 1223 1224 arr = [_labelDict objectForKey: property]; 1225 if(!arr || ![arr count]) arr = [_labelDict objectForKey: @"Default"]; 1226 if(!arr || ![arr count]) return @"!!UNKNOWN!!"; 1227 1228 index = [arr indexOfObject: previous]; 1229 if(index == NSNotFound) return [arr objectAtIndex: 0]; 1230 index++; if(index >= [arr count]) index = 0; 1231 return [arr objectAtIndex: index]; 1232} 1233 1234+ (NSString*) defaultLabelForProperty: (NSString*) property 1235{ 1236 NSArray *arr; 1237 1238 arr = [_labelDict objectForKey: property]; 1239 if(!arr || ![arr count]) arr = [_labelDict objectForKey: @"Default"]; 1240 if(!arr || ![arr count]) return @"!!UNKNOWN!!"; 1241 return [arr objectAtIndex: 0]; 1242} 1243 1244+ (id) emptyValueForProperty: (NSString*) property 1245{ 1246 ADPropertyType type = [ADPerson typeOfProperty: property]; 1247 switch(type) 1248 { 1249 case ADDateProperty: 1250 case ADStringProperty: 1251 case ADMultiStringProperty: 1252 return [NSString stringWithFormat: @"[%@]", 1253 ADLocalizedPropertyOrLabel(property)]; 1254 case ADDictionaryProperty: 1255 case ADMultiDictionaryProperty: 1256 return [NSMutableDictionary dictionary]; 1257 1258 default: 1259 NSLog(@"Can't create empty value for %@ (type 0x%x)\n", 1260 property, type); 1261 } 1262 1263 return nil; 1264} 1265 1266+ (NSArray*) layoutRuleForProperty: (NSString*) property 1267 value: (NSDictionary*) dict 1268{ 1269 NSString *countryCode, *countryName; 1270 NSArray *layout; 1271 1272 countryCode = [dict objectForKey: ADAddressCountryCodeKey]; 1273 countryName = [dict objectForKey: ADAddressCountryKey]; 1274 if(!countryCode && countryName) 1275 countryCode = [self isoCountryCodeForCountryName: countryName]; 1276 if(!countryCode && __defaultCountryCode) 1277 countryCode = __defaultCountryCode; 1278 if(!countryCode) 1279 countryCode = [self isoCountryCodeForCurrentLocale]; 1280 1281 layout = [_addressLayoutDict objectForKey: countryCode]; 1282 if(!layout) 1283 layout = [_addressLayoutDict objectForKey: @"Default"]; 1284 1285 return layout; 1286} 1287 1288+ (NSString*) isoCountryCodeForCountryName: (NSString*) name 1289{ 1290 NSEnumerator *e; NSString *key; 1291 1292 e = [[_isoCodeDict allKeys] objectEnumerator]; 1293 while((key = [e nextObject])) 1294 if([[_isoCodeDict objectForKey: key] containsObject: name]) 1295 return key; 1296 1297 NSLog(@"No default set\n"); 1298 return [self isoCountryCodeForCurrentLocale]; 1299} 1300 1301+ (void) setDefaultISOCountryCode: (NSString*) code 1302{ 1303 [__defaultCountryCode release]; 1304 __defaultCountryCode = [code copy]; 1305} 1306 1307+ (NSString*) isoCountryCodeForCurrentLocale 1308{ 1309 NSString *lang; NSRange range; 1310 1311 lang = [[[NSProcessInfo processInfo] environment] objectForKey: @"LANG"]; 1312 if(!lang) return @"us"; // hard-coded default!! 1313 1314 range = [lang rangeOfString: @"_"]; 1315 if(range.location != NSNotFound) 1316 lang = [[lang substringFromIndex: range.location+range.length] 1317 lowercaseString]; 1318 1319 if(![[_isoCodeDict allKeys] containsObject: lang]) 1320 lang = @"us"; 1321 1322 return lang; 1323} 1324 1325@end 1326