Fixed: <rdar://problem/4119282> clicking a link in an RTF file opens the link with...
[WebKit-https.git] / WebKit / WebView.subproj / WebTextView.m
1 /*      
2     WebTextView.m
3         Copyright 2002, Apple, Inc. All rights reserved.
4 */
5
6 #import <WebKit/WebTextView.h>
7
8 #import <WebKit/WebAssertions.h>
9 #import <WebKit/WebBridge.h>
10 #import <WebKit/WebDataSourcePrivate.h>
11 #import <WebKit/WebDocumentInternal.h>
12 #import <WebKit/WebFramePrivate.h>
13 #import <WebKit/WebFrameView.h>
14 #import <WebKit/WebNSObjectExtras.h>
15 #import <WebKit/WebNSURLExtras.h>
16 #import <WebKit/WebNSViewExtras.h>
17 #import <WebKit/WebPreferences.h>
18 #import <WebKit/WebTextRendererFactory.h>
19 #import <WebKit/WebViewPrivate.h>
20
21 #import <Foundation/NSURLResponse.h>
22
23 @interface NSTextView (AppKitSecret)
24 + (NSURL *)_URLForString:(NSString *)string;
25 @end
26
27 @interface WebTextView (ForwardDeclarations)
28 - (void)_updateTextSizeMultiplier;
29 @end
30
31 @interface WebTextView (TextSizing) <_web_WebDocumentTextSizing>
32 @end
33
34 @implementation WebTextView
35
36 + (NSArray *)unsupportedTextMIMETypes
37 {
38     return [NSArray arrayWithObjects:
39         @"text/calendar",       // iCal
40         @"text/x-calendar",
41         @"text/x-vcalendar",
42         @"text/vcalendar",
43         @"text/vcard",          // vCard
44         @"text/x-vcard",
45         @"text/directory",
46         @"text/ldif",           // Netscape Address Book
47         @"text/qif",            // Quicken
48         @"text/x-qif",
49         @"text/x-csv",          // CSV (for Address Book and Microsoft Outlook)
50         @"text/x-vcf",          // vCard type used in Sun affinity app
51         nil];
52 }
53
54 - (id)initWithFrame:(NSRect)frame
55 {
56     self = [super initWithFrame:frame];
57     if (self) {
58         _textSizeMultiplier = 1.0;
59         [self setAutoresizingMask:NSViewWidthSizable];
60         [self setEditable:NO];
61         [[NSNotificationCenter defaultCenter] addObserver:self
62                                                  selector:@selector(defaultsChanged:)
63                                                      name:WebPreferencesChangedNotification
64                                                    object:nil];
65     }
66     return self;
67 }
68
69 - (void)dealloc
70 {
71     [[NSNotificationCenter defaultCenter] removeObserver:self];
72     [super dealloc];
73 }
74
75 - (void)finalize
76 {
77     [[NSNotificationCenter defaultCenter] removeObserver:self];
78     [super finalize];
79 }
80
81 - (float)_textSizeMultiplierFromWebView
82 {
83     // Note that we are not guaranteed to be the subview of a WebView at any given time.
84     WebView *webView = [self _web_parentWebView];
85     return webView ? [webView textSizeMultiplier] : 1.0;
86 }
87
88 - (WebPreferences *)_preferences
89 {
90     // Handle nil result because we might not be in a WebView at any given time.
91     WebPreferences *preferences = [[self _web_parentWebView] preferences];
92     if (preferences == nil) {
93         preferences = [WebPreferences standardPreferences];
94     }
95     return preferences;
96 }
97
98
99 - (void)setFixedWidthFont
100 {
101     WebPreferences *preferences = [self _preferences];
102     NSString *families[2];
103     families[0] = [preferences fixedFontFamily];
104     families[1] = nil;
105     NSFont *font = [[WebTextRendererFactory sharedFactory] fontWithFamilies:families 
106                                                                      traits:0 
107                                                                        size:[preferences defaultFixedFontSize]*_textSizeMultiplier];
108     if (font) {
109         [self setFont:font];
110     }
111 }
112
113 // This method was borrowed from Mail and changed to use ratios rather than deltas.
114 // Also, I removed the isEditable clause since RTF displayed in WebKit is never editable.
115 - (void)_adjustRichTextFontSizeByRatio:(float)ratio 
116 {
117     NSTextStorage *storage = [self textStorage];
118     NSRange remainingRange = NSMakeRange(0, [storage length]);
119     
120     while (remainingRange.length > 0) {
121         NSRange effectiveRange;
122         NSFont *font = [storage attribute:NSFontAttributeName atIndex:remainingRange.location longestEffectiveRange:&effectiveRange inRange:remainingRange];
123         
124         if (font) {
125             font = [[NSFontManager sharedFontManager] convertFont:font toSize:[font pointSize]*ratio];
126             [storage addAttribute:NSFontAttributeName value:font range:effectiveRange];
127         }
128         if (NSMaxRange(effectiveRange) < NSMaxRange(remainingRange)) {
129             remainingRange.length = NSMaxRange(remainingRange) - NSMaxRange(effectiveRange);
130             remainingRange.location = NSMaxRange(effectiveRange);
131         } else {
132             break;
133         }
134     }
135 }
136
137 - (void)_updateTextSizeMultiplier
138 {
139     float newMultiplier = [self _textSizeMultiplierFromWebView];
140     if (newMultiplier == _textSizeMultiplier) {
141         return;
142     }
143     
144     float oldMultiplier = _textSizeMultiplier;
145     _textSizeMultiplier = newMultiplier;
146     
147     if ([self isRichText]) {
148         [self _adjustRichTextFontSizeByRatio:newMultiplier/oldMultiplier];
149     } else {
150         [self setFixedWidthFont];
151     }
152 }
153
154 - (void)setDataSource:(WebDataSource *)dataSource
155 {
156     [self setRichText:[[[dataSource response] MIMEType] isEqualToString:@"text/rtf"]];
157     
158     float oldMultiplier = _textSizeMultiplier;
159     [self _updateTextSizeMultiplier];
160     // If the multiplier didn't change, we still need to update the fixed-width font.
161     // If the multiplier did change, this was already handled.
162     if (_textSizeMultiplier == oldMultiplier && ![self isRichText]) {
163         [self setFixedWidthFont];
164     }
165
166 }
167
168 // We handle incoming data here rather than in dataSourceUpdated because we
169 // need to distinguish the last hunk of received data from the whole glob
170 // of data received so far. This is a bad design in that it requires
171 // WebTextRepresentation to know that it's view is a WebTextView, but this
172 // bad design already existed.
173 - (void)appendReceivedData:(NSData *)data fromDataSource:(WebDataSource *)dataSource;
174 {
175     if ([self isRichText]) {
176         // FIXME: We should try to progressively load RTF.
177         [self replaceCharactersInRange:NSMakeRange(0, [[self string] length])
178                                withRTF:[dataSource data]];
179         if (_textSizeMultiplier != 1.0) {
180             [self _adjustRichTextFontSizeByRatio:_textSizeMultiplier];
181         }
182     } else {
183         [self replaceCharactersInRange:NSMakeRange([[self string] length], 0)
184                             withString:[dataSource _stringWithData:data]];
185     }
186 }
187
188
189 - (void)dataSourceUpdated:(WebDataSource *)dataSource
190 {
191 }
192
193 - (void)viewDidMoveToSuperview
194 {
195     NSView *superview = [self superview];
196     if (superview) {
197         [self setFrameSize:NSMakeSize([superview frame].size.width, [self frame].size.height)];
198     }
199 }
200
201 - (void)setNeedsLayout:(BOOL)flag
202 {
203 }
204
205 - (void)layout
206 {
207     [self sizeToFit];
208 }
209
210 - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
211 {
212
213 }
214
215 - (void)viewDidMoveToHostWindow
216 {
217
218 }
219
220 - (void)defaultsChanged:(NSNotification *)notification
221 {
222     // We use the default fixed-width font, but rich text
223     // pages specify their own fonts
224     if (![self isRichText]) {
225         [self setFixedWidthFont];
226     }
227 }
228
229 - (BOOL)canTakeFindStringFromSelection
230 {
231     return [self isSelectable] && [self selectedRange].length && ![self hasMarkedText];
232 }
233
234
235 - (IBAction)takeFindStringFromSelection:(id)sender
236 {
237     if (![self canTakeFindStringFromSelection]) {
238         NSBeep();
239         return;
240     }
241     
242     // Note: can't use writeSelectionToPasteboard:type: here, though it seems equivalent, because
243     // it doesn't declare the types to the pasteboard and thus doesn't bump the change count
244     [self writeSelectionToPasteboard:[NSPasteboard pasteboardWithName:NSFindPboard]
245                                types:[NSArray arrayWithObject:NSStringPboardType]];
246 }
247
248 - (BOOL)supportsTextEncoding
249 {
250     return YES;
251 }
252
253 - (NSString *)string
254 {
255     return [super string];
256 }
257
258 - (NSAttributedString *)attributedString
259 {
260     return [self attributedSubstringFromRange:NSMakeRange(0, [[self string] length])];
261 }
262
263 - (NSString *)selectedString
264 {
265     return [[self string] substringWithRange:[self selectedRange]];
266 }
267
268 - (NSAttributedString *)selectedAttributedString
269 {
270     return [self attributedSubstringFromRange:[self selectedRange]];
271 }
272
273 - (void)selectAll
274 {
275     [self setSelectedRange:NSMakeRange(0, [[self string] length])];
276 }
277
278 - (void)deselectAll
279 {
280     [self setSelectedRange:NSMakeRange(0,0)];
281 }
282
283 - (void)keyDown:(NSEvent *)event
284 {
285     [[self nextResponder] keyDown:event];
286 }
287
288 - (void)keyUp:(NSEvent *)event
289 {
290     [[self nextResponder] keyUp:event];
291 }
292
293 - (NSDictionary *)_elementAtWindowPoint:(NSPoint)windowPoint
294 {
295     WebFrame *frame = [[self _web_parentWebFrameView] webFrame];
296     ASSERT(frame);
297     
298     NSPoint screenPoint = [[self window] convertBaseToScreen:windowPoint];
299     BOOL isPointSelected = NSLocationInRange([self characterIndexForPoint:screenPoint], [self selectedRange]);
300     return [NSDictionary dictionaryWithObjectsAndKeys:
301         [NSNumber numberWithBool:isPointSelected], WebElementIsSelectedKey,
302         frame, WebElementFrameKey, nil];
303 }
304
305 - (NSDictionary *)elementAtPoint:(NSPoint)point
306 {
307     return [self _elementAtWindowPoint:[self convertPoint:point toView:nil]];
308 }
309
310 - (NSMenu *)menuForEvent:(NSEvent *)event
311 {    
312     WebView *webView = [self _web_parentWebView];
313     ASSERT(webView);
314     return [webView _menuForElement:[self _elementAtWindowPoint:[event locationInWindow]]];
315 }
316
317 - (NSArray *)pasteboardTypesForSelection
318 {
319     return [self writablePasteboardTypes];
320 }
321
322 - (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard
323 {
324     [self writeSelectionToPasteboard:pasteboard types:types];
325 }
326
327 // This approach could be relaxed when dealing with 3228554
328 - (BOOL)resignFirstResponder
329 {
330     BOOL resign = [super resignFirstResponder];
331     if (resign) {
332         [self setSelectedRange:NSMakeRange(0,0)];
333     }
334     return resign;
335 }
336
337 - (void)clickedOnLink:(id)link atIndex:(unsigned)charIndex
338 {
339     NSURL *URL = nil;
340     if ([link isKindOfClass:[NSURL class]]) {
341         URL = (NSURL *)link;
342     } else if ([link isKindOfClass:[NSString class]]) {
343         URL = [[self class] _URLForString:(NSString *)link];
344     }
345     if (URL != nil) {    
346         // Call the bridge because this is where our security checks are made.
347         WebFrame *frame = [[self _web_parentWebFrameView] webFrame];
348         [[frame _bridge] loadURL:URL 
349                         referrer:[[[[frame dataSource] request] URL] _web_originalDataAsString]
350                           reload:NO
351                      userGesture:YES       
352                           target:nil
353                  triggeringEvent:[[self window] currentEvent]
354                             form:nil 
355                       formValues:nil];
356     }
357 }
358
359 #pragma mark PRINTING
360
361 - (void)drawPageBorderWithSize:(NSSize)borderSize
362 {
363     ASSERT(NSEqualSizes(borderSize, [[[NSPrintOperation currentOperation] printInfo] paperSize]));
364     [[self _web_parentWebView] _drawHeaderAndFooter];
365 }
366
367 - (BOOL)knowsPageRange:(NSRangePointer)range {
368     // Waiting for beginDocument to adjust the printing margins is too late.
369     [[self _web_parentWebView] _adjustPrintingMarginsForHeaderAndFooter];
370     return [super knowsPageRange:range];
371 }
372
373 - (BOOL)canPrintHeadersAndFooters
374 {
375     return YES;
376 }
377
378 @end
379
380 @implementation WebTextView (TextSizing)
381
382 - (void)_web_textSizeMultiplierChanged
383 {
384     [self _updateTextSizeMultiplier];
385 }
386
387 @end