source: trunk/ICeCoffEE/ICeCoffEE/ICeCoffEEWebKit.m

Last change on this file was 495, checked in by Nicholas Riley, 11 years ago

English.proj/APEInfo.rtfd: Updated for 1.5b5.

English.lproj/InfoPlist.strings: Updated copyright date.

English.lproj/Localizable.strings: Don't report an error in WebKit? if
there's nothing to launch ("Sorry, ICeCoffEE was unable to find
anything to select").

ICeCoffEE/ICeCoffEE.m: Don't trigger exception with Command-click
outside text range (introduced in 10.5.x?).

ICeCoffEE/ICeCoffEEWebKit.m: Update for WebFrame? changes (r39145).

VERSION.xcconfig: Updated for 1.5b5.

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