Put global history on the Go menu, implemented using the HistoryDataSource this time. Bugs 179676 and 291414.

This commit is contained in:
smfr%smfr.org 2005-05-14 05:30:47 +00:00
Родитель 75e72cc8d7
Коммит c89819224a
18 изменённых файлов: 585 добавлений и 321 удалений

Двоичный файл не отображается.

Просмотреть файл

@ -16,7 +16,13 @@
LANGUAGE = ObjC;
SUPERCLASS = NSObject;
},
{CLASS = GoMenu; LANGUAGE = ObjC; SUPERCLASS = NSMenu; },
{CLASS = GoMenu; LANGUAGE = ObjC; SUPERCLASS = HistoryMenu; },
{
CLASS = HistoryMenu;
LANGUAGE = ObjC;
OUTLETS = {mItemBeforeHistoryItems = NSMenuItem; };
SUPERCLASS = NSMenu;
},
{
ACTIONS = {
aboutPlugins = id;

Просмотреть файл

@ -7,7 +7,7 @@
<key>IBEditorPositions</key>
<dict>
<key>29</key>
<string>199 682 433 44 0 0 1280 938 </string>
<string>274 731 433 44 0 0 1600 1002 </string>
<key>494</key>
<string>726 607 116 61 0 0 1600 1002 </string>
<key>670</key>
@ -15,11 +15,7 @@
</dict>
<key>IBFramework Version</key>
<string>364.0</string>
<key>IBOpenObjects</key>
<array>
<integer>29</integer>
</array>
<key>IBSystem Version</key>
<string>7S215</string>
<string>7W98</string>
</dict>
</plist>

Двоичные данные
camino/resources/localized/English.lproj/MainMenu.nib/keyedobjects.nib сгенерированный

Двоичный файл не отображается.

Просмотреть файл

@ -108,6 +108,7 @@ const int kReuseWindowOnAE = 2;
@interface MainController(Private)<NetworkServicesClient>
- (void)setupStartpage;
- (void)setupRendezvous;
- (void)installBookmarksMenuEnableHandler;
- (NSMenu*)bookmarksMenu;
- (BOOL)bookmarksItemsEnabled;
@ -245,11 +246,14 @@ const int kReuseWindowOnAE = 2;
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self installBookmarksMenuEnableHandler];
// initialize prefs if we haven't already.
PreferenceManager *pm = [PreferenceManager sharedInstance];
// register our app components with the embed layer
unsigned int numComps = 0;
const nsModuleComponentInfo* comps = GetAppComponents(&numComps);
CHBrowserService::RegisterAppComponents(comps, numComps);
// To work around a bug on Tiger where the view hookup order has been changed from postfix to prefix
// order, we need to set a user default to return to the old behavior.
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSViewSetAncestorsWindowFirst"];
@ -261,6 +265,7 @@ const int kReuseWindowOnAE = 2;
[[NSFileManager defaultManager] removeFileAtPath:cacheDir handler:nil];
// register for window layering changes, so that we can update the bookmarks menu
[self installBookmarksMenuEnableHandler];
NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(windowLayeringDidChange:) name:NSWindowDidBecomeKeyNotification object:nil];
[notificationCenter addObserver:self selector:@selector(windowLayeringDidChange:) name:NSWindowDidResignKeyNotification object:nil];
@ -274,17 +279,6 @@ const int kReuseWindowOnAE = 2;
[self setupStartpage];
// register our app components with the embed layer
unsigned int numComps = 0;
const nsModuleComponentInfo* comps = GetAppComponents(&numComps);
CHBrowserService::RegisterAppComponents(comps, numComps);
// don't open a new browser window if we already have one
// (for example, from an GetURL Apple Event)
NSWindow* browserWindow = [self getFrontmostBrowserWindow];
if (!browserWindow)
[self newWindow:self];
// Initialize offline mode.
mOffline = NO;
nsCOMPtr<nsIIOService> ioService(do_GetService(ioServiceContractID));
@ -302,27 +296,17 @@ const int kReuseWindowOnAE = 2;
if ([pm getBooleanPref:"chimera.log_js_to_console" withSuccess:&success])
[JSConsole sharedJSConsole];
BOOL doingRendezvous = NO;
if ([pm getBooleanPref:"chimera.enable_rendezvous" withSuccess:&success]) {
if ([MainController supportsBonjour]) {
[notificationCenter addObserver:self selector:@selector(availableServicesChanged:) name:NetworkServicesAvailableServicesChanged object:nil];
[notificationCenter addObserver:self selector:@selector(serviceResolved:) name:NetworkServicesResolutionSuccess object:nil];
[notificationCenter addObserver:self selector:@selector(serviceResolutionFailed:) name:NetworkServicesResolutionFailure object:nil];
doingRendezvous = YES;
}
}
if (!doingRendezvous) {
// remove rendezvous items
int itemIndex;
while ((itemIndex = [mGoMenu indexOfItemWithTag:kRendezvousRelatedItemTag]) != -1)
[mGoMenu removeItemAtIndex:itemIndex];
}
[self setupRendezvous];
// load up the charset dictionary with keys and menu titles.
NSString* charsetPath = [NSBundle pathForResource:@"Charset" ofType:@"dict" inDirectory:[[NSBundle mainBundle] bundlePath]];
mCharsets = [[NSDictionary dictionaryWithContentsOfFile:charsetPath] retain];
// open a new browser window if we don't already have one
// (for example, from an GetURL Apple Event)
NSWindow* browserWindow = [self getFrontmostBrowserWindow];
if (!browserWindow)
[self newWindow:self];
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
@ -390,6 +374,29 @@ const int kReuseWindowOnAE = 2;
}
}
- (void)setupRendezvous // aka "Bonjour"
{
BOOL doingRendezvous = NO;
PreferenceManager *pm = [PreferenceManager sharedInstance];
if ([pm getBooleanPref:"chimera.enable_rendezvous" withSuccess:NULL]) {
if ([MainController supportsBonjour]) {
NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(availableServicesChanged:) name:NetworkServicesAvailableServicesChanged object:nil];
[notificationCenter addObserver:self selector:@selector(serviceResolved:) name:NetworkServicesResolutionSuccess object:nil];
[notificationCenter addObserver:self selector:@selector(serviceResolutionFailed:) name:NetworkServicesResolutionFailure object:nil];
doingRendezvous = YES;
}
}
if (!doingRendezvous) {
// remove rendezvous items
int itemIndex;
while ((itemIndex = [mGoMenu indexOfItemWithTag:kRendezvousRelatedItemTag]) != -1)
[mGoMenu removeItemAtIndex:itemIndex];
}
}
//
// setupBookmarkMenus
//

