Camino only - Bug 166288: Autocomplete from bookmark URLs in addition to history. Patch by Dan Weber <dan.j.weber@gmail.com>. r=smorgan, sr=pink

git-svn-id: svn://10.0.0.236/trunk@257706 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
stuart.morgan%alumni.case.edu 2009-07-15 18:41:46 +00:00
parent c9c1ad08b1
commit 5ba317e9b4
10 changed files with 345 additions and 239 deletions

View File

@ -39,26 +39,46 @@
#import <AppKit/AppKit.h>
class nsIAutoCompleteResults;
@class HistoryItem;
//
// This class is in charge of retrieving/sorting the history and bookmark results
// for the location bar autocomplete, and is also the datasource for the table view
// in the autocomplete popup window. All of the work is performed on the main thread
// using a chunked asynchronous method, which should allow everything to run smoothly
// even with large number of bookmark/history items.
//
@interface AutoCompleteDataSource : NSObject
{
NSImage* mGenericSiteIcon; // owned
NSImage* mGenericFileIcon; // owned
NSString* mErrorMessage; // owned
id mDelegate;
nsIAutoCompleteResults* mResults; // owned
NSRange mChunkRange;
NSPredicate* mRegexTest; // owned
NSMutableArray* mBookmarkData; // owned
NSMutableArray* mHistoryData; // owned
NSMutableArray* mBookmarkResultsInProgress; // owned
NSMutableArray* mHistoryResultsInProgress; // owned
NSMutableArray* mResults; // owned
NSImage* mGenericSiteIcon; // owned
NSImage* mGenericFileIcon; // owned
}
- (id) init;
// Pulls bookmarks and history items and puts them into mBookmarkData and mHistoryData.
- (void)loadSearchableData;
- (int) rowCount;
- (id) resultForRow:(int)aRow columnIdentifier:(NSString *)aColumnIdentifier;
// Starts the search for matching bookmarks/history items using the string passed
// from AutoCompleteTextField. This is an asynchronous method--when the search is
// complete, searchResultsAvailable will be called on the delegate.
- (void)performSearchWithString:(NSString *)searchString delegate:(id)delegate;
- (void) setErrorMessage: (NSString*) error;
- (NSString*) errorMessage;
// Returns the number of rows matching the search string, including headers.
- (int)rowCount;
- (void) setResults: (nsIAutoCompleteResults*) results;
- (nsIAutoCompleteResults*) results;
// Datasource methods.
- (int)numberOfRowsInTableView:(NSTableView*)aTableView;
- (id)resultForRow:(int)aRow columnIdentifier:(NSString *)aColumnIdentifier;
@end

View File

