Patch by Kevin Ballard <kevin@sb.org>
[WebKit-https.git] / WebCore / kwq / KWQTextArea.mm
1 /*
2  * Copyright (C) 2004 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "KWQTextArea.h"
27
28 #import "DOMCSS.h"
29 #import "DOMHTML.h"
30 #import "KWQAssertions.h"
31 #import "KWQKHTMLPart.h"
32 #import "KWQNSViewExtras.h"
33 #import "KWQTextEdit.h"
34 #import "render_replaced.h"
35 #import "WebCoreBridge.h"
36
37 using DOM::EventImpl;
38 using DOM::NodeImpl;
39 using khtml::RenderWidget;
40
41 @interface NSTextView (WebCoreKnowsCertainAppKitSecrets)
42 - (void)setWantsNotificationForMarkedText:(BOOL)wantsNotification;
43 @end
44
45 /*
46     This widget is used to implement the <TEXTAREA> element.
47     
48     It has a small set of features required by the definition of the <TEXTAREA>
49     element.  
50     
51     It supports the three wierd <TEXTAREA> WRAP attributes:
52     
53               OFF - Text is not wrapped.  It is kept to single line, although if
54                     the user enters a return the line IS broken.  This emulates
55                     Mac IE 5.1.
56      SOFT|VIRTUAL - Text is wrapped, but not actually broken.  
57     HARD|PHYSICAL - Text is wrapped, and text is broken into separate lines.
58 */
59
60 @interface NSView (KWQTextArea)
61 - (void)_KWQ_setKeyboardFocusRingNeedsDisplay;
62 @end
63
64 @interface NSTextView (KWQTextArea)
65 - (NSParagraphStyle *)_KWQ_typingParagraphStyle;
66 - (void)_KWQ_setTypingParagraphStyle:(NSParagraphStyle *)style;
67 @end
68
69 @interface NSTextStorage (KWQTextArea)
70 - (void)_KWQ_setBaseWritingDirection:(NSWritingDirection)direction;
71 @end
72
73 @interface KWQTextArea (KWQTextAreaTextView)
74 + (NSImage *)_resizeCornerImage;
75 - (BOOL)_isResizableByUser;
76 - (BOOL)_textViewShouldHandleResizing;
77 - (void)_trackResizeFromMouseDown:(NSEvent *)event;
78 @end
79
80 const int MinimumWidthWhileResizing = 100;
81 const int MinimumHeightWhileResizing = 40;
82
83 @interface KWQTextAreaTextView : NSTextView <KWQWidgetHolder>
84 {
85     QTextEdit *widget;
86     BOOL disabled;
87     BOOL editableIfEnabled;
88     BOOL inCut;
89     int inResponderChange;
90 }
91
92 - (void)setWidget:(QTextEdit *)widget;
93
94 - (void)setEnabled:(BOOL)flag;
95 - (BOOL)isEnabled;
96
97 - (void)setEditableIfEnabled:(BOOL)flag;
98 - (BOOL)isEditableIfEnabled;
99
100 - (void)updateTextColor;
101
102 - (BOOL)inResponderChange;
103
104 @end
105
106 @implementation KWQTextArea
107
108 const float LargeNumberForText = 1.0e7;
109
110 + (NSImage *)_resizeCornerImage
111 {
112     static NSImage *cornerImage = nil;
113     if (cornerImage == nil) {
114         cornerImage = [[NSImage alloc] initWithContentsOfFile:
115             [[NSBundle bundleForClass:[self class]]
116             pathForResource:@"textAreaResizeCorner" ofType:@"tiff"]];
117     }
118     ASSERT(cornerImage != nil);
119     return cornerImage;
120 }
121
122 - (void)_configureTextViewForWordWrapMode
123 {
124     [textView setHorizontallyResizable:!wrap];
125     [textView setMaxSize:NSMakeSize(LargeNumberForText, LargeNumberForText)];
126
127     [[textView textContainer] setWidthTracksTextView:wrap];
128     [[textView textContainer] setContainerSize:NSMakeSize(LargeNumberForText, LargeNumberForText)];
129 }
130
131 - (void)_createTextView
132 {
133     textView = [[KWQTextAreaTextView alloc] init];
134
135     [textView setRichText:NO];
136     [textView setAllowsUndo:YES];
137     [textView setDelegate:self];
138     [textView setWantsNotificationForMarkedText:YES];
139
140     [self setDocumentView:textView];
141
142     [self _configureTextViewForWordWrapMode];
143 }
144
145 - (void)_updateTextViewWidth
146 {
147     if (wrap) {
148         [textView setFrameSize:NSMakeSize([self contentSize].width, [textView frame].size.height)];
149     }
150 }
151
152 - initWithFrame:(NSRect)frame
153 {
154     [super initWithFrame:frame];
155
156     inInitWithFrame = YES;
157     wrap = YES;
158
159     [self setBorderType:NSBezelBorder];
160
161     [self _createTextView];
162     [self _updateTextViewWidth];
163     
164     // In WebHTMLView, we set a clip. This is not typical to do in an
165     // NSView, and while correct for any one invocation of drawRect:,
166     // it causes some bad problems if that clip is cached between calls.
167     // The cached graphics state, which clip views keep around, does
168     // cache the clip in this undesirable way. Consequently, we want to 
169     // release the GState for all clip views for all views contained in 
170     // a WebHTMLView. Here we do it for textareas used in forms.
171     // See these bugs for more information:
172     // <rdar://problem/3310943>: REGRESSION (Panther): textareas in forms sometimes draw blank (bugreporter)
173     [[self contentView] releaseGState];
174     [[self documentView] releaseGState];
175
176     inInitWithFrame = NO;
177
178     return self;
179 }
180
181 - initWithQTextEdit:(QTextEdit *)w 
182 {
183     [self init];
184
185     widget = w;
186     [textView setWidget:widget];
187
188     return self;
189 }
190
191 - (void)detachQTextEdit
192 {
193     widget = 0;
194     [textView setWidget:0];
195 }
196
197 - (void)dealloc
198 {
199     [textView release];
200     [_font release];
201     
202     [super dealloc];
203 }
204
205 - (void)textViewDidChangeSelection:(NSNotification *)notification
206 {
207     if (widget && ![textView inResponderChange])
208         widget->selectionChanged();
209 }
210
211 - (void)textDidChange:(NSNotification *)notification
212 {
213     if (widget)
214         widget->textChanged();
215     
216     WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(widget);
217     [bridge textDidChange:notification];
218 }
219
220 - (void)setWordWrap:(BOOL)f
221 {
222     if (f != wrap) {
223         wrap = f;
224         [self _configureTextViewForWordWrapMode];
225     }
226 }
227
228 - (BOOL)wordWrap
229 {
230     return wrap;
231 }
232
233 - (void)setText:(NSString *)s
234 {
235     [textView setString:s];
236     [textView updateTextColor];
237 }
238
239 - (NSString *)text
240 {
241     NSString *text = [textView string];
242     
243     if ([text rangeOfString:@"\r" options:NSLiteralSearch].location != NSNotFound) {
244         NSMutableString *mutableText = [[text mutableCopy] autorelease];
245     
246         [mutableText replaceOccurrencesOfString:@"\r\n" withString:@"\n"
247             options:NSLiteralSearch range:NSMakeRange(0, [mutableText length])];
248         [mutableText replaceOccurrencesOfString:@"\r" withString:@"\n"
249             options:NSLiteralSearch range:NSMakeRange(0, [mutableText length])];
250         
251         text = mutableText;
252     }
253
254     return text;
255 }
256
257 - (NSString *)textWithHardLineBreaks
258 {
259     NSMutableString *textWithHardLineBreaks = [NSMutableString string];
260     
261     NSString *text = [textView string];
262     NSLayoutManager *layoutManager = [textView layoutManager];
263     
264     unsigned numberOfGlyphs = [layoutManager numberOfGlyphs];
265     NSRange lineGlyphRange = NSMakeRange(0, 0);
266     while (NSMaxRange(lineGlyphRange) < numberOfGlyphs) {
267         [layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineGlyphRange) effectiveRange:&lineGlyphRange];
268         NSRange lineRange = [layoutManager characterRangeForGlyphRange:lineGlyphRange actualGlyphRange:NULL];
269         if ([textWithHardLineBreaks length]) {
270             unichar lastCharacter = [textWithHardLineBreaks characterAtIndex:[textWithHardLineBreaks length] - 1];
271             if (lastCharacter != '\n' && lastCharacter != '\r') {
272                 [textWithHardLineBreaks appendString:@"\n"];
273             }
274         }
275         [textWithHardLineBreaks appendString:[text substringWithRange:lineRange]];
276     }
277
278     [textWithHardLineBreaks replaceOccurrencesOfString:@"\r\n" withString:@"\n"
279         options:NSLiteralSearch range:NSMakeRange(0, [textWithHardLineBreaks length])];
280     [textWithHardLineBreaks replaceOccurrencesOfString:@"\r" withString:@"\n"
281         options:NSLiteralSearch range:NSMakeRange(0, [textWithHardLineBreaks length])];
282
283     return textWithHardLineBreaks;
284 }
285
286 - (void)selectAll
287 {
288     [textView selectAll:nil];
289 }
290
291 - (void)setSelectedRange:(NSRange)aRange
292 {
293     NSString *text = [textView string];
294     // Ok, the selection has to match up with the string returned by -text
295     // and since -text translates \r\n to \n, we have to modify our selection
296     // if a \r\n sequence is anywhere in or before the selection
297     unsigned count = 0;
298     NSRange foundRange, searchRange = NSMakeRange(0, aRange.location);
299     while (foundRange = [text rangeOfString:@"\r\n" options:NSLiteralSearch range:searchRange],
300            foundRange.location != NSNotFound) {
301         count++;
302         searchRange.location = NSMaxRange(foundRange);
303         if (searchRange.location >= aRange.location) break;
304         searchRange.length = aRange.location - searchRange.location;
305     }
306     aRange.location += count;
307     count = 0;
308     searchRange = NSMakeRange(aRange.location, aRange.length);
309     while (foundRange = [text rangeOfString:@"\r\n" options:NSLiteralSearch range:searchRange],
310            foundRange.location != NSNotFound) {
311         count++;
312         searchRange.location = NSMaxRange(foundRange);
313         if (searchRange.location >= NSMaxRange(aRange)) break;
314         searchRange.length = NSMaxRange(aRange) - searchRange.location;
315     }
316     aRange.length += count;
317     [textView setSelectedRange:aRange];
318 }
319
320 - (NSRange)selectedRange
321 {
322     NSRange aRange = [textView selectedRange];
323     if (aRange.location == NSNotFound) {
324         return aRange;
325     }
326     // Same issue as with -setSelectedRange: regarding \r\n sequences
327     unsigned count = 0;
328     NSRange foundRange, searchRange = NSMakeRange(0, aRange.location);
329     NSString *text = [textView string];
330     while (foundRange = [text rangeOfString:@"\r\n" options:NSLiteralSearch range:searchRange],
331            foundRange.location != NSNotFound) {
332         count++;
333         searchRange.location = NSMaxRange(foundRange);
334         if (searchRange.location >= aRange.location) break;
335         searchRange.length = aRange.location - searchRange.location;
336     }
337     aRange.location -= count;
338     count = 0;
339     searchRange = NSMakeRange(aRange.location, aRange.length);
340     while (foundRange = [text rangeOfString:@"\r\n" options:NSLiteralSearch range:searchRange],
341            foundRange.location != NSNotFound) {
342         count++;
343         searchRange.location = NSMaxRange(foundRange);
344         if (searchRange.location >= NSMaxRange(aRange)) break;
345         searchRange.length = NSMaxRange(aRange) - searchRange.location;
346     }
347     aRange.length -= count;
348     return aRange;
349 }
350
351 - (BOOL)hasSelection
352 {
353     return [textView selectedRange].length > 0;
354 }
355
356 - (void)setEditable:(BOOL)flag
357 {
358     [textView setEditableIfEnabled:flag];
359 }
360
361 - (BOOL)isEditable
362 {
363     return [textView isEditableIfEnabled];
364 }
365
366 - (void)setEnabled:(BOOL)flag
367 {
368     if (flag == [textView isEnabled])
369         return;
370         
371     [textView setEnabled:flag];
372     
373     [self setNeedsDisplay:YES];
374 }
375
376 - (BOOL)isEnabled
377 {
378     return [textView isEnabled];
379 }
380
381 - (BOOL)_isResizableByUser
382 {
383     // Compute the value once, then cache it. We don't react to changes in the settings, so each
384     // instance needs to keep track of its own state. We can't compute this at init time, because
385     // the part isn't reachable, hence the settings aren't reachable, until the event filter has
386     // been installed.
387     if (!resizableByUserComputed) {
388         resizableByUser = [KWQKHTMLPart::bridgeForWidget(widget) part]->settings()->textAreasAreResizable();
389         resizableByUserComputed = YES;
390     }
391     return resizableByUser;
392 }
393
394 - (BOOL)_textViewShouldHandleResizing
395 {
396     // Only enabled textareas can be resized
397     if (![textView isEnabled]) {
398         return NO;
399     }
400     
401     // No need to handle resizing if we aren't user-resizable
402     if (![self _isResizableByUser]) {
403         return NO;
404     }
405     
406     // If either scroller is visible, the drawing and tracking for resizing are done by this class
407     // rather than by the enclosed textview.
408     NSScroller *verticalScroller = [self verticalScroller];
409     if (verticalScroller != nil && ![verticalScroller isHidden]) {
410         return NO;
411     }
412     
413     NSScroller *horizontalScroller = [self horizontalScroller];
414     if (horizontalScroller != nil && ![horizontalScroller isHidden]) {
415         return NO;
416     }
417     
418     return YES;
419 }
420
421 - (void)tile
422 {
423     [super tile];
424 #if !BUILDING_ON_PANTHER
425     [self _updateTextViewWidth];
426 #else
427     // Pre-Tiger, if we change the width here we re-enter in a way that makes NSText unhappy.
428     [self performSelector:@selector(_updateTextViewWidth) withObject:nil afterDelay:0];
429 #endif
430     
431     // If we're still initializing, it's too early to call _isResizableByUser. -tile will be called
432     // again before we're displayed, so just skip the resizable stuff.
433     if (!inInitWithFrame && [self _isResizableByUser]) {
434         // Shrink the vertical scroller to make room for the resize corner.
435         if ([self hasVerticalScroller]) {
436             NSScroller *verticalScroller = [self verticalScroller];
437             NSRect scrollerFrame = [verticalScroller frame];
438             // The 2 pixel offset matches NSScrollView's positioning when both scrollers are present.
439             [verticalScroller setFrameSize:NSMakeSize(scrollerFrame.size.width, NSHeight([self frame]) - scrollerFrame.size.width - 2)];
440         }
441     }
442 }
443
444 - (void)getCursorPositionAsIndex:(int *)index inParagraph:(int *)paragraph
445 {
446     assert(paragraph != NULL);
447     assert(index != NULL);
448     
449     NSString *text = [textView string];
450     NSRange selectedRange = [textView selectedRange];
451     
452     if (selectedRange.location == NSNotFound) {
453         *paragraph = 0;
454         *index = 0;
455         return;
456     }
457     
458     int paragraphSoFar = 0;
459     int paragraphStart = 0;
460     
461     NSScanner *scanner = [NSScanner scannerWithString:text];
462     [scanner setCharactersToBeSkipped:nil];
463     NSCharacterSet *newlineSet = [NSCharacterSet characterSetWithCharactersInString:@"\r\n"];
464
465     while (true) {
466         [scanner scanUpToCharactersFromSet:newlineSet intoString:NULL];
467         
468         if ([scanner isAtEnd] || selectedRange.location <= [scanner scanLocation]) {
469             break;
470         }
471         
472         paragraphSoFar++;
473         
474         unichar c = [text characterAtIndex:[scanner scanLocation]];
475         [scanner setScanLocation:[scanner scanLocation]+1]; // skip over the found char
476         if (c == '\r') {
477             [scanner scanString:@"\n" intoString:NULL]; // get the entire crlf if it is one
478         }
479         
480         paragraphStart = [scanner scanLocation];
481     }
482     
483     *paragraph = paragraphSoFar;
484     // It shouldn't happen, but it might be possible for the selection
485     // to be between the cr and lf if there's a crlf sequence
486     // that would result in a -1 index when it should be 0. Lets handle that
487     *index = MAX(selectedRange.location - paragraphStart, 0);
488 }
489
490 static NSRange RangeOfParagraph(NSString *text, int paragraph)
491 {
492     int paragraphSoFar = 0;
493     int paragraphStart = 0;
494     
495     NSScanner *scanner = [NSScanner scannerWithString:text];
496     [scanner setCharactersToBeSkipped:nil];
497     NSCharacterSet *newlineSet = [NSCharacterSet characterSetWithCharactersInString:@"\r\n"];
498
499     while (true) {
500         [scanner scanUpToCharactersFromSet:newlineSet intoString:NULL];
501         
502         if ([scanner isAtEnd] || paragraphSoFar == paragraph) {
503             break;
504         }
505         
506         paragraphSoFar++;
507         
508         unichar c = [text characterAtIndex:[scanner scanLocation]];
509         [scanner setScanLocation:[scanner scanLocation]+1]; // skip over the found char
510         if (c == '\r') {
511             [scanner scanString:@"\n" intoString:NULL]; // get the entire crlf if it is one
512         }
513         
514         paragraphStart = [scanner scanLocation];
515     }
516     
517     if (paragraphSoFar < paragraph) {
518         return NSMakeRange(NSNotFound, 0);
519     }
520     
521     return NSMakeRange(paragraphStart, [scanner scanLocation]);
522 }
523
524 - (void)setCursorPositionToIndex:(int)index inParagraph:(int)paragraph
525 {
526     NSString *text = [textView string];
527     NSRange range = RangeOfParagraph(text, paragraph);
528     if (range.location == NSNotFound) {
529         [textView setSelectedRange:NSMakeRange([text length], 0)];
530     } else {
531         if (index < 0) {
532             index = 0;
533         } else if ((unsigned)index > range.length) {
534             index = range.length;
535         }
536         [textView setSelectedRange:NSMakeRange(range.location + index, 0)];
537     }
538 }
539
540 - (void)setFont:(NSFont *)font
541 {
542     [font retain];
543     [_font release];
544     _font = font;
545     [textView setFont:font];
546 }
547
548 - (void)setTextColor:(NSColor *)color
549 {
550     [textView setTextColor:color];
551 }
552
553 - (void)setBackgroundColor:(NSColor *)color
554 {
555     [textView setBackgroundColor:color];
556 }
557
558 - (void)setDrawsBackground:(BOOL)drawsBackground
559 {
560     [super setDrawsBackground:drawsBackground];
561     [[self contentView] setDrawsBackground:drawsBackground];
562     [textView setDrawsBackground:drawsBackground];
563 }
564
565 - (BOOL)becomeFirstResponder
566 {
567     if (widget)
568         [KWQKHTMLPart::bridgeForWidget(widget) makeFirstResponder:textView];
569     return YES;
570 }
571
572 - (NSView *)nextKeyView
573 {
574     return inNextValidKeyView && widget
575         ? KWQKHTMLPart::nextKeyViewForWidget(widget, KWQSelectingNext)
576         : [super nextKeyView];
577 }
578
579 - (NSView *)previousKeyView
580 {
581    return inNextValidKeyView && widget
582         ? KWQKHTMLPart::nextKeyViewForWidget(widget, KWQSelectingPrevious)
583         : [super previousKeyView];
584 }
585
586 - (NSView *)nextValidKeyView
587 {
588     inNextValidKeyView = YES;
589     NSView *view = [super nextValidKeyView];
590     inNextValidKeyView = NO;
591     return view;
592 }
593
594 - (NSView *)previousValidKeyView
595 {
596     inNextValidKeyView = YES;
597     NSView *view = [super previousValidKeyView];
598     inNextValidKeyView = NO;
599     return view;
600 }
601
602 - (BOOL)needsPanelToBecomeKey
603 {
604     // If we don't return YES here, tabbing backwards into this view doesn't work.
605     return YES;
606 }
607
608 - (NSRect)_resizeCornerRect
609 {
610     NSRect bounds = [self bounds];
611     float cornerWidth = NSWidth([[self verticalScroller] frame]);
612 //    NSImage *cornerImage = [KWQTextArea _resizeCornerImage];
613 //    NSSize imageSize = [cornerImage size];
614     ASSERT([self borderType] == NSBezelBorder);
615     // Add one pixel to account for our border, and another to leave a pixel of whitespace at the right and
616     // bottom of the resize image to match normal resize corner appearance.
617 //    return NSMakeRect(NSMaxX(bounds) - imageSize.width - 2, NSMaxY(bounds) - imageSize.height - 2, imageSize.width + 2, imageSize.height + 2);
618     return NSMakeRect(NSMaxX(bounds) - cornerWidth, NSMaxY(bounds) - cornerWidth, cornerWidth, cornerWidth);
619 }
620
621 - (void)_trackResizeFromMouseDown:(NSEvent *)event
622 {
623     // If the cursor tracking worked perfectly, this next line wouldn't be necessary, but it would be harmless still.
624     [[NSCursor arrowCursor] set];
625     
626     WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(widget);
627     DOMHTMLTextAreaElement *element = [bridge elementForView:self];
628     ASSERT([element isKindOfClass:[DOMHTMLTextAreaElement class]]);
629     
630     KWQTextArea *textArea = self;
631     NSPoint initialLocalPoint = [self convertPoint:[event locationInWindow] fromView:nil];
632     NSSize initialTextAreaSize = [textArea frame].size;
633     
634     int minWidth = kMin((int)initialTextAreaSize.width, MinimumWidthWhileResizing);
635     int minHeight = kMin((int)initialTextAreaSize.height, MinimumHeightWhileResizing);
636     
637     BOOL handledIntrinsicMargins = NO;
638     DOMCSSStyleDeclaration *oldComputedStyle = [[element ownerDocument] getComputedStyle:element :@""];
639     NSString *oldMarginLeft = [oldComputedStyle marginLeft];
640     NSString *oldMarginRight = [oldComputedStyle marginRight];
641     NSString *oldMarginTop = [oldComputedStyle marginTop];
642     NSString *oldMarginBottom = [oldComputedStyle marginBottom];
643     
644     DOMCSSStyleDeclaration *inlineStyle = [element style];
645     
646     for (;;) {
647         if ([event type] == NSRightMouseDown || [event type] == NSRightMouseUp) {
648             // Ignore right mouse button events and remove them from the queue.
649         } else {
650             NSPoint localPoint = [self convertPoint:[event locationInWindow] fromView:nil];
651             if ([event type] == NSLeftMouseUp) {
652                 break;
653             }
654             
655             // FIXME Radar 4118559: This behaves very oddly for textareas that are in blocks with right-aligned text; you have
656             // to drag the bottom-right corner to make the bottom-left corner move.
657             // FIXME Radar 4118564: ideally we'd autoscroll the window as necessary to keep the point under
658             // the cursor in view.
659             int newWidth = kMax(minWidth, (int)(initialTextAreaSize.width + (localPoint.x - initialLocalPoint.x)));
660             int newHeight = kMax(minHeight, (int)(initialTextAreaSize.height + (localPoint.y - initialLocalPoint.y)));
661             [inlineStyle setWidth:[NSString stringWithFormat:@"%dpx", newWidth]];
662             [inlineStyle setHeight:[NSString stringWithFormat:@"%dpx", newHeight]];
663             
664             // render_form.cpp has a mechanism to use intrinsic margins on form elements under certain conditions.
665             // Setting the width or height explicitly suppresses the intrinsic margins. We don't want the user's
666             // manual resizing to affect the margins, so we check whether the margin was changed and, if so, compensat
667             // with an explicit margin that matches the old implicit one. We only need to do this once per element.
668             if (!handledIntrinsicMargins) {
669                 DOMCSSStyleDeclaration *newComputedStyle = [[element ownerDocument] getComputedStyle:element :@""];
670                 if (![oldMarginLeft isEqualToString:[newComputedStyle marginLeft]])
671                     [inlineStyle setMarginLeft:oldMarginLeft];
672                 if (![oldMarginRight isEqualToString:[newComputedStyle marginRight]])
673                     [inlineStyle setMarginRight:oldMarginRight];
674                 if (![oldMarginTop isEqualToString:[newComputedStyle marginTop]])
675                     [inlineStyle setMarginTop:oldMarginTop];
676                 if (![oldMarginBottom isEqualToString:[newComputedStyle marginBottom]])
677                     [inlineStyle setMarginBottom:oldMarginBottom];
678                 handledIntrinsicMargins = YES;
679             }
680             
681             [bridge part]->forceLayout();
682         }
683         
684         // Go get the next event.
685         event = [[self window] nextEventMatchingMask:
686             NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSRightMouseDownMask | NSRightMouseUpMask];
687     }    
688 }
689
690 - (void)mouseDown:(NSEvent *)event
691 {
692     if ([textView isEnabled] && [self _isResizableByUser]) {
693         NSPoint localPoint = [self convertPoint:[event locationInWindow] fromView:nil];
694        if (NSPointInRect(localPoint, [self _resizeCornerRect])) {
695             [self _trackResizeFromMouseDown:event];
696             return;
697         }
698     }
699     
700     [super mouseDown:event];
701 }
702
703 - (void)drawRect:(NSRect)rect
704 {
705     [super drawRect:rect];
706     
707     if (![textView isEnabled]) {
708         // draw a disabled bezel border
709         [[NSColor controlColor] set];
710         NSFrameRect(rect);
711         
712         rect = NSInsetRect(rect, 1, 1);
713         [[NSColor controlShadowColor] set];
714         NSFrameRect(rect);
715     
716         rect = NSInsetRect(rect, 1, 1);
717         [[NSColor textBackgroundColor] set];
718         NSRectFill(rect);
719     } else {
720         if ([self _isResizableByUser]) {
721             NSImage *cornerImage = [KWQTextArea _resizeCornerImage];
722             NSRect cornerRect = [self _resizeCornerRect];
723             // one pixel to account for the border; a second to add a pixel of white space between image and border
724             NSPoint imagePoint = NSMakePoint(NSMaxX(cornerRect) - [cornerImage size].width - 2, NSMaxY(cornerRect) - 2);
725             [cornerImage compositeToPoint:imagePoint operation:NSCompositeSourceOver];            
726             // FIXME 4129417: we probably want some sort of border on the left side here, so the resize image isn't
727             // floating in space. Maybe we want to use a slightly larger resize image here that fits the scroller
728             // width better.
729         }
730         if (widget && [KWQKHTMLPart::bridgeForWidget(widget) firstResponder] == textView) {
731             NSSetFocusRingStyle(NSFocusRingOnly);
732             NSRectFill([self bounds]);
733         }
734     }
735 }
736
737 - (void)_KWQ_setKeyboardFocusRingNeedsDisplay
738 {
739     [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]];
740 }
741
742 - (QWidget *)widget
743 {
744     return widget;
745 }
746
747 - (void)setAlignment:(NSTextAlignment)alignment
748 {
749     [textView setAlignment:alignment];
750 }
751
752 - (void)setBaseWritingDirection:(NSWritingDirection)direction
753 {
754     // Set the base writing direction for typing.
755     NSParagraphStyle *style = [textView _KWQ_typingParagraphStyle];
756     if ([style baseWritingDirection] != direction) {
757         NSMutableParagraphStyle *mutableStyle = [style mutableCopy];
758         [mutableStyle setBaseWritingDirection:direction];
759         [textView _KWQ_setTypingParagraphStyle:mutableStyle];
760         [mutableStyle release];
761     }
762
763     // Set the base writing direction for text.
764     [[textView textStorage] _KWQ_setBaseWritingDirection:direction];
765     [textView setNeedsDisplay:YES];
766 }
767
768 // This is the only one of the display family of calls that we use, and the way we do
769 // displaying in WebCore means this is called on this NSView explicitly, so this catches
770 // all cases where we are inside the normal display machinery. (Used only by the insertion
771 // point method below.)
772 - (void)displayRectIgnoringOpacity:(NSRect)rect
773 {
774     inDrawingMachinery = YES;
775     [super displayRectIgnoringOpacity:rect];
776     inDrawingMachinery = NO;
777 }
778
779 // Use the "needs display" mechanism to do all insertion point drawing in the web view.
780 - (BOOL)textView:(NSTextView *)view shouldDrawInsertionPointInRect:(NSRect)rect color:(NSColor *)color turnedOn:(BOOL)drawInsteadOfErase
781 {
782     // We only need to take control of the cases where we are being asked to draw by something
783     // outside the normal display machinery, and when we are being asked to draw the insertion
784     // point, not erase it.
785     if (inDrawingMachinery || !drawInsteadOfErase) {
786         return YES;
787     }
788
789     // NSTextView's insertion-point drawing code sets the rect width to 1.
790     // So we do the same thing, to affect exactly the same rectangle.
791     rect.size.width = 1;
792
793     // Call through to the setNeedsDisplayInRect implementation in NSView.
794     // If we call the one in NSTextView through the normal method dispatch
795     // we will reenter the caret blinking code and end up with a nasty crash
796     // (see Radar 3250608).
797     SEL selector = @selector(setNeedsDisplayInRect:);
798     typedef void (*IMPWithNSRect)(id, SEL, NSRect);
799     IMPWithNSRect implementation = (IMPWithNSRect)[NSView instanceMethodForSelector:selector];
800     implementation(view, selector, rect);
801
802     return NO;
803 }
804
805 - (NSSize)sizeWithColumns:(int)numColumns rows:(int)numRows
806 {
807     // Must use font from _font field rather than from the text view's font method,
808     // because the text view will return a substituted font if the first character in
809     // the text view requires font substitution, and we don't want the size to depend on
810     // the text in the text view.
811     
812     NSSize textSize = NSMakeSize(ceil(numColumns * [_font widthOfString:@"0"]), numRows * [_font defaultLineHeightForFont]);
813     NSSize textContainerSize = NSMakeSize(textSize.width + [[textView textContainer] lineFragmentPadding] * 2, textSize.height);
814     NSSize textContainerInset = [textView textContainerInset];
815     NSSize textViewSize = NSMakeSize(textContainerSize.width + textContainerInset.width, textContainerSize.height + textContainerInset.height); 
816     return [[self class] frameSizeForContentSize:textViewSize
817         hasHorizontalScroller:[self hasHorizontalScroller]
818         hasVerticalScroller:[self hasVerticalScroller]
819         borderType:[self borderType]];
820 }
821
822 - (void)viewWillMoveToWindow:(NSWindow *)window
823 {
824     if ([self window] != window) {
825         [[textView undoManager] removeAllActionsWithTarget:[textView textStorage]];
826     }
827     [super viewWillMoveToWindow:window];
828 }
829
830 @end
831
832 @implementation KWQTextAreaTextView
833
834 static BOOL _spellCheckingInitiallyEnabled = NO;
835 static NSString *WebContinuousSpellCheckingEnabled = @"WebContinuousSpellCheckingEnabled";
836
837 + (void)_setContinuousSpellCheckingEnabledForNewTextAreas:(BOOL)flag
838 {
839     _spellCheckingInitiallyEnabled = flag;
840     [[NSUserDefaults standardUserDefaults] setBool:flag forKey:WebContinuousSpellCheckingEnabled];
841 }
842
843 + (BOOL)_isContinuousSpellCheckingEnabledForNewTextAreas
844 {
845     static BOOL _checkedUserDefault = NO;
846     if (!_checkedUserDefault) {
847         _spellCheckingInitiallyEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebContinuousSpellCheckingEnabled];
848         _checkedUserDefault = YES;
849     }
850
851     return _spellCheckingInitiallyEnabled;
852 }
853
854 - (id)initWithFrame:(NSRect)frame textContainer:(NSTextContainer *)aTextContainer
855 {
856     self = [super initWithFrame:frame textContainer:aTextContainer];
857     [super setContinuousSpellCheckingEnabled:
858         [[self class] _isContinuousSpellCheckingEnabledForNewTextAreas]];
859
860     editableIfEnabled = YES;
861
862     return self;
863 }
864
865 - (void)setContinuousSpellCheckingEnabled:(BOOL)flag
866 {
867     [[self class] _setContinuousSpellCheckingEnabledForNewTextAreas:flag];
868     [super setContinuousSpellCheckingEnabled:flag];
869 }
870
871
872 - (void)setWidget:(QTextEdit *)w
873 {
874     widget = w;
875 }
876
877 - (void)insertTab:(id)sender
878 {
879     NSView *view = [[self delegate] nextValidKeyView];
880     if (view && view != self && view != [self delegate] && widget) {
881         [KWQKHTMLPart::bridgeForWidget(widget) makeFirstResponder:view];
882     }
883 }
884
885 - (void)insertBacktab:(id)sender
886 {
887     NSView *view = [[self delegate] previousValidKeyView];
888     if (view && view != self && view != [self delegate] && widget) {
889         [KWQKHTMLPart::bridgeForWidget(widget) makeFirstResponder:view];
890     }
891 }
892
893 - (BOOL)becomeFirstResponder
894 {
895     if (disabled)
896         return NO;
897
898     ++inResponderChange;
899
900     BOOL become = [super becomeFirstResponder];
901     
902     if (become) {
903         // Select all the text if we are tabbing in, but otherwise preserve/remember
904         // the selection from last time we had focus (to match WinIE).
905         if ([[self window] keyViewSelectionDirection] != NSDirectSelection) {
906             [self selectAll:nil];
907         }
908     }
909
910     --inResponderChange;
911
912     if (become) {
913         if (!KWQKHTMLPart::currentEventIsMouseDownInWidget(widget)) {
914             [[self enclosingScrollView] _KWQ_scrollFrameToVisible];
915         }
916         [self _KWQ_setKeyboardFocusRingNeedsDisplay];
917         if (widget) {
918             QFocusEvent event(QEvent::FocusIn);
919             const_cast<QObject *>(widget->eventFilterObject())->eventFilter(widget, &event);
920         }
921     }
922
923     return become;
924 }
925
926 - (BOOL)resignFirstResponder
927 {
928     ++inResponderChange;
929
930     BOOL resign = [super resignFirstResponder];
931
932     --inResponderChange;
933
934     if (resign) {
935         [self _KWQ_setKeyboardFocusRingNeedsDisplay];
936
937         if (widget) {
938             QFocusEvent event(QEvent::FocusOut);
939             const_cast<QObject *>(widget->eventFilterObject())->eventFilter(widget, &event);
940         }
941     }
942
943     return resign;
944 }
945
946 - (BOOL)shouldDrawInsertionPoint
947 {
948     return widget && self == [KWQKHTMLPart::bridgeForWidget(widget) firstResponder] && [super shouldDrawInsertionPoint];
949 }
950
951 - (NSDictionary *)selectedTextAttributes
952 {
953     if (widget && self != [KWQKHTMLPart::bridgeForWidget(widget) firstResponder])
954         return nil;
955     return [super selectedTextAttributes];
956 }
957
958 - (void)scrollPageUp:(id)sender
959 {
960     // After hitting the top, tell our parent to scroll
961     float oldY = [[[self enclosingScrollView] contentView] bounds].origin.y;
962     [super scrollPageUp:sender];
963     if (oldY == [[[self enclosingScrollView] contentView] bounds].origin.y) {
964         [[self nextResponder] tryToPerform:@selector(scrollPageUp:) with:nil];
965     }
966 }
967
968 - (void)scrollPageDown:(id)sender
969 {
970     // After hitting the bottom, tell our parent to scroll
971     float oldY = [[[self enclosingScrollView] contentView] bounds].origin.y;
972     [super scrollPageDown:sender];
973     if (oldY == [[[self enclosingScrollView] contentView] bounds].origin.y) {
974         [[self nextResponder] tryToPerform:@selector(scrollPageDown:) with:nil];
975     }
976 }
977
978 - (QWidget *)widget
979 {
980     return widget;
981 }
982
983 - (KWQTextArea *)_enclosingTextArea
984 {
985     KWQTextArea *textArea = [[self superview] superview];
986     ASSERT([textArea isKindOfClass:[KWQTextArea class]]);
987     return textArea;
988 }
989
990 - (NSRect)_resizeCornerRect
991 {
992     NSClipView *clipView = [self superview];
993     NSRect visibleRect = [clipView documentVisibleRect];
994     NSImage *cornerImage = [KWQTextArea _resizeCornerImage];
995     NSSize imageSize = [cornerImage size];
996     // Add one pixel of whitespace at right and bottom of image to match normal resize corner appearance.
997     // This could be built into the image, alternatively.
998     return NSMakeRect(NSMaxX(visibleRect) - imageSize.width - 1, NSMaxY(visibleRect) - imageSize.height - 1, imageSize.width + 1, imageSize.height + 1);
999 }
1000
1001 - (void)resetCursorRects
1002 {
1003     [super resetCursorRects];
1004     
1005     // FIXME Radar 4118575: This is intended to change the cursor to the arrow cursor whenever it is
1006     // over the resize corner. However, it currently only works when the cursor had
1007     // been inside the textarea, presumably due to interactions with the way NSTextView
1008     // sets the cursor via [NSClipView setDocumentCursor:]. Also, it stops working once
1009     // the textview has been resized, for reasons not yet understood.
1010     // FIXME: need to test that the cursor rect we add here is removed when the return value of _textViewShouldHandleResizing
1011     // changes for any reason
1012     if (!NSIsEmptyRect([self visibleRect]) && [[self _enclosingTextArea] _textViewShouldHandleResizing]) {
1013         [self addCursorRect:[self _resizeCornerRect] cursor:[NSCursor arrowCursor]];
1014     }
1015 }
1016
1017 - (void)drawRect:(NSRect)rect
1018 {
1019     [super drawRect:rect];
1020     
1021     if ([[self _enclosingTextArea] _textViewShouldHandleResizing]) {
1022         NSImage *cornerImage = [KWQTextArea _resizeCornerImage];
1023         NSPoint imagePoint = [self _resizeCornerRect].origin;
1024         imagePoint.y += [cornerImage size].height;
1025         [cornerImage compositeToPoint:imagePoint operation:NSCompositeSourceOver];
1026     }
1027 }
1028
1029 - (void)mouseDown:(NSEvent *)event
1030 {
1031     if (disabled)
1032         return;
1033     
1034     if ([[self _enclosingTextArea] _textViewShouldHandleResizing]) {
1035         NSPoint localPoint = [self convertPoint:[event locationInWindow] fromView:nil];
1036         // FIXME Radar 4118599: With this "bottom right corner" design, we might want to distinguish between a click in text
1037         // and a drag-to-resize. This code currently always does the drag-to-resize behavior.
1038         if (NSPointInRect(localPoint, [self _resizeCornerRect])) {
1039             [[self _enclosingTextArea] _trackResizeFromMouseDown:event];
1040             return;
1041         }
1042     }
1043     
1044     [super mouseDown:event];
1045     if (widget) {
1046         widget->sendConsumedMouseUp();
1047     }
1048     if (widget) {
1049         widget->clicked();
1050     }
1051 }
1052
1053 - (void)keyDown:(NSEvent *)event
1054 {
1055     if (disabled || !widget) {
1056         return;
1057     }
1058     
1059     // Don't mess with text marked by an input method
1060     if ([[NSInputManager currentInputManager] hasMarkedText]) {
1061         [super keyDown:event];
1062         return;
1063     }
1064
1065     WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(widget);
1066     if ([bridge interceptKeyEvent:event toView:self]) {
1067         return;
1068     }
1069     
1070     // Don't let option-tab insert a character since we use it for
1071     // tabbing between links
1072     if (KWQKHTMLPart::handleKeyboardOptionTabInView(self)) {
1073         return;
1074     }
1075     
1076     [super keyDown:event];
1077 }
1078
1079 - (void)keyUp:(NSEvent *)event
1080 {
1081     if (disabled || !widget)
1082         return;
1083
1084     WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(widget);
1085     if (![[NSInputManager currentInputManager] hasMarkedText]) {
1086         [bridge interceptKeyEvent:event toView:self];
1087     }
1088     // Don't call super because NSTextView will simply pass the
1089     // event along the responder chain. This is arguably a bug in
1090     // NSTextView; see Radar 3507083.
1091 }
1092
1093 - (void)setEnabled:(BOOL)flag
1094 {
1095     if (disabled == !flag) {
1096         return;
1097     }
1098
1099     disabled = !flag;
1100     if (editableIfEnabled) {
1101         [self setEditable:!disabled];
1102     }
1103     [self updateTextColor];
1104 }
1105
1106 - (BOOL)isEnabled
1107 {
1108     return !disabled;
1109 }
1110
1111 - (void)setEditableIfEnabled:(BOOL)flag
1112 {
1113     editableIfEnabled = flag;
1114     if (!disabled) {
1115         [self setEditable:editableIfEnabled];
1116     }
1117 }
1118
1119 - (BOOL)isEditableIfEnabled
1120 {
1121     return editableIfEnabled;
1122 }
1123
1124 - (void)updateTextColor
1125 {
1126     // Make the text look disabled by changing its color.
1127     NSColor *color = disabled ? [NSColor disabledControlTextColor] : [NSColor controlTextColor];
1128     [[self textStorage] setForegroundColor:color];
1129 }
1130
1131 // Could get fancy and send this to QTextEdit, then RenderTextArea, but there's really no harm
1132 // in doing this directly right here. Could refactor some day if you disagree.
1133
1134 // FIXME: This does not yet implement the feature of canceling the operation, or the necessary
1135 // support to implement the clipboard operations entirely in JavaScript.
1136 - (void)dispatchHTMLEvent:(EventImpl::EventId)eventID
1137 {
1138     if (widget) {
1139         const RenderWidget *rw = static_cast<const RenderWidget *>(widget->eventFilterObject());
1140         if (rw) {
1141             NodeImpl *node = rw->element();
1142             if (node) {
1143                 node->dispatchHTMLEvent(eventID, false, false);
1144             }
1145         }
1146     }
1147 }
1148
1149 - (void)cut:(id)sender
1150 {
1151     inCut = YES;
1152     [self dispatchHTMLEvent:EventImpl::BEFORECUT_EVENT];
1153     [super cut:sender];
1154     [self dispatchHTMLEvent:EventImpl::CUT_EVENT];
1155     inCut = NO;
1156 }
1157
1158 - (void)copy:(id)sender
1159 {
1160     if (!inCut)
1161         [self dispatchHTMLEvent:EventImpl::BEFORECOPY_EVENT];
1162     [super copy:sender];
1163     if (!inCut)
1164         [self dispatchHTMLEvent:EventImpl::COPY_EVENT];
1165 }
1166
1167 - (void)paste:(id)sender
1168 {
1169     [self dispatchHTMLEvent:EventImpl::BEFOREPASTE_EVENT];
1170     [super paste:sender];
1171     [self dispatchHTMLEvent:EventImpl::PASTE_EVENT];
1172 }
1173
1174 - (void)pasteAsPlainText:(id)sender
1175 {
1176     [self dispatchHTMLEvent:EventImpl::BEFOREPASTE_EVENT];
1177     [super pasteAsPlainText:sender];
1178     [self dispatchHTMLEvent:EventImpl::PASTE_EVENT];
1179 }
1180
1181 - (void)pasteAsRichText:(id)sender
1182 {
1183     [self dispatchHTMLEvent:EventImpl::BEFOREPASTE_EVENT];
1184     [super pasteAsRichText:sender];
1185     [self dispatchHTMLEvent:EventImpl::PASTE_EVENT];
1186 }
1187
1188 - (BOOL)inResponderChange
1189 {
1190     return inResponderChange != 0;
1191 }
1192
1193 @end
1194
1195 @implementation NSView (KWQTextArea)
1196
1197 - (void)_KWQ_setKeyboardFocusRingNeedsDisplay
1198 {
1199     [[self superview] _KWQ_setKeyboardFocusRingNeedsDisplay];
1200 }
1201
1202 @end
1203
1204 @implementation NSTextView (KWQTextArea)
1205
1206 - (NSParagraphStyle *)_KWQ_typingParagraphStyle
1207 {
1208     NSParagraphStyle *style = [[self typingAttributes] objectForKey:NSParagraphStyleAttributeName];
1209     if (style != nil) {
1210         return style;
1211     }
1212     style = [self defaultParagraphStyle];
1213     if (style != nil) {
1214         return style;
1215     }
1216     return [NSParagraphStyle defaultParagraphStyle];
1217 }
1218
1219 - (void)_KWQ_setTypingParagraphStyle:(NSParagraphStyle *)style
1220 {
1221     NSParagraphStyle *immutableStyle = [style copy];
1222     NSDictionary *attributes = [self typingAttributes];
1223     if (attributes != nil) {
1224         NSMutableDictionary *mutableAttributes = [attributes mutableCopy];
1225         [mutableAttributes setObject:immutableStyle forKey:NSParagraphStyleAttributeName];
1226         attributes = mutableAttributes;
1227     } else {
1228         attributes = [[NSDictionary alloc] initWithObjectsAndKeys:immutableStyle, NSParagraphStyleAttributeName, nil];
1229     }
1230     [immutableStyle release];
1231     [self setTypingAttributes:attributes];
1232     [attributes release];
1233 }
1234
1235 @end
1236
1237 @implementation NSTextStorage (KWQTextArea)
1238
1239 - (void)_KWQ_setBaseWritingDirection:(NSWritingDirection)direction
1240 {
1241     unsigned end = [self length];
1242     NSRange range = NSMakeRange(0, end);
1243     if (end != 0) {
1244         [self beginEditing];
1245         for (unsigned i = 0; i < end; ) {
1246             NSRange effectiveRange;
1247             NSParagraphStyle *style = [self attribute:NSParagraphStyleAttributeName atIndex:i longestEffectiveRange:&effectiveRange inRange:range];
1248             if (style == nil) {
1249                 style = [NSParagraphStyle defaultParagraphStyle];
1250             }
1251             if ([style baseWritingDirection] != direction) {
1252                 NSMutableParagraphStyle *mutableStyle = [style mutableCopy];
1253                 [mutableStyle setBaseWritingDirection:direction];
1254                 [self addAttribute:NSParagraphStyleAttributeName value:mutableStyle range:effectiveRange];
1255                 [mutableStyle release];
1256             }
1257             i = NSMaxRange(effectiveRange);
1258         }
1259         [self endEditing];
1260     }
1261 }
1262
1263 @end