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