#import #import "BPPilesDisplay.h" #import "BPPilesDisplayController.h" #import "BPPilesDataController.h" #import "BPIcon.h" #import "BPIconGroup.h" #import "BPIconGroupLabel.h" #import "BPInteractiveItem.h" #import "BPUtil.h" @implementation BPPilesDisplay - (id)initWithFrame:(NSRect)frameRect { if ((self = [super initWithFrame:frameRect]) != nil) { // Add initialization code here // icons = [[NSMutableArray alloc] initWithCapacity:100]; iconGroups = [[NSMutableArray alloc] initWithCapacity:10]; iconGroupsContainingCursor = [[NSMutableSet alloc] initWithCapacity:5]; iconsContainingCursor = [[NSMutableSet alloc] initWithCapacity:5]; draggingSelectionRect = false; mainDragItem = nil; itemsBeingDragged = [[NSMutableArray alloc] initWithCapacity:20]; iconReferenceForBaseOfNewPile = nil; mouseDownPoint = malloc(sizeof(NSPoint)); *mouseDownPoint = NSZeroPoint; groupsHoveringOver = [[NSMutableArray alloc] initWithCapacity:5]; selectedIcons = [[NSMutableSet alloc] initWithCapacity:20]; dummyGroupOutline = nil; showDummyGroupOutline = NO; plusSignImg = [NSImage imageNamed:@"plus_sign"]; selectionRect = malloc(sizeof(NSRect)); boundsTrackingRectNum = 0; // selectors handleNotificationSel = @selector(handleNotification:); depthCompareSel = @selector(depthCompare:); drawDragFormSel = @selector(drawDragForm); toggleShiftSel = @selector(toggleShift); iconsToColour = [[NSMutableArray alloc] initWithCapacity:5]; } return self; } - (NSPoint)mouseLoc { NSPoint mouseLoc = [[self window] mouseLocationOutsideOfEventStream]; return [self convertPoint:mouseLoc fromView:nil]; } - (NSPoint)mouseLoc:(NSEvent *)event { NSPoint mouseLoc = [event locationInWindow]; return [self convertPoint:mouseLoc fromView:nil]; } /* int subviewSort(id objA, id objB, void *context) { if (![objA isKindOfClass:[BPIconGroupLabel class]] && ![objB isKindOfClass:[BPIconGroupLabel class]]) NSLog(@"subviewSort Error: received non-BPIconGroupLabel object/s: %@ %@", objA, objB); // return in reverse order int groupCompare = [[objA iconGroup] depthCompare:[objB iconGroup]]; if (groupCompare == NSOrderedAscending) return NSOrderedDescending; else if (groupCompare == NSOrderedDescending) return NSOrderedAscending; else return NSOrderedSame; } - (void)sortSubviews { int(*pSortFunc)(id, id, void *); pSortFunc = subviewSort; [self sortSubviewsUsingFunction:pSortFunc context:NULL]; // NOT SUPPOSED TO FREE pSortFunc BECAUSE IT WASN'T MALLOCED???? } */ - (void)refreshIconsContainingCursorListForGroup:(BPIconGroup *)group { // check iconsContainingCursor list NSPoint mouseLoc = [self mouseLoc]; BPIcon *currentIcon; int i; for (i=0; i<[[group icons] count]; i++) { currentIcon = [[group icons] objectAtIndex:i]; if ([self mouse:mouseLoc inRect:[currentIcon bounds]]) { [currentIcon mouseEnteredTrackingRect]; [iconsContainingCursor addObject:currentIcon]; } else { [iconsContainingCursor removeObject:currentIcon]; } } } - (void)resetTrashButtonPositionForTrashBounds:(NSRect)trashBounds { float x = NSMidX(trashBounds) - [emptyTrashButton frame].size.width/2; float buttonHeight = [emptyTrashButton frame].size.height; NSPoint posn = NSMakePoint(x, trashBounds.origin.y - buttonHeight); [emptyTrashButton setFrameOrigin:posn]; } - (void)registerForNotifications { NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; // icon notifications [defaultCenter addObserver:self selector:handleNotificationSel name:@"Icon_PositionChanged" object:nil]; // icon group notifications [defaultCenter addObserver:self selector:handleNotificationSel name:@"IconGroup_PositionChanged" object:nil]; [defaultCenter addObserver:self selector:handleNotificationSel name:@"IconGroup_IconAdded" object:nil]; [defaultCenter addObserver:self selector:handleNotificationSel name:@"IconGroup_IconRemoved" object:nil]; } - (void)handleNotification:(NSNotification *)notif { if ([[notif name] isEqual:@"Icon_PositionChanged"]) { // for icon refresh BPIcon *icon = [[notif userInfo] objectForKey:@"Icon"]; [self refreshDisplayForIcon:icon]; [self setNeedsDisplay:YES]; } else if ([[notif name] isEqual:@"IconGroup_PositionChanged"]) { // icon group moved BPIconGroup *group = [[notif userInfo] objectForKey:@"IconGroup"]; [self refreshDisplayForIconGroup:group]; // resort icon groups & their labels according to depth [iconGroups sortUsingSelector:depthCompareSel]; //[self sortSubviews]; [self refreshIconsContainingCursorListForGroup:group]; // hack in for moving the "empty trash" button with the trash pile if ([group representedObject] == [BPPilesDataController TRASH]) { [self resetTrashButtonPositionForTrashBounds:[group bounds]]; } } else if ([[notif name] isEqual:@"IconGroup_IconAdded"]) { // icon added to a group BPIconGroup *group = [[notif userInfo] objectForKey:@"IconGroup"]; [self refreshDisplayForIconGroup:group]; [self refreshIconsContainingCursorListForGroup:group]; // redraw group?? [self setNeedsDisplayInRect:[group bounds]]; } if ([[notif name] isEqual:@"IconGroup_IconRemoved"]) { // icon removed from a group BPIcon *icon = [[notif userInfo] objectForKey:@"Icon"]; // remove traces [self removeTrackingRectForIcon:icon]; [iconsContainingCursor removeObject:icon]; // redraw icon's group and refreshDisplayForIconGroup (to redo its tracking rect) BPIconGroup *group = [[notif userInfo] objectForKey:@"IconGroup"]; [self refreshDisplayForIconGroup:group]; [self refreshIconsContainingCursorListForGroup:group]; // redraw group?? NSRect rect = [group bounds]; rect.size.height += 100; // since the pile's gotten smaller since [self setNeedsDisplayInRect:rect]; } //NSLog(@"handled notif %@", [notif name]); //[self setNeedsDisplay:YES]; } - (void)setupForDisplay { // note if the mouse is already inside any icons or groups NSPoint mouseLoc = [self mouseLoc]; BPIconGroup *group; BPIcon *icon; int i, j; for (i=0; i<[iconGroups count]; i++) { group = [iconGroups objectAtIndex:i]; if ([self mouse:mouseLoc inRect:[group bounds]]) { [group mouseEnteredTrackingRect]; [iconGroupsContainingCursor addObject:group]; [groupsHoveringOver addObject:group]; } for (j=0; j<[[group icons] count]; j++) { icon = [[group icons] objectAtIndex:j]; if ([self mouse:[self mouseLoc] inRect:[icon bounds]]) { [icon mouseEnteredTrackingRect]; [iconsContainingCursor addObject:icon]; } } } } - (void)awakeFromNib { [self registerForNotifications]; [self setupForDisplay]; // hack in to set "empty trash" button starting position NSRect trashBounds; trashBounds.origin = NSMakePoint(600,50); trashBounds.size = [BPIconGroup startingSize]; [self resetTrashButtonPositionForTrashBounds:trashBounds]; //[[self window] setAcceptsMouseMovedEvents:YES]; //[[self window] makeFirstResponder:self]; // set up right-click menu [[contextMenu itemWithTitle:@"Red"] setRepresentedObject:[NSColor redColor]]; [[contextMenu itemWithTitle:@"Orange"] setRepresentedObject:[NSColor orangeColor]]; [[contextMenu itemWithTitle:@"Yellow"] setRepresentedObject:[NSColor yellowColor]]; [[contextMenu itemWithTitle:@"Green"] setRepresentedObject:[NSColor greenColor]]; [[contextMenu itemWithTitle:@"Blue"] setRepresentedObject:[NSColor blueColor]]; [[contextMenu itemWithTitle:@"Purple"] setRepresentedObject:[NSColor purpleColor]]; } #pragma mark - - (void)refreshDisplayForIcon:(BPIcon *)icon { // remove & add tracking rect [self removeTrackingRectForIcon:icon]; [self addTrackingRectForIcon:icon]; // note if mouse is already inside icon //if ([icon isMouseOver:[self mouseLoc]]) if ([self mouse:[self mouseLoc] inRect:[icon bounds]]) { [icon mouseEnteredTrackingRect]; [iconsContainingCursor addObject:icon]; } else { [icon mouseExitedTrackingRect]; [iconsContainingCursor removeObject:icon]; } // redraw in icon bounds?? //[self setNeedsDisplayInRect:[icon bounds]]; } - (void)refreshDisplayForIcons:(NSArray *)icons { // do it together so you don't have to fetch the mouseLoc so often BPIcon *icon; int i; for (i=0; i<[icons count]; i++) { icon = [icons objectAtIndex:i]; // remove & add tracking rect [self removeTrackingRectForIcon:icon]; [self addTrackingRectForIcon:icon]; NSPoint mouseLoc = [self mouseLoc]; // note if mouse is already inside icon //if ([icon isMouseOver:[self mouseLoc]]) if ([self mouse:mouseLoc inRect:[icon bounds]]) { [icon mouseEnteredTrackingRect]; [iconsContainingCursor addObject:icon]; } else { [icon mouseExitedTrackingRect]; [iconsContainingCursor removeObject:icon]; } } } - (void)refreshDisplayForIconGroup:(BPIconGroup *)group { // remove & add tracking rect (refresh) FOR GROUP ONLY (not all its icons too) [self resetTrackingRectsForIconGroup:group]; // check if mouse is inside group //NSPoint mouseLoc = [self mouseLoc]; //if ([group isMouseOver:mouseLoc]) { if ([self mouse:[self mouseLoc] inRect:[group bounds]]) { [group mouseEnteredTrackingRect]; [iconGroupsContainingCursor addObject:group]; [groupsHoveringOver addObject:group]; } else { [group mouseExitedTrackingRect]; [iconGroupsContainingCursor removeObject:group]; [groupsHoveringOver removeObject:group]; } // NSLog(@"refreshDisplayForIconGroup"); // redraw group?? //[self setNeedsDisplayInRect:[group bounds]]; } - (BOOL)isIcon:(NSObject *)object { return [object isKindOfClass:[BPIcon class]]; } - (BOOL)isGroup:(NSObject *)object { return [object isKindOfClass:[BPIconGroup class]]; } // returns an autoreleased array containing the groups of the given icons - (NSArray *)groupsForIcons:(NSArray *)theIcons { NSMutableSet *groups = [[NSMutableSet alloc] initWithCapacity:[theIcons count]]; id icon; int i; for (i=0; i<[theIcons count]; i++) { icon = [theIcons objectAtIndex:i]; if ([self isIcon:icon]) [groups addObject:[icon iconGroup]]; } [groups autorelease]; return [groups allObjects]; } - (NSArray *)iconGroups { return iconGroups; } - (void)addIconGroup:(BPIconGroup *)group { if ([iconGroups containsObject:group]) { NSLog(@"ERROR addIconGroup: iconGroup %@ already exists", group); return; } // add to group list & resort the group list by depth [iconGroups addObject:group]; [iconGroups sortUsingSelector:depthCompareSel]; // add the group's label text field to this view (otherwise it won't get drawn) [self addSubview:(NSTextField *)[group labelTextField]]; // resort order of display for text fields //[self sortSubviews]; // set up display aspects for the group [self refreshDisplayForIconGroup:group]; // set to redraw the group [self setNeedsDisplayInRect:[group bounds]]; } - (void)removeIconGroup:(BPIconGroup *)group { // remove tracking rect [self removeTrackingRectForIconGroup:group]; // remove if under the cursor [iconGroupsContainingCursor removeObject:group]; [groupsHoveringOver removeObject:group]; // remove tracking rect for each icon in group NSArray *iconsInGroup = [group icons]; BPIcon *icon; int i; for (i=0; i<[iconsInGroup count]; i++) { icon = [iconsInGroup objectAtIndex:i]; [self removeTrackingRectForIcon:icon]; } // something weird with labels not being removed, so make sure they are [[group labelTextField] removeFromSuperviewWithoutNeedingDisplay]; // post notification NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: group, @"IconGroup", NULL]; NSNotification *notif = [NSNotification notificationWithName:@"IconGroup_Deleted" object:nil userInfo:dict]; [[NSNotificationCenter defaultCenter] postNotification:notif]; // have to record this otherwise after removing group from list, it might get // deallocated and can't access its 'bounds' anymore? NSRect groupBounds = [group bounds]; // remove the group [iconGroups removeObject:group]; // set to redraw the area [self setNeedsDisplayInRect:groupBounds]; } - (BPIconGroup *)addNewIconGroupAtPoint:(NSPoint)point { NSRect groupBounds; groupBounds.origin = point; groupBounds.size = [BPIconGroup startingSize]; BPIconGroup *newGroup = [[BPIconGroup alloc] initWithIcons:nil inBounds:groupBounds label:@""]; [newGroup autorelease]; // set display controller as delegate of icon group [newGroup setMouseActionsDelegate:displayController]; // add to display's list of groups [self addIconGroup:newGroup]; // post notification NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: newGroup, @"IconGroup", NULL]; NSNotification *notif = [NSNotification notificationWithName:@"IconGroup_Created" object:nil userInfo:dict]; [[NSNotificationCenter defaultCenter] postNotification:notif]; return newGroup; } - (void)undoMergedIconGroupAtPoint:(NSPoint)point withMovedIcons:(NSArray *)icons label:(NSString *)label { BPIconGroup *group = [self addNewIconGroupAtPoint:point]; [self moveIcons:icons toGroup:group]; [group setLabel:label]; } - (void)moveIcons:(NSArray *)theIcons toGroup:(BPIconGroup *)group { int i; BPIcon *icon; NSMutableArray *oldGroups = [NSMutableArray arrayWithCapacity:[theIcons count]]; [[self undoManager] beginUndoGrouping]; [[self undoManager] setActionName:@"move items"]; if ([[group icons] count] == 0 && [group isDeletable]) { // this must be a new group, created by user just for these icons [[[self undoManager] prepareWithInvocationTarget:self] removeIconGroup:group]; } for (i=0; i<[theIcons count]; i++) { icon = (BPIcon *)[theIcons objectAtIndex:i]; //NSLog(@"move %@", icon); if (![oldGroups containsObject:[icon iconGroup]]) [oldGroups addObject:[icon iconGroup]]; [[[self undoManager] prepareWithInvocationTarget:[icon iconGroup]] insert:icon atIndex:[[icon iconGroup] indexOfIcon:icon]]; // retain in case removing it from its group will release it [icon retain]; // remove from old group [[icon iconGroup] remove:icon]; // add to the new group [group add:icon]; // release to counter earlier retain [icon release]; } [[self undoManager] endUndoGrouping]; [displayController movedIcons:theIcons fromGroups:oldGroups toGroup:group]; } - (void)removeGroupIfEmpty:(BPIconGroup *)group { //if ([[group icons] count] == 0 && ![group hasLabel]) { if ([[group icons] count] == 0 && [group isDeletable]) { [self removeIconGroup:group]; } } - (void)removeEmptyIconGroups { int i; BPIconGroup *group; for (i=0; i<[iconGroups count]; i++) { [self removeGroupIfEmpty:[iconGroups objectAtIndex:i]]; } } - (NSMutableSet *)itemsContainingCursorForType:(NSObject *)item { if ([self isGroup:item]) return iconGroupsContainingCursor; else if ([self isIcon:item]) return iconsContainingCursor; NSLog(@"itemsContainingCursorForType Error: unknown BPInteractiveItem %@", item); } #pragma mark - #pragma mark --- drawing --- - (void)drawEverything { /* NSImage *img = [NSImage imageNamed:@"futurework.tif"]; NSRect drawRect; drawRect.origin = NSZeroPoint; drawRect.size = [img size]; [img drawAtPoint:NSMakePoint(300,100) fromRect:drawRect operation:NSCompositeSourceOver fraction:1.0]; */ // draw groups, from groups furtherest away to front-most groups // (iconGroups is sorted by depth in reverse order, so draw in reverse) BPIconGroup *iconGroup; int i; for (i=[iconGroups count]-1; i>=0; i--) { iconGroup = [iconGroups objectAtIndex:i]; [iconGroup drawSelf]; } // if dragging icons into empty space, show a dummy outline so user knows // s/he can drop the icons here if (showDummyGroupOutline) { [[NSColor blackColor] set]; [dummyGroupOutline stroke]; NSRect newGroupOutline = [dummyGroupOutline bounds]; NSPoint plusSignPosn = NSMakePoint(newGroupOutline.origin.x + newGroupOutline.size.width - [plusSignImg size].width/2, //newGroupOutline.origin.y + newGroupOutline.size.height - [plusSignImg size].height/2); newGroupOutline.origin.y - [plusSignImg size].height/2); NSRect drawRect; drawRect.origin = NSZeroPoint; drawRect.size = [plusSignImg size]; [plusSignImg drawAtPoint:plusSignPosn fromRect:drawRect operation:NSCompositeSourceOver fraction:1.0]; } // draw any items being dragged if (dragging && mainDragItem != nil) { [itemsBeingDragged makeObjectsPerformSelector:drawDragFormSel]; } // draw the selection rectangle if user is dragging one if (draggingSelectionRect) { [[NSColor whiteColor] set]; // draw the selection rect using a solid line [NSBezierPath strokeRect:*selectionRect]; /* // draw the selection rect using a dotted line NSBezierPath *selectionPath = [NSBezierPath bezierPathWithRect:*selectionRect]; float pattern[] = { 2.0, 2.0 }; [selectionPath setLineDash:pattern count:2 phase:0.0]; [selectionPath stroke]; */ // draw the selection rect by filling a gray background (like Mac Finder) [[[NSColor lightGrayColor] colorWithAlphaComponent:0.3] set]; [NSBezierPath fillRect:*selectionRect]; } // draw the focus icon now, on top of everything else if ( (mainDragItem != nil && !dragging) || [[NSUserDefaults standardUserDefaults] boolForKey:@"MailStacker_focusBrowsedItem"]) { if (focusIcon != nil) { [focusIcon drawSelf]; } } } // do all the drawing for displaying the groups - (void)drawRect:(NSRect)rect { [self drawEverything]; } #pragma mark - #pragma mark --- focus icon (for browsing) --- - (void)setFocusIcon:(BPIcon *)newFocusIcon { NSRect repaintBounds; if (newFocusIcon == nil) { // if newFocusIcon==nil then we're saying no icon should have the focus // now, so repaint the current focus icon so it no longer looks focused if (focusIcon != nil) { repaintBounds = [focusIcon bounds]; } } else { // setting a new focus icon // if there was an old focus icon, repaint it along with the new icon // so that the old icon no longer looks focused if (focusIcon == nil) { repaintBounds = [newFocusIcon bounds]; } else { repaintBounds = NSUnionRect([newFocusIcon bounds], [focusIcon bounds]); } } [newFocusIcon retain]; [focusIcon release]; focusIcon = newFocusIcon; // redraw [self setNeedsDisplayInRect:repaintBounds]; } #pragma mark - #pragma mark --- selection/deselection of icons --- - (BOOL)isSelectedIcon:(BPIcon *)icon { return [selectedIcons containsObject:icon]; } - (void)selectIcon:(BPIcon *)icon { [selectedIcons addObject:icon]; [icon setHighlighted:YES]; //[self setNeedsDisplayInRect:[icon bounds]]; } - (void)deselectIcon:(BPIcon *)icon { [selectedIcons removeObject:icon]; [icon setHighlighted:NO]; //[self setNeedsDisplayInRect:[icon bounds]]; } - (void)toggleSelectionOfIcon:(BPIcon *)icon { if ([selectedIcons containsObject:icon]) [self deselectIcon:icon]; else [self selectIcon:icon]; } - (void)clearSelectedIcons { NSEnumerator *enumerator = [selectedIcons objectEnumerator]; BPIcon *icon; while (icon = [enumerator nextObject]) { [self deselectIcon:icon]; } [displayController clearedIconSelection]; } #pragma mark - #pragma mark --- dragging a selection rectangle --- - (void)startSelectionByDraggingRectAtPoint:(NSPoint)point { draggingSelectionRect = YES; //NSLog(@"Starting drag select"); selectionRect->origin = point; } - (void)stopSelectionByDraggingRect { draggingSelectionRect = NO; //NSLog(@"Stopped drag select"); selectionRect->size = NSZeroSize; } - (void)resizeSelectionRectToPoint:(NSPoint)point { // resize selection rectangle int width = abs(point.x - mouseDownPoint->x); int height = abs(point.y - mouseDownPoint->y); if (point.x < mouseDownPoint->x) selectionRect->origin.x = point.x; if (point.y < mouseDownPoint->y) selectionRect->origin.y = point.y; selectionRect->size.width = width; selectionRect->size.height = height; } - (void)updateDragSelectionForPoint:(NSPoint)point { BPIconGroup *group; BPIcon *icon; BOOL iconTouchingOrInsideSelection; BOOL iconBottomOK; int i, j; // search all groups for (i=0; i<[iconGroups count]; i++) { group = [iconGroups objectAtIndex:i]; // ignore group if not selecting anything within this group if (! NSIntersectsRect([group bounds], *selectionRect)) continue; for (j=0; j<[[group icons] count]; j++) { icon = [[group icons] objectAtIndex:j]; iconTouchingOrInsideSelection = ( NSIntersectsRect([icon bounds], *selectionRect) || NSContainsRect(*selectionRect, [icon bounds]) ); // check for selecting from top->bottom iconBottomOK = YES; //if (point.y > selectionRect->origin.y) { if (point.y < selectionRect->origin.y + selectionRect->size.height) { //if (point.y == selectionRect->origin.y) { //NSLog(@"in here"); iconBottomOK = ([icon bounds].origin.y >= selectionRect->origin.y); } if (iconTouchingOrInsideSelection && iconBottomOK) { [self selectIcon:icon]; } else { [self deselectIcon:icon]; } } } } #pragma mark - #pragma mark --- tracking rects --- - (void)addTrackingRectForIcon:(BPIcon *)icon { if ([icon trackingRectNum] > 0) { NSLog(@"addTrackingRectForIcon: Icon %@ already has tracking rect " "(num %d), removing tracking rect first", icon, [icon trackingRectNum]); [self removeTrackingRectForIcon:icon]; } int trackingRectNum = [self addTrackingRect:[icon trackingRectBounds] owner:self userData:icon // pass icon as user data assumeInside:[self mouse:[self mouseLoc] inRect:[icon trackingRectBounds]]]; // don't use isMouseOver [icon setTrackingRectNum:trackingRectNum]; } - (void)removeTrackingRectForIcon:(BPIcon *)icon { if ([icon trackingRectNum] > 0) { [self removeTrackingRect:[icon trackingRectNum]]; [icon setTrackingRectNum:0]; } } - (void)addTrackingRectForIconGroup:(BPIconGroup *)group { if ([group trackingRectNum] > 0) { NSLog(@"addTrackingRectForIconGroup: Group %@ already has tracking rect " "(num %d), removing tracking rect first", group, [group trackingRectNum]); [self removeTrackingRectForIconGroup:group]; } int trackingRectNum = [self addTrackingRect:[group trackingRectBounds] owner:self userData:group // pass group as user data assumeInside:[self mouse:[self mouseLoc] inRect:[group trackingRectBounds]]]; // don't use isMouseOver [group setTrackingRectNum:trackingRectNum]; } - (void)removeTrackingRectForIconGroup:(BPIconGroup *)group { if ([group trackingRectNum] > 0) { [self removeTrackingRect:[group trackingRectNum]]; [group setTrackingRectNum:0]; } } - (void)resetTrackingRectsForIcons:(NSArray *)theIcons { // remove then add a new tracking rect for each icon BPIcon *icon; int i; for (i=0; i<[theIcons count]; i++) { icon = [theIcons objectAtIndex:i]; [self removeTrackingRectForIcon:icon]; [self addTrackingRectForIcon:icon]; } } - (void)resetTrackingRectsForIconGroup:(BPIconGroup *)group { // remove then add a new tracking rect for group [self removeTrackingRectForIconGroup:group]; [self addTrackingRectForIconGroup:group]; } - (void)resetTrackingRects { // reset all icons' and groups' tracking rects int i; BPIconGroup *group; for (i=0; i<[iconGroups count]; i++) { group = [iconGroups objectAtIndex:i]; [self resetTrackingRectsForIconGroup:group]; [self resetTrackingRectsForIcons:[group icons]]; } } - (void)resetViewTrackingRect { // reset tracking rect for whole window if (boundsTrackingRectNum > 0) [self removeTrackingRect:boundsTrackingRectNum]; boundsTrackingRectNum = [self addTrackingRect:[self bounds] owner:self userData:@"self" assumeInside:YES]; } // called automatically after a view is moved, resized, or scrolled - (void)resetCursorRects { // reset items' tracking rects [self resetTrackingRects]; // reset tracking rect for whole window [self resetViewTrackingRect]; //NSLog(@"resetting cursor rects"); [super resetCursorRects]; } #pragma mark - #pragma mark --- mouse events --- - (BOOL)draggingSingleIcon { return ([self isIcon:mainDragItem] && [itemsBeingDragged count] == 1); } - (BOOL)draggingAllIconsInGroup { if ([self isIcon:mainDragItem]) { if (![[(BPIcon *)mainDragItem iconGroup] isDeletable]) return NO; if ([selectedIcons count] == 0) { // if there's only 1 item in the group being dragged, return true if ([[[(BPIcon *)mainDragItem iconGroup] icons] count] == 1) { return YES; } } else { // if selected icons consists of just all the icons in 1 group, return true NSArray *groupsWithSelectedIcons = [self groupsForIcons:[selectedIcons allObjects]]; if ([groupsWithSelectedIcons count] == 1) { BPIconGroup *group = [groupsWithSelectedIcons objectAtIndex:0]; if ([[group icons] count] == [selectedIcons count]) { return YES; } } } } return NO; } - (NSArray *)itemsWithMouseOverInList:(NSEnumerator *)enumerator forMouseLoc:(NSPoint)point { NSMutableArray *filteredList = [[[NSMutableArray alloc] init] autorelease]; NSObject *item; while (item = [enumerator nextObject]) { if ([item isMouseOver:point]) { [filteredList addObject:item]; } } //NSLog(@"filteredList %@", filteredList); return filteredList; } // returns a BPIcon or BPIconGroup pointer, as the next item that should be // dragged depending on items that have been identified as being currently // "under" the mouse. // Returns nil if nothing is under the mouse. - (id)itemUnderMouseAtPoint:(NSPoint)mouseLoc filterForMouseOver:(BOOL)filter { //NSLog(@"iconsContainingCursor %@", iconsContainingCursor); // icons take precedent over groups - i.e. if mouse is over an icon, the // icon should be dragged, not the group. So, check icons array first // in this if-else statement. // Calls itemsWithMouseOverInList:forMouseLoc: to filter the list so that // items are ignored if mouse has been clicked in the item's alpha transparent // region. NSArray *items = nil; if ([iconsContainingCursor count] > 0) { if (filter) { items = [self itemsWithMouseOverInList:[iconsContainingCursor objectEnumerator] forMouseLoc:mouseLoc]; } else { items = [iconsContainingCursor allObjects]; } } if ( [iconsContainingCursor count] == 0 || (items != nil && [items count] == 0) ) { if ([iconGroupsContainingCursor count] > 0) { if (filter) { items = [self itemsWithMouseOverInList:[iconGroupsContainingCursor objectEnumerator] forMouseLoc:mouseLoc]; } else { items = [iconGroupsContainingCursor allObjects]; } } } if (items == nil || (items != nil && [items count] == 0) ) { return nil; } // work out which icon or group to return //NSLog(@"items %@", items); // there's only 1 icon/group, so return it if ([items count] == 1) return [items objectAtIndex:0]; // todo should figure out that if icon is more over one pile than the other, // then the dest pile should be that one. Cos currently if you're over 2 piles // then the front one is picked, even if the icon is almost totally on top // of another pile. // work out which icon/group // icon most at the "front" on the display is the LAST in the sorted array // (it's sorted ascending by depth, so front one is last) NSArray *sortedItems = [items sortedArrayUsingSelector:depthCompareSel]; //NSLog(@"sortedItems %@", sortedItems); return [sortedItems lastObject]; } - (id)itemUnderMouseAtPoint:(NSPoint)mouseLoc { return [self itemUnderMouseAtPoint:mouseLoc filterForMouseOver:YES]; } /* - (void)mouseMoved:(NSEvent *)event { //NSLog(@"moved"); if ([iconsContainingCursor count] == 0) return; id itemUnderMouse = [self itemUnderMouseAtPoint:[self mouseLoc:event]]; if (itemUnderMouse == nil) { [displayController stopBrowse]; } else if ([itemUnderMouse isKindOfClass:[BPIcon class]]) { if (currentMouseOverIcon != itemUnderMouse) { [displayController startBrowseOnIcon:itemUnderMouse]; NSLog(@"browse %@", itemUnderMouse); [self setNeedsDisplayInRect:[itemUnderMouse bounds]]; } currentMouseOverIcon = itemUnderMouse; } } */ - (void)mouseEntered:(NSEvent *)event { //NSLog(@"mouse entered %@", [event userData]); if ([event userData] == @"self") { [displayController mouseEnteredDisplay]; return; } NSObject *item = [event userData]; if (![self isIcon:item] && ![self isGroup:item]) return; // add item as being a // candidate for being dragged for the mouse is pressed [[self itemsContainingCursorForType:item] addObject:item]; // so if the icon/group has any mouse-over actions //[item mouseEnteredTrackingRect]; id topItem = [self itemUnderMouseAtPoint:[self mouseLoc:event] filterForMouseOver:NO]; [topItem mouseEnteredTrackingRect]; [self setNeedsDisplayInRect:[BPUtil padRect:[item trackingRectBounds] amount:2]]; } - (void)mouseExited:(NSEvent *)event { //NSLog(@"mouse exit %@", [event userData]); if ([event userData] == @"self") { [displayController mouseExitedDisplay]; return; } NSObject *item = [event userData]; if (![self isIcon:item] && ![self isGroup:item]) return; // remove item as being a // candidate for being dragged for the mouse is pressed [[self itemsContainingCursorForType:item] removeObject:item]; // so if the icon/group has any mouse-off actions [item mouseExitedTrackingRect]; [self setNeedsDisplayInRect:[BPUtil padRect:[item trackingRectBounds] amount:2]]; } // assumes itemsBeingDragged is sorted by depthCompare. - (void)resetIconReferenceForBaseOfNewPile { // nothing to do if not dragging an icon if (!([self isIcon:mainDragItem])) return; // if there's only 1 icon to drag if ([itemsBeingDragged count] == 1) { iconReferenceForBaseOfNewPile = (BPIcon *)mainDragItem; return; } // are all icons to move inside the same pile? BOOL allDragIconsInsideSamePile = YES; BPIconGroup *groupOfmainDragItem = [(BPIcon *)mainDragItem iconGroup]; BPIcon *icon; int i; for (i=0; i<[itemsBeingDragged count]; i++) { icon = [itemsBeingDragged objectAtIndex:i]; if ([icon iconGroup] != groupOfmainDragItem) { allDragIconsInsideSamePile = NO; break; } } if (allDragIconsInsideSamePile) { // make the icon reference to be the bottom-most icon in the pile iconReferenceForBaseOfNewPile = [itemsBeingDragged objectAtIndex:0]; } else { iconReferenceForBaseOfNewPile = (BPIcon *)mainDragItem; } } - (void)mouseDown:(NSEvent *)event { [displayController mouseDownInDisplay]; // finish editing pile labels int i; for (i=0; i<[iconGroups count]; i++) { [[[iconGroups objectAtIndex:i] labelTextField] finishEditing:nil]; } NSPoint mouseLoc = [self mouseLoc:event]; *mouseDownPoint = mouseLoc; // what item has been clicked on? (nil if nothing clicked) clickedItem = [self itemUnderMouseAtPoint:mouseLoc]; //NSLog(@"clicked %@", clickedItem); unsigned int flags = [event modifierFlags]; if (flags & NSCommandKeyMask) { // Doing Command-click, so we're selecting/deselecting items if (clickedItem != nil) { // selecting/deselecting certain items if ([self isIcon:clickedItem]) { // Select/deselect the clicked icon [self toggleSelectionOfIcon:(BPIcon *)clickedItem]; [displayController clickedIconToToggleSelect:(BPIcon *)clickedItem]; } else { // ignore select/deselect of groups for now } } else { // user is modifying selection [self startSelectionByDraggingRectAtPoint:mouseLoc]; } } else { // set mainDragItem to whatever the mouse hovering over mainDragItem = clickedItem; // user is not moving items if (mainDragItem == nil) { // user is starting a selection rectangle? [self clearSelectedIcons]; [self startSelectionByDraggingRectAtPoint:mouseLoc]; return; } // We're dragging stuff. // initiate dragging of items // if dragging all items in a group, pretend you're dragging the whole group if ([self draggingAllIconsInGroup]) { mainDragItem = [(BPIcon *)mainDragItem iconGroup]; } // add item to the list of items to be dragged [itemsBeingDragged addObject:mainDragItem]; if ([self isGroup:mainDragItem]) { // Dragging a group. // mainDragItem was set as the group. // put all the group's icons into the itemsBeingDragged list [itemsBeingDragged addObjectsFromArray:[(BPIconGroup *)mainDragItem icons]]; } else { // Dragging icons. BPIcon *mainDragIcon = (BPIcon *)mainDragItem; // set flag for shifting icons horizontally [mainDragIcon setHasMovedOutOfGroupBefore:NO]; if ([selectedIcons count] > 0) { // dragging > 1 icon if ([selectedIcons containsObject:mainDragItem]) { // User is dragging the selected icons. // so mainDragItem isn't duplicated when it's added as part // of the selected icons as well [itemsBeingDragged removeObject:mainDragItem]; // Add selected icons to list of items to be dragged [itemsBeingDragged addObjectsFromArray:[selectedIcons allObjects]]; // sort the selected icons NSArray *sorted = [itemsBeingDragged sortedArrayUsingSelector:depthCompareSel]; [itemsBeingDragged removeAllObjects]; [itemsBeingDragged addObjectsFromArray:sorted]; } else { // User has selected icons but dragged on an icon that // wasn't selected. [self clearSelectedIcons]; } } // add dragged item to the selection, if not already added [self selectIcon:mainDragIcon]; [self setFocusIcon:mainDragIcon]; [self setNeedsDisplayInRect:[[mainDragIcon iconGroup] bounds]]; } // define offsets to mouse position, for dragging items NSEnumerator *enumerator = [itemsBeingDragged objectEnumerator]; NSObject *item; NSSize offset; while (item = [enumerator nextObject]) { offset.width = [item position].x - mouseLoc.x; offset.height = [item position].y - mouseLoc.y; [item setPositionOffset:offset]; } // initiate the positions of the dragging forms of the items to be dragged enumerator = [itemsBeingDragged objectEnumerator]; // reset the enumerator while (item = [enumerator nextObject]) [item moveDragFormWithOffsetToPoint:mouseLoc]; // reset which icon the dummy pile outline should be outlined around, for // if the icons get dragged into empty space [self resetIconReferenceForBaseOfNewPile]; } } - (NSRect)baseRectForGroup:(BPIconGroup *)group { BPIcon *destGroupBaseIcon; NSRect destGroupBaseBounds; if ([[group icons] count] > 0) { // get a rect around the bottom icon of the group // the padding is to enforce the default spacing between piles // when testing whether a new pile can be put here destGroupBaseIcon = [[group icons] objectAtIndex:0]; destGroupBaseBounds.origin = [destGroupBaseIcon dragFormPosition]; destGroupBaseBounds.size = [destGroupBaseIcon bounds].size; //destGroupBaseBounds = [BPIconGroup addDefaultPaddingToBounds:destGroupBaseBounds]; } else { destGroupBaseBounds = [group bounds]; } return destGroupBaseBounds; } - (BPIconGroup *)groupBaseIntersectsBase:(NSRect)testBounds ignoringGroup:(BPIconGroup *)ignoreGroup { BPIconGroup *group; BPIcon *destGroupBaseIcon; NSRect destGroupBaseBounds; int i; for (i=0; i<[iconGroups count]; i++) { group = [iconGroups objectAtIndex:i]; if (group == ignoreGroup) continue; destGroupBaseBounds = [self baseRectForGroup:group]; if (NSIntersectsRect(destGroupBaseBounds, testBounds)) return group; } return nil; } - (BOOL)newGroupAllowedAtPoint:(NSPoint)point withBaseIcon:(BPIcon *)baseIcon ignoringGroup:(BPIconGroup *)groupToIgnore { NSRect newGroupBounds; if (baseIcon == nil) { newGroupBounds.origin = point; newGroupBounds.size = [BPIconGroup startingSize]; } else { newGroupBounds.origin = [baseIcon dragFormPosition]; newGroupBounds.size = [baseIcon bounds].size; } return ([self groupBaseIntersectsBase:newGroupBounds ignoringGroup:groupToIgnore] == nil); } - (BOOL)newGroupAllowedAtPoint:(NSPoint)point withBaseIcon:(BPIcon *)baseIcon { return [self newGroupAllowedAtPoint:point withBaseIcon:baseIcon ignoringGroup:nil]; } - (BPIconGroup *)groupToReceiveCurrentDragItem { // establish bounds for testing whether the drag item/s intersect with // any groups NSRect testBounds; testBounds.origin = [mainDragItem dragFormPosition]; // following code assumes iconGroups is sorted using depthCompare, so that // we can search through groups in order of their z-depth // (so you search through the more front groups first) int i; BPIconGroup *group; NSPoint mouseLoc = [self mouseLoc]; if ([self isGroup:mainDragItem]) { // if mouse is over a group, return it for (i=0; i<[iconGroups count]; i++) { group = [iconGroups objectAtIndex:i]; if (group != mainDragItem) if ([self mouse:mouseLoc inRect:[group bounds]]) return group; } /* // test if the group intersects with any other group testBounds.size = [BPIconGroup startingSize]; // if base of pile intersects with another group, return it for (i=0; i<[iconGroups count]; i++) { group = [iconGroups objectAtIndex:i]; if (group != mainDragItem) if (NSIntersectsRect([group bounds], testBounds)) return group; } */ return [self groupBaseIntersectsBase:[self baseRectForGroup:(BPIconGroup *)mainDragItem] ignoringGroup:(BPIconGroup *)mainDragItem]; } else { /* // if mouse is within mainDragItem's group, return its group BPIconGroup *ownGroup = [(BPIcon *)mainDragItem iconGroup]; //if (NSRectContainsPoint([ownGroup bounds], mouseLoc)) { if ([ownGroup isMouseOver:mouseLoc]) return ownGroup; */ // if mouse is over another group, return the group for (i=0; i<[iconGroups count]; i++) { group = [iconGroups objectAtIndex:i]; if ([group isMouseOver:mouseLoc]) return group; } // if mouse is not over a group, but the base reference icon bounds of any // group intersects with this group, return that group if (iconReferenceForBaseOfNewPile == nil) { NSLog(@"groupToReceiveCurrentDragItem Error: iconReferenceForBaseOfNewPile == nil," "Setting iconReferenceForBaseOfNewPile = mainDragItem"); iconReferenceForBaseOfNewPile = (BPIcon *)mainDragItem; } // reset the testBounds testBounds.origin = [iconReferenceForBaseOfNewPile dragFormPosition]; testBounds.size = [iconReferenceForBaseOfNewPile bounds].size; // add group defaultOutlinePadding onto all 4 sides of the testBounds //testBounds = [BPIconGroup addDefaultPaddingToBounds:testBounds]; return [self groupBaseIntersectsBase:testBounds ignoringGroup:[(BPIcon *)mainDragItem iconGroup]]; } } - (BOOL)horizontalDragOnlyForIcon:(BPIcon *)icon atDragPoint:(NSPoint)point { if ([icon hasMovedOutOfGroupBefore]) return NO; int currentY = point.y; int originalY = mouseDownPoint->y; // return YES if icon hasn't been dragged up/down too far from original posn return (currentY < originalY + 10 && currentY > originalY - 10); } - (void)resetDummyGroupOutline { if (iconReferenceForBaseOfNewPile == nil) { NSLog(@"resetDummyGroupOutline Error: iconReferenceForBaseOfNewPile == nil," "Setting iconReferenceForBaseOfNewPile = mainDragItem"); iconReferenceForBaseOfNewPile = (BPIcon *)mainDragItem; } // get bounds for the new outline NSRect baseIconRect; baseIconRect.origin = [iconReferenceForBaseOfNewPile dragFormPosition]; baseIconRect.size = [iconReferenceForBaseOfNewPile bounds].size; NSRect newGroupOutline = [BPIconGroup defaultOutlineForBaseIconBounds:baseIconRect forIconCount:[itemsBeingDragged count]-1]; // -1 just cos it works /* NSRect newGroupOutline; newGroupOutline.size = [BPIconGroup outlineSizeForIconCount:[itemsBeingDragged count]]; newGroupOutline.origin = [iconReferenceForBaseOfNewPile dragFormPosition]; newGroupOutline.origin.x -= [BPIconGroup defaultOutlinePadding]; newGroupOutline.origin.y -= [BPIconGroup defaultOutlinePadding]; */ // record the new outline NSBezierPath *newPath = [[NSBezierPath bezierPathWithRect:newGroupOutline] retain]; [dummyGroupOutline release]; dummyGroupOutline = newPath; } - (void)mouseDragged:(NSEvent *)event { dragging = YES; NSPoint mouseLoc = [self mouseLoc:event]; if (draggingSelectionRect) { // user is dragging a selection rectangle [self resizeSelectionRectToPoint:mouseLoc]; [self updateDragSelectionForPoint:mouseLoc]; [self setNeedsDisplay:YES]; } // not dragging items? if (mainDragItem == nil) return; // user is dragging items BOOL shiftHorizontallyOnly = NO; if ([self isIcon:mainDragItem] && ![(BPIcon *)mainDragItem hasMovedOutOfGroupBefore]) { // dragging icon/s within own group, and icon hasn't moved out of its group yet shiftHorizontallyOnly = [self horizontalDragOnlyForIcon:(BPIcon *)mainDragItem atDragPoint:mouseLoc]; if (!shiftHorizontallyOnly && [self draggingSingleIcon]) { // mac-dock dragging. Move other icons in response to icon's current drag position. //NSLog(@"mac-dock dragging"); } } // highlight destination pile BPIconGroup *destGroup = [self groupToReceiveCurrentDragItem]; // hide the outline of previous destination groups we hovered over // if no longer within them BPIconGroup *group; int j; for (j=0; j<[groupsHoveringOver count]; j++) { group = [groupsHoveringOver objectAtIndex:j]; if (group != destGroup) { [group setShowOutline:NO]; [group setShowAsDropTarget:NO]; } } if (destGroup == nil) { // not hovering over a group [groupsHoveringOver removeAllObjects]; [mainDragItem setOnDragDest:NO]; if ([self isIcon:mainDragItem]) { [(BPIcon *)mainDragItem setHasMovedOutOfGroupBefore:YES]; // can move icons here, so show some kind of dummy pile outline [self resetDummyGroupOutline]; showDummyGroupOutline = YES; } else { // can move group into this new position showDummyGroupOutline = NO; [(BPIconGroup *)mainDragItem setShowOutline:YES]; } } else { // hovering over a group - i.e. there's a destination pile showDummyGroupOutline = NO; [groupsHoveringOver addObject:destGroup]; [mainDragItem setOnDragDest:YES]; if ([self isIcon:mainDragItem] && destGroup != [(BPIcon *)mainDragItem iconGroup]) [(BPIcon *)mainDragItem setHasMovedOutOfGroupBefore:YES]; BOOL draggedIconHasntMovedOutOfItsGroup = ([self isIcon:mainDragItem] && ![(BPIcon *)mainDragItem hasMovedOutOfGroupBefore]); if (!draggedIconHasntMovedOutOfItsGroup) [destGroup setShowAsDropTarget:YES]; } // now finally, move the items NSPoint destPoint = mouseLoc; // is user just sliding items horizontally within the piles? if (shiftHorizontallyOnly) { destPoint.y = mouseDownPoint->y; } // move the drag images for each icon to be dragged NSObject *item; NSEnumerator *enumerator = [itemsBeingDragged objectEnumerator]; while (item = [enumerator nextObject]) { [item moveDragFormWithOffsetToPoint:destPoint]; } [self setNeedsDisplay:YES]; } // whether it's considered a "mouse clicked" at the given mouse up point // i.e. was it mouse down then mouse up without moving the mouse - (BOOL)mouseClickedForMouseUpPoint:(NSPoint)point { return ( abs(point.x - mouseDownPoint->x) < 1 && abs(point.y - mouseDownPoint->y) < 1 ); } - (BOOL)iconMovedMinimumDistanceFromDefaultPosition:(BPIcon *)icon { NSPoint iconDragPosn = [icon dragFormPosition]; NSPoint iconDefaultPosn = [[icon iconGroup] defaultPositionForIcon:icon]; NSPoint iconPosn = [icon position]; // only continue calculation for icons that are already in default posn if (!NSEqualPoints(iconPosn, iconDefaultPosn)) { return YES; } BOOL exceededMinimumHorizDrag = ( iconDragPosn.x > iconDefaultPosn.x + 5 || iconDragPosn.x < iconDefaultPosn.x - 5 ); BOOL exceededMinimumVerticalDrag = ( iconDragPosn.y > iconDefaultPosn.y + 5 || iconDragPosn.y < iconDefaultPosn.y - 5 ); return (exceededMinimumHorizDrag || exceededMinimumVerticalDrag); } - (BOOL)doingHorizontalSlideForIcon:(BPIcon *)icon { return ([icon dragFormPosition].y == [icon position].y && ![icon hasMovedOutOfGroupBefore]); } - (void)completeHorizontalSlideForIcons:(NSArray *)icons { NSRect iconGroupBounds; NSRect dragBounds; NSPoint destPoint; BPIcon *icon; [[self undoManager] beginUndoGrouping]; [[self undoManager] setActionName:@"shift items"]; int i; for (i=0; i<[icons count]; i++) { icon = [icons objectAtIndex:i]; dragBounds.origin = [icon dragFormPosition]; dragBounds.size = [icon bounds].size; destPoint.y = [icon position].y; iconGroupBounds = [[icon iconGroup] bounds]; [[[self undoManager] prepareWithInvocationTarget:icon] setPosition:[icon position]]; // if icon's too far over the left or right edges of the group, move // them back into the group a bit when releasing them // ("too far" is more than halfway over the edge of the group) if (NSMidX(dragBounds) > NSMaxX(iconGroupBounds)) { // icon's too far over the right edge of the group destPoint.x = NSMaxX(iconGroupBounds) - (dragBounds.size.width/2); //[icon moveDragFormWithOffsetToPoint:destPoint]; [icon setPosition:destPoint]; } else if (NSMidX(dragBounds) < NSMinX(iconGroupBounds)) { // icon's too far over the left edge of the group destPoint.x = NSMinX(iconGroupBounds) - (dragBounds.size.width/2); //[icon moveDragFormWithOffsetToPoint:destPoint]; [icon setPosition:destPoint]; } else { // this spot is OK [icon drop]; } } [[self undoManager] endUndoGrouping]; } - (void)setTimerForSingleClickForIcon:(BPIcon *)icon { clickTimer = [[NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(timeToDoSingleIconClick:) userInfo:icon repeats:NO] retain]; } - (void)timeToDoSingleIconClick:(NSTimer *)aTimer { [displayController singleClickedIcon:(BPIcon *)[aTimer userInfo]]; // release timer [clickTimer release]; clickTimer = nil; } - (void)completeIconClickOnIcon:(BPIcon *)icon { [self selectIcon:icon]; [self setNeedsDisplayInRect:[[icon iconGroup] bounds]]; //NSLog(@"in completeIconClick"); [displayController clickedIcon:icon]; /* if ([[NSUserDefaults standardUserDefaults] boolForKey:@"MailStacker_clickBrowsing"]) { [displayController browseOnIcon:icon]; } else {*/ if (clickTimer != nil && [clickTimer isValid]) { // clickTimer is valid, which means the timer is running, meaning // we've already had 1 click, so now do double-click action // switch off timer, so that the single-click action won't happen [clickTimer invalidate]; // release timer [clickTimer release]; clickTimer = nil; // do double-click action [displayController doubleClickedIcon:icon]; } else { // clickTimer hasn't been started, so user is either clicking once // or doing the 1st click of a double-click [self setTimerForSingleClickForIcon:icon]; } //} } - (BOOL)completeIconDrag { BPIcon *mainDragIcon = (BPIcon *)mainDragItem; BPIconGroup *destGroup = [self groupToReceiveCurrentDragItem]; //NSLog(@"completeIconDrag"); if (destGroup == [mainDragIcon iconGroup]) { // dropping icon/s back within its own group if ([self doingHorizontalSlideForIcon:mainDragIcon]) { // icon is being slided horizontally [self completeHorizontalSlideForIcons:itemsBeingDragged]; //NSLog(@"Horizontal slide"); return YES; } else { // for now, if the icon's dropped back in own pile but not doing // horizontal slide, snap back // (Do mac-dock effect later if possible, to allow drag-drop // re-ordering of icons within a group //NSLog(@"Drag-drop within own group unsuccessful"); return NO; } } else { // dropping icon/s into space, or into another group BOOL successfulDrop = YES; if (destGroup == nil) { // dropping icon/s into space //NSLog(@"drop icons into space"); NSPoint newGroupPosition = [dummyGroupOutline bounds].origin; if ([self newGroupAllowedAtPoint:newGroupPosition withBaseIcon:mainDragIcon ignoringGroup:[mainDragIcon iconGroup]]) { // move icons into a new group in this empty spot BPIconGroup *newGroup = [self addNewIconGroupAtPoint:newGroupPosition]; [self moveIcons:itemsBeingDragged toGroup:newGroup]; //NSLog(@"Dragged icons into new pile"); } else { // drop unsuccessful - couldn't make a new group here //NSLog(@"Can't drop icons here - new group not allowed here"); successfulDrop = NO; } } else { // dropping icon/s onto another group // move icons to the other group [self moveIcons:itemsBeingDragged toGroup:destGroup]; // stop showing group as a drop destination [destGroup setShowAsDropTarget:NO]; //NSLog(@"moved icons into another group"); } // user might have just removed all icons from a group, so // delete the group/s if they're empty if (successfulDrop) [self removeEmptyIconGroups]; return successfulDrop; } } - (BOOL)completeIconGroupDrag { BPIconGroup *group = (BPIconGroup *)mainDragItem; BPIconGroup *destGroup = [self groupToReceiveCurrentDragItem]; if (destGroup == nil) { // moving group into empty space if ([self newGroupAllowedAtPoint:[self mouseLoc] withBaseIcon:([[group icons] count] > 0? [[group icons] objectAtIndex:0] : nil) ignoringGroup:group]) { // drop group here [[self undoManager] beginUndoGrouping]; [[self undoManager] setActionName:@"move pile"]; [[[self undoManager] prepareWithInvocationTarget:group] setPosition:[group position]]; [[self undoManager] endUndoGrouping]; [group drop]; //NSLog(@"Dragged group to new position"); } else { // unsuccessful drop //NSLog(@"Can't drag group here - new group not allowed here"); return NO; } } else { // merge group into another group BPIconGroup *sourceGroup = group; // move group's icons to the dest group // (can't just call [self moveIcons:[group icons] toGroup:destGroup] // because it deallocates the group or something, icons disappear) [itemsBeingDragged removeObject:sourceGroup]; [self moveIcons:itemsBeingDragged toGroup:destGroup]; // stop showing group as a drop destination [destGroup setShowAsDropTarget:NO]; if ([sourceGroup isDeletable]) { // delete the dragged group [[self undoManager] beginUndoGrouping]; [[self undoManager] setActionName:@"move items"]; [[[self undoManager] prepareWithInvocationTarget:self] undoMergedIconGroupAtPoint:[sourceGroup position] withMovedIcons:[NSArray arrayWithArray:itemsBeingDragged] label:[sourceGroup label]]; [[self undoManager] endUndoGrouping]; [self removeIconGroup:sourceGroup]; } //NSLog(@"merged group with another group"); } return YES; } - (void)cancelItemDragging { // cancel dragging mainDragItem = nil; iconReferenceForBaseOfNewPile = nil; [itemsBeingDragged removeAllObjects]; dragging = NO; } - (void)mouseUp:(NSEvent *)event { [self stopSelectionByDraggingRect]; [self setNeedsDisplay:YES]; showDummyGroupOutline = NO; // return if didn't click on any items if (mainDragItem == nil) return; NSPoint mouseLoc = [self mouseLoc:event]; BOOL didDragIcons = NO; if ([self mouseClickedForMouseUpPoint:mouseLoc]) { // clicked on something //NSLog(@"mouse click on %@", mainDragItem); if ([self isIcon:clickedItem]) { // clicked on icon/s (completeIconClick works out whether single or dbl-click) [self completeIconClickOnIcon:clickedItem]; } else if ([self isGroup:mainDragItem]) { // perform click on the group's label //[[(BPIconGroup *)mainDragItem labelTextField] performClick:nil]; } } else { // completing a drag-and-drop // if dragging icon/s, don't do anything unless it's moved a minimum distance // from its original position BOOL doDrag = YES; if ([self isIcon:mainDragItem] && ![self iconMovedMinimumDistanceFromDefaultPosition:(BPIcon *)mainDragItem]) doDrag = NO; if (doDrag) { if ([self isGroup:mainDragItem]) { // dragging group didDragIcons = [self completeIconGroupDrag]; } else if ([self isIcon:mainDragItem]) { // dragging icon/s didDragIcons = [self completeIconDrag]; } } } if (didDragIcons) { [self clearSelectedIcons]; } else { // snap back items to their original positions int i; for (i=0; i<[itemsBeingDragged count]; i++) [[itemsBeingDragged objectAtIndex:i] snapBackWithOffsetToPoint:*mouseDownPoint]; //NSLog(@"Snapped back items to original positions"); } [self cancelItemDragging]; //[self setNeedsDisplay:YES]; // DON'T NEED THIS????? //NSLog(@"completed mouseUp"); } #pragma mark - #pragma mark --- key events --- - (BOOL)performKeyEquivalent:(NSEvent *)theEvent { return [displayController performKeyEquivalentInDisplay:theEvent]; } - (void)scrollWheel:(NSEvent *)theEvent { [displayController scrolledWheelInDisplay:theEvent]; } - (NSMenu *)menuForEvent:(NSEvent *)theEvent { BPIcon *iconUnderMouse = [self itemUnderMouseAtPoint:[self mouseLoc:theEvent] filterForMouseOver:YES]; if (iconUnderMouse != nil) { [iconsToColour removeAllObjects]; if ([selectedIcons count] > 0 && [selectedIcons containsObject:iconUnderMouse]) { [iconsToColour addObjectsFromArray:[selectedIcons allObjects]]; } else { [iconsToColour addObject:iconUnderMouse]; } return contextMenu; } else { return nil; } } - (NSArray *)iconsToColour { return iconsToColour; } #pragma mark - - (void)dealloc { NSLog(@"destroying %@", self); // stop requesting notifications [[NSNotificationCenter defaultCenter] removeObserver:self]; // [icons release]; [iconGroups release]; [iconGroupsContainingCursor release]; [iconsContainingCursor release]; [mainDragItem release]; [itemsBeingDragged release]; free(mouseDownPoint); [groupsHoveringOver release]; [selectedIcons release]; free(selectionRect); [emptyTrashButton release]; [iconsToColour release]; [super dealloc]; } @end