10bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch/*
20bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
30bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch *
40bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * Redistribution and use in source and binary forms, with or without
50bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * modification, are permitted provided that the following conditions
60bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * are met:
70bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch *
80bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * 1.  Redistributions of source code must retain the above copyright
90bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch *     notice, this list of conditions and the following disclaimer.
100bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * 2.  Redistributions in binary form must reproduce the above copyright
110bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch *     notice, this list of conditions and the following disclaimer in the
120bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch *     documentation and/or other materials provided with the distribution.
130bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
140bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch *     its contributors may be used to endorse or promote products derived
150bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch *     from this software without specific prior written permission.
160bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch *
170bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
180bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
190bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
200bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
210bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
220bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
230bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
240bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
250bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
260bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
270bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch */
280bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
290bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch#import "WebTextCompletionController.h"
300bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
3181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch#import "DOMNodeInternal.h"
320bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch#import "DOMRangeInternal.h"
330bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch#import "WebFrameInternal.h"
340bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch#import "WebHTMLViewInternal.h"
350bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch#import "WebTypesInternal.h"
3681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch#import "WebView.h"
370bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch#import <WebCore/Frame.h>
380bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
390bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch@interface NSWindow (WebNSWindowDetails)
400bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (void)_setForceActiveControls:(BOOL)flag;
410bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch@end
420bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
430bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdochusing namespace WebCore;
44231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Blockusing namespace std;
450bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
460bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch// This class handles the complete: operation.
470bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch// It counts on its host view to call endRevertingChange: whenever the current completion needs to be aborted.
480bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
490bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch// The class is in one of two modes: Popup window showing, or not.
500bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch// It is shown when a completion yields more than one match.
510bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch// If a completion yields one or zero matches, it is not shown, and there is no state carried across to the next completion.
520bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
530bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch@implementation WebTextCompletionController
540bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
550bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (id)initWithWebView:(WebView *)view HTMLView:(WebHTMLView *)htmlView
560bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
570bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    self = [super init];
580bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    if (!self)
590bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        return nil;
600bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    _view = view;
610bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    _htmlView = htmlView;
620bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    return self;
630bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
640bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
650bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (void)dealloc
660bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
670bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_popupWindow release];
680bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_completions release];
690bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_originalString release];
700bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
710bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [super dealloc];
720bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
730bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
740bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (void)_insertMatch:(NSString *)match
750bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
760bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    // FIXME: 3769654 - We should preserve case of string being inserted, even in prefix (but then also be
770bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    // able to revert that).  Mimic NSText.
780bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    WebFrame *frame = [_htmlView _frame];
790bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    NSString *newText = [match substringFromIndex:prefixLength];
800bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [frame _replaceSelectionWithText:newText selectReplacement:YES smartReplace:NO];
810bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
820bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
830bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch// mostly lifted from NSTextView_KeyBinding.m
840bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (void)_buildUI
850bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
860bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    NSRect scrollFrame = NSMakeRect(0, 0, 100, 100);
870bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    NSRect tableFrame = NSZeroRect;
880bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    tableFrame.size = [NSScrollView contentSizeForFrameSize:scrollFrame.size hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSNoBorder];
89dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    NSTableColumn *column = [[NSTableColumn alloc] init];
900bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [column setWidth:tableFrame.size.width];
910bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [column setEditable:NO];
920bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
930bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    _tableView = [[NSTableView alloc] initWithFrame:tableFrame];
940bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView setAutoresizingMask:NSViewWidthSizable];
950bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView addTableColumn:column];
960bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [column release];
970bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView setGridStyleMask:NSTableViewGridNone];
980bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView setCornerView:nil];
990bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView setHeaderView:nil];
1000bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView setColumnAutoresizingStyle:NSTableViewUniformColumnAutoresizingStyle];
1010bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView setDelegate:self];
1020bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView setDataSource:self];
1030bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView setTarget:self];
1040bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView setDoubleAction:@selector(tableAction:)];
1050bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
1060bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame:scrollFrame];
1070bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [scrollView setBorderType:NSNoBorder];
1080bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [scrollView setHasVerticalScroller:YES];
1090bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
1100bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [scrollView setDocumentView:_tableView];
1110bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView release];
1120bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
1130bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    _popupWindow = [[NSWindow alloc] initWithContentRect:scrollFrame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
1140bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_popupWindow setAlphaValue:0.88f];
1150bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_popupWindow setContentView:scrollView];
1160bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [scrollView release];
1170bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_popupWindow setHasShadow:YES];
1180bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_popupWindow setOneShot:YES];
1190bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_popupWindow _setForceActiveControls:YES];
1200bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_popupWindow setReleasedWhenClosed:NO];
1210bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
1220bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
1230bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch// mostly lifted from NSTextView_KeyBinding.m
1240bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (void)_placePopupWindow:(NSPoint)topLeft
1250bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
1260bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    int numberToShow = [_completions count];
1270bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    if (numberToShow > 20)
1280bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        numberToShow = 20;
1290bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
1300bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    NSRect windowFrame;
1310bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    NSPoint wordStart = topLeft;
1320bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    windowFrame.origin = [[_view window] convertBaseToScreen:[_htmlView convertPoint:wordStart toView:nil]];
1330bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    windowFrame.size.height = numberToShow * [_tableView rowHeight] + (numberToShow + 1) * [_tableView intercellSpacing].height;
1340bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    windowFrame.origin.y -= windowFrame.size.height;
1350bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont systemFontOfSize:12.0f], NSFontAttributeName, nil];
136231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block    CGFloat maxWidth = 0;
1370bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    int maxIndex = -1;
1380bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    int i;
1390bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    for (i = 0; i < numberToShow; i++) {
1400bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        float width = ceilf([[_completions objectAtIndex:i] sizeWithAttributes:attributes].width);
1410bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        if (width > maxWidth) {
1420bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            maxWidth = width;
1430bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            maxIndex = i;
1440bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        }
1450bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    }
1460bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    windowFrame.size.width = 100;
1470bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    if (maxIndex >= 0) {
1480bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        maxWidth = ceilf([NSScrollView frameSizeForContentSize:NSMakeSize(maxWidth, 100.0f) hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSNoBorder].width);
1490bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        maxWidth = ceilf([NSWindow frameRectForContentRect:NSMakeRect(0.0f, 0.0f, maxWidth, 100.0f) styleMask:NSBorderlessWindowMask].size.width);
1500bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        maxWidth += 5.0f;
151231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block        windowFrame.size.width = max(maxWidth, windowFrame.size.width);
152231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block        maxWidth = min<CGFloat>(400, windowFrame.size.width);
1530bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    }
1540bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_popupWindow setFrame:windowFrame display:NO];
1550bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
1560bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView reloadData];
1570bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
1580bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_tableView scrollRowToVisible:0];
1590bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [self _reflectSelection];
1600bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_popupWindow setLevel:NSPopUpMenuWindowLevel];
1610bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [_popupWindow orderFront:nil];
1620bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [[_view window] addChildWindow:_popupWindow ordered:NSWindowAbove];
1630bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
1640bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
1650bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (void)doCompletion
1660bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
1670bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    if (!_popupWindow) {
1680bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
1690bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        if (!checker) {
1700bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            LOG_ERROR("No NSSpellChecker");
1710bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            return;
1720bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        }
1730bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
1740bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        // Get preceeding word stem
1750bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        WebFrame *frame = [_htmlView _frame];
1760bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        DOMRange *selection = kit(core(frame)->selection()->toNormalizedRange().get());
1775af96e2c7b73ebc627c6894727826a7576d31758Leon Clarke        DOMRange *wholeWord = [frame _rangeByAlteringCurrentSelection:SelectionController::AlterationExtend
178cad810f21b803229eb11403f9209855525a25d57Steve Block            direction:DirectionBackward granularity:WordGranularity];
1790bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        DOMRange *prefix = [wholeWord cloneRange];
1800bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        [prefix setEnd:[selection startContainer] offset:[selection startOffset]];
1810bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
1820bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        // Reject some NOP cases
1830bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        if ([prefix collapsed]) {
1840bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            NSBeep();
1850bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            return;
1860bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        }
1870bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        NSString *prefixStr = [frame _stringForRange:prefix];
1880bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        NSString *trimmedPrefix = [prefixStr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
1890bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        if ([trimmedPrefix length] == 0) {
1900bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            NSBeep();
1910bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            return;
1920bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        }
1930bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        prefixLength = [prefixStr length];
1940bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
1950bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        // Lookup matches
1960bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        [_completions release];
1970bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        _completions = [checker completionsForPartialWordRange:NSMakeRange(0, [prefixStr length]) inString:prefixStr language:nil inSpellDocumentWithTag:[_view spellCheckerDocumentTag]];
1980bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        [_completions retain];
1990bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
2000bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        if (!_completions || [_completions count] == 0) {
2010bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            NSBeep();
2020bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        } else if ([_completions count] == 1) {
2030bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            [self _insertMatch:[_completions objectAtIndex:0]];
2040bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        } else {
2050bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            ASSERT(!_originalString);       // this should only be set IFF we have a popup window
2060bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            _originalString = [[frame _stringForRange:selection] retain];
2070bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            [self _buildUI];
20881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            NSRect wordRect = [frame _caretRectAtPosition:Position(core([wholeWord startContainer]), [wholeWord startOffset], Position::PositionIsOffsetInAnchor) affinity:NSSelectionAffinityDownstream];
2090bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            // +1 to be under the word, not the caret
2100bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            // FIXME - 3769652 - Wrong positioning for right to left languages.  We should line up the upper
2110bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            // right corner with the caret instead of upper left, and the +1 would be a -1.
2120bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            NSPoint wordLowerLeft = { NSMinX(wordRect)+1, NSMaxY(wordRect) };
2130bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            [self _placePopupWindow:wordLowerLeft];
2140bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        }
2150bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    } else {
2160bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        [self endRevertingChange:YES moveLeft:NO];
2170bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    }
2180bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
2190bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
2200bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (void)endRevertingChange:(BOOL)revertChange moveLeft:(BOOL)goLeft
2210bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
2220bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    if (_popupWindow) {
2230bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        // tear down UI
2240bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        [[_view window] removeChildWindow:_popupWindow];
2250bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        [_popupWindow orderOut:self];
2260bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        // Must autorelease because event tracking code may be on the stack touching UI
2270bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        [_popupWindow autorelease];
2280bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        _popupWindow = nil;
2290bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
2300bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        if (revertChange) {
2310bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            WebFrame *frame = [_htmlView _frame];
2320bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            [frame _replaceSelectionWithText:_originalString selectReplacement:YES smartReplace:NO];
2330bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        } else if ([_htmlView _hasSelection]) {
2340bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            if (goLeft)
2350bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch                [_htmlView moveBackward:nil];
2360bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            else
2370bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch                [_htmlView moveForward:nil];
2380bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        }
2390bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        [_originalString release];
2400bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        _originalString = nil;
2410bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    }
2420bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    // else there is no state to abort if the window was not up
2430bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
2440bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
2450bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (BOOL)popupWindowIsOpen
2460bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
2470bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    return _popupWindow != nil;
2480bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
2490bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
2500bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch// WebHTMLView gives us a crack at key events it sees. Return whether we consumed the event.
2510bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch// The features for the various keys mimic NSTextView.
2520bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (BOOL)filterKeyDown:(NSEvent *)event
2530bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
2540bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    if (!_popupWindow)
2550bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        return NO;
2560bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    NSString *string = [event charactersIgnoringModifiers];
2570bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    if (![string length])
2580bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        return NO;
2590bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    unichar c = [string characterAtIndex:0];
2600bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    if (c == NSUpArrowFunctionKey) {
2610bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        int selectedRow = [_tableView selectedRow];
2620bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        if (0 < selectedRow) {
2630bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            [_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:selectedRow - 1] byExtendingSelection:NO];
2640bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            [_tableView scrollRowToVisible:selectedRow - 1];
2650bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        }
2660bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        return YES;
2670bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    }
2680bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    if (c == NSDownArrowFunctionKey) {
2690bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        int selectedRow = [_tableView selectedRow];
2700bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        if (selectedRow < (int)[_completions count] - 1) {
2710bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            [_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:selectedRow + 1] byExtendingSelection:NO];
2720bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch            [_tableView scrollRowToVisible:selectedRow + 1];
2730bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        }
2740bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        return YES;
2750bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    }
2760bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    if (c == NSRightArrowFunctionKey || c == '\n' || c == '\r' || c == '\t') {
2770bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        // FIXME: What about backtab?
2780bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        [self endRevertingChange:NO moveLeft:NO];
2790bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        return YES;
2800bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    }
2810bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    if (c == NSLeftArrowFunctionKey) {
2820bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        [self endRevertingChange:NO moveLeft:YES];
2830bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        return YES;
2840bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    }
2850bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    if (c == 0x1B || c == NSF5FunctionKey) {
2860bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        // FIXME: F5?
2870bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        [self endRevertingChange:YES moveLeft:NO];
2880bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        return YES;
2890bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    }
290f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch    if (c == ' ' || (c >= 0x21 && c <= 0x2F) || (c >= 0x3A && c <= 0x40) || (c >= 0x5B && c <= 0x60) || (c >= 0x7B && c <= 0x7D)) {
2910bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        // FIXME: Is the above list of keys really definitive?
2920bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        // Originally this code called ispunct; aren't there other punctuation keys on international keyboards?
2930bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        [self endRevertingChange:NO moveLeft:NO];
2940bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch        return NO; // let the char get inserted
2950bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    }
2960bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    return NO;
2970bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
2980bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
2990bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (void)_reflectSelection
3000bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
3010bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    int selectedRow = [_tableView selectedRow];
302cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block    ASSERT(selectedRow >= 0);
303cac0f67c402d107cdb10971b95719e2ff9c7c76bSteve Block    ASSERT(selectedRow < (int)[_completions count]);
3040bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [self _insertMatch:[_completions objectAtIndex:selectedRow]];
3050bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
3060bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
3070bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (void)tableAction:(id)sender
3080bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
3090bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [self _reflectSelection];
3100bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [self endRevertingChange:NO moveLeft:NO];
3110bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
3120bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
3130bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
3140bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
3150bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    return [_completions count];
3160bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
3170bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
3180bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
3190bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
3200bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    return [_completions objectAtIndex:row];
3210bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
3220bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
3230bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch- (void)tableViewSelectionDidChange:(NSNotification *)notification
3240bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch{
3250bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch    [self _reflectSelection];
3260bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch}
3270bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch
3280bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5Ben Murdoch@end
329