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:
parent
c9c1ad08b1
commit
5ba317e9b4
@ -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
|
||||
|
||||
@ -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]];
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user