// // ICeCoffEEWebKit.m // ICeCoffEE APE // // Created by Nicholas Riley on Sun Jan 19 2003. // Copyright (c) 2003 Nicholas Riley. All rights reserved. // #import "ICeCoffEEWebKit.h" #import "ICeCoffEEWebPolicyDelegate.h" #import "ICeCoffEEParser.h" #import "ICeCoffEETrigger.h" #import #import // Safari 3.1 and earlier (pre-r31014) // eliminated in http://bugs.webkit.org/show_bug.cgi?id=17640 @interface WebCoreFrameBridge : NSObject - (DOMRange *)convertNSRangeToDOMRange:(NSRange)range; - (NSString *)stringForRange:(DOMRange *)range; @end // r31014 and later; likely to move again @interface WebFrame (WebFrameInternal) - (DOMRange *)_convertNSRangeToDOMRange:(NSRange)range; - (NSString *)_stringForRange:(DOMRange *)range; @end // XXX WebHTMLView is going away @interface WebHTMLView : NSObject - (NSRect)selectionRect; - (DOMRange *)_documentRange; - (WebView *)_webView; - (WebCoreFrameBridge *)_bridge; // moved from WebNSViewExtras in r14032; removed in r31014 - (WebFrame *)_frame; // moved from WebNSViewExtras in r14032 @end // from WebViewPrivate.h (will become public, part of WebViewEditing) @interface WebView (webViewGrammarChecking) - (BOOL)isGrammarCheckingEnabled; - (void)setGrammarCheckingEnabled:(BOOL)flag; @end @implementation ICeCoffEEWebKit - (NSMenu *)menuForEvent:(NSEvent *)e; { NSMenu *myMenu = [super menuForEvent: e]; return ICCF_MenuForEvent(self, myMenu, e); } static NSEvent *downEvent = nil; static id /* (WebPolicyDelegate) */ policyDelegate; static id /* DOMRange */ selectedRange; - (void)mouseDown:(NSEvent *)e; { [downEvent release]; downEvent = nil; if (ICCF_enabled && ICCF_prefs.commandClickEnabled && ICCF_EventIsCommandMouseDown(e)) { if ([self respondsToSelector: @selector(_webView)]) { WebView *webView = [(WebHTMLView *)self _webView]; // save selection: it may be deselected on super mouseDown selectedRange = [[webView selectedDOMRange] retain]; // stop any URL launching from happening if ([webView isEditable]) { policyDelegate = [[webView policyDelegate] retain]; [webView setPolicyDelegate: [ICeCoffEEWebPolicyDelegate sharedDelegate]]; } downEvent = [e retain]; } } [super mouseDown: e]; } static BOOL ICCF_DOMParentOfClass(DOMNode *node, Class cls) { do { if ([node isKindOfClass: cls]) return YES; } while ( (node = [node parentNode]) != nil); return NO; } #define ICCF_NodeIs(elt) [clickedNode isKindOfClass: NSClassFromString(@"DOMHTML"elt"Element")] #define ICCF_DOMParentIs(elt) ICCF_DOMParentOfClass(clickedNode, NSClassFromString(@"DOMHTML"elt"Element")) - (void)mouseUp:(NSEvent *)e; { [super mouseUp: e]; if (downEvent == nil) return; WebView *webView = [(WebHTMLView *)self _webView]; BOOL webViewIsEditable = [webView isEditable]; BOOL elementIsEditable = YES; // don't restore BOOL isContinuousSpellCheckingEnabled = NO, isGrammarCheckingEnabled = NO; if (webViewIsEditable) { [webView setPolicyDelegate: policyDelegate]; [policyDelegate release]; policyDelegate = nil; } NSPoint downPt = [downEvent locationInWindow]; NSPoint upPt = [e locationInWindow]; if (abs(downPt.x - upPt.x) > kICHysteresisPixels && abs(downPt.y - upPt.y) > kICHysteresisPixels) return; @try { NSPoint viewClickPt = [webView convertPoint: downPt fromView: nil]; NSDictionary *elementDict = [webView elementAtPoint: viewClickPt]; ICLog(@"elementDict: %@", elementDict); NSAssert([elementDict count] != 0, ICCF_LocalizedString(@"Sorry, ICeCoffEE was unable to find anything to select")); elementIsEditable = [[elementDict objectForKey: @"WebElementIsContentEditableKey"] boolValue]; if (!elementIsEditable) { NSAssert(!webViewIsEditable, @"Internal error: uneditable element inside editable WebView"); [webView setEditable: YES]; // don't want spelling/grammar marks to persist when view is made uneditable again if ([webView respondsToSelector: @selector(isContinuousSpellCheckingEnabled)] && (isContinuousSpellCheckingEnabled = [webView isContinuousSpellCheckingEnabled])) { if ([webView respondsToSelector: @selector(setContinuousSpellCheckingEnabled:)]) [webView setContinuousSpellCheckingEnabled: NO]; else isContinuousSpellCheckingEnabled = NO; // don't restore } if ([webView respondsToSelector: @selector(isGrammarCheckingEnabled)] && (isGrammarCheckingEnabled = [webView isGrammarCheckingEnabled])) { if ([webView respondsToSelector: @selector(setGrammarCheckingEnabled:)]) [webView setGrammarCheckingEnabled: NO]; else isGrammarCheckingEnabled = NO; // don't restore } } DOMNode *clickedNode = [elementDict objectForKey: @"WebElementDOMNode"]; BOOL elementIsHTMLInput = ICCF_NodeIs("Input"); if (!elementIsEditable && (elementIsHTMLInput || ICCF_DOMParentIs("Button") || ICCF_NodeIs("FieldSet"))) { ICLog(@"got an uneditable form field (e.g., button)"); return; } id link = [elementDict objectForKey: @"WebElementLinkURL"]; NSString *url = [link isKindOfClass: [NSURL class]] ? [link absoluteString] : nil; ICCF_StartIC(); id /* DOMRange */ domRange = nil; if (url != nil) { ICLog(@"got a link"); if (!elementIsEditable) { return; } // XXX handle existing selection domRange = [webView selectedDOMRange]; [domRange selectNode: clickedNode]; [webView setSelectedDOMRange: domRange affinity: NSSelectionAffinityDownstream]; } else { if (!elementIsEditable) // may have become deselected in mouseDown [webView setSelectedDOMRange: selectedRange affinity: NSSelectionAffinityDownstream]; NSRange range = [ICeCoffEETrigger rangeForEvent: downEvent onTarget: (NSView *)self]; NSAssert(range.location != NSNotFound, ICCF_LocalizedString(@"Sorry, ICeCoffEE was unable to find anything to select")); WebFrame *frame = [(WebHTMLView *)self _frame]; WebCoreFrameBridge *bridge = nil; if ([self respondsToSelector: @selector(_bridge)]) bridge = [(WebHTMLView *)self _bridge]; // XXX limit to a reasonable size NSString *s; if (elementIsHTMLInput || ICCF_NodeIs("TextArea") || (elementIsEditable && !webViewIsEditable)) { // for form fields, range will be field-relative rather than document-relative ICLog(@"Using attributedSubstringFromRange:"); s = [[(NSView *)self attributedSubstringFromRange: NSMakeRange(0, UINT_MAX)] string]; } else { // sometimes attributedSubstringFromRange: returns nil ICLog(@"Using stringForRange:"); domRange = [(WebHTMLView *)self _documentRange]; if (bridge != nil) s = [bridge stringForRange: domRange]; else if (![frame respondsToSelector: @selector(_stringForRange:)]) return; // WebKit too new? else s = [frame _stringForRange: domRange]; } if (bridge == nil && ![frame respondsToSelector: @selector(_convertNSRangeToDOMRange:)]) { return; // WebKit too new? } if (range.length == 0) { range.length = 1; range = ICCF_URLEnclosingRange(s, range); domRange = bridge ? [bridge convertNSRangeToDOMRange: range] : [frame _convertNSRangeToDOMRange: range]; [webView setSelectedDOMRange: domRange affinity: NSSelectionAffinityDownstream]; } else { domRange = bridge ? [bridge convertNSRangeToDOMRange: range] : [frame _convertNSRangeToDOMRange: range]; } url = [s substringWithRange: range]; } if (ICCF_LaunchURL(url, ICCF_KeyboardAction(downEvent)) && ICCF_prefs.textBlinkEnabled && domRange != nil) { NSRect selectionRect = [(WebHTMLView *)self selectionRect]; for (int i = 0 ; i < ICCF_prefs.textBlinkCount ; i++) { [webView setSelectedDOMRange: nil affinity: NSSelectionAffinityDownstream]; [self setNeedsDisplayInRect: selectionRect]; [self display]; usleep(kICBlinkDelayUsecs); [webView setSelectedDOMRange: domRange affinity: NSSelectionAffinityDownstream]; [self setNeedsDisplayInRect: selectionRect]; [self display]; usleep(kICBlinkDelayUsecs); } } } @catch (NSException *e) { ICCF_HandleException(e, downEvent); } @finally { [selectedRange release]; selectedRange = nil; [downEvent release]; downEvent = nil; if (!elementIsEditable && !webViewIsEditable) { [webView setEditable: NO]; if (isContinuousSpellCheckingEnabled) [webView setContinuousSpellCheckingEnabled: YES]; if (isGrammarCheckingEnabled) [webView setGrammarCheckingEnabled: YES]; } ICCF_StopIC(); } } @end