#import "BPPilesDisplayController.h" #import "BPPilesDisplay.h" #import "BPIcon.h" #import "BPIconGroup.h" #import "BPDocumentGroup.h" #import "BPDocument.h" #import "BPEmailDocument.h" #import "BPDocumentViewManager.h" #import "BPPilesDataController.h" #import "BPUtil.h" #import "BPIconGroupLabel.h" #import "BPIconManager.h" #import "BPSearcher.h" @implementation BPPilesDisplayController - (id)init { [super init]; pilesToIconGroups = [[NSMutableDictionary alloc] initWithCapacity:50]; shortBrowseDelay = 0.06; longBrowseDelay = 0.06; browseDelay = shortBrowseDelay; return self; } - (void)registerForNotifications { NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; [defaultCenter addObserver:self selector:@selector(handleNotification:) name:@"IconGroup_Created" object:nil]; [defaultCenter addObserver:self selector:@selector(handleNotification:) name:@"IconGroup_Deleted" object:nil]; } - (void)awakeFromNib { [docViewManager setDelegate:self]; [self registerForNotifications]; } - (void)handleNotification:(NSNotification *)notif { if ([[notif name] isEqual:@"IconGroup_Created"]) { [self newGroupInDisplay:[[notif userInfo] objectForKey:@"IconGroup"]]; } else if ([[notif name] isEqual:@"IconGroup_Deleted"]) { [self deletedGroupFromDisplay:[[notif userInfo] objectForKey:@"IconGroup"]]; } } // returns YES if the mouse is hovering over any pile or icon - (BOOL)mouseOverAnyItem { return ([pilesDisplay itemUnderMouseAtPoint:[pilesDisplay mouseLoc]] != nil); } #pragma mark - #pragma mark browsing // BPDocumentViewManager delegate methods - (void)tempDocViewerClosed:(id)sender { // if you don't reset the tracking rects, if you've just stopped browsing // then the mouse enter/exits for the pilesDisplay are stuffed, cos for // some reason it thinks the tempDocViewer is still there (even though it's // closed now) and will do mouse exit on the pilesDisplay if you hover over // where the tempDocViewer was // only need to reset tracking rects for groups intersecting with viewer NSRect browserFrame = [sender frame]; NSArray *groups = [pilesDisplay iconGroups]; int i; BPIconGroup *group; for (i=0; i<[groups count]; i++) { group = [groups objectAtIndex:i]; if (NSIntersectsRect([group bounds], browserFrame)) { [pilesDisplay resetTrackingRectsForIconGroup:group]; [pilesDisplay resetTrackingRectsForIcons:[group icons]]; } } // and also reset for whole view [pilesDisplay resetViewTrackingRect]; } - (BOOL)canHideTempDocViewer:(id)sender { // don't hide the viewer if mouse is still inside an icon group if ([self mouseOverAnyItem]) return NO; return YES; } // prefer to show viewer on the right side of the icon, but if this would // make the window disappear off the screen, put it on the left side - (NSPoint)bestTempViewerTopLeftDisplayPointForIcon:(BPIcon *)icon onIconSide:(NSRectEdge *)whichSideOfIcon { // calculate possible display coordinates (if the viewer was on the left // or the right of the icon), converting it to screen coordinates // For x-coord, the viewer should be attached to either the right edge or // left edge of the icon's group's outline // For y-coord, the viewer should be attached on the level of the top edge // of the icon NSRect screenBounds = [[NSScreen mainScreen] frame]; NSRect pileBounds = [[icon iconGroup] bounds]; float iconTopEdgeY = NSMaxY([icon bounds]); NSPoint rightSideOfIcon = NSMakePoint(NSMaxX(pileBounds), iconTopEdgeY); rightSideOfIcon = [[pilesDisplay window] convertBaseToScreen:[pilesDisplay convertPoint:rightSideOfIcon toView:nil]]; NSPoint leftSideOfIcon = NSMakePoint(NSMinX(pileBounds), iconTopEdgeY); leftSideOfIcon = [[pilesDisplay window] convertBaseToScreen:[pilesDisplay convertPoint:leftSideOfIcon toView:nil]]; // now see whether viewer should be shown on the left or right side of the icon NSRect tempViewerFrame = [docViewManager tempViewerFrameRectForDocument:[icon representedObject]]; BOOL spillOverRightEdgeOfScreen = NO; BOOL spillOverLeftEdgeOfScreen = NO; float rightSpill; float leftSpill; // test if spill over right side of screen tempViewerFrame.origin.x = rightSideOfIcon.x; rightSpill = NSMaxX(tempViewerFrame) - NSMaxX(screenBounds); spillOverRightEdgeOfScreen = rightSpill > 0; //(NSMaxX(tempViewerFrame) > NSMaxX(screenBounds)); // test if spill over left side of screen tempViewerFrame.origin.x = leftSideOfIcon.x - tempViewerFrame.size.width; leftSpill = tempViewerFrame.origin.x > 0 ? 0 : -(tempViewerFrame.origin.x); spillOverLeftEdgeOfScreen = leftSpill > 0; //(NSMinX(tempViewerFrame) < NSMinX(screenBounds)); /* // if you can't put it on the right but you can put it on the left, put it // on the right. Otherwise put on the right (i.e. if it spills over both ways) NSPoint decidedOrigin; decidedOrigin.y = rightSideOfIcon.y - tempViewerFrame.size.height; // either y will do. minus height cos it's flipped. if (spillOverRightEdgeOfScreen && !spillOverLeftEdgeOfScreen) { // put it on the left decidedOrigin.x = leftSideOfIcon.x - tempViewerFrame.size.width; *whichSideOfIcon = NSMinXEdge; // use NSRectEdge as NSDrawer uses it } else { // put it on the right decidedOrigin.x = rightSideOfIcon.x; *whichSideOfIcon = NSMaxXEdge; } */ // put it on the side where it spills over less // do a check like (rightSpill > tempViewerFrame.size.width*0.2) as well // because even if it fits better on the left, it's still easier to read on // the right because you want to read the beginning of the headers, etc. NSPoint decidedOrigin; decidedOrigin.y = rightSideOfIcon.y - tempViewerFrame.size.height; // either y will do. minus height cos it's flipped. if (leftSpill < rightSpill && rightSpill > tempViewerFrame.size.width*0.2) { // put it on the left decidedOrigin.x = leftSideOfIcon.x - tempViewerFrame.size.width; *whichSideOfIcon = NSMinXEdge; // use NSRectEdge as NSDrawer uses it } else { // put it on the right decidedOrigin.x = rightSideOfIcon.x; *whichSideOfIcon = NSMaxXEdge; } // if displaying the viewer here would cause it to be partly below the dock, // move it up so the whole viewer will be visible // (dunno how to know which edge the dock is on) if (decidedOrigin.y < 60) decidedOrigin.y = 60; // return origin as the top-left corner decidedOrigin.y += tempViewerFrame.size.height; return decidedOrigin; } - (void)stopBrowse { if (lastBrowsedIcon == nil) return; [docViewManager hideTempViewer]; [[lastBrowsedIcon iconGroup] stopBrowseOnIcon:lastBrowsedIcon]; [pilesDisplay setFocusIcon:nil]; } - (void)stopBrowseImmediately { // hide the temp viewer [docViewManager hideTempViewerRegardless]; [self stopBrowse]; } - (void)performStopBrowse { //if ([[docViewManager tempDocViewer] isKeyWindow]) if ([docViewManager mouseInsideTempDocViewer]) return; if ([self canHideTempDocViewer:nil]) { [self stopBrowse]; } } - (void)showTempViewerForIcon:(BPIcon *)icon { /* if (lastBrowsedIcon != icon) { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"MailStacker_startViewsAsMicro"]) { [[icon representedObject] setInMicroView:YES]; } } */ NSRectEdge drawerEdge; NSPoint displayPoint = [self bestTempViewerTopLeftDisplayPointForIcon:icon onIconSide:&drawerEdge]; // sometimes the mouse has missed entering the group somehow //[pilesDisplay resetTrackingRectsForIconGroup:[icon iconGroup]]; //[[icon iconGroup] mouseEnteredTrackingRect]; //NSLog(@"display viewer"); [docViewManager setTempViewerToShowDocument:[icon representedObject] atTopLeftPoint:displayPoint drawerEdge:drawerEdge]; } - (void)browseOnIcon:(BPIcon *)icon { if (icon == nil) return; // make sure the last icon that was browsed is no longer being browsed // (sometimes this happens if a pile didn't get the mouseExited event) if ([lastBrowsedIcon isBeingBrowsed] && [lastBrowsedIcon iconGroup] != [icon iconGroup]) { [self stopBrowseImmediately]; } // change the browsed icon's look, etc. [[icon iconGroup] browseOnIcon:icon]; [pilesDisplay setFocusIcon:icon]; // set the temp viewer to show the document's contents [self showTempViewerForIcon:icon]; lastBrowsedIcon = icon; // reset img if have been read if ([[icon representedObject] isKindOfClass:[BPEmailDocument class]]) { if ([[icon representedObject] hasBeenRead] == NO) { [[icon representedObject] setHasBeenRead:YES]; NSImage *newImg = [BPIconManager iconForDocument:[icon representedObject]]; [[icon representedObject] setIcon:newImg]; [icon setImage:newImg]; } } } - (void)timeToBrowseIcon:(NSTimer *)aTimer { // only browse if the user hasn't moved onto another icon already, and // if the mouse isn't inside the doc viewer (if it is, the user's probably trying // to view the document) //if (nextIconToBrowse == [aTimer userInfo] && // ![docViewManager mouseInsideTempDocViewer]) if (nextIconToBrowse == [aTimer userInfo] && ![[docViewManager tempDocViewer] isKeyWindow]) { [self browseOnIcon:[aTimer userInfo]]; } } - (void)browseWithDelayForIcon:(BPIcon *)icon { NSTimer *aTimer = [NSTimer scheduledTimerWithTimeInterval:browseDelay target:self selector:@selector(timeToBrowseIcon:) userInfo:icon repeats:NO]; } - (void)startBrowseOnIcon:(BPIcon *)icon { nextIconToBrowse = icon; [self browseWithDelayForIcon:icon]; } - (void)browseUpwards { if (lastBrowsedIcon == nil) return; if (![[docViewManager tempDocViewer] isVisible]) return; NSArray *icons = [[lastBrowsedIcon iconGroup] icons]; int index = [icons indexOfObject:lastBrowsedIcon]; if (index+1 >= [icons count]) return; [self browseOnIcon:[icons objectAtIndex:index+1]]; } - (void)browseDownwards { if (lastBrowsedIcon == nil) return; if (![[docViewManager tempDocViewer] isVisible]) return; NSArray *icons = [[lastBrowsedIcon iconGroup] icons]; int index = [icons indexOfObject:lastBrowsedIcon]; if (index == 0 || index == NSNotFound) return; [self browseOnIcon:[icons objectAtIndex:index-1]]; } - (void)openStandaloneViewerForIcon:(BPIcon *)icon { [self stopBrowseImmediately]; // open a standalone viewer [docViewManager openStandaloneViewerForDocument:[icon representedObject]]; } - (void)mouseEnteredIcon:(BPIcon *)icon { if (![[NSUserDefaults standardUserDefaults] boolForKey:@"MailStacker_clickBrowsing"]) { browseDelay = shortBrowseDelay; [self startBrowseOnIcon:icon]; /* id itemUnderMouse = [pilesDisplay itemUnderMouseAtPoint:[pilesDisplay mouseLoc] filterForMouseOver:NO]; if ([itemUnderMouse isKindOfClass:[BPIcon class]]) [self startBrowseOnIcon:icon]; */ } } - (void)mouseExitedIcon:(BPIcon *)icon { if (![[NSUserDefaults standardUserDefaults] boolForKey:@"MailStacker_clickBrowsing"]) { id itemUnderMouse = [pilesDisplay itemUnderMouseAtPoint:[pilesDisplay mouseLoc]]; if ([itemUnderMouse isKindOfClass:[BPIcon class]]) { browseDelay = shortBrowseDelay; [self startBrowseOnIcon:(BPIcon *)itemUnderMouse]; } } } - (void)mouseEnteredIconGroup:(BPIconGroup *)iconGroup { //NSLog(@"mouseEnteredIconGroup %@", iconGroup); browseDelay = shortBrowseDelay; } - (void)mouseExitedIconGroup:(BPIconGroup *)iconGroup { /* // if mouse is not inside any group or icon, hide the temp document viewer id itemUnderMouse = [pilesDisplay itemUnderMouseAtPoint:[pilesDisplay mouseLoc]]; if ([itemUnderMouse isKindOfClass:[BPIcon class]] || [itemUnderMouse isKindOfClass:[BPIconGroup class]]) { [docViewManager setMouseOverPileItem:YES]; } else { [docViewManager setMouseOverPileItem:NO]; } */ //NSLog(@"mouseExitedIconGroup %@", iconGroup); [self performStopBrowse]; browseDelay = shortBrowseDelay; } - (void)mouseDownInDisplay { [self stopBrowseImmediately]; [[pilesDisplay window] makeFirstResponder:nil]; } - (void)mouseEnteredDisplay { //NSLog(@"mouseEnteredDisplay"); // only make key immediately if user is going between main window & // the tempDocViewer if ([[docViewManager tempDocViewer] isVisible]) { //NSLog(@"make display key"); [[pilesDisplay window] makeKeyWindow]; if (![self mouseOverAnyItem]) { [self stopBrowse]; } browseDelay = longBrowseDelay; nextIconToBrowse = nil; } } - (void)mouseExitedDisplay { //NSLog(@"mouseExitedDisplay"); if ([docViewManager mouseInsideTempDocViewer]) { //NSLog(@"make viewer key"); [[docViewManager tempDocViewer] makeKeyWindow]; browseDelay = longBrowseDelay; nextIconToBrowse = nil; } /* BOOL isTypingInTextField = NO; if ([[[pilesDisplay window] firstResponder] isKindOfClass:[NSTextView class]]) isTypingInTextField = YES; if (!isTypingInTextField && [docViewManager mouseInsideTempDocViewer]) { NSLog(@"make viewer key"); [[docViewManager tempDocViewer] makeKeyWindow]; }*/ } - (void)clearedIconSelection { [recommendedLabel release]; recommendedLabel = nil; } #pragma mark - // have to call this whenever you move a whole bunch of icons, doesn't work to // call this everytime an icon is added/removed (cos then the one label changes many times) - (void)setSuitableLabelForGroup:(BPIconGroup *)iconGroup { if (![[NSUserDefaults standardUserDefaults] boolForKey:@"MailStacker_autoLabelPiles"]) return; // use some intelligence to guess a suitable label for the new group BPDocumentGroup *docGroup = [iconGroup representedObject]; if (![docGroup canAutoLabel]) return; if ([[docGroup label] isEqual:@""]) { // set a suitable label NSString *label = [docGroup summary]; if (![label isEqual:@""]) { [iconGroup setAutoLabel:label]; [docGroup setLabel:label]; } } else { NSString *fromPrefix = @"from "; NSString *wasFromPrefix = @"(was 'from "; NSString *removePrefix = @"(was '"; NSString *wasFromEnd = @"')"; if ([[docGroup label] length] >= [fromPrefix length] && [[[docGroup label] substringToIndex:[fromPrefix length]] isEqual:fromPrefix]) { if (![docGroup allDocumentsCorrespondSameAuthor]) { // group's documents used to be all by same person, but now they're not NSString *newLabel = [NSString stringWithFormat:@"(was '%@')", [docGroup label]]; [iconGroup setAutoLabel:newLabel]; [docGroup setLabel:newLabel]; } } else if ([[docGroup label] length] >= [wasFromPrefix length] && [[[docGroup label] substringToIndex:[wasFromPrefix length]] isEqual:wasFromPrefix]) { // group's documents used to be not all by same person, but now they are if ([docGroup allDocumentsCorrespondSameAuthor]) { NSRange substrRange = NSMakeRange([removePrefix length], [[docGroup label] length] - [removePrefix length]-[wasFromEnd length]); NSString *newLabel = [[docGroup label] substringWithRange:substrRange]; [iconGroup setAutoLabel:newLabel]; [docGroup setLabel:newLabel]; } } } //[[pilesDisplay window] makeFirstResponder:[iconGroup labelTextField]]; } - (void)newGroupInDisplay:(BPIconGroup *)newGroup { // set recommended label for group if label exists if ([[newGroup label] isEqual:@""] && recommendedLabel != nil) { [newGroup setLabel:recommendedLabel]; } // get the documents representing the group's icons NSMutableArray *documents = [NSMutableArray arrayWithCapacity:[[newGroup icons] count]]; int i; for (i=0; i<[[newGroup icons] count]; i++) { [documents addObject:[[[newGroup icons] objectAtIndex:i] representedObject]]; } // make new document newGroup for this icon newGroup BPDocumentGroup *newDocGroup = [BPDocumentGroup groupWithLabel:[newGroup label] atPosition:[newGroup position] documents:documents]; // set the IconGroup attribute [newGroup setRepresentedObject:newDocGroup]; // add to dictionary [pilesToIconGroups setObject:newGroup forKey:[NSNumber numberWithInt:[newDocGroup identifier]]]; // notify data [pilesDataController viewCreatedGroup:newDocGroup]; } - (void)deletedGroupFromDisplay:(BPIconGroup *)group { [group retain]; // delete dictionary ref int ident = [(BPDocumentGroup *)[group representedObject] identifier]; [pilesToIconGroups removeObjectForKey:[NSNumber numberWithInt:ident]]; // notify data [pilesDataController viewDeletedGroup:[group representedObject]]; [group release]; } - (void)movedIcons:(NSArray *)theIcons fromGroups:(NSArray *)groups toGroup:(BPIconGroup *)group { // see if label should be auto-changed for each group that had an icon added/removed [self setSuitableLabelForGroup:group]; int i=0; BPIconGroup *currGroup; for (i=0; i<[groups count]; i++) { [self setSuitableLabelForGroup:[groups objectAtIndex:i]]; } } - (void)draggingItemsInDisplay { } - (void)trashIcon:(BPIcon *)icon { if (icon == nil) return; BPIconGroup *trash = [pilesToIconGroups objectForKey:[NSNumber numberWithInt:[[BPPilesDataController TRASH] identifier]]]; if (trash == nil) { NSLog(@"BPPilesDisplayController trashIcon: can't find trash icon group"); return; } BPIconGroup *sourceGroup = [icon iconGroup]; BPIcon *nextToBrowse; NSArray *icons = [sourceGroup icons]; int index = [icons indexOfObject:icon]; if ([icons count] == 1) { // deleting the single item in this pile [self stopBrowseImmediately]; } else { if (icon == [icons lastObject]) { nextToBrowse = [icons objectAtIndex:index-1]; } else { nextToBrowse = [icons objectAtIndex:index+1]; } } [pilesDisplay moveIcons:[NSArray arrayWithObject:icon] toGroup:trash]; [pilesDisplay removeGroupIfEmpty:sourceGroup]; if (nextToBrowse != nil) [self browseOnIcon:nextToBrowse]; } - (void)trashDocument:(NSObject *)document { [self trashIcon:[self iconForDocument:document]]; } - (void)clearGroup:(BPIconGroup *)group { // due to weird bugs in my code I can't just remove the icons in the // group in a proper way. // So I have to make a new group, move all the icons from the group into there, // then delete the new group (to remove all the icons' tracking rects). // I don't add the new group to the piles display cos I don't want it to show up. BPIconGroup *tempGroup = [[BPIconGroup alloc] initWithIcons:nil inBounds:NSZeroRect label:@""]; // have to move in reversed order, otherwise not all icons are moved // (not exactly sure why) NSArray *reversed = [BPUtil reversedArray:[group icons]]; // CURRENTLY A PROBLEM WITH DEALLOC-ING ICONS // so retain them here [reversed retain]; [pilesDisplay moveIcons:reversed toGroup:tempGroup]; [pilesDisplay removeIconGroup:tempGroup]; [tempGroup removeAll]; [tempGroup release]; } - (void)emptyTrash:(id)sender { // clear undo [[pilesDisplay undoManager] removeAllActions]; BPIconGroup *group = [pilesToIconGroups objectForKey:[NSNumber numberWithInt:[[BPPilesDataController TRASH] identifier]]]; if (group == nil) { NSLog(@"emptyTrash: can't find trash icon group"); return; } [self clearGroup:group]; } - (void)clearDisplay { NSArray *keys = [pilesToIconGroups allKeys]; NSNumber *key; BPIconGroup *iconGroup; int i; for (i=0; i<[keys count]; i++) { key = [keys objectAtIndex:i]; iconGroup = [pilesToIconGroups objectForKey:key]; if ([iconGroup isDeletable]) { [pilesDisplay removeIconGroup:iconGroup]; [pilesToIconGroups removeObjectForKey:key]; //should remove but may cause crash //NSLog(@"iconGroup %@", iconGroup); } else { [self clearGroup:iconGroup]; } } } - (void)toggleViewForIcon:(BPIcon *)icon { //NSLog(@"toggleViewForIcon"); if (icon != nil) { [[icon representedObject] toggleView]; [self browseOnIcon:icon]; } } // called on all clicks, whether single or double - (void)clickedIcon:(BPIcon *)icon { //NSLog(@"clicked icon"); if ([[NSUserDefaults standardUserDefaults] boolForKey:@"MailStacker_toggleViews"]) { [self toggleViewForIcon:icon]; } /* [self stopBrowseImmediately]; [pilesDisplay selectIcon:icon]; [pilesDisplay setNeedsDisplayInRect:[icon bounds]]; */ } - (void)singleClickedIcon:(BPIcon *)icon { // anything to do if it was a single-click rather than a double-click?? //[self browseOnIcon:icon]; //[pilesDisplay toggleSelectionOfIcon:icon]; //[pilesDisplay setNeedsDisplayInRect:[icon bounds]]; } - (void)doubleClickedIcon:(BPIcon *)icon { [self openStandaloneViewerForIcon:icon]; } - (void)clickedIconToToggleSelect:(BPIcon *)icon { //[self browseOnIcon:icon]; [self stopBrowseImmediately]; } - (void)toggleFlagForIcon:(BPIcon *)icon { [icon toggleShift]; //NSLog(@"toggle"); } - (BPIcon *)iconForKeyboardOperation { BPIcon *icon; if (lastBrowsedIcon != nil && [[docViewManager tempDocViewer] isVisible]) { icon = lastBrowsedIcon; } else { icon = [pilesDisplay itemUnderMouseAtPoint:[pilesDisplay mouseLoc] filterForMouseOver:YES]; if (![icon isKindOfClass:[BPIcon class]]) return nil; } return icon; } - (BOOL)performKeyEquivalentInDisplay:(NSEvent *)theEvent { //NSLog(@"[theEvent keyCode] %d", [theEvent keyCode]); if ([theEvent type] == NSKeyDown) { if ([theEvent keyCode] == 126) { // pressed up key [self browseUpwards]; } else if ([theEvent keyCode] == 125) { // pressed down key [self browseDownwards]; } else if ([theEvent keyCode] == 52 || [theEvent keyCode] == 36) { // pressed enter/return key if ([[NSUserDefaults standardUserDefaults] boolForKey:@"MailStacker_toggleViews"]) { [self toggleViewForIcon:[self iconForKeyboardOperation]]; } else { [self openStandaloneViewerForIcon:[self iconForKeyboardOperation]]; } } else if ([theEvent keyCode] == 49) { // pressed space bar [self toggleFlagForIcon:[self iconForKeyboardOperation]]; } else if ([theEvent keyCode] == 51) { // pressed delete [self trashIcon:[self iconForKeyboardOperation]]; } else { return NO; } } return YES; } - (void)scrolledWheelInDisplay:(NSEvent *)theEvent { if ([theEvent type] == NSScrollWheel) { //NSLog(@"%f %f %f", [theEvent deltaX], [theEvent deltaY], [theEvent deltaZ]); if ([theEvent deltaY] > 0) { // scrolled up [self browseUpwards]; } else if ([theEvent deltaY] < 0) { // scrolled down [self browseDownwards]; } } } - (IBAction)clickedContextMenu:(id)sender { NSColor *colour = [sender representedObject]; [[pilesDisplay iconsToColour] makeObjectsPerformSelector:@selector(paintWithColour:) withObject:colour]; } - (IBAction)filterMailDisplay:(id)sender { //NSLog(@"displayedMailButton %@", [displayedMailButton titleOfSelectedItem]); int index = [displayedMailButton indexOfSelectedItem]; if (index == -1) return; float reducedOpacity = 0.15; NSArray *groups = [pilesDisplay iconGroups]; NSArray *icons; BPIcon *icon; int i, j; if (index == 0) { // All mail for (i=0; i<[groups count]; i++) { icons = [[groups objectAtIndex:i] icons]; for (j=0; j<[icons count]; j++) { icon = [icons objectAtIndex:j]; [icon setOpacity:1.0]; } } } else if (index == 1) { // Flagged mail for (i=0; i<[groups count]; i++) { icons = [[groups objectAtIndex:i] icons]; for (j=0; j<[icons count]; j++) { icon = [icons objectAtIndex:j]; if ([icon isMarked]) [icon setOpacity:1.0]; else [icon setOpacity:reducedOpacity]; } } } else if (index == 2) { // Selected mail for (i=0; i<[groups count]; i++) { icons = [[groups objectAtIndex:i] icons]; for (j=0; j<[icons count]; j++) { icon = [icons objectAtIndex:j]; if ([icon isHighlighted]) [icon setOpacity:1.0]; else [icon setOpacity:reducedOpacity]; } } } else if (index == 3) { // last search results NSArray *lastSearchResults = [searcher lastSearchResults]; for (i=0; i<[groups count]; i++) { icons = [[groups objectAtIndex:i] icons]; for (j=0; j<[icons count]; j++) { icon = [icons objectAtIndex:j]; if ([lastSearchResults containsObject:icon]) [icon setOpacity:1.0]; else [icon setOpacity:reducedOpacity]; } } } [pilesDisplay setNeedsDisplay:YES]; } #pragma mark - #pragma mark interface for Models/Controllers - (BPIcon *)iconForDocument:(NSObject *)document { BPIconGroup *group = [pilesToIconGroups objectForKey:[NSNumber numberWithInt:[[document documentGroup] identifier]]]; if (group == nil) { NSLog(@"iconForDocument: unknown icon group. " "(document: %@)", document); return; } BPIcon *icon; int i; for (i=0; i<[[group icons] count]; i++) { icon = [[group icons] objectAtIndex:i]; if ([icon representedObject] == document) { return icon; } } return nil; } - (BPIcon *)createIconForDocument:(NSObject *)document inGroup:(BPIconGroup *)group { BPIcon *newIcon = [[[BPIcon alloc] initWithImage:[document icon] opacity:1.0 represents:document] autorelease]; // set various icon attributes [newIcon setMarked:[document isFlagged]]; [newIcon setMarkAmount:[document markAmount]]; if ([document colourCode] != nil) [newIcon paintWithColour:[document colourCode]]; // set this controller to be notified when there's mouse over/exit this icon [newIcon setMouseActionsDelegate:self]; // add icon to the group [group add:newIcon]; } - (void)addedPile:(BPDocumentGroup *)pile { NSRect rect; rect.origin = [pile position]; rect.size = [BPIconGroup startingSize]; // make icon group to represent this pile BPIconGroup *newGroup = [[BPIconGroup alloc] initWithIcons:nil inBounds:rect label:[pile label]]; [newGroup setRepresentedObject:pile]; if ([pile isSystemGroup]) { [newGroup setDeletable:NO]; //[[newGroup labelTextField] setEditable:NO]; [[newGroup labelTextField] setupForGroup:newGroup]; } // set this controller to be notified when there's mouse over/exit this icon [newGroup setMouseActionsDelegate:self]; // create icon for each document in the pile and add it to the icon group // representing this pile if ([[pile documents] count] > 0) { NSArray *documents = [pile documents]; NSObject *document; //BPIcon *newIcon; int i=0; for (i=0; i<[documents count]; i++) { document = [documents objectAtIndex:i]; [self createIconForDocument:document inGroup:newGroup]; } } // add to dictionary [pilesToIconGroups setObject:newGroup forKey:[NSNumber numberWithInt:[pile identifier]]]; // add to display [pilesDisplay addIconGroup:newGroup]; } - (void)addedDocument:(NSObject *)document toPile:(BPDocumentGroup *)pile { BPIconGroup *group = [pilesToIconGroups objectForKey:[NSNumber numberWithInt:[pile identifier]]]; if (group == nil) { NSLog(@"addedDocument:toPile: trying to add icon to unknown icon group. " "(document: %@, pile: %@)", document, pile); return; } BPIcon *newIcon = [self createIconForDocument:document inGroup:group]; } - (void)removedDocument:(NSObject *)document fromPile:(BPDocumentGroup *)pile { // find the icon & remove it from its group BPIcon *icon = [self iconForDocument:document]; if (icon != nil) [[icon iconGroup] remove:icon]; } - (void)movedDocument:(NSObject *)document fromPile:(BPDocumentGroup *)srcPile toPile:(BPDocumentGroup *)destPile { BPIconGroup *srcGroup = [pilesToIconGroups objectForKey:[NSNumber numberWithInt:[srcPile identifier]]]; if (srcGroup == nil) { NSLog(@"movedDocument: trying to move icon from unknown src icon group. " "(document: %@, pile: %@)", document, srcPile); return; } BPIconGroup *destGroup = [pilesToIconGroups objectForKey:[NSNumber numberWithInt:[destPile identifier]]]; if (destGroup == nil) { NSLog(@"movedDocument: trying to move icon to unknown dest icon group. " "(document: %@, pile: %@)", document, destPile); return; } BPIcon *icon; int i; for (i=0; i<[[srcGroup icons] count]; i++) { icon = [[srcGroup icons] objectAtIndex:i]; if ([icon representedObject] == document) { [icon retain]; // in case srcGroup remove will release & dealloc icon [srcGroup remove:icon]; break; } } if (icon != nil) { [pilesDisplay moveIcons:[NSArray arrayWithObject:icon] toGroup:destGroup]; [icon release]; // counter earlier retain } } - (void)changedFlagForDocument:(NSObject *)document { BPIcon *icon = [self iconForDocument:document]; [icon setMarked:[document isFlagged]]; [icon resetPosition]; //NSLog(@"marking"); } - (void)movedPile:(BPDocumentGroup *)pile { BPIconGroup *group = [pilesToIconGroups objectForKey:[NSNumber numberWithInt:[pile identifier]]]; if (group == nil) { NSLog(@"movedPile: unknown pile %@", pile); return; } [group setPosition:[pile position]]; } - (void)refreshDraftsPile { [self refreshDisplayForPile:[BPPilesDataController TRASH]]; } - (void)refreshDisplayForPile:(BPDocumentGroup *)pile { [[pilesDisplay window] makeKeyWindow]; BPIconGroup *group = [pilesToIconGroups objectForKey:[NSNumber numberWithInt:[pile identifier]]]; if (group == nil) { NSLog(@"refreshDraftsPile can't find pile %@", pile); return; } [pilesDisplay refreshDisplayForIconGroup:group]; [pilesDisplay refreshDisplayForIcons:[group icons]]; } - (void)setRecommendedLabel:(NSString *)label { //if ([[NSUserDefaults standardUserDefaults] boolForKey:@"MailStacker_autoLabelPiles"]) { [label retain]; [recommendedLabel release]; recommendedLabel = label; //} } #pragma mark - // ----------- dealloc - (void)dealloc { NSLog(@"destroying %@", self); [pilesToIconGroups release]; [super dealloc]; } @end