// // BPIconGroup.m // Attempt2 // // Created by Bea on 13/05/05. // Copyright 2005 __MyCompanyName__. All rights reserved. // #import "BPIconGroup.h" #import "BPIconGroupLabel.h" #import "BPIcon.h" #import "BPUtil.h" @implementation BPIconGroup + (NSSize)startingSize { return NSMakeSize(150, 50); } + (int)defaultLabelPadding { return 3; // padding below the label???? } // padding within group between an icon and the group outline + (int)defaultOutlineSidePadding { return 15; } + (int)defaultOutlineBottomPadding { return 25 + [self defaultLabelPadding]; } + (int)defaultOutlineTopPadding { return 15; } + (int)defaultGapBetweenItems { return 12; } /* + (NSSize)outlineSizeForIconCount:(int)numOfIcons { NSSize emptyGroupSize = [BPIconGroup startingSize]; return NSMakeSize(emptyGroupSize.width, emptyGroupSize.height + 20 * numOfIcons); } */ + (NSRect)addDefaultPaddingToBounds:(NSRect)rect { rect.origin.x -= [BPIconGroup defaultOutlineSidePadding]; rect.origin.y -= [BPIconGroup defaultOutlineBottomPadding]; rect.size.width += [BPIconGroup defaultOutlineSidePadding] * 2; rect.size.height += [BPIconGroup defaultOutlineBottomPadding] + [BPIconGroup defaultOutlineTopPadding]; return rect; } + (NSRect)defaultOutlineForBaseIconBounds:(NSRect)iconRect forIconCount:(int)numOfIcons { NSSize outlineSize = iconRect.size; //[BPIconGroup startingSize]; outlineSize.height += [BPIconGroup defaultGapBetweenItems] * numOfIcons; NSRect returnBounds; returnBounds.origin = iconRect.origin; returnBounds.size = outlineSize; return [BPIconGroup addDefaultPaddingToBounds:returnBounds]; } #pragma mark - - (id)initWithIcons:(NSArray *)theIcons inBounds:(NSRect)theBounds label:(NSString *)groupLabel { [super init]; deletable = YES; icons = [[NSMutableArray alloc] initWithArray:theIcons]; bounds = malloc(sizeof(NSRect)); bounds->origin = theBounds.origin; bounds->size = theBounds.size; //outline = [NSBezierPath bezierPathWithRect:*bounds]; dragFormOutline = nil; outline = nil; [self resetOutline]; showOutline = NO; showAsDropTarget = NO; // text field setup labelField = [[BPIconGroupLabel alloc] initWithFrame:NSMakeRect(0,0,60,22)]; [labelField setDelegate:self]; [labelField setText:groupLabel]; [self resetLabelPosition]; // for the mouse actions delegate mouseActionsDelegate = nil; mouseEnteredIconGroupSel = @selector(mouseEnteredIconGroup:); mouseExitedIconGroupSel = @selector(mouseExitedIconGroup:); return self; } - (void)setRepresentedObject:(id)representedObject { [representedObject retain]; [representedObj release]; representedObj = representedObject; } - (id)representedObject { return representedObj; } - (void)setMouseActionsDelegate:(id)delegate { //[delegate retain]; //[mouseActionsDelegate release]; mouseActionsDelegate = delegate; } - (void)setDeletable:(BOOL)isDeletable { deletable = isDeletable; } - (BOOL)isDeletable { return deletable; } - (void)insert:(BPIcon *)icon atIndex:(int)index { if ([icons containsObject:icon]) return; if (index > [icons count]) index = [icons count]; if (index < 0) index = 0; [icons insertObject:icon atIndex:index]; if ([icon iconGroup] != nil) { [[icon iconGroup] remove:icon]; } [icon setIconGroup:self]; if ([icons count] == 1) { // just added the 1st icon to the group. Resize the bounds to fit it. NSRect newBounds = [BPIconGroup defaultOutlineForBaseIconBounds:[icon bounds] forIconCount:0]; if (newBounds.size.width > bounds->size.width) bounds->size.width = newBounds.size.width; bounds->size.height = newBounds.size.height; } else { bounds->size.height += [BPIconGroup defaultGapBetweenItems]; } [self resetOutline]; // reset all my icons' positions [self resetPositionsOfIcons]; // post notification NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: self, @"IconGroup", icon, @"Icon", NULL]; NSNotification *notif = [NSNotification notificationWithName:@"IconGroup_IconAdded" object:nil userInfo:dict]; [[NSNotificationCenter defaultCenter] postNotification:notif]; } - (void)add:(BPIcon *)icon { [self insert:icon atIndex:[icons count]]; } - (void)remove:(BPIcon *)icon { if (![icons containsObject:icon]) return; // make notification (do this before removing icon from icons array in case // doing that will release and dealloc the icon) NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: self, @"IconGroup", icon, @"Icon", NULL]; NSNotification *notif = [NSNotification notificationWithName:@"IconGroup_IconRemoved" object:nil userInfo:dict]; [icon retain]; [icons removeObject:icon]; [icon setIconGroup:nil]; if ([icons count] == 0) { bounds->size = [BPIconGroup startingSize]; } else { bounds->size.height -= [BPIconGroup defaultGapBetweenItems]; } [self resetOutline]; // reset all my icons' positions [self resetPositionsOfIcons]; // post notification [[NSNotificationCenter defaultCenter] postNotification:notif]; [icon release]; } - (void)removeAll { [icons makeObjectsPerformSelector:@selector(setIconGroup:) withObject:nil]; [icons removeAllObjects]; } - (NSArray *)icons { //NSLog(@"icons: %@", icons); return icons; } - (int)indexOfIcon:(BPIcon *)icon { return [icons indexOfObject:icon]; } - (NSPoint)defaultPositionForIcon:(BPIcon *)icon { int iconIndex = [icons indexOfObject:icon]; if (iconIndex == NSNotFound) { NSLog([NSString stringWithFormat: @"defaultPositionForIcon Error: " "icon %@ not in group, returning NSZeroPoint.", icon]); return NSZeroPoint; } float pileCentre = bounds->origin.x + bounds->size.width/2; NSPoint p; //p.x = bounds->origin.x + [BPIconGroup defaultOutlineSidePadding]; p.x = pileCentre - [[icon image] size].width/2; // centre icon on pile p.y = bounds->origin.y + (iconIndex * [BPIconGroup defaultGapBetweenItems]) + [BPIconGroup defaultOutlineBottomPadding]; return p; } - (void)resetPositionsOfIcons { int i; BPIcon *icon; for (i=0; i<[icons count]; i++) { icon = [icons objectAtIndex:i]; [icon resetPosition]; [icon setBeingBrowsed:NO]; } } #pragma mark - #pragma mark label - (NSString *)label { return [labelField text]; } - (void)setAutoLabel:(NSString *)newLabel { [labelField useItalicFont]; [self setLabel:newLabel]; } - (void)setLabel:(NSString *)newLabel { [labelField setText:newLabel]; [labelField fixIfEmptyLabel]; [self resetLabelPosition]; if (![newLabel isEqual:@""]) [labelField setHidden:NO]; } - (BOOL)hasLabel { return ![labelField isBlank]; } - (BPIconGroupLabel *)labelTextField { return labelField; } - (void)resetLabelPosition { NSRect labelFrame = [labelField frame]; labelFrame.origin.x = (NSMidX(*bounds) - labelFrame.size.width/2); labelFrame.origin.y = bounds->origin.y + [BPIconGroup defaultLabelPadding]; [labelField setFrame:labelFrame]; } - (void)endLabelEditing { [self resetLabelPosition]; NSLog(@"Label changed to '%@'", [labelField text]); // post a notification. NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:2]; [dict setObject:self forKey:@"IconGroup"]; [dict setObject:[labelField text] forKey:@"Label"]; NSNotification *notif = [NSNotification notificationWithName:@"IconGroup_LabelChanged" object:nil userInfo:dict]; [[NSNotificationCenter defaultCenter] postNotification:notif]; } #pragma mark - #pragma mark outline - (void)resetDragFormOutline { NSRect dragFormBounds = *bounds; dragFormBounds.origin = *dragFormPosition; NSBezierPath *newDragFormOutline = [NSBezierPath bezierPathWithRect:dragFormBounds]; [newDragFormOutline retain]; [dragFormOutline release]; dragFormOutline = newDragFormOutline; } - (void)resetOutline { NSBezierPath *newOutline = [NSBezierPath bezierPathWithRect:*bounds]; if ([icons count] == 0) { float pattern[] = { 5.0, 1.0 }; [newOutline setLineDash:pattern count:2 phase:0.0]; } [newOutline retain]; [outline release]; outline = newOutline; //[outline setLineCapStyle:NSRoundLineCapStyle]; // since outline is reset, reset the outline for dragging as well [self resetDragFormOutline]; } - (void)setShowOutline:(BOOL)show { showOutline = show; } - (void)drawOutline { if ([icons count] > 0) { [[NSColor blackColor] set]; } else { [[NSColor grayColor] set]; } [outline stroke]; } #pragma mark - #pragma mark positioning - (NSRect)trackingRectBounds { return *bounds; } - (NSRect)bounds { return *bounds; } - (void)setPosition:(NSPoint)newPosition { // do nothing if newPosition isn't changing if (NSEqualPoints(newPosition, bounds->origin)) return; // change drag form newPosition *dragFormPosition = newPosition; // change bounds int xOffset = newPosition.x - bounds->origin.x; int yOffset = newPosition.y - bounds->origin.y; *bounds = NSOffsetRect(*bounds, xOffset, yOffset); // reset all my icons' positions [self resetPositionsOfIcons]; // reset label newPosition [self resetLabelPosition]; // reset my group outline [self resetOutline]; // post notification NSDictionary *dict = [NSDictionary dictionaryWithObject:self forKey:@"IconGroup"]; NSNotification *notif = [NSNotification notificationWithName:@"IconGroup_PositionChanged" object:nil userInfo:dict]; [[NSNotificationCenter defaultCenter] postNotification:notif]; } - (NSPoint)position { return bounds->origin; } - (void)moveDragFormWithOffsetToPoint:(NSPoint)point { [super moveDragFormWithOffsetToPoint:point]; [self resetDragFormOutline]; } - (void)setShowAsDropTarget:(BOOL)show { showAsDropTarget = show; // don't show oultine if showing as drop target //if (show) showOutline = NO; } - (int)z { return NSMinY(*bounds); } - (int)depthCompare:(BPIconGroup *)otherGroup { // sort first by group's z value, then by position within the group if ([self z] < [otherGroup z]) { // this group is in front of the other group return NSOrderedAscending; } else if ([self z] > [otherGroup z]) { // this group is behind the other group return NSOrderedDescending; } else { return NSOrderedSame; } } #pragma mark - #pragma mark browsing - (void)browseOnIcon:(BPIcon *)icon { [icon setBeingBrowsed:YES]; // set all other icons to browsed=NO BPIcon *currIcon; int i; for (i=0; i<[icons count]; i++) { currIcon = [icons objectAtIndex:i]; if (currIcon != icon) [currIcon setBeingBrowsed:NO]; } if ([[NSUserDefaults standardUserDefaults] boolForKey:@"MailStacker_browseShift"]) { int shiftAmount = [[NSUserDefaults standardUserDefaults] integerForKey:@"MailStacker_browseShiftAmount"]; // reset position of this icon & all icons visually below it int iconIndex = [icons indexOfObject:icon]; if (iconIndex == NSNotFound) { NSLog(@"browseOnIcon error: given icon %@ not in this group %@", icon, self); return; } for (i=0; i<=iconIndex; i++) { currIcon = [icons objectAtIndex:i]; [currIcon resetPosition]; } // if there are icons in the pile above this one, shift them upwards if (iconIndex+1 < [icons count]) { for (i=iconIndex+1; i<[icons count]; i++) { currIcon = [icons objectAtIndex:i]; NSPoint defaultPosn = [self defaultPositionForIcon:currIcon]; NSPoint posn = [currIcon position]; if (posn.y == defaultPosn.y) { // if it's in the default y position, shift up posn.y += shiftAmount; // shift upwards [currIcon setPosition:posn]; } } } } } - (void)stopBrowseOnIcon:(BPIcon *)icon { [icon setBeingBrowsed:NO]; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"MailStacker_browseShift"]) { BPIcon *currIcon; int i; for (i=0; i<[icons count]; i++) { currIcon = [icons objectAtIndex:i]; // if currIcon has shifted upwards (i.e. being browsed) reset position, etc. NSPoint defaultPosn = [self defaultPositionForIcon:currIcon]; NSPoint posn = [currIcon position]; if (posn.y != defaultPosn.y) { [currIcon setPosition:NSMakePoint(posn.x, defaultPosn.y)]; } } } } #pragma mark - #pragma mark drawing - (void)drawSelf { if (showAsDropTarget) { [[[NSColor lightGrayColor] colorWithAlphaComponent:0.3] set]; [outline fill]; [self drawOutline]; } // draw outline if (showOutline || [icons count] == 0) { [self drawOutline]; [[[NSColor whiteColor] colorWithAlphaComponent:0.3] set]; [outline fill]; } // tell each icon in this group to draw // this will draw the 1st icon -> last icon (i.e. bottom -> top of a pile) int i; for (i=0; i<[icons count]; i++) { //NSLog(@"icon %@", [icons objectAtIndex:i]); [[icons objectAtIndex:i] drawSelf]; } } - (void)drawDragForm { // draw outline if (![self isOnDragDest]) { [[NSColor lightGrayColor] set]; [dragFormOutline stroke]; } // tell each icon in this group to draw // this will draw the 1st icon -> last icon (i.e. bottom -> top of a pile) int i; for (i=0; i<[icons count]; i++) { [[icons objectAtIndex:i] drawDragForm]; } } #pragma mark - #pragma mark tracking rect - (BOOL)isMouseOver:(NSPoint)point { /* if (! [BPUtil point:point overRect:*bounds] ) return NO; return YES; */ return [outline containsPoint:point]; } - (void)mouseEnteredTrackingRect { showOutline = YES; [labelField setHidden:NO]; //[labelField setEnabled:YES]; if ([mouseActionsDelegate respondsToSelector:mouseEnteredIconGroupSel]) [mouseActionsDelegate performSelector:mouseEnteredIconGroupSel withObject:self]; } - (void)mouseExitedTrackingRect { showOutline = NO; showAsDropTarget = NO; [labelField hideIfEmpty]; if ([mouseActionsDelegate respondsToSelector:mouseExitedIconGroupSel]) [mouseActionsDelegate performSelector:mouseExitedIconGroupSel withObject:self]; } #pragma mark - #pragma mark NSTextField delegate methods - (void)controlTextDidChange:(NSNotification *)aNotification { [self resetLabelPosition]; } - (void)controlTextDidEndEditing:(NSNotification *)aNotification { [self endLabelEditing]; } #pragma mark - - (NSString *)description { if (icons == nil) return @"NIL icongroup icons"; else return [NSString stringWithFormat:@"BPIconGroup '%@' contents: %d", [labelField text], [icons count]]; } // ----------- dealloc - (void)dealloc { NSLog(@"dealloc %@", self); [representedObj release]; [icons release]; [labelField removeFromSuperview]; [labelField release]; free(bounds); [outline release]; [dragFormOutline release]; [super dealloc]; } @end