source: trunk/ICeCoffEE/ICeCoffEE/ICeCoffEEWebKit.m @ 457

Last change on this file since 457 was 457, checked in by Nicholas Riley, 12 years ago

Better workarounds for WebKit? range bugs.

File size: 8.4 KB
Line 
1//
2//  ICeCoffEEWebKit.m
3//  ICeCoffEE APE
4//
5//  Created by Nicholas Riley on Sun Jan 19 2003.
6//  Copyright (c) 2003 Nicholas Riley. All rights reserved.
7//
8
9#import "ICeCoffEEWebKit.h"
10#import "ICeCoffEEWebPolicyDelegate.h"
11#import "ICeCoffEEParser.h"
12#import "ICeCoffEETrigger.h"
13#import <WebKit/WebKit.h>
14#import <unistd.h>
15
16// Safari 3.1 and earlier (pre-r31014)
17// eliminated in http://bugs.webkit.org/show_bug.cgi?id=17640
18@interface WebCoreFrameBridge : NSObject
19- (DOMRange *)convertNSRangeToDOMRange:(NSRange)range;
20- (NSString *)stringForRange:(DOMRange *)range;
21@end
22
23// r31014 and later; likely to move again
24@interface WebFrame (WebFrameInternal)
25- (DOMRange *)_convertNSRangeToDOMRange:(NSRange)range;
26- (NSString *)_stringForRange:(DOMRange *)range;
27@end
28
29// XXX WebHTMLView is going away
30@interface WebHTMLView : NSObject
31
32- (NSRect)selectionRect;
33- (DOMRange *)_documentRange;
34
35- (WebView *)_webView;
36- (WebCoreFrameBridge *)_bridge; // moved from WebNSViewExtras in r14032; removed in r31014
37- (WebFrame *)_frame; // moved from WebNSViewExtras in r14032
38
39@end
40
41// from WebViewPrivate.h (will become public, part of WebViewEditing)
42@interface WebView (webViewGrammarChecking)
43- (BOOL)isGrammarCheckingEnabled;
44- (void)setGrammarCheckingEnabled:(BOOL)flag;
45@end
46
47@implementation ICeCoffEEWebKit
48
49- (NSMenu *)menuForEvent:(NSEvent *)e;
50{
51    NSMenu *myMenu = [super menuForEvent: e];
52    return ICCF_MenuForEvent(self, myMenu, e);
53}
54
55static NSEvent *downEvent = nil;
56static id /* (WebPolicyDelegate) */ policyDelegate;
57static id /* DOMRange */ selectedRange;
58
59- (void)mouseDown:(NSEvent *)e;
60{
61    [downEvent release]; downEvent = nil;
62    if (ICCF_enabled && ICCF_prefs.commandClickEnabled && ICCF_EventIsCommandMouseDown(e)) {
63        if ([self respondsToSelector: @selector(_webView)]) {
64            WebView *webView = [(WebHTMLView *)self _webView];
65           
66            // save selection: it may be deselected on super mouseDown
67            selectedRange = [[webView selectedDOMRange] retain];
68           
69            // stop any URL launching from happening
70            if ([webView isEditable]) {
71                policyDelegate = [[webView policyDelegate] retain];
72                [webView setPolicyDelegate: [ICeCoffEEWebPolicyDelegate sharedDelegate]];
73            }
74           
75            downEvent = [e retain];
76        }
77    }
78    [super mouseDown: e];
79}
80
81
82static BOOL ICCF_DOMParentOfClass(DOMNode *node, Class cls) {
83    do {
84        if ([node isKindOfClass: cls])
85            return YES;
86    } while ( (node = [node parentNode]) != nil);
87   
88    return NO;
89}
90#define ICCF_NodeIs(elt) [clickedNode isKindOfClass: NSClassFromString(@"DOMHTML"elt"Element")]
91#define ICCF_DOMParentIs(elt) ICCF_DOMParentOfClass(clickedNode, NSClassFromString(@"DOMHTML"elt"Element"))
92
93- (void)mouseUp:(NSEvent *)e;
94{
95    [super mouseUp: e];
96
97    if (downEvent == nil)
98        return;
99   
100    WebView *webView = [(WebHTMLView *)self _webView];
101    BOOL webViewIsEditable = [webView isEditable];
102    BOOL elementIsEditable = YES; // don't restore
103    BOOL isContinuousSpellCheckingEnabled = NO, isGrammarCheckingEnabled = NO;
104
105    if (webViewIsEditable) {
106        [webView setPolicyDelegate: policyDelegate];
107        [policyDelegate release]; policyDelegate = nil;
108    }
109
110    NSPoint downPt = [downEvent locationInWindow];
111    NSPoint upPt = [e locationInWindow];
112    if (abs(downPt.x - upPt.x) > kICHysteresisPixels && abs(downPt.y - upPt.y) > kICHysteresisPixels)
113        return;
114
115    @try {
116        NSPoint viewClickPt = [webView convertPoint: downPt fromView: nil];
117        NSDictionary *elementDict = [webView elementAtPoint: viewClickPt];
118        ICLog(@"elementDict: %@", elementDict);
119
120        NSAssert([elementDict count] != 0, ICCF_LocalizedString(@"Sorry, ICeCoffEE was unable to find anything to select"));
121
122        elementIsEditable = [[elementDict objectForKey: @"WebElementIsContentEditableKey"] boolValue];
123        if (!elementIsEditable) {
124            NSAssert(!webViewIsEditable, @"Internal error: uneditable element inside editable WebView");
125            [webView setEditable: YES];
126       
127            // don't want spelling/grammar marks to persist when view is made uneditable again
128            if ([webView respondsToSelector: @selector(isContinuousSpellCheckingEnabled)] &&
129                (isContinuousSpellCheckingEnabled = [webView isContinuousSpellCheckingEnabled])) {
130                if ([webView respondsToSelector: @selector(setContinuousSpellCheckingEnabled:)])
131                    [webView setContinuousSpellCheckingEnabled: NO];
132                else
133                    isContinuousSpellCheckingEnabled = NO; // don't restore
134            }
135
136            if ([webView respondsToSelector: @selector(isGrammarCheckingEnabled)] &&
137                (isGrammarCheckingEnabled = [webView isGrammarCheckingEnabled])) {
138                if ([webView respondsToSelector: @selector(setGrammarCheckingEnabled:)])
139                    [webView setGrammarCheckingEnabled: NO];
140                else
141                    isGrammarCheckingEnabled = NO; // don't restore
142            }
143        }
144
145        DOMNode *clickedNode = [elementDict objectForKey: @"WebElementDOMNode"];
146        BOOL elementIsHTMLInput = ICCF_NodeIs("Input");
147       
148        if (!elementIsEditable && (elementIsHTMLInput || ICCF_DOMParentIs("Button") || ICCF_NodeIs("FieldSet"))) {
149            ICLog(@"got an uneditable form field (e.g., button)");
150            return;
151        }
152
153        id link = [elementDict objectForKey: @"WebElementLinkURL"];
154        NSString *url = [link isKindOfClass: [NSURL class]] ? [link absoluteString] : nil;
155       
156        ICCF_StartIC();
157       
158        id /* DOMRange */ domRange = nil;
159       
160        if (url != nil) {
161            ICLog(@"got a link");
162            if (!elementIsEditable) {
163                return;
164            }
165            // XXX handle existing selection
166            domRange = [webView selectedDOMRange];
167            [domRange selectNode: clickedNode];
168            [webView setSelectedDOMRange: domRange affinity: NSSelectionAffinityDownstream];
169        } else {
170            if (!elementIsEditable) // may have become deselected in mouseDown
171                [webView setSelectedDOMRange: selectedRange affinity: NSSelectionAffinityDownstream];
172           
173            NSRange range = [ICeCoffEETrigger rangeForEvent: downEvent onTarget: (NSView<NSTextInput> *)self];
174            NSAssert(range.location != NSNotFound, ICCF_LocalizedString(@"Sorry, ICeCoffEE was unable to find anything to select"));
175
176            WebFrame *frame = [(WebHTMLView *)self _frame];
177            WebCoreFrameBridge *bridge = nil;
178            if ([self respondsToSelector: @selector(_bridge)])
179                bridge = [(WebHTMLView *)self _bridge];
180
181            // XXX limit to a reasonable size
182            NSString *s;
183            if (elementIsHTMLInput || ICCF_NodeIs("TextArea") || (elementIsEditable && !webViewIsEditable)) {
184                // for form fields, range will be field-relative rather than document-relative
185                ICLog(@"Using attributedSubstringFromRange:");
186                s = [[(NSView<NSTextInput> *)self attributedSubstringFromRange: NSMakeRange(0, UINT_MAX)] string];
187            } else { // sometimes attributedSubstringFromRange: returns nil
188                ICLog(@"Using stringForRange:");
189                domRange = [(WebHTMLView *)self _documentRange];
190                if (bridge != nil)
191                    s = [bridge stringForRange: domRange];
192                else if (![frame respondsToSelector: @selector(_stringForRange:)])
193                    return; // WebKit too new?
194                else
195                    s = [frame _stringForRange: domRange];
196            }
197           
198            if (bridge == nil && ![frame respondsToSelector: @selector(_convertNSRangeToDOMRange:)]) {
199                return; // WebKit too new?
200            }
201               
202            if (range.length == 0) {
203                range.length = 1;
204                range = ICCF_URLEnclosingRange(s, range);
205                domRange = bridge ? [bridge convertNSRangeToDOMRange: range] :
206                                    [frame _convertNSRangeToDOMRange: range];
207                [webView setSelectedDOMRange: domRange affinity: NSSelectionAffinityDownstream];
208            } else {
209                domRange = bridge ? [bridge convertNSRangeToDOMRange: range] :
210                                    [frame _convertNSRangeToDOMRange: range];
211            }
212           
213            url = [s substringWithRange: range];
214        }       
215       
216        if (ICCF_LaunchURL(url, ICCF_KeyboardAction(downEvent)) && ICCF_prefs.textBlinkEnabled && domRange != nil) {
217            NSRect selectionRect = [(WebHTMLView *)self selectionRect];
218            for (int i = 0 ; i < ICCF_prefs.textBlinkCount ; i++) {
219                [webView setSelectedDOMRange: nil affinity: NSSelectionAffinityDownstream];
220                [self setNeedsDisplayInRect: selectionRect];
221                [self display];
222                usleep(kICBlinkDelayUsecs);
223                [webView setSelectedDOMRange: domRange affinity: NSSelectionAffinityDownstream];
224                [self setNeedsDisplayInRect: selectionRect];
225                [self display];
226                usleep(kICBlinkDelayUsecs);
227            }
228        }
229    } @catch (NSException *e) {
230        ICCF_HandleException(e, downEvent);
231    } @finally {
232        [selectedRange release]; selectedRange = nil;
233        [downEvent release]; downEvent = nil;
234        if (!elementIsEditable && !webViewIsEditable) {
235            [webView setEditable: NO];
236            if (isContinuousSpellCheckingEnabled)
237                [webView setContinuousSpellCheckingEnabled: YES];
238            if (isGrammarCheckingEnabled)
239                [webView setGrammarCheckingEnabled: YES];
240        }
241        ICCF_StopIC();
242    }
243}
244
245@end
Note: See TracBrowser for help on using the repository browser.