Просмотреть файл

@ -40,7 +40,9 @@
#import "BookmarkMenu.h"
#import "BookmarkFolder.h"
#import "Bookmark.h"
#import "NSString+Utils.h"
#import "NSMenu+Utils.h"
// Definitions
#define MENU_TRUNCATION_CHARS 60
@ -118,7 +120,7 @@ const long kOpenInTabsTag = 0xBEEF;
-(void)setFirstItemIndex:(int)anIndex
{
mFirstItemIndex=anIndex;
mFirstItemIndex = anIndex;
}
//
@ -128,9 +130,7 @@ const long kOpenInTabsTag = 0xBEEF;
- (void)flushMenu
{
int firstItemIndex = [self firstItemIndex];
NSMenu *menu = [self menu];
while ([menu numberOfItems] > firstItemIndex)
[menu removeItemAtIndex:firstItemIndex];
[[self menu] removeItemsFromIndex:firstItemIndex];
}
//

Просмотреть файл

@ -39,7 +39,9 @@
#import "BrowserTabBarView.h"
#import "BrowserTabViewItem.h"
#import "TabButtonCell.h"
#import "NSPasteboard+Utils.h"
#import "NSMenu+Utils.h"
@interface BrowserTabBarView (PRIVATE)
-(void)layoutButtons;
@ -410,9 +412,9 @@ static const int kOverflowButtonMargin = 1;
mOverflowMenu = [[NSMenu alloc] init];
[mOverflowMenu addItemWithTitle:@"" action:NULL keyEquivalent:@""];
}
// remove any items on the menu other than the dummy item
for (int i = [mOverflowMenu numberOfItems]; i > 1; i--)
[mOverflowMenu removeItemAtIndex:i-1];
[mOverflowMenu removeItemsFromIndex:1];
}
- (IBAction)overflowMenu:(id)sender

Просмотреть файл

@ -39,15 +39,38 @@
#import <Appkit/Appkit.h>
@interface GoMenu : NSMenu
@class HistoryDataSource;
@class HistoryItem;
@interface HistoryMenu : NSMenu
{
NSMutableDictionary* mBuckets; // cached history menu objects, rebuilt every time the menu is reopened, STRONG
IBOutlet NSMenuItem* mItemBeforeHistoryItems; // the item after which we add history items. Not retained.
HistoryItem* mRootItem; // root history item for this menu (retained)
HistoryItem* mAdditionalItemsParent; // may also contain children of this item (retained)
int mNumIgnoreItems; // if > 0, ignore the first N items (for "earlier today")
BOOL mHistoryItemsDirty; // whether we need to rebuild the items on next update
}
- (void) emptyHistoryItems;
- (void) addHistoryItems;
- (void)setRootHistoryItem:(HistoryItem*)inRootItem;
- (HistoryItem*)rootItem;
// NSMenu
- (void) update;
- (void)setNumLeadingItemsToIgnore:(int)inIgnoreItems;
- (int)numLeadingItemsToIgnore;
// specify the item after which history items will be added
// (they are assumed to go to the end of the menu). If nil,
// the entire menu is full of history items.
- (void)setItemBeforeHistoryItems:(NSMenuItem*)inItem;
- (NSMenuItem*)itemBeforeHistoryItems;
@end
@interface GoMenu : HistoryMenu
{
BOOL mAppLaunchDone; // has app launching completed?
}
@end

Просмотреть файл

