summaryrefslogtreecommitdiffstats
path: root/WebKit/mac/WebView/WebTextCompletionController.mm
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2009-08-11 17:01:47 +0100
committerBen Murdoch <benm@google.com>2009-08-11 18:21:02 +0100
commit0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5 (patch)
tree2943df35f62d885c89d01063cc528dd73b480fea /WebKit/mac/WebView/WebTextCompletionController.mm
parent7e7a70bfa49a1122b2597a1e6367d89eb4035eca (diff)
downloadexternal_webkit-0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5.zip
external_webkit-0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5.tar.gz
external_webkit-0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5.tar.bz2
Merge in WebKit r47029.
Diffstat (limited to 'WebKit/mac/WebView/WebTextCompletionController.mm')
-rw-r--r--WebKit/mac/WebView/WebTextCompletionController.mm325
1 files changed, 325 insertions, 0 deletions
diff --git a/WebKit/mac/WebView/WebTextCompletionController.mm b/WebKit/mac/WebView/WebTextCompletionController.mm
new file mode 100644
index 0000000..f7c172f
--- /dev/null
+++ b/WebKit/mac/WebView/WebTextCompletionController.mm
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "WebTextCompletionController.h"
+
+#import "DOMRangeInternal.h"
+#import "WebFrameInternal.h"
+#import "WebHTMLViewInternal.h"
+#import "WebTypesInternal.h"
+#import <WebCore/Frame.h>
+
+@interface NSWindow (WebNSWindowDetails)
+- (void)_setForceActiveControls:(BOOL)flag;
+@end
+
+using namespace WebCore;
+
+// This class handles the complete: operation.
+// It counts on its host view to call endRevertingChange: whenever the current completion needs to be aborted.
+
+// The class is in one of two modes: Popup window showing, or not.
+// It is shown when a completion yields more than one match.
+// If a completion yields one or zero matches, it is not shown, and there is no state carried across to the next completion.
+
+@implementation WebTextCompletionController
+
+- (id)initWithWebView:(WebView *)view HTMLView:(WebHTMLView *)htmlView
+{
+ self = [super init];
+ if (!self)
+ return nil;
+ _view = view;
+ _htmlView = htmlView;
+ return self;
+}
+
+- (void)dealloc
+{
+ [_popupWindow release];
+ [_completions release];
+ [_originalString release];
+
+ [super dealloc];
+}
+
+- (void)_insertMatch:(NSString *)match
+{
+ // FIXME: 3769654 - We should preserve case of string being inserted, even in prefix (but then also be
+ // able to revert that). Mimic NSText.
+ WebFrame *frame = [_htmlView _frame];
+ NSString *newText = [match substringFromIndex:prefixLength];
+ [frame _replaceSelectionWithText:newText selectReplacement:YES smartReplace:NO];
+}
+
+// mostly lifted from NSTextView_KeyBinding.m
+- (void)_buildUI
+{
+ NSRect scrollFrame = NSMakeRect(0, 0, 100, 100);
+ NSRect tableFrame = NSZeroRect;
+ tableFrame.size = [NSScrollView contentSizeForFrameSize:scrollFrame.size hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSNoBorder];
+ // Added cast to work around problem with multiple Foundation initWithIdentifier: methods with different parameter types.
+ NSTableColumn *column = [(NSTableColumn *)[NSTableColumn alloc] initWithIdentifier:[NSNumber numberWithInt:0]];
+ [column setWidth:tableFrame.size.width];
+ [column setEditable:NO];
+
+ _tableView = [[NSTableView alloc] initWithFrame:tableFrame];
+ [_tableView setAutoresizingMask:NSViewWidthSizable];
+ [_tableView addTableColumn:column];
+ [column release];
+ [_tableView setGridStyleMask:NSTableViewGridNone];
+ [_tableView setCornerView:nil];
+ [_tableView setHeaderView:nil];
+ [_tableView setColumnAutoresizingStyle:NSTableViewUniformColumnAutoresizingStyle];
+ [_tableView setDelegate:self];
+ [_tableView setDataSource:self];
+ [_tableView setTarget:self];
+ [_tableView setDoubleAction:@selector(tableAction:)];
+
+ NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame:scrollFrame];
+ [scrollView setBorderType:NSNoBorder];
+ [scrollView setHasVerticalScroller:YES];
+ [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ [scrollView setDocumentView:_tableView];
+ [_tableView release];
+
+ _popupWindow = [[NSWindow alloc] initWithContentRect:scrollFrame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
+ [_popupWindow setAlphaValue:0.88f];
+ [_popupWindow setContentView:scrollView];
+ [scrollView release];
+ [_popupWindow setHasShadow:YES];
+ [_popupWindow setOneShot:YES];
+ [_popupWindow _setForceActiveControls:YES];
+ [_popupWindow setReleasedWhenClosed:NO];
+}
+
+// mostly lifted from NSTextView_KeyBinding.m
+- (void)_placePopupWindow:(NSPoint)topLeft
+{
+ int numberToShow = [_completions count];
+ if (numberToShow > 20)
+ numberToShow = 20;
+
+ NSRect windowFrame;
+ NSPoint wordStart = topLeft;
+ windowFrame.origin = [[_view window] convertBaseToScreen:[_htmlView convertPoint:wordStart toView:nil]];
+ windowFrame.size.height = numberToShow * [_tableView rowHeight] + (numberToShow + 1) * [_tableView intercellSpacing].height;
+ windowFrame.origin.y -= windowFrame.size.height;
+ NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont systemFontOfSize:12.0f], NSFontAttributeName, nil];
+ float maxWidth = 0.0f;
+ int maxIndex = -1;
+ int i;
+ for (i = 0; i < numberToShow; i++) {
+ float width = ceilf([[_completions objectAtIndex:i] sizeWithAttributes:attributes].width);
+ if (width > maxWidth) {
+ maxWidth = width;
+ maxIndex = i;
+ }
+ }
+ windowFrame.size.width = 100;
+ if (maxIndex >= 0) {
+ maxWidth = ceilf([NSScrollView frameSizeForContentSize:NSMakeSize(maxWidth, 100.0f) hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSNoBorder].width);
+ maxWidth = ceilf([NSWindow frameRectForContentRect:NSMakeRect(0.0f, 0.0f, maxWidth, 100.0f) styleMask:NSBorderlessWindowMask].size.width);
+ maxWidth += 5.0f;
+ windowFrame.size.width = MAX(maxWidth, windowFrame.size.width);
+ maxWidth = MIN(400.0f, windowFrame.size.width);
+ }
+ [_popupWindow setFrame:windowFrame display:NO];
+
+ [_tableView reloadData];
+ [_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
+ [_tableView scrollRowToVisible:0];
+ [self _reflectSelection];
+ [_popupWindow setLevel:NSPopUpMenuWindowLevel];
+ [_popupWindow orderFront:nil];
+ [[_view window] addChildWindow:_popupWindow ordered:NSWindowAbove];
+}
+
+- (void)doCompletion
+{
+ if (!_popupWindow) {
+ NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
+ if (!checker) {
+ LOG_ERROR("No NSSpellChecker");
+ return;
+ }
+
+ // Get preceeding word stem
+ WebFrame *frame = [_htmlView _frame];
+ DOMRange *selection = kit(core(frame)->selection()->toNormalizedRange().get());
+ DOMRange *wholeWord = [frame _rangeByAlteringCurrentSelection:SelectionController::EXTEND
+ direction:SelectionController::BACKWARD granularity:WordGranularity];
+ DOMRange *prefix = [wholeWord cloneRange];
+ [prefix setEnd:[selection startContainer] offset:[selection startOffset]];
+
+ // Reject some NOP cases
+ if ([prefix collapsed]) {
+ NSBeep();
+ return;
+ }
+ NSString *prefixStr = [frame _stringForRange:prefix];
+ NSString *trimmedPrefix = [prefixStr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ if ([trimmedPrefix length] == 0) {
+ NSBeep();
+ return;
+ }
+ prefixLength = [prefixStr length];
+
+ // Lookup matches
+ [_completions release];
+ _completions = [checker completionsForPartialWordRange:NSMakeRange(0, [prefixStr length]) inString:prefixStr language:nil inSpellDocumentWithTag:[_view spellCheckerDocumentTag]];
+ [_completions retain];
+
+ if (!_completions || [_completions count] == 0) {
+ NSBeep();
+ } else if ([_completions count] == 1) {
+ [self _insertMatch:[_completions objectAtIndex:0]];
+ } else {
+ ASSERT(!_originalString); // this should only be set IFF we have a popup window
+ _originalString = [[frame _stringForRange:selection] retain];
+ [self _buildUI];
+ NSRect wordRect = [frame _caretRectAtNode:[wholeWord startContainer] offset:[wholeWord startOffset] affinity:NSSelectionAffinityDownstream];
+ // +1 to be under the word, not the caret
+ // FIXME - 3769652 - Wrong positioning for right to left languages. We should line up the upper
+ // right corner with the caret instead of upper left, and the +1 would be a -1.
+ NSPoint wordLowerLeft = { NSMinX(wordRect)+1, NSMaxY(wordRect) };
+ [self _placePopupWindow:wordLowerLeft];
+ }
+ } else {
+ [self endRevertingChange:YES moveLeft:NO];
+ }
+}
+
+- (void)endRevertingChange:(BOOL)revertChange moveLeft:(BOOL)goLeft
+{
+ if (_popupWindow) {
+ // tear down UI
+ [[_view window] removeChildWindow:_popupWindow];
+ [_popupWindow orderOut:self];
+ // Must autorelease because event tracking code may be on the stack touching UI
+ [_popupWindow autorelease];
+ _popupWindow = nil;
+
+ if (revertChange) {
+ WebFrame *frame = [_htmlView _frame];
+ [frame _replaceSelectionWithText:_originalString selectReplacement:YES smartReplace:NO];
+ } else if ([_htmlView _hasSelection]) {
+ if (goLeft)
+ [_htmlView moveBackward:nil];
+ else
+ [_htmlView moveForward:nil];
+ }
+ [_originalString release];
+ _originalString = nil;
+ }
+ // else there is no state to abort if the window was not up
+}
+
+- (BOOL)popupWindowIsOpen
+{
+ return _popupWindow != nil;
+}
+
+// WebHTMLView gives us a crack at key events it sees. Return whether we consumed the event.
+// The features for the various keys mimic NSTextView.
+- (BOOL)filterKeyDown:(NSEvent *)event
+{
+ if (!_popupWindow)
+ return NO;
+ NSString *string = [event charactersIgnoringModifiers];
+ if (![string length])
+ return NO;
+ unichar c = [string characterAtIndex:0];
+ if (c == NSUpArrowFunctionKey) {
+ int selectedRow = [_tableView selectedRow];
+ if (0 < selectedRow) {
+ [_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:selectedRow - 1] byExtendingSelection:NO];
+ [_tableView scrollRowToVisible:selectedRow - 1];
+ }
+ return YES;
+ }
+ if (c == NSDownArrowFunctionKey) {
+ int selectedRow = [_tableView selectedRow];
+ if (selectedRow < (int)[_completions count] - 1) {
+ [_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:selectedRow + 1] byExtendingSelection:NO];
+ [_tableView scrollRowToVisible:selectedRow + 1];
+ }
+ return YES;
+ }
+ if (c == NSRightArrowFunctionKey || c == '\n' || c == '\r' || c == '\t') {
+ // FIXME: What about backtab?
+ [self endRevertingChange:NO moveLeft:NO];
+ return YES;
+ }
+ if (c == NSLeftArrowFunctionKey) {
+ [self endRevertingChange:NO moveLeft:YES];
+ return YES;
+ }
+ if (c == 0x1B || c == NSF5FunctionKey) {
+ // FIXME: F5?
+ [self endRevertingChange:YES moveLeft:NO];
+ return YES;
+ }
+ if (c == ' ' || c >= 0x21 && c <= 0x2F || c >= 0x3A && c <= 0x40 || c >= 0x5B && c <= 0x60 || c >= 0x7B && c <= 0x7D) {
+ // FIXME: Is the above list of keys really definitive?
+ // Originally this code called ispunct; aren't there other punctuation keys on international keyboards?
+ [self endRevertingChange:NO moveLeft:NO];
+ return NO; // let the char get inserted
+ }
+ return NO;
+}
+
+- (void)_reflectSelection
+{
+ int selectedRow = [_tableView selectedRow];
+ ASSERT(selectedRow >= 0 && selectedRow < (int)[_completions count]);
+ [self _insertMatch:[_completions objectAtIndex:selectedRow]];
+}
+
+- (void)tableAction:(id)sender
+{
+ [self _reflectSelection];
+ [self endRevertingChange:NO moveLeft:NO];
+}
+
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
+{
+ return [_completions count];
+}
+
+- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
+{
+ return [_completions objectAtIndex:row];
+}
+
+- (void)tableViewSelectionDidChange:(NSNotification *)notification
+{
+ [self _reflectSelection];
+}
+
+@end