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

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

Better workarounds for WebKit range bugs.

File size: 8.4 KB
RevLine 
[66]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"
[443]10#import "ICeCoffEEWebPolicyDelegate.h"
11#import "ICeCoffEEParser.h"
12#import "ICeCoffEETrigger.h"
13#import <WebKit/WebKit.h>
[66]14#import <unistd.h>
15
[455]16// Safari 3.1 and earlier (pre-r31014)
17// eliminated in http://bugs.webkit.org/show_bug.cgi?id=17640
[449]18@interface WebCoreFrameBridge : NSObject
[443]19- (DOMRange *)convertNSRangeToDOMRange:(NSRange)range;
20- (NSString *)stringForRange:(DOMRange *)range;
[66]21@end
22
[456]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
[443]29// XXX WebHTMLView is going away
[167]30@interface WebHTMLView : NSObject
[66]31
[320]32- (NSRect)selectionRect;
[443]33- (DOMRange *)_documentRange;
[66]34
[443]35- (WebView *)_webView;
[456]36- (WebCoreFrameBridge *)_bridge; // moved from WebNSViewExtras in r14032; removed in r31014
37- (WebFrame *)_frame; // moved from WebNSViewExtras in r14032
[167]38
[66]39@end
40
[448]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
[66]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;
[443]56static id /* (WebPolicyDelegate) */ policyDelegate;
57static id /* DOMRange */ selectedRange;
[167]58
[66]59- (void)mouseDown:(NSEvent *)e;
60{
[182]61 [downEvent release]; downEvent = nil;
[74]62 if (ICCF_enabled && ICCF_prefs.commandClickEnabled && ICCF_EventIsCommandMouseDown(e)) {
[443]63 if ([self respondsToSelector: @selector(_webView)]) {
64 WebView *webView = [(WebHTMLView *)self _webView];
65
[182]66 // save selection: it may be deselected on super mouseDown
[443]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];
[182]76 }
[66]77 }
78 [super mouseDown: e];
79}
80
[457]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
[66]93- (void)mouseUp:(NSEvent *)e;
94{
95 [super mouseUp: e];
96
[443]97 if (downEvent == nil)
98 return;
99
100 WebView *webView = [(WebHTMLView *)self _webView];
[455]101 BOOL webViewIsEditable = [webView isEditable];
102 BOOL elementIsEditable = YES; // don't restore
[448]103 BOOL isContinuousSpellCheckingEnabled = NO, isGrammarCheckingEnabled = NO;
[443]104
[455]105 if (webViewIsEditable) {
[443]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 {
[455]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");
[443]125 [webView setEditable: YES];
[448]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 }
[443]135
[448]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
[455]145 DOMNode *clickedNode = [elementDict objectForKey: @"WebElementDOMNode"];
[457]146 BOOL elementIsHTMLInput = ICCF_NodeIs("Input");
[455]147
[457]148 if (!elementIsEditable && (elementIsHTMLInput || ICCF_DOMParentIs("Button") || ICCF_NodeIs("FieldSet"))) {
[455]149 ICLog(@"got an uneditable form field (e.g., button)");
150 return;
151 }
[443]152
153 id link = [elementDict objectForKey: @"WebElementLinkURL"];
154 NSString *url = [link isKindOfClass: [NSURL class]] ? [link absoluteString] : nil;
[455]155
[443]156 ICCF_StartIC();
157
158 id /* DOMRange */ domRange = nil;
159
160 if (url != nil) {
161 ICLog(@"got a link");
[455]162 if (!elementIsEditable) {
[436]163 return;
[182]164 }
[443]165 // XXX handle existing selection
166 domRange = [webView selectedDOMRange];
[455]167 [domRange selectNode: clickedNode];
[443]168 [webView setSelectedDOMRange: domRange affinity: NSSelectionAffinityDownstream];
169 } else {
[455]170 if (!elementIsEditable) // may have become deselected in mouseDown
171 [webView setSelectedDOMRange: selectedRange affinity: NSSelectionAffinityDownstream];
[443]172
173 NSRange range = [ICeCoffEETrigger rangeForEvent: downEvent onTarget: (NSView<NSTextInput> *)self];
[446]174 NSAssert(range.location != NSNotFound, ICCF_LocalizedString(@"Sorry, ICeCoffEE was unable to find anything to select"));
[443]175
[456]176 WebFrame *frame = [(WebHTMLView *)self _frame];
177 WebCoreFrameBridge *bridge = nil;
178 if ([self respondsToSelector: @selector(_bridge)])
179 bridge = [(WebHTMLView *)self _bridge];
[455]180
[443]181 // XXX limit to a reasonable size
[455]182 NSString *s;
[457]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:");
[455]186 s = [[(NSView<NSTextInput> *)self attributedSubstringFromRange: NSMakeRange(0, UINT_MAX)] string];
[457]187 } else { // sometimes attributedSubstringFromRange: returns nil
188 ICLog(@"Using stringForRange:");
[456]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 }
[455]197
[456]198 if (bridge == nil && ![frame respondsToSelector: @selector(_convertNSRangeToDOMRange:)]) {
199 return; // WebKit too new?
200 }
201
[443]202 if (range.length == 0) {
203 range.length = 1;
204 range = ICCF_URLEnclosingRange(s, range);
[456]205 domRange = bridge ? [bridge convertNSRangeToDOMRange: range] :
206 [frame _convertNSRangeToDOMRange: range];
[443]207 [webView setSelectedDOMRange: domRange affinity: NSSelectionAffinityDownstream];
208 } else {
[456]209 domRange = bridge ? [bridge convertNSRangeToDOMRange: range] :
210 [frame _convertNSRangeToDOMRange: range];
[183]211 }
[443]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);
[182]227 }
[436]228 }
[443]229 } @catch (NSException *e) {
230 ICCF_HandleException(e, downEvent);
231 } @finally {
232 [selectedRange release]; selectedRange = nil;
233 [downEvent release]; downEvent = nil;
[455]234 if (!elementIsEditable && !webViewIsEditable) {
[443]235 [webView setEditable: NO];
[448]236 if (isContinuousSpellCheckingEnabled)
237 [webView setContinuousSpellCheckingEnabled: YES];
238 if (isGrammarCheckingEnabled)
239 [webView setGrammarCheckingEnabled: YES];
240 }
[456]241 ICCF_StopIC();
[443]242 }
[66]243}
244
245@end
Note: See TracBrowser for help on using the repository browser.