@ -39,295 +39,429 @@
#import "NSString+Utils.h"
#import "NSDate+Utils.h"
#import "NSMenu+Utils.h"
#import "GoMenu.h"
#import "MainController.h"
#import "BrowserWindowController.h"
#import "ChimeraUIConstants.h"
#import "CHBrowserService.h"
#import "PreferenceManager.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#define USE_GO_MENU 1
#if USE_GO_MENU
#include "nsIWebBrowser.h"
#include "nsISHistory.h"
#include "nsIWebNavigation.h"
#include "nsIHistoryEntry.h"
#include "nsCRT.h"
#else
#include "nsIBrowserHistory.h"
#include "nsIHistoryItems.h"
#include "nsISimpleEnumerator.h"
#include "nsIServiceManager.h"
#endif
#import "HistoryItem.h"
#import "HistoryDataSource.h"
#import "GoMenu.h"
// the maximum number of history entry menuitems to display
static const int kMaxItems = 15;
// the maximum number of "today" items to show on the main menu
static const int kMaxTodayItems = 12;
// the maximum number of characters in a menu title before cropping it
static const unsigned int kMaxTitleLength = 80;
#if !USE_GO_MENU
//
// HistoryMenuItem
//
// An object that passes some data between the menu items and the history data source. This
// item lives longer than the lifetime of the tracking of the menu.
//
@interface HistoryMenuItem : NSObject
// this little class manages the singleton history data source, and takes
// care of shutting it down at XPCOM shutdown time.
@interface GoMenuHistoryDataSourceOwner : NSObject
{
@private
NSString* mURL; // strong
NSString* mTitle; // strong
PRTime mLastVisit;
HistoryDataSource* mHistoryDataSource;
}
-(id)initWithURL:(NSString*)inURL title:(NSString*)inTitle lastVisit:(PRTime)inLastVisit;
-(NSString*)url;
-(NSString*)title;
+ (GoMenuHistoryDataSourceOwner*)sharedGoMenuHistoryDataSourceOwner;
+ (HistoryDataSource*)sharedHistoryDataSource; // just a shortcut
- (HistoryDataSource*)historyDataSource;
@end
@implementation HistoryMenuItem
-(id)initWithURL:(NSString*)inURL title:(NSString*)inTitle lastVisit:(PRTime)inLastVisit
@implementation GoMenuHistoryDataSourceOwner
+ (GoMenuHistoryDataSourceOwner*)sharedGoMenuHistoryDataSourceOwner
{
if ((self = [super init])) {
mURL = [inURL retain];
mTitle = [inTitle retain];
mLastVisit = inLastVisit;
static GoMenuHistoryDataSourceOwner* sHistoryOwner = nil;
if (!sHistoryOwner)
sHistoryOwner = [[GoMenuHistoryDataSourceOwner alloc] init];
return sHistoryOwner;
}
+ (HistoryDataSource*)sharedHistoryDataSource
{
return [[GoMenuHistoryDataSourceOwner sharedGoMenuHistoryDataSourceOwner] historyDataSource];
}
- (id)init
{
if ((self = [super init]))
{
// register for xpcom shutdown
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(xpcomShutdownNotification:)
name:XPCOMShutDownNotificationName
object:nil];
}
return self;
}
- (void)dealloc
{
[mURL release];
[mTitle release];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[mHistoryDataSource release];
[super dealloc];
}
-(NSString*)url
- (void)xpcomShutdownNotification:(NSNotification*)inNotification
{
return mURL;
[mHistoryDataSource release];
mHistoryDataSource = nil;
}
-(NSString*)title
- (HistoryDataSource*)historyDataSource
{
return mTitle;
if (!mHistoryDataSource)
{
mHistoryDataSource = [[HistoryDataSource alloc] init];
[mHistoryDataSource setHistoryView:kHistoryViewByDate];
[mHistoryDataSource setSortColumnIdentifier:@"last_visit"]; // always sort by last visit
[mHistoryDataSource setSortDescending:YES];
}
return mHistoryDataSource;
}
- (NSComparisonResult)compare:(HistoryMenuItem *)inItem
{
if (inItem->mLastVisit > mLastVisit)
return NSOrderedAscending;
if (inItem->mLastVisit < mLastVisit)
return NSOrderedDescending;
return NSOrderedSame;
}
@end // GoMenuHistoryDataSourceOwner
@end
#endif
#pragma mark -
@implementation GoMenu
@interface HistoryMenu(Private)
+ (NSString*)menuItemTitleForHistoryItem:(HistoryItem*)inItem;
- (void)setupHistoryMenu;
- (void)rebuildHistoryItems;
- (void)historyChanged:(NSNotification*)inNotification;
- (void)openHistoryItem:(id)sender;
@end
#pragma mark -
@implementation HistoryMenu
+ (NSString*)menuItemTitleForHistoryItem:(HistoryItem*)inItem
{
NSString* itemTitle = [inItem title];
if ([itemTitle length] == 0)
itemTitle = [inItem url];
return [itemTitle stringByTruncatingTo:kMaxTitleLength at:kTruncateAtMiddle];
}
- (id)initWithTitle:(NSString *)inTitle
{
if ((self = [super initWithTitle:inTitle]))
{
mHistoryItemsDirty = YES;
// we're making the assumption here that the Go menu only gets awakeFromNib,
// and that other history submenus are allocated dynamically later on
[self setupHistoryMenu];
}
return self;
}
- (void)awakeFromNib
{
mHistoryItemsDirty = YES;
}
// this should only be called after app launch, when the data source is available
- (void)setupHistoryMenu
{
// set ourselves up to listen for history changes
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(historyChanged:)
name:kNotificationNameHistoryDataSourceChanged
object:[GoMenuHistoryDataSourceOwner sharedHistoryDataSource]];
}
#if !USE_GO_MENU
- (void)dealloc
{
[mBuckets autorelease];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[mRootItem release];
[mAdditionalItemsParent release];
[super dealloc];
}
#endif
- (void) update
- (void)setRootHistoryItem:(HistoryItem*)inRootItem
{
[self emptyHistoryItems];
[self addHistoryItems];
[mRootItem autorelease];
mRootItem = [inRootItem retain];
}
- (HistoryItem*)rootItem
{
return mRootItem;
}
- (void)setNumLeadingItemsToIgnore:(int)inIgnoreItems
{
mNumIgnoreItems = inIgnoreItems;
}
- (int)numLeadingItemsToIgnore
{
return mNumIgnoreItems;
}
- (void)setItemBeforeHistoryItems:(NSMenuItem*)inItem
{
mItemBeforeHistoryItems = inItem;
}
- (NSMenuItem*)itemBeforeHistoryItems
{
return mItemBeforeHistoryItems;
}
- (void)historyChanged:(NSNotification*)inNotification
{
id rootChangedItem = [[inNotification userInfo] objectForKey:kNotificationHistoryDataSourceChangedUserInfoChangedItem];
// We could optimize by only changing single menu items if itemOnlyChanged is true. Normally this will also be a visit
// date change, which we can ignore.
//BOOL itemOnlyChanged = [[[inNotification userInfo] objectForKey:kNotificationHistoryDataSourceChangedUserInfoChangedItemOnly] boolValue];
// If rootChangedItem is nil, the whole history tree is being rebuilt.
// We need to clear our root item, because it will become invalid. We'll set it again when we rebuild.
if (!rootChangedItem)
{
[self setRootHistoryItem:nil];
[mAdditionalItemsParent release];
mAdditionalItemsParent = nil;
mHistoryItemsDirty = YES;
}
else
{
mHistoryItemsDirty = mRootItem == rootChangedItem ||
[mRootItem isDescendentOfItem:rootChangedItem] ||
[rootChangedItem isDescendentOfItem:mRootItem];
if (mAdditionalItemsParent)
mHistoryItemsDirty |= (rootChangedItem == mAdditionalItemsParent ||
[rootChangedItem parentItem] == mAdditionalItemsParent);
}
}
- (void)rebuildHistoryItems
{
if (!mHistoryItemsDirty)
return;
// remove everything after the "before" item
[self removeItemsAfterItem:mItemBeforeHistoryItems];
// now iterate through the history items
NSEnumerator* childEnum = [[mRootItem children] objectEnumerator];
// skip the first mNumIgnoreItems items
for (int i = 0; i < mNumIgnoreItems; ++i)
[childEnum nextObject];
BOOL separatorPending = NO;
HistoryItem* curChild;
while ((curChild = [childEnum nextObject]))
{
NSMenuItem* newItem = nil;
// XXX should we impose a max number of items in any one menu, to avoid crazy 2000-item menus?
if ([curChild isKindOfClass:[HistorySiteItem class]])
{
newItem = [[[NSMenuItem alloc] initWithTitle:[HistoryMenu menuItemTitleForHistoryItem:curChild]
action:@selector(openHistoryItem:)
keyEquivalent:@""] autorelease];
[newItem setTarget:self];
[newItem setRepresentedObject:curChild];
}
else if ([curChild isKindOfClass:[HistoryCategoryItem class]] && ([curChild numberOfChildren] > 0))
{
// The "today" category is special, because we hoist the 10 most recent items up to the parent menu.
// This code assumes that we'll hit this first, by virtue of the last visit date sorting.
if ([curChild respondsToSelector:@selector(isTodayCategory)] && [(HistoryDateCategoryItem*)curChild isTodayCategory])
{
// take note of the fact that we're showing children of this item
[mAdditionalItemsParent autorelease];
mAdditionalItemsParent = [curChild retain];
NSMutableArray* menuTodayItems = [NSMutableArray arrayWithArray:[curChild children]];
while ((int)[menuTodayItems count] > kMaxTodayItems)
[menuTodayItems removeObjectAtIndex:kMaxTodayItems];
NSEnumerator* todayItemsEnum = [menuTodayItems objectEnumerator];
HistoryItem* curTodayItem;
while ((curTodayItem = [todayItemsEnum nextObject]))
{
NSMenuItem* todayMenuItem = [[[NSMenuItem alloc] initWithTitle:[HistoryMenu menuItemTitleForHistoryItem:curTodayItem]
action:@selector(openHistoryItem:)
keyEquivalent:@""] autorelease];
[todayMenuItem setTarget:self];
[todayMenuItem setRepresentedObject:curTodayItem];
[self addItem:todayMenuItem];
separatorPending = YES;
}
// make a submenu for "earlier today"
if ([curChild numberOfChildren] > kMaxTodayItems)
{
if (separatorPending)
{
[self addItem:[NSMenuItem separatorItem]];
separatorPending = NO;
}
NSString* itemTitle = NSLocalizedString(@"GoMenuEarlierToday", @"");
newItem = [[[NSMenuItem alloc] initWithTitle:itemTitle
action:nil
keyEquivalent:@""] autorelease];
HistoryMenu* newSubmenu = [[HistoryMenu alloc] initWithTitle:itemTitle];
[newSubmenu setRootHistoryItem:curChild];
[newSubmenu setNumLeadingItemsToIgnore:kMaxTodayItems];
[newItem setSubmenu:newSubmenu];
}
}
else
{
if (separatorPending)
{
[self addItem:[NSMenuItem separatorItem]];
separatorPending = NO;
}
// Not the "today" category, and has entries
NSString* itemTitle = [HistoryMenu menuItemTitleForHistoryItem:curChild];
newItem = [[[NSMenuItem alloc] initWithTitle:itemTitle
action:nil
keyEquivalent:@""] autorelease];
HistoryMenu* newSubmenu = [[HistoryMenu alloc] initWithTitle:itemTitle];
[newSubmenu setRootHistoryItem:curChild];
[newItem setSubmenu:newSubmenu];
}
}
if (newItem)
[self addItem:newItem];
}
mHistoryItemsDirty = NO;
}
- (void)update
{
[self rebuildHistoryItems];
[super update];
}
#if USE_GO_MENU
- (nsIWebNavigation*) currentWebNavigation
- (void)openHistoryItem:(id)sender
{
// get controller for current window
BrowserWindowController *controller = [(MainController *)[NSApp delegate] getMainWindowBrowserController];
if (!controller) return nsnull;
return [controller currentWebNavigation];
}
#endif
- (void) historyItemAction:(id)sender
{
#if USE_GO_MENU
// get web navigation for current browser
nsIWebNavigation* webNav = [self currentWebNavigation];
if (!webNav) return;
// browse to the history entry for the menuitem that was selected
PRInt32 historyIndex = ([sender tag] - 1) - kDividerTag;
webNav->GotoIndex(historyIndex);
#else
NSString* urlToLoad = [[sender representedObject] url];
BrowserWindowController* bwc = [(MainController *)[NSApp delegate] getMainWindowBrowserController];
if (bwc)
[bwc loadURL:urlToLoad referrer:nil activate:YES allowPopups:NO];
else
[(MainController *)[NSApp delegate] openNewWindowOrTabWithURL:urlToLoad andReferrer:nil];
#endif
}
- (void) emptyHistoryItems
{
// remove every history item after the insertion point
int insertionIndex = [self indexOfItemWithTag:kDividerTag];
for (int i = [self numberOfItems]-1; i > insertionIndex ; --i) {
[self removeItemAtIndex:i];
}
#if !USE_GO_MENU
[mBuckets autorelease];
#endif
}
- (void) addHistoryItems
{
#if USE_GO_MENU
// get session history for current browser
nsIWebNavigation* webNav = [self currentWebNavigation];
if (!webNav) return;
nsCOMPtr<nsISHistory> sessionHistory;
webNav->GetSessionHistory(getter_AddRefs(sessionHistory));
PRInt32 count;
sessionHistory->GetCount(&count);
PRInt32 currentIndex;
sessionHistory->GetIndex(&currentIndex);
// determine the range of items to display
int rangeStart, rangeEnd;
int above = kMaxItems/2;
int below = (kMaxItems-above)-1;
if (count <= kMaxItems) {
// if the whole history fits within our menu, fit range to show all
rangeStart = count-1;
rangeEnd = 0;
} else {
// if the whole history is too large for menu, try to put current index as close to
// the middle of the list as possible, so the user can see both back and forward in session
rangeStart = currentIndex + above;
rangeEnd = currentIndex - below;
if (rangeStart >= count-1) {
rangeEnd -= (rangeStart-count)+1; // shift overflow to the end
rangeStart = count-1;
} else if (rangeEnd < 0) {
rangeStart -= rangeEnd; // shift overflow to the start
rangeEnd = 0;
}
}
// create a new menu item for each history entry (up to MAX_MENUITEM entries)
for (PRInt32 i = rangeStart; i >= rangeEnd; --i) {
nsCOMPtr<nsIHistoryEntry> entry;
sessionHistory->GetEntryAtIndex(i, PR_FALSE, getter_AddRefs(entry));
nsXPIDLString textStr;
entry->GetTitle(getter_Copies(textStr));
NSString* title = [[NSString stringWith_nsAString: textStr] stringByTruncatingTo:kMaxTitleLength at:kTruncateAtMiddle];
NSMenuItem *newItem = [self addItemWithTitle:title action:@selector(historyItemAction:) keyEquivalent:@""];
[newItem setTarget:self];
[newItem setTag:kDividerTag+1+i];
if (currentIndex == i)
[newItem setState:NSOnState];
}
#else
// first grab all the items in the history and put them into buckets based on
// the last visit date. we'll then sort each bucket. each bucket becomes a menu.
// XXX if there is just one menu and there are less than, say, 10 items, show them inline.
// XXX optimize by building once, then saving the results and observing additions/removals
// |mBuckets| is a dictionary of arrays. Each array represents a menu of all the
// websites for a given day in sorted order. The key for the array is the corresponding
// NSDate for that day. This lives until the menu is rebuilt so we don't have any
// lifetime issues.
mBuckets = [[NSMutableDictionary dictionary] retain];
nsCOMPtr<nsIBrowserHistory> globalHist = do_GetService("@mozilla.org/browser/global-history;2");
nsCOMPtr<nsIHistoryItems> history = do_QueryInterface(globalHist);
if (history) {
// walk the list of history items placing them into buckets by day.
nsCOMPtr<nsISimpleEnumerator> items;
history->GetItemEnumerator(getter_AddRefs(items));
PRBool hasMore = PR_FALSE;
while (NS_SUCCEEDED(items->HasMoreElements(&hasMore)) && hasMore) {
nsCOMPtr<nsISupports> supp;
if (NS_FAILED(items->GetNext(getter_AddRefs(supp))))
break;
nsCOMPtr<nsIHistoryItem> historyItem = do_QueryInterface(supp);
// the key for the elements of |mBuckets| is the date with the hours/minutes/seconds
// stripped off so that all visits that fall on the same day will map down to the same hash key.
// we then map back to a date object so it can be used to display a pretty date and sorted below.
PRTime lastVisit;
historyItem->GetLastVisitDate(&lastVisit);
NSDate* dateKey = [NSDate dateWithString:
[[NSDate dateWithPRTime:lastVisit] descriptionWithCalendarFormat:@"%Y-%m-%d 00:00:00 +0000"
timeZone:nil locale:nil]];
NSMutableArray* bucket = [mBuckets objectForKey:dateKey];
if (!bucket) {
bucket = [NSMutableArray array];
[mBuckets setObject:bucket forKey:dateKey];
id repObject = [sender representedObject];
if ([repObject isKindOfClass:[HistoryItem class]])
{
NSString* itemURL = [repObject url];
// XXX share this logic with MainController and HistoryOutlineViewDelegate
BrowserWindowController* bwc = [(MainController *)[NSApp delegate] getMainWindowBrowserController];
if (bwc)
{
if (GetCurrentKeyModifiers() & cmdKey)
{
BOOL backgroundLoad = [[PreferenceManager sharedInstance] getBooleanPref:"browser.tabs.loadInBackground" withSuccess:NULL];
if ([[PreferenceManager sharedInstance] getBooleanPref:"browser.tabs.opentabfor.middleclick" withSuccess:NULL])
[bwc openNewTabWithURL:itemURL referrer:nil loadInBackground:backgroundLoad allowPopups:NO];
else
[bwc openNewWindowWithURL:itemURL referrer: nil loadInBackground:backgroundLoad allowPopups:NO];
}
else
{
[bwc loadURL:itemURL referrer:nil activate:YES allowPopups:NO];
}
// create the HistoryMenuItem which we store until the next time the menu is built. The ordering
// of the array isn't really useful for us, we'll have to sort it later.
nsCString url;
historyItem->GetURL(url);
NSString* urlStr = [NSString stringWith_nsACString:url];
nsString title;
historyItem->GetTitle(title);
NSString* titleStr = [NSString stringWith_nsAString:title];
HistoryMenuItem* menuObject = [[[HistoryMenuItem alloc] initWithURL:urlStr title:titleStr lastVisit:lastVisit] autorelease];
[bucket addObject:menuObject];
}
else
[(MainController *)[NSApp delegate] openNewWindowOrTabWithURL:itemURL andReferrer:nil];
}
// Now that the buckets of days are built, we need to build up the actual menu. First sort the keys then
// walk them in order, building the top level items and their submenus
NSArray* sortedKeys = [[mBuckets allKeys] sortedArrayUsingSelector:@selector(compare:)];
NSEnumerator* e = [sortedKeys reverseObjectEnumerator]; // most recent at the top
NSDate* dateKey = nil;
while ((dateKey = [e nextObject])) {
// we sort the items in the bucket which sorts them by the full last visit date. Since we want the
// most recent (highest time) at the top, we need to use a reverse enumerator below as well.
NSMutableArray* bucket = [[mBuckets objectForKey:dateKey] sortedArrayUsingSelector:@selector(compare:)];
// create a toplevel menu with the date for |dateKey|
NSString* menuTitle = [dateKey descriptionWithCalendarFormat:NSLocalizedString(@"HistoryMenuDateFormat",nil) timeZone:nil locale:nil];
NSMenuItem* newItem = [self addItemWithTitle:menuTitle action:nil keyEquivalent:@""];
// build the child menu with all the history urls for this day We tie back to the url for the action by
// setting the represented object on the menu item (it's a weak ref, but means that the
// |menuObject| must outlive the duration of the time the menu is being tracked, which it
// certainly is).
NSMenu* childMenu = [[[NSMenu allocWithZone:[NSMenu menuZone]] initWithTitle:@""] autorelease];
NSEnumerator* e2 = [bucket reverseObjectEnumerator];
HistoryMenuItem* menuObject = nil;
while ((menuObject = [e2 nextObject])) {
NSString* itemTitle = [[menuObject title] stringByTruncatingTo:kMaxTitleLength at:kTruncateAtMiddle];
NSMenuItem* childMenuItem = [childMenu addItemWithTitle:itemTitle action:@selector(historyItemAction:) keyEquivalent:@""];
[childMenuItem setRepresentedObject:menuObject];
[childMenuItem setTarget:self];
}
[self setSubmenu:childMenu forItem:newItem];
}
#endif
}
@end
#pragma mark -
@interface GoMenu(Private)
- (void)appLaunchFinished:(NSNotification*)inNotification;
@end
@implementation GoMenu
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)awakeFromNib
{
[super awakeFromNib];
// listen for app launch completion
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appLaunchFinished:)
name:NSApplicationDidFinishLaunchingNotification
object:nil];
}
- (void)appLaunchFinished:(NSNotification*)inNotification
{
mAppLaunchDone = YES;
// setup the history menu after a delay, so that other app launch stuff
// finishes first
[self performSelector:@selector(setupHistoryMenu) withObject:nil afterDelay:0];
}
- (void)update
{
if (mAppLaunchDone)
{
// the root item is nil at launch, and if the history gets totally rebuilt
if (!mRootItem)
{
HistoryDataSource* dataSource = [GoMenuHistoryDataSourceOwner sharedHistoryDataSource];
[dataSource loadLazily];
mRootItem = [[dataSource rootItem] retain];
}
}
[super update];
}
@end