@ -42,21 +42,63 @@
#import <AppKit/AppKit.h>
#import "AutoCompleteDataSource.h"
#import "AutoCompleteTextField.h"
#import "AutoCompleteResult.h"
#import "CHBrowserService.h"
#import "SiteIconProvider.h"
#include "nsString.h"
#include "nsCRT.h"
#include "nsIAutoCompleteResults.h"
#include "nsIHistoryItems.h"
#import "Bookmark.h"
#import "BookmarkFolder.h"
#import "BookmarkManager.h"
#import "HistoryItem.h"
#import "HistoryDataSource.h"
const unsigned int kMaxResultsPerHeading = 5;
const unsigned int kNumberOfItemsPerChunk = 100;
@interface AutoCompleteDataSource (Private)
// Clears data previously loaded into mBookmarkData and mHistoryData.
// Also resets the chunk for a new search.
- (void)resetSearch;
// Checks if a given item (bookmark or history) matches the search string.
- (BOOL)searchStringMatchesItem:(id)item;
// Creates and returns an AutoCompleteResult object for the given item.
// The AutoCompleteResult class is used by the AutoCompleteCell to store
// all of the data relevant to drawing a cell.
- (AutoCompleteResult *)autoCompleteResultForItem:(id)item;
// Processes one chunk of the bookmark/history data (as specified by
// kNumberOfItemsPerChunk), then checks if we are done (i.e. when we
// have finished looking at all bookmark/history data or when we have
// enough results to satisfy kMaxResultsPerHeading).
- (void)processNextSearchChunk;
// Iterates though a chunk of the data array and looks for matches to the
// search string. When a match is found, it is added to the results array.
- (void)processChunkOfData:(NSArray *)dataArray forResults:(NSMutableArray *)resultsArray;
// Adds headers to results arrays and consolidates into mResults array,
// then calls searchResultsAvailable on the delegate.
- (void)reportResults;
// Adds the header to the specified results array.
- (void)addHeader:(NSString *)header toResults:(NSMutableArray *)results;
@end
@implementation AutoCompleteDataSource
-(id)init
{
if ((self = [super init])) {
mResults = nil;
mBookmarkData = [[NSMutableArray alloc] init];
mHistoryData = [[NSMutableArray alloc] init];
mBookmarkResultsInProgress = [[NSMutableArray alloc] init];
mHistoryResultsInProgress = [[NSMutableArray alloc] init];
mResults = [[NSMutableArray alloc] init];
mGenericSiteIcon = [[NSImage imageNamed:@"globe_ico"] retain];
mGenericFileIcon = [[NSImage imageNamed:@"smallDocument"] retain];
}
@ -65,99 +107,161 @@
-(void)dealloc
{
NS_IF_RELEASE(mResults);
[mBookmarkData release];
[mHistoryData release];
[mBookmarkResultsInProgress release];
[mHistoryResultsInProgress release];
[mResults release];
[mRegexTest release];
[mGenericSiteIcon release];
[mGenericFileIcon release];
[mErrorMessage release];
[super dealloc];
}
- (void) setErrorMessage: (NSString*) error
- (void)resetSearch
{
[self setResults:nsnull]; // releases mErrorMessage
mErrorMessage = [error retain];
[mBookmarkResultsInProgress removeAllObjects];
[mHistoryResultsInProgress removeAllObjects];
[mRegexTest release];
mRegexTest = nil;
mChunkRange = NSMakeRange(0, kNumberOfItemsPerChunk);
}
- (NSString*) errorMessage
- (void)loadSearchableData
{
return mErrorMessage;
[mBookmarkData removeAllObjects];
[mHistoryData removeAllObjects];
BookmarkFolder *bookmarkRoot = [[BookmarkManager sharedBookmarkManager] bookmarkRoot];
[mBookmarkData addObjectsFromArray:[bookmarkRoot allChildBookmarks]];
NSSortDescriptor *visitCountDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"numberOfVisits"
ascending:NO] autorelease];
[mBookmarkData sortUsingDescriptors:[NSArray arrayWithObject:visitCountDescriptor]];
HistoryDataSource *historyDataSource = [[[HistoryDataSource alloc] init] autorelease];
[historyDataSource setHistoryView:kHistoryViewFlat];
[historyDataSource setSortColumnIdentifier:@"visit_count"];
[historyDataSource setSortDescending:YES];
[historyDataSource loadLazily];
HistoryItem *rootHistoryItem = [historyDataSource rootItem];
NSEnumerator *historyEnum = [[rootHistoryItem children] objectEnumerator];
HistoryItem *curChild;
while ((curChild = [historyEnum nextObject])) {
if ([curChild isKindOfClass:[HistorySiteItem class]])
[mHistoryData addObject:curChild];
}
}
- (void) setResults:(nsIAutoCompleteResults*)aResults
- (void)performSearchWithString:(NSString *)searchString delegate:(id)delegate
{
NS_IF_RELEASE(mResults);
[mErrorMessage release];
mErrorMessage = nil;
mResults = aResults;
NS_IF_ADDREF(mResults);
mDelegate = delegate;
[self resetSearch];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
// Construct the regular expression. NSPredicate will only evaluate to true if the
// entire string matches--thus the leading and trailing ".*".
NSString *regex;
if ([searchString rangeOfString:@"://"].location == NSNotFound)
regex = [NSString stringWithFormat:@".*://(www\\.)?%@.*", searchString];
else
regex = [NSString stringWithFormat:@".*%@.*", searchString];
mRegexTest = [[NSPredicate predicateWithFormat:@"SELF MATCHES[cd] %@", regex] retain];
[self performSelector:@selector(processNextSearchChunk) withObject:nil afterDelay:0.0];
}
- (nsIAutoCompleteResults *) results
- (void)processNextSearchChunk
{
return mResults;
[self processChunkOfData:mBookmarkData forResults:mBookmarkResultsInProgress];
[self processChunkOfData:mHistoryData forResults:mHistoryResultsInProgress];
// Check if finished.
BOOL bookmarksDone = [mBookmarkResultsInProgress count] == kMaxResultsPerHeading ||
NSMaxRange(mChunkRange) >= [mBookmarkData count];
BOOL historyDone = [mHistoryResultsInProgress count] == kMaxResultsPerHeading ||
NSMaxRange(mChunkRange) >= [mHistoryData count];
if (bookmarksDone && historyDone) {
[self reportResults];
} else {
// Set new range and process the next chunk.
mChunkRange = NSMakeRange(NSMaxRange(mChunkRange), kNumberOfItemsPerChunk);
[self performSelector:@selector(processNextSearchChunk) withObject:nil afterDelay:0.0];
}
}
- (int) rowCount
- (void)processChunkOfData:(NSArray *)dataArray forResults:(NSMutableArray *)resultsArray
{
if (!mResults)
return 0;
nsCOMPtr<nsISupportsArray> items;
mResults->GetItems(getter_AddRefs(items));
PRUint32 count;
items->Count(&count);
return count;
for (unsigned int i = mChunkRange.location; i < [dataArray count] && i < NSMaxRange(mChunkRange)
&& [resultsArray count] < kMaxResultsPerHeading; i++) {
id dataItem = [dataArray objectAtIndex:i];
if ([self searchStringMatchesItem:dataItem]) {
AutoCompleteResult *info = [self autoCompleteResultForItem:dataItem];
if (![mBookmarkResultsInProgress containsObject:info] && ![mHistoryResultsInProgress containsObject:info])
[resultsArray addObject:info];
}
}
}
- (id) resultForRow:(int)aRow columnIdentifier:(NSString *)aColumnIdentifier
- (BOOL)searchStringMatchesItem:(id)item
{
return [mRegexTest evaluateWithObject:[item url]];
}
- (AutoCompleteResult *)autoCompleteResultForItem:(id)item
{
AutoCompleteResult *info = [[[AutoCompleteResult alloc] init] autorelease];
[info setIcon:mGenericSiteIcon];
if (!mResults)
return info;
nsCOMPtr<nsISupportsArray> items;
mResults->GetItems(getter_AddRefs(items));
nsCOMPtr<nsISupports> itemSupports = dont_AddRef(items->ElementAt(aRow));
nsCOMPtr<nsIHistoryItem> item = do_QueryInterface(itemSupports);
if (!item)
return info;
// Set URL.
nsCAutoString value;
item->GetURL(value);
NSString* urlString = [[NSString stringWith_nsACString:value] unescapedURI];
[info setUrl:urlString];
// Set title.
nsAutoString titleStr;
item->GetTitle(titleStr);
[info setTitle:[NSString stringWith_nsAString:titleStr]];
// Set icon.
NSImage* cachedFavicon = [[SiteIconProvider sharedFavoriteIconProvider] favoriteIconForPage:urlString];
if (cachedFavicon) {
[info setTitle:[item title]];
[info setUrl:[item url]];
NSImage* cachedFavicon = [[SiteIconProvider sharedFavoriteIconProvider] favoriteIconForPage:[info url]];
if (cachedFavicon)
[info setIcon:cachedFavicon];
} else if ([urlString hasPrefix:@"file://"]) {
else if ([[info url] hasPrefix:@"file://"])
[info setIcon:mGenericFileIcon];
}
else
[info setIcon:mGenericSiteIcon];
return info;
}
-(int) numberOfRowsInTableView:(NSTableView*)aTableView
- (void)reportResults
{
[self addHeader:@"Bookmarks" toResults:mBookmarkResultsInProgress];
[self addHeader:@"History" toResults:mHistoryResultsInProgress];
[mResults removeAllObjects];
[mResults addObjectsFromArray:mBookmarkResultsInProgress];
[mResults addObjectsFromArray:mHistoryResultsInProgress];
[self resetSearch];
[mDelegate searchResultsAvailable];
}
- (void)addHeader:(NSString *)header toResults:(NSMutableArray *)results
{
if ([results count] == 0) {
// Don't add a header to empty results.
return;
}
AutoCompleteResult *info = [[[AutoCompleteResult alloc] init] autorelease];
[info setIsHeader:YES];
[info setTitle:header];
[results insertObject:info atIndex:0];
}
- (int)rowCount {
return [mResults count];
}
- (id)resultForRow:(int)aRow columnIdentifier:(NSString *)aColumnIdentifier
{
if (aRow >= 0 && aRow < [self rowCount])
return [mResults objectAtIndex:aRow];
return nil;
}
- (int)numberOfRowsInTableView:(NSTableView*)aTableView
{
return [self rowCount];
}
-(id)tableView:(NSTableView*)aTableView objectValueForTableColumn:(NSTableColumn*)aTableColumn row:(int)aRowIndex
- (id)tableView:(NSTableView*)aTableView objectValueForTableColumn:(NSTableColumn*)aTableColumn row:(int)aRowIndex
{
return [self resultForRow:aRowIndex columnIdentifier:[aTableColumn identifier]];
}

