diff --git a/camino/src/bookmarks/Bookmark.h b/camino/src/bookmarks/Bookmark.h index 3fb6299058d..94785f5c220 100644 --- a/camino/src/bookmarks/Bookmark.h +++ b/camino/src/bookmarks/Bookmark.h @@ -77,3 +77,20 @@ - (id)savedFaviconURL; @end + + +@interface RendezvousBookmark : Bookmark +{ + int mServiceID; + BOOL mResolved; +} + +- (id)initWithServiceID:(int)inServiceID; +- (void)setServiceID:(int)inServiceID; +- (int)serviceID; + +- (BOOL)resolved; +- (void)setResolved:(BOOL)inResolved; + +@end + diff --git a/camino/src/bookmarks/Bookmark.mm b/camino/src/bookmarks/Bookmark.mm index b761396cdd3..79d9784e397 100644 --- a/camino/src/bookmarks/Bookmark.mm +++ b/camino/src/bookmarks/Bookmark.mm @@ -475,4 +475,121 @@ NSString* const URLLoadSuccessKey = @"url_bool"; return nil; } +#pragma mark - + +// sorting + +- (NSComparisonResult)compareURL:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending +{ + NSComparisonResult result; + // sort folders before sites + if ([aItem isKindOfClass:[BookmarkFolder class]]) + result = NSOrderedDescending; + else + result = [[self url] compare:[(Bookmark*)aItem url] options:NSCaseInsensitiveSearch]; + + return [inDescending boolValue] ? (NSComparisonResult)(-1 * (int)result) : result; +} + +// base class does the title compare + +- (NSComparisonResult)compareType:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending +{ + NSComparisonResult result; + // sort folders before other stuff, and separators before bookmarks + if ([aItem isKindOfClass:[BookmarkFolder class]]) + result = NSOrderedDescending; + else + result = (NSComparisonResult)((int)[self isSeparator] - (int)[(Bookmark*)aItem isSeparator]); + + return [inDescending boolValue] ? (NSComparisonResult)(-1 * (int)result) : result; +} + +- (NSComparisonResult)compareVisitCount:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending +{ + NSComparisonResult result; + // sort folders before other stuff + if ([aItem isKindOfClass:[BookmarkFolder class]]) + result = NSOrderedDescending; + else + { + int myVisits = [self numberOfVisits]; + int otherVisits = [(Bookmark*)aItem numberOfVisits]; + if (myVisits == otherVisits) + result = NSOrderedSame; + else + result = (otherVisits > myVisits) ? NSOrderedAscending : NSOrderedDescending; + } + + return [inDescending boolValue] ? (NSComparisonResult)(-1 * (int)result) : result; +} + +- (NSComparisonResult)compareLastVisitDate:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending +{ + NSComparisonResult result; + // sort categories before sites + if ([aItem isKindOfClass:[BookmarkFolder class]]) + result = NSOrderedDescending; + else + result = [mLastVisit compare:[(Bookmark*)aItem lastVisit]]; + + return [inDescending boolValue] ? (NSComparisonResult)(-1 * (int)result) : result; +} + +- (NSComparisonResult)compareForTop10:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending +{ + NSComparisonResult result; + // sort folders before other stuff + if ([aItem isKindOfClass:[BookmarkFolder class]]) + result = NSOrderedDescending; + else + { + int myVisits = [self numberOfVisits]; + int otherVisits = [(Bookmark*)aItem numberOfVisits]; + if (myVisits == otherVisits) + result = [mLastVisit compare:[(Bookmark*)aItem lastVisit]]; + else + result = (otherVisits > myVisits) ? NSOrderedAscending : NSOrderedDescending; + } + + return [inDescending boolValue] ? (NSComparisonResult)(-1 * (int)result) : result; +} + @end + +#pragma mark - + +@implementation RendezvousBookmark + +- (id)initWithServiceID:(int)inServiceID +{ + if ((self = [super init])) + { + mServiceID = inServiceID; + mResolved = NO; + } + return self; +} + +- (void)setServiceID:(int)inServiceID +{ + mServiceID = inServiceID; +} + +- (int)serviceID +{ + return mServiceID; +} + +- (BOOL)resolved +{ + return mResolved; +} + +- (void)setResolved:(BOOL)inResolved +{ + mResolved = inResolved; +} + +@end + diff --git a/camino/src/bookmarks/BookmarkFolder.h b/camino/src/bookmarks/BookmarkFolder.h index cfbc5caa26d..6f45c92d7ee 100644 --- a/camino/src/bookmarks/BookmarkFolder.h +++ b/camino/src/bookmarks/BookmarkFolder.h @@ -121,8 +121,15 @@ enum { // Smart Folder only methods -(void) insertIntoSmartFolderChild:(BookmarkItem *)aItem; +-(void) insertIntoSmartFolderChild:(BookmarkItem *)aItem atIndex:(unsigned)inIndex; -(void) deleteFromSmartFolderChildAtIndex:(unsigned)index; +// sorting +- (void)sortChildrenUsingSelector:(SEL)inSelector reverseSort:(BOOL)inReverse sortDeep:(BOOL)inDeep; +- (void)sortChildrenUsingPrimarySelector:(SEL)inSelector primaryReverseSort:(BOOL)inReverse + secondarySelector:(SEL)inSecondarySelector secondaryReverseSort:(BOOL)inSecondaryReverse + sortDeep:(BOOL)inDeep; + // generation menus -(void) buildFlatFolderList:(NSMenu *)menu depth:(unsigned)pad; diff --git a/camino/src/bookmarks/BookmarkFolder.mm b/camino/src/bookmarks/BookmarkFolder.mm index 7db1ff19f19..98cb2e5b5f0 100644 --- a/camino/src/bookmarks/BookmarkFolder.mm +++ b/camino/src/bookmarks/BookmarkFolder.mm @@ -51,6 +51,28 @@ NSString* const BookmarkFolderChildIndexKey = @"bf_ik"; NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; +struct BookmarkSortData +{ + SEL mSortSelector; + NSNumber* mReverseSort; + + SEL mSecondarySortSelector; + NSNumber* mSecondaryReverseSort; +}; + + +static int BookmarkItemSort(id firstItem, id secondItem, void* context) +{ + BookmarkSortData* sortData = (BookmarkSortData*)context; + int comp = (int)[firstItem performSelector:sortData->mSortSelector withObject:secondItem withObject:sortData->mReverseSort]; + + if (comp == 0 && sortData->mSecondarySortSelector) + comp = (int)[firstItem performSelector:sortData->mSecondarySortSelector withObject:secondItem withObject:sortData->mSecondaryReverseSort]; + + return comp; +} + + @interface BookmarksEnumerator : NSEnumerator { BookmarkFolder* mRootFolder; @@ -92,6 +114,7 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; // notification methods -(void) itemAddedNote:(BookmarkItem *)theItem atIndex:(unsigned)anIndex; -(void) itemRemovedNote:(BookmarkItem *)theItem; +-(void) itemChangedNote:(BookmarkItem *)theItem; -(void) dockMenuChanged:(NSNotification *)note; // aids in searching @@ -153,23 +176,22 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; // we're at the end of this folder. while (1) { + if (mCurFolder == mRootFolder) // we're done + { + mCurFolder = nil; + break; + } + // back up to parent folder, next index BookmarkFolder* newCurFolder = [mCurFolder parent]; mCurChildIndex = [newCurFolder indexOfObject:mCurFolder] + 1; mCurFolder = newCurFolder; - if (mCurChildIndex < (int)[newCurFolder count]) { BookmarkItem* nextItem = [mCurFolder objectAtIndex:mCurChildIndex]; [self setupNextForItem:nextItem]; return nextItem; } - - if (mCurFolder == mRootFolder) // we finished - { - mCurFolder = nil; - break; - } } return nil; @@ -323,9 +345,7 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; -(BOOL) isSpecial { - if ([self isToolbar] || [self isRoot] || [self isSmartFolder] || [self isDockMenu]) - return YES; - return NO; + return ([self isToolbar] || [self isRoot] || [self isSmartFolder] || [self isDockMenu]); } -(BOOL) isToolbar @@ -487,14 +507,23 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; // // Smart folder utilities - we don't set ourself as parent // --(void) insertIntoSmartFolderChild:(BookmarkItem *)aItem +- (void)insertIntoSmartFolderChild:(BookmarkItem *)aItem { if ([self isSmartFolder]) { [[self childArray] addObject:aItem]; - [self itemAddedNote:aItem atIndex:([self count]-1)]; + [self itemAddedNote:aItem atIndex:([self count] - 1)]; } } --(void) deleteFromSmartFolderChildAtIndex:(unsigned)index + +- (void)insertIntoSmartFolderChild:(BookmarkItem *)aItem atIndex:(unsigned)inIndex +{ + if ([self isSmartFolder]) { + [[self childArray] insertObject:aItem atIndex:inIndex]; + [self itemAddedNote:aItem atIndex:inIndex]; + } +} + +- (void)deleteFromSmartFolderChildAtIndex:(unsigned)index { if ([self isSmartFolder]) { BookmarkItem *item = [[[self childArray] objectAtIndex:index] retain]; @@ -503,6 +532,54 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; } } +- (void)sortChildrenUsingSelector:(SEL)inSelector reverseSort:(BOOL)inReverse sortDeep:(BOOL)inDeep +{ + BookmarkSortData sortData = { inSelector, [NSNumber numberWithBool:inReverse], NULL, nil }; + [mChildArray sortUsingFunction:BookmarkItemSort context:&sortData]; + + // notify + [self itemChangedNote:self]; + + if (inDeep) + { + NSEnumerator *enumerator = [mChildArray objectEnumerator]; + id childItem; + while ((childItem = [enumerator nextObject])) + { + if ([childItem isKindOfClass:[BookmarkFolder class]]) + [childItem sortChildrenUsingSelector:inSelector reverseSort:inReverse sortDeep:inDeep]; + } + } +} + +- (void)sortChildrenUsingPrimarySelector:(SEL)inSelector primaryReverseSort:(BOOL)inReverse + secondarySelector:(SEL)inSecondarySelector secondaryReverseSort:(BOOL)inSecondaryReverse + sortDeep:(BOOL)inDeep +{ + BookmarkSortData sortData = { inSelector, [NSNumber numberWithBool:inReverse], + inSecondarySelector, [NSNumber numberWithBool:inSecondaryReverse] + }; + [mChildArray sortUsingFunction:BookmarkItemSort context:&sortData]; + + // notify + [self itemChangedNote:self]; + + if (inDeep) + { + NSEnumerator *enumerator = [mChildArray objectEnumerator]; + id childItem; + while ((childItem = [enumerator nextObject])) + { + if ([childItem isKindOfClass:[BookmarkFolder class]]) + [childItem sortChildrenUsingPrimarySelector:inSelector + primaryReverseSort:inReverse + secondarySelector:inSecondarySelector + secondaryReverseSort:inSecondaryReverse + sortDeep:inDeep]; + } + } +} + // // Adding bookmarks // @@ -970,6 +1047,11 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; [[NSNotificationCenter defaultCenter] postNotification:note]; } +-(void) itemChangedNote:(BookmarkItem *)theItem +{ + [self itemUpdatedNote:kBookmarkItemChildrenChangedMask]; +} + -(void) dockMenuChanged:(NSNotification *)note { if (([self isDockMenu]) && ([note object] != self)) @@ -977,10 +1059,11 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; } +#pragma mark - + // // reading/writing from/to disk // -#pragma mark - -(BOOL) readNativeDictionary:(NSDictionary *)aDict { @@ -1332,6 +1415,7 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; [self removeFromChildArrayAtIndex:aIndex]; [self insertChild:aItem atIndex:aIndex isMove:NO]; } + // // child bookmark stuff // @@ -1369,6 +1453,7 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; [self removeFromChildBookmarksAtIndex:aIndex]; [self insertInChildBookmarks:aItem atIndex:aIndex]; } + // // child bookmark folder stuff // @@ -1377,7 +1462,7 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; NSArray *folderArray = [self childFolders]; BookmarkFolder *aFolder; unsigned realIndex; - if (aIndex < [folderArray count]) { + if (aIndex < [folderArray count]) { aFolder = [folderArray objectAtIndex:aIndex]; realIndex = [[self childArray] indexOfObject:aFolder]; } else { @@ -1434,11 +1519,14 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; NSRelativePosition relPos = [relSpec relativePosition]; if (baseSpec == nil) return nil; + if ([kiddies count] == 0) return [NSArray array]; + if ([baseKey isEqualToString:@"childBookmarks"] || [baseKey isEqualToString:@"childArray"] || - [baseKey isEqualToString:@"childFolders"] ){ + [baseKey isEqualToString:@"childFolders"] ) + { unsigned baseIndex; id baseObject = [baseSpec objectsByEvaluatingWithContainers:self]; if ([baseObject isKindOfClass:[NSArray class]]) { @@ -1446,16 +1534,17 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; if (baseCount == 0) baseObject = nil; else { - if (relPos == NSRelativeBefore){ + if (relPos == NSRelativeBefore) baseObject = [baseObject objectAtIndex:0]; - } else { + else baseObject = [baseObject objectAtIndex:(baseCount-1)]; - } } } + if (!baseObject) // Oops. We could not find the base object. return nil; + baseIndex = [kiddies indexOfObjectIdenticalTo:baseObject]; if (baseIndex == NSNotFound) // Oops. We couldn't find the base object in the child array. This should not happen. @@ -1470,7 +1559,9 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; baseIndex--; else baseIndex++; - while ((baseIndex >= 0) && (baseIndex < kiddiesCount)) { + + while ((baseIndex >= 0) && (baseIndex < kiddiesCount)) + { if (keyIsArray) { [result addObject:[NSNumber numberWithInt:baseIndex]]; break; @@ -1482,12 +1573,13 @@ NSString* const BookmarkFolderDockMenuChangeNotificaton = @"bf_dmc"; break; } } + if (relPos == NSRelativeBefore) baseIndex--; else baseIndex++; } - return result; + return result; } } return nil; diff --git a/camino/src/bookmarks/BookmarkItem.h b/camino/src/bookmarks/BookmarkItem.h index 39b3af2e179..99324cb6fb7 100644 --- a/camino/src/bookmarks/BookmarkItem.h +++ b/camino/src/bookmarks/BookmarkItem.h @@ -53,6 +53,9 @@ enum kBookmarkItemLastVisitChangedMask = (1 << 6), kBookmarkItemStatusChangedMask = (1 << 7), // really "flags", like separator vs. bookmark kBookmarkItemNumVisitsChangedMask = (1 << 8), + + // flags for bookmark folder changes + kBookmarkItemChildrenChangedMask = (1 << 9), // mask of flags that require a save of the bookmarks kBookmarkItemSignificantChangeFlagsMask = kBookmarkItemTitleChangedMask | @@ -78,6 +81,9 @@ enum unsigned int mPendingChangeFlags; } +// returns YES if any of the supplied flags are set in the userInfo ++ (BOOL)bookmarkChangedNotificationUserInfo:(NSDictionary*)inUserInfo containsFlags:(unsigned int)inFlags; + // Setters/Getters -(id) parent; -(NSString *) title; @@ -137,6 +143,16 @@ enum - (id)savedKeyword; - (id)savedUUID; // does not generate a new UUID if UUID is not set +// sorting + +// we put sort comparators on the base class for convenience +- (NSComparisonResult)compareURL:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending; +- (NSComparisonResult)compareTitle:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending; +- (NSComparisonResult)compareType:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending; +- (NSComparisonResult)compareVisitCount:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending; +- (NSComparisonResult)compareLastVisitDate:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending; +- (NSComparisonResult)compareForTop10:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending; + @end // Bunch of Keys for reading/writing dictionaries. diff --git a/camino/src/bookmarks/BookmarkItem.mm b/camino/src/bookmarks/BookmarkItem.mm index 22f92913059..fd2b4745cef 100644 --- a/camino/src/bookmarks/BookmarkItem.mm +++ b/camino/src/bookmarks/BookmarkItem.mm @@ -93,6 +93,18 @@ NSString* const CaminoTrueKey = @"true"; static BOOL gSuppressAllUpdates = NO; + ++ (BOOL)bookmarkChangedNotificationUserInfo:(NSDictionary*)inUserInfo containsFlags:(unsigned int)inFlags +{ + unsigned int changeFlags = kBookmarkItemEverythingChangedMask; // assume everything changed + NSNumber* noteChangeFlags = [inUserInfo objectForKey:BookmarkItemChangedFlagsKey]; + if (noteChangeFlags) + changeFlags = [noteChangeFlags unsignedIntValue]; + + return ((changeFlags & inFlags) != 0); +} + + //Initialization -(id) init { @@ -132,6 +144,7 @@ static BOOL gSuppressAllUpdates = NO; [super dealloc]; } +@class BookmarkFolder; // Basic properties -(id) parent @@ -405,6 +418,41 @@ static BOOL gSuppressAllUpdates = NO; return mUUID ? mUUID : @""; } +#pragma mark - + +// sorting + +- (NSComparisonResult)compareURL:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending +{ + return NSOrderedSame; +} + +- (NSComparisonResult)compareTitle:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending +{ + NSComparisonResult result = [[self title] compare:[aItem title] options:NSCaseInsensitiveSearch]; + return [inDescending boolValue] ? (NSComparisonResult)(-1 * (int)result) : result; +} + +- (NSComparisonResult)compareType:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending +{ + return NSOrderedSame; +} + +- (NSComparisonResult)compareVisitCount:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending +{ + return NSOrderedSame; +} + +- (NSComparisonResult)compareLastVisitDate:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending +{ + return NSOrderedSame; +} + +- (NSComparisonResult)compareForTop10:(BookmarkItem *)aItem sortDescending:(NSNumber*)inDescending +{ + return NSOrderedSame; +} + @end diff --git a/camino/src/bookmarks/BookmarkManager.mm b/camino/src/bookmarks/BookmarkManager.mm index d12cf60a888..ccb0bd9e3c8 100644 --- a/camino/src/bookmarks/BookmarkManager.mm +++ b/camino/src/bookmarks/BookmarkManager.mm @@ -279,6 +279,7 @@ static BookmarkManager* gBookmarkManager = nil; // All interested parties haven't been init'd yet, and/or will receive the // managerStartedNotification when setup is actually complete. [BookmarkItem setSuppressAllUpdateNotifications:YES]; + BOOL bookmarksReadOK = [self readBookmarks]; if (!bookmarksReadOK) { @@ -339,14 +340,14 @@ static BookmarkManager* gBookmarkManager = nil; [[self toolbarFolder] refreshIcon]; NSArray* allBookmarks = [[self rootBookmarks] allChildBookmarks]; - + NSEnumerator* bmEnum = [allBookmarks objectEnumerator]; Bookmark* thisBM; while ((thisBM = [bmEnum nextObject])) { [self registerBookmarkForLoads:thisBM]; } - + // load favicons (w/out hitting the network, cache only). Spread it out so that we only get // ten every three seconds to avoid locking up the UI with large bookmark lists. // XXX probably want a better way to do this. This sets up a timer (internally) for every @@ -790,8 +791,9 @@ static BookmarkManager* gBookmarkManager = nil; NSMutableSet* urlSet = [urlMap objectForKey:inURL]; if (!urlSet) { - urlSet = [NSMutableSet setWithCapacity:1]; + urlSet = [[NSMutableSet alloc] initWithCapacity:1]; [urlMap setObject:urlSet forKey:inURL]; + [urlSet release]; } [urlSet addObject:inBookmark]; } @@ -843,7 +845,9 @@ static BookmarkManager* gBookmarkManager = nil; [BookmarkManager addItem:inBookmark toURLMap:mBookmarkURLMap usingURL:bookmarkURL]; // and add it to the site icon map - [BookmarkManager addItem:inBookmark toURLMap:mBookmarkFaviconURLMap usingURL:[BookmarkManager faviconURLForBookmark:inBookmark]]; + NSString* faviconURL = [BookmarkManager faviconURLForBookmark:inBookmark]; + if ([faviconURL length] > 0) + [BookmarkManager addItem:inBookmark toURLMap:mBookmarkFaviconURLMap usingURL:faviconURL]; } - (void)unregisterBookmarkForLoads:(Bookmark*)inBookmark ignoringURL:(BOOL)inIgnoreURL @@ -851,7 +855,9 @@ static BookmarkManager* gBookmarkManager = nil; NSString* bookmarkURL = inIgnoreURL ? nil : [BookmarkManager canonicalBookmarkURL:[inBookmark url]]; [BookmarkManager removeItem:inBookmark fromURLMap:mBookmarkURLMap usingURL:bookmarkURL]; - [BookmarkManager removeItem:inBookmark fromURLMap:mBookmarkFaviconURLMap usingURL:[BookmarkManager faviconURLForBookmark:inBookmark]]; + NSString* faviconURL = [BookmarkManager faviconURLForBookmark:inBookmark]; + if ([faviconURL length] > 0) + [BookmarkManager removeItem:inBookmark fromURLMap:mBookmarkFaviconURLMap usingURL:faviconURL]; } diff --git a/camino/src/bookmarks/BookmarkViewController.mm b/camino/src/bookmarks/BookmarkViewController.mm index d47aaf760df..b7d9744505e 100644 --- a/camino/src/bookmarks/BookmarkViewController.mm +++ b/camino/src/bookmarks/BookmarkViewController.mm @@ -481,12 +481,11 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; while ((curItem = [itemsEnum nextObject])) { // see if it's a rendezvous item - id parent = [curItem parent]; - if (![parent isKindOfClass:[BookmarkItem class]]) + if ([curItem isKindOfClass:[RendezvousBookmark class]] && ![curItem resolved]) { - [[NetworkServices sharedNetworkServices] attemptResolveService:[parent intValue] forSender:curItem]; + [[NetworkServices sharedNetworkServices] attemptResolveService:[(RendezvousBookmark*)curItem serviceID] forSender:curItem]; mOpenActionFlag = kOpenBookmarkAction; - } + } else if ([curItem isKindOfClass:[BookmarkFolder class]]) { if (![curItem isGroup]) @@ -518,10 +517,9 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; while ((curItem = [itemsEnum nextObject])) { // see if it's a rendezvous item - id parent = [curItem parent]; - if (![parent isKindOfClass:[BookmarkItem class]]) + if ([curItem isKindOfClass:[RendezvousBookmark class]] && ![curItem resolved]) { - [[NetworkServices sharedNetworkServices] attemptResolveService:[parent intValue] forSender:curItem]; + [[NetworkServices sharedNetworkServices] attemptResolveService:[(RendezvousBookmark*)curItem serviceID] forSender:curItem]; mOpenActionFlag = kOpenInNewTabAction; } else @@ -548,10 +546,9 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; while ((curItem = [itemsEnum nextObject])) { // see if it's a rendezvous item (this won't open in the new window, because we suck) - id parent = [curItem parent]; - if (![parent isKindOfClass:[BookmarkItem class]]) + if ([curItem isKindOfClass:[RendezvousBookmark class]] && ![curItem resolved]) { - [[NetworkServices sharedNetworkServices] attemptResolveService:[parent intValue] forSender:curItem]; + [[NetworkServices sharedNetworkServices] attemptResolveService:[(RendezvousBookmark*)curItem serviceID] forSender:curItem]; mOpenActionFlag = kOpenInNewTabAction; } else @@ -586,10 +583,9 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; while ((curItem = [itemsEnum nextObject])) { // see if it's a rendezvous item - id parent = [curItem parent]; - if (![parent isKindOfClass:[BookmarkItem class]]) + if ([curItem isKindOfClass:[RendezvousBookmark class]] && ![curItem resolved]) { - [[NetworkServices sharedNetworkServices] attemptResolveService:[parent intValue] forSender:curItem]; + [[NetworkServices sharedNetworkServices] attemptResolveService:[(RendezvousBookmark*)curItem serviceID] forSender:curItem]; mOpenActionFlag = kOpenInNewWindowAction; } else @@ -1511,10 +1507,10 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; if (mBookmarkUpdatesDisabled) return; - if (!item) + if (!item || (item == mActiveRootCollection)) [mBookmarksOutlineView reloadData]; else - [mBookmarksOutlineView reloadItem: item reloadChildren: aReloadChildren]; + [mBookmarksOutlineView reloadItem:item reloadChildren:aReloadChildren]; } - (int)numberOfSelectedRows @@ -1785,17 +1781,22 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; return; NSDictionary *dict = [note userInfo]; id aClient = [dict objectForKey:NetworkServicesClientKey]; - if ([aClient isKindOfClass:[Bookmark class]]) { - switch (mOpenActionFlag) { + if ([aClient isKindOfClass:[Bookmark class]]) + { + switch (mOpenActionFlag) + { case (kOpenBookmarkAction): - [[NSRunLoop currentRunLoop] performSelector:@selector(openBookmark:) target:self argument:aClient order:10 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]; + [self performSelector:@selector(openBookmark:) withObject:aClient afterDelay:0]; break; + case (kOpenInNewTabAction): - [[NSRunLoop currentRunLoop] performSelector:@selector(openBookmarkInNewTab:) target:self argument:aClient order:10 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]; + [self performSelector:@selector(openBookmarkInNewTab:) withObject:aClient afterDelay:0]; break; + case (kOpenInNewWindowAction): - [[NSRunLoop currentRunLoop] performSelector:@selector(openBookmarkInNewWindow:) target:self argument:aClient order:10 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]; + [self performSelector:@selector(openBookmarkInNewWindow:) withObject:aClient afterDelay:0]; break; + default: break; } @@ -1853,8 +1854,18 @@ static const int kDisabledQuicksearchPopupItemTag = 9999; - (void)bookmarkChanged:(NSNotification *)note { - // XXX look at change flags - [self reloadDataForItem:[note object] reloadChildren:NO]; + const unsigned int kVisibleAttributeChangedFlags = (kBookmarkItemTitleChangedMask | + kBookmarkItemURLChangedMask | + kBookmarkItemKeywordChangedMask | + kBookmarkItemDescriptionChangedMask | + kBookmarkItemLastVisitChangedMask | + kBookmarkItemStatusChangedMask); + + BOOL reloadItem = [BookmarkItem bookmarkChangedNotificationUserInfo:[note userInfo] containsFlags:kVisibleAttributeChangedFlags]; + BOOL reloadChildren = [BookmarkItem bookmarkChangedNotificationUserInfo:[note userInfo] containsFlags:kBookmarkItemChildrenChangedMask]; + + if (reloadItem || reloadChildren) + [self reloadDataForItem:[note object] reloadChildren:reloadChildren]; } - (void)bookmarksViewDidMoveToWindow:(NSWindow*)inWindow diff --git a/camino/src/bookmarks/KindaSmartFolderManager.h b/camino/src/bookmarks/KindaSmartFolderManager.h index 859ea6b12ad..3fef618b4e1 100644 --- a/camino/src/bookmarks/KindaSmartFolderManager.h +++ b/camino/src/bookmarks/KindaSmartFolderManager.h @@ -54,7 +54,6 @@ BookmarkFolder* mAddressBookFolder; BookmarkFolder* mRendezvousFolder; AddressBookManager* mAddressBookManager; - unsigned mFewestVisits; } -(id)initWithBookmarkManager:(BookmarkManager *)manager; diff --git a/camino/src/bookmarks/KindaSmartFolderManager.mm b/camino/src/bookmarks/KindaSmartFolderManager.mm index c5010d477a6..cd2b30c94fc 100644 --- a/camino/src/bookmarks/KindaSmartFolderManager.mm +++ b/camino/src/bookmarks/KindaSmartFolderManager.mm @@ -56,6 +56,8 @@ -(void)rebuildTop10List; @end +const unsigned kNumTop10Items = 10; // well, 10, duh! + @implementation KindaSmartFolderManager -(id)initWithBookmarkManager:(BookmarkManager *)manager @@ -65,12 +67,11 @@ mTop10Folder = [[manager top10Folder] retain]; mAddressBookFolder = [[manager addressBookFolder] retain]; mRendezvousFolder = [[manager rendezvousFolder] retain]; - mFewestVisits = 0; + // client notifications NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self selector:@selector(bookmarkRemoved:) name:BookmarkFolderDeletionNotification object:nil]; [nc addObserver:self selector:@selector(bookmarkChanged:) name:BookmarkItemChangedNotification object:nil]; - [nc addObserver:self selector:@selector(bookmarkChanged:) name:BookmarkIconChangedNotification object:nil]; } return self; } @@ -94,15 +95,11 @@ [nc addObserver:self selector:@selector(availableServicesChanged:) name:NetworkServicesAvailableServicesChanged object:nil]; [nc addObserver:self selector:@selector(serviceResolved:) name:NetworkServicesResolutionSuccess object:nil]; } + if (mAddressBookFolder) [self setupAddressBook]; - // get top 10 list started - NSArray *bookmarkArray = [[manager rootBookmarks] allChildBookmarks]; - unsigned i, j = [bookmarkArray count]; - for (i=0; i < j; i++) { - Bookmark *aBookmark = [bookmarkArray objectAtIndex:i]; - [self checkForNewTop10:aBookmark]; - } + + [self rebuildTop10List]; } -(void) setupAddressBook @@ -118,56 +115,110 @@ -(void)rebuildTop10List { unsigned i, count = [mTop10Folder count]; - for (i=0;i < count;i++) + for (i = 0; i < count; i++) [mTop10Folder deleteFromSmartFolderChildAtIndex:0]; - mFewestVisits = 0; - // get top 10 list started - BookmarkManager *manager = [BookmarkManager sharedBookmarkManager]; - NSArray *bookmarkArray = [[manager rootBookmarks] allChildBookmarks]; - count = [bookmarkArray count]; - for (i=0; i < count; i++) { - Bookmark *aBookmark = [bookmarkArray objectAtIndex:i]; - [self checkForNewTop10:aBookmark]; - } -} + BookmarkManager *manager = [BookmarkManager sharedBookmarkManager]; + BookmarkItem* curItem; + + // We don't use rootBookmarks, because that will include the top10 folder, + // so we do the menu and toolbar folders manually. This also allows us to + // skip Rendezvous and Address Book folders, for which we don't store + // visit counts anyway. However, we will skip any other custom bookmark + // container folders that the user has created. + NSEnumerator* bookmarksEnum = [[manager bookmarkMenuFolder] objectEnumerator]; + while ((curItem = [bookmarksEnum nextObject])) + { + if ([curItem isKindOfClass:[Bookmark class]]) + [self checkForNewTop10:curItem]; + } + + bookmarksEnum = [[manager toolbarFolder] objectEnumerator]; + while ((curItem = [bookmarksEnum nextObject])) + { + if ([curItem isKindOfClass:[Bookmark class]]) + [self checkForNewTop10:curItem]; + } + + // This is somewhat broken. -checkForNewTop10 doesn't insert based on visit count, + // it just assumes that later added bookmarks should replace earlier ones (with + // the same visit count). + [mTop10Folder sortChildrenUsingPrimarySelector:@selector(compareVisitCount:sortDescending:) + primaryReverseSort:YES + secondarySelector:@selector(compareLastVisitDate:sortDescending:) + secondaryReverseSort:YES + sortDeep:NO]; +} -(void)checkForNewTop10:(Bookmark *)aBookmark { - unsigned smallVisit = [aBookmark numberOfVisits]; - if (smallVisit == 0) { - if ([mTop10Folder indexOfObjectIdenticalTo:aBookmark] != NSNotFound) - //whoops. we just cleared the visits on a top 10 item. rebuild from scratch. - [self rebuildTop10List]; + NSMutableArray* top10ItemsArray = [mTop10Folder childArray]; + +// NSLog(@"checkForNewTop10 %@ (%d items)", aBookmark, [top10ItemsArray count]); + + // if it's already in the list + unsigned curIndex = [top10ItemsArray indexOfObjectIdenticalTo:aBookmark]; + unsigned visitCount = [aBookmark numberOfVisits]; + + // if it's not in the list, and the visit count is zero, nothing to do + if (curIndex == NSNotFound && visitCount == 0) return; + + // we assume the list is sorted here + unsigned currentMinVisits = 1; + if ([top10ItemsArray count] == kNumTop10Items) + currentMinVisits = [[top10ItemsArray lastObject] numberOfVisits]; + + if (curIndex != NSNotFound) // it's already in the list + { + if (visitCount < currentMinVisits) + { + // the item dropped off the list. rather than grovel for the next highest item, just rebuild the list + // (this could be optimized) + [self rebuildTop10List]; // XXX potential recursion! + } + else + { + // just resort + [mTop10Folder sortChildrenUsingPrimarySelector:@selector(compareVisitCount:sortDescending:) + primaryReverseSort:YES + secondarySelector:@selector(compareLastVisitDate:sortDescending:) + secondaryReverseSort:YES + sortDeep:NO]; + } } - if (([mTop10Folder indexOfObjectIdenticalTo:aBookmark] != NSNotFound)) - return; - // cycle through list of children - // find item with fewest visits, mark it for destruction - // if the URL from the new bookmark is already present in the - // list, we bail out - NSMutableArray *childArray = [mTop10Folder childArray]; - unsigned i, kidVisit, count = [childArray count]; //j should be 10 - if ((count >=10) && (smallVisit < mFewestVisits)) - return; - Bookmark *aKid = nil; - NSString *newURL = [aBookmark url]; - int doomedKidIndex = -1; - for (i=0; i< count; i++) { - aKid = [childArray objectAtIndex:i]; - if ([newURL isEqualToString:[aKid url]]) - return; - kidVisit = [aKid numberOfVisits]; - if (kidVisit == mFewestVisits) - doomedKidIndex = i; - else if (smallVisit > kidVisit) - smallVisit = kidVisit; + else if (visitCount >= currentMinVisits) + { + // enter it into the list using insertion sort. it will go before other items with the same visit + // count (thus maintaining the visit count/last visit sort). + unsigned numItems = [top10ItemsArray count]; + int insertionIndex = -1; + + NSString* newItemURL = [aBookmark url]; + NSNumber* reverseSort = [NSNumber numberWithBool:YES]; + + // we check the entire list to look for items with a duplicate url + for (unsigned i = 0; i < numItems; i ++) + { + Bookmark* curChild = [top10ItemsArray objectAtIndex:i]; + if ([newItemURL isEqualToString:[curChild url]]) + return; + + // add before the first item with the same or lower visit count + if (([aBookmark compareForTop10:curChild sortDescending:reverseSort] != NSOrderedDescending) && insertionIndex == -1) + insertionIndex = i; + } + + if (insertionIndex == -1 && [top10ItemsArray count] < kNumTop10Items) + insertionIndex = [top10ItemsArray count]; + + if (insertionIndex != -1) + { + [mTop10Folder insertIntoSmartFolderChild:aBookmark atIndex:insertionIndex]; + if ([top10ItemsArray count] > kNumTop10Items) + [mTop10Folder deleteFromSmartFolderChildAtIndex:[top10ItemsArray count] - 1]; + } } - if ((doomedKidIndex != -1) && (count >= 10)) - [mTop10Folder deleteFromSmartFolderChildAtIndex:doomedKidIndex]; - [mTop10Folder insertIntoSmartFolderChild:aBookmark]; - mFewestVisits = smallVisit; } @@ -181,6 +232,7 @@ if (index == NSNotFound) [aFolder insertIntoSmartFolderChild:aBookmark]; } + // // if we have this item, remove it // @@ -209,29 +261,37 @@ static int SortByProtocolAndName(NSDictionary* item1, NSDictionary* item2, void unsigned int i, count = [mRendezvousFolder count]; for (i = 0; i < count; i++) [mRendezvousFolder deleteChild:[mRendezvousFolder objectAtIndex:0]]; + NetworkServices *netserv = [note object]; NSEnumerator* keysEnumerator = [netserv serviceEnumerator]; NSMutableArray* servicesArray = [[NSMutableArray alloc] initWithCapacity:10]; id key; - while ((key = [keysEnumerator nextObject])) { + while ((key = [keysEnumerator nextObject])) + { NSDictionary* serviceDict = [NSDictionary dictionaryWithObjectsAndKeys: - key, @"id", - [netserv serviceName:[key intValue]], @"name", - [netserv serviceProtocol:[key intValue]], @"protocol", - nil]; + key, @"id", + [netserv serviceName:[key intValue]], @"name", + [netserv serviceProtocol:[key intValue]], @"protocol", + nil]; [servicesArray addObject:serviceDict]; } - if ([servicesArray count] != 0) { + + if ([servicesArray count] != 0) + { // sort on protocol, then name [servicesArray sortUsingFunction:SortByProtocolAndName context:NULL]; + + // make bookmarks unsigned int numServices = [servicesArray count]; for (i = 0; i < numServices; i ++) { NSDictionary* serviceDict = [servicesArray objectAtIndex:i]; NSString* itemName = [[serviceDict objectForKey:@"name"] stringByAppendingString:NSLocalizedString([serviceDict objectForKey:@"protocol"], @"")]; - Bookmark *aBookmark = [mRendezvousFolder addBookmark]; - [aBookmark setTitle:itemName]; - [aBookmark setParent:[serviceDict objectForKey:@"id"]]; + + RendezvousBookmark* serviceBookmark = [[RendezvousBookmark alloc] initWithServiceID:[[serviceDict objectForKey:@"id"] intValue]]; + [serviceBookmark setTitle:itemName]; + [mRendezvousFolder appendChild:serviceBookmark]; + [serviceBookmark release]; } } [servicesArray release]; @@ -240,20 +300,25 @@ static int SortByProtocolAndName(NSDictionary* item1, NSDictionary* item2, void - (void)serviceResolved:(NSNotification *)note { NSDictionary *dict = [note userInfo]; - id aClient = [dict objectForKey:NetworkServicesClientKey]; - if ([aClient isKindOfClass:[Bookmark class]]) { - Bookmark *aKid; + id client = [dict objectForKey:NetworkServicesClientKey]; + if ([client isKindOfClass:[Bookmark class]]) + { + // I'm not sure why we have to check to see that the client is a child + // of the rendezvous folder. Maybe just see if it's a RendezvousBookmark? NSEnumerator* enumerator = [[mRendezvousFolder childArray] objectEnumerator]; - while ((aKid = [enumerator nextObject])) { - if (aKid == aClient) + Bookmark *curChild; + while ((curChild = [enumerator nextObject])) + { + if (curChild == client) { - [aClient setUrl:[dict objectForKey:NetworkServicesResolvedURLKey]]; - [aClient setParent:mRendezvousFolder]; + [client setUrl:[dict objectForKey:NetworkServicesResolvedURLKey]]; + [client setResolved:YES]; } } } return; } + - (void)serviceResolutionFailed:(NSNotification *)note { return; @@ -261,6 +326,7 @@ static int SortByProtocolAndName(NSDictionary* item1, NSDictionary* item2, void #pragma mark - + // // BookmarkClient protocol // @@ -281,10 +347,12 @@ static int SortByProtocolAndName(NSDictionary* item1, NSDictionary* item2, void - (void)bookmarkChanged:(NSNotification *)note { - // XXX look at change flags - BookmarkItem *anItem = [note object]; - if ([anItem isKindOfClass:[Bookmark class]]) { - [self checkForNewTop10:anItem]; + BOOL visitCountChanged = [BookmarkItem bookmarkChangedNotificationUserInfo:[note userInfo] containsFlags:kBookmarkItemNumVisitsChangedMask]; + if (visitCountChanged) + { + BookmarkItem *anItem = [note object]; + if ([anItem isKindOfClass:[Bookmark class]]) + [self checkForNewTop10:anItem]; } } diff --git a/camino/src/browser/SiteIconProvider.mm b/camino/src/browser/SiteIconProvider.mm index 3f2c1b2c403..398a6fc4bb6 100644 --- a/camino/src/browser/SiteIconProvider.mm +++ b/camino/src/browser/SiteIconProvider.mm @@ -265,32 +265,6 @@ NeckoCacheHelper::ClearCache() #pragma mark - -static nsresult -MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& outFaviconURI) -{ - outFaviconURI.Truncate(0); - - nsCOMPtr uri; - nsresult rv = NS_NewURI(getter_AddRefs(uri), inURIString); - if (NS_FAILED(rv)) - return rv; - - // check for http/https - PRBool isHTTP = PR_FALSE, isHTTPS = PR_FALSE; - uri->SchemeIs("http", &isHTTP); - uri->SchemeIs("https", &isHTTPS); - if (!isHTTP && !isHTTPS) - return NS_OK; - - nsCAutoString faviconURI; - uri->GetPrePath(faviconURI); - faviconURI.Append("/favicon.ico"); - - outFaviconURI.Assign(NS_ConvertUTF8toUCS2(faviconURI)); - return NS_OK; -} - - @interface SiteIconProvider(Private) - (void)addToMissedIconsCache:(NSString*)inURI withExpirationSeconds:(unsigned int)inExpSeconds; @@ -567,7 +541,6 @@ MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& outFaviconURI) return sIconProvider; } - + (NSString*)defaultFaviconLocationStringFromURI:(NSString*)inURI { // about: urls are special @@ -579,12 +552,26 @@ MakeFaviconURIFromURI(const nsAString& inURIString, nsAString& outFaviconURI) if ([inURI compare:@"file://" options:NSCaseInsensitiveSearch range:NSMakeRange(0, 7)] == NSOrderedSame) return @"about:local_file"; - nsAutoString uriString; - [inURI assignTo_nsAString:uriString]; + // we use nsIURI here, rather than NSURL, because the former does + // a better job with suspect urls (e.g. those containing |), and + // allows us go keep the port + nsCOMPtr uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), [inURI UTF8String]); + if (NS_FAILED(rv)) + return @""; - nsAutoString faviconURIString; - MakeFaviconURIFromURI(uriString, faviconURIString); - return [NSString stringWith_nsAString:faviconURIString]; + // check for http/https + PRBool isHTTP = PR_FALSE, isHTTPS = PR_FALSE; + uri->SchemeIs("http", &isHTTP); + uri->SchemeIs("https", &isHTTPS); + if (!isHTTP && !isHTTPS) + return @""; + + nsCAutoString faviconURI; + uri->GetPrePath(faviconURI); + faviconURI.Append("/favicon.ico"); + + return [NSString stringWith_nsACString:faviconURI]; } @end diff --git a/camino/src/includes/ChimeraUtils.h b/camino/src/includes/ChimeraUtils.h index 7fcb49b7691..b1eff6673ac 100644 --- a/camino/src/includes/ChimeraUtils.h +++ b/camino/src/includes/ChimeraUtils.h @@ -56,3 +56,28 @@ protected: NSAutoreleasePool* mPool; }; + +// handy utility class for timing actions +class StActionTimer +{ +public: + + StActionTimer(NSString* inActionName) + : mActionName(inActionName) + { + Microseconds(&mStartTime); + } + + ~StActionTimer() + { + UnsignedWide endTime; + Microseconds(&endTime); + + long long actionTime = UnsignedWideToUInt64(endTime) - UnsignedWideToUInt64(mStartTime); + NSLog(@"%@ took %qi us", mActionName, actionTime); + } + +protected: + UnsignedWide mStartTime; + NSString* mActionName; +};