Просмотреть файл

@ -47,6 +47,7 @@
// two shutdown notifcations exist to allow listeners to guarantee ordering of
// notifcations, such that they can save state before xpcom-reliant data structures
// are torn down.
extern NSString* const InitEmbeddingNotificationName; // embedding was initted
extern NSString* const TermEmbeddingNotificationName; // someone called TermEmbedding
extern NSString* const XPCOMShutDownNotificationName; // XPCOM is about to shut down

Просмотреть файл

@ -55,6 +55,7 @@
#include "nsIMIMEInfo.h"
#include "nsIPref.h"
NSString* const InitEmbeddingNotificationName = @"InitEmebedding"; // this is actually broadcast from MainController
NSString* const TermEmbeddingNotificationName = @"TermEmbedding";
NSString* const XPCOMShutDownNotificationName = @"XPCOMShutDown";
@ -109,8 +110,10 @@ CHBrowserService::InitEmbedding()
static NS_DEFINE_CID(kHelperDlgCID, NS_HELPERAPPLAUNCHERDIALOG_CID);
nsresult rv = cr->RegisterFactory(kHelperDlgCID, NS_IHELPERAPPLAUNCHERDLG_CLASSNAME, NS_IHELPERAPPLAUNCHERDLG_CONTRACTID,
sSingleton);
if (NS_FAILED(rv))
return rv;
return rv;
return NS_OK;
}
/* static */