View File

@ -66,10 +66,6 @@ extern NSString* const kWillShowFeedMenu;
AutoCompleteDataSource* mDataSource;
NSString* mSearchString;
NSTimer* mOpenTimer;
nsIAutoCompleteSession* mSession; // owned
nsIAutoCompleteResults* mResults; // owned
nsIAutoCompleteListener* mListener; // owned
// used to remember if backspace was pressed in complete: so we can check this in controlTextDidChange
BOOL mBackspaced;
@ -80,9 +76,7 @@ extern NSString* const kWillShowFeedMenu;
BOOL mCompleteWhileTyping;
}
// get/set the autcomplete session
- (void) setSession:(NSString *)aSession;
- (NSString *) session;
- (void)searchResultsAvailable;
- (PageProxyIcon*)pageProxyIcon;
- (void)setPageProxyIcon:(NSImage *)aImage;

View File

@ -53,17 +53,11 @@
#import "PreferenceManager.h"
#import "CHBrowserService.h"
#include "nsIAutoCompleteSession.h"
#include "nsIAutoCompleteResults.h"
#include "nsIAutoCompleteListener.h"
#include "nsIBrowserHistory.h"
#include "BookmarkManager.h"
#include "Bookmark.h"
#include "nsIServiceManager.h"
#include "nsIWebProgressListener.h"
#include "nsMemory.h"
#include "nsString.h"
static const int kMaxRows = 6;
static const int kFrameMargin = 1;
const float kSecureIconRightOffset = 19.0; // offset from right size of url bar
@ -83,11 +77,9 @@ NSString* const kWillShowFeedMenu = @"WillShowFeedMenu";
@interface AutoCompleteTextField(Private)
- (NSTableView *) tableView;
- (int) visibleRows;
- (void) startSearch:(NSString*)aString complete:(BOOL)aComplete;
- (void) performSearch;
- (void) dataReady:(nsIAutoCompleteResults*)aResults status:(AutoCompleteStatus)aStatus;
- (void) searchTimer:(NSTimer *)aTimer;
- (void) completeDefaultResult;
@ -294,34 +286,6 @@ NSString* const kWillShowFeedMenu = @"WillShowFeedMenu";
#pragma mark -
class AutoCompleteListener : public nsIAutoCompleteListener
{
public:
AutoCompleteListener(AutoCompleteTextField* aTextField)
{
mTextField = aTextField;
}
NS_DECL_ISUPPORTS
NS_IMETHODIMP OnStatus(const PRUnichar* aText) { return NS_OK; }
NS_IMETHODIMP SetParam(nsISupports *aParam) { return NS_OK; }
NS_IMETHODIMP GetParam(nsISupports **aParam) { return NS_OK; }
NS_IMETHODIMP OnAutoComplete(nsIAutoCompleteResults *aResults, AutoCompleteStatus aStatus)
{
[mTextField dataReady:aResults status:aStatus];
return NS_OK;
}
private:
AutoCompleteTextField *mTextField;
};
NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
#pragma mark -
@implementation AutoCompleteTextField
+ (Class) cellClass
@ -347,15 +311,9 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
- (void) awakeFromNib
{
mListener = (nsIAutoCompleteListener *)new AutoCompleteListener(self);
NS_IF_ADDREF(mListener);
[self setFont:[NSFont controlContentFontOfSize:0]];
[self setDelegate: self];
// XXX the owner of the textfield should set this
[self setSession:@"history"];
// construct and configure the view
mTableView = [[[NSTableView alloc] initWithFrame:NSZeroRect] autorelease];
[mTableView setIntercellSpacing:NSMakeSize(0, 2)];
@ -401,16 +359,11 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
// hide the table header
[mTableView setHeaderView:nil];
// construct the scroll view that contains the table view
NSScrollView *scrollView = [[[NSScrollView alloc] initWithFrame:NSZeroRect] autorelease];
[scrollView setHasVerticalScroller:NO];
[scrollView setDocumentView: mTableView];
// Construct and configure the popup window. It is necessary to give the view some
// initial dimension because of the way that MAAttachedWindow constructs itself.
NSView* tableViewContainerView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)] autorelease];
[tableViewContainerView addSubview:scrollView];
[tableViewContainerView addSubview:mTableView];
const float kPopupWindowOffsetFromLocationField = 8.0;
mPopupWin = [[MAAttachedWindow alloc] initWithView:tableViewContainerView
attachedToPoint:NSZeroPoint
@ -493,10 +446,6 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
mLock = nil;
[mFeedIcon release];
mFeedIcon = nil;
NS_IF_RELEASE(mSession);
NS_IF_RELEASE(mResults);
NS_IF_RELEASE(mListener);
}
- (void)shutdown: (NSNotification*)aNotification
@ -504,34 +453,11 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
[self cleanup];
}
- (void) setSession:(NSString *)aSession
{
NS_IF_RELEASE(mSession);
// XXX add aSession to contract id
nsCOMPtr<nsIAutoCompleteSession> session =
do_GetService(NS_GLOBALHISTORY_AUTOCOMPLETE_CONTRACTID);
mSession = session;
NS_IF_ADDREF(mSession);
}
- (NSString *) session
{
// XXX return session name
return @"";
}
- (NSTableView *) tableView
{
return mTableView;
}
- (int) visibleRows
{
int minRows = [mDataSource rowCount];
return minRows < kMaxRows ? minRows : kMaxRows;
}
- (PageProxyIcon*) pageProxyIcon
{
return mProxyIcon;
@ -579,6 +505,9 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
if ([self isOpen]) {
[self performSearch];
} else {
// Reload search data.
[mDataSource loadSearchableData];
// delay the search when the popup is not yet opened so that users
// don't see a jerky flashing popup when they start typing for the first time
if (mOpenTimer) {
@ -594,37 +523,17 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
- (void) performSearch
{
// sometimes we get a null mSession, and if we don't check for that we crash
if (mSession) {
nsAutoString searchString;
[mSearchString assignTo_nsAString:searchString];
nsresult rv = mSession->OnStartLookup(searchString.get(), mResults, mListener);
if (NS_FAILED(rv))
NSLog(@"Unable to perform autocomplete lookup");
}
[mDataSource performSearchWithString:mSearchString delegate:self];
}
- (void) dataReady:(nsIAutoCompleteResults*)aResults status:(AutoCompleteStatus)aStatus
{
NS_IF_RELEASE(mResults);
mResults = nsnull;
if (aStatus == nsIAutoCompleteStatus::failed) {
[mDataSource setErrorMessage:@""];
} else if (aStatus == nsIAutoCompleteStatus::ignored) {
[mDataSource setErrorMessage:@""];
} else if (aStatus == nsIAutoCompleteStatus::noMatch) {
[mDataSource setErrorMessage:@""];
} else if (aStatus == nsIAutoCompleteStatus::matchFound) {
mResults = aResults;
NS_IF_ADDREF(mResults);
[mDataSource setResults:aResults];
[self completeDefaultResult];
}
- (void)searchResultsAvailable {
if ([mDataSource rowCount] > 0) {
// Make sure a header row doesn't get selected.
if ([[mDataSource resultForRow:[mTableView selectedRow] columnIdentifier:@"main"] isHeader])
[self selectRowBy:-1];
[mTableView noteNumberOfRowsChanged];
[self openPopup];
[self completeDefaultResult];
} else {
[self closePopup];
}
@ -644,11 +553,6 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
if (mSearchString)
[mSearchString release];
mSearchString = nil;
NS_IF_RELEASE(mResults);
mResults = nsnull;
[mDataSource setResults:nil];
[self closePopup];
}
@ -670,7 +574,7 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
- (void) resizePopup:(BOOL)forceResize
{
if ([self visibleRows] == 0) {
if ([mDataSource rowCount] == 0) {
[self closePopup];
return;
}
@ -687,15 +591,15 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
// Resize views.
const float kHorizontalPadding = 5.0;
int tableHeight = (int)([mTableView rowHeight] + [mTableView intercellSpacing].height) * [self visibleRows];
NSRect scrollViewFrame = NSZeroRect;
scrollViewFrame.size.height = tableHeight;
scrollViewFrame.size.width = locationFrame.size.width - (2 * kFrameMargin);
scrollViewFrame.origin.y = kHorizontalPadding;
NSRect containerViewFrame = scrollViewFrame;
int tableHeight = (int)([mTableView rowHeight] + [mTableView intercellSpacing].height) * [mDataSource rowCount];
NSRect tableViewFrame = NSZeroRect;
tableViewFrame.size.height = tableHeight;
tableViewFrame.size.width = locationFrame.size.width - (2 * kFrameMargin);
tableViewFrame.origin.y = kHorizontalPadding;
NSRect containerViewFrame = tableViewFrame;
containerViewFrame.size.height += kHorizontalPadding * 2.0;
[[[mTableView enclosingScrollView] superview] setFrame:containerViewFrame];
[[mTableView enclosingScrollView] setFrame:scrollViewFrame];
[[mTableView superview] setFrame:containerViewFrame];
[mTableView setFrame:tableViewFrame];
[mPopupWin setPoint:NSMakePoint(NSMidX(locationFrame), locationFrame.origin.y) side:MAPositionBottom];
}
@ -785,10 +689,8 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
- (void) completeDefaultResult
{
PRInt32 defaultRow;
mResults->GetDefaultItemIndex(&defaultRow);
if (mCompleteResult && mCompleteWhileTyping) {
PRInt32 defaultRow = 1;
[self selectRowAt:defaultRow];
[self completeResult:defaultRow];
} else {
@ -812,7 +714,7 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
[[self fieldEditor] setSelectedRange:selectAtEnd];
}
else {
if ([mDataSource rowCount] <= 0)
if ([mDataSource rowCount] == 0)
return;
// Fill in the suggestion from the list, but change only the text
@ -831,7 +733,7 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
if (([[searchURL scheme] length] == 0) || ![[searchURL scheme] isEqualToString:[resultURL scheme]])
protocolLength = [[resultURL scheme] length];
}
NSRange matchRange = [result rangeOfString:mSearchString options:NSCaseInsensitiveSearch range:NSMakeRange(protocolLength, [result length] - protocolLength)];
if (matchRange.length > 0 && matchRange.location != NSNotFound) {
unsigned int location = matchRange.location + matchRange.length;
@ -1007,6 +909,10 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
// selecting rows /////////////////////////////////////////
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)rowIndex {
return (![[mDataSource resultForRow:rowIndex columnIdentifier:@"main"] isHeader]);
}
- (void) selectRowAt:(int)aRow
{
if (aRow >= -1 && [mDataSource rowCount] > 0) {
@ -1047,6 +953,9 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
} else {
// no special case, just increment current row
row += aRows;
// make sure we didn't just select a header
if ([[mDataSource resultForRow:row columnIdentifier:@"main"] isHeader])
row += aRows / abs(aRows);
}
[self selectRowAt:row];
@ -1182,22 +1091,16 @@ NS_IMPL_ISUPPORTS1(AutoCompleteListener, nsIAutoCompleteListener)
[self startSearch:[self stringValue] complete:YES];
return YES;
}
} else if (command == @selector(scrollPageUp:)) {
[self selectRowBy:-kMaxRows];
[self completeSelectedResult];
} else if (command == @selector(scrollPageDown:)) {
[self selectRowBy:kMaxRows];
[self completeSelectedResult];
} else if (command == @selector(moveToBeginningOfDocument:)) {
[self selectRowAt:0];
[self selectRowAt:1];
[self completeSelectedResult];
} else if (command == @selector(moveToEndOfDocument:)) {
[self selectRowAt:[mTableView numberOfRows]-1];
[self completeSelectedResult];
} else if (command == @selector(complete:)) {
[self selectRowBy:1];
[self completeSelectedResult];
return YES;
[self selectRowBy:1];
[self completeSelectedResult];
return YES;
} else if (command == @selector(insertTab:)) {
if ([mPopupWin isVisible]) {
[self selectRowBy:1];

View File

@ -49,6 +49,9 @@ static NSSize kIconSize = {16, 16};
// Creates and returns a dictionary of attributes for the url string.
- (NSMutableDictionary *)urlAttributes;
// Creates and returns a dictionary of attributes for the url string.
- (NSDictionary *)headerAttributes;
// Divides a rectangle into appropriate-sized rectangles for the site
// favicon, title string, and url string.
- (void)createImageRect:(NSRect *)imageRect titleRect:(NSRect *)titleTextRect urlRect:(NSRect *)urlTextRect fromRect:(NSRect)cellFrame;
@ -82,6 +85,14 @@ static NSSize kIconSize = {16, 16};
nil];
}
- (NSDictionary *)headerAttributes
{
return [NSDictionary dictionaryWithObjectsAndKeys:
[NSColor grayColor], NSForegroundColorAttributeName,
[NSFont systemFontOfSize:12.0], NSFontAttributeName,
nil];
}
- (void)createImageRect:(NSRect *)imageRect titleRect:(NSRect *)titleTextRect urlRect:(NSRect *)urlTextRect fromRect:(NSRect)cellFrame
{
const int kHorizontalPadding = 10;
@ -133,26 +144,38 @@ static NSSize kIconSize = {16, 16};
// Start drawing.
[controlView lockFocus];
if ([self isHighlighted]) {
// We're highlighted, so draw selection gradient.
[self drawHighlightInRect:cellFrame];
// Set highlighted text colors.
[titleAttributes setValue:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
[urlAttributes setValue:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
}
NSRect imageRect;
NSRect titleTextRect;
NSRect urlTextRect;
[self createImageRect:&imageRect titleRect:&titleTextRect urlRect:&urlTextRect fromRect:cellFrame];
// Move the origin of the smaller-size url text so it aligns with the title text.
urlTextRect.origin.y += titleTextHeight - urlTextHeight;
if ([[self objectValue] isHeader]) {
// Draw a header row.
const int kHorizontalPadding = 10;
const int kTextVerticalPadding = (NSHeight(cellFrame) - titleTextHeight) * 0.5;
[title drawInRect:NSMakeRect(cellFrame.origin.x + kHorizontalPadding,
cellFrame.origin.y + kTextVerticalPadding,
cellFrame.size.width - kHorizontalPadding * 2,
titleTextHeight)
withAttributes:[self headerAttributes]];
} else {
if ([self isHighlighted]) {
// We're highlighted, so draw selection gradient.
[self drawHighlightInRect:cellFrame];
// Set highlighted text colors.
[titleAttributes setValue:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
[urlAttributes setValue:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
}
NSRect imageRect;
NSRect titleTextRect;
NSRect urlTextRect;
[self createImageRect:&imageRect titleRect:&titleTextRect urlRect:&urlTextRect fromRect:cellFrame];
// Move the origin of the smaller-size url text so it aligns with the title text.
urlTextRect.origin.y += titleTextHeight - urlTextHeight;
// Draw the columns.
[siteIcon setFlipped:YES];
[siteIcon drawInRect:imageRect fromRect:NSMakeRect(0, 0, kIconSize.width, kIconSize.height) operation:NSCompositeSourceOver fraction:1.0];
[siteIcon setFlipped:NO];
[title drawInRect:titleTextRect withAttributes:titleAttributes];
[url drawInRect:urlTextRect withAttributes:urlAttributes];
// Draw the columns.
[siteIcon setFlipped:YES];
[siteIcon drawInRect:imageRect fromRect:NSMakeRect(0, 0, kIconSize.width, kIconSize.height) operation:NSCompositeSourceOver fraction:1.0];
// The icon needs to be flipped back so it displays properly in other places where it's used.
[siteIcon setFlipped:NO];
[title drawInRect:titleTextRect withAttributes:titleAttributes];
[url drawInRect:urlTextRect withAttributes:urlAttributes];
}
[controlView unlockFocus];
}

View File

@ -47,6 +47,7 @@
NSImage* mSiteIcon;
NSMutableString* mSiteURL;
NSMutableString* mSiteTitle;
BOOL mIsHeader;
}
- (void)setIcon:(NSImage *)anImage;
@ -55,5 +56,7 @@
- (NSString *)url;
- (void)setTitle:(NSString *)aString;
- (NSString *)title;
- (void)setIsHeader:(BOOL)aBOOL;
- (BOOL)isHeader;
@end

View File

@ -63,9 +63,15 @@
[copy setIcon:mSiteIcon];
[copy setTitle:mSiteTitle];
[copy setUrl:mSiteURL];
[copy setIsHeader:mIsHeader];
return copy;
}
- (BOOL)isEqual:(id)anObject
{
return [[anObject url] isEqualToString:mSiteURL];
}
- (void)setIcon:(NSImage *)anImage
{
[mSiteIcon autorelease];
@ -99,4 +105,14 @@
return mSiteTitle;
}
- (void)setIsHeader:(BOOL)aBOOL
{
mIsHeader = aBOOL;
}
- (BOOL)isHeader
{
return mIsHeader;
}
@end

View File

@ -960,6 +960,9 @@ NS_IMPL_ISUPPORTS1(nsHistoryObserver, nsIHistoryObserver);
if ([mSortColumn isEqualToString:@"last_visit"])
return @selector(compareLastVisitDate:sortDescending:);
if ([mSortColumn isEqualToString:@"visit_count"])
return @selector(compareVisitCount:sortDescending:);
if ([mSortColumn isEqualToString:@"hostname"])
return @selector(compareHostname:sortDescending:);

View File

@ -60,6 +60,7 @@ class nsIHistoryItem;
- (NSString*)url;
- (NSDate*)firstVisit;
- (NSDate*)lastVisit;
- (NSNumber*)visitCount;
- (NSString*)hostname;
- (NSString*)identifier;
@ -78,6 +79,7 @@ class nsIHistoryItem;
- (NSComparisonResult)compareTitle:(HistoryItem *)aItem sortDescending:(NSNumber*)inDescending;
- (NSComparisonResult)compareFirstVisitDate:(HistoryItem *)aItem sortDescending:(NSNumber*)inDescending;
- (NSComparisonResult)compareLastVisitDate:(HistoryItem *)aItem sortDescending:(NSNumber*)inDescending;
- (NSComparisonResult)compareVisitCount:(HistoryItem *)aItem sortDescending:(NSNumber*)inDescending;
- (NSComparisonResult)compareHostname:(HistoryItem *)aItem sortDescending:(NSNumber*)inDescending;
@end
@ -134,6 +136,7 @@ class nsIHistoryItem;
NSString* mHostname;
NSDate* mFirstVisitDate;
NSDate* mLastVisitDate;
NSNumber* mVisitCount;
NSImage* mSiteIcon;
BOOL mAttemptedIconLoad;

View File

@ -114,6 +114,11 @@ enum
return nil;
}
- (NSNumber*)visitCount
{
return nil;
}
- (NSString*)hostname
{
return @"";
@ -177,6 +182,11 @@ enum
return NSOrderedSame;
}
- (NSComparisonResult)compareVisitCount:(HistoryItem *)aItem sortDescending:(NSNumber*)inDescending
{
return NSOrderedSame;
}
- (NSComparisonResult)compareHostname:(HistoryItem *)aItem sortDescending:(NSNumber*)inDescending
{
return NSOrderedSame;
@ -238,6 +248,11 @@ enum
return nil;
}
- (NSNumber*)visitCount
{
return nil;
}
- (NSString*)hostname
{
return @"";
@ -482,7 +497,7 @@ enum
nsCString url;
if (NS_SUCCEEDED(inItem->GetURL(url)))
mURL = [[NSString alloc] initWith_nsACString:url];
nsString title;
if (NS_SUCCEEDED(inItem->GetTitle(title)))
mTitle = [[NSString alloc] initWith_nsAString:title];
@ -493,7 +508,7 @@ enum
if ([mHostname length] == 0 && [mURL hasPrefix:@"file://"])
mHostname = [[NSString alloc] initWithString:@"local_file"];
PRTime firstVisit;
if (NS_SUCCEEDED(inItem->GetFirstVisitDate(&firstVisit)))
mFirstVisitDate = [[NSDate dateWithPRTime:firstVisit] retain];
@ -501,6 +516,10 @@ enum
PRTime lastVisit;
if (NS_SUCCEEDED(inItem->GetLastVisitDate(&lastVisit)))
mLastVisitDate = [[NSDate dateWithPRTime:lastVisit] retain];
PRInt32 visitCount;
if (NS_SUCCEEDED(inItem->GetVisitCount(&visitCount)))
mVisitCount = [[NSNumber numberWithInt:visitCount] retain];
}
return self;
}
@ -545,6 +564,7 @@ enum
[mHostname release];
[mFirstVisitDate release];
[mLastVisitDate release];
[mVisitCount release];
[mSiteIcon release];
[super dealloc];
@ -570,6 +590,11 @@ enum
return mLastVisitDate;
}
- (NSNumber*)visitCount
{
return mVisitCount;
}
- (NSString*)hostname
{
return mHostname;
@ -676,6 +701,18 @@ enum
return [inDescending boolValue] ? (NSComparisonResult)(-1 * (int)result) : result;
}
- (NSComparisonResult)compareVisitCount:(HistoryItem *)aItem sortDescending:(NSNumber*)inDescending
{
NSComparisonResult result;
// sort categories before sites
if ([aItem isKindOfClass:[HistoryCategoryItem class]])
result = NSOrderedDescending;
else
result = [mVisitCount compare:[aItem visitCount]];
return [inDescending boolValue] ? (NSComparisonResult)(-1 * (int)result) : result;
}
- (NSComparisonResult)compareHostname:(HistoryItem *)aItem sortDescending:(NSNumber*)inDescending
{
NSComparisonResult result;