/* * Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved. * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com) * * 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 "WebHTMLView.h" #import "DOMNodeInternal.h" #import "DOMRangeInternal.h" #import "WebArchive.h" #import "WebArchiver.h" #import "WebBaseNetscapePluginViewInternal.h" #import "WebClipView.h" #import "WebDOMOperationsPrivate.h" #import "WebDataSourceInternal.h" #import "WebDefaultUIDelegate.h" #import "WebDocumentInternal.h" #import "WebEditingDelegate.h" #import "WebElementDictionary.h" #import "WebFrameBridge.h" #import "WebFrameInternal.h" #import "WebFramePrivate.h" #import "WebFrameViewInternal.h" #import "WebHTMLRepresentationPrivate.h" #import "WebHTMLViewInternal.h" #import "WebKitLogging.h" #import "WebKitNSStringExtras.h" #import "WebKitPluginContainerView.h" #import "WebKitVersionChecks.h" #import "WebLocalizableStrings.h" #import "WebNSAttributedStringExtras.h" #import "WebNSEventExtras.h" #import "WebNSFileManagerExtras.h" #import "WebNSImageExtras.h" #import "WebNSObjectExtras.h" #import "WebNSPasteboardExtras.h" #import "WebNSPrintOperationExtras.h" #import "WebNSURLExtras.h" #import "WebNSViewExtras.h" #import "WebNetscapePluginEmbeddedView.h" #import "WebPluginController.h" #import "WebPreferences.h" #import "WebPreferencesPrivate.h" #import "WebResourcePrivate.h" #import "WebStringTruncator.h" #import "WebUIDelegatePrivate.h" #import "WebViewInternal.h" #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import using namespace WebCore; using namespace HTMLNames; extern "C" { // Need to declare these attribute names because AppKit exports them but does not make them available in API or SPI headers. extern NSString *NSMarkedClauseSegmentAttributeName; extern NSString *NSTextInputReplacementRangeAttributeName; // Kill ring calls. Would be better to use NSKillRing.h, but that's not available as API or SPI. void _NSInitializeKillRing(void); void _NSAppendToKillRing(NSString *); void _NSPrependToKillRing(NSString *); NSString *_NSYankFromKillRing(void); NSString *_NSYankPreviousFromKillRing(void); void _NSNewKillRingSequence(void); void _NSSetKillRingToYankedState(void); void _NSResetKillRingOperationFlag(void); } @interface NSView (AppKitSecretsIKnowAbout) - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView; - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect; - (NSRect)_dirtyRect; - (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants; - (void)_propagateDirtyRectsToOpaqueAncestors; - (void)_windowChangedKeyState; @end @interface NSApplication (AppKitSecretsIKnowAbout) - (void)speakString:(NSString *)string; @end @interface NSWindow (AppKitSecretsIKnowAbout) - (id)_newFirstResponderAfterResigning; - (void)_setForceActiveControls:(BOOL)flag; @end @interface NSAttributedString (AppKitSecretsIKnowAbout) - (id)_initWithDOMRange:(DOMRange *)domRange; - (DOMDocumentFragment *)_documentFromRange:(NSRange)range document:(DOMDocument *)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources; @end @interface NSSpellChecker (CurrentlyPrivateForTextView) - (void)learnWord:(NSString *)word; @end // By imaging to a width a little wider than the available pixels, // thin pages will be scaled down a little, matching the way they // print in IE and Camino. This lets them use fewer sheets than they // would otherwise, which is presumably why other browsers do this. // Wide pages will be scaled down more than this. #define PrintingMinimumShrinkFactor 1.25f // This number determines how small we are willing to reduce the page content // in order to accommodate the widest line. If the page would have to be // reduced smaller to make the widest line fit, we just clip instead (this // behavior matches MacIE and Mozilla, at least) #define PrintingMaximumShrinkFactor 2.0f // This number determines how short the last printed page of a multi-page print session // can be before we try to shrink the scale in order to reduce the number of pages, and // thus eliminate the orphan. #define LastPrintedPageOrphanRatio 0.1f // This number determines the amount the scale factor is adjusted to try to eliminate orphans. // It has no direct mathematical relationship to LastPrintedPageOrphanRatio, due to variable // numbers of pages, logic to avoid breaking elements, and CSS-supplied hard page breaks. #define PrintingOrphanShrinkAdjustment 1.1f #define AUTOSCROLL_INTERVAL 0.1f #define DRAG_LABEL_BORDER_X 4.0f //Keep border_y in synch with DragController::LinkDragBorderInset #define DRAG_LABEL_BORDER_Y 2.0f #define DRAG_LABEL_RADIUS 5.0f #define DRAG_LABEL_BORDER_Y_OFFSET 2.0f #define MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP 120.0f #define MAX_DRAG_LABEL_WIDTH 320.0f #define DRAG_LINK_LABEL_FONT_SIZE 11.0f #define DRAG_LINK_URL_FONT_SIZE 10.0f // Any non-zero value will do, but using something recognizable might help us debug some day. #define TRACKING_RECT_TAG 0xBADFACE // FIXME: This constant is copied from AppKit's _NXSmartPaste constant. #define WebSmartPastePboardType @"NeXT smart paste pasteboard type" #define STANDARD_WEIGHT 5 #define MIN_BOLD_WEIGHT 9 #define STANDARD_BOLD_WEIGHT 10 // Fake URL scheme. #define WebDataProtocolScheme @"webkit-fake-url" // References to WebCoreScrollView as a subview of a WebHTMLView may be present // in some NIB files, so NSUnarchiver must be still able to look up this now-unused class. @interface WebCoreScrollView : NSScrollView @end @implementation WebCoreScrollView @end // if YES, do the standard NSView hit test (which can't give the right result when HTML overlaps a view) static BOOL forceNSViewHitTest; // if YES, do the "top WebHTMLView" hit test (which we'd like to do all the time but can't because of Java requirements [see bug 4349721]) static BOOL forceWebHTMLViewHitTest; static WebHTMLView *lastHitView; // We need this to be able to safely reference the CachedImage for the promised drag data static CachedResourceClient* promisedDataClient() { static CachedResourceClient* staticCachedResourceClient = new CachedResourceClient; return staticCachedResourceClient; } @interface WebHTMLView (WebTextSizing) <_WebDocumentTextSizing> @end @interface WebHTMLView (WebHTMLViewFileInternal) - (BOOL)_imageExistsAtPaths:(NSArray *)paths; - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard inContext:(DOMRange *)context allowPlainText:(BOOL)allowPlainText chosePlainText:(BOOL *)chosePlainText; - (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard; - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText; - (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard; - (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action; - (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action; - (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action; - (float)_calculatePrintHeight; - (void)_updateTextSizeMultiplier; - (DOMRange *)_selectedRange; - (BOOL)_shouldDeleteRange:(DOMRange *)range; - (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard; - (NSView *)_hitViewForEvent:(NSEvent *)event; - (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString; - (DOMRange *)_documentRange; - (WebFrameBridge *)_bridge; - (void)_setMouseDownEvent:(NSEvent *)event; - (WebHTMLView *)_topHTMLView; - (BOOL)_isTopHTMLView; @end @interface WebHTMLView (WebForwardDeclaration) // FIXME: Put this in a normal category and stop doing the forward declaration trick. - (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize; @end @interface WebHTMLView (WebNSTextInputSupport) - (void)_updateSelectionForInputManager; @end @interface WebHTMLView (WebEditingStyleSupport) - (DOMCSSStyleDeclaration *)_emptyStyle; - (NSString *)_colorAsString:(NSColor *)color; @end @interface NSView (WebHTMLViewFileInternal) - (void)_web_setPrintingModeRecursive; - (void)_web_setPrintingModeRecursiveAndAdjustViewSize; - (void)_web_clearPrintingModeRecursive; - (void)_web_layoutIfNeededRecursive; @end @interface NSMutableDictionary (WebHTMLViewFileInternal) - (void)_web_setObjectIfNotNil:(id)object forKey:(id)key; @end // Handles the complete: text command @interface WebTextCompleteController : NSObject { @private WebHTMLView *_view; NSWindow *_popupWindow; NSTableView *_tableView; NSArray *_completions; NSString *_originalString; int prefixLength; } - (id)initWithHTMLView:(WebHTMLView *)view; - (void)doCompletion; - (void)endRevertingChange:(BOOL)revertChange moveLeft:(BOOL)goLeft; - (BOOL)popupWindowIsOpen; - (BOOL)filterKeyDown:(NSEvent *)event; - (void)_reflectSelection; @end struct WebHTMLViewInterpretKeyEventsParameters { KeyboardEvent* event; BOOL eventWasHandled; BOOL shouldSaveCommand; // The Input Method may consume an event and not tell us, in // which case we should not bubble the event up the DOM BOOL consumedByIM; }; @implementation WebHTMLViewPrivate #ifndef BUILDING_ON_TIGER + (void)initialize { WebCoreObjCFinalizeOnMainThread(self); } #endif - (void)dealloc { ASSERT(!autoscrollTimer); ASSERT(!autoscrollTriggerEvent); ASSERT(!updateActiveStateTimer); ASSERT(!updateMouseoverTimer); [mouseDownEvent release]; [keyDownEvent release]; [pluginController release]; [toolTip release]; [compController release]; [firstResponderTextViewAtMouseDownTime release]; [dataSource release]; [highlighters release]; if (promisedDragTIFFDataSource) promisedDragTIFFDataSource->deref(promisedDataClient()); [super dealloc]; } - (void)finalize { ASSERT_MAIN_THREAD(); if (promisedDragTIFFDataSource) promisedDragTIFFDataSource->deref(promisedDataClient()); [super finalize]; } - (void)clear { [mouseDownEvent release]; [keyDownEvent release]; [pluginController release]; [toolTip release]; [compController release]; [firstResponderTextViewAtMouseDownTime release]; [dataSource release]; [highlighters release]; if (promisedDragTIFFDataSource) promisedDragTIFFDataSource->deref(promisedDataClient()); mouseDownEvent = nil; keyDownEvent = nil; pluginController = nil; toolTip = nil; compController = nil; firstResponderTextViewAtMouseDownTime = nil; dataSource = nil; highlighters = nil; promisedDragTIFFDataSource = 0; } @end @implementation WebHTMLView (WebHTMLViewFileInternal) - (DOMRange *)_documentRange { return [[[self _frame] DOMDocument] _documentRange]; } - (BOOL)_imageExistsAtPaths:(NSArray *)paths { NSEnumerator *enumerator = [paths objectEnumerator]; NSString *path; while ((path = [enumerator nextObject]) != nil) { NSString *MIMEType = WKGetMIMETypeForExtension([path pathExtension]); if (MIMETypeRegistry::isSupportedImageResourceMIMEType(MIMEType)) return YES; } return NO; } - (WebDataSource *)_dataSource { return _private->dataSource; } - (WebFrameBridge *)_bridge { return [_private->dataSource _bridge]; } - (WebView *)_webView { return [_private->dataSource _webView]; } - (WebFrameView *)_frameView { return [[_private->dataSource webFrame] frameView]; } - (DOMDocumentFragment *)_documentFragmentWithPaths:(NSArray *)paths { DOMDocumentFragment *fragment; NSEnumerator *enumerator = [paths objectEnumerator]; NSMutableArray *domNodes = [[NSMutableArray alloc] init]; NSString *path; while ((path = [enumerator nextObject]) != nil) { // Non-image file types; _web_userVisibleString is appropriate here because this will // be pasted as visible text. NSString *url = [[[NSURL fileURLWithPath:path] _webkit_canonicalize] _web_userVisibleString]; [domNodes addObject:[[[self _frame] DOMDocument] createTextNode: url]]; } fragment = [[self _bridge] documentFragmentWithNodesAsParagraphs:domNodes]; [domNodes release]; return [fragment firstChild] != nil ? fragment : nil; } + (NSArray *)_excludedElementsForAttributedStringConversion { static NSArray *elements = nil; if (elements == nil) { elements = [[NSArray alloc] initWithObjects: // Omit style since we want style to be inline so the fragment can be easily inserted. @"style", // Omit xml so the result is not XHTML. @"xml", // Omit tags that will get stripped when converted to a fragment anyway. @"doctype", @"html", @"head", @"body", // Omit deprecated tags. @"applet", @"basefont", @"center", @"dir", @"font", @"isindex", @"menu", @"s", @"strike", @"u", // Omit object so no file attachments are part of the fragment. @"object", nil]; CFRetain(elements); } return elements; } static NSURL* uniqueURLWithRelativePart(NSString *relativePart) { CFUUIDRef UUIDRef = CFUUIDCreate(kCFAllocatorDefault); NSString *UUIDString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef); CFRelease(UUIDRef); NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@/%@", WebDataProtocolScheme, UUIDString, relativePart]]; CFRelease(UUIDString); return URL; } - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard inContext:(DOMRange *)context allowPlainText:(BOOL)allowPlainText chosePlainText:(BOOL *)chosePlainText { NSArray *types = [pasteboard types]; *chosePlainText = NO; DOMDocumentFragment *fragment = nil; if ([types containsObject:WebArchivePboardType] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:WebArchivePboardType inContext:context subresources:0])) return fragment; if ([types containsObject:NSFilenamesPboardType] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:NSFilenamesPboardType inContext:context subresources:0])) return fragment; if ([types containsObject:NSHTMLPboardType] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:NSHTMLPboardType inContext:context subresources:0])) return fragment; if ([types containsObject:NSRTFPboardType] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:NSRTFPboardType inContext:context subresources:0])) return fragment; if ([types containsObject:NSRTFDPboardType] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:NSRTFDPboardType inContext:context subresources:0])) return fragment; if ([types containsObject:NSTIFFPboardType] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:NSTIFFPboardType inContext:context subresources:0])) return fragment; if ([types containsObject:NSPICTPboardType] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:NSPICTPboardType inContext:context subresources:0])) return fragment; if ([types containsObject:NSURLPboardType] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:NSURLPboardType inContext:context subresources:0])) return fragment; if (allowPlainText && [types containsObject:NSStringPboardType] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:NSStringPboardType inContext:context subresources:0])) { *chosePlainText = YES; return fragment; } return nil; } - (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard { NSArray *types = [pasteboard types]; if ([types containsObject:NSStringPboardType]) return [pasteboard stringForType:NSStringPboardType]; NSAttributedString *attributedString = nil; NSString *string; if ([types containsObject:NSRTFDPboardType]) attributedString = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL]; if (attributedString == nil && [types containsObject:NSRTFPboardType]) attributedString = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL]; if (attributedString != nil) { string = [[attributedString string] copy]; [attributedString release]; return [string autorelease]; } if ([types containsObject:NSFilenamesPboardType]) { string = [[pasteboard propertyListForType:NSFilenamesPboardType] componentsJoinedByString:@"\n"]; if (string != nil) return string; } NSURL *URL; if ((URL = [NSURL URLFromPasteboard:pasteboard])) { string = [URL _web_userVisibleString]; if ([string length] > 0) return string; } return nil; } - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText { DOMRange *range = [self _selectedRange]; BOOL chosePlainText; DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard inContext:range allowPlainText:allowPlainText chosePlainText:&chosePlainText]; WebFrameBridge *bridge = [self _bridge]; if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:[self _selectedRange] givenAction:WebViewInsertActionPasted]) { [bridge replaceSelectionWithFragment:fragment selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard] matchStyle:chosePlainText]; } } - (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard { NSString *text = [self _plainTextFromPasteboard:pasteboard]; if ([self _shouldReplaceSelectionWithText:text givenAction:WebViewInsertActionPasted]) [[self _bridge] replaceSelectionWithText:text selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard]]; } - (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action { WebView *webView = [self _webView]; DOMNode *child = [fragment firstChild]; if ([fragment lastChild] == child && [child isKindOfClass:[DOMCharacterData class]]) return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:[(DOMCharacterData *)child data] replacingDOMRange:range givenAction:action]; return [[webView _editingDelegateForwarder] webView:webView shouldInsertNode:fragment replacingDOMRange:range givenAction:action]; } - (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action { WebView *webView = [self _webView]; return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:range givenAction:action]; } - (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action { return [self _shouldInsertText:text replacingDOMRange:[self _selectedRange] givenAction:action]; } // Calculate the vertical size of the view that fits on a single page - (float)_calculatePrintHeight { // Obtain the print info object for the current operation NSPrintInfo *pi = [[NSPrintOperation currentOperation] printInfo]; // Calculate the page height in points NSSize paperSize = [pi paperSize]; return paperSize.height - [pi topMargin] - [pi bottomMargin]; } - (void)_updateTextSizeMultiplier { [[self _bridge] setTextSizeMultiplier:[[self _webView] textSizeMultiplier]]; } - (DOMRange *)_selectedRange { Frame* coreFrame = core([self _frame]); return coreFrame ? kit(coreFrame->selectionController()->toRange().get()) : nil; } - (BOOL)_shouldDeleteRange:(DOMRange *)range { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->editor()->shouldDeleteRange(core(range)); } - (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard { return [[self _webView] smartInsertDeleteEnabled] && [[pasteboard types] containsObject:WebSmartPastePboardType]; } - (NSView *)_hitViewForEvent:(NSEvent *)event { // Usually, we hack AK's hitTest method to catch all events at the topmost WebHTMLView. // Callers of this method, however, want to query the deepest view instead. forceNSViewHitTest = YES; NSView *hitView = [[[self window] contentView] hitTest:[event locationInWindow]]; forceNSViewHitTest = NO; return hitView; } - (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString { // Put HTML on the pasteboard. if ([types containsObject:WebArchivePboardType]) { WebArchive *archive = [WebArchiver archiveSelectionInFrame:[self _frame]]; [pasteboard setData:[archive data] forType:WebArchivePboardType]; } // Put the attributed string on the pasteboard (RTF/RTFD format). if ([types containsObject:NSRTFDPboardType]) { if (attributedString == nil) { attributedString = [self selectedAttributedString]; } NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil]; [pasteboard setData:RTFDData forType:NSRTFDPboardType]; } if ([types containsObject:NSRTFPboardType]) { if (attributedString == nil) { attributedString = [self selectedAttributedString]; } if ([attributedString containsAttachments]) { attributedString = [attributedString _web_attributedStringByStrippingAttachmentCharacters]; } NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil]; [pasteboard setData:RTFData forType:NSRTFPboardType]; } // Put plain string on the pasteboard. if ([types containsObject:NSStringPboardType]) { // Map   to a plain old space because this is better for source code, other browsers do it, // and because HTML forces you to do this any time you want two spaces in a row. NSMutableString *s = [[self selectedString] mutableCopy]; const unichar NonBreakingSpaceCharacter = 0xA0; NSString *NonBreakingSpaceString = [NSString stringWithCharacters:&NonBreakingSpaceCharacter length:1]; [s replaceOccurrencesOfString:NonBreakingSpaceString withString:@" " options:0 range:NSMakeRange(0, [s length])]; [pasteboard setString:s forType:NSStringPboardType]; [s release]; } if ([self _canSmartCopyOrDelete] && [types containsObject:WebSmartPastePboardType]) { [pasteboard setData:nil forType:WebSmartPastePboardType]; } } - (void)_setMouseDownEvent:(NSEvent *)event { ASSERT(!event || [event type] == NSLeftMouseDown || [event type] == NSRightMouseDown || [event type] == NSOtherMouseDown); if (event == _private->mouseDownEvent) return; [event retain]; [_private->mouseDownEvent release]; _private->mouseDownEvent = event; [_private->firstResponderTextViewAtMouseDownTime release]; // The only code that checks this ivar only cares about NSTextViews. The code used to be more general, // but it caused reference cycles leading to world leaks (see 4557386). We should be able to eliminate // firstResponderTextViewAtMouseDownTime entirely when all the form controls are native widgets, because // the only caller (in WebCore) will be unnecessary. if (event) { NSResponder *firstResponder = [[self window] firstResponder]; if ([firstResponder isKindOfClass:[NSTextView class]]) _private->firstResponderTextViewAtMouseDownTime = [firstResponder retain]; else _private->firstResponderTextViewAtMouseDownTime = nil; } else _private->firstResponderTextViewAtMouseDownTime = nil; } - (void)_cancelUpdateActiveStateTimer { if (_private->updateActiveStateTimer) { CFRunLoopTimerInvalidate(_private->updateActiveStateTimer); CFRelease(_private->updateActiveStateTimer); _private->updateActiveStateTimer = NULL; } } - (void)_cancelUpdateMouseoverTimer { if (_private->updateMouseoverTimer) { CFRunLoopTimerInvalidate(_private->updateMouseoverTimer); CFRelease(_private->updateMouseoverTimer); _private->updateMouseoverTimer = NULL; } } - (WebHTMLView *)_topHTMLView { // FIXME: this can fail if the dataSource is nil, which happens when the WebView is tearing down from the window closing. WebHTMLView *view = (WebHTMLView *)[[[[_private->dataSource _webView] mainFrame] frameView] documentView]; ASSERT(view); ASSERT([view isKindOfClass:[WebHTMLView class]]); return view; } - (BOOL)_isTopHTMLView { // FIXME: this should be a cached boolean that doesn't rely on _topHTMLView since that can fail (see _topHTMLView). return self == [self _topHTMLView]; } @end @implementation WebHTMLView (WebPrivate) + (NSArray *)supportedMIMETypes { return [WebHTMLRepresentation supportedMIMETypes]; } + (NSArray *)supportedImageMIMETypes { return [WebHTMLRepresentation supportedImageMIMETypes]; } + (NSArray *)supportedNonImageMIMETypes { return [WebHTMLRepresentation supportedNonImageMIMETypes]; } + (NSArray *)unsupportedTextMIMETypes { return [NSArray arrayWithObjects: @"text/calendar", // iCal @"text/x-calendar", @"text/x-vcalendar", @"text/vcalendar", @"text/vcard", // vCard @"text/x-vcard", @"text/directory", @"text/ldif", // Netscape Address Book @"text/qif", // Quicken @"text/x-qif", @"text/x-csv", // CSV (for Address Book and Microsoft Outlook) @"text/x-vcf", // vCard type used in Sun affinity app @"text/rtf", // Rich Text Format nil]; } + (void)_postFlagsChangedEvent:(NSEvent *)flagsChangedEvent { NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved location:[[flagsChangedEvent window] convertScreenToBase:[NSEvent mouseLocation]] modifierFlags:[flagsChangedEvent modifierFlags] timestamp:[flagsChangedEvent timestamp] windowNumber:[flagsChangedEvent windowNumber] context:[flagsChangedEvent context] eventNumber:0 clickCount:0 pressure:0]; // Pretend it's a mouse move. [[NSNotificationCenter defaultCenter] postNotificationName:WKMouseMovedNotification() object:self userInfo:[NSDictionary dictionaryWithObject:fakeEvent forKey:@"NSEvent"]]; } - (void)_updateMouseoverWithFakeEvent { [self _cancelUpdateMouseoverTimer]; NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved location:[[self window] convertScreenToBase:[NSEvent mouseLocation]] modifierFlags:[[NSApp currentEvent] modifierFlags] timestamp:[NSDate timeIntervalSinceReferenceDate] windowNumber:[[self window] windowNumber] context:[[NSApp currentEvent] context] eventNumber:0 clickCount:0 pressure:0]; [self _updateMouseoverWithEvent:fakeEvent]; } static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) { WebHTMLView *view = (WebHTMLView *)info; [view _updateMouseoverWithFakeEvent]; } - (void)_frameOrBoundsChanged { if (!NSEqualSizes(_private->lastLayoutSize, [(NSClipView *)[self superview] documentVisibleRect].size)) { [self setNeedsLayout:YES]; [self setNeedsDisplay:YES]; [_private->compController endRevertingChange:NO moveLeft:NO]; } NSPoint origin = [[self superview] bounds].origin; if (!NSEqualPoints(_private->lastScrollPosition, origin)) { [[self _bridge] sendScrollEvent]; [_private->compController endRevertingChange:NO moveLeft:NO]; WebView *webView = [self _webView]; [[webView _UIDelegateForwarder] webView:webView didScrollDocumentInFrameView:[self _frameView]]; } _private->lastScrollPosition = origin; if (!_private->updateMouseoverTimer) { CFRunLoopTimerContext context = { 0, self, NULL, NULL, NULL }; _private->updateMouseoverTimer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 0, 0, 0, _updateMouseoverTimerCallback, &context); CFRunLoopAddTimer(CFRunLoopGetCurrent(), _private->updateMouseoverTimer, kCFRunLoopDefaultMode); } } - (void)_setAsideSubviews { ASSERT(!_private->subviewsSetAside); ASSERT(_private->savedSubviews == nil); _private->savedSubviews = _subviews; _subviews = nil; _private->subviewsSetAside = YES; } - (void)_restoreSubviews { ASSERT(_private->subviewsSetAside); ASSERT(_subviews == nil); _subviews = _private->savedSubviews; _private->savedSubviews = nil; _private->subviewsSetAside = NO; } #ifdef BUILDING_ON_TIGER // This is called when we are about to draw, but before our dirty rect is propagated to our ancestors. // That's the perfect time to do a layout, except that ideally we'd want to be sure that we're dirty // before doing it. As a compromise, when we're opaque we do the layout only when actually asked to // draw, but when we're transparent we do the layout at this stage so views behind us know that they // need to be redrawn (in case the layout causes some things to get dirtied). - (void)_propagateDirtyRectsToOpaqueAncestors { if (![[self _webView] drawsBackground]) [self _web_layoutIfNeededRecursive]; [super _propagateDirtyRectsToOpaqueAncestors]; } #else - (void)viewWillDraw { // On window close we will be called when the datasource is nil, then hit an assert in _topHTMLView // So check if the dataSource is nil before calling [self _isTopHTMLView], this can be removed // once the FIXME in _isTopHTMLView is fixed. if (_private->dataSource && [self _isTopHTMLView]) [self _web_layoutIfNeededRecursive]; [super viewWillDraw]; } #endif // Don't let AppKit even draw subviews. We take care of that. - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView { // This helps when we print as part of a larger print process. // If the WebHTMLView itself is what we're printing, then we will never have to do this. BOOL wasInPrintingMode = _private->printing; BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen]; if (wasInPrintingMode != isPrinting) { if (isPrinting) [self _web_setPrintingModeRecursive]; else [self _web_clearPrintingModeRecursive]; } #ifdef BUILDING_ON_TIGER // Because Tiger does not have viewWillDraw we need to do layout here. [self _web_layoutIfNeededRecursive]; [_subviews makeObjectsPerformSelector:@selector(_propagateDirtyRectsToOpaqueAncestors)]; #endif [self _setAsideSubviews]; [super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:isVisibleRect rectIsVisibleRectForView:visibleView topView:topView]; [self _restoreSubviews]; if (wasInPrintingMode != isPrinting) { if (wasInPrintingMode) [self _web_setPrintingModeRecursive]; else [self _web_clearPrintingModeRecursive]; } } // Don't let AppKit even draw subviews. We take care of that. - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect { BOOL needToSetAsideSubviews = !_private->subviewsSetAside; BOOL wasInPrintingMode = _private->printing; BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen]; if (needToSetAsideSubviews) { // This helps when we print as part of a larger print process. // If the WebHTMLView itself is what we're printing, then we will never have to do this. if (wasInPrintingMode != isPrinting) { if (isPrinting) [self _web_setPrintingModeRecursive]; else [self _web_clearPrintingModeRecursive]; } #ifdef BUILDING_ON_TIGER // Because Tiger does not have viewWillDraw we need to do layout here. NSRect boundsBeforeLayout = [self bounds]; if (!NSIsEmptyRect(visRect)) [self _web_layoutIfNeededRecursive]; // If layout changes the view's bounds, then we need to recompute the visRect. // That's because the visRect passed to us was based on the bounds at the time // we were called. This method is only displayed to draw "all", so it's safe // to just call visibleRect to compute the entire rectangle. if (!NSEqualRects(boundsBeforeLayout, [self bounds])) visRect = [self visibleRect]; #endif [self _setAsideSubviews]; } [super _recursiveDisplayAllDirtyWithLockFocus:needsLockFocus visRect:visRect]; if (needToSetAsideSubviews) { if (wasInPrintingMode != isPrinting) { if (wasInPrintingMode) [self _web_setPrintingModeRecursive]; else [self _web_clearPrintingModeRecursive]; } [self _restoreSubviews]; } } - (BOOL)_insideAnotherHTMLView { return self != [self _topHTMLView]; } - (void)scrollPoint:(NSPoint)point { // Since we can't subclass NSTextView to do what we want, we have to second guess it here. // If we get called during the handling of a key down event, we assume the call came from // NSTextView, and ignore it and use our own code to decide how to page up and page down // We are smarter about how far to scroll, and we have "superview scrolling" logic. NSEvent *event = [[self window] currentEvent]; if ([event type] == NSKeyDown) { const unichar pageUp = NSPageUpFunctionKey; if ([[event characters] rangeOfString:[NSString stringWithCharacters:&pageUp length:1]].length == 1) { [self tryToPerform:@selector(scrollPageUp:) with:nil]; return; } const unichar pageDown = NSPageDownFunctionKey; if ([[event characters] rangeOfString:[NSString stringWithCharacters:&pageDown length:1]].length == 1) { [self tryToPerform:@selector(scrollPageDown:) with:nil]; return; } } [super scrollPoint:point]; } - (NSView *)hitTest:(NSPoint)point { // WebHTMLView objects handle all events for objects inside them. // To get those events, we prevent hit testing from AppKit. // But there are three exceptions to this: // 1) For right mouse clicks and control clicks we don't yet have an implementation // that works for nested views, so we let the hit testing go through the // standard NSView code path (needs to be fixed, see bug 4361618). // 2) Java depends on doing a hit test inside it's mouse moved handling, // so we let the hit testing go through the standard NSView code path // when the current event is a mouse move (except when we are calling // from _updateMouseoverWithEvent, so we have to use a global, // forceWebHTMLViewHitTest, for that) // 3) The acceptsFirstMouse: and shouldDelayWindowOrderingForEvent: methods // both need to figure out which view to check with inside the WebHTMLView. // They use a global to change the behavior of hitTest: so they can get the // right view. The global is forceNSViewHitTest and the method they use to // do the hit testing is _hitViewForEvent:. (But this does not work correctly // when there is HTML overlapping the view, see bug 4361626) // 4) NSAccessibilityHitTest relies on this for checking the cursor position. // Our check for that is whether the event is NSFlagsChanged. This works // for VoiceOver's cntl-opt-f5 command (move focus to item under cursor) // and Dictionary's cmd-cntl-D (open dictionary popup for item under cursor). // This is of course a hack. BOOL captureHitsOnSubviews; if (forceNSViewHitTest) captureHitsOnSubviews = NO; else if (forceWebHTMLViewHitTest) captureHitsOnSubviews = YES; else { NSEvent *event = [[self window] currentEvent]; captureHitsOnSubviews = !([event type] == NSMouseMoved || [event type] == NSRightMouseDown || ([event type] == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask) != 0) || [event type] == NSFlagsChanged); } if (!captureHitsOnSubviews) return [super hitTest:point]; if ([[self superview] mouse:point inRect:[self frame]]) return self; return nil; } - (void)_clearLastHitViewIfSelf { if (lastHitView == self) lastHitView = nil; } - (NSTrackingRectTag)addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside { ASSERT(_private->trackingRectOwner == nil); _private->trackingRectOwner = owner; _private->trackingRectUserData = data; return TRACKING_RECT_TAG; } - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside useTrackingNum:(int)tag { ASSERT(tag == 0 || tag == TRACKING_RECT_TAG); ASSERT(_private->trackingRectOwner == nil); _private->trackingRectOwner = owner; _private->trackingRectUserData = data; return TRACKING_RECT_TAG; } - (void)_addTrackingRects:(NSRect *)rects owner:(id)owner userDataList:(void **)userDataList assumeInsideList:(BOOL *)assumeInsideList trackingNums:(NSTrackingRectTag *)trackingNums count:(int)count { ASSERT(count == 1); ASSERT(trackingNums[0] == 0 || trackingNums[0] == TRACKING_RECT_TAG); ASSERT(_private->trackingRectOwner == nil); _private->trackingRectOwner = owner; _private->trackingRectUserData = userDataList[0]; trackingNums[0] = TRACKING_RECT_TAG; } - (void)removeTrackingRect:(NSTrackingRectTag)tag { if (tag == 0) return; if (_private && (tag == TRACKING_RECT_TAG)) { _private->trackingRectOwner = nil; return; } if (_private && (tag == _private->lastToolTipTag)) { [super removeTrackingRect:tag]; _private->lastToolTipTag = 0; return; } // If any other tracking rect is being removed, we don't know how it was created // and it's possible there's a leak involved (see 3500217) ASSERT_NOT_REACHED(); } - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count { int i; for (i = 0; i < count; ++i) { int tag = tags[i]; if (tag == 0) continue; ASSERT(tag == TRACKING_RECT_TAG); if (_private != nil) { _private->trackingRectOwner = nil; } } } - (void)_sendToolTipMouseExited { // Nothing matters except window, trackingNumber, and userData. NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:[[self window] windowNumber] context:NULL eventNumber:0 trackingNumber:TRACKING_RECT_TAG userData:_private->trackingRectUserData]; [_private->trackingRectOwner mouseExited:fakeEvent]; } - (void)_sendToolTipMouseEntered { // Nothing matters except window, trackingNumber, and userData. NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:[[self window] windowNumber] context:NULL eventNumber:0 trackingNumber:TRACKING_RECT_TAG userData:_private->trackingRectUserData]; [_private->trackingRectOwner mouseEntered:fakeEvent]; } - (void)_setToolTip:(NSString *)string { NSString *toolTip = [string length] == 0 ? nil : string; NSString *oldToolTip = _private->toolTip; if ((toolTip == nil || oldToolTip == nil) ? toolTip == oldToolTip : [toolTip isEqualToString:oldToolTip]) { return; } if (oldToolTip) { [self _sendToolTipMouseExited]; [oldToolTip release]; } _private->toolTip = [toolTip copy]; if (toolTip) { // See radar 3500217 for why we remove all tooltips rather than just the single one we created. [self removeAllToolTips]; NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000); _private->lastToolTipTag = [self addToolTipRect:wideOpenRect owner:self userData:NULL]; [self _sendToolTipMouseEntered]; } } - (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data { return [[_private->toolTip copy] autorelease]; } - (void)_updateMouseoverWithEvent:(NSEvent *)event { if (_private->closed) return; NSView *contentView = [[event window] contentView]; NSPoint locationForHitTest = [[contentView superview] convertPoint:[event locationInWindow] fromView:nil]; forceWebHTMLViewHitTest = YES; NSView *hitView = [contentView hitTest:locationForHitTest]; forceWebHTMLViewHitTest = NO; WebHTMLView *view = nil; if ([hitView isKindOfClass:[WebHTMLView class]] && ![[(WebHTMLView *)hitView _webView] isHoverFeedbackSuspended]) view = (WebHTMLView *)hitView; if (view) [view retain]; if (lastHitView != view && lastHitView && [lastHitView _frame]) { // If we are moving out of a view (or frame), let's pretend the mouse moved // all the way out of that view. But we have to account for scrolling, because // khtml doesn't understand our clipping. NSRect visibleRect = [[[[lastHitView _frame] frameView] _scrollView] documentVisibleRect]; float yScroll = visibleRect.origin.y; float xScroll = visibleRect.origin.x; event = [NSEvent mouseEventWithType:NSMouseMoved location:NSMakePoint(-1 - xScroll, -1 - yScroll ) modifierFlags:[[NSApp currentEvent] modifierFlags] timestamp:[NSDate timeIntervalSinceReferenceDate] windowNumber:[[view window] windowNumber] context:[[NSApp currentEvent] context] eventNumber:0 clickCount:0 pressure:0]; if (Frame* lastHitCoreFrame = core([lastHitView _frame])) lastHitCoreFrame->eventHandler()->mouseMoved(event); } lastHitView = view; if (view) { if (Frame* coreFrame = core([view _frame])) coreFrame->eventHandler()->mouseMoved(event); [view release]; } } // keep in sync with WebPasteboardHelper::insertablePasteboardTypes + (NSArray *)_insertablePasteboardTypes { static NSArray *types = nil; if (!types) { types = [[NSArray alloc] initWithObjects:WebArchivePboardType, NSHTMLPboardType, NSFilenamesPboardType, NSTIFFPboardType, NSPICTPboardType, NSURLPboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, NSColorPboardType, nil]; CFRetain(types); } return types; } + (NSArray *)_selectionPasteboardTypes { // FIXME: We should put data for NSHTMLPboardType on the pasteboard but Microsoft Excel doesn't like our format of HTML (3640423). return [NSArray arrayWithObjects:WebArchivePboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil]; } - (NSImage *)_dragImageForURL:(NSString*)urlString withLabel:(NSString*)label { BOOL drawURLString = YES; BOOL clipURLString = NO, clipLabelString = NO; if (!label) { drawURLString = NO; label = urlString; } NSFont *labelFont = [[NSFontManager sharedFontManager] convertFont:[NSFont systemFontOfSize:DRAG_LINK_LABEL_FONT_SIZE] toHaveTrait:NSBoldFontMask]; NSFont *urlFont = [NSFont systemFontOfSize: DRAG_LINK_URL_FONT_SIZE]; NSSize labelSize; labelSize.width = [label _web_widthWithFont: labelFont]; labelSize.height = [labelFont ascender] - [labelFont descender]; if (labelSize.width > MAX_DRAG_LABEL_WIDTH){ labelSize.width = MAX_DRAG_LABEL_WIDTH; clipLabelString = YES; } NSSize imageSize, urlStringSize; imageSize.width = labelSize.width + DRAG_LABEL_BORDER_X * 2.0f; imageSize.height = labelSize.height + DRAG_LABEL_BORDER_Y * 2.0f; if (drawURLString) { urlStringSize.width = [urlString _web_widthWithFont: urlFont]; urlStringSize.height = [urlFont ascender] - [urlFont descender]; imageSize.height += urlStringSize.height; if (urlStringSize.width > MAX_DRAG_LABEL_WIDTH) { imageSize.width = MAX(MAX_DRAG_LABEL_WIDTH + DRAG_LABEL_BORDER_X * 2.0f, MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP); clipURLString = YES; } else { imageSize.width = MAX(labelSize.width + DRAG_LABEL_BORDER_X * 2.0f, urlStringSize.width + DRAG_LABEL_BORDER_X * 2.0f); } } NSImage *dragImage = [[[NSImage alloc] initWithSize: imageSize] autorelease]; [dragImage lockFocus]; [[NSColor colorWithCalibratedRed: 0.7f green: 0.7f blue: 0.7f alpha: 0.8f] set]; // Drag a rectangle with rounded corners/ NSBezierPath *path = [NSBezierPath bezierPath]; [path appendBezierPathWithOvalInRect: NSMakeRect(0.0f, 0.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)]; [path appendBezierPathWithOvalInRect: NSMakeRect(0, imageSize.height - DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)]; [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2.0f, imageSize.height - DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)]; [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2.0f, 0.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)]; [path appendBezierPathWithRect: NSMakeRect(DRAG_LABEL_RADIUS, 0.0f, imageSize.width - DRAG_LABEL_RADIUS * 2.0f, imageSize.height)]; [path appendBezierPathWithRect: NSMakeRect(0.0f, DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 10.0f, imageSize.height - 2.0f * DRAG_LABEL_RADIUS)]; [path appendBezierPathWithRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS - 20.0f, DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 20.0f, imageSize.height - 2.0f * DRAG_LABEL_RADIUS)]; [path fill]; NSColor *topColor = [NSColor colorWithCalibratedWhite:0.0f alpha:0.75f]; NSColor *bottomColor = [NSColor colorWithCalibratedWhite:1.0f alpha:0.5f]; if (drawURLString) { if (clipURLString) urlString = [WebStringTruncator centerTruncateString: urlString toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2.0f) withFont:urlFont]; [urlString _web_drawDoubledAtPoint:NSMakePoint(DRAG_LABEL_BORDER_X, DRAG_LABEL_BORDER_Y - [urlFont descender]) withTopColor:topColor bottomColor:bottomColor font:urlFont]; } if (clipLabelString) label = [WebStringTruncator rightTruncateString: label toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2.0f) withFont:labelFont]; [label _web_drawDoubledAtPoint:NSMakePoint (DRAG_LABEL_BORDER_X, imageSize.height - DRAG_LABEL_BORDER_Y_OFFSET - [labelFont pointSize]) withTopColor:topColor bottomColor:bottomColor font:labelFont]; [dragImage unlockFocus]; return dragImage; } - (NSImage *)_dragImageForLinkElement:(NSDictionary *)element { NSURL *linkURL = [element objectForKey: WebElementLinkURLKey]; NSString *label = [element objectForKey: WebElementLinkLabelKey]; NSString *urlString = [linkURL _web_userVisibleString]; return [self _dragImageForURL:urlString withLabel:label]; } - (void)pasteboardChangedOwner:(NSPasteboard *)pasteboard { [self setPromisedDragTIFFDataSource:0]; } - (void)pasteboard:(NSPasteboard *)pasteboard provideDataForType:(NSString *)type { if ([type isEqual:NSRTFDPboardType] && [[pasteboard types] containsObject:WebArchivePboardType]) { WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]]; [pasteboard _web_writePromisedRTFDFromArchive:archive containsImage:[[pasteboard types] containsObject:NSTIFFPboardType]]; [archive release]; } else if ([type isEqual:NSTIFFPboardType] && [self promisedDragTIFFDataSource]) { if (Image* image = [self promisedDragTIFFDataSource]->image()) [pasteboard setData:(NSData *)image->getTIFFRepresentation() forType:NSTIFFPboardType]; [self setPromisedDragTIFFDataSource:0]; } } - (void)_handleAutoscrollForMouseDragged:(NSEvent *)event { [self autoscroll:event]; [self _startAutoscrollTimer:event]; } - (WebPluginController *)_pluginController { return _private->pluginController; } - (void)_web_setPrintingModeRecursive { [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; [super _web_setPrintingModeRecursive]; } - (void)_web_clearPrintingModeRecursive { [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; [super _web_clearPrintingModeRecursive]; } - (void)_web_setPrintingModeRecursiveAndAdjustViewSize { [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:YES]; [super _web_setPrintingModeRecursiveAndAdjustViewSize]; } - (void)_layoutForPrinting { // Set printing mode temporarily so we can adjust the size of the view. This will allow // AppKit's pagination code to use the correct height for the page content. Leaving printing // mode on indefinitely would interfere with Mail's printing mechanism (at least), so we just // turn it off again after adjusting the size. [self _web_setPrintingModeRecursiveAndAdjustViewSize]; [self _web_clearPrintingModeRecursive]; } - (void)_layoutIfNeeded { ASSERT(!_private->subviewsSetAside); if ([[self _bridge] needsLayout]) _private->needsLayout = YES; if (_private->needsToApplyStyles || _private->needsLayout) [self layout]; } - (void)_web_layoutIfNeededRecursive { [self _layoutIfNeeded]; [super _web_layoutIfNeededRecursive]; } - (void)_startAutoscrollTimer: (NSEvent *)triggerEvent { if (_private->autoscrollTimer == nil) { _private->autoscrollTimer = [[NSTimer scheduledTimerWithTimeInterval:AUTOSCROLL_INTERVAL target:self selector:@selector(_autoscroll) userInfo:nil repeats:YES] retain]; _private->autoscrollTriggerEvent = [triggerEvent retain]; } } // FIXME: _selectionRect is deprecated in favor of selectionRect, which is in protocol WebDocumentSelection. // We can't remove this yet because it's still in use by Mail. - (NSRect)_selectionRect { return [self selectionRect]; } - (void)_stopAutoscrollTimer { NSTimer *timer = _private->autoscrollTimer; _private->autoscrollTimer = nil; [_private->autoscrollTriggerEvent release]; _private->autoscrollTriggerEvent = nil; [timer invalidate]; [timer release]; } - (void)_autoscroll { // Guarantee that the autoscroll timer is invalidated, even if we don't receive // a mouse up event. BOOL isStillDown = CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kCGMouseButtonLeft); if (!isStillDown){ [self _stopAutoscrollTimer]; return; } NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged location:[[self window] convertScreenToBase:[NSEvent mouseLocation]] modifierFlags:[[NSApp currentEvent] modifierFlags] timestamp:[NSDate timeIntervalSinceReferenceDate] windowNumber:[[self window] windowNumber] context:[[NSApp currentEvent] context] eventNumber:0 clickCount:0 pressure:0]; [self mouseDragged:fakeEvent]; } - (BOOL)_canEdit { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->editor()->canEdit(); } - (BOOL)_canEditRichly { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->editor()->canEditRichly(); } - (BOOL)_canAlterCurrentSelection { return [self _hasSelectionOrInsertionPoint] && [self _isEditable]; } - (BOOL)_hasSelection { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->selectionController()->isRange(); } - (BOOL)_hasSelectionOrInsertionPoint { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->selectionController()->isCaretOrRange(); } - (BOOL)_hasInsertionPoint { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->selectionController()->isCaret(); } - (BOOL)_isEditable { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->selectionController()->isContentEditable(); } - (BOOL)_transparentBackground { return _private->transparentBackground; } - (void)_setTransparentBackground:(BOOL)f { _private->transparentBackground = f; } - (NSImage *)_selectionDraggingImage { if ([self _hasSelection]) { NSImage *dragImage = core([self _frame])->selectionImage(); [dragImage _web_dissolveToFraction:WebDragImageAlpha]; return dragImage; } return nil; } - (NSRect)_selectionDraggingRect { // Mail currently calls this method. We can eliminate it when Mail no longer calls it. return [self selectionRect]; } - (DOMNode *)_insertOrderedList { Frame* coreFrame = core([self _frame]); return coreFrame ? kit(coreFrame->editor()->insertOrderedList().get()) : nil; } - (DOMNode *)_insertUnorderedList { Frame* coreFrame = core([self _frame]); return coreFrame ? kit(coreFrame->editor()->insertUnorderedList().get()) : nil; } - (BOOL)_canIncreaseSelectionListLevel { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->editor()->canIncreaseSelectionListLevel(); } - (BOOL)_canDecreaseSelectionListLevel { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->editor()->canDecreaseSelectionListLevel(); } - (DOMNode *)_increaseSelectionListLevel { Frame* coreFrame = core([self _frame]); return coreFrame ? kit(coreFrame->editor()->increaseSelectionListLevel().get()) : nil; } - (DOMNode *)_increaseSelectionListLevelOrdered { Frame* coreFrame = core([self _frame]); return coreFrame ? kit(coreFrame->editor()->increaseSelectionListLevelOrdered().get()) : nil; } - (DOMNode *)_increaseSelectionListLevelUnordered { Frame* coreFrame = core([self _frame]); return coreFrame ? kit(coreFrame->editor()->increaseSelectionListLevelUnordered().get()) : nil; } - (void)_decreaseSelectionListLevel { Frame* coreFrame = core([self _frame]); if (coreFrame) coreFrame->editor()->decreaseSelectionListLevel(); } - (void)_setHighlighter:(id)highlighter ofType:(NSString*)type { if (!_private->highlighters) _private->highlighters = [[NSMutableDictionary alloc] init]; [_private->highlighters setObject:highlighter forKey:type]; } - (void)_removeHighlighterOfType:(NSString*)type { [_private->highlighters removeObjectForKey:type]; } - (BOOL)_web_firstResponderCausesFocusDisplay { return [[self window] firstResponder] == self || [[self window] firstResponder] == [self _frameView]; } - (void)_updateActiveState { [self _cancelUpdateActiveStateTimer]; // This method does the job of updating the view based on the view's firstResponder-ness and // the window key-ness of the window containing this view. This involves four kinds of // drawing updates right now. // // The four display attributes are as follows: // // 1. The background color used to draw behind selected content (active | inactive color) // 2. Caret blinking (blinks | does not blink) // 3. The drawing of a focus ring around links in web pages. // 4. Changing the tint of controls from clear to aqua/graphite and vice versa // // Also, this is responsible for letting the bridge know if the window has gained or lost focus // so we can send focus and blur events. Frame* frame = core([self _frame]); if (!frame) return; Page* page = frame->page(); if (!page) return; NSWindow *window = [self window]; BOOL windowIsKey = [window isKeyWindow]; BOOL windowOrSheetIsKey = windowIsKey || [[window attachedSheet] isKeyWindow]; BOOL isActive = !_private->resigningFirstResponder && windowIsKey && [self _web_firstResponderCausesFocusDisplay]; frame->setIsActive(isActive); Frame* focusedFrame = page->focusController()->focusedOrMainFrame(); frame->setWindowHasFocus(frame == focusedFrame && windowOrSheetIsKey); } - (void)_writeSelectionToPasteboard:(NSPasteboard *)pasteboard { ASSERT([self _hasSelection]); NSArray *types = [self pasteboardTypesForSelection]; // Don't write RTFD to the pasteboard when the copied attributed string has no attachments. NSAttributedString *attributedString = [self selectedAttributedString]; NSMutableArray *mutableTypes = nil; if (![attributedString containsAttachments]) { mutableTypes = [types mutableCopy]; [mutableTypes removeObject:NSRTFDPboardType]; types = mutableTypes; } [pasteboard declareTypes:types owner:[self _topHTMLView]]; [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:attributedString]; [mutableTypes release]; } - (void)close { // Check for a nil _private here incase we were created with initWithCoder. In that case, the WebView is just throwing // out the archived WebHTMLView and recreating a new one if needed. So close dosen't need to do anything in that case. if (!_private || _private->closed) return; [self _clearLastHitViewIfSelf]; // FIXME: This is slow; should remove individual observers instead. [[NSNotificationCenter defaultCenter] removeObserver:self]; [_private->pluginController destroyAllPlugins]; [_private->pluginController setDataSource:nil]; // remove tooltips before clearing _private so removeTrackingRect: will work correctly [self removeAllToolTips]; [_private clear]; _private->closed = YES; Page* page = core([self _webView]); if (page) page->dragController()->setDraggingImageURL(KURL()); } - (BOOL)_hasHTMLDocument { Frame* coreFrame = core([self _frame]); if (!coreFrame) return NO; Document* document = coreFrame->document(); return document && document->isHTMLDocument(); } - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard forType:(NSString *)pboardType inContext:(DOMRange *)context subresources:(NSArray **)subresources { if (pboardType == WebArchivePboardType) { WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]]; if (subresources) *subresources = [archive subresources]; DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithArchive:archive]; [archive release]; return fragment; } if (pboardType == NSFilenamesPboardType) return [self _documentFragmentWithPaths:[pasteboard propertyListForType:NSFilenamesPboardType]]; if (pboardType == NSHTMLPboardType) { NSString *HTMLString = [pasteboard stringForType:NSHTMLPboardType]; // This is a hack to make Microsoft's HTML pasteboard data work. See 3778785. if ([HTMLString hasPrefix:@"Version:"]) { NSRange range = [HTMLString rangeOfString:@" window stops getting mouse moved events after first tooltip appears // The trick is to define a category on NSToolTipPanel that implements setAcceptsMouseMovedEvents:. // Since the category will be searched before the real class, we'll prevent the flag from being // set on the tool tip panel. @interface NSToolTipPanel : NSPanel @end @interface NSToolTipPanel (WebHTMLViewFileInternal) @end @implementation NSToolTipPanel (WebHTMLViewFileInternal) - (void)setAcceptsMouseMovedEvents:(BOOL)flag { // Do nothing, preventing the tool tip panel from trying to accept mouse-moved events. } @end #endif @interface NSArray (WebHTMLView) - (void)_web_makePluginViewsPerformSelector:(SEL)selector withObject:(id)object; @end @implementation WebHTMLView + (void)initialize { [NSApp registerServicesMenuSendTypes:[[self class] _selectionPasteboardTypes] returnTypes:[[self class] _insertablePasteboardTypes]]; _NSInitializeKillRing(); #ifndef BUILDING_ON_TIGER WebCoreObjCFinalizeOnMainThread(self); #endif } - (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; if (!self) return nil; [self setFocusRingType:NSFocusRingTypeNone]; // Make all drawing go through us instead of subviews. [self _setDrawsOwnDescendants:YES]; _private = [[WebHTMLViewPrivate alloc] init]; _private->pluginController = [[WebPluginController alloc] initWithDocumentView:self]; _private->needsLayout = YES; return self; } - (void)dealloc { // We can't assert that close has already been called because // this view can be removed from it's superview, even though // it could be needed later, so close if needed. [self close]; [_private release]; _private = nil; [super dealloc]; } - (void)finalize { ASSERT_MAIN_THREAD(); // We can't assert that close has already been called because // this view can be removed from it's superview, even though // it could be needed later, so close if needed. [self close]; [super finalize]; } // Returns YES if the delegate returns YES (so we should do no more work). - (BOOL)callDelegateDoCommandBySelectorIfNeeded:(SEL)selector { BOOL callerAlreadyCalledDelegate = _private->selectorForDoCommandBySelector == selector; _private->selectorForDoCommandBySelector = 0; if (callerAlreadyCalledDelegate) return NO; WebView *webView = [self _webView]; id editingDelegate = [webView editingDelegate]; if (![editingDelegate respondsToSelector:@selector(webView:doCommandBySelector:)]) return NO; return [editingDelegate webView:webView doCommandBySelector:selector]; } - (void)callWebCoreCommand:(SEL)selector { if ([self callDelegateDoCommandBySelectorIfNeeded:selector]) return; Frame* coreFrame = core([self _frame]); if (!coreFrame) return; // Capitalize the first letter of the selector, since we use capitalized command // names in the Editor object (why?). And remove the trailing colon. const char* selectorName = sel_getName(selector); size_t selectorNameLength = strlen(selectorName); ASSERT(selectorNameLength >= 2); ASSERT(selectorName[selectorNameLength - 1] == ':'); Vector commandName(selectorNameLength - 1 + 1); commandName[0] = toupper(selectorName[0]); memcpy(&commandName[1], &selectorName[1], selectorNameLength - 2); commandName[selectorNameLength - 1] = 0; coreFrame->editor()->execCommand(commandName.data()); } // These commands are forwarded to the Editor object in WebCore. // Ideally we'd do this for all editing commands; more of the code // should be moved from here to there, and more commands should be // added to this list. #define WEBCORE_COMMAND(command) - (void)command:(id)sender { [self callWebCoreCommand:_cmd]; } WEBCORE_COMMAND(deleteWordBackward) WEBCORE_COMMAND(deleteWordForward) WEBCORE_COMMAND(insertBacktab) WEBCORE_COMMAND(insertLineBreak) WEBCORE_COMMAND(insertNewline) WEBCORE_COMMAND(insertTab) WEBCORE_COMMAND(moveBackward) WEBCORE_COMMAND(moveBackwardAndModifySelection) WEBCORE_COMMAND(moveDown) WEBCORE_COMMAND(moveDownAndModifySelection) WEBCORE_COMMAND(moveForward) WEBCORE_COMMAND(moveForwardAndModifySelection) WEBCORE_COMMAND(moveLeft) WEBCORE_COMMAND(moveLeftAndModifySelection) WEBCORE_COMMAND(moveParagraphBackwardAndModifySelection) WEBCORE_COMMAND(moveParagraphForwardAndModifySelection) WEBCORE_COMMAND(moveRight) WEBCORE_COMMAND(moveRightAndModifySelection) WEBCORE_COMMAND(moveToBeginningOfDocument) WEBCORE_COMMAND(moveToBeginningOfDocumentAndModifySelection) WEBCORE_COMMAND(moveToBeginningOfLine) WEBCORE_COMMAND(moveToBeginningOfLineAndModifySelection) WEBCORE_COMMAND(moveToBeginningOfParagraph) WEBCORE_COMMAND(moveToBeginningOfParagraphAndModifySelection) WEBCORE_COMMAND(moveToBeginningOfSentence) WEBCORE_COMMAND(moveToBeginningOfSentenceAndModifySelection) WEBCORE_COMMAND(moveToEndOfDocument) WEBCORE_COMMAND(moveToEndOfDocumentAndModifySelection) WEBCORE_COMMAND(moveToEndOfLine) WEBCORE_COMMAND(moveToEndOfLineAndModifySelection) WEBCORE_COMMAND(moveToEndOfParagraph) WEBCORE_COMMAND(moveToEndOfParagraphAndModifySelection) WEBCORE_COMMAND(moveToEndOfSentence) WEBCORE_COMMAND(moveToEndOfSentenceAndModifySelection) WEBCORE_COMMAND(moveUp) WEBCORE_COMMAND(moveUpAndModifySelection) WEBCORE_COMMAND(moveWordBackward) WEBCORE_COMMAND(moveWordBackwardAndModifySelection) WEBCORE_COMMAND(moveWordForward) WEBCORE_COMMAND(moveWordForwardAndModifySelection) WEBCORE_COMMAND(moveWordLeft) WEBCORE_COMMAND(moveWordLeftAndModifySelection) WEBCORE_COMMAND(moveWordRight) WEBCORE_COMMAND(moveWordRightAndModifySelection) #undef WEBCORE_COMMAND #define COMMAND_PROLOGUE if ([self callDelegateDoCommandBySelectorIfNeeded:_cmd]) return; - (IBAction)takeFindStringFromSelection:(id)sender { COMMAND_PROLOGUE if (![self _hasSelection]) { NSBeep(); return; } [NSPasteboard _web_setFindPasteboardString:[self selectedString] withOwner:self]; } - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray *)types { [pasteboard declareTypes:types owner:[self _topHTMLView]]; [self writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard]; return YES; } - (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pasteboard { Frame* coreFrame = core([self _frame]); if (!coreFrame) return NO; if (coreFrame->selectionController()->isContentRichlyEditable()) [self _pasteWithPasteboard:pasteboard allowPlainText:YES]; else [self _pasteAsPlainTextWithPasteboard:pasteboard]; return YES; } - (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType { if (sendType != nil && [[self pasteboardTypesForSelection] containsObject:sendType] && [self _hasSelection]) { return self; } else if (returnType != nil && [[[self class] _insertablePasteboardTypes] containsObject:returnType] && [self _isEditable]) { return self; } return [[self nextResponder] validRequestorForSendType:sendType returnType:returnType]; } - (void)selectAll:(id)sender { COMMAND_PROLOGUE [self selectAll]; } // jumpToSelection is the old name for what AppKit now calls centerSelectionInVisibleArea. Safari // was using the old jumpToSelection selector in its menu. Newer versions of Safari will us the // selector centerSelectionInVisibleArea. We'll leave this old selector in place for two reasons: // (1) compatibility between older Safari and newer WebKit; (2) other WebKit-based applications // might be using the jumpToSelection: selector, and we don't want to break them. - (void)jumpToSelection:(id)sender { COMMAND_PROLOGUE if (Frame* coreFrame = core([self _frame])) coreFrame->revealSelection(RenderLayer::gAlignCenterAlways); } - (BOOL)validateUserInterfaceItemWithoutDelegate:(id )item { SEL action = [item action]; Frame* frame = core([self _frame]); if (Document* doc = frame->document()) { if (doc->isPluginDocument()) return NO; if (doc->isImageDocument()) { if (action == @selector(copy:)) return frame->loader()->isComplete(); return NO; } } if (action == @selector(changeSpelling:) || action == @selector(_changeSpellingFromMenu:) || action == @selector(checkSpelling:) || action == @selector(complete:) || action == @selector(deleteBackward:) || action == @selector(deleteBackwardByDecomposingPreviousCharacter:) || action == @selector(deleteForward:) || action == @selector(deleteToBeginningOfLine:) || action == @selector(deleteToBeginningOfParagraph:) || action == @selector(deleteToEndOfLine:) || action == @selector(deleteToEndOfParagraph:) || action == @selector(deleteToMark:) || action == @selector(deleteWordBackward:) || action == @selector(deleteWordForward:) || action == @selector(insertBacktab:) || action == @selector(insertLineBreak:) || action == @selector(insertNewline:) || action == @selector(insertNewlineIgnoringFieldEditor:) || action == @selector(insertParagraphSeparator:) || action == @selector(insertTab:) || action == @selector(insertTabIgnoringFieldEditor:) || action == @selector(moveBackward:) || action == @selector(moveBackwardAndModifySelection:) || action == @selector(moveDown:) || action == @selector(moveDownAndModifySelection:) || action == @selector(moveForward:) || action == @selector(moveForwardAndModifySelection:) || action == @selector(moveLeft:) || action == @selector(moveLeftAndModifySelection:) || action == @selector(moveParagraphBackwardAndModifySelection:) || action == @selector(moveParagraphForwardAndModifySelection:) || action == @selector(moveRight:) || action == @selector(moveRightAndModifySelection:) || action == @selector(moveToBeginningOfDocument:) || action == @selector(moveToBeginningOfDocumentAndModifySelection:) || action == @selector(moveToBeginningOfSentence:) || action == @selector(moveToBeginningOfSentenceAndModifySelection:) || action == @selector(moveToBeginningOfLine:) || action == @selector(moveToBeginningOfLineAndModifySelection:) || action == @selector(moveToBeginningOfParagraph:) || action == @selector(moveToBeginningOfParagraphAndModifySelection:) || action == @selector(moveToEndOfDocument:) || action == @selector(moveToEndOfDocumentAndModifySelection:) || action == @selector(moveToEndOfSentence:) || action == @selector(moveToEndOfSentenceAndModifySelection:) || action == @selector(moveToEndOfLine:) || action == @selector(moveToEndOfLineAndModifySelection:) || action == @selector(moveToEndOfParagraph:) || action == @selector(moveToEndOfParagraphAndModifySelection:) || action == @selector(moveUp:) || action == @selector(moveUpAndModifySelection:) || action == @selector(moveWordBackward:) || action == @selector(moveWordBackwardAndModifySelection:) || action == @selector(moveWordForward:) || action == @selector(moveWordForwardAndModifySelection:) || action == @selector(moveWordLeft:) || action == @selector(moveWordLeftAndModifySelection:) || action == @selector(moveWordRight:) || action == @selector(moveWordRightAndModifySelection:) || action == @selector(pageDown:) || action == @selector(pageDownAndModifySelection:) || action == @selector(pageUp:) || action == @selector(pageUpAndModifySelection:) || action == @selector(pasteFont:) || action == @selector(transpose:) || action == @selector(yank:) || action == @selector(yankAndSelect:)) return [self _canEdit]; if (action == @selector(showGuessPanel:)) { #ifndef BUILDING_ON_TIGER // Match OS X AppKit behavior for post-Tiger. Don't change Tiger behavior. NSMenuItem *menuItem = (NSMenuItem *)item; if ([menuItem isKindOfClass:[NSMenuItem class]]) { BOOL panelShowing = [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible]; [menuItem setTitle:panelShowing ? UI_STRING("Hide Spelling and Grammar", "menu item title") : UI_STRING("Show Spelling and Grammar", "menu item title")]; } #endif return [self _canEdit]; } if (action == @selector(changeBaseWritingDirection:)) { NSMenuItem *menuItem = (NSMenuItem *)item; if ([menuItem isKindOfClass:[NSMenuItem class]]) { NSWritingDirection writingDirection = static_cast([item tag]); if (writingDirection == NSWritingDirectionNatural) { [menuItem setState:NO]; return NO; } DOMCSSStyleDeclaration* style = [self _emptyStyle]; [style setDirection:writingDirection == NSWritingDirectionLeftToRight ? @"LTR" : @"RTL"]; [menuItem setState:[[self _bridge] selectionHasStyle:style]]; } return [self _canEdit]; } if (action == @selector(toggleBaseWritingDirection:)) { NSMenuItem *menuItem = (NSMenuItem *)item; if ([menuItem isKindOfClass:[NSMenuItem class]]) { DOMCSSStyleDeclaration* rtl = [self _emptyStyle]; [rtl setDirection:@"RTL"]; // Take control of the title of the menu item, instead of just checking/unchecking it because otherwise // we don't know what the check would mean. [menuItem setTitle:[[self _bridge] selectionHasStyle:rtl] ? UI_STRING("Left to Right", "Left to Right context menu item") : UI_STRING("Right to Left", "Right to Left context menu item")]; } return [self _canEdit]; } if (action == @selector(alignCenter:) || action == @selector(alignLeft:) || action == @selector(alignJustified:) || action == @selector(alignRight:) || action == @selector(changeAttributes:) || action == @selector(changeColor:) || action == @selector(changeFont:) || action == @selector(indent:) || action == @selector(outdent:)) return [self _canEditRichly]; if (action == @selector(capitalizeWord:) || action == @selector(lowercaseWord:) || action == @selector(uppercaseWord:)) return [self _hasSelection] && [self _isEditable]; if (action == @selector(centerSelectionInVisibleArea:) || action == @selector(jumpToSelection:) || action == @selector(copyFont:) || action == @selector(setMark:)) return [self _hasSelection] || ([self _isEditable] && [self _hasInsertionPoint]); if (action == @selector(changeDocumentBackgroundColor:)) return [[self _webView] isEditable] && [self _canEditRichly]; if (action == @selector(copy:)) return (frame && frame->editor()->canDHTMLCopy()) || frame->editor()->canCopy(); if (action == @selector(cut:)) return (frame && frame->editor()->canDHTMLCut()) || frame->editor()->canCut(); if (action == @selector(delete:)) return (frame && frame->editor()->canDelete()); if (action == @selector(_ignoreSpellingFromMenu:) || action == @selector(_learnSpellingFromMenu:) || action == @selector(takeFindStringFromSelection:)) return [self _hasSelection]; if (action == @selector(paste:) || action == @selector(pasteAsPlainText:)) return (frame && frame->editor()->canDHTMLPaste()) || frame->editor()->canPaste(); if (action == @selector(pasteAsRichText:)) return frame && (frame->editor()->canDHTMLPaste() || (frame->editor()->canPaste() && frame->selectionController()->isContentRichlyEditable())); if (action == @selector(performFindPanelAction:)) // FIXME: Not yet implemented. return NO; if (action == @selector(selectToMark:) || action == @selector(swapWithMark:)) return [self _hasSelectionOrInsertionPoint] && [[self _bridge] markDOMRange] != nil; if (action == @selector(subscript:)) { NSMenuItem *menuItem = (NSMenuItem *)item; if ([menuItem isKindOfClass:[NSMenuItem class]]) { DOMCSSStyleDeclaration *style = [self _emptyStyle]; [style setVerticalAlign:@"sub"]; [menuItem setState:[[self _bridge] selectionHasStyle:style]]; } return [self _canEditRichly]; } if (action == @selector(superscript:)) { NSMenuItem *menuItem = (NSMenuItem *)item; if ([menuItem isKindOfClass:[NSMenuItem class]]) { DOMCSSStyleDeclaration *style = [self _emptyStyle]; [style setVerticalAlign:@"super"]; [menuItem setState:[[self _bridge] selectionHasStyle:style]]; } return [self _canEditRichly]; } if (action == @selector(underline:)) { NSMenuItem *menuItem = (NSMenuItem *)item; if ([menuItem isKindOfClass:[NSMenuItem class]]) { DOMCSSStyleDeclaration *style = [self _emptyStyle]; [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""]; [menuItem setState:[[self _bridge] selectionHasStyle:style]]; } return [self _canEditRichly]; } if (action == @selector(unscript:)) { NSMenuItem *menuItem = (NSMenuItem *)item; if ([menuItem isKindOfClass:[NSMenuItem class]]) { DOMCSSStyleDeclaration *style = [self _emptyStyle]; [style setVerticalAlign:@"baseline"]; [menuItem setState:[[self _bridge] selectionHasStyle:style]]; } return [self _canEditRichly]; } if (action == @selector(_lookUpInDictionaryFromMenu:)) { return [self _hasSelection]; } #ifndef BUILDING_ON_TIGER if (action == @selector(toggleGrammarChecking:)) { // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must validate // the selector here because we implement it here, and we must implement it here because the AppKit // code checks the first responder. BOOL checkMark = [self isGrammarCheckingEnabled]; if ([(NSObject *)item isKindOfClass:[NSMenuItem class]]) { NSMenuItem *menuItem = (NSMenuItem *)item; [menuItem setState:checkMark ? NSOnState : NSOffState]; } return YES; } #endif return YES; } - (BOOL)validateUserInterfaceItem:(id )item { BOOL result = [self validateUserInterfaceItemWithoutDelegate:item]; id ud = [[self _webView] UIDelegate]; if (ud && [ud respondsToSelector:@selector(webView:validateUserInterfaceItem:defaultValidation:)]) return [ud webView:[self _webView] validateUserInterfaceItem:item defaultValidation:result]; return result; } - (BOOL)acceptsFirstResponder { // Don't accept first responder when we first click on this view. // We have to pass the event down through WebCore first to be sure we don't hit a subview. // Do accept first responder at any other time, for example from keyboard events, // or from calls back from WebCore once we begin mouse-down event handling. NSEvent *event = [NSApp currentEvent]; if ([event type] == NSLeftMouseDown && !_private->handlingMouseDownEvent && NSPointInRect([event locationInWindow], [self convertRect:[self visibleRect] toView:nil])) { return NO; } return YES; } - (BOOL)maintainsInactiveSelection { // This method helps to determine whether the WebHTMLView should maintain // an inactive selection when it's not first responder. // Traditionally, these views have not maintained such selections, // clearing them when the view was not first responder. However, // to fix bugs like this one: // : "Editable WebViews should maintain a selection even // when they're not firstResponder" // it was decided to add a switch to act more like an NSTextView. if ([[self _webView] maintainsInactiveSelection]) return YES; // Predict the case where we are losing first responder status only to // gain it back again. Want to keep the selection in that case. id nextResponder = [[self window] _newFirstResponderAfterResigning]; if ([nextResponder isKindOfClass:[NSScrollView class]]) { id contentView = [nextResponder contentView]; if (contentView) nextResponder = contentView; } if ([nextResponder isKindOfClass:[NSClipView class]]) { id documentView = [nextResponder documentView]; if (documentView) nextResponder = documentView; } if (nextResponder == self) return YES; Frame* coreFrame = core([self _frame]); bool selectionIsEditable = coreFrame && coreFrame->selectionController()->isContentEditable(); bool nextResponderIsInWebView = [nextResponder isKindOfClass:[NSView class]] && [nextResponder isDescendantOf:[[[self _webView] mainFrame] frameView]]; return selectionIsEditable && nextResponderIsInWebView; } - (void)addMouseMovedObserver { if (!_private->dataSource || ![self _isTopHTMLView]) return; // Unless the Dashboard asks us to do this for all windows, keep an observer going only for the key window. if (!([[self window] isKeyWindow] || [[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows])) return; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mouseMovedNotification:) name:WKMouseMovedNotification() object:nil]; [self _frameOrBoundsChanged]; } - (void)removeMouseMovedObserverUnconditionally { [[NSNotificationCenter defaultCenter] removeObserver:self name:WKMouseMovedNotification() object:nil]; } - (void)removeMouseMovedObserver { // Don't remove the observer if we're running the Dashboard. if ([[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows]) return; [[self _webView] _mouseDidMoveOverElement:nil modifierFlags:0]; [self removeMouseMovedObserverUnconditionally]; } - (void)addSuperviewObservers { // We watch the bounds of our superview, so that we can do a layout when the size // of the superview changes. This is different from other scrollable things that don't // need this kind of thing because their layout doesn't change. // We need to pay attention to both height and width because our "layout" has to change // to extend the background the full height of the space and because some elements have // sizes that are based on the total size of the view. NSView *superview = [self superview]; if (superview && [self window]) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_frameOrBoundsChanged) name:NSViewFrameDidChangeNotification object:superview]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_frameOrBoundsChanged) name:NSViewBoundsDidChangeNotification object:superview]; // In addition to registering for frame/bounds change notifications, call -_frameOrBoundsChanged. // It will check the current size/scroll against the previous layout's size/scroll. We need to // do this here to catch the case where the WebView is laid out at one size, removed from its // window, resized, and inserted into another window. Our frame/bounds changed notifications // will not be sent in that situation, since we only watch for changes while in the view hierarchy. [self _frameOrBoundsChanged]; } } - (void)removeSuperviewObservers { NSView *superview = [self superview]; if (superview && [self window]) { [[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewFrameDidChangeNotification object:superview]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewBoundsDidChangeNotification object:superview]; } } - (void)addWindowObservers { NSWindow *window = [self window]; if (window) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowWillClose:) name:NSWindowWillCloseNotification object:window]; } } - (void)removeWindowObservers { NSWindow *window = [self window]; if (window) { [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidBecomeKeyNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidResignKeyNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowWillCloseNotification object:window]; } } - (void)viewWillMoveToSuperview:(NSView *)newSuperview { [self removeSuperviewObservers]; } - (void)viewDidMoveToSuperview { // Do this here in case the text size multiplier changed when a non-HTML // view was installed. if ([self superview] != nil) { [self _updateTextSizeMultiplier]; [self addSuperviewObservers]; } } static void _updateActiveStateTimerCallback(CFRunLoopTimerRef timer, void *info) { WebHTMLView *view = (WebHTMLView *)info; [view _updateActiveState]; } - (void)viewWillMoveToWindow:(NSWindow *)window { // Don't do anything if we aren't initialized. This happens // when decoding a WebView. When WebViews are decoded their subviews // are created by initWithCoder: and so won't be normally // initialized. The stub views are discarded by WebView. if (!_private) return; // FIXME: Some of these calls may not work because this view may be already removed from it's superview. [self removeMouseMovedObserverUnconditionally]; [self removeWindowObservers]; [self removeSuperviewObservers]; [self _cancelUpdateMouseoverTimer]; [self _cancelUpdateActiveStateTimer]; [[self _pluginController] stopAllPlugins]; } - (void)viewDidMoveToWindow { // Don't do anything if we aren't initialized. This happens // when decoding a WebView. When WebViews are decoded their subviews // are created by initWithCoder: and so won't be normally // initialized. The stub views are discarded by WebView. if (!_private) return; [self _stopAutoscrollTimer]; if ([self window]) { _private->lastScrollPosition = [[self superview] bounds].origin; [self addWindowObservers]; [self addSuperviewObservers]; [self addMouseMovedObserver]; // Schedule this update, rather than making the call right now. // The reason is that placing the caret in the just-installed view requires // the HTML/XML document to be available on the WebCore side, but it is not // at the time this code is running. However, it will be there on the next // crank of the run loop. Doing this helps to make a blinking caret appear // in a new, empty window "automatic". if (!_private->updateActiveStateTimer) { CFRunLoopTimerContext context = { 0, self, NULL, NULL, NULL }; _private->updateActiveStateTimer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 0, 0, 0, _updateActiveStateTimerCallback, &context); CFRunLoopAddTimer(CFRunLoopGetCurrent(), _private->updateActiveStateTimer, kCFRunLoopDefaultMode); } [[self _pluginController] startAllPlugins]; _private->lastScrollPosition = NSZeroPoint; } } - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow { [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewWillMoveToHostWindow:) withObject:hostWindow]; } - (void)viewDidMoveToHostWindow { [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewDidMoveToHostWindow) withObject:nil]; } - (void)addSubview:(NSView *)view { [super addSubview:view]; if ([WebPluginController isPlugInView:view]) [[self _pluginController] addPlugin:view]; } - (void)willRemoveSubview:(NSView *)subview { if ([WebPluginController isPlugInView:subview]) [[self _pluginController] destroyPlugin:subview]; [super willRemoveSubview:subview]; } - (void)reapplyStyles { if (!_private->needsToApplyStyles) { return; } #ifdef _KWQ_TIMING double start = CFAbsoluteTimeGetCurrent(); #endif [[self _bridge] reapplyStylesForDeviceType: _private->printing ? WebCoreDevicePrinter : WebCoreDeviceScreen]; #ifdef _KWQ_TIMING double thisTime = CFAbsoluteTimeGetCurrent() - start; LOG(Timing, "%s apply style seconds = %f", [self URL], thisTime); #endif _private->needsToApplyStyles = NO; } // Do a layout, but set up a new fixed width for the purposes of doing printing layout. // minPageWidth==0 implies a non-printing layout - (void)layoutToMinimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustingViewSize:(BOOL)adjustViewSize { [self reapplyStyles]; if (!_private->needsLayout && ![[self _bridge] needsLayout]) return; #ifdef _KWQ_TIMING double start = CFAbsoluteTimeGetCurrent(); #endif LOG(View, "%@ doing layout", self); if (minPageWidth > 0.0) { [[self _bridge] forceLayoutWithMinimumPageWidth:minPageWidth maximumPageWidth:maxPageWidth adjustingViewSize:adjustViewSize]; } else { [[self _bridge] forceLayoutAdjustingViewSize:adjustViewSize]; } _private->needsLayout = NO; if (!_private->printing) { // get size of the containing dynamic scrollview, so // appearance and disappearance of scrollbars will not show up // as a size change NSSize newLayoutFrameSize = [[[self superview] superview] frame].size; if (_private->laidOutAtLeastOnce && !NSEqualSizes(_private->lastLayoutFrameSize, newLayoutFrameSize)) { [[self _bridge] sendResizeEvent]; if ([[self _bridge] needsLayout]) [[self _bridge] forceLayoutAdjustingViewSize:NO]; } _private->laidOutAtLeastOnce = YES; _private->lastLayoutSize = [(NSClipView *)[self superview] documentVisibleRect].size; _private->lastLayoutFrameSize = newLayoutFrameSize; } #ifdef _KWQ_TIMING double thisTime = CFAbsoluteTimeGetCurrent() - start; LOG(Timing, "%s layout seconds = %f", [self URL], thisTime); #endif } - (void)layout { [self layoutToMinimumPageWidth:0.0f maximumPageWidth:0.0f adjustingViewSize:NO]; } - (NSMenu *)menuForEvent:(NSEvent *)event { [_private->compController endRevertingChange:NO moveLeft:NO]; _private->handlingMouseDownEvent = YES; BOOL handledEvent = NO; Frame* coreFrame = core([self _frame]); if (!coreFrame) { _private->handlingMouseDownEvent = NO; return nil; } Page* page = coreFrame->page(); if (!page) return nil; page->contextMenuController()->clearContextMenu(); handledEvent = coreFrame->eventHandler()->sendContextMenuEvent(PlatformMouseEvent(event)); _private->handlingMouseDownEvent = NO; if (!handledEvent) return nil; ContextMenu* coreMenu = page->contextMenuController()->contextMenu(); if (!coreMenu) return nil; NSArray* menuItems = coreMenu->platformDescription(); NSMenu* menu = nil; if (menuItems && [menuItems count] > 0) { menu = [[[NSMenu alloc] init] autorelease]; for (unsigned i = 0; i < [menuItems count]; i++) [menu addItem:[menuItems objectAtIndex:i]]; } return menu; } - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag { return [self searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:NO]; } - (void)clearFocus { Frame* coreFrame = core([self _frame]); if (!coreFrame) return; Document* document = coreFrame->document(); if (!document) return; document->setFocusedNode(0); } - (BOOL)isOpaque { return [[self _webView] drawsBackground]; } - (void)setNeedsDisplay:(BOOL)flag { LOG(View, "%@ flag = %d", self, (int)flag); [super setNeedsDisplay: flag]; } - (void)setNeedsLayout: (BOOL)flag { LOG(View, "%@ flag = %d", self, (int)flag); _private->needsLayout = flag; } - (void)setNeedsToApplyStyles: (BOOL)flag { LOG(View, "%@ flag = %d", self, (int)flag); _private->needsToApplyStyles = flag; } - (void)drawSingleRect:(NSRect)rect { [NSGraphicsContext saveGraphicsState]; NSRectClip(rect); ASSERT([[self superview] isKindOfClass:[WebClipView class]]); [(WebClipView *)[self superview] setAdditionalClip:rect]; NS_DURING { if ([self _transparentBackground]) { [[NSColor clearColor] set]; NSRectFill (rect); } [[self _bridge] drawRect:rect]; // This hack is needed for . We can hit a race condition where drawRect will be // called after the WebView has closed. If the client did not properly close the WebView and set the // UIDelegate to nil, then the UIDelegate will be stale and this code will crash. static BOOL version3OrLaterClient = WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITHOUT_QUICKBOOKS_QUIRK); if (version3OrLaterClient) { WebView *webView = [self _webView]; [[webView _UIDelegateForwarder] webView:webView didDrawRect:[webView convertRect:rect fromView:self]]; } [(WebClipView *)[self superview] resetAdditionalClip]; [NSGraphicsContext restoreGraphicsState]; } NS_HANDLER { [(WebClipView *)[self superview] resetAdditionalClip]; [NSGraphicsContext restoreGraphicsState]; LOG_ERROR("Exception caught while drawing: %@", localException); [localException raise]; } NS_ENDHANDLER } - (void)drawRect:(NSRect)rect { ASSERT_MAIN_THREAD(); LOG(View, "%@ drawing", self); const NSRect *rects; NSInteger count; [self getRectsBeingDrawn:&rects count:&count]; BOOL subviewsWereSetAside = _private->subviewsSetAside; if (subviewsWereSetAside) [self _restoreSubviews]; #ifdef _KWQ_TIMING double start = CFAbsoluteTimeGetCurrent(); #endif // If count == 0 here, use the rect passed in for drawing. This is a workaround for: // REGRESSION (Mail): No drag image dragging selected text in Blot and Mail // The reason for the workaround is that this method is called explicitly from the code // to generate a drag image, and at that time, getRectsBeingDrawn:count: will return a zero count. const int cRectThreshold = 10; const float cWastedSpaceThreshold = 0.75f; BOOL useUnionedRect = (count <= 1) || (count > cRectThreshold); if (!useUnionedRect) { // Attempt to guess whether or not we should use the unioned rect or the individual rects. // We do this by computing the percentage of "wasted space" in the union. If that wasted space // is too large, then we will do individual rect painting instead. float unionPixels = (rect.size.width * rect.size.height); float singlePixels = 0; for (int i = 0; i < count; ++i) singlePixels += rects[i].size.width * rects[i].size.height; float wastedSpace = 1 - (singlePixels / unionPixels); if (wastedSpace <= cWastedSpaceThreshold) useUnionedRect = YES; } if (useUnionedRect) [self drawSingleRect:rect]; else for (int i = 0; i < count; ++i) [self drawSingleRect:rects[i]]; #ifdef _KWQ_TIMING double thisTime = CFAbsoluteTimeGetCurrent() - start; LOG(Timing, "%s draw seconds = %f", widget->part()->baseURL().URL().latin1(), thisTime); #endif if (subviewsWereSetAside) [self _setAsideSubviews]; } // Turn off the additional clip while computing our visibleRect. - (NSRect)visibleRect { if (!([[self superview] isKindOfClass:[WebClipView class]])) return [super visibleRect]; WebClipView *clipView = (WebClipView *)[self superview]; BOOL hasAdditionalClip = [clipView hasAdditionalClip]; if (!hasAdditionalClip) { return [super visibleRect]; } NSRect additionalClip = [clipView additionalClip]; [clipView resetAdditionalClip]; NSRect visibleRect = [super visibleRect]; [clipView setAdditionalClip:additionalClip]; return visibleRect; } - (BOOL)isFlipped { return YES; } - (void)windowDidBecomeKey:(NSNotification *)notification { NSWindow *keyWindow = [notification object]; if (keyWindow == [self window]) [self addMouseMovedObserver]; if (keyWindow == [self window] || keyWindow == [[self window] attachedSheet]) [self _updateActiveState]; } - (void)windowDidResignKey:(NSNotification *)notification { NSWindow *formerKeyWindow = [notification object]; if (formerKeyWindow == [self window]) [self removeMouseMovedObserver]; if (formerKeyWindow == [self window] || formerKeyWindow == [[self window] attachedSheet]) { [self _updateActiveState]; [_private->compController endRevertingChange:NO moveLeft:NO]; } } - (void)windowWillClose:(NSNotification *)notification { [_private->compController endRevertingChange:NO moveLeft:NO]; [[self _pluginController] destroyAllPlugins]; } - (void)scrollWheel:(NSEvent *)event { [self retain]; Frame* frame = core([self _frame]); if (!frame || !frame->eventHandler()->wheelEvent(event)) [[self nextResponder] scrollWheel:event]; [self release]; } - (BOOL)_isSelectionEvent:(NSEvent *)event { NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil]; return [[[self elementAtPoint:point allowShadowContent:YES] objectForKey:WebElementIsSelectedKey] boolValue]; } - (BOOL)acceptsFirstMouse:(NSEvent *)event { NSView *hitView = [self _hitViewForEvent:event]; WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil; if ([[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysAcceptsFirstMouse]) return YES; if (hitHTMLView) { bool result = false; if (Frame* coreFrame = core([hitHTMLView _frame])) { coreFrame->eventHandler()->setActivationEventNumber([event eventNumber]); [hitHTMLView _setMouseDownEvent:event]; if ([hitHTMLView _isSelectionEvent:event]) result = coreFrame->eventHandler()->eventMayStartDrag(event); [hitHTMLView _setMouseDownEvent:nil]; } return result; } return [hitView acceptsFirstMouse:event]; } - (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)event { NSView *hitView = [self _hitViewForEvent:event]; WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil; if (hitHTMLView) { bool result = false; if ([hitHTMLView _isSelectionEvent:event]) if (Frame* coreFrame = core([hitHTMLView _frame])) { [hitHTMLView _setMouseDownEvent:event]; result = coreFrame->eventHandler()->eventMayStartDrag(event); [hitHTMLView _setMouseDownEvent:nil]; } return result; } return [hitView shouldDelayWindowOrderingForEvent:event]; } - (void)mouseDown:(NSEvent *)event { [self retain]; _private->handlingMouseDownEvent = YES; // Record the mouse down position so we can determine drag hysteresis. [self _setMouseDownEvent:event]; NSInputManager *currentInputManager = [NSInputManager currentInputManager]; if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) goto done; [_private->compController endRevertingChange:NO moveLeft:NO]; // If the web page handles the context menu event and menuForEvent: returns nil, we'll get control click events here. // We don't want to pass them along to KHTML a second time. if (!([event modifierFlags] & NSControlKeyMask)) { _private->ignoringMouseDraggedEvents = NO; // Don't do any mouseover while the mouse is down. [self _cancelUpdateMouseoverTimer]; // Let WebCore get a chance to deal with the event. This will call back to us // to start the autoscroll timer if appropriate. if (Frame* coreframe = core([self _frame])) coreframe->eventHandler()->mouseDown(event); } done: [_private->firstResponderTextViewAtMouseDownTime release]; _private->firstResponderTextViewAtMouseDownTime = nil; _private->handlingMouseDownEvent = NO; [self release]; } - (void)dragImage:(NSImage *)dragImage at:(NSPoint)at offset:(NSSize)offset event:(NSEvent *)event pasteboard:(NSPasteboard *)pasteboard source:(id)source slideBack:(BOOL)slideBack { ASSERT(self == [self _topHTMLView]); [super dragImage:dragImage at:at offset:offset event:event pasteboard:pasteboard source:source slideBack:slideBack]; } - (void)mouseDragged:(NSEvent *)event { NSInputManager *currentInputManager = [NSInputManager currentInputManager]; if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) return; [self retain]; if (!_private->ignoringMouseDraggedEvents) if (Frame* coreframe = core([self _frame])) coreframe->eventHandler()->mouseDragged(event); [self release]; } - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { ASSERT(![self _webView] || [self _isTopHTMLView]); Page *page = core([self _webView]); if (!page) return NSDragOperationNone; if (page->dragController()->dragOperation() == DragOperationNone) return NSDragOperationGeneric | NSDragOperationCopy; return (NSDragOperation)page->dragController()->dragOperation(); } - (void)draggedImage:(NSImage *)image movedTo:(NSPoint)screenLoc { ASSERT(![self _webView] || [self _isTopHTMLView]); NSPoint windowImageLoc = [[self window] convertScreenToBase:screenLoc]; NSPoint windowMouseLoc = windowImageLoc; if (Page* page = core([self _webView])) { DragController* dragController = page->dragController(); NSPoint windowMouseLoc = NSMakePoint(windowImageLoc.x + dragController->dragOffset().x(), windowImageLoc.y + dragController->dragOffset().y()); } [[self _bridge] dragSourceMovedTo:windowMouseLoc]; } - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation { ASSERT(![self _webView] || [self _isTopHTMLView]); NSPoint windowImageLoc = [[self window] convertScreenToBase:aPoint]; NSPoint windowMouseLoc = windowImageLoc; if (Page* page = core([self _webView])) { DragController* dragController = page->dragController(); windowMouseLoc = NSMakePoint(windowImageLoc.x + dragController->dragOffset().x(), windowImageLoc.y + dragController->dragOffset().y()); dragController->dragEnded(); } [[self _bridge] dragSourceEndedAt:windowMouseLoc operation:operation]; // Prevent queued mouseDragged events from coming after the drag and fake mouseUp event. _private->ignoringMouseDraggedEvents = YES; // Once the dragging machinery kicks in, we no longer get mouse drags or the up event. // WebCore expects to get balanced down/up's, so we must fake up a mouseup. NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp location:windowMouseLoc modifierFlags:[[NSApp currentEvent] modifierFlags] timestamp:[NSDate timeIntervalSinceReferenceDate] windowNumber:[[self window] windowNumber] context:[[NSApp currentEvent] context] eventNumber:0 clickCount:0 pressure:0]; [self mouseUp:fakeEvent]; // This will also update the mouseover state. } - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination { NSFileWrapper *wrapper = nil; if (WebCore::CachedResource* tiffResource = [self promisedDragTIFFDataSource]) { SharedBuffer *buffer = tiffResource->data(); if (!buffer) goto noPromisedData; NSData *data = buffer->createNSData(); NSURLResponse *response = tiffResource->response().nsURLResponse(); wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:data] autorelease]; [wrapper setPreferredFilename:[response suggestedFilename]]; } noPromisedData: if (!wrapper) { ASSERT(![self _webView] || [self _isTopHTMLView]); Page* page = core([self _webView]); //If a load occurs midway through a drag, the view may be detached, which gives //us no ability to get to the original Page, so we cannot access any drag state //FIXME: is there a way to recover? if (!page) return nil; KURL imageURL = page->dragController()->draggingImageURL(); ASSERT(!imageURL.isEmpty()); wrapper = [[self _dataSource] _fileWrapperForURL:imageURL.getNSURL()]; } if (wrapper == nil) { LOG_ERROR("Failed to create image file."); return nil; } // FIXME: Report an error if we fail to create a file. NSString *path = [[dropDestination path] stringByAppendingPathComponent:[wrapper preferredFilename]]; path = [[NSFileManager defaultManager] _webkit_pathWithUniqueFilenameForPath:path]; if (![wrapper writeToFile:path atomically:NO updateFilenames:YES]) LOG_ERROR("Failed to create image file via -[NSFileWrapper writeToFile:atomically:updateFilenames:]"); return [NSArray arrayWithObject:[path lastPathComponent]]; } - (void)mouseUp:(NSEvent *)event { [self _setMouseDownEvent:nil]; NSInputManager *currentInputManager = [NSInputManager currentInputManager]; if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) return; [self retain]; [self _stopAutoscrollTimer]; if (Frame* coreframe = core([self _frame])) coreframe->eventHandler()->mouseUp(event); [self _updateMouseoverWithFakeEvent]; [self release]; } - (void)mouseMovedNotification:(NSNotification *)notification { [self _updateMouseoverWithEvent:[[notification userInfo] objectForKey:@"NSEvent"]]; } // returning YES from this method is the way we tell AppKit that it is ok for this view // to be in the key loop even when "tab to all controls" is not on. - (BOOL)needsPanelToBecomeKey { return YES; } - (BOOL)becomeFirstResponder { NSSelectionDirection direction = NSDirectSelection; if (![[self _webView] _isPerformingProgrammaticFocus] && !_private->willBecomeFirstResponderForNodeFocus) direction = [[self window] keyViewSelectionDirection]; _private->willBecomeFirstResponderForNodeFocus = NO; [self _updateActiveState]; [self _updateFontPanel]; Frame* frame = core([self _frame]); if (!frame) return YES; frame->editor()->setStartNewKillRingSequence(true); if (direction == NSDirectSelection) return YES; Page* page = frame->page(); if (!page) return YES; page->focusController()->setFocusedFrame(frame); if (Document* document = frame->document()) document->setFocusedNode(0); page->focusController()->setInitialFocus(frame->eventHandler()->currentKeyboardEvent().get()); return YES; } - (BOOL)resignFirstResponder { BOOL resign = [super resignFirstResponder]; if (resign) { [_private->compController endRevertingChange:NO moveLeft:NO]; _private->resigningFirstResponder = YES; if (![self maintainsInactiveSelection]) { [self deselectAll]; if (![[self _webView] _isPerformingProgrammaticFocus]) [self clearFocus]; } [self _updateActiveState]; _private->resigningFirstResponder = NO; _private->willBecomeFirstResponderForNodeFocus = NO; } return resign; } - (void)setDataSource:(WebDataSource *)dataSource { ASSERT(dataSource); if (_private->dataSource != dataSource) { ASSERT(!_private->dataSource); ASSERT(!_private->closed); _private->dataSource = [dataSource retain]; [_private->pluginController setDataSource:dataSource]; [self addMouseMovedObserver]; } } - (void)dataSourceUpdated:(WebDataSource *)dataSource { } // This is an override of an NSControl method that wants to repaint the entire view when the window resigns/becomes // key. WebHTMLView is an NSControl only because it hosts NSCells that are painted by WebCore's Aqua theme // renderer (and those cells must be hosted by an enclosing NSControl in order to paint properly). - (void)updateCell:(NSCell*)cell { } // Does setNeedsDisplay:NO as a side effect when printing is ending. // pageWidth != 0 implies we will relayout to a new width - (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize { WebFrame *frame = [self _frame]; NSArray *subframes = [frame childFrames]; unsigned n = [subframes count]; unsigned i; for (i = 0; i != n; ++i) { WebFrame *subframe = [subframes objectAtIndex:i]; WebFrameView *frameView = [subframe frameView]; if ([[subframe _dataSource] _isDocumentHTML]) { [(WebHTMLView *)[frameView documentView] _setPrinting:printing minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:adjustViewSize]; } } if (printing != _private->printing) { [_private->pageRects release]; _private->pageRects = nil; _private->printing = printing; if (!printing) _private->avoidingPrintOrphan = NO; [self setNeedsToApplyStyles:YES]; [self setNeedsLayout:YES]; [self layoutToMinimumPageWidth:minPageWidth maximumPageWidth:maxPageWidth adjustingViewSize:adjustViewSize]; if (!printing) { // Can't do this when starting printing or nested printing won't work, see 3491427. [self setNeedsDisplay:NO]; } } } - (BOOL)canPrintHeadersAndFooters { return YES; } // This is needed for the case where the webview is embedded in the view that's being printed. // It shouldn't be called when the webview is being printed directly. - (void)adjustPageHeightNew:(float *)newBottom top:(float)oldTop bottom:(float)oldBottom limit:(float)bottomLimit { // This helps when we print as part of a larger print process. // If the WebHTMLView itself is what we're printing, then we will never have to do this. BOOL wasInPrintingMode = _private->printing; if (!wasInPrintingMode) [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; [[self _bridge] adjustPageHeightNew:newBottom top:oldTop bottom:oldBottom limit:bottomLimit]; if (!wasInPrintingMode) { NSPrintOperation *currenPrintOperation = [NSPrintOperation currentOperation]; if (currenPrintOperation) // delay _setPrinting:NO until back to main loop as this method may get called repeatedly [self performSelector:@selector(_delayedEndPrintMode:) withObject:currenPrintOperation afterDelay:0]; else // not sure if this is actually ever invoked, it probably shouldn't be [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; } } - (float)_availablePaperWidthForPrintOperation:(NSPrintOperation *)printOperation { NSPrintInfo *printInfo = [printOperation printInfo]; return [printInfo paperSize].width - [printInfo leftMargin] - [printInfo rightMargin]; } - (float)_scaleFactorForPrintOperation:(NSPrintOperation *)printOperation { float viewWidth = NSWidth([self bounds]); if (viewWidth < 1) { LOG_ERROR("%@ has no width when printing", self); return 1.0f; } float userScaleFactor = [printOperation _web_pageSetupScaleFactor]; float maxShrinkToFitScaleFactor = 1.0f / PrintingMaximumShrinkFactor; float shrinkToFitScaleFactor = [self _availablePaperWidthForPrintOperation:printOperation]/viewWidth; float shrinkToAvoidOrphan = _private->avoidingPrintOrphan ? (1.0f / PrintingOrphanShrinkAdjustment) : 1.0f; return userScaleFactor * MAX(maxShrinkToFitScaleFactor, shrinkToFitScaleFactor) * shrinkToAvoidOrphan; } // FIXME 3491344: This is a secret AppKit-internal method that we need to override in order // to get our shrink-to-fit to work with a custom pagination scheme. We can do this better // if AppKit makes it SPI/API. - (float)_provideTotalScaleFactorForPrintOperation:(NSPrintOperation *)printOperation { return [self _scaleFactorForPrintOperation:printOperation]; } // This is used for Carbon printing. At some point we might want to make this public API. - (void)setPageWidthForPrinting:(float)pageWidth { [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; [self _setPrinting:YES minimumPageWidth:pageWidth maximumPageWidth:pageWidth adjustViewSize:YES]; } - (void)_endPrintMode { [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:YES]; [[self window] setAutodisplay:YES]; } - (void)_delayedEndPrintMode:(NSPrintOperation *)initiatingOperation { ASSERT_ARG(initiatingOperation, initiatingOperation != nil); NSPrintOperation *currentOperation = [NSPrintOperation currentOperation]; if (initiatingOperation == currentOperation) { // The print operation is still underway. We don't expect this to ever happen, hence the assert, but we're // being extra paranoid here since the printing code is so fragile. Delay the cleanup // further. ASSERT_NOT_REACHED(); [self performSelector:@selector(_delayedEndPrintMode:) withObject:initiatingOperation afterDelay:0]; } else if ([currentOperation view] == self) { // A new print job has started, but it is printing the same WebHTMLView again. We don't expect // this to ever happen, hence the assert, but we're being extra paranoid here since the printing code is so // fragile. Do nothing, because we don't want to break the print job currently in progress, and // the print job currently in progress is responsible for its own cleanup. ASSERT_NOT_REACHED(); } else { // The print job that kicked off this delayed call has finished, and this view is not being // printed again. We expect that no other print job has started. Since this delayed call wasn't // cancelled, beginDocument and endDocument must not have been called, and we need to clean up // the print mode here. ASSERT(currentOperation == nil); [self _endPrintMode]; } } // Return the number of pages available for printing - (BOOL)knowsPageRange:(NSRangePointer)range { // Must do this explicit display here, because otherwise the view might redisplay while the print // sheet was up, using printer fonts (and looking different). [self displayIfNeeded]; [[self window] setAutodisplay:NO]; // If we are a frameset just print with the layout we have onscreen, otherwise relayout // according to the paper size float minLayoutWidth = 0.0f; float maxLayoutWidth = 0.0f; Frame* frame = core([self _frame]); if (!frame) return NO; if (!frame->isFrameSet()) { float paperWidth = [self _availablePaperWidthForPrintOperation:[NSPrintOperation currentOperation]]; minLayoutWidth = paperWidth * PrintingMinimumShrinkFactor; maxLayoutWidth = paperWidth * PrintingMaximumShrinkFactor; } [self _setPrinting:YES minimumPageWidth:minLayoutWidth maximumPageWidth:maxLayoutWidth adjustViewSize:YES]; // will relayout NSPrintOperation *printOperation = [NSPrintOperation currentOperation]; // Certain types of errors, including invalid page ranges, can cause beginDocument and // endDocument to be skipped after we've put ourselves in print mode (see 4145905). In those cases // we need to get out of print mode without relying on any more callbacks from the printing mechanism. // If we get as far as beginDocument without trouble, then this delayed request will be cancelled. // If not cancelled, this delayed call will be invoked in the next pass through the main event loop, // which is after beginDocument and endDocument would be called. [self performSelector:@selector(_delayedEndPrintMode:) withObject:printOperation afterDelay:0]; [[self _webView] _adjustPrintingMarginsForHeaderAndFooter]; // There is a theoretical chance that someone could do some drawing between here and endDocument, // if something caused setNeedsDisplay after this point. If so, it's not a big tragedy, because // you'd simply see the printer fonts on screen. As of this writing, this does not happen with Safari. range->location = 1; float totalScaleFactor = [self _scaleFactorForPrintOperation:printOperation]; float userScaleFactor = [printOperation _web_pageSetupScaleFactor]; [_private->pageRects release]; float fullPageHeight = floorf([self _calculatePrintHeight]/totalScaleFactor); NSArray *newPageRects = [[self _bridge] computePageRectsWithPrintWidthScaleFactor:userScaleFactor printHeight:fullPageHeight]; // AppKit gets all messed up if you give it a zero-length page count (see 3576334), so if we // hit that case we'll pass along a degenerate 1 pixel square to print. This will print // a blank page (with correct-looking header and footer if that option is on), which matches // the behavior of IE and Camino at least. if ([newPageRects count] == 0) newPageRects = [NSArray arrayWithObject:[NSValue valueWithRect:NSMakeRect(0, 0, 1, 1)]]; else if ([newPageRects count] > 1) { // If the last page is a short orphan, try adjusting the print height slightly to see if this will squeeze the // content onto one fewer page. If it does, use the adjusted scale. If not, use the original scale. float lastPageHeight = NSHeight([[newPageRects lastObject] rectValue]); if (lastPageHeight/fullPageHeight < LastPrintedPageOrphanRatio) { NSArray *adjustedPageRects = [[self _bridge] computePageRectsWithPrintWidthScaleFactor:userScaleFactor printHeight:fullPageHeight*PrintingOrphanShrinkAdjustment]; // Use the adjusted rects only if the page count went down if ([adjustedPageRects count] < [newPageRects count]) { newPageRects = adjustedPageRects; _private->avoidingPrintOrphan = YES; } } } _private->pageRects = [newPageRects retain]; range->length = [_private->pageRects count]; return YES; } // Return the drawing rectangle for a particular page number - (NSRect)rectForPage:(int)page { return [[_private->pageRects objectAtIndex:page - 1] rectValue]; } - (void)drawPageBorderWithSize:(NSSize)borderSize { ASSERT(NSEqualSizes(borderSize, [[[NSPrintOperation currentOperation] printInfo] paperSize])); [[self _webView] _drawHeaderAndFooter]; } - (void)beginDocument { NS_DURING // From now on we'll get a chance to call _endPrintMode in either beginDocument or // endDocument, so we can cancel the "just in case" pending call. [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_delayedEndPrintMode:) object:[NSPrintOperation currentOperation]]; [super beginDocument]; NS_HANDLER // Exception during [super beginDocument] means that endDocument will not get called, // so we need to clean up our "print mode" here. [self _endPrintMode]; NS_ENDHANDLER } - (void)endDocument { [super endDocument]; // Note sadly at this point [NSGraphicsContext currentContextDrawingToScreen] is still NO [self _endPrintMode]; } - (void)keyDown:(NSEvent *)event { RetainPtr selfProtector = self; BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); BOOL callSuper = NO; [_private->keyDownEvent release]; _private->keyDownEvent = [event retain]; BOOL completionPopupWasOpen = _private->compController && [_private->compController popupWindowIsOpen]; if (!eventWasSentToWebCore && core([self _frame])->eventHandler()->keyEvent(event)) { // WebCore processed a key event, bail on any preexisting complete: UI if (completionPopupWasOpen) [_private->compController endRevertingChange:YES moveLeft:NO]; } else if (!_private->compController || ![_private->compController filterKeyDown:event]) { // Not consumed by complete: popup window [_private->compController endRevertingChange:YES moveLeft:NO]; callSuper = YES; } if (callSuper) [super keyDown:event]; else [NSCursor setHiddenUntilMouseMoves:YES]; } - (void)keyUp:(NSEvent *)event { BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); [self retain]; if (eventWasSentToWebCore || !core([self _frame])->eventHandler()->keyEvent(event)) [super keyUp:event]; [self release]; } - (id)accessibilityAttributeValue:(NSString*)attributeName { if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) { id accTree = [[self _bridge] accessibilityTree]; if (accTree) return [NSArray arrayWithObject:accTree]; return nil; } return [super accessibilityAttributeValue:attributeName]; } - (id)accessibilityFocusedUIElement { id accTree = [[self _bridge] accessibilityTree]; if (accTree) return [accTree accessibilityFocusedUIElement]; return self; } - (id)accessibilityHitTest:(NSPoint)point { id accTree = [[self _bridge] accessibilityTree]; if (accTree) { NSPoint windowCoord = [[self window] convertScreenToBase:point]; return [accTree accessibilityHitTest:[self convertPoint:windowCoord fromView:nil]]; } return self; } - (id)_accessibilityParentForSubview:(NSView *)subview { id accTree = [[self _bridge] accessibilityTree]; if (!accTree) return self; id parent = [accTree _accessibilityParentForSubview:subview]; if (!parent) return self; return parent; } - (void)centerSelectionInVisibleArea:(id)sender { COMMAND_PROLOGUE if (Frame* coreFrame = core([self _frame])) coreFrame->revealSelection(RenderLayer::gAlignCenterAlways); } - (void)pageUp:(id)sender { COMMAND_PROLOGUE WebFrameView *frameView = [self _frameView]; if (!frameView) return; if ([self _canAlterCurrentSelection]) [[self _bridge] alterCurrentSelection:SelectionController::MOVE verticalDistance:-[frameView _verticalPageScrollDistance]]; } - (void)pageDown:(id)sender { COMMAND_PROLOGUE WebFrameView *frameView = [self _frameView]; if (!frameView) return; if ([self _canAlterCurrentSelection]) [[self _bridge] alterCurrentSelection:SelectionController::MOVE verticalDistance:[frameView _verticalPageScrollDistance]]; } - (void)pageUpAndModifySelection:(id)sender { COMMAND_PROLOGUE WebFrameView *frameView = [self _frameView]; if (!frameView) return; if ([self _canAlterCurrentSelection]) [[self _bridge] alterCurrentSelection:SelectionController::EXTEND verticalDistance:-[frameView _verticalPageScrollDistance]]; } - (void)pageDownAndModifySelection:(id)sender { COMMAND_PROLOGUE WebFrameView *frameView = [self _frameView]; if (frameView == nil) return; if ([self _canAlterCurrentSelection]) [[self _bridge] alterCurrentSelection:SelectionController::EXTEND verticalDistance:[frameView _verticalPageScrollDistance]]; } - (void)_expandSelectionToGranularity:(TextGranularity)granularity { if (![self _canAlterCurrentSelection]) return; Frame* coreFrame = core([self _frame]); if (!coreFrame || !coreFrame->selectionController()->isCaretOrRange()) return; // NOTE: The enums *must* match the very similar ones declared in SelectionController.h Selection selection(coreFrame->selectionController()->selection()); selection.expandUsingGranularity(static_cast(granularity)); RefPtr range = selection.toRange(); DOMRange *domRange = kit(range.get()); if ([domRange collapsed]) return; EAffinity affinity = coreFrame->selectionController()->affinity(); WebView *webView = [self _webView]; if ([[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[self _selectedRange] toDOMRange:domRange affinity:kit(affinity) stillSelecting:NO]) { ExceptionCode ec = 0; coreFrame->selectionController()->setSelectedRange(range.get(), affinity, true, ec); } } - (void)selectParagraph:(id)sender { COMMAND_PROLOGUE [self _expandSelectionToGranularity:ParagraphGranularity]; } - (void)selectLine:(id)sender { COMMAND_PROLOGUE [self _expandSelectionToGranularity:LineGranularity]; } - (void)selectSentence:(id)sender { COMMAND_PROLOGUE [self _expandSelectionToGranularity:SentenceGranularity]; } - (void)selectWord:(id)sender { COMMAND_PROLOGUE [self _expandSelectionToGranularity:WordGranularity]; } - (void)delete:(id)sender { COMMAND_PROLOGUE if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->performDelete(); } - (NSData *)_selectionStartFontAttributesAsRTF { NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"x" attributes:core([self _frame])->fontAttributesForSelectionStart()]; NSData *data = [string RTFFromRange:NSMakeRange(0, [string length]) documentAttributes:nil]; [string release]; return data; } - (NSDictionary *)_fontAttributesFromFontPasteboard { NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard]; if (fontPasteboard == nil) return nil; NSData *data = [fontPasteboard dataForType:NSFontPboardType]; if (data == nil || [data length] == 0) return nil; // NSTextView does something more efficient by parsing the attributes only, but that's not available in API. NSAttributedString *string = [[[NSAttributedString alloc] initWithRTF:data documentAttributes:NULL] autorelease]; if (string == nil || [string length] == 0) return nil; return [string fontAttributesInRange:NSMakeRange(0, 1)]; } - (DOMCSSStyleDeclaration *)_emptyStyle { return [[[self _frame] DOMDocument] createCSSStyleDeclaration]; } - (NSString *)_colorAsString:(NSColor *)color { NSColor *rgbColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; // FIXME: If color is non-nil and rgbColor is nil, that means we got some kind // of fancy color that can't be converted to RGB. Changing that to "transparent" // might not be great, but it's probably OK. if (rgbColor == nil) return @"transparent"; float r = [rgbColor redComponent]; float g = [rgbColor greenComponent]; float b = [rgbColor blueComponent]; float a = [rgbColor alphaComponent]; if (a == 0) return @"transparent"; if (r == 0 && g == 0 && b == 0 && a == 1) return @"black"; if (r == 1 && g == 1 && b == 1 && a == 1) return @"white"; // FIXME: Lots more named colors. Maybe we could use the table in WebCore? if (a == 1) return [NSString stringWithFormat:@"rgb(%.0f,%.0f,%.0f)", r * 255, g * 255, b * 255]; return [NSString stringWithFormat:@"rgba(%.0f,%.0f,%.0f,%f)", r * 255, g * 255, b * 255, a]; } - (NSString *)_shadowAsString:(NSShadow *)shadow { if (shadow == nil) return @"none"; NSSize offset = [shadow shadowOffset]; float blurRadius = [shadow shadowBlurRadius]; if (offset.width == 0 && offset.height == 0 && blurRadius == 0) return @"none"; NSColor *color = [shadow shadowColor]; if (color == nil) return @"none"; // FIXME: Handle non-integral values here? if (blurRadius == 0) return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height]; return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height, blurRadius]; } - (DOMCSSStyleDeclaration *)_styleFromFontAttributes:(NSDictionary *)dictionary { DOMCSSStyleDeclaration *style = [self _emptyStyle]; NSColor *color = [dictionary objectForKey:NSBackgroundColorAttributeName]; [style setBackgroundColor:[self _colorAsString:color]]; NSFont *font = [dictionary objectForKey:NSFontAttributeName]; if (font == nil) { [style setFontFamily:@"Helvetica"]; [style setFontSize:@"12px"]; [style setFontWeight:@"normal"]; [style setFontStyle:@"normal"]; } else { NSFontManager *fm = [NSFontManager sharedFontManager]; // FIXME: Need more sophisticated escaping code if we want to handle family names // with characters like single quote or backslash in their names. [style setFontFamily:[NSString stringWithFormat:@"'%@'", [font familyName]]]; [style setFontSize:[NSString stringWithFormat:@"%0.fpx", [font pointSize]]]; if ([fm weightOfFont:font] >= MIN_BOLD_WEIGHT) [style setFontWeight:@"bold"]; else [style setFontWeight:@"normal"]; if (([fm traitsOfFont:font] & NSItalicFontMask) != 0) [style setFontStyle:@"italic"]; else [style setFontStyle:@"normal"]; } color = [dictionary objectForKey:NSForegroundColorAttributeName]; [style setColor:color ? [self _colorAsString:color] : (NSString *)@"black"]; NSShadow *shadow = [dictionary objectForKey:NSShadowAttributeName]; [style setTextShadow:[self _shadowAsString:shadow]]; int strikethroughInt = [[dictionary objectForKey:NSStrikethroughStyleAttributeName] intValue]; int superscriptInt = [[dictionary objectForKey:NSSuperscriptAttributeName] intValue]; if (superscriptInt > 0) [style setVerticalAlign:@"super"]; else if (superscriptInt < 0) [style setVerticalAlign:@"sub"]; else [style setVerticalAlign:@"baseline"]; int underlineInt = [[dictionary objectForKey:NSUnderlineStyleAttributeName] intValue]; // FIXME: Underline wins here if we have both (see bug 3790443). if (strikethroughInt == NSUnderlineStyleNone && underlineInt == NSUnderlineStyleNone) [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""]; else if (underlineInt == NSUnderlineStyleNone) [style setProperty:@"-khtml-text-decorations-in-effect" value:@"line-through" priority:@""]; else [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""]; return style; } - (void)_applyStyleToSelection:(DOMCSSStyleDeclaration *)style withUndoAction:(EditAction)undoAction { if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->applyStyleToSelection(core(style), undoAction); } - (void)_applyParagraphStyleToSelection:(DOMCSSStyleDeclaration *)style withUndoAction:(EditAction)undoAction { if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->applyParagraphStyleToSelection(core(style), undoAction); } - (void)_toggleBold { if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->execCommand("ToggleBold"); } - (void)_toggleItalic { if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->execCommand("ToggleItalic"); } - (BOOL)_handleStyleKeyEquivalent:(NSEvent *)event { ASSERT([self _webView]); if (![[[self _webView] preferences] respectStandardStyleKeyEquivalents]) return NO; if (![self _canEdit]) return NO; if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) != NSCommandKeyMask) return NO; NSString *string = [event characters]; if ([string caseInsensitiveCompare:@"b"] == NSOrderedSame) { [self _toggleBold]; return YES; } if ([string caseInsensitiveCompare:@"i"] == NSOrderedSame) { [self _toggleItalic]; return YES; } return NO; } - (BOOL)performKeyEquivalent:(NSEvent *)event { if ([self _handleStyleKeyEquivalent:event]) return YES; BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); BOOL ret = NO; [_private->keyDownEvent release]; _private->keyDownEvent = [event retain]; [self retain]; // Pass command-key combos through WebCore if there is a key binding available for // this event. This lets web pages have a crack at intercepting command-modified keypresses. // But don't do it if we have already handled the event. // Pressing Esc results in a fake event being sent - don't pass it to WebCore. if (!eventWasSentToWebCore && event == [NSApp currentEvent] && self == [[self window] firstResponder]) if (Frame* frame = core([self _frame])) ret = frame->eventHandler()->keyEvent(event); if (!ret) ret = [super performKeyEquivalent:event]; [self release]; return ret; } - (void)copyFont:(id)sender { COMMAND_PROLOGUE // Put RTF with font attributes on the pasteboard. // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font. NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard]; [fontPasteboard declareTypes:[NSArray arrayWithObject:NSFontPboardType] owner:nil]; [fontPasteboard setData:[self _selectionStartFontAttributesAsRTF] forType:NSFontPboardType]; } - (void)pasteFont:(id)sender { COMMAND_PROLOGUE // Read RTF with font attributes from the pasteboard. // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font. [self _applyStyleToSelection:[self _styleFromFontAttributes:[self _fontAttributesFromFontPasteboard]] withUndoAction:EditActionPasteFont]; } - (void)pasteAsRichText:(id)sender { COMMAND_PROLOGUE // Since rich text always beats plain text when both are on the pasteboard, it's not // clear how this is different from plain old paste. [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:NO]; } - (NSFont *)_originalFontA { return [[NSFontManager sharedFontManager] fontWithFamily:@"Helvetica" traits:0 weight:STANDARD_WEIGHT size:10.0f]; } - (NSFont *)_originalFontB { return [[NSFontManager sharedFontManager] fontWithFamily:@"Times" traits:(NSBoldFontMask | NSItalicFontMask) weight:STANDARD_BOLD_WEIGHT size:12.0f]; } - (void)_addToStyle:(DOMCSSStyleDeclaration *)style fontA:(NSFont *)a fontB:(NSFont *)b { // Since there's no way to directly ask NSFontManager what style change it's going to do // we instead pass two "specimen" fonts to it and let it change them. We then deduce what // style change it was doing by looking at what happened to each of the two fonts. // So if it was making the text bold, both fonts will be bold after the fact. if (a == nil || b == nil) return; NSFontManager *fm = [NSFontManager sharedFontManager]; NSFont *oa = [self _originalFontA]; NSString *aFamilyName = [a familyName]; NSString *bFamilyName = [b familyName]; int aPointSize = (int)[a pointSize]; int bPointSize = (int)[b pointSize]; int aWeight = [fm weightOfFont:a]; int bWeight = [fm weightOfFont:b]; BOOL aIsBold = aWeight >= MIN_BOLD_WEIGHT; BOOL aIsItalic = ([fm traitsOfFont:a] & NSItalicFontMask) != 0; BOOL bIsItalic = ([fm traitsOfFont:b] & NSItalicFontMask) != 0; if ([aFamilyName isEqualToString:bFamilyName]) { NSString *familyNameForCSS = aFamilyName; // The family name may not be specific enough to get us the font specified. // In some cases, the only way to get exactly what we are looking for is to use // the Postscript name. // Find the font the same way the rendering code would later if it encountered this CSS. NSFontTraitMask traits = 0; if (aIsBold) traits |= NSBoldFontMask; if (aIsItalic) traits |= NSItalicFontMask; NSFont *foundFont = WebCoreFindFont(aFamilyName, traits, aPointSize); // If we don't find a font with the same Postscript name, then we'll have to use the // Postscript name to make the CSS specific enough. if (![[foundFont fontName] isEqualToString:[a fontName]]) { familyNameForCSS = [a fontName]; } // FIXME: Need more sophisticated escaping code if we want to handle family names // with characters like single quote or backslash in their names. [style setFontFamily:[NSString stringWithFormat:@"'%@'", familyNameForCSS]]; } int soa = (int)[oa pointSize]; if (aPointSize == bPointSize) [style setFontSize:[NSString stringWithFormat:@"%dpx", aPointSize]]; else if (aPointSize < soa) [style _setFontSizeDelta:@"-1px"]; else if (aPointSize > soa) [style _setFontSizeDelta:@"1px"]; if (aWeight == bWeight) [style setFontWeight:aIsBold ? @"bold" : @"normal"]; if (aIsItalic == bIsItalic) [style setFontStyle:aIsItalic ? @"italic" : @"normal"]; } - (DOMCSSStyleDeclaration *)_styleFromFontManagerOperation { DOMCSSStyleDeclaration *style = [self _emptyStyle]; NSFontManager *fm = [NSFontManager sharedFontManager]; NSFont *oa = [self _originalFontA]; NSFont *ob = [self _originalFontB]; [self _addToStyle:style fontA:[fm convertFont:oa] fontB:[fm convertFont:ob]]; return style; } - (void)changeFont:(id)sender { COMMAND_PROLOGUE [self _applyStyleToSelection:[self _styleFromFontManagerOperation] withUndoAction:EditActionSetFont]; } - (DOMCSSStyleDeclaration *)_styleForAttributeChange:(id)sender { DOMCSSStyleDeclaration *style = [self _emptyStyle]; NSShadow *shadow = [[NSShadow alloc] init]; [shadow setShadowOffset:NSMakeSize(1, 1)]; NSDictionary *oa = [NSDictionary dictionaryWithObjectsAndKeys: [self _originalFontA], NSFontAttributeName, nil]; NSDictionary *ob = [NSDictionary dictionaryWithObjectsAndKeys: [NSColor blackColor], NSBackgroundColorAttributeName, [self _originalFontB], NSFontAttributeName, [NSColor whiteColor], NSForegroundColorAttributeName, shadow, NSShadowAttributeName, [NSNumber numberWithInt:NSUnderlineStyleSingle], NSStrikethroughStyleAttributeName, [NSNumber numberWithInt:1], NSSuperscriptAttributeName, [NSNumber numberWithInt:NSUnderlineStyleSingle], NSUnderlineStyleAttributeName, nil]; [shadow release]; #if 0 NSObliquenessAttributeName /* float; skew to be applied to glyphs, default 0: no skew */ // font-style, but that is just an on-off switch NSExpansionAttributeName /* float; log of expansion factor to be applied to glyphs, default 0: no expansion */ // font-stretch? NSKernAttributeName /* float, amount to modify default kerning, if 0, kerning off */ // letter-spacing? probably not good enough NSUnderlineColorAttributeName /* NSColor, default nil: same as foreground color */ NSStrikethroughColorAttributeName /* NSColor, default nil: same as foreground color */ // text-decoration-color? NSLigatureAttributeName /* int, default 1: default ligatures, 0: no ligatures, 2: all ligatures */ NSBaselineOffsetAttributeName /* float, in points; offset from baseline, default 0 */ NSStrokeWidthAttributeName /* float, in percent of font point size, default 0: no stroke; positive for stroke alone, negative for stroke and fill (a typical value for outlined text would be 3.0) */ NSStrokeColorAttributeName /* NSColor, default nil: same as foreground color */ // need extensions? #endif NSDictionary *a = [sender convertAttributes:oa]; NSDictionary *b = [sender convertAttributes:ob]; NSColor *ca = [a objectForKey:NSBackgroundColorAttributeName]; NSColor *cb = [b objectForKey:NSBackgroundColorAttributeName]; if (ca == cb) { [style setBackgroundColor:[self _colorAsString:ca]]; } [self _addToStyle:style fontA:[a objectForKey:NSFontAttributeName] fontB:[b objectForKey:NSFontAttributeName]]; ca = [a objectForKey:NSForegroundColorAttributeName]; cb = [b objectForKey:NSForegroundColorAttributeName]; if (ca == cb) { [style setColor:[self _colorAsString:ca]]; } NSShadow *sha = [a objectForKey:NSShadowAttributeName]; if (sha) [style setTextShadow:[self _shadowAsString:sha]]; else if ([b objectForKey:NSShadowAttributeName] == nil) [style setTextShadow:@"none"]; int sa = [[a objectForKey:NSStrikethroughStyleAttributeName] intValue]; int sb = [[b objectForKey:NSStrikethroughStyleAttributeName] intValue]; if (sa == sb) { if (sa == NSUnderlineStyleNone) [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""]; // we really mean "no line-through" rather than "none" else [style setProperty:@"-khtml-text-decorations-in-effect" value:@"line-through" priority:@""]; // we really mean "add line-through" rather than "line-through" } sa = [[a objectForKey:NSSuperscriptAttributeName] intValue]; sb = [[b objectForKey:NSSuperscriptAttributeName] intValue]; if (sa == sb) { if (sa > 0) [style setVerticalAlign:@"super"]; else if (sa < 0) [style setVerticalAlign:@"sub"]; else [style setVerticalAlign:@"baseline"]; } int ua = [[a objectForKey:NSUnderlineStyleAttributeName] intValue]; int ub = [[b objectForKey:NSUnderlineStyleAttributeName] intValue]; if (ua == ub) { if (ua == NSUnderlineStyleNone) [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""]; // we really mean "no underline" rather than "none" else [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""]; // we really mean "add underline" rather than "underline" } return style; } - (void)changeAttributes:(id)sender { COMMAND_PROLOGUE [self _applyStyleToSelection:[self _styleForAttributeChange:sender] withUndoAction:EditActionChangeAttributes]; } - (DOMCSSStyleDeclaration *)_styleFromColorPanelWithSelector:(SEL)selector { DOMCSSStyleDeclaration *style = [self _emptyStyle]; ASSERT([style respondsToSelector:selector]); [style performSelector:selector withObject:[self _colorAsString:[[NSColorPanel sharedColorPanel] color]]]; return style; } - (EditAction)_undoActionFromColorPanelWithSelector:(SEL)selector { if (selector == @selector(setBackgroundColor:)) return EditActionSetBackgroundColor; return EditActionSetColor; } - (void)_changeCSSColorUsingSelector:(SEL)selector inRange:(DOMRange *)range { DOMCSSStyleDeclaration *style = [self _styleFromColorPanelWithSelector:selector]; WebView *webView = [self _webView]; if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:range]) core([self _frame])->editor()->applyStyle(core(style), [self _undoActionFromColorPanelWithSelector:selector]); } - (void)changeDocumentBackgroundColor:(id)sender { COMMAND_PROLOGUE // Mimicking NSTextView, this method sets the background color for the // entire document. There is no NSTextView API for setting the background // color on the selected range only. Note that this method is currently // never called from the UI (see comment in changeColor:). // FIXME: this actually has no effect when called, probably due to 3654850. _documentRange seems // to do the right thing because it works in startSpeaking:, and I know setBackgroundColor: does the // right thing because I tested it with [self _selectedRange]. // FIXME: This won't actually apply the style to the entire range here, because it ends up calling // [bridge applyStyle:], which operates on the current selection. To make this work right, we'll // need to save off the selection, temporarily set it to the entire range, make the change, then // restore the old selection. [self _changeCSSColorUsingSelector:@selector(setBackgroundColor:) inRange:[self _documentRange]]; } - (void)changeColor:(id)sender { COMMAND_PROLOGUE // FIXME: in NSTextView, this method calls changeDocumentBackgroundColor: when a // private call has earlier been made by [NSFontFontEffectsBox changeColor:], see 3674493. // AppKit will have to be revised to allow this to work with anything that isn't an // NSTextView. However, this might not be required for Tiger, since the background-color // changing box in the font panel doesn't work in Mail (3674481), though it does in TextEdit. [self _applyStyleToSelection:[self _styleFromColorPanelWithSelector:@selector(setColor:)] withUndoAction:EditActionSetColor]; } - (void)_alignSelectionUsingCSSValue:(NSString *)CSSAlignmentValue withUndoAction:(EditAction)undoAction { if (![self _canEditRichly]) return; DOMCSSStyleDeclaration *style = [self _emptyStyle]; [style setTextAlign:CSSAlignmentValue]; [self _applyStyleToSelection:style withUndoAction:undoAction]; } - (void)alignCenter:(id)sender { COMMAND_PROLOGUE [self _alignSelectionUsingCSSValue:@"center" withUndoAction:EditActionCenter]; } - (void)alignJustified:(id)sender { COMMAND_PROLOGUE [self _alignSelectionUsingCSSValue:@"justify" withUndoAction:EditActionJustify]; } - (void)alignLeft:(id)sender { COMMAND_PROLOGUE [self _alignSelectionUsingCSSValue:@"left" withUndoAction:EditActionAlignLeft]; } - (void)alignRight:(id)sender { COMMAND_PROLOGUE [self _alignSelectionUsingCSSValue:@"right" withUndoAction:EditActionAlignRight]; } - (void)insertParagraphSeparator:(id)sender { COMMAND_PROLOGUE if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->execCommand("InsertNewline"); } - (void)_changeWordCaseWithSelector:(SEL)selector { if (![self _canEdit]) return; WebFrameBridge *bridge = [self _bridge]; [self selectWord:nil]; NSString *word = [[bridge selectedString] performSelector:selector]; // FIXME: Does this need a different action context other than "typed"? if ([self _shouldReplaceSelectionWithText:word givenAction:WebViewInsertActionTyped]) [bridge replaceSelectionWithText:word selectReplacement:NO smartReplace:NO]; } - (void)uppercaseWord:(id)sender { COMMAND_PROLOGUE [self _changeWordCaseWithSelector:@selector(uppercaseString)]; } - (void)lowercaseWord:(id)sender { COMMAND_PROLOGUE [self _changeWordCaseWithSelector:@selector(lowercaseString)]; } - (void)capitalizeWord:(id)sender { COMMAND_PROLOGUE [self _changeWordCaseWithSelector:@selector(capitalizedString)]; } - (void)deleteForward:(id)sender { COMMAND_PROLOGUE if (![self _isEditable]) return; Frame* coreFrame = core([self _frame]); if (coreFrame) coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, CharacterGranularity, false, true); } - (void)deleteBackward:(id)sender { COMMAND_PROLOGUE if (![self _isEditable]) return; Frame* coreFrame = core([self _frame]); if (coreFrame) coreFrame->editor()->deleteWithDirection(SelectionController::BACKWARD, CharacterGranularity, false, true); } - (void)deleteBackwardByDecomposingPreviousCharacter:(id)sender { COMMAND_PROLOGUE LOG_ERROR("unimplemented, doing deleteBackward instead"); if (![self _isEditable]) return; Frame* coreFrame = core([self _frame]); if (coreFrame) coreFrame->editor()->deleteWithDirection(SelectionController::BACKWARD, CharacterGranularity, false, true); } - (void)deleteToBeginningOfLine:(id)sender { COMMAND_PROLOGUE Frame* coreFrame = core([self _frame]); if (coreFrame) coreFrame->editor()->deleteWithDirection(SelectionController::BACKWARD, LineBoundary, true, false); } - (void)deleteToEndOfLine:(id)sender { COMMAND_PROLOGUE // To match NSTextView, this command should delete the newline at the end of // a paragraph if you are at the end of a paragraph (like deleteToEndOfParagraph does below). Frame* coreFrame = core([self _frame]); if (coreFrame) { if (!coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, LineBoundary, true, false)) coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, CharacterGranularity, true, false); } } - (void)deleteToBeginningOfParagraph:(id)sender { COMMAND_PROLOGUE Frame* coreFrame = core([self _frame]); if (coreFrame) coreFrame->editor()->deleteWithDirection(SelectionController::BACKWARD, ParagraphBoundary, true, false); } - (void)deleteToEndOfParagraph:(id)sender { COMMAND_PROLOGUE // Despite the name of the method, this should delete the newline if the caret is at the end of a paragraph. // If deletion to the end of the paragraph fails, we delete one character forward, which will delete the newline. Frame* coreFrame = core([self _frame]); if (coreFrame) { if (!coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, ParagraphBoundary, true, false)) coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, CharacterGranularity, true, false); } } - (void)complete:(id)sender { COMMAND_PROLOGUE if (![self _canEdit]) return; if (!_private->compController) _private->compController = [[WebTextCompleteController alloc] initWithHTMLView:self]; [_private->compController doCompletion]; } - (void)checkSpelling:(id)sender { COMMAND_PROLOGUE NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; if (!checker) { LOG_ERROR("No NSSpellChecker"); return; } core([self _frame])->editor()->advanceToNextMisspelling(); } - (void)showGuessPanel:(id)sender { COMMAND_PROLOGUE NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; if (!checker) { LOG_ERROR("No NSSpellChecker"); return; } NSPanel *spellingPanel = [checker spellingPanel]; #ifndef BUILDING_ON_TIGER // Post-Tiger, this menu item is a show/hide toggle, to match AppKit. Leave Tiger behavior alone // to match rest of OS X. if ([spellingPanel isVisible]) { [spellingPanel orderOut:sender]; return; } #endif core([self _frame])->editor()->advanceToNextMisspelling(true); [spellingPanel orderFront:sender]; } - (void)_changeSpellingToWord:(NSString *)newWord { if (![self _canEdit]) return; // Don't correct to empty string. (AppKit checked this, we might as well too.) if (![NSSpellChecker sharedSpellChecker]) { LOG_ERROR("No NSSpellChecker"); return; } if ([newWord isEqualToString:@""]) return; if ([self _shouldReplaceSelectionWithText:newWord givenAction:WebViewInsertActionPasted]) [[self _bridge] replaceSelectionWithText:newWord selectReplacement:YES smartReplace:NO]; } - (void)changeSpelling:(id)sender { COMMAND_PROLOGUE [self _changeSpellingToWord:[[sender selectedCell] stringValue]]; } - (void)ignoreSpelling:(id)sender { COMMAND_PROLOGUE NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; if (!checker) { LOG_ERROR("No NSSpellChecker"); return; } NSString *stringToIgnore = [sender stringValue]; unsigned int length = [stringToIgnore length]; if (stringToIgnore && length > 0) { [checker ignoreWord:stringToIgnore inSpellDocumentWithTag:[[self _webView] spellCheckerDocumentTag]]; // FIXME: Need to clear misspelling marker if the currently selected word is the one we are to ignore? } } - (void)performFindPanelAction:(id)sender { COMMAND_PROLOGUE // Implementing this will probably require copying all of NSFindPanel.h and .m. // We need *almost* the same thing as AppKit, but not quite. LOG_ERROR("unimplemented"); } - (void)startSpeaking:(id)sender { COMMAND_PROLOGUE WebFrameBridge *bridge = [self _bridge]; DOMRange *range = [self _selectedRange]; if (!range || [range collapsed]) range = [self _documentRange]; [NSApp speakString:[bridge stringForRange:range]]; } - (void)stopSpeaking:(id)sender { COMMAND_PROLOGUE [NSApp stopSpeaking:sender]; } - (void)insertNewlineIgnoringFieldEditor:(id)sender { COMMAND_PROLOGUE if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->execCommand("InsertNewline"); } - (void)insertTabIgnoringFieldEditor:(id)sender { COMMAND_PROLOGUE if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->execCommand("InsertTab"); } - (void)subscript:(id)sender { COMMAND_PROLOGUE DOMCSSStyleDeclaration *style = [self _emptyStyle]; [style setVerticalAlign:@"sub"]; [self _applyStyleToSelection:style withUndoAction:EditActionSubscript]; } - (void)superscript:(id)sender { COMMAND_PROLOGUE DOMCSSStyleDeclaration *style = [self _emptyStyle]; [style setVerticalAlign:@"super"]; [self _applyStyleToSelection:style withUndoAction:EditActionSuperscript]; } - (void)unscript:(id)sender { COMMAND_PROLOGUE DOMCSSStyleDeclaration *style = [self _emptyStyle]; [style setVerticalAlign:@"baseline"]; [self _applyStyleToSelection:style withUndoAction:EditActionUnscript]; } - (void)underline:(id)sender { COMMAND_PROLOGUE Frame* coreFrame = core([self _frame]); if (!coreFrame) return; // Despite the name, this method is actually supposed to toggle underline. // FIXME: This currently clears overline, line-through, and blink as an unwanted side effect. DOMCSSStyleDeclaration *style = [self _emptyStyle]; [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""]; if (coreFrame->editor()->selectionStartHasStyle(core(style))) [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""]; [self _applyStyleToSelection:style withUndoAction:EditActionUnderline]; } - (void)yank:(id)sender { COMMAND_PROLOGUE if (![self _canEdit]) return; NSString* yankee = _NSYankFromKillRing(); if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->insertTextWithoutSendingTextEvent(yankee, false); _NSSetKillRingToYankedState(); } - (void)yankAndSelect:(id)sender { COMMAND_PROLOGUE if (![self _canEdit]) return; NSString* yankee = _NSYankFromKillRing(); if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->insertTextWithoutSendingTextEvent(yankee, true); _NSSetKillRingToYankedState(); } - (void)setMark:(id)sender { COMMAND_PROLOGUE [[self _bridge] setMarkDOMRange:[self _selectedRange]]; } static DOMRange *unionDOMRanges(DOMRange *a, DOMRange *b) { ASSERT(a); ASSERT(b); DOMRange *s = [a compareBoundaryPoints:DOM_START_TO_START sourceRange:b] <= 0 ? a : b; DOMRange *e = [a compareBoundaryPoints:DOM_END_TO_END sourceRange:b] <= 0 ? b : a; DOMRange *r = [[[a startContainer] ownerDocument] createRange]; [r setStart:[s startContainer] offset:[s startOffset]]; [r setEnd:[e endContainer] offset:[e endOffset]]; return r; } - (void)deleteToMark:(id)sender { COMMAND_PROLOGUE if (![self _canEdit]) return; DOMRange *mark = [[self _bridge] markDOMRange]; if (mark == nil) { if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->performDelete(); } else { DOMRange *selection = [self _selectedRange]; DOMRange *r; NS_DURING r = unionDOMRanges(mark, selection); NS_HANDLER r = selection; NS_ENDHANDLER Frame* coreFrame = core([self _frame]); if (coreFrame) coreFrame->editor()->deleteRange([r _range], true, true, false, deleteSelectionAction, CharacterGranularity); } [[self _bridge] setMarkDOMRange:[self _selectedRange]]; } - (void)selectToMark:(id)sender { COMMAND_PROLOGUE WebFrameBridge *bridge = [self _bridge]; DOMRange *mark = [bridge markDOMRange]; DOMRange *selection = [self _selectedRange]; Frame* coreFrame = core([self _frame]); if (!mark || !selection || !coreFrame) { NSBeep(); return; } ExceptionCode ec = 0; coreFrame->selectionController()->setSelectedRange(core(unionDOMRanges(mark, [self _selectedRange])), DOWNSTREAM, true, ec); } - (void)swapWithMark:(id)sender { COMMAND_PROLOGUE WebFrameBridge *bridge = [self _bridge]; DOMRange *mark = [bridge markDOMRange]; DOMRange *selection = [self _selectedRange]; Frame* coreFrame = core([self _frame]); if (!mark || !selection || !coreFrame) { NSBeep(); return; } ExceptionCode ec = 0; coreFrame->selectionController()->setSelectedRange(core(mark), DOWNSTREAM, true, ec); if (ec == 0) [bridge setMarkDOMRange:selection]; } - (void)transpose:(id)sender { COMMAND_PROLOGUE if (![self _canEdit]) return; WebFrameBridge *bridge = [self _bridge]; DOMRange *r = [bridge rangeOfCharactersAroundCaret]; if (!r) return; NSString *characters = [bridge stringForRange:r]; if ([characters length] != 2) return; NSString *transposed = [[characters substringFromIndex:1] stringByAppendingString:[characters substringToIndex:1]]; WebView *webView = [self _webView]; if (![[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[self _selectedRange] toDOMRange:r affinity:NSSelectionAffinityDownstream stillSelecting:NO]) return; Frame* coreFrame = core([self _frame]); if (!coreFrame) return; ExceptionCode ec = 0; coreFrame->selectionController()->setSelectedRange(core(r), DOWNSTREAM, true, ec); if ([self _shouldReplaceSelectionWithText:transposed givenAction:WebViewInsertActionTyped]) [bridge replaceSelectionWithText:transposed selectReplacement:NO smartReplace:NO]; } - (void)toggleBaseWritingDirection:(id)sender { COMMAND_PROLOGUE if (![self _canEdit]) return; NSString *direction = @"RTL"; switch ([[self _bridge] baseWritingDirectionForSelectionStart]) { case NSWritingDirectionLeftToRight: break; case NSWritingDirectionRightToLeft: direction = @"LTR"; break; // The writingDirectionForSelectionStart method will never return "natural". It // will always return a concrete direction. So, keep the compiler happy, and assert not reached. case NSWritingDirectionNatural: ASSERT_NOT_REACHED(); break; } DOMCSSStyleDeclaration *style = [self _emptyStyle]; [style setDirection:direction]; [self _applyParagraphStyleToSelection:style withUndoAction:EditActionSetWritingDirection]; } - (void)changeBaseWritingDirection:(id)sender { COMMAND_PROLOGUE if (![self _canEdit]) return; NSWritingDirection writingDirection = static_cast([sender tag]); // We disable the menu item that performs this action because we can't implement // NSWritingDirectionNatural's behavior using CSS. ASSERT(writingDirection != NSWritingDirectionNatural); DOMCSSStyleDeclaration *style = [self _emptyStyle]; [style setDirection:writingDirection == NSWritingDirectionLeftToRight ? @"LTR" : @"RTL"]; [self _applyParagraphStyleToSelection:style withUndoAction:EditActionSetWritingDirection]; } - (void)indent:(id)sender { COMMAND_PROLOGUE core([self _frame])->editor()->indent(); } - (void)outdent:(id)sender { COMMAND_PROLOGUE core([self _frame])->editor()->outdent(); } #if 0 // CSS does not have a way to specify an outline font, which may make this difficult to implement. // Maybe a special case of text-shadow? - (void)outline:(id)sender; // This is part of table support, which may be in NSTextView for Tiger. // It's probably simple to do the equivalent thing for WebKit. - (void)insertTable:(id)sender; // This could be important. - (void)toggleTraditionalCharacterShape:(id)sender; // I'm not sure what the equivalents of these in the web world are. - (void)insertLineSeparator:(id)sender; - (void)insertPageBreak:(id)sender; // These methods are not implemented in NSTextView yet at the time of this writing. - (void)changeCaseOfLetter:(id)sender; - (void)transposeWords:(id)sender; #endif // Super-hack alert. // Workaround for bug 3789278. // Returns a selector only if called while: // 1) first responder is self // 2) handling a key down event // 3) not yet inside keyDown: method // 4) key is an arrow key // The selector is the one that gets sent by -[NSWindow _processKeyboardUIKey] for this key. - (SEL)_arrowKeyDownEventSelectorIfPreprocessing { NSWindow *w = [self window]; if ([w firstResponder] != self) return NULL; NSEvent *e = [w currentEvent]; if ([e type] != NSKeyDown) return NULL; if (e == _private->keyDownEvent) return NULL; NSString *s = [e charactersIgnoringModifiers]; if ([s length] == 0) return NULL; switch ([s characterAtIndex:0]) { case NSDownArrowFunctionKey: return @selector(moveDown:); case NSLeftArrowFunctionKey: return @selector(moveLeft:); case NSRightArrowFunctionKey: return @selector(moveRight:); case NSUpArrowFunctionKey: return @selector(moveUp:); default: return NULL; } } // Returns NO instead of YES if called on the selector that the // _arrowKeyDownEventSelectorIfPreprocessing method returns. // This should only happen inside -[NSWindow _processKeyboardUIKey], // and together with the change below should cause that method // to return NO rather than handling the key. // Also set a 1-shot flag for the nextResponder check below. - (BOOL)respondsToSelector:(SEL)selector { if (![super respondsToSelector:selector]) return NO; SEL arrowKeySelector = [self _arrowKeyDownEventSelectorIfPreprocessing]; if (selector != arrowKeySelector) return YES; _private->nextResponderDisabledOnce = YES; return NO; } // Returns nil instead of the next responder if called when the // one-shot flag is set, and _arrowKeyDownEventSelectorIfPreprocessing // returns something other than NULL. This should only happen inside // -[NSWindow _processKeyboardUIKey] and together with the change above // should cause that method to return NO rather than handling the key. - (NSResponder *)nextResponder { BOOL disabled = _private->nextResponderDisabledOnce; _private->nextResponderDisabledOnce = NO; if (disabled && [self _arrowKeyDownEventSelectorIfPreprocessing] != NULL) return nil; return [super nextResponder]; } // Despite its name, this is called at different times than windowDidBecomeKey is. // It takes into account all the other factors that determine when NSCell draws // with different tints, so it's the right call to use for control tints. We'd prefer // to do this with API. - (void)_windowChangedKeyState { if (Frame* frame = core([self _frame])) if (FrameView* view = frame->view()) view->updateControlTints(); [super _windowChangedKeyState]; } @end @implementation WebHTMLView (WebTextSizing) - (IBAction)_makeTextSmaller:(id)sender { [self _updateTextSizeMultiplier]; } - (IBAction)_makeTextLarger:(id)sender { [self _updateTextSizeMultiplier]; } - (IBAction)_makeTextStandardSize:(id)sender { [self _updateTextSizeMultiplier]; } - (BOOL)_tracksCommonSizeFactor { return YES; } - (void)_textSizeMultiplierChanged { [self _updateTextSizeMultiplier]; } // never sent because we track the common size factor - (BOOL)_canMakeTextSmaller { ASSERT_NOT_REACHED(); return NO; } - (BOOL)_canMakeTextLarger { ASSERT_NOT_REACHED(); return NO; } - (BOOL)_canMakeTextStandardSize { ASSERT_NOT_REACHED(); return NO; } @end @implementation NSArray (WebHTMLView) - (void)_web_makePluginViewsPerformSelector:(SEL)selector withObject:(id)object { #ifndef __LP64__ NSEnumerator *enumerator = [self objectEnumerator]; WebNetscapePluginEmbeddedView *view; while ((view = [enumerator nextObject]) != nil) if ([view isKindOfClass:[WebNetscapePluginEmbeddedView class]]) [view performSelector:selector withObject:object]; #endif } @end @implementation WebHTMLView (WebInternal) - (void)_selectionChanged { [self _updateSelectionForInputManager]; [self _updateFontPanel]; if (core([self _frame])) core([self _frame])->editor()->setStartNewKillRingSequence(true); } - (void)_updateFontPanel { // FIXME: NSTextView bails out if becoming or resigning first responder, for which it has ivar flags. Not // sure if we need to do something similar. if (![self _canEdit]) return; NSWindow *window = [self window]; // FIXME: is this first-responder check correct? What happens if a subframe is editable and is first responder? if ([NSApp keyWindow] != window || [window firstResponder] != self) return; BOOL multiple = NO; NSFont *font = [[self _bridge] fontForSelection:&multiple]; // FIXME: for now, return a bogus font that distinguishes the empty selection from the non-empty // selection. We should be able to remove this once the rest of this code works properly. if (font == nil) font = [self _hasSelection] ? [NSFont menuFontOfSize:23] : [NSFont toolTipsFontOfSize:17]; ASSERT(font != nil); NSFontManager *fm = [NSFontManager sharedFontManager]; [fm setSelectedFont:font isMultiple:multiple]; // FIXME: we don't keep track of selected attributes, or set them on the font panel. This // appears to have no effect on the UI. E.g., underlined text in Mail or TextEdit is // not reflected in the font panel. Maybe someday this will change. } - (BOOL)_canSmartCopyOrDelete { return [[self _webView] smartInsertDeleteEnabled] && [[self _bridge] selectionGranularity] == WordGranularity; } - (DOMRange *)_smartDeleteRangeForProposedRange:(DOMRange *)proposedRange { if (proposedRange == nil || [self _canSmartCopyOrDelete] == NO) return nil; return [[self _bridge] smartDeleteRangeForProposedRange:proposedRange]; } - (void)_smartInsertForString:(NSString *)pasteString replacingRange:(DOMRange *)rangeToReplace beforeString:(NSString **)beforeString afterString:(NSString **)afterString { if (!pasteString || !rangeToReplace || ![[self _webView] smartInsertDeleteEnabled]) { if (beforeString) *beforeString = nil; if (afterString) *afterString = nil; return; } [[self _bridge] smartInsertForString:pasteString replacingRange:rangeToReplace beforeString:beforeString afterString:afterString]; } - (BOOL)_textViewWasFirstResponderAtMouseDownTime:(NSTextView *)textView { return textView == _private->firstResponderTextViewAtMouseDownTime; } - (NSEvent *)_mouseDownEvent { return _private->mouseDownEvent; } #ifndef __LP64__ - (void)_pauseNullEventsForAllNetscapePlugins { NSArray *subviews = [self subviews]; unsigned int subviewCount = [subviews count]; unsigned int subviewIndex; for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) { NSView *subview = [subviews objectAtIndex:subviewIndex]; if ([subview isKindOfClass:[WebBaseNetscapePluginView class]]) [(WebBaseNetscapePluginView *)subview stopNullEvents]; } } #endif #ifndef __LP64__ - (void)_resumeNullEventsForAllNetscapePlugins { NSArray *subviews = [self subviews]; unsigned int subviewCount = [subviews count]; unsigned int subviewIndex; for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) { NSView *subview = [subviews objectAtIndex:subviewIndex]; if ([subview isKindOfClass:[WebBaseNetscapePluginView class]]) [(WebBaseNetscapePluginView *)subview restartNullEvents]; } } #endif - (void)_willMakeFirstResponderForNodeFocus { _private->willBecomeFirstResponderForNodeFocus = YES; } - (id)_highlighterForType:(NSString*)type { return [_private->highlighters objectForKey:type]; } - (WebFrame *)_frame { return [_private->dataSource webFrame]; } - (void)copy:(id)sender { COMMAND_PROLOGUE if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->copy(); } - (void)cut:(id)sender { COMMAND_PROLOGUE if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->cut(); } - (void)paste:(id)sender { COMMAND_PROLOGUE Frame* coreFrame = core([self _frame]); if (coreFrame && coreFrame->editor()->tryDHTMLPaste()) return; // DHTML did the whole operation if (!coreFrame->editor()->canPaste()) return; if (coreFrame && coreFrame->selectionController()->isContentRichlyEditable()) [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:YES]; else coreFrame->editor()->pasteAsPlainText(); } - (void)pasteAsPlainText:(id)sender { COMMAND_PROLOGUE if (![self _canEdit]) return; [self _pasteAsPlainTextWithPasteboard:[NSPasteboard generalPasteboard]]; } - (void)closeIfNotCurrentView { if ([[[self _frame] frameView] documentView] != self) [self close]; } - (DOMDocumentFragment*)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard { BOOL discard; return [self _documentFragmentFromPasteboard:pasteboard inContext:nil allowPlainText:NO chosePlainText:&discard]; } #ifndef BUILDING_ON_TIGER - (BOOL)isGrammarCheckingEnabled { // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because // the AppKit code checks the first responder. return [[self _webView] isGrammarCheckingEnabled]; } - (void)setGrammarCheckingEnabled:(BOOL)flag { // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because // the AppKit code checks the first responder. [[self _webView] setGrammarCheckingEnabled:flag]; } - (void)toggleGrammarChecking:(id)sender { // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because // the AppKit code checks the first responder. [[self _webView] toggleGrammarChecking:sender]; } static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) { NSArray *screens = [NSScreen screens]; if ([screens count] == 0) { // You could theoretically get here if running with no monitor, in which case it doesn't matter // much where the "on-screen" point is. return CGPointMake(point.x, point.y); } // Flip the y coordinate from the top of the menu bar screen -- see 4636390 return CGPointMake(point.x, NSMaxY([[screens objectAtIndex:0] frame]) - point.y); } #endif - (void)_lookUpInDictionaryFromMenu:(id)sender { // Dictionary API will accept a whitespace-only string and display UI as if it were real text, // so bail out early to avoid that. if ([[[self selectedString] _webkit_stringByTrimmingWhitespace] length] == 0) return; // We soft link to get the function that displays the dictionary (either pop-up window or app) to avoid the performance // penalty of linking to another framework. This function changed signature as well as framework between Tiger and Leopard, // so the two cases are handled separately. #ifdef BUILDING_ON_TIGER typedef OSStatus (*ServiceWindowShowFunction)(id inWordString, NSRect inWordBoundary, UInt16 inLineDirection); const char *frameworkPath = "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/LangAnalysis.framework/LangAnalysis"; const char *functionName = "DCMDictionaryServiceWindowShow"; #else typedef void (*ServiceWindowShowFunction)(id unusedDictionaryRef, id inWordString, CFRange selectionRange, id unusedFont, CGPoint textOrigin, Boolean verticalText, id unusedTransform); const char *frameworkPath = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox"; const char *functionName = "HIDictionaryWindowShow"; #endif static bool lookedForFunction = false; static ServiceWindowShowFunction dictionaryServiceWindowShow = NULL; if (!lookedForFunction) { void* langAnalysisFramework = dlopen(frameworkPath, RTLD_LAZY); ASSERT(langAnalysisFramework); if (langAnalysisFramework) dictionaryServiceWindowShow = (ServiceWindowShowFunction)dlsym(langAnalysisFramework, functionName); lookedForFunction = true; } ASSERT(dictionaryServiceWindowShow); if (!dictionaryServiceWindowShow) { NSLog(@"Couldn't find the %s function in %s", functionName, frameworkPath); return; } NSAttributedString *attrString = [self selectedAttributedString]; #ifdef BUILDING_ON_TIGER // FIXME: must check for right-to-left here NSWritingDirection writingDirection = NSWritingDirectionLeftToRight; // FIXME: the dictionary API expects the rect for the first line of selection. Passing // the rect for the entire selection, as we do here, positions the pop-up window near // the bottom of the selection rather than at the selected word. NSRect rect = [self convertRect:core([self _frame])->selectionRect() toView:nil]; rect.origin = [[self window] convertBaseToScreen:rect.origin]; NSData *data = [attrString RTFFromRange:NSMakeRange(0, [attrString length]) documentAttributes:nil]; dictionaryServiceWindowShow(data, rect, (writingDirection == NSWritingDirectionRightToLeft) ? 1 : 0); #else // The HIDictionaryWindowShow function requires the origin, in CG screen coordinates, of the first character of text in the selection. // FIXME 4945808: We approximate this in a way that works well when a single word is selected, and less well in some other cases // (but no worse than we did in Tiger) NSRect rect = core([self _frame])->selectionRect(); NSDictionary *attributes = [attrString fontAttributesInRange:NSMakeRange(0,1)]; NSFont *font = [attributes objectForKey:NSFontAttributeName]; if (font) rect.origin.y += [font ascender]; NSPoint windowPoint = [self convertPoint:rect.origin toView:nil]; NSPoint screenPoint = [[self window] convertBaseToScreen:windowPoint]; dictionaryServiceWindowShow(nil, attrString, CFRangeMake(0, [attrString length]), nil, coreGraphicsScreenPointForAppKitScreenPoint(screenPoint), false, nil); #endif } - (void)_hoverFeedbackSuspendedChanged { [self _updateMouseoverWithFakeEvent]; } - (BOOL)_interceptEditingKeyEvent:(KeyboardEvent*)event shouldSaveCommand:(BOOL)shouldSave { // Ask AppKit to process the key event -- it will call back with either insertText or doCommandBySelector. WebHTMLViewInterpretKeyEventsParameters parameters; parameters.eventWasHandled = false; parameters.shouldSaveCommand = shouldSave; // If we're intercepting the initial IM call we assume that the IM has consumed the event, // and only change this assumption if one of the NSTextInput/Responder callbacks is used. // We assume the IM will *not* consume hotkey sequences parameters.consumedByIM = !event->metaKey() && shouldSave; if (const PlatformKeyboardEvent* platformEvent = event->keyEvent()) { NSEvent *macEvent = platformEvent->macEvent(); if ([macEvent type] == NSKeyDown && [_private->compController filterKeyDown:macEvent]) return true; parameters.event = event; _private->interpretKeyEventsParameters = ¶meters; _private->receivedNOOP = NO; KeypressCommand command = event->keypressCommand(); bool hasKeypressCommand = !command.commandNames.isEmpty() || !command.text.isEmpty(); if (parameters.shouldSaveCommand || !hasKeypressCommand) [self interpretKeyEvents:[NSArray arrayWithObject:macEvent]]; else { if (!command.text.isEmpty()) [self insertText:command.text]; else { size_t size = command.commandNames.size(); for (size_t i = 0; i < size; ++i) [self doCommandBySelector:NSSelectorFromString(command.commandNames[i])]; } } _private->interpretKeyEventsParameters = 0; } return (!_private->receivedNOOP && parameters.eventWasHandled) || parameters.consumedByIM; } - (WebCore::CachedImage*)promisedDragTIFFDataSource { return _private->promisedDragTIFFDataSource; } - (void)setPromisedDragTIFFDataSource:(WebCore::CachedImage*)source { if (source) source->ref(promisedDataClient()); if (_private->promisedDragTIFFDataSource) _private->promisedDragTIFFDataSource->deref(promisedDataClient()); _private->promisedDragTIFFDataSource = source; } #undef COMMAND_PROLOGUE @end @implementation WebHTMLView (WebNSTextInputSupport) - (NSArray *)validAttributesForMarkedText { static NSArray *validAttributes; if (!validAttributes) { validAttributes = [[NSArray alloc] initWithObjects: NSUnderlineStyleAttributeName, NSUnderlineColorAttributeName, NSMarkedClauseSegmentAttributeName, NSTextInputReplacementRangeAttributeName, nil]; // NSText also supports the following attributes, but it's // hard to tell which are really required for text input to // work well; I have not seen any input method make use of them yet. // NSFontAttributeName, NSForegroundColorAttributeName, // NSBackgroundColorAttributeName, NSLanguageAttributeName. CFRetain(validAttributes); } LOG(TextInput, "validAttributesForMarkedText -> (...)"); return validAttributes; } // Utility function to make sure we don't return anything through the NSTextInput // API when an editable region is not currently focused. static BOOL isTextInput(Frame* coreFrame) { return coreFrame && !coreFrame->selectionController()->isNone() && coreFrame->selectionController()->isContentEditable(); } - (NSAttributedString *)textStorage { if (!isTextInput(core([self _frame]))) { LOG(TextInput, "textStorage -> nil"); return nil; } NSAttributedString *result = [self attributedSubstringFromRange:NSMakeRange(0, UINT_MAX)]; LOG(TextInput, "textStorage -> \"%s\"", result ? [[result string] UTF8String] : ""); // We have to return an empty string rather than null to prevent TSM from calling -string return result ? result : [[[NSAttributedString alloc] initWithString:@""] autorelease]; } - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint { NSWindow *window = [self window]; WebFrameBridge *bridge = [self _bridge]; if (window) thePoint = [window convertScreenToBase:thePoint]; thePoint = [self convertPoint:thePoint fromView:nil]; DOMRange *range = [bridge characterRangeAtPoint:thePoint]; if (!range) { LOG(TextInput, "characterIndexForPoint:(%f, %f) -> NSNotFound", thePoint.x, thePoint.y); return NSNotFound; } unsigned result = [bridge convertDOMRangeToNSRange:range].location; LOG(TextInput, "characterIndexForPoint:(%f, %f) -> %u", thePoint.x, thePoint.y, result); return result; } - (NSRect)firstRectForCharacterRange:(NSRange)theRange { WebFrameBridge *bridge = [self _bridge]; // Just to match NSTextView's behavior. Regression tests cannot detect this; // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682 // (type something; try ranges (1, -1) and (2, -1). if ((theRange.location + theRange.length < theRange.location) && (theRange.location + theRange.length != 0)) theRange.length = 0; DOMRange *range = [bridge convertNSRangeToDOMRange:theRange]; if (!range) { LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (0, 0, 0, 0)", theRange.location, theRange.length); return NSMakeRect(0, 0, 0, 0); } ASSERT([range startContainer]); ASSERT([range endContainer]); NSRect resultRect = [bridge firstRectForDOMRange:range]; resultRect = [self convertRect:resultRect toView:nil]; NSWindow *window = [self window]; if (window) resultRect.origin = [window convertBaseToScreen:resultRect.origin]; LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (%f, %f, %f, %f)", theRange.location, theRange.length, resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height); return resultRect; } - (NSRange)selectedRange { if (!isTextInput(core([self _frame]))) { LOG(TextInput, "selectedRange -> (NSNotFound, 0)"); return NSMakeRange(NSNotFound, 0); } NSRange result = [[self _bridge] selectedNSRange]; LOG(TextInput, "selectedRange -> (%u, %u)", result.location, result.length); return result; } - (NSRange)markedRange { if (![self hasMarkedText]) { LOG(TextInput, "markedRange -> (NSNotFound, 0)"); return NSMakeRange(NSNotFound, 0); } NSRange result = [[self _bridge] markedTextNSRange]; LOG(TextInput, "markedRange -> (%u, %u)", result.location, result.length); return result; } - (NSAttributedString *)attributedSubstringFromRange:(NSRange)nsRange { if (!isTextInput(core([self _frame]))) { LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length); return nil; } WebFrameBridge *bridge = [self _bridge]; DOMRange *domRange = [bridge convertNSRangeToDOMRange:nsRange]; if (!domRange) { LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length); return nil; } NSAttributedString *result = [NSAttributedString _web_attributedStringFromRange:core(domRange)]; LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> \"%s\"", nsRange.location, nsRange.length, [[result string] UTF8String]); return result; } // test for 10.4 because of #ifdef BUILDING_ON_TIGER - (long)conversationIdentifier { return (long)self; } #else - (NSInteger)conversationIdentifier { return (NSInteger)self; } #endif - (BOOL)hasMarkedText { BOOL result = [[self _bridge] markedTextDOMRange] != nil; LOG(TextInput, "hasMarkedText -> %u", result); return result; } - (void)unmarkText { LOG(TextInput, "unmarkText"); // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; _private->interpretKeyEventsParameters = 0; if (parameters) { parameters->eventWasHandled = YES; parameters->consumedByIM = NO; } if (Frame* coreFrame = core([self _frame])) coreFrame->editor()->unmarkText(); } - (void)_extractAttributes:(NSArray **)a ranges:(NSArray **)r fromAttributedString:(NSAttributedString *)string { int length = [[string string] length]; int i = 0; NSMutableArray *attributes = [NSMutableArray array]; NSMutableArray *ranges = [NSMutableArray array]; while (i < length) { NSRange effectiveRange; NSDictionary *attrs = [string attributesAtIndex:i longestEffectiveRange:&effectiveRange inRange:NSMakeRange(i,length - i)]; [attributes addObject:attrs]; [ranges addObject:[NSValue valueWithRange:effectiveRange]]; i = effectiveRange.location + effectiveRange.length; } *a = attributes; *r = ranges; } - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange { BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; // Otherwise, NSString LOG(TextInput, "setMarkedText:\"%s\" selectedRange:(%u, %u)", isAttributedString ? [[string string] UTF8String] : [string UTF8String], newSelRange.location, newSelRange.length); // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; _private->interpretKeyEventsParameters = 0; if (parameters) { parameters->eventWasHandled = YES; parameters->consumedByIM = NO; } Frame* coreFrame = core([self _frame]); if (!coreFrame) return; WebFrameBridge *bridge = [self _bridge]; if (![self _isEditable]) return; if (isAttributedString) { unsigned markedTextLength = [(NSString *)string length]; NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:NULL inRange:NSMakeRange(0, markedTextLength)]; LOG(TextInput, " ReplacementRange: %s", [rangeString UTF8String]); // The AppKit adds a 'secret' property to the string that contains the replacement // range. The replacement range is the range of the the text that should be replaced // with the new string. if (rangeString) [[self _bridge] selectNSRange:NSRangeFromString(rangeString)]; } coreFrame->editor()->setIgnoreMarkedTextSelectionChange(true); // if we had marked text already, we need to make sure to replace // that, instead of the selection/caret coreFrame->editor()->selectMarkedText(); NSString *text = string; NSArray *attributes = nil; NSArray *ranges = nil; if (isAttributedString) { text = [string string]; [self _extractAttributes:&attributes ranges:&ranges fromAttributedString:string]; } coreFrame->editor()->replaceMarkedText(text); [bridge setMarkedTextDOMRange:[self _selectedRange] customAttributes:attributes ranges:ranges]; if ([self hasMarkedText]) coreFrame->selectRangeInMarkedText(newSelRange.location, newSelRange.length); coreFrame->editor()->setIgnoreMarkedTextSelectionChange(false); } - (void)doCommandBySelector:(SEL)selector { LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector)); // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. // The same call to interpretKeyEvents can do more than one command. WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; if (parameters) parameters->consumedByIM = NO; if (selector == @selector(noop:)) { _private->receivedNOOP = YES; return; } KeyboardEvent* event = parameters ? parameters->event : 0; bool shouldSaveCommand = parameters && parameters->shouldSaveCommand; if (event && shouldSaveCommand) { KeypressCommand command = event->keypressCommand(); command.commandNames.append(NSStringFromSelector(selector)); event->setKeypressCommand(command); } else { // Make sure that only direct calls to doCommandBySelector: see the parameters by setting to 0. _private->interpretKeyEventsParameters = 0; bool eventWasHandled = true; WebView *webView = [self _webView]; Frame* coreFrame = core([self _frame]); if (![[webView _editingDelegateForwarder] webView:webView doCommandBySelector:selector] && coreFrame) { if (selector == @selector(insertNewline:) || selector == @selector(insertParagraphSeparator:) || selector == @selector(insertNewlineIgnoringFieldEditor:)) eventWasHandled = coreFrame->editor()->execCommand("InsertNewline", event); else if (selector == @selector(insertLineBreak:)) eventWasHandled = coreFrame->editor()->execCommand("InsertLineBreak", event); else if (selector == @selector(insertTab:) || selector == @selector(insertTabIgnoringFieldEditor:)) eventWasHandled = coreFrame->editor()->execCommand("InsertTab", event); else if (selector == @selector(insertBacktab:)) eventWasHandled = coreFrame->editor()->execCommand("InsertBacktab", event); else { _private->selectorForDoCommandBySelector = selector; [super doCommandBySelector:selector]; _private->selectorForDoCommandBySelector = 0; } } if (parameters) parameters->eventWasHandled = eventWasHandled; // Restore the parameters so that other calls to doCommandBySelector: see them, // and other commands can participate in setting the "eventWasHandled" flag. _private->interpretKeyEventsParameters = parameters; } } - (void)insertText:(id)string { BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; // Otherwise, NSString LOG(TextInput, "insertText:\"%s\"", isAttributedString ? [[string string] UTF8String] : [string UTF8String]); WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; _private->interpretKeyEventsParameters = 0; if (parameters) parameters->consumedByIM = NO; // We don't support inserting an attributed string but input methods don't appear to require this. NSString *text; bool isFromInputMethod = [self hasMarkedText]; if (isAttributedString) { text = [string string]; // We deal with the NSTextInputReplacementRangeAttributeName attribute from NSAttributedString here // simply because it is used by at least one Input Method -- it corresonds to the kEventParamTextInputSendReplaceRange // event in TSM. This behaviour matches that of -[WebHTMLView setMarkedText:selectedRange:] when it receives an // NSAttributedString NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:NULL inRange:NSMakeRange(0, [text length])]; LOG(TextInput, " ReplacementRange: %s", [rangeString UTF8String]); if (rangeString) { [[self _bridge] selectNSRange:NSRangeFromString(rangeString)]; isFromInputMethod = YES; } } else text = string; bool eventHandled = false; if ([text length]) { KeyboardEvent* event = parameters ? parameters->event : 0; // insertText can be called from an input method or from normal key event processing // If its from normal key event processing, we may need to save the action to perform it later. // If its from an input method, then we should go ahead and insert the text now. // We assume it's from the input method if we have marked text. // FIXME: In theory, this could be wrong for some input methods, so we should try to find // another way to determine if the call is from the input method bool shouldSaveCommand = parameters && parameters->shouldSaveCommand; if (event && shouldSaveCommand && !isFromInputMethod) { KeypressCommand command; command.text = text; event->setKeypressCommand(command); return; } Frame* coreFrame = core([self _frame]); String eventText = text; eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore eventHandled = coreFrame && coreFrame->editor()->insertText(eventText, event); } if (!parameters) return; if (isFromInputMethod) { // Allow doCommandBySelector: to be called after insertText: by resetting interpretKeyEventsParameters _private->interpretKeyEventsParameters = parameters; parameters->consumedByIM = YES; return; } parameters->eventWasHandled = eventHandled; } - (BOOL)_selectionIsInsideMarkedText { WebFrameBridge *bridge = [self _bridge]; DOMRange *selection = [self _selectedRange]; DOMRange *markedTextRange = [bridge markedTextDOMRange]; ASSERT([markedTextRange startContainer] == [markedTextRange endContainer]); if ([selection startContainer] != [markedTextRange startContainer]) return NO; if ([selection endContainer] != [markedTextRange startContainer]) return NO; if ([selection startOffset] < [markedTextRange startOffset]) return NO; if ([selection endOffset] > [markedTextRange endOffset]) return NO; return YES; } - (void)_updateSelectionForInputManager { if (![self hasMarkedText]) return; Frame* coreFrame = core([self _frame]); if (!coreFrame) return; if (coreFrame->editor()->ignoreMarkedTextSelectionChange()) return; if ([self _selectionIsInsideMarkedText]) { DOMRange *selection = [self _selectedRange]; DOMRange *markedTextDOMRange = [[self _bridge] markedTextDOMRange]; unsigned markedSelectionStart = [selection startOffset] - [markedTextDOMRange startOffset]; unsigned markedSelectionLength = [selection endOffset] - [selection startOffset]; NSRange newSelectionRange = NSMakeRange(markedSelectionStart, markedSelectionLength); [[NSInputManager currentInputManager] markedTextSelectionChanged:newSelectionRange client:self]; } else { [self unmarkText]; [[NSInputManager currentInputManager] markedTextAbandoned:self]; } } @end /* This class runs the show for handing the complete: NSTextView operation. It counts on its HTML view to call endRevertingChange: whenever the current completion needs to be aborted. The class is in one of two modes: PopupWindow 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 WebTextCompleteController - (id)initWithHTMLView:(WebHTMLView *)view { self = [super init]; if (!self) return nil; _view = view; 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. WebFrameBridge *bridge = [_view _bridge]; NSString *newText = [match substringFromIndex:prefixLength]; [bridge 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 setDrawsGrid:NO]; [_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:[_view 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 selectRow: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 WebFrameBridge *bridge = [_view _bridge]; DOMRange *selection = kit(core([_view _frame])->selectionController()->toRange().get()); DOMRange *wholeWord = [bridge 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 = [bridge 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 _webView] 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 = [[bridge stringForRange:selection] retain]; [self _buildUI]; NSRect wordRect = [bridge 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) { WebFrameBridge *bridge = [_view _bridge]; [bridge replaceSelectionWithText:_originalString selectReplacement:YES smartReplace:NO]; } else if ([_view _hasSelection]) { if (goLeft) [_view moveBackward:nil]; else [_view 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) { NSString *string = [event charactersIgnoringModifiers]; unichar c = [string characterAtIndex:0]; if (c == NSUpArrowFunctionKey) { int selectedRow = [_tableView selectedRow]; if (0 < selectedRow) { [_tableView selectRow:selectedRow-1 byExtendingSelection:NO]; [_tableView scrollRowToVisible:selectedRow-1]; } return YES; } else if (c == NSDownArrowFunctionKey) { int selectedRow = [_tableView selectedRow]; if (selectedRow < (int)[_completions count]-1) { [_tableView selectRow:selectedRow+1 byExtendingSelection:NO]; [_tableView scrollRowToVisible:selectedRow+1]; } return YES; } else if (c == NSRightArrowFunctionKey || c == '\n' || c == '\r' || c == '\t') { [self endRevertingChange:NO moveLeft:NO]; return YES; } else if (c == NSLeftArrowFunctionKey) { [self endRevertingChange:NO moveLeft:YES]; return YES; } else if (c == 0x1b || c == NSF5FunctionKey) { [self endRevertingChange:YES moveLeft:NO]; return YES; } else if (c == ' ' || ispunct(c)) { [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]; } - (int)numberOfRowsInTableView:(NSTableView *)tableView { return [_completions count]; } - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row { return [_completions objectAtIndex:row]; } - (void)tableViewSelectionDidChange:(NSNotification *)notification { [self _reflectSelection]; } @end @implementation WebHTMLView (WebDocumentPrivateProtocols) - (NSRect)selectionRect { if ([self _hasSelection]) return core([self _frame])->selectionRect(); return NSZeroRect; } - (NSArray *)selectionTextRects { if (![self _hasSelection]) return nil; Vector list; core([self _frame])->selectionTextRects(list); unsigned size = list.size(); NSMutableArray *result = [[[NSMutableArray alloc] initWithCapacity:size] autorelease]; for (unsigned i = 0; i < size; ++i) [result addObject:[NSValue valueWithRect:list[i]]]; return result; } - (NSView *)selectionView { return self; } - (NSImage *)selectionImageForcingBlackText:(BOOL)forceBlackText { if ([self _hasSelection]) return core([self _frame])->selectionImage(forceBlackText); return nil; } - (NSImage *)selectionImageForcingWhiteText:(BOOL)forceWhiteText { // NOTE: this method is obsolete and doesn't behave as its name suggests. // See comment in WebDocumentPrivate.h. return [self selectionImageForcingBlackText:forceWhiteText]; } - (NSRect)selectionImageRect { if ([self _hasSelection]) return core([self _frame])->selectionRect(); return NSZeroRect; } - (NSArray *)pasteboardTypesForSelection { if ([self _canSmartCopyOrDelete]) { NSMutableArray *types = [[[[self class] _selectionPasteboardTypes] mutableCopy] autorelease]; [types addObject:WebSmartPastePboardType]; return types; } else { return [[self class] _selectionPasteboardTypes]; } } - (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard { [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:nil]; } - (void)selectAll { Frame* coreFrame = core([self _frame]); if (coreFrame) coreFrame->selectionController()->selectAll(); } - (void)deselectAll { Frame* coreFrame = core([self _frame]); if (!coreFrame) return; coreFrame->selectionController()->clear(); } - (NSString *)string { return [[self _bridge] stringForRange:[self _documentRange]]; } - (NSAttributedString *)_attributeStringFromDOMRange:(DOMRange *)range { NSAttributedString *attributedString; #if !LOG_DISABLED double start = CFAbsoluteTimeGetCurrent(); #endif attributedString = [[[NSAttributedString alloc] _initWithDOMRange:range] autorelease]; #if !LOG_DISABLED double duration = CFAbsoluteTimeGetCurrent() - start; LOG(Timing, "creating attributed string from selection took %f seconds.", duration); #endif return attributedString; } - (NSAttributedString *)attributedString { DOMDocument *document = [[self _frame] DOMDocument]; NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[document _documentRange]]; if (!attributedString) { Document* coreDocument = core(document); Range range(coreDocument, coreDocument, 0, 0, 0); attributedString = [NSAttributedString _web_attributedStringFromRange:&range]; } return attributedString; } - (NSString *)selectedString { return [[self _bridge] selectedString]; } - (NSAttributedString *)selectedAttributedString { NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[self _selectedRange]]; if (!attributedString) { Frame* coreFrame = core([self _frame]); if (coreFrame) { RefPtr range = coreFrame->selectionController()->selection().toRange(); attributedString = [NSAttributedString _web_attributedStringFromRange:range.get()]; } } return attributedString; } - (BOOL)supportsTextEncoding { return YES; } - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection { if (![string length]) return NO; return [[self _bridge] searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:startInSelection]; } @end @implementation WebHTMLView (WebDocumentInternalProtocols) - (NSDictionary *)elementAtPoint:(NSPoint)point { return [self elementAtPoint:point allowShadowContent:NO]; } - (NSDictionary *)elementAtPoint:(NSPoint)point allowShadowContent:(BOOL)allow; { Frame* coreframe = core([self _frame]); if (coreframe) return [[[WebElementDictionary alloc] initWithHitTestResult:coreframe->eventHandler()->hitTestResultAtPoint(IntPoint(point), allow)] autorelease]; return nil; } - (NSUInteger)markAllMatchesForText:(NSString *)string caseSensitive:(BOOL)caseFlag limit:(NSUInteger)limit { return [[self _bridge] markAllMatchesForText:string caseSensitive:caseFlag limit:limit]; } - (void)setMarkedTextMatchesAreHighlighted:(BOOL)newValue { [[self _bridge] setMarkedTextMatchesAreHighlighted:newValue]; } - (BOOL)markedTextMatchesAreHighlighted { return [[self _bridge] markedTextMatchesAreHighlighted]; } - (void)unmarkAllTextMatches { return [[self _bridge] unmarkAllTextMatches]; } - (NSArray *)rectsForTextMatches { return [[self _bridge] rectsForTextMatches]; } @end // This is used by AppKit and is included here so that WebDataProtocolScheme is only defined once. @implementation NSURL (WebDataURL) + (NSURL *)_web_uniqueWebDataURL { CFUUIDRef UUIDRef = CFUUIDCreate(kCFAllocatorDefault); NSString *UUIDString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef); CFRelease(UUIDRef); NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@", WebDataProtocolScheme, UUIDString]]; CFRelease(UUIDString); return URL; } @end