Просмотреть файл

@ -53,6 +53,12 @@
// return the first item (if any) with the given target and action.
- (id<NSMenuItem>)itemWithTarget:(id)anObject andAction:(SEL)actionSelector;
// remove items after the given item, or all items if nil
- (void)removeItemsAfterItem:(id<NSMenuItem>)inItem;
// remove all items including and after the given index (i.e. all items if index is 0)
- (void)removeItemsFromIndex:(int)inItemIndex;
@end

Просмотреть файл

@ -90,6 +90,25 @@
return [self itemAtIndex:itemIndex];
}
- (void)removeItemsAfterItem:(id<NSMenuItem>)inItem
{
int firstItemToRemoveIndex = 0;
if (inItem)
firstItemToRemoveIndex = [self indexOfItem:inItem] + 1;
[self removeItemsFromIndex:firstItemToRemoveIndex];
}
- (void)removeItemsFromIndex:(int)inItemIndex
{
if (inItemIndex < 0)
inItemIndex = 0;
while ([self numberOfItems] > inItemIndex)
[self removeItemAtIndex:inItemIndex];
}
@end

Просмотреть файл

@ -47,9 +47,9 @@
class nsIBrowserHistory;
class nsHistoryObserver;
extern NSString* const kHistoryViewByDate;
extern NSString* const kHistoryViewBySite;
extern NSString* const kHistoryViewFlat;
extern NSString* const kHistoryViewByDate; // grouped by last visit date
extern NSString* const kHistoryViewBySite; // grouped by site
extern NSString* const kHistoryViewFlat; // flat
// notification object is the data source.
extern NSString* const kNotificationNameHistoryDataSourceChanged;
@ -91,6 +91,7 @@ extern NSString* const kNotificationHistoryDataSourceChangedUserInfoChangedItemO
// XXX remove?
- (void)loadLazily;
- (HistoryItem*)rootItem;
// ideally sorting would be on the view, not the data source, but this keeps thing simpler
- (void)setSortColumnIdentifier:(NSString*)sortColumnIdentifier;

