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