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 | |
---|
55 | static NSEvent *downEvent = nil; |
---|
56 | static id /* (WebPolicyDelegate) */ policyDelegate; |
---|
57 | static 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 | |
---|
82 | static 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 |
---|