Просмотреть файл

@ -92,9 +92,9 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
// base class for a 'builder' object. This one just builds a flat list
@interface HistoryTreeBuilder : NSObject
{
HistoryItem* mRootItem;
SEL mSortSelector;
BOOL mSortDescending;
HistoryCategoryItem* mRootItem;
SEL mSortSelector;
BOOL mSortDescending;
}
// sets up the tree and sorts it
@ -104,8 +104,6 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
- (HistoryItem*)addItem:(HistorySiteItem*)item;
- (HistoryItem*)removeItem:(HistorySiteItem*)item;
- (HistoryItem*)parentOfItem:(HistorySiteItem*)item;
- (void)resortWithSelector:(SEL)sortSelector descending:(BOOL)descending;
// for internal use
@ -130,7 +128,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
@interface HistoryByDateTreeBuilder : HistoryTreeBuilder
{
NSMutableArray* mDateCategories; // array of HistoryCategoryItems ordered recent -> old
NSMutableArray* mDateCategories; // array of HistoryCategoryItems ordered recent -> old
}
- (void)setupDateCategories;
@ -225,21 +223,16 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
return mRootItem;
}
- (HistoryItem*)parentOfItem:(HistorySiteItem*)item
{
return mRootItem;
}
- (HistoryItem*)addItem:(HistorySiteItem*)item
{
[[mRootItem children] addObject:item];
[mRootItem addChild:item];
[self resortFromItem:mRootItem];
return mRootItem;
}
- (HistoryItem*)removeItem:(HistorySiteItem*)item
{
[[mRootItem children] removeObject:item];
[mRootItem removeChild:item];
// no need to resort
return mRootItem;
}
@ -248,7 +241,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
{
mRootItem = [[HistoryCategoryItem alloc] initWithTitle:@"" childCapacity:[items count]];
[[mRootItem children] addObjectsFromArray:items];
[mRootItem addChildren:items];
[self resortFromItem:mRootItem];
}
@ -294,11 +287,6 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
[super dealloc];
}
- (HistoryItem*)parentOfItem:(HistorySiteItem*)item
{
return [mSiteDictionary objectForKey:[item hostname]];
}
- (HistoryCategoryItem*)ensureHostCategoryForItem:(HistorySiteItem*)inItem
{
NSString* itemHostname = [inItem hostname];
@ -311,7 +299,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
hostCategory = [[HistorySiteCategoryItem alloc] initWithSite:itemHostname title:itemTitle childCapacity:10];
[mSiteDictionary setObject:hostCategory forKey:itemHostname];
[[mRootItem children] addObject:hostCategory];
[mRootItem addChild:hostCategory];
[hostCategory release];
}
return hostCategory;
@ -320,7 +308,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
- (void)removeSiteCategory:(HistoryCategoryItem*)item forHostname:(NSString*)hostname
{
[mSiteDictionary removeObjectForKey:hostname];
[[mRootItem children] removeObject:item];
[mRootItem removeChild:item];
}
- (HistoryItem*)addItem:(HistorySiteItem*)item
@ -331,7 +319,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
if (!hostCategory)
hostCategory = [self ensureHostCategoryForItem:item];
[[hostCategory children] addObject:item];
[hostCategory addChild:item];
[self resortFromItem:newHost ? mRootItem : hostCategory];
return hostCategory;
@ -341,7 +329,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
{
NSString* itemHostname = [item hostname];
HistoryCategoryItem* hostCategory = [mSiteDictionary objectForKey:itemHostname];
[[hostCategory children] removeObject:item];
[hostCategory removeChild:item];
// is the category is now empty, remove it
if ([hostCategory numberOfChildren] == 0)
@ -368,7 +356,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
while ((item = [itemsEnum nextObject]))
{
HistoryCategoryItem* hostCategory = [self ensureHostCategoryForItem:item];
[[hostCategory children] addObject:item];
[hostCategory addChild:item];
}
[self resortFromItem:mRootItem];
@ -385,9 +373,9 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
{
if ((self = [super init]))
{
mSortSelector = sortSelector;
mSortDescending = descending;
mSortSelector = sortSelector;
mSortDescending = descending;
[self setupDateCategories];
[self buildTreeWithItems:items];
}
@ -470,15 +458,10 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
return nil;
}
- (HistoryItem*)parentOfItem:(HistorySiteItem*)item
{
return [self categoryItemForDate:[item lastVisit]];
}
- (HistoryItem*)addItem:(HistorySiteItem*)item
{
HistoryCategoryItem* dateCategory = [self categoryItemForDate:[item lastVisit]];
[[dateCategory children] addObject:item];
[dateCategory addChild:item];
[self resortFromItem:dateCategory];
return dateCategory;
@ -487,7 +470,7 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
- (HistoryItem*)removeItem:(HistorySiteItem*)item
{
HistoryCategoryItem* dateCategory = [self categoryItemForDate:[item lastVisit]];
[[dateCategory children] removeObject:item];
[dateCategory removeChild:item];
// no need to resort
return dateCategory;
}
@ -499,11 +482,11 @@ static int HistoryItemSort(id firstItem, id secondItem, void* context)
while ((item = [itemsEnum nextObject]))
{
HistoryCategoryItem* dateCategory = [self categoryItemForDate:[item lastVisit]];
[[dateCategory children] addObject:item];
[dateCategory addChild:item];
}
mRootItem = [[HistoryCategoryItem alloc] initWithTitle:@"" childCapacity:[mDateCategories count]];
[[mRootItem children] addObjectsFromArray:mDateCategories];
[mRootItem addChildren:mDateCategories];
[self resortFromItem:mRootItem];
}
@ -633,6 +616,7 @@ NS_IMPL_ISUPPORTS1(nsHistoryObserver, nsIHistoryObserver);
mGlobalHistory = globalHist;
if (!mGlobalHistory)
{
NSLog(@"Failed to initialize HistoryDataSource (couldn't get global history)");
[self autorelease];
return nil;
}
@ -814,6 +798,11 @@ NS_IMPL_ISUPPORTS1(nsHistoryObserver, nsIHistoryObserver);
[self rebuildHistory];
}
- (HistoryItem*)rootItem
{
return [mTreeBuilder rootItem];
}
- (void)notifyChanged:(HistoryItem*)changeRoot itemOnly:(BOOL)itemOnly
{
// if we are displaying search results, make sure that updates

Просмотреть файл

@ -44,6 +44,7 @@ class nsIHistoryItem;
@interface HistoryItem : NSObject
{
HistoryItem* mParentItem; // not retained
}
- (NSString*)title;
@ -56,6 +57,10 @@ class nsIHistoryItem;
- (NSString*)hostname;
- (NSString*)identifier;
- (void)setParentItem:(HistoryItem*)inParent;
- (HistoryItem*)parentItem;
- (BOOL)isDescendentOfItem:(HistoryItem*)inItem;
- (NSMutableArray*)children;
- (int)numberOfChildren;
- (HistoryItem*)childAtIndex:(int)inIndex;
@ -83,6 +88,10 @@ class nsIHistoryItem;
- (NSString*)title;
- (NSString*)identifier; // return UUID for this folder
- (void)addChild:(HistoryItem*)inChild;
- (void)removeChild:(HistoryItem*)inChild;
- (void)addChildren:(NSArray*)inChildren;
@end
@ -106,6 +115,7 @@ class nsIHistoryItem;
- (id)initWithStartDate:(NSDate*)startDate ageInDays:(int)days title:(NSString*)title childCapacity:(int)capacity;
- (NSDate*)startDate;
- (BOOL)isTodayCategory;
@end

Просмотреть файл

@ -109,6 +109,24 @@ enum
return @"";
}
- (void)setParentItem:(HistoryItem*)inParent
{
mParentItem = inParent;
}
- (HistoryItem*)parentItem
{
return mParentItem;
}
- (BOOL)isDescendentOfItem:(HistoryItem*)inItem
{
if (inItem == mParentItem)
return YES;
return [mParentItem isDescendentOfItem:inItem];
}
- (NSMutableArray*)children
{
return nil;
@ -233,6 +251,36 @@ enum
return nil;
}
- (void)addChild:(HistoryItem*)inChild
{
[mChildren addObject:inChild];
[inChild setParentItem:self];
}
- (void)insertChild:(HistoryItem*)inChild atIndex:(unsigned int)inIndex
{
[mChildren insertObject:inChild atIndex:inIndex];
[inChild setParentItem:self];
}
- (void)removeChild:(HistoryItem*)inChild
{
[mChildren removeObject:inChild];
[inChild setParentItem:nil];
}
- (void)addChildren:(NSArray*)inChildren
{
[inChildren makeObjectsPerformSelector:@selector(setParentItem:) withObject:self];
[mChildren addObjectsFromArray:inChildren];
}
- (void)removeAllChildren
{
[mChildren makeObjectsPerformSelector:@selector(setParentItem:) withObject:nil];
[mChildren removeAllObjects];
}
- (NSComparisonResult)compareURL:(HistoryItem *)aItem sortDescending:(NSNumber*)inDescending
{
// always sort categories before sites
@ -314,6 +362,11 @@ enum
return [NSString stringWithFormat:@"site:%d", mSite];
}
- (NSString*)description
{
return [NSString stringWithFormat:@"HistorySiteCategoryItem %p, site %@", self, mSite];
}
@end // HistorySiteCategoryItem
#pragma mark -
@ -341,11 +394,21 @@ enum
return mStartDate;
}
- (BOOL)isTodayCategory
{
return (mAgeInDays == 0);
}
- (NSString*)identifier
{
return [NSString stringWithFormat:@"age_in_days:%d", mAgeInDays];
}
- (NSString*)description
{
return [NSString stringWithFormat:@"HistoryDateCategoryItem %p, start date %@, age %d days", self, mStartDate, mAgeInDays];
}
- (NSComparisonResult)startDateCompare:(HistoryItem *)aItem sortDescending:(NSNumber*)inDescending
{
// always sort categories before sites
@ -378,7 +441,7 @@ enum
return NSOrderedAscending;
// sort category items on date, always ascending
NSComparisonResult result = [[self startDate] compare:[aItem startDate]];
NSComparisonResult result = [[self startDate] compare:[(HistoryDateCategoryItem*)aItem startDate]];
return [inDescending boolValue] ? (NSComparisonResult)(-1 * (int)result) : result;
}

Просмотреть файл

@ -290,6 +290,10 @@ static BOOL gMadePrefManager;
NS_IF_ADDREF(mPrefs);
[self syncMozillaPrefs];
// send out initted notification
[[NSNotificationCenter defaultCenter] postNotificationName:InitEmbeddingNotificationName object:nil];
return YES;
}