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