API  0.9.10
CPTextView.j
Go to the documentation of this file.
1 /*
2  * CPTextView.j
3  * AppKit
4  *
5  * Created by Daniel Boehringer on 27/12/2013.
6  * All modifications copyright Daniel Boehringer 2013.
7  * Extensive code formatting and review by Andrew Hankinson
8  * Based on original work by
9  * Created by Emmanuel Maillard on 27/02/2010.
10  * Copyright Emmanuel Maillard 2010.
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2.1 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25  */
26 
27 
28 
29 
30 
31 @protocol CPTextViewDelegate <CPTextDelegate>
32 
33 - (BOOL)textView:(CPTextView)aTextView doCommandBySelector:(SEL)aSelector;
34 - (BOOL)textView:(CPTextView)aTextView shouldChangeTextInRange:(CPRange)affectedCharRange replacementString:(CPString)replacementString;
35 - (CPDictionary)textView:(CPTextView)textView shouldChangeTypingAttributes:(CPDictionary)oldTypingAttributes toAttributes:(CPDictionary)newTypingAttributes;
36 - (CPRange)textView:(CPTextView)aTextView willChangeSelectionFromCharacterRange:(CPRange)oldSelectedCharRange toCharacterRange:(CPRange)newSelectedCharRange;
37 - (void)textViewDidChangeSelection:(CPNotification)aNotification;
38 - (void)textViewDidChangeTypingAttributes:(CPNotification)aNotification;
39 
40 @end
41 
42 _MakeRangeFromAbs = function(a1, a2)
43 {
44  return (a1 < a2) ? CPMakeRange(a1, a2 - a1) : CPMakeRange(a2, a1 - a2);
45 };
46 
47 _MidRange = function(a1)
48 {
49  return Math.floor((CPMaxRange(a1) + a1.location) / 2);
50 };
51 
52 function _isWhitespaceCharacter(chr)
53 {
54  return (chr === '\n' || chr === '\r' || chr === ' ' || chr === '\t');
55 }
56 
57 _characterTripletFromStringAtIndex = function(string, index)
58 {
59  if ([string isKindOfClass:CPAttributedString])
60  string = string._string;
61 
62  var tripletRange = _MakeRangeFromAbs(MAX(0, index - 1), MIN(string.length, index + 2));
63 
64  return [string substringWithRange:tripletRange];
65 }
66 
67 _regexMatchesStringAtIndex=function(regex, string, index)
68 {
69  var triplet = _characterTripletFromStringAtIndex(string, index);
70 
71  return regex.exec(triplet) !== null;
72 }
73 
74 
75 /*
76  CPSelectionGranularity
77 */
78 @typedef CPSelectionGranularity
79 CPSelectByCharacter = 0;
82 
91 
92 
96 @implementation CPTextView : CPText
97 {
98  BOOL _allowsUndo;
99  BOOL _isHorizontallyResizable;
100  BOOL _isVerticallyResizable;
101  BOOL _usesFontPanel;
102  CGPoint _textContainerOrigin;
103  CGSize _minSize;
104  CGSize _maxSize;
105  CGSize _textContainerInset;
106  CPColor _insertionPointColor;
107  CPColor _textColor;
108  CPDictionary _selectedTextAttributes;
109  CPDictionary _typingAttributes;
110  CPFont _font;
111  CPLayoutManager _layoutManager;
112  CPRange _selectionRange;
113  CPSelectionGranularity _selectionGranularity;
114 
115  CPSelectionGranularity _previousSelectionGranularity; // private
116  CPSelectionGranularity _copySelectionGranularity; // private
117 
118  CPTextContainer _textContainer;
119  CPTextStorage _textStorage;
120  id <CPTextViewDelegate> _delegate @accessors(property=delegate);
121 
122  unsigned _delegateRespondsToSelectorMask;
123 
124  int _startTrackingLocation;
125 
126  _CPCaret _caret;
127  CPTimer _scrollingTimer;
128 
129  BOOL _scrollingDownward;
130  CPRange _movingSelection;
131 
132  int _stickyXLocation;
133 
134  CPArray _selectionSpans;
135  CPView _observedClipView;
136  CGRect _exposedRect;
137 
138  CPTimer _scrollingTimer;
139 
140  CPString _placeholderString;
141 }
142 
143 + (Class)_binderClassForBinding:(CPString)aBinding
144 {
145  if (aBinding === CPValueBinding || aBinding === CPAttributedStringBinding)
146  return [_CPTextViewValueBinder class];
147 
148  return [super _binderClassForBinding:aBinding];
149 }
150 
151 #pragma mark -
152 #pragma mark Class methods
153 
154 /* <!> FIXME
155  just a testing characterSet
156  all of this depend of the current language.
157  Need some CPLocale support and maybe even a FSM...
158  */
159 
160 #pragma mark -
161 #pragma mark Init methods
162 
163 - (id)initWithFrame:(CGRect)aFrame textContainer:(CPTextContainer)aContainer
164 {
165  if (self = [super initWithFrame:aFrame])
166  {
167  [self _init];
168  [aContainer setTextView:self];
169 
170  [self setEditable:YES];
171  [self setSelectable:YES];
172  [self setRichText:NO];
174 
175  _usesFontPanel = YES;
176  _allowsUndo = YES;
177 
179  forKey:CPBackgroundColorAttributeName];
180 
181  _insertionPointColor = [CPColor blackColor];
182 
183  _textColor = [CPColor blackColor];
184  _font = [CPFont systemFontOfSize:12.0];
185  [self setFont:_font];
186 
187  _typingAttributes = [[CPDictionary alloc] initWithObjects:[_font, _textColor] forKeys:[CPFontAttributeName, CPForegroundColorAttributeName]];
188  }
189 
190  return self;
191 }
192 
193 - (id)initWithFrame:(CGRect)aFrame
194 {
195  var container = [[CPTextContainer alloc] initWithContainerSize:CGSizeMake(aFrame.size.width, 1e7)];
196 
197  return [self initWithFrame:aFrame textContainer:container];
198 }
199 
200 - (void)_init
201 {
202 #if PLATFORM(DOM)
203  _DOMElement.style.cursor = "text";
204 #endif
205 
206  _selectionRange = CPMakeRange(0, 0);
207  _textContainerInset = CGSizeMake(2, 0);
208  _textContainerOrigin = CGPointMake(_bounds.origin.x, _bounds.origin.y);
209 
210  _selectionGranularity = CPSelectByCharacter;
211 
212  _minSize = CGSizeCreateCopy(_frame.size);
213  _maxSize = CGSizeMake(_frame.size.width, 1e7);
214 
215  _isVerticallyResizable = YES;
216  _isHorizontallyResizable = NO;
217 
218  _typingAttributes = [CPMutableDictionary new];
219  _selectedTextAttributes = [CPMutableDictionary new];
220 
221  _caret = [[_CPCaret alloc] initWithTextView:self];
222  [_caret setRect:CGRectMake(0, 0, 1, 11)]
223 
224  var pboardTypes = [CPStringPboardType, CPColorDragType];
225 
226  if ([self isRichText])
227  pboardTypes.push(CPRTFPboardType);
228 
229  [self registerForDraggedTypes:pboardTypes];
230 }
231 
232 - (void)_setObserveWindowKeyNotifications:(BOOL)shouldObserve
233 {
234  if (shouldObserve)
235  {
236  [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_windowDidResignKey:) name:CPWindowDidResignKeyNotification object:[self window]];
237  [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_windowDidBecomeKey:) name:CPWindowDidBecomeKeyNotification object:[self window]];
238  }
239  else
240  {
241  [[CPNotificationCenter defaultCenter] removeObserver:self name:CPWindowDidResignKeyNotification object:[self window]];
242  [[CPNotificationCenter defaultCenter] removeObserver:self name:CPWindowDidBecomeKeyNotification object:[self window]];
243  }
244 }
245 
246 - (void)_removeObservers
247 {
248  if (!_isObserving)
249  return;
250 
251  [super _removeObservers];
252  [self _setObserveWindowKeyNotifications:NO];
253 }
254 
255 - (void)_addObservers
256 {
257  if (_isObserving)
258  return;
259 
260  [super _addObservers];
261  [self _setObserveWindowKeyNotifications:YES];
262  [self _startObservingClipView];
263 }
264 - (void)_startObservingClipView
265 {
266  if (!_observedClipView)
267  return;
268 
269  var defaultCenter = [CPNotificationCenter defaultCenter];
270 
271  [_observedClipView setPostsFrameChangedNotifications:YES];
272  [_observedClipView setPostsBoundsChangedNotifications:YES];
273 
274  [defaultCenter addObserver:self
275  selector:@selector(superviewFrameChanged:)
276  name:CPViewFrameDidChangeNotification
277  object:_observedClipView];
278 
279  [defaultCenter addObserver:self
280  selector:@selector(superviewBoundsChanged:)
281  name:CPViewBoundsDidChangeNotification
282  object:_observedClipView];
283 }
284 - (CGRect)exposedRect
285 {
286  if (!_exposedRect)
287  {
288  var superview = [self superview];
289 
290  if ([superview isKindOfClass:[CPClipView class]])
291  _exposedRect = [superview bounds];
292  else
293  _exposedRect = [self bounds];
294  }
295 
296  return _exposedRect;
297 }
298 
302 - (void)superviewBoundsChanged:(CPNotification)aNotification
303 {
304  _exposedRect = nil;
305  [self setNeedsDisplay:YES];
306 }
307 
311 - (void)superviewFrameChanged:(CPNotification)aNotification
312 {
313  _exposedRect = nil;
314 }
315 
316 - (void)viewWillMoveToSuperview:(CPView)aView
317 {
318  if ([aView isKindOfClass:[CPClipView class]])
319  _observedClipView = aView;
320  else
321  [self _stopObservingClipView];
322 
323  [super viewWillMoveToSuperview:aView];
324 }
325 
326 - (void)_stopObservingClipView
327 {
328  if (!_observedClipView)
329  return;
330 
331  var defaultCenter = [CPNotificationCenter defaultCenter];
332 
333  [defaultCenter removeObserver:self
334  name:CPViewFrameDidChangeNotification
335  object:_observedClipView];
336 
337  [defaultCenter removeObserver:self
338  name:CPViewBoundsDidChangeNotification
339  object:_observedClipView];
340 
341  _observedClipView = nil;
342 }
343 
344 - (void)_windowDidResignKey:(CPNotification)aNotification
345 {
346  if (![[self window] isKeyWindow])
347  [self resignFirstResponder];
348 }
349 
350 - (void)_windowDidBecomeKey:(CPNotification)aNotification
351 {
352  if ([self _isFocused])
353  [self _becomeFirstResponder];
354 }
355 
356 #pragma mark -
357 #pragma mark Copy and paste methods
358 
359 - (void)copy:(id)sender
360 {
361  _copySelectionGranularity = _previousSelectionGranularity;
362  [super copy:sender];
363 
364  if (![self isRichText])
365  return;
366 
367  var selectedRange = [self selectedRange],
368  pasteboard = [CPPasteboard generalPasteboard],
369  stringForPasting = [[self textStorage] attributedSubstringFromRange:CPMakeRangeCopy(selectedRange)],
370  richData = [_CPRTFProducer produceRTF:stringForPasting documentAttributes:@{}];
371 
372  [pasteboard declareTypes:[CPStringPboardType, CPRTFPboardType] owner:nil];
373  [pasteboard setString:stringForPasting._string forType:CPStringPboardType];
374  [pasteboard setString:richData forType:CPRTFPboardType];
375 }
376 
377 - (void)_pasteString:(id)stringForPasting
378 {
379  if (!stringForPasting)
380  return;
381 
382  if (_copySelectionGranularity > 0 && _selectionRange.location > 0)
383  {
384  if (!_isWhitespaceCharacter([[_textStorage string] characterAtIndex:_selectionRange.location - 1]) &&
385  _selectionRange.location != [_layoutManager numberOfCharacters])
386  {
387  [self insertText:" "];
388  }
389  }
390 
391  if (_copySelectionGranularity == CPSelectByParagraph)
392  {
393  var peekStr = stringForPasting,
394  i = 0;
395 
396  if (![stringForPasting isKindOfClass:[CPString class]])
397  peekStr = stringForPasting._string;
398 
399  while (_isWhitespaceCharacter([peekStr characterAtIndex:i]))
400  i++;
401 
402  if (i)
403  {
404  if ([stringForPasting isKindOfClass:[CPString class]])
405  stringForPasting = [stringForPasting stringByReplacingCharactersInRange:CPMakeRange(0, i) withString:''];
406  else
407  [stringForPasting replaceCharactersInRange:CPMakeRange(0, i) withString:''];
408  }
409  }
410 
411  [self insertText:stringForPasting];
412 
413  if (_copySelectionGranularity > 0)
414  {
415  if (!_isWhitespaceCharacter([[_textStorage string] characterAtIndex:CPMaxRange(_selectionRange)]) &&
416  !_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, _selectionRange.location - 1)]) &&
417  _selectionRange.location != [_layoutManager numberOfCharacters])
418  {
419  [self insertText:" "];
420  }
421  }
422 }
423 - (void)pasteAsPlainText:(id)sender
424 {
425  if (![sender isKindOfClass:_CPNativeInputManager] && [[CPApp currentEvent] type] != CPAppKitDefined)
426  return
427 
428  [self _pasteString:[self _plainStringForPasting]];
429 }
430 
431 - (void)paste:(id)sender
432 {
433  if (![sender isKindOfClass:_CPNativeInputManager] && [[CPApp currentEvent] type] != CPAppKitDefined)
434  return
435 
436  [self _pasteString:[self _stringForPasting]];
437 }
438 
439 #pragma mark -
440 #pragma mark Responders method
441 
443 {
444  return [self isSelectable]; // editable textviews are automatically selectable
445 }
446 
447 - (void)_becomeFirstResponder
448 {
450  [[CPFontManager sharedFontManager] setSelectedFont:[self font] isMultiple:NO];
451  [self setNeedsDisplay:YES];
452  [[CPRunLoop currentRunLoop] performSelector:@selector(focusForTextView:) target:[_CPNativeInputManager class] argument:self order:0 modes:[CPDefaultRunLoopMode]];
453 }
454 
455 
457 {
458  [super becomeFirstResponder];
459  [self _becomeFirstResponder];
460 
461  return YES;
462 }
463 
465 {
466  [self _reverseSetBinding];
467  [_caret stopBlinking];
468  [self setNeedsDisplay:YES];
469  [_CPNativeInputManager cancelCurrentInputSessionIfNeeded];
470 
471  return YES;
472 }
473 
474 
475 #pragma mark -
476 #pragma mark Delegate methods
477 
481 - (void)setDelegate:(id <CPTextViewDelegate>)aDelegate
482 {
483  if (aDelegate === _delegate)
484  return;
485 
486  _delegateRespondsToSelectorMask = 0;
487  _delegate = aDelegate;
488 
489  if (_delegate)
490  {
491  if ([_delegate respondsToSelector:@selector(textDidChange:)])
492  _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_textDidChange;
493 
494  if ([_delegate respondsToSelector:@selector(textViewDidChangeSelection:)])
495  _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_didChangeSelection;
496 
497  if ([_delegate respondsToSelector:@selector(textViewDidChangeTypingAttributes:)])
498  _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_didChangeTypingAttributes;
499 
500  if ([_delegate respondsToSelector:@selector(textView:doCommandBySelector:)])
501  _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_doCommandBySelector;
502 
503  if ([_delegate respondsToSelector:@selector(textShouldBeginEditing:)])
504  _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textShouldBeginEditing;
505 
506  if ([_delegate respondsToSelector:@selector(textView:willChangeSelectionFromCharacterRange:toCharacterRange:)])
508 
509  if ([_delegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementString:)])
511 
512  if ([_delegate respondsToSelector:@selector(textView:shouldChangeTypingAttributes:toAttributes:)])
514  }
515 }
516 
517 
518 #pragma mark -
519 #pragma mark Key window methods
520 
522 {
523  [self setNeedsDisplay:YES];
524 }
525 
530 {
531  [self setNeedsDisplay:YES];
532 }
533 
534 - (BOOL)_isFirstResponder
535 {
536  return [[self window] firstResponder] === self;
537 }
538 
539 - (BOOL)_isFocused
540 {
541  return [[self window] isKeyWindow] && [self _isFirstResponder];
542 }
543 
544 
545 #pragma mark -
546 #pragma mark Undo redo methods
547 
548 - (void)undo:(id)sender
549 {
550  if (_allowsUndo)
551  [[[self window] undoManager] undo];
552 }
553 
554 - (void)redo:(id)sender
555 {
556  if (_allowsUndo)
557  [[[self window] undoManager] redo];
558 }
559 
560 
561 #pragma mark -
562 #pragma mark Accessors
563 
565 {
566  return _textStorage._string;
567 }
568 
570 {
571  if (_placeholderString)
572  return nil;
573 
574  return [self isRichText]? _textStorage : _textStorage._string;
575 }
576 - (void)setObjectValue:(id)aValue
577 {
578  if (_placeholderString)
579  return;
580 
581  if (!aValue)
582  aValue = @"";
583 
584  if (![aValue isKindOfClass:[CPAttributedString class]] && ![aValue isKindOfClass:[CPString class]] && [aValue respondsToSelector:@selector(description)])
585  aValue = [aValue description];
586 
587  [self setString:aValue];
588 }
589 
590 - (void)setString:(id)aString
591 {
592  if ([aString isKindOfClass:[CPAttributedString class]])
593  {
594  [_textStorage replaceCharactersInRange:CPMakeRange(0, [_layoutManager numberOfCharacters]) withAttributedString:aString];
595  }
596  else
597  {
598  [_textStorage replaceCharactersInRange:CPMakeRange(0, [_layoutManager numberOfCharacters]) withString:aString];
599  }
600 
601  if (CPMaxRange(_selectionRange) > [_layoutManager numberOfCharacters])
602  [self setSelectedRange:CPMakeRange([_layoutManager numberOfCharacters], 0)];
603 
604  [self didChangeText];
605  [_layoutManager _validateLayoutAndGlyphs];
606  [self sizeToFit];
607  [self setNeedsDisplay:YES];
608 }
609 
611 {
612  return [_textStorage string];
613 }
614 
615 - (void)setTextContainer:(CPTextContainer)aContainer
616 {
617  _textContainer = aContainer;
618  _layoutManager = [_textContainer layoutManager];
619  _textStorage = [_layoutManager textStorage];
620  [_textStorage setFont:[self font]];
621  [_textStorage setForegroundColor:_textColor];
622 
624 }
625 
626 - (void)setTextContainerInset:(CGSize)aSize
627 {
628  _textContainerInset = aSize;
630 }
631 
633 {
634  _textContainerOrigin.x = _bounds.origin.x;
635  _textContainerOrigin.x += _textContainerInset.width;
636 
637  _textContainerOrigin.y = _bounds.origin.y;
638  _textContainerOrigin.y += _textContainerInset.height;
639 }
640 
641 - (void)doCommandBySelector:(SEL)aSelector
642 {
643  if (![self _sendDelegateDoCommandBySelector:aSelector])
644  [super doCommandBySelector:aSelector];
645 }
646 
648 {
649  [[CPNotificationCenter defaultCenter] postNotificationName:CPTextDidChangeNotification object:self];
650 
651  if (_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_textDidChange)
652  [_delegate textDidChange:[[CPNotification alloc] initWithName:CPTextDidChangeNotification object:self userInfo:nil]];
653 }
654 
655 - (BOOL)shouldChangeTextInRange:(CPRange)aRange replacementString:(CPString)aString
656 {
657  if (![self isEditable])
658  return NO;
659 
660  return [self _sendDelegateTextShouldBeginEditing] && [self _sendDelegateShouldChangeTextInRange:aRange replacementString:aString];
661 }
662 
663 
664 #pragma mark -
665 #pragma mark Insert characters methods
666 
667 - (void)_fixupReplaceForRange:(CPRange)aRange
668 {
669  [self setSelectedRange:aRange];
670  [_layoutManager _validateLayoutAndGlyphs];
671  [self sizeToFit];
672  [self scrollRangeToVisible:_selectionRange];
673  [self setNeedsDisplay:YES];
674 }
675 
676 - (void)_replaceCharactersInRange:(CPRange)aRange withAttributedString:(CPString)aString selectionRange:(CPRange)selectionRange
677 {
679  _replaceCharactersInRange:CPMakeRange(aRange.location, [aString length])
680  withAttributedString:[_textStorage attributedSubstringFromRange:CPMakeRangeCopy(aRange)]
681  selectionRange:CPMakeRangeCopy(_selectionRange)];
682 
683  [_textStorage replaceCharactersInRange:aRange withAttributedString:aString];
684  [self _fixupReplaceForRange:selectionRange];
685 }
686 
687 - (void)insertText:(CPString)aString
688 {
689  var isAttributed = [aString isKindOfClass:CPAttributedString],
690  string = isAttributed ? [aString string]:aString;
691 
692  if (![self shouldChangeTextInRange:CPMakeRangeCopy(_selectionRange) replacementString:string])
693  return;
694 
695  if (!isAttributed)
696  aString = [[CPAttributedString alloc] initWithString:aString attributes:_typingAttributes];
697  else if (![self isRichText])
698  aString = [[CPAttributedString alloc] initWithString:string attributes:_typingAttributes];
699 
700  var undoManager = [[self window] undoManager];
701  [undoManager setActionName:@"Replace/insert text"];
702 
703  [[undoManager prepareWithInvocationTarget:self]
704  _replaceCharactersInRange:CPMakeRange(_selectionRange.location, [aString length])
705  withAttributedString:[_textStorage attributedSubstringFromRange:CPMakeRangeCopy(_selectionRange)]
706  selectionRange:CPMakeRangeCopy(_selectionRange)];
707 
708  [self willChangeValueForKey:@"objectValue"];
709  [_textStorage replaceCharactersInRange:CPMakeRangeCopy(_selectionRange) withAttributedString:aString];
710  [self didChangeValueForKey:@"objectValue"];
711  [self _continuouslyReverseSetBinding];
712 
713  [self _setSelectedRange:CPMakeRange(_selectionRange.location + [string length], 0) affinity:0 stillSelecting:NO overwriteTypingAttributes:NO];
714  _startTrackingLocation = _selectionRange.location;
715 
716  [self didChangeText];
717  [_layoutManager _validateLayoutAndGlyphs];
718  [self sizeToFit];
719  [self scrollRangeToVisible:_selectionRange];
720  _stickyXLocation = MAX(0, _caret._rect.origin.x - 1);
721 }
722 
723 #pragma mark -
724 #pragma mark Drawing methods
725 
726 - (void)drawInsertionPointInRect:(CGRect)aRect color:(CPColor)aColor turnedOn:(BOOL)flag
727 {
728  [_caret setRect:aRect];
729  [_caret setVisibility:flag stop:NO];
730 }
731 
732 - (void)displayRectIgnoringOpacity:(CGRect)aRect inContext:(CPGraphicsContext)aGraphicsContext
733 { if ([self isHidden])
734  return;
735 
736  [self drawRect:aRect];
737 }
738 
739 - (void)drawRect:(CGRect)aRect
740 {
741 #if PLATFORM(DOM)
742  var range = [_layoutManager glyphRangeForBoundingRect:aRect inTextContainer:_textContainer];
743 
744  for (var i = 0; i < [_selectionSpans count]; i++)
745  [_selectionSpans[i] removeFromTextView];
746 
747  _selectionSpans = [];
748 
749  if (_selectionRange.length)
750  {
751  var rects = [_layoutManager rectArrayForCharacterRange:_selectionRange
752  withinSelectedCharacterRange:_selectionRange
753  inTextContainer:_textContainer
754  rectCount:nil],
755  effectiveSelectionColor = [self _isFocused] ? [_selectedTextAttributes objectForKey:CPBackgroundColorAttributeName] : [CPColor _selectedTextBackgroundColorUnfocussed],
756  lengthRect = rects.length;
757 
758  for (var i = 0; i < lengthRect; i++)
759  {
760  rects[i].origin.x += _textContainerOrigin.x;
761  rects[i].origin.y += _textContainerOrigin.y;
762 
763  var newSpan = [[_CPSelectionBox alloc] initWithTextView:self rect:rects[i] color:effectiveSelectionColor];
764  [_selectionSpans addObject:newSpan];
765  }
766  }
767 
768  if (range.length)
769  [_layoutManager drawGlyphsForGlyphRange:range atPoint:_textContainerOrigin];
770 
771  if ([self shouldDrawInsertionPoint])
772  {
774  [self drawInsertionPointInRect:_caret._rect color:_insertionPointColor turnedOn:_caret._drawCaret];
775  }
776  else
777  [_caret setVisibility:NO];
778 #endif
779 }
780 
781 
782 #pragma mark -
783 #pragma mark Select methods
784 
785 - (void)selectAll:(id)sender
786 {
787  if ([self isSelectable])
788  {
789  [_caret stopBlinking];
790  [self setSelectedRange:CPMakeRange(0, [_layoutManager numberOfCharacters])];
791  }
792 }
793 
794 - (void)setSelectedRange:(CPRange)range
795 {
796  [_CPNativeInputManager cancelCurrentInputSessionIfNeeded];
797  [self setSelectedRange:range affinity:0 stillSelecting:NO];
798 }
799 
800 - (void)setSelectedRange:(CPRange)range affinity:(CPSelectionAffinity)affinity stillSelecting:(BOOL)selecting
801 {
802  [self _setSelectedRange:range affinity:affinity stillSelecting:selecting overwriteTypingAttributes:YES];
803 }
804 
805 - (void)_setSelectedRange:(CPRange)range affinity:(CPSelectionAffinity)affinity stillSelecting:(BOOL)selecting overwriteTypingAttributes:(BOOL)doOverwrite
806 {
807  var maxRange = CPMakeRange(0, [_layoutManager numberOfCharacters]);
808 
809  range = CPIntersectionRange(maxRange, range);
810 
811  if (!selecting && [self _delegateRespondsToWillChangeSelectionFromCharacterRangeToCharacterRange])
812  {
813  _selectionRange = [self _sendDelegateWillChangeSelectionFromCharacterRange:_selectionRange toCharacterRange:range];
814  }
815  else
816  {
817  _selectionRange = CPMakeRangeCopy(range);
818  _selectionRange = [self selectionRangeForProposedRange:_selectionRange granularity:[self selectionGranularity]];
819  }
820 
821  if (_selectionRange.length)
822  [_layoutManager invalidateDisplayForGlyphRange:_selectionRange];
823  else
824  [self setNeedsDisplay:YES];
825 
826  if (!selecting)
827  {
828  if ([self _isFirstResponder])
829  [self updateInsertionPointStateAndRestartTimer:((_selectionRange.length === 0) && ![_caret isBlinking])];
830 
831  if (doOverwrite && _placeholderString === nil)
832  [self setTypingAttributes:[_textStorage attributesAtIndex:CPMaxRange(range) effectiveRange:nil]];
833 
834  [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeSelectionNotification object:self];
835 
836  if (_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_didChangeSelection)
837  [_delegate textViewDidChangeSelection:[[CPNotification alloc] initWithName:CPTextViewDidChangeSelectionNotification object:self userInfo:nil]];
838  }
839 
840  if (!selecting && _selectionRange.length > 0)
841  [_CPNativeInputManager focusForClipboardOfTextView:self];
842 }
843 
844 #if PLATFORM(DOM)
845 - (CGPoint)_cumulativeOffset
846 {
847  var top = 0,
848  left = 0,
849  element = self._DOMElement;
850 
851  do
852  {
853  top += element.offsetTop || 0;
854  left += element.offsetLeft || 0;
855  element = element.offsetParent;
856  }
857  while(element);
858 
859  return CGPointMake(left, top);
860 }
861 #endif
862 
863 
864 // interface to the _CPNativeInputManager
865 - (void)_activateNativeInputElement:(DOMElemet)aNativeField
866 {
867  var attributes = [[self typingAttributes] copy];
868 
869  // make it invisible
870  [attributes setObject:[CPColor colorWithRed:1 green:1 blue:1 alpha:0] forKey:CPForegroundColorAttributeName];
871 
872  // FIXME: this hack to provide the visual space for the inputmanager should at least bypass the undomanager
873  var placeholderString = [[CPAttributedString alloc] initWithString:aNativeField.innerHTML attributes:attributes];
874  [self insertText:placeholderString];
875 
876  var caretOrigin = [_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0, _selectionRange.location - 1), 1) inTextContainer:_textContainer].origin;
877  caretOrigin.y += [_layoutManager _characterOffsetAtLocation:MAX(0, _selectionRange.location - 1)];
878  caretOrigin.x += 2; // two pixel offset to the LHS character
879  var cumulativeOffset = [self _cumulativeOffset];
880 
881 
882 #if PLATFORM(DOM)
883  aNativeField.style.left = (caretOrigin.x + cumulativeOffset.x) + "px";
884  aNativeField.style.top = (caretOrigin.y + cumulativeOffset.y) + "px";
885  aNativeField.style.font = [[_typingAttributes objectForKey:CPFontAttributeName] cssString];
886  aNativeField.style.color = [[_typingAttributes objectForKey:CPForegroundColorAttributeName] cssString];
887 #endif
888 
889  [_caret setVisibility:NO]; // hide our caret because now the system caret takes over
890 }
891 
892 - (CPArray)selectedRanges
893 {
894  return [_selectionRange];
895 }
896 
897 #pragma mark -
898 #pragma mark Keyboard events
899 
900 - (void)keyDown:(CPEvent)event
901 {
902 
903  [[_window platformWindow] _propagateCurrentDOMEvent:YES]; // for the _CPNativeInputManager (necessary at least on FF and chrome)
904 
905  if (![_CPNativeInputManager isNativeInputFieldActive] && [event charactersIgnoringModifiers].charCodeAt(0) != 229) // filter out 229 because this would be inserted in chrome on each deadkey
906  [self interpretKeyEvents:[event]];
907 
908  [_caret setPermanentlyVisible:YES];
909 }
910 
911 - (void)keyUp:(CPEvent)event
912 {
913  [super keyUp:event];
914 
915  setTimeout(function() {
916  [_caret setPermanentlyVisible:NO];
917  }, 1000);
918 }
919 
920 - (CGPoint)_characterIndexFromRawPoint:(CGPoint)point
921 {
922  var fraction = [],
923  point = [self convertPoint:point fromView:nil];
924 
925  // convert to container coordinate
926  point.x -= _textContainerOrigin.x;
927  point.y -= _textContainerOrigin.y;
928 
929  var index = [_layoutManager glyphIndexForPoint:point inTextContainer:_textContainer fractionOfDistanceThroughGlyph:fraction];
930 
931  if (index === CPNotFound)
932  index = [_layoutManager numberOfCharacters];
933  else if (fraction[0] > 0.5)
934  index++;
935 
936  return index;
937 }
938 - (CGPoint)_characterIndexFromEvent:(CPEvent)event
939 {
940  return [self _characterIndexFromRawPoint:[event locationInWindow]];
941 }
942 
944 {
945  return YES;
946 }
947 
948 #pragma mark -
949 #pragma mark Mouse Events
950 
951 - (void)mouseDown:(CPEvent)event
952 {
953  if (![self isSelectable])
954  return;
955 
956  [_CPNativeInputManager cancelCurrentInputSessionIfNeeded];
957  [_caret setVisibility:NO];
958 
959  _startTrackingLocation = [self _characterIndexFromEvent:event];
960 
961  var granularities = [CPNotFound, CPSelectByCharacter, CPSelectByWord, CPSelectByParagraph];
962  [self setSelectionGranularity:granularities[[event clickCount]]];
963 
964  // dragging the selection
965  if ([self selectionGranularity] == CPSelectByCharacter && CPLocationInRange(_startTrackingLocation, _selectionRange))
966  {
967  var lineBeginningIndex = [_layoutManager _firstLineFragmentForLineFromLocation:_selectionRange.location]._range.location,
968  placeholderRange = _MakeRangeFromAbs(lineBeginningIndex, CPMaxRange(_selectionRange)),
969  placeholderString = [_textStorage attributedSubstringFromRange:placeholderRange],
970  placeholderFrame = CGRectIntersection([_layoutManager boundingRectForGlyphRange:placeholderRange inTextContainer:_textContainer], _frame),
971  rangeToHide = CPMakeRange(0, _selectionRange.location - lineBeginningIndex),
972  dragPlaceholder;
973 
974  // hide the left part of the first line of the selection that is not included
975  [placeholderString addAttribute:CPForegroundColorAttributeName
976  value:[CPColor colorWithRed:1 green:1 blue:1 alpha:0]
977  range:rangeToHide];
978 
979  _movingSelection = CPMakeRange(_startTrackingLocation, 0);
980 
981  dragPlaceholder = [[CPTextView alloc] initWithFrame:placeholderFrame];
982  [dragPlaceholder._textStorage replaceCharactersInRange:CPMakeRange(0, 0) withAttributedString:placeholderString];
983 
984  [dragPlaceholder setBackgroundColor:[CPColor colorWithRed:1 green:1 blue:1 alpha:0]];
985  [dragPlaceholder setAlphaValue:0.5];
986 
987  var stringForPasting = [_textStorage attributedSubstringFromRange:CPMakeRangeCopy(_selectionRange)],
988  richData = [_CPRTFProducer produceRTF:stringForPasting documentAttributes:@{}],
989  draggingPasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
990  [draggingPasteboard declareTypes:[CPRTFPboardType, CPStringPboardType] owner:nil];
991  [draggingPasteboard setString:richData forType:CPRTFPboardType];
992  [draggingPasteboard setString:stringForPasting._string forType:CPStringPboardType];
993 
994  [self dragView:dragPlaceholder
995  at:placeholderFrame.origin
996  offset:nil
997  event:event
998  pasteboard:draggingPasteboard
999  source:self
1000  slideBack:YES];
1001 
1002  return;
1003  }
1004 
1005  var setRange = CPMakeRange(_startTrackingLocation, 0);
1006 
1007  if ([event modifierFlags] & CPShiftKeyMask)
1008  setRange = _MakeRangeFromAbs(_startTrackingLocation < _MidRange(_selectionRange) ? CPMaxRange(_selectionRange) : _selectionRange.location, _startTrackingLocation);
1009  else
1010  _scrollingTimer = [CPTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(_supportScrolling:) userInfo:nil repeats:YES]; // fixme: only start if we are in the scrolling areas
1011 
1012  [self setSelectedRange:setRange affinity:0 stillSelecting:YES];
1013 }
1014 
1015 - (void)_supportScrolling:(CPTimer)aTimer
1016 {
1017  [self mouseDragged:[CPApp currentEvent]];
1018 }
1019 
1020 - (void)mouseDragged:(CPEvent)event
1021 {
1022  if (![self isSelectable])
1023  return;
1024 
1025  if (_movingSelection)
1026  return;
1027 
1028  var oldRange = [self selectedRange],
1029  index = [self _characterIndexFromEvent:event];
1030 
1031  if (index > oldRange.location)
1032  _scrollingDownward = YES;
1033 
1034  if (index < CPMaxRange(oldRange))
1035  _scrollingDownward = NO;
1036 
1037  [self setSelectedRange:_MakeRangeFromAbs(index, _startTrackingLocation)
1038  affinity:0
1039  stillSelecting:YES];
1040 
1041  [self scrollRangeToVisible:CPMakeRange(index, 0)];
1042 }
1043 
1044 // handle all the other methods from CPKeyBinding.j
1045 
1046 - (void)mouseUp:(CPEvent)event
1047 {
1048  _movingSelection = nil;
1049 
1050  // will post CPTextViewDidChangeSelectionNotification
1051  _previousSelectionGranularity = [self selectionGranularity];
1052  [self setSelectionGranularity:CPSelectByCharacter];
1054 
1055  var point = [_layoutManager locationForGlyphAtIndex:[self selectedRange].location];
1056  _stickyXLocation = point.x;
1057  _startTrackingLocation = _selectionRange.location;
1058 
1059  if (_scrollingTimer)
1060  {
1061  [_scrollingTimer invalidate];
1062  _scrollingTimer = nil;
1063  }
1064 }
1065 
1066 - (void)moveDown:(id)sender
1067 {
1068  if (![self isSelectable])
1069  return;
1070 
1071  var fraction = [],
1072  nglyphs = [_layoutManager numberOfCharacters],
1073  sindex = CPMaxRange([self selectedRange]),
1074  rectSource = [_layoutManager boundingRectForGlyphRange:CPMakeRange(sindex, 1) inTextContainer:_textContainer],
1075  rectEnd = nglyphs ? [_layoutManager boundingRectForGlyphRange:CPMakeRange(nglyphs - 1, 1) inTextContainer:_textContainer] : rectSource,
1076  point = rectSource.origin;
1077 
1078  if (_stickyXLocation)
1079  point.x = _stickyXLocation;
1080 
1081  // <!> FIXME: find a better way for getting the coordinates of the next line
1082  point.y += 2 + rectSource.size.height;
1083 
1084  var dindex = point.y >= CGRectGetMaxY(rectEnd) ? nglyphs : [_layoutManager glyphIndexForPoint:point inTextContainer:_textContainer fractionOfDistanceThroughGlyph:fraction],
1085  oldStickyLoc = _stickyXLocation;
1086 
1087  if (fraction[0] > 0.5)
1088  dindex++;
1089 
1090  [self _establishSelection:CPMakeRange(dindex, 0) byExtending:NO];
1091  _stickyXLocation = oldStickyLoc;
1092  [self scrollRangeToVisible:CPMakeRange(dindex, 0)]
1093 
1094 }
1095 
1096 - (void)moveDownAndModifySelection:(id)sender
1097 {
1098  if (![self isSelectable])
1099  return;
1100 
1101  var oldStartTrackingLocation = _startTrackingLocation;
1102 
1103  [self _performSelectionFixupForRange:CPMakeRange(_selectionRange.location < _startTrackingLocation ? _selectionRange.location : CPMaxRange(_selectionRange), 0)];
1104  [self moveDown:sender];
1105  _startTrackingLocation = oldStartTrackingLocation;
1106  [self _performSelectionFixupForRange:_MakeRangeFromAbs(_startTrackingLocation, (_selectionRange.location < _startTrackingLocation ? _selectionRange.location : CPMaxRange(_selectionRange)))];
1107 }
1108 
1109 - (void)moveUp:(id)sender
1110 {
1111  if (![self isSelectable])
1112  return;
1113 
1114  var dindex = [self selectedRange].location;
1115 
1116  if (dindex < 1)
1117  return;
1118 
1119  var rectSource = [_layoutManager boundingRectForGlyphRange:CPMakeRange(dindex, 1) inTextContainer:_textContainer];
1120 
1121  if (!(dindex === [_layoutManager numberOfCharacters] && _isNewlineCharacter([[_textStorage string] characterAtIndex:dindex - 1])))
1122  dindex = [_layoutManager glyphIndexForPoint:CGPointMake(0, rectSource.origin.y + 1) inTextContainer:_textContainer fractionOfDistanceThroughGlyph:nil];
1123 
1124  if (dindex < 1)
1125  return;
1126 
1127  var fraction = [];
1128  rectSource = [_layoutManager boundingRectForGlyphRange:CPMakeRange(dindex - 1, 1) inTextContainer:_textContainer];
1129  dindex = [_layoutManager glyphIndexForPoint:CGPointMake(_stickyXLocation, rectSource.origin.y + 1) inTextContainer:_textContainer fractionOfDistanceThroughGlyph:fraction];
1130 
1131  if (fraction[0] > 0.5)
1132  dindex++;
1133 
1134  var oldStickyLoc = _stickyXLocation;
1135  [self _establishSelection:CPMakeRange(dindex,0) byExtending:NO];
1136  _stickyXLocation = oldStickyLoc;
1137 
1138  [self scrollRangeToVisible:CPMakeRange(dindex, 0)];
1139 }
1140 
1141 - (void)moveUpAndModifySelection:(id)sender
1142 {
1143  if (![self isSelectable])
1144  return;
1145 
1146  var oldStartTrackingLocation = _startTrackingLocation;
1147 
1148  [self _performSelectionFixupForRange:CPMakeRange(_selectionRange.location < _startTrackingLocation ? _selectionRange.location : CPMaxRange(_selectionRange), 0)];
1149  [self moveUp:sender];
1150  _startTrackingLocation = oldStartTrackingLocation;
1151  [self _performSelectionFixupForRange:_MakeRangeFromAbs(_startTrackingLocation, (_selectionRange.location < _startTrackingLocation ? _selectionRange.location : CPMaxRange(_selectionRange)))];
1152 }
1153 
1154 - (void)_performSelectionFixupForRange:(CPRange)aSel
1155 {
1156  aSel.location = MAX(0, aSel.location);
1157 
1158  if (CPMaxRange(aSel) > [_layoutManager numberOfCharacters])
1159  aSel = CPMakeRange([_layoutManager numberOfCharacters], 0);
1160 
1161  [self setSelectedRange:aSel];
1162 
1163  var point = [_layoutManager locationForGlyphAtIndex:aSel.location];
1164 
1165  _stickyXLocation = point.x;
1166 }
1167 
1168 - (void)_establishSelection:(CPSelection)aSel byExtending:(BOOL)flag
1169 {
1170  if (flag)
1171  aSel = CPUnionRange(aSel, _selectionRange);
1172 
1173  [self _performSelectionFixupForRange:aSel];
1174  _startTrackingLocation = _selectionRange.location;
1175 }
1176 
1177 - (unsigned)_calculateMoveSelectionFromRange:(CPRange)aRange intoDirection:(integer)move granularity:(CPSelectionGranularity)granularity
1178 {
1179  var inWord = [self _isCharacterAtIndex:(move > 0 ? CPMaxRange(aRange) : aRange.location) + move granularity:granularity],
1180  aSel = [self selectionRangeForProposedRange:CPMakeRange((move > 0 ? CPMaxRange(aRange) : aRange.location) + move, 0) granularity:granularity],
1181  bSel = [self selectionRangeForProposedRange:CPMakeRange((move > 0 ? CPMaxRange(aSel) : aSel.location) + move, 0) granularity:granularity];
1182 
1183  return move > 0 ? CPMaxRange(inWord? aSel:bSel) : (inWord? aSel:bSel).location;
1184 }
1185 
1186 - (void)_moveSelectionIntoDirection:(integer)move granularity:(CPSelectionGranularity)granularity
1187 {
1188  var pos = [self _calculateMoveSelectionFromRange:_selectionRange intoDirection:move granularity:granularity];
1189 
1190  [self _performSelectionFixupForRange:CPMakeRange(pos, 0)];
1191  _startTrackingLocation = _selectionRange.location;
1192 }
1193 
1194 - (void)_extendSelectionIntoDirection:(integer)move granularity:(CPSelectionGranularity)granularity
1195 {
1196  var aSel = CPMakeRangeCopy(_selectionRange);
1197 
1198  if (granularity !== CPSelectByCharacter)
1199  {
1200  var pos = [self _calculateMoveSelectionFromRange:CPMakeRange(aSel.location < _startTrackingLocation ? aSel.location : CPMaxRange(aSel), 0)
1201  intoDirection:move
1202  granularity:granularity];
1203  aSel = CPMakeRange(pos, 0);
1204  }
1205 
1206  else
1207  aSel = CPMakeRange((aSel.location < _startTrackingLocation? aSel.location : CPMaxRange(aSel)) + move, 0);
1208 
1209  aSel = _MakeRangeFromAbs(_startTrackingLocation, aSel.location);
1210  [self _performSelectionFixupForRange:aSel];
1211 }
1212 
1213 - (void)moveLeftAndModifySelection:(id)sender
1214 {
1215  if ([self isSelectable])
1216  [self _extendSelectionIntoDirection:-1 granularity:CPSelectByCharacter];
1217 }
1218 
1219 - (void)moveBackward:(id)sender
1220 {
1221  [self moveLeft:sender];
1222 }
1223 
1224 - (void)moveBackwardAndModifySelection:(id)sender
1225 {
1226  [self moveLeftAndModifySelection:sender];
1227 }
1228 
1229 - (void)moveRightAndModifySelection:(id)sender
1230 {
1231  if ([self isSelectable])
1232  [self _extendSelectionIntoDirection:1 granularity:CPSelectByCharacter];
1233 }
1234 
1235 - (void)moveLeft:(id)sender
1236 {
1237  if ([self isSelectable])
1238  [self _establishSelection:CPMakeRange(_selectionRange.location - (_selectionRange.length ? 0 : 1), 0) byExtending:NO];
1239 }
1240 
1241 - (void)moveToEndOfParagraph:(id)sender
1242 {
1243  if (![self isSelectable])
1244  return;
1245 
1246  if (!_isNewlineCharacter([[_textStorage string] characterAtIndex:_selectionRange.location]))
1247  [self _moveSelectionIntoDirection:1 granularity:CPSelectByParagraph];
1248 
1249  if (_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, _selectionRange.location - 1)]))
1250  [self moveLeft:sender];
1251 }
1252 
1253 - (void)moveToEndOfParagraphAndModifySelection:(id)sender
1254 {
1255  if ([self isSelectable])
1256  [self _extendSelectionIntoDirection:1 granularity:CPSelectByParagraph];
1257 }
1258 
1259 - (void)moveParagraphForwardAndModifySelection:(id)sender
1260 {
1261  if ([self isSelectable])
1262  [self _extendSelectionIntoDirection:1 granularity:CPSelectByParagraph];
1263 }
1264 
1265 - (void)moveParagraphForward:(id)sender
1266 {
1267  if ([self isSelectable])
1268  [self _moveSelectionIntoDirection:1 granularity:CPSelectByParagraph];
1269 }
1270 
1271 - (void)moveWordBackwardAndModifySelection:(id)sender
1272 {
1273  [self moveWordLeftAndModifySelection:sender];
1274 }
1275 
1276 - (void)moveWordBackward:(id)sender
1277 {
1278  [self moveWordLeft:sender];
1279 }
1280 
1281 - (void)moveWordForwardAndModifySelection:(id)sender
1282 {
1283  [self moveWordRightAndModifySelection:sender];
1284 }
1285 
1286 - (void)moveWordForward:(id)sender
1287 {
1288  [self moveWordRight:sender];
1289 }
1290 
1291 - (void)moveToBeginningOfDocument:(id)sender
1292 {
1293  if ([self isSelectable])
1294  [self _establishSelection:CPMakeRange(0, 0) byExtending:NO];
1295 }
1296 
1297 - (void)moveToBeginningOfDocumentAndModifySelection:(id)sender
1298 {
1299  if ([self isSelectable])
1300  [self _establishSelection:CPMakeRange(0, 0) byExtending:YES];
1301 }
1302 
1303 - (void)moveToEndOfDocument:(id)sender
1304 {
1305  if ([self isSelectable])
1306  [self _establishSelection:CPMakeRange([_layoutManager numberOfCharacters], 0) byExtending:NO];
1307 }
1308 
1309 - (void)moveToEndOfDocumentAndModifySelection:(id)sender
1310 {
1311  if ([self isSelectable])
1312  [self _establishSelection:CPMakeRange([_layoutManager numberOfCharacters], 0) byExtending:YES];
1313 }
1314 
1315 - (void)moveWordRight:(id)sender
1316 {
1317  if ([self isSelectable])
1318  [self _moveSelectionIntoDirection:1 granularity:CPSelectByWord];
1319 }
1320 
1321 - (void)moveToBeginningOfParagraph:(id)sender
1322 {
1323  if (![self isSelectable])
1324  return;
1325 
1326  if (!_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, _selectionRange.location - 1)]))
1327  [self _moveSelectionIntoDirection:-1 granularity:CPSelectByParagraph];
1328 }
1329 
1330 - (void)moveToBeginningOfParagraphAndModifySelection:(id)sender
1331 {
1332  if ([self isSelectable])
1333  [self _extendSelectionIntoDirection:-1 granularity:CPSelectByParagraph];
1334 }
1335 
1336 - (void)moveParagraphBackward:(id)sender
1337 {
1338  if ([self isSelectable])
1339  [self _moveSelectionIntoDirection:-1 granularity:CPSelectByParagraph];
1340 }
1341 
1342 - (void)moveParagraphBackwardAndModifySelection:(id)sender
1343 {
1344  if ([self isSelectable])
1345  [self _extendSelectionIntoDirection:-1 granularity:CPSelectByParagraph];
1346 }
1347 
1348 - (void)moveWordRightAndModifySelection:(id)sender
1349 {
1350  if ([self isSelectable])
1351  [self _extendSelectionIntoDirection:+1 granularity:CPSelectByWord];
1352 }
1353 
1354 - (void)deleteToEndOfParagraph:(id)sender
1355 {
1356  if (![self isSelectable] || ![self isEditable])
1357  return;
1358 
1360  [self delete:self];
1361 }
1362 
1363 - (void)deleteToBeginningOfParagraph:(id)sender
1364 {
1365  if (![self isSelectable] || ![self isEditable])
1366  return;
1367 
1369  [self delete:self];
1370 }
1371 
1372 - (void)deleteToBeginningOfLine:(id)sender
1373 {
1374  if (![self isSelectable] || ![self isEditable])
1375  return;
1376 
1378  [self delete:self];
1379 }
1380 
1381 - (void)deleteToEndOfLine:(id)sender
1382 {
1383  if (![self isSelectable] || ![self isEditable])
1384  return;
1385 
1387  [self delete:self];
1388 }
1389 
1390 - (void)deleteWordBackward:(id)sender
1391 {
1392  if (![self isSelectable] || ![self isEditable])
1393  return;
1394 
1395  [self moveWordLeftAndModifySelection:self];
1396  [self delete:self];
1397 }
1398 
1399 - (void)deleteWordForward:(id)sender
1400 {
1401  if (![self isSelectable] || ![self isEditable])
1402  return;
1403 
1404  [self moveWordRightAndModifySelection:self];
1405  [self delete:self];
1406 }
1407 
1408 - (void)moveToLeftEndOfLine:(id)sender byExtending:(BOOL)flag
1409 {
1410  if (![self isSelectable])
1411  return;
1412 
1413  var nglyphs = [_layoutManager numberOfCharacters],
1414  loc = nglyphs == _selectionRange.location ? MAX(0, _selectionRange.location - 1) : _selectionRange.location,
1415  fragment = [_layoutManager _firstLineFragmentForLineFromLocation:loc];
1416 
1417  if (fragment)
1418  [self _establishSelection:CPMakeRange(fragment._range.location, 0) byExtending:flag];
1419 }
1420 
1421 - (void)moveToLeftEndOfLine:(id)sender
1422 {
1423  [self moveToLeftEndOfLine:sender byExtending:NO];
1424 }
1425 
1426 - (void)moveToLeftEndOfLineAndModifySelection:(id)sender
1427 {
1428  [self moveToLeftEndOfLine:sender byExtending:YES];
1429 }
1430 
1431 - (void)moveToRightEndOfLine:(id)sender byExtending:(BOOL)flag
1432 {
1433  if (![self isSelectable])
1434  return;
1435 
1436  var fragment = [_layoutManager _lastLineFragmentForLineFromLocation:_selectionRange.location];
1437 
1438  if (!fragment)
1439  return;
1440 
1441  var nglyphs = [_layoutManager numberOfCharacters],
1442  loc = nglyphs == CPMaxRange(fragment._range) ? nglyphs : MAX(0, CPMaxRange(fragment._range) - 1);
1443 
1444  [self _establishSelection:CPMakeRange(loc, 0) byExtending:flag];
1445 }
1446 
1447 - (void)moveToRightEndOfLine:(id)sender
1448 {
1449  [self moveToRightEndOfLine:sender byExtending:NO];
1450 }
1451 
1452 - (void)moveToRightEndOfLineAndModifySelection:(id)sender
1453 {
1454  [self moveToRightEndOfLine:sender byExtending:YES];
1455 }
1456 
1457 - (void)moveWordLeftAndModifySelection:(id)sender
1458 {
1459  if ([self isSelectable])
1460  [self _extendSelectionIntoDirection:-1 granularity:CPSelectByWord];
1461 }
1462 
1463 - (void)moveWordLeft:(id)sender
1464 {
1465  if ([self isSelectable])
1466  [self _moveSelectionIntoDirection:-1 granularity:CPSelectByWord]
1467 }
1468 
1469 - (void)moveRight:(id)sender
1470 {
1471  if ([self isSelectable])
1472  [self _establishSelection:CPMakeRange(CPMaxRange(_selectionRange) + (_selectionRange.length ? 0 : 1), 0) byExtending:NO];
1473 }
1474 
1475 - (void)_deleteForRange:(CPRange)changedRange
1476 {
1477  if (![self shouldChangeTextInRange:changedRange replacementString:@""])
1478  return;
1479 
1480  changedRange = CPIntersectionRange(CPMakeRange(0, [_layoutManager numberOfCharacters]), changedRange);
1481 
1482  [[[_window undoManager] prepareWithInvocationTarget:self] _replaceCharactersInRange:CPMakeRange(changedRange.location, 0)
1483  withAttributedString:[_textStorage attributedSubstringFromRange:CPMakeRangeCopy(changedRange)]
1484  selectionRange:CPMakeRangeCopy(_selectionRange)];
1485  [self willChangeValueForKey:@"objectValue"];
1486  [_textStorage deleteCharactersInRange:CPMakeRangeCopy(changedRange)];
1487  [self didChangeValueForKey:@"objectValue"];
1488  [self _continuouslyReverseSetBinding];
1489 
1490  [self setSelectedRange:CPMakeRange(changedRange.location, 0)];
1491  [self didChangeText];
1492  [_layoutManager _validateLayoutAndGlyphs];
1493  [self sizeToFit];
1494  _stickyXLocation = _caret._rect.origin.x;
1495 }
1496 
1497 - (void)cancelOperation:(id)sender
1498 {
1499  [_CPNativeInputManager cancelCurrentInputSessionIfNeeded]; // handle ESC during native input
1500 }
1501 
1502 - (void)deleteBackward:(id)sender ignoreSmart:(BOOL)ignoreFlag
1503 {
1504  var changedRange;
1505 
1506  if (CPEmptyRange(_selectionRange) && _selectionRange.location > 0)
1507  changedRange = CPMakeRange(_selectionRange.location - 1, 1);
1508  else
1509  changedRange = _selectionRange;
1510 
1511  // smart delete
1512  if (!ignoreFlag && _copySelectionGranularity > 0 &&
1513  changedRange.location > 0 && _isWhitespaceCharacter([[_textStorage string] characterAtIndex:_selectionRange.location - 1]) &&
1514  changedRange.location < [[self string] length] && _isWhitespaceCharacter([[_textStorage string] characterAtIndex:CPMaxRange(changedRange)]))
1515  changedRange.length++;
1516 
1517  [self _deleteForRange:changedRange];
1518  _startTrackingLocation = _selectionRange.location;
1519 }
1520 
1521 - (void)deleteBackward:(id)sender
1522 {
1523  _copySelectionGranularity = _previousSelectionGranularity; // smart delete
1524  [self deleteBackward:self ignoreSmart:_selectionRange.length > 0? NO:YES];
1525 }
1526 
1527 - (void)deleteForward:(id)sender
1528 {
1529  var changedRange;
1530 
1531  if (CPEmptyRange(_selectionRange) && _selectionRange.location < [_layoutManager numberOfCharacters])
1532  changedRange = CPMakeRange(_selectionRange.location, 1);
1533  else
1534  changedRange = _selectionRange;
1535 
1536  [self _deleteForRange:changedRange];
1537 }
1538 
1539 - (void)cut:(id)sender
1540 {
1541  var selectedRange = [self selectedRange];
1542 
1543  if (selectedRange.length < 1)
1544  return;
1545 
1546  [self copy:sender];
1547  [self deleteBackward:sender ignoreSmart:NO];
1548 }
1549 
1550 - (void)insertLineBreak:(id)sender
1551 {
1552  [self insertText:@"\n"];
1553 }
1554 
1555 - (void)insertTab:(id)sender
1556 {
1557  [self insertText:@"\t"];
1558 }
1559 
1560 - (void)insertTabIgnoringFieldEditor:(id)sender
1561 {
1562  [self insertTab:sender];
1563 }
1564 
1565 - (void)insertNewlineIgnoringFieldEditor:(id)sender
1566 {
1567  [self insertLineBreak:sender];
1568 }
1569 
1570 - (void)insertNewline:(id)sender
1571 {
1572  [self insertLineBreak:sender];
1573 }
1574 
1575 - (void)_enrichEssentialTypingAttributes:(CPDictionary)attributes
1576 {
1577  if (![attributes containsKey:CPFontAttributeName])
1578  [attributes setObject:[self font] forKey:CPFontAttributeName];
1579 
1580  if (![attributes containsKey:CPForegroundColorAttributeName])
1581  [attributes setObject:[self textColor] forKey:CPForegroundColorAttributeName];
1582 }
1583 
1584 - (void)setTypingAttributes:(CPDictionary)attributes
1585 {
1586  if (!attributes)
1587  attributes = [CPDictionary dictionary];
1588 
1589  if ([self _delegateRespondsToShouldChangeTypingAttributesToAttributes])
1590  {
1591  _typingAttributes = [self _sendDelegateShouldChangeTypingAttributes:_typingAttributes toAttributes:attributes];
1592  }
1593  else
1594  {
1595  _typingAttributes = [attributes copy];
1596 
1597  [self _enrichEssentialTypingAttributes:_typingAttributes];
1598  }
1599 
1600  [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification
1601  object:self];
1602 
1603  if (_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_didChangeTypingAttributes)
1604  [_delegate textViewDidChangeTypingAttributes:[[CPNotification alloc] initWithName:CPTextViewDidChangeTypingAttributesNotification object:self userInfo:nil]];
1605 }
1606 
1607 - (CPDictionary)_attributesForFontPanel
1608 {
1609  var attributes = [[_textStorage attributesAtIndex:CPMaxRange(_selectionRange) effectiveRange:nil] copy];
1610 
1611  [self _enrichEssentialTypingAttributes:attributes];
1612 
1613  return attributes;
1614 }
1615 
1616 - (void)delete:(id)sender
1617 {
1618  [self deleteBackward:sender];
1619 }
1620 
1621 
1622 #pragma mark -
1623 #pragma mark Font methods
1624 
1626 {
1627  return _font || [CPFont systemFontOfSize:12.0];
1628 }
1629 
1630 - (void)setFont:(CPFont)font
1631 {
1632  _font = font;
1633 
1634  var length = [_layoutManager numberOfCharacters];
1635 
1636  if (length)
1637  {
1638  [_textStorage addAttribute:CPFontAttributeName value:_font range:CPMakeRange(0, length)];
1639  [_textStorage setFont:_font];
1640  [self scrollRangeToVisible:CPMakeRange(length, 0)];
1641  }
1642 }
1643 
1644 - (void)setFont:(CPFont)font range:(CPRange)range
1645 {
1646  if (![self isRichText])
1647  {
1648  _font = font;
1649  [_textStorage setFont:_font];
1650  }
1651 
1652  var currentAttributes = [_textStorage attributesAtIndex:range.location effectiveRange:nil] || _typingAttributes;
1653 
1655  setFont:[currentAttributes objectForKey:CPFontAttributeName] || [self font]
1656  range:CPMakeRangeCopy(range)];
1657 
1658  [_textStorage addAttribute:CPFontAttributeName value:font range:CPMakeRangeCopy(range)];
1659  [_layoutManager _validateLayoutAndGlyphs];
1660 }
1661 
1662 - (void)changeFont:(id)sender
1663 {
1664  var currRange = CPMakeRange(_selectionRange.location, 0),
1665  oldFont,
1666  attributes,
1667  scrollRange = CPMakeRange(CPMaxRange(_selectionRange), 0),
1668  undoManager = [[self window] undoManager];
1669 
1670  [undoManager beginUndoGrouping];
1671 
1672  if ([self isRichText])
1673  {
1674  if (!CPEmptyRange(_selectionRange))
1675  {
1676  while (CPMaxRange(currRange) < CPMaxRange(_selectionRange)) // iterate all "runs"
1677  {
1678  attributes = [_textStorage attributesAtIndex:CPMaxRange(currRange)
1679  longestEffectiveRange:currRange
1680  inRange:_selectionRange];
1681  oldFont = [attributes objectForKey:CPFontAttributeName] || [self font];
1682 
1683  [self setFont:[sender convertFont:oldFont] range:currRange];
1684  }
1685  }
1686  else
1687  {
1688  [_typingAttributes setObject:[sender selectedFont] forKey:CPFontAttributeName];
1689  }
1690  }
1691  else
1692  {
1693  var length = [_textStorage length];
1694 
1695  oldFont = [self font];
1696  [self setFont:[sender convertFont:oldFont] range:CPMakeRange(0, length)];
1697  scrollRange = CPMakeRange(length, 0);
1698  }
1699 
1700  [undoManager endUndoGrouping];
1701 
1702  [_layoutManager _validateLayoutAndGlyphs];
1703  [self sizeToFit];
1704  [self setNeedsDisplay:YES];
1705  [self scrollRangeToVisible:scrollRange];
1706 }
1707 
1708 
1709 #pragma mark -
1710 #pragma mark Color methods
1711 
1712 - (void)changeColor:(id)sender
1713 {
1714  [self setTextColor:[sender color] range:_selectionRange];
1715 }
1716 
1717 - (void)setTextColor:(CPColor)aColor
1718 {
1719  _textColor = [aColor copy];
1720  [self setTextColor:aColor range:CPMakeRange(0, [_layoutManager numberOfCharacters])];
1721  [_typingAttributes setObject:_textColor forKey:CPForegroundColorAttributeName];
1722 }
1723 
1724 - (void)setTextColor:(CPColor)aColor range:(CPRange)range
1725 {
1726  var currentAttributes = [_textStorage attributesAtIndex:range.location effectiveRange:nil] || _typingAttributes;
1727 
1729  setTextColor:[currentAttributes objectForKey:CPForegroundColorAttributeName] || _textColor
1730  range:CPMakeRangeCopy(range)];
1731 
1732  if (!CPEmptyRange(range))
1733  {
1734  if (aColor)
1735  [_textStorage addAttribute:CPForegroundColorAttributeName value:aColor range:CPMakeRangeCopy(range)];
1736  else
1737  [_textStorage removeAttribute:CPForegroundColorAttributeName range:CPMakeRangeCopy(range)];
1738  }
1739  else
1740  [_typingAttributes setObject:aColor forKey:CPForegroundColorAttributeName];
1741 
1742  [_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(range) changeInLength:0 invalidatedRange:CPMakeRangeCopy(range)];
1743 }
1744 
1745 - (void)underline:(id)sender
1746 {
1747  if (![self shouldChangeTextInRange:_selectionRange replacementString:nil])
1748  return;
1749 
1750  if (!CPEmptyRange(_selectionRange))
1751  {
1752  var attrib = [_textStorage attributesAtIndex:_selectionRange.location effectiveRange:nil];
1753 
1754  if ([attrib containsKey:CPUnderlineStyleAttributeName] && [[attrib objectForKey:CPUnderlineStyleAttributeName] intValue])
1755  [_textStorage removeAttribute:CPUnderlineStyleAttributeName range:_selectionRange];
1756  else
1757  [_textStorage addAttribute:CPUnderlineStyleAttributeName value:[CPNumber numberWithInt:1] range:CPMakeRangeCopy(_selectionRange)];
1758  }
1759  else
1760  {
1761  if ([_typingAttributes containsKey:CPUnderlineStyleAttributeName] && [[_typingAttributes objectForKey:CPUnderlineStyleAttributeName] intValue])
1762  [_typingAttributes setObject:[CPNumber numberWithInt:0] forKey:CPUnderlineStyleAttributeName];
1763  else
1764  [_typingAttributes setObject:[CPNumber numberWithInt:1] forKey:CPUnderlineStyleAttributeName];
1765  }
1766 
1767  [_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(_selectionRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(_selectionRange)];
1768 }
1769 
1770 - (CPSelectionAffinity)selectionAffinity
1771 {
1772  return 0;
1773 }
1774 
1776 {
1777  return NO;
1778 }
1779 
1780 - (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString
1781 {
1782  [_textStorage replaceCharactersInRange:aRange withString:aString];
1783 }
1784 
1785 - (void)setConstrainedFrameSize:(CGSize)desiredSize
1786 {
1787  [self setFrameSize:desiredSize];
1788 }
1789 
1790 - (void)sizeToFit
1791 {
1792  [self setFrameSize:[self frameSize]];
1793 }
1794 
1795 - (void)setFrameSize:(CGSize)aSize
1796 {
1797  var minSize = [self minSize],
1798  maxSize = [self maxSize],
1799  desiredSize = CGSizeCreateCopy(aSize),
1800  rect = CGRectUnion([_layoutManager boundingRectForGlyphRange:CPMakeRange(0, 1) inTextContainer:_textContainer],
1801  [_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0, [_layoutManager numberOfCharacters] - 2), 1) inTextContainer:_textContainer]),
1802  myClipviewSize = nil;
1803 
1804  if ([[self superview] isKindOfClass:[CPClipView class]])
1805  myClipviewSize = [[self superview] frame].size;
1806 
1807  if ([_layoutManager extraLineFragmentTextContainer] === _textContainer)
1808  rect = CGRectUnion(rect, [_layoutManager extraLineFragmentRect]);
1809 
1810  if (_isHorizontallyResizable)
1811  {
1812  rect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(0, MAX(0, [_layoutManager numberOfCharacters] - 1)) inTextContainer:_textContainer]; // needs expensive "deep" recalculation
1813 
1814  desiredSize.width = rect.size.width + 2 * _textContainerInset.width;
1815 
1816  if (desiredSize.width < minSize.width)
1817  desiredSize.width = minSize.width;
1818  else if (desiredSize.width > maxSize.width)
1819  desiredSize.width = maxSize.width;
1820  }
1821 
1822  if (_isVerticallyResizable)
1823  {
1824  desiredSize.height = rect.size.height + 2 * _textContainerInset.height;
1825 
1826  if (desiredSize.height < minSize.height)
1827  desiredSize.height = minSize.height;
1828  else if (desiredSize.height > maxSize.height)
1829  desiredSize.height = maxSize.height;
1830  }
1831 
1832  if (myClipviewSize)
1833  {
1834  if (desiredSize.width < myClipviewSize.width)
1835  desiredSize.width = myClipviewSize.width;
1836  if (desiredSize.height < myClipviewSize.height)
1837  desiredSize.height = myClipviewSize.height;
1838  }
1839 
1840  [super setFrameSize:desiredSize];
1841 }
1842 
1843 - (void)scrollRangeToVisible:(CPRange)aRange
1844 {
1845  var rect;
1846 
1847  if (CPEmptyRange(aRange))
1848  {
1849  if (aRange.location >= [_layoutManager numberOfCharacters])
1850  rect = [_layoutManager extraLineFragmentRect];
1851  else
1852  rect = [_layoutManager lineFragmentRectForGlyphAtIndex:aRange.location effectiveRange:nil];
1853  }
1854  else
1855  {
1856  rect = [_layoutManager boundingRectForGlyphRange:aRange inTextContainer:_textContainer];
1857  }
1858 
1859  rect.origin.x += _textContainerOrigin.x;
1860  rect.origin.y += _textContainerOrigin.y;
1861 
1862  [self scrollRectToVisible:rect];
1863 }
1864 
1865 - (BOOL)_isCharacterAtIndex:(unsigned)index granularity:(CPSelectionGranularity)granularity
1866 {
1867  var characterSet;
1868 
1869  switch (granularity)
1870  {
1871  case CPSelectByWord:
1872  characterSet = [[self class] _wordBoundaryRegex];
1873  break;
1874 
1875  case CPSelectByParagraph:
1876  characterSet = [[self class] _paragraphBoundaryRegex];
1877  break;
1878  default:
1879  // FIXME if (!characterSet) croak!
1880  }
1881 
1882  return _regexMatchesStringAtIndex(characterSet, [_textStorage string], index);
1883 }
1884 
1885 + (JSObject)_wordBoundaryRegex
1886 {
1887  return new RegExp("(^[0-9][\\.,])|(^.[^-\\.,+#'\"!§$%&/\\(<\\[\\]>\\)=?`´*\\s{}\\|¶])", "m");
1888 }
1889 
1890 + (JSObject)_paragraphBoundaryRegex
1891 {
1892  return new RegExp("^.[^\\n\\r]", "m");
1893 }
1894 
1895 + (JSObject)_whitespaceRegex
1896 {
1897  // do not include \n here or we will get cross paragraph selections
1898  return new RegExp("^.[ \\t]+", "m");
1899 }
1900 
1901 - (CPRange)_characterRangeForIndex:(unsigned)index asDefinedByRegex:(JSObject)regex
1902 {
1903  return [self _characterRangeForIndex:index asDefinedByLRegex:regex andRRegex:regex]
1904 }
1905 
1906 - (CPRange)_characterRangeForIndex:(unsigned)index asDefinedByLRegex:(JSObject)lregex andRRegex:(JSObject)rregex
1907 {
1908  var wordRange = CPMakeRange(index, 0),
1909  numberOfCharacters = [_layoutManager numberOfCharacters],
1910  string = [_textStorage string];
1911 
1912  // extend to the left
1913  for (var searchIndex = index - 1; searchIndex >= 0 && _regexMatchesStringAtIndex(lregex, string, searchIndex); searchIndex--)
1914  wordRange.location = searchIndex;
1915 
1916  // extend to the right
1917  searchIndex = index + 1;
1918 
1919  while (searchIndex < numberOfCharacters && _regexMatchesStringAtIndex(rregex, string, searchIndex))
1920  searchIndex++;
1921 
1922  return _MakeRangeFromAbs(wordRange.location, MIN(MAX(0, numberOfCharacters), searchIndex));
1923 }
1924 
1925 - (CPRange)selectionRangeForProposedRange:(CPRange)proposedRange granularity:(CPSelectionGranularity)granularity
1926 {
1927  var textStorageLength = [_layoutManager numberOfCharacters];
1928 
1929  if (textStorageLength == 0)
1930  return CPMakeRange(0, 0);
1931 
1932  if (proposedRange.location >= textStorageLength)
1933  proposedRange = CPMakeRange(textStorageLength, 0);
1934 
1935  if (CPMaxRange(proposedRange) > textStorageLength)
1936  proposedRange.length = textStorageLength - proposedRange.location;
1937 
1938  var string = [_textStorage string],
1939  lregex,
1940  rregex,
1941  lloc = proposedRange.location,
1942  rloc = CPMaxRange(proposedRange);
1943 
1944  switch (granularity)
1945  {
1946  case CPSelectByWord:
1947  lregex = _isWhitespaceCharacter([string characterAtIndex:lloc])? [[self class] _whitespaceRegex] : [[self class] _wordBoundaryRegex];
1948  rregex = _isWhitespaceCharacter([string characterAtIndex:CPMaxRange(proposedRange)])? [[self class] _whitespaceRegex] : [[self class] _wordBoundaryRegex];
1949  break;
1950  case CPSelectByParagraph:
1951  lregex = rregex = [[self class] _paragraphBoundaryRegex];
1952 
1953  // triple click right in last line of a paragraph-> select this paragraph completely
1954  if (lloc > 0 && _isNewlineCharacter([string characterAtIndex:lloc]) &&
1955  !_isNewlineCharacter([string characterAtIndex:lloc - 1]))
1956  lloc--;
1957 
1958  if (rloc > 0 && _isNewlineCharacter([string characterAtIndex:rloc]))
1959  rloc--;
1960 
1961  break;
1962  default:
1963  return proposedRange;
1964  }
1965 
1966  var granularRange = [self _characterRangeForIndex:lloc
1967  asDefinedByLRegex:lregex
1968  andRRegex:rregex];
1969 
1970  if (proposedRange.length == 0 && _isNewlineCharacter([string characterAtIndex:proposedRange.location]))
1971  return _MakeRangeFromAbs(_isNewlineCharacter([string characterAtIndex:lloc])? proposedRange.location : granularRange.location, proposedRange.location + 1);
1972 
1973  if (proposedRange.length)
1974  granularRange = CPUnionRange(granularRange, [self _characterRangeForIndex:rloc
1975  asDefinedByLRegex:lregex
1976  andRRegex:rregex]);
1977 
1978  // include the newline character in case of triple click selecting as is done by apple
1979  if (granularity == CPSelectByParagraph && _isNewlineCharacter([string characterAtIndex:CPMaxRange(granularRange)]))
1980  granularRange.length++;
1981 
1982  return granularRange;
1983 }
1984 
1986 {
1987  return (_selectionRange.length === 0 && [self _isFocused]);
1988 }
1989 
1990 - (void)updateInsertionPointStateAndRestartTimer:(BOOL)flag
1991 {
1992  var caretRect,
1993  numberOfGlyphs = [_layoutManager numberOfCharacters];
1994 
1995  if (_selectionRange.length)
1996  [_caret setVisibility:NO];
1997 
1998  if (_selectionRange.location >= numberOfGlyphs) // cursor is "behind" the last chacacter
1999  {
2000  caretRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0,_selectionRange.location - 1), 1) inTextContainer:_textContainer];
2001 
2002  if (!numberOfGlyphs)
2003  {
2004  var font = [_typingAttributes objectForKey:CPFontAttributeName] || [self font];
2005 
2006  caretRect.size.height = [font size];
2007  caretRect.origin.y = ([font ascender] - [font descender]) * 0.5 + _textContainerOrigin.y;
2008  }
2009 
2010  caretRect.origin.x += caretRect.size.width;
2011 
2012  if (_selectionRange.location > 0 && [[_textStorage string] characterAtIndex:_selectionRange.location - 1] === '\n')
2013  {
2014  caretRect.origin.y += caretRect.size.height;
2015  caretRect.origin.x = 0;
2016  }
2017  }
2018  else
2019  caretRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(_selectionRange.location, 1) inTextContainer:_textContainer];
2020 
2021  var loc = (_selectionRange.location === numberOfGlyphs && numberOfGlyphs > 0) ? _selectionRange.location - 1 : _selectionRange.location,
2022  caretOffset = [_layoutManager _characterOffsetAtLocation:loc],
2023  oldYPosition = CGRectGetMaxY(caretRect),
2024  caretDescend = [_layoutManager _descentAtLocation:loc];
2025 
2026  if (caretOffset > 0)
2027  {
2028  caretRect.origin.y += caretOffset;
2029  caretRect.size.height = oldYPosition - caretRect.origin.y;
2030  }
2031  if (caretDescend < 0)
2032  caretRect.size.height -= caretDescend;
2033 
2034  caretRect.origin.x += _textContainerOrigin.x;
2035  caretRect.origin.y += _textContainerOrigin.y;
2036  caretRect.size.width = 1;
2037 
2038  [_caret setRect:caretRect];
2039 
2040  if (flag)
2041  [_caret startBlinking];
2042 }
2043 
2044 - (void)draggingUpdated:(CPDraggingInfo)info
2045 {
2046  var point = [info draggingLocation],
2047  location = [self _characterIndexFromRawPoint:CGPointCreateCopy(point)];
2048 
2049  _movingSelection = CPMakeRange(location, 0);
2050  [_caret _drawCaretAtLocation:_movingSelection.location];
2051  [_caret setVisibility:YES];
2052 }
2053 
2054 #pragma mark -
2055 #pragma mark Dragging operation
2056 
2057 - (void)performDragOperation:(CPDraggingInfo)aSender
2058 {
2059  var location = [self convertPoint:[aSender draggingLocation] fromView:nil],
2060  pasteboard = [aSender draggingPasteboard];
2061 
2062  if ([pasteboard availableTypeFromArray:[CPRTFPboardType, CPStringPboardType]])
2063  {
2064  [_caret setVisibility:NO];
2065 
2066  if (CPLocationInRange(_movingSelection.location, _selectionRange))
2067  {
2068  [self setSelectedRange:_movingSelection];
2069  _movingSelection = nil;
2070  return;
2071  }
2072 
2073  if (_movingSelection.location > CPMaxRange(_selectionRange))
2074  _movingSelection.location -= _selectionRange.length;
2075 
2076  [self _deleteForRange:_selectionRange];
2077  [self setSelectedRange:_movingSelection];
2078 
2079  var dataForPasting = [pasteboard stringForType:CPRTFPboardType] || [pasteboard stringForType:CPStringPboardType];
2080 
2081  // setTimeout is to a work around a transaction issue with the undomanager
2082  setTimeout(function(){
2083 
2084  if ([dataForPasting hasPrefix:"{\\rtf"])
2085  [self insertText:[[_CPRTFParser new] parseRTF:dataForPasting]];
2086  else
2087  [self insertText:dataForPasting];
2088  }, 0);
2089  }
2090 
2091  if ([pasteboard availableTypeFromArray:[CPColorDragType]])
2092  [self setTextColor:[CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]] range:_selectionRange];
2093 }
2094 
2096 {
2097  return [super isSelectable] && !_placeholderString;
2098 }
2099 - (BOOL)isEditable
2100 {
2101  return [super isEditable] && !_placeholderString;
2102 }
2103 
2104 - (void)_setPlaceholderString:(CPString)aString
2105 {
2106  if (_placeholderString === aString)
2107  return;
2108 
2109  _placeholderString = aString;
2110 
2111  [self setString:[[CPAttributedString alloc] initWithString:_placeholderString attributes:@{CPForegroundColorAttributeName:[CPColor colorWithRed:0.66 green:0.66 blue:0.66 alpha:1]}]];
2112 }
2113 
2114 - (void)_continuouslyReverseSetBinding
2115 {
2116  var binderClass = [[self class] _binderClassForBinding:CPAttributedStringBinding] ||
2117  [[self class] _binderClassForBinding:CPValueBinding],
2118  theBinding = [binderClass getBinding:CPAttributedStringBinding forObject:self] || [binderClass getBinding:CPValueBinding forObject:self];
2119 
2120  if ([theBinding continuouslyUpdatesValue])
2121  [theBinding reverseSetValueFor:@"objectValue"];
2122 }
2123 
2124 - (void)_reverseSetBinding
2125 {
2126  var binderClass = [[self class] _binderClassForBinding:CPAttributedStringBinding] ||
2127  [[self class] _binderClassForBinding:CPValueBinding],
2128  theBinding = [binderClass getBinding:CPAttributedStringBinding forObject:self] || [binderClass getBinding:CPValueBinding forObject:self];
2129 
2130  [theBinding reverseSetValueFor:@"objectValue"];
2131 }
2132 
2133 @end
2134 
2135 
2137 
2138 - (BOOL)_delegateRespondsToShouldChangeTypingAttributesToAttributes
2139 {
2140  return _delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_shouldChangeTypingAttributes_toAttributes;
2141 }
2142 
2143 - (BOOL)_delegateRespondsToWillChangeSelectionFromCharacterRangeToCharacterRange
2144 {
2146 }
2147 
2148 - (BOOL)_sendDelegateDoCommandBySelector:(SEL)aSelector
2149 {
2150  if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_doCommandBySelector))
2151  return NO;
2152 
2153  return [_delegate textView:self doCommandBySelector:aSelector];
2154 }
2155 
2156 - (BOOL)_sendDelegateTextShouldBeginEditing
2157 {
2158  if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textShouldBeginEditing))
2159  return YES;
2160 
2161  return [_delegate textShouldBeginEditing:self];
2162 }
2163 
2164 - (BOOL)_sendDelegateShouldChangeTextInRange:(CPRange)aRange replacementString:(CPString)aString
2165 {
2166  if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_shouldChangeTextInRange_replacementString))
2167  return YES;
2168 
2169  return [_delegate textView:self shouldChangeTextInRange:aRange replacementString:aString];
2170 }
2171 
2172 - (CPDictionary)_sendDelegateShouldChangeTypingAttributes:(CPDictionary)typingAttributes toAttributes:(CPDictionary)attributes
2173 {
2174  if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_doCommandBySelector))
2175  return [CPDictionary dictionary];
2176 
2177  return [_delegate textView:self shouldChangeTypingAttributes:typingAttributes toAttributes:attributes];
2178 }
2179 
2180 - (CPRange)_sendDelegateWillChangeSelectionFromCharacterRange:(CPRange)selectionRange toCharacterRange:(CPRange)range
2181 {
2183  return CPMakeRange(0, 0);
2184 
2185  return [_delegate textView:self willChangeSelectionFromCharacterRange:selectionRange toCharacterRange:range];
2186 }
2187 
2188 @end
2189 
2190 
2191 var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey",
2192  CPTextViewUsesFontPanelKey = @"CPTextViewUsesFontPanelKey",
2193  CPTextViewContainerKey = @"CPTextViewContainerKey",
2194  CPTextViewLayoutManagerKey = @"CPTextViewLayoutManagerKey",
2195  CPTextViewTextStorageKey = @"CPTextViewTextStorageKey",
2196  CPTextViewInsertionPointColorKey = @"CPTextViewInsertionPointColorKey",
2197  CPTextViewSelectedTextAttributesKey = @"CPTextViewSelectedTextAttributesKey",
2198  CPTextViewDelegateKey = @"CPTextViewDelegateKey";
2199 
2200 @implementation CPTextView (CPCoding)
2201 
2202 - (id)initWithCoder:(CPCoder)aCoder
2203 {
2204  self = [super initWithCoder:aCoder];
2205 
2206  if (self)
2207  {
2208  [self _init];
2209 
2210  [self setInsertionPointColor:[aCoder decodeObjectForKey:CPTextViewInsertionPointColorKey]];
2211 
2212  var selectedTextAttributes = [aCoder decodeObjectForKey:CPTextViewSelectedTextAttributesKey],
2213  enumerator = [selectedTextAttributes keyEnumerator],
2214  key;
2215 
2216  while (key = [enumerator nextObject])
2217  [_selectedTextAttributes setObject:[selectedTextAttributes valueForKey:key] forKey:key];
2218 
2219  if (![_selectedTextAttributes valueForKey:CPBackgroundColorAttributeName])
2220  [_selectedTextAttributes setObject:[CPColor selectedTextBackgroundColor] forKey:CPBackgroundColorAttributeName];
2221 
2222  [self setAllowsUndo:[aCoder decodeBoolForKey:CPTextViewAllowsUndoKey]];
2223  [self setUsesFontPanel:[aCoder decodeBoolForKey:CPTextViewUsesFontPanelKey]];
2224 
2225  [self setDelegate:[aCoder decodeObjectForKey:CPTextViewDelegateKey]];
2226 
2227  var container = [aCoder decodeObjectForKey:CPTextViewContainerKey];
2228  [container setTextView:self];
2229 
2230  _typingAttributes = [[_textStorage attributesAtIndex:0 effectiveRange:nil] copy];
2231 
2232  if (![_typingAttributes valueForKey:CPForegroundColorAttributeName])
2233  [_typingAttributes setObject:[CPColor blackColor] forKey:CPForegroundColorAttributeName];
2234 
2235  _textColor = [_typingAttributes valueForKey:CPForegroundColorAttributeName];
2236  [self setFont:[_typingAttributes valueForKey:CPFontAttributeName]];
2237 
2238  [self setString:[_textStorage string]];
2239  }
2240 
2241  return self;
2242 }
2243 
2244 - (void)encodeWithCoder:(CPCoder)aCoder
2245 {
2246  [super encodeWithCoder:aCoder];
2247 
2248  [aCoder encodeObject:_delegate forKey:CPTextViewDelegateKey];
2249  [aCoder encodeObject:_textContainer forKey:CPTextViewContainerKey];
2250  [aCoder encodeObject:_insertionPointColor forKey:CPTextViewInsertionPointColorKey];
2251  [aCoder encodeObject:_selectedTextAttributes forKey:CPTextViewSelectedTextAttributesKey];
2252  [aCoder encodeBool:_allowsUndo forKey:CPTextViewAllowsUndoKey];
2253  [aCoder encodeBool:_usesFontPanel forKey:CPTextViewUsesFontPanelKey];
2254 }
2255 
2256 @end
2257 
2258 
2259 @implementation _CPSelectionBox : CPObject
2260 {
2261  DOMElement _selectionBoxDOM;
2262  CGRect _rect;
2263  CPColor _color
2264  CPTextView _textView;
2265 }
2266 
2267 - (id)initWithTextView:(CPTextView)aTextView rect:(CGRect)aRect color:(CPColor)aColor
2268 {
2269  if (self = [super init])
2270  {
2271  _textView = aTextView;
2272  _rect = aRect;
2273  _color = aColor;
2274 
2275  [self _createSpan];
2276  _textView._DOMElement.appendChild(_selectionBoxDOM);
2277  }
2278 
2279  return self;
2280 }
2281 
2282 - (void)removeFromTextView
2283 {
2284  _textView._DOMElement.removeChild(_selectionBoxDOM);
2285 }
2286 
2287 - (void)_createSpan
2288 {
2289 
2290 #if PLATFORM(DOM)
2291  _selectionBoxDOM = document.createElement("span");
2292  _selectionBoxDOM.style.position = "absolute";
2293  _selectionBoxDOM.style.visibility = "visible";
2294  _selectionBoxDOM.style.padding = "0px";
2295  _selectionBoxDOM.style.margin = "0px";
2296  _selectionBoxDOM.style.whiteSpace = "pre";
2297  _selectionBoxDOM.style.backgroundColor = [_color cssString];
2298 
2299  _selectionBoxDOM.style.width = (_rect.size.width) + "px";
2300  _selectionBoxDOM.style.left = (_rect.origin.x) + "px";
2301  _selectionBoxDOM.style.top = (_rect.origin.y) + "px";
2302  _selectionBoxDOM.style.height = (_rect.size.height) + "px";
2303  _selectionBoxDOM.style.zIndex = -1000;
2304  _selectionBoxDOM.oncontextmenu = _selectionBoxDOM.onmousedown = _selectionBoxDOM.onselectstart = function () { return false; };
2305 #endif
2306 
2307 }
2308 
2309 @end
2310 
2311 
2312 @implementation _CPCaret : CPObject
2313 {
2314  BOOL _drawCaret;
2315  BOOL _permanentlyVisible;
2316  CGRect _rect;
2317  CPTextView _textView;
2318  CPTimer _caretTimer;
2319  DOMElement _caretDOM;
2320 }
2321 
2322 - (void)setRect:(CGRect)aRect
2323 {
2324  _rect = CGRectCreateCopy(aRect);
2325 
2326 #if PLATFORM(DOM)
2327  _caretDOM.style.left = (aRect.origin.x) + "px";
2328  _caretDOM.style.top = (aRect.origin.y) + "px";
2329  _caretDOM.style.height = (aRect.size.height) + "px";
2330 #endif
2331 }
2332 
2333 - (id)initWithTextView:(CPTextView)aView
2334 {
2335  if (self = [super init])
2336  {
2337 #if PLATFORM(DOM)
2338  var style;
2339 
2340  if (!_caretDOM)
2341  {
2342  _caretDOM = document.createElement("span");
2343  style = _caretDOM.style;
2344  style.position = "absolute";
2345  style.visibility = "visible";
2346  style.padding = "0px";
2347  style.margin = "0px";
2348  style.whiteSpace = "pre";
2349  style.backgroundColor = "black";
2350  _caretDOM.style.width = "1px";
2351  _textView = aView;
2352  _textView._DOMElement.appendChild(_caretDOM);
2353  }
2354 #endif
2355  }
2356 
2357  return self;
2358 }
2359 
2360 - (void)setVisibility:(BOOL)visibilityFlag stop:(BOOL)stopFlag
2361 {
2362 
2363 #if PLATFORM(DOM)
2364  _caretDOM.style.visibility = visibilityFlag ? "visible" : "hidden";
2365 #endif
2366 
2367  if (!visibilityFlag && stopFlag)
2368  [self stopBlinking];
2369 }
2370 
2371 - (void)setVisibility:(BOOL)visibilityFlag
2372 {
2373  [self setVisibility:visibilityFlag stop:YES];
2374 }
2375 
2376 - (void)_blinkCaret:(CPTimer)aTimer
2377 {
2378  _drawCaret = (!_drawCaret) || _permanentlyVisible;
2379  [_textView setNeedsDisplayInRect:_rect];
2380 }
2381 
2382 - (void)startBlinking
2383 {
2384  _drawCaret = YES;
2385 
2386  if ([self isBlinking])
2387  return;
2388 
2389  _caretTimer = [CPTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(_blinkCaret:) userInfo:nil repeats:YES];
2390 }
2391 
2392 - (void)isBlinking
2393 {
2394  return [_caretTimer isValid];
2395 }
2396 
2397 - (void)stopBlinking
2398 {
2399  _drawCaret = NO;
2400 
2401  if (_caretTimer)
2402  {
2403  [_caretTimer invalidate];
2404  _caretTimer = nil;
2405  }
2406 }
2407 
2408 - (void)_drawCaretAtLocation:(int)aLoc
2409 {
2410  var rect = [_textView._layoutManager boundingRectForGlyphRange:CPMakeRange(aLoc, 1) inTextContainer:_textView._textContainer];
2411 
2412  if (aLoc >= [_textView._layoutManager numberOfCharacters])
2413  rect.origin.x = CGRectGetMaxX(rect)
2414 
2415  [self setRect:rect];
2416 }
2417 
2418 @end
2419 
2420 
2421 var _CPNativeInputField,
2422  _CPNativeInputFieldKeyDownCalled,
2423  _CPNativeInputFieldKeyUpCalled,
2424  _CPNativeInputFieldKeyPressedCalled,
2425  _CPNativeInputFieldActive;
2426 
2427 var _CPCopyPlaceholder = '-';
2428 @implementation _CPNativeInputManager : CPObject
2429 {
2430  id __doxygen__;
2431 }
2432 
2433 + (BOOL)isNativeInputFieldActive
2434 {
2435  return _CPNativeInputFieldActive;
2436 }
2437 
2438 + (void)cancelCurrentNativeInputSession
2439 {
2440 
2441 #if PLATFORM(DOM)
2442  _CPNativeInputField.innerHTML = '';
2443 #endif
2444 
2445  [self _endInputSessionWithString:_CPNativeInputField.innerHTML];
2446 }
2447 
2448 + (void)cancelCurrentInputSessionIfNeeded
2449 {
2450  if (!_CPNativeInputFieldActive)
2451  return;
2452 
2453  [self cancelCurrentNativeInputSession];
2454 }
2455 
2456 + (void)_endInputSessionWithString:(CPString)aStr
2457 {
2458  _CPNativeInputFieldActive = NO;
2459 
2460  var currentFirstResponder = [[CPApp keyWindow] firstResponder],
2461  placeholderRange = CPMakeRange([currentFirstResponder selectedRange].location - 1, 1);
2462 
2463  [currentFirstResponder setSelectedRange:placeholderRange];
2464  [currentFirstResponder insertText:aStr];
2465  _CPNativeInputField.innerHTML = '';
2466 
2467 
2468  [self hideInputElement];
2469  [currentFirstResponder updateInsertionPointStateAndRestartTimer:YES];
2470 }
2471 
2472 + (void)initialize
2473 {
2474 #if PLATFORM(DOM)
2475  _CPNativeInputField = document.createElement("div");
2476  _CPNativeInputField.contentEditable = YES;
2477  _CPNativeInputField.style.width = "64px";
2478  _CPNativeInputField.style.zIndex = 10000;
2479  _CPNativeInputField.style.position = "absolute";
2480  _CPNativeInputField.style.visibility = "visible";
2481  _CPNativeInputField.style.padding = "0px";
2482  _CPNativeInputField.style.margin = "0px";
2483  _CPNativeInputField.style.whiteSpace = "pre";
2484  _CPNativeInputField.style.outline = "0px solid transparent";
2485 
2486  document.body.appendChild(_CPNativeInputField);
2487 
2488  _CPNativeInputField.addEventListener("keyup", function(e)
2489  {
2490  _CPNativeInputFieldKeyUpCalled = YES;
2491 
2492  // filter out the shift-up, cursor keys and friends used to access the deadkeys
2493  // fixme: e.which is depreciated(?) -> find a better way to identify the modifier-keyups
2494  if (e.which < 27 || e.which == 91 || e.which == 93) // include apple command keys
2495  {
2496  if (e.which == 13)
2497  _CPNativeInputField.innerHTML = '';
2498 
2499  if (_CPNativeInputField.innerHTML.length == 0 || _CPNativeInputField.innerHTML.length > 2) // backspace
2500  [self cancelCurrentInputSessionIfNeeded];
2501 
2502  return false; // prevent the default behaviour
2503  }
2504 
2505  var currentFirstResponder = [[CPApp keyWindow] firstResponder];
2506 
2507  if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)])
2508  return false; // prevent the default behaviour
2509 
2510  var charCode = _CPNativeInputField.innerHTML.charCodeAt(0);
2511 
2512  // å and Å need to be filtered out in keyDown: due to chrome inserting 229 on a deadkey
2513  if (charCode == 229 || charCode == 197)
2514  {
2515  [currentFirstResponder insertText:_CPNativeInputField.innerHTML];
2516  _CPNativeInputField.innerHTML = '';
2517  return;
2518  }
2519 
2520  // chrome-trigger: keypressed is omitted for deadkeys
2521  if (!_CPNativeInputFieldActive && _CPNativeInputFieldKeyPressedCalled == NO && _CPNativeInputField.innerHTML.length && _CPNativeInputField.innerHTML != _CPCopyPlaceholder && _CPNativeInputField.innerHTML.length < 3)
2522  {
2523  _CPNativeInputFieldActive = YES;
2524  [currentFirstResponder _activateNativeInputElement:_CPNativeInputField];
2525  }
2526  else
2527  {
2528  if (_CPNativeInputFieldActive)
2529  [self _endInputSessionWithString:_CPNativeInputField.innerHTML];
2530 
2531  // prevent the copy placeholder beeing removed by cursor keys
2532  if (_CPNativeInputFieldKeyPressedCalled)
2533  _CPNativeInputField.innerHTML = '';
2534  }
2535 
2536  _CPNativeInputFieldKeyDownCalled = NO;
2537 
2538  return false; // prevent the default behaviour
2539  }, true);
2540 
2541  _CPNativeInputField.addEventListener("keydown", function(e)
2542  {
2543  // this protects from heavy typing and the shift key
2544  if (_CPNativeInputFieldKeyDownCalled)
2545  return true;
2546 
2547  _CPNativeInputFieldKeyDownCalled = YES;
2548  _CPNativeInputFieldKeyUpCalled = NO;
2549  _CPNativeInputFieldKeyPressedCalled = NO;
2550  var currentFirstResponder = [[CPApp keyWindow] firstResponder];
2551 
2552  // webkit-browsers: cursor keys do not emit keypressed and would otherwise activate deadkey mode
2553  if (!CPBrowserIsEngine(CPGeckoBrowserEngine) && e.which >= 37 && e.which <= 40)
2554  _CPNativeInputFieldKeyPressedCalled = YES;
2555 
2556  if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)])
2557  return;
2558 
2559  // FF-trigger: here the best way to detect a dead key is the missing keyup event
2561  setTimeout(function(){
2562  _CPNativeInputFieldKeyDownCalled = NO;
2563 
2564  if (!_CPNativeInputFieldActive && _CPNativeInputFieldKeyUpCalled == NO && _CPNativeInputField.innerHTML.length && _CPNativeInputField.innerHTML != _CPCopyPlaceholder && _CPNativeInputField.innerHTML.length < 3 && !e.repeat)
2565  {
2566  _CPNativeInputFieldActive = YES;
2567  [currentFirstResponder _activateNativeInputElement:_CPNativeInputField];
2568  }
2569  else if (!_CPNativeInputFieldActive)
2570  [self hideInputElement];
2571  }, 200);
2572 
2573  return false;
2574  }, true); // capture mode
2575 
2576  _CPNativeInputField.addEventListener("keypress", function(e)
2577  {
2578  _CPNativeInputFieldKeyUpCalled = YES;
2579  _CPNativeInputFieldKeyPressedCalled = YES;
2580  return false;
2581 
2582  }, true); // capture mode
2583 
2584  _CPNativeInputField.onpaste = function(e)
2585  {
2586  var nativeClipboard = (e.originalEvent || e).clipboardData,
2587  richtext,
2588  pasteboard = [CPPasteboard generalPasteboard],
2589  currentFirstResponder = [[CPApp keyWindow] firstResponder],
2590  isPlain = NO;
2591 
2592  if ([currentFirstResponder respondsToSelector:@selector(isRichText)] && ![currentFirstResponder isRichText])
2593  isPlain = YES;
2594 
2595  // this is the rich chrome / FF codepath (where we can use RTF directly)
2596  if ((richtext = nativeClipboard.getData('text/rtf')) && !(!!window.event.shiftKey) && !isPlain)
2597  {
2598  e.preventDefault();
2599 
2600  // setTimeout to prevent flickering in FF
2601  setTimeout(function(){
2602  [currentFirstResponder insertText:[[_CPRTFParser new] parseRTF:richtext]]
2603  }, 20);
2604 
2605  return false;
2606  }
2607 
2608  // plain is the same in all browsers...
2609 
2610  var data = e.clipboardData.getData('text/plain'),
2611  cappString = [pasteboard stringForType:CPStringPboardType];
2612 
2613  if (cappString != data)
2614  {
2615  [pasteboard declareTypes:[CPStringPboardType] owner:nil];
2616  [pasteboard setString:data forType:CPStringPboardType];
2617  }
2618 
2619  setTimeout(function(){ // prevent dom-flickering (only needed for FF)
2620  [currentFirstResponder paste:self];
2621  }, 20);
2622 
2623  return false;
2624  }
2625 
2627  {
2628  _CPNativeInputField.oncopy = function(e)
2629  {
2630  var pasteboard = [CPPasteboard generalPasteboard],
2631  string,
2632  currentFirstResponder = [[CPApp keyWindow] firstResponder];
2633 
2634  [currentFirstResponder copy:self];
2635 
2636  var stringForPasting = [pasteboard stringForType:CPStringPboardType];
2637  e.clipboardData.setData('text/plain', stringForPasting);
2638 
2639  return false;
2640  }
2641 
2642  _CPNativeInputField.oncut = function(e)
2643  {
2644  var pasteboard = [CPPasteboard generalPasteboard],
2645  string,
2646  currentFirstResponder = [[CPApp keyWindow] firstResponder];
2647 
2648  // prevent dom-flickering
2649  setTimeout(function(){
2650  [currentFirstResponder cut:self];
2651  }, 20);
2652 
2653  // this is necessary because cut will only execute in the future
2654  [currentFirstResponder copy:self];
2655 
2656  var stringForPasting = [pasteboard stringForType:CPStringPboardType];
2657 
2658  e.clipboardData.setData('text/plain', stringForPasting);
2659 
2660  return false;
2661  }
2662  }
2663 #endif
2664 }
2665 
2666 + (void)focusForTextView:(CPTextView)currentFirstResponder
2667 {
2668  if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)])
2669  return;
2670 
2671  [self hideInputElement];
2672 
2673 #if PLATFORM(DOM)
2674  _CPNativeInputField.focus();
2675 #endif
2676 
2677 }
2678 
2679 + (void)focusForClipboardOfTextView:(CPTextView)textview
2680 {
2681 
2682 #if PLATFORM(DOM)
2683  if (!_CPNativeInputFieldActive && _CPNativeInputField.innerHTML.length == 0)
2684  _CPNativeInputField.innerHTML = _CPCopyPlaceholder; // make sure we have a selection to allow the native pasteboard work in safari
2685 
2686  [self focusForTextView:textview];
2687 
2688  // select all in the contenteditable div (http://stackoverflow.com/questions/12243898/how-to-select-all-text-in-contenteditable-div)
2689  if (document.body.createTextRange)
2690  {
2691  var range = document.body.createTextRange();
2692 
2693  range.moveToElementText(_CPNativeInputField);
2694  range.select();
2695  }
2696  else if (window.getSelection)
2697  {
2698  var selection = window.getSelection(),
2699  range = document.createRange();
2700 
2701  range.selectNodeContents(_CPNativeInputField);
2702  selection.removeAllRanges();
2703  selection.addRange(range);
2704  }
2705 #endif
2706 
2707 }
2708 
2709 + (void)hideInputElement
2710 {
2711 
2712 #if PLATFORM(DOM)
2713  _CPNativeInputField.style.top = "-10000px";
2714  _CPNativeInputField.style.left = "-10000px";
2715 #endif
2716 
2717 }
2718 
2719 @end
2720 @implementation _CPTextViewValueBinder : _CPTextFieldValueBinder
2721 {
2722  id __doxygen__;
2723 }
2724 
2725 - (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
2726 {
2727  [_source _setPlaceholderString:aValue];
2728 }
2729 
2730 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
2731 {
2732  if (aValue === nil || (aValue.isa && [aValue isMemberOfClass:CPNull]))
2733  [_source _setPlaceholderString:[self _placeholderForMarker:CPNullMarker]];
2734  else
2735  [_source _setPlaceholderString:nil];
2736 
2737  [_source setObjectValue:aValue];
2738 }
2739 
2740 @end
2741 
2742 
2743 
2744 
2746 
2750 - (BOOL)allowsUndo
2751 {
2752  return _allowsUndo;
2753 }
2754 
2758 - (void)setAllowsUndo:(BOOL)aValue
2759 {
2760  _allowsUndo = aValue;
2761 }
2762 
2767 {
2768  return _isHorizontallyResizable;
2769 }
2770 
2774 - (void)setHorizontallyResizable:(BOOL)aValue
2775 {
2776  _isHorizontallyResizable = aValue;
2777 }
2778 
2783 {
2784  return _isVerticallyResizable;
2785 }
2786 
2790 - (void)setVerticallyResizable:(BOOL)aValue
2791 {
2792  _isVerticallyResizable = aValue;
2793 }
2794 
2799 {
2800  return _usesFontPanel;
2801 }
2802 
2806 - (void)setUsesFontPanel:(BOOL)aValue
2807 {
2808  _usesFontPanel = aValue;
2809 }
2810 
2815 {
2816  return _textContainerOrigin;
2817 }
2818 
2822 - (CGSize)minSize
2823 {
2824  return _minSize;
2825 }
2826 
2830 - (void)setMinSize:(CGSize)aValue
2831 {
2832  _minSize = aValue;
2833 }
2834 
2838 - (CGSize)maxSize
2839 {
2840  return _maxSize;
2841 }
2842 
2846 - (void)setMaxSize:(CGSize)aValue
2847 {
2848  _maxSize = aValue;
2849 }
2850 
2855 {
2856  return _textContainerInset;
2857 }
2858 
2862 - (void)setTextContainerInset:(CGSize)aValue
2863 {
2864  _textContainerInset = aValue;
2865 }
2866 
2871 {
2872  return _insertionPointColor;
2873 }
2874 
2878 - (void)setInsertionPointColor:(CPColor)aValue
2879 {
2880  _insertionPointColor = aValue;
2881 }
2882 
2887 {
2888  return _textColor;
2889 }
2890 
2894 - (void)setTextColor:(CPColor)aValue
2895 {
2896  _textColor = aValue;
2897 }
2898 
2903 {
2904  return _selectedTextAttributes;
2905 }
2906 
2910 - (void)setSelectedTextAttributes:(CPDictionary)aValue
2911 {
2912  _selectedTextAttributes = aValue;
2913 }
2914 
2919 {
2920  return _typingAttributes;
2921 }
2922 
2926 - (void)setTypingAttributes:(CPDictionary)aValue
2927 {
2928  _typingAttributes = aValue;
2929 }
2930 
2935 {
2936  return _layoutManager;
2937 }
2938 
2942 - (CPRange)selectedRange
2943 {
2944  return _selectionRange;
2945 }
2946 
2950 - (CPSelectionGranularity)selectionGranularity
2951 {
2952  return _selectionGranularity;
2953 }
2954 
2958 - (void)setSelectionGranularity:(CPSelectionGranularity)aValue
2959 {
2960  _selectionGranularity = aValue;
2961 }
2962 
2967 {
2968  return _textContainer;
2969 }
2970 
2974 - (void)setTextContainer:(CPTextContainer)aValue
2975 {
2976  _textContainer = aValue;
2977 }
2978 
2983 {
2984  return _textStorage;
2985 }
2986 
2987 @end
void setFrameSize:(CGSize aSize)
Definition: CPTextView.j:1795
Definition: CPFont.h:2
void encodeWithCoder:(CPCoder aCoder)
Definition: CPText.j:340
CPPasteboard draggingPasteboard()
Definition: CPDragServer.j:46
BOOL becomeFirstResponder()
Definition: CPTextView.j:456
void mouseDragged:(CPEvent event)
Definition: CPTextView.j:1020
CGRect exposedRect()
Definition: CPTextView.j:284
void dragView:at:offset:event:pasteboard:source:slideBack:(CPView aView, [at] CGPoint aLocation, [offset] CGSize mouseOffset, [event] CPEvent anEvent, [pasteboard] CPPasteboard aPasteboard, [source] id aSourceObject, [slideBack] BOOL slideBack)
Definition: CPView.j:2414
void moveUp:(id sender)
Definition: CPTextView.j:1109
var CPTextViewUsesFontPanelKey
Definition: CPTextView.j:2192
function CPUnionRange(lhsRange, rhsRange)
Definition: CPRange.j:106
var kDelegateRespondsTo_textView_willChangeSelectionFromCharacterRange_toCharacterRange
Definition: CPTextView.j:85
void scrollRangeToVisible:(CPRange aRange)
Definition: CPTextView.j:1843
void addAttribute:value:range:(CPString anAttribute, [value] id aValue, [range] CPRange aRange)
An object representation of nil.
Definition: CPNull.h:2
CPStringPboardType
Definition: CPPasteboard.j:37
var kDelegateRespondsTo_textView_shouldChangeTextInRange_replacementString
Definition: CPTextView.j:86
CPFont systemFontOfSize:(CGSize aSize)
Definition: CPFont.j:282
CPFont font()
Definition: CPTextView.j:1625
id initWithFrame:textContainer:(CGRect aFrame, [textContainer] CPTextContainer aContainer)
Definition: CPTextView.j:163
CPSelectByWord
Definition: CPTextView.j:80
void setAllowsUndo:(BOOL aValue)
Definition: CPTextView.j:2758
BOOL isEditable()
Definition: CPTextView.j:2099
void moveToRightEndOfLineAndModifySelection:(id sender)
Definition: CPTextView.j:1452
CPGeckoBrowserEngine
var CPTextViewTextStorageKey
Definition: CPTextView.j:2195
void moveToRightEndOfLine:byExtending:(id sender, [byExtending] BOOL flag)
Definition: CPTextView.j:1431
The main run loop for the application.
Definition: CPRunLoop.h:2
id generalPasteboard()
Definition: CPPasteboard.j:82
BOOL isEditable()
Definition: CPText.j:355
var kDelegateRespondsTo_textView_didChangeTypingAttributes
Definition: CPTextView.j:90
void addObserver:selector:name:object:(id anObserver, [selector] SEL aSelector, [name] CPString aNotificationName, [object] id anObject)
void performSelector:target:argument:order:modes:(SEL aSelector, [target] id aTarget, [argument] id anArgument, [order] int anOrder, [modes] CPArray modes)
Definition: CPRunLoop.j:253
CPResponder firstResponder()
Definition: CPWindow.j:1642
BOOL isRulerVisible()
Definition: CPTextView.j:1775
CGPoint locationInWindow()
Definition: CPEvent.j:290
CPColor whiteColor()
Definition: CPColor.j:360
CPArray selectedRanges()
Definition: CPTextView.j:892
id delegate()
Definition: CALayer.j:965
void setInsertionPointColor:(CPColor aValue)
Definition: CPTextView.j:2878
void moveToEndOfParagraphAndModifySelection:(id sender)
Definition: CPTextView.j:1253
CGRect bounds()
Definition: CPView.j:1302
void sizeToFit()
Definition: CPTextView.j:1790
void postNotificationName:object:(CPString aNotificationName, [object] id anObject)
void copy:(id sender)
Definition: CPTextView.j:359
CPForegroundColorAttributeName
Definition: CPText.j:97
id initWithContainerSize:(CGSize aSize)
void resignKeyWindow()
Definition: CPTextView.j:529
CPColor textColor()
Definition: CPTextView.j:2886
Unarchives objects created using CPKeyedArchiver.
CPString stringValue()
Definition: CPTextView.j:564
CPColor backgroundColor()
Definition: CPView.j:2196
void initialize()
Definition: CPView.j:240
BOOL isSelectable()
Definition: CPTextView.j:2095
CPRTFPboardType
Definition: CPPasteboard.j:41
CPNotificationCenter defaultCenter()
A mutable key-value pair collection.
Definition: CPDictionary.h:2
CPRunLoop currentRunLoop()
Definition: CPRunLoop.j:232
void setSelectedRange:(CPRange range)
Definition: CPTextView.j:794
BOOL isBlinking
Definition: CPTableView.j:6196
void setUsesFontPanel:(BOOL aValue)
Definition: CPTextView.j:2806
CPColorDragType
Definition: CPColorPanel.j:405
id initWithCoder:(CPCoder aCoder)
Definition: CPText.j:326
CPAttributedString attributedSubstringFromRange:(CPRange aRange)
var kDelegateRespondsTo_textShouldBeginEditing
Definition: CPTextView.j:83
BOOL isSelectable()
Definition: CPText.j:371
A mutable character string with attributes.
void setDelegate:(id< CPTextViewDelegate > aDelegate)
Definition: CPTextView.j:481
CPWindow window()
Definition: CPView.j:503
CGSize frameSize()
Definition: CPView.j:1032
var CPTextViewInsertionPointColorKey
Definition: CPTextView.j:2196
CPColor blackColor()
Definition: CPColor.j:283
void viewWillMoveToSuperview:(CPView aView)
Definition: CPView.j:875
FrameUpdater prototype stop
var CPTextViewSelectedTextAttributesKey
Definition: CPTextView.j:2197
CPSelectByParagraph
Definition: CPTextView.j:81
BOOL becomeFirstResponder()
Definition: CPView.j:3251
id initWithName:object:userInfo:(CPString aNotificationName, [object] id anObject, [userInfo] CPDictionary aUserInfo)
function CPEmptyRange(aRange)
Definition: CPRange.j:59
void setSelectionGranularity:(CPSelectionGranularity aValue)
Definition: CPTextView.j:2958
function CPMaxRange(aRange)
Definition: CPRange.j:70
An immutable string (collection of characters).
Definition: CPString.h:2
CGPoint convertPoint:fromView:(CGPoint aPoint, [fromView] CPView aView)
Definition: CPView.j:2208
CPUndoManager undoManager()
Definition: CPWindow.j:3614
CGSize minSize()
Definition: CPTextView.j:2822
id objectForKey:(id aKey)
Definition: CPDictionary.j:515
if(CPFeatureIsCompatible(CPHTMLCanvasFeature))
void moveDown:(id sender)
Definition: CPTextView.j:1066
CPString string()
Definition: CPTextView.j:610
var kDelegateRespondsTo_textView_shouldChangeTypingAttributes_toAttributes
Definition: CPTextView.j:87
id init()
Definition: CPView.j:325
void invalidateTextContainerOrigin()
Definition: CPTextView.j:632
CPColor colorWithRed:green:blue:alpha:(float red, [green] float green, [blue] float blue, [alpha] float alpha)
Definition: CPColor.j:121
CPRange selectedRange()
Definition: CPTextView.j:2942
CPSelectionGranularity selectionGranularity()
Definition: CPTextView.j:2950
void setNeedsDisplay:(BOOL aFlag)
Definition: CPView.j:2556
CPBackgroundColorAttributeName
Definition: CPText.j:98
BOOL isHidden()
Definition: CPView.j:1697
var CPTextViewDelegateKey
Definition: CPTextView.j:2198
function CPIntersectionRange(lhsRange, rhsRange)
Definition: CPRange.j:120
int length()
Definition: CPString.j:186
CPShiftKeyMask
CPSelectionGranularity selectionGranularity()
Definition: CPTextView.j:2950
CPFontManager sharedFontManager()
Definition: CPFontManager.j:82
A notification that can be posted to a CPNotificationCenter.
Definition: CPNotification.h:2
CPTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:(CPTimeInterval seconds, [target] id aTarget, [selector] SEL aSelector, [userInfo] id userInfo, [repeats] BOOL shouldRepeat)
Definition: CPTimer.j:58
void insertLineBreak:(id sender)
Definition: CPTextView.j:1550
void setSelectable:(BOOL flag)
Definition: CPText.j:114
void delete:(id sender)
Definition: CPTextView.j:1616
BOOL isKindOfClass:(Class aClass)
Definition: CPObject.j:219
CPUnderlineStyleAttributeName
Definition: CPText.j:100
void copy:(id sender)
Definition: CPText.j:139
BOOL acceptsFirstResponder()
Definition: CPTextView.j:442
CGPoint draggingLocation()
Definition: CPDragServer.j:63
void setTypingAttributes:(CPDictionary attributes)
Definition: CPTextView.j:1584
function CPMakeRangeCopy(aRange)
Definition: CPRange.j:48
void moveWordRight:(id sender)
Definition: CPTextView.j:1315
BOOL isRichText()
Definition: CPText.j:387
void setTextColor:range:(CPColor aColor, [range] CPRange range)
Definition: CPTextView.j:1724
void moveToLeftEndOfLine:byExtending:(id sender, [byExtending] BOOL flag)
Definition: CPTextView.j:1408
int clickCount()
Definition: CPEvent.j:372
CPTextStorage textStorage()
Definition: CPTextView.j:2982
BOOL isKeyWindow()
Definition: CPWindow.j:2051
A timer object that can send a message after the given time interval.
Definition: CPTimer.h:2
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
CPColor selectedTextBackgroundColor()
Definition: CPColor.j:497
CPFontAttributeName
Definition: CPText.j:96
void setFont:(CPFont font)
Definition: CPTextView.j:1630
CPNotFound
Definition: CPObjJRuntime.j:62
id numberWithInt:(int anInt)
Definition: CPNumber.j:74
void setSelectedRange:affinity:stillSelecting:(CPRange range, [affinity] CPSelectionAffinity affinity, [stillSelecting] BOOL selecting)
Definition: CPTextView.j:800
id unarchiveObjectWithData:(CPData aData)
void insertText:(CPString aString)
Definition: CPTextView.j:687
Sends messages (CPNotification) between objects.
function CPLocationInRange(aLocation, aRange)
Definition: CPRange.j:93
void drawRect:(CGRect aRect)
Definition: CPTextView.j:739
id new()
Definition: CPObject.j:122
float size()
Definition: CPFont.j:375
CGSize maxSize()
Definition: CPTextView.j:2838
CPString objectValue()
Definition: CPTextView.j:569
id prepareWithInvocationTarget:(id aTarget)
void setBackgroundColor:(CPColor aColor)
Definition: CPView.j:1923
var kDelegateRespondsTo_textView_textDidChange
Definition: CPTextView.j:88
void setTextView:(CPTextView aTextView)
void moveToLeftEndOfLineAndModifySelection:(id sender)
Definition: CPTextView.j:1426
BOOL needsPanelToBecomeKey()
Definition: CPTextView.j:943
void setFrameSize:(CGSize aSize)
Definition: CPView.j:1100
void becomeKeyWindow()
Definition: CPTextView.j:521
void removeObserver:name:object:(id anObserver, [name] CPString aNotificationName, [object] id anObject)
Definition: CPEvent.h:2
var kDelegateRespondsTo_textView_doCommandBySelector
Definition: CPTextView.j:84
CPRange selectionRangeForProposedRange:granularity:(CPRange proposedRange, [granularity] CPSelectionGranularity granularity)
Definition: CPTextView.j:1925
CPAttributedStringBinding
CPView superview()
Definition: CPView.j:486
var CPTextViewLayoutManagerKey
Definition: CPTextView.j:2194
void moveToBeginningOfParagraphAndModifySelection:(id sender)
Definition: CPTextView.j:1330
void deleteBackward:ignoreSmart:(id sender, [ignoreSmart] BOOL ignoreFlag)
Definition: CPTextView.j:1502
BOOL resignFirstResponder()
Definition: CPTextView.j:464
function CPBrowserIsEngine(anEngine)
void moveLeft:(id sender)
Definition: CPTextView.j:1235
void moveWordLeftAndModifySelection:(id sender)
Definition: CPTextView.j:1457
void drawInsertionPointInRect:color:turnedOn:(CGRect aRect, [color] CPColor aColor, [turnedOn] BOOL flag)
Definition: CPTextView.j:726
void moveWordLeft:(id sender)
Definition: CPTextView.j:1463
CGRect frame()
Definition: CPView.j:1022
CPAppKitDefined
void setSelectedFont:isMultiple:(CPFont aFont, [isMultiple] BOOL aFlag)
void didChangeText()
Definition: CPTextView.j:647
CPValueBinding
BOOL shouldDrawInsertionPoint()
Definition: CPTextView.j:1985
A bridged object to native Javascript numbers.
Definition: CPNumber.h:2
BOOL scrollRectToVisible:(CGRect aRect)
Definition: CPView.j:2821
void moveWordRightAndModifySelection:(id sender)
Definition: CPTextView.j:1348
id< CPTextViewDelegate > _delegate accessors(property=delegate)
void updateInsertionPointStateAndRestartTimer:(BOOL flag)
Definition: CPTextView.j:1990
id string()
Definition: CPString.j:98
void deleteBackward:(id sender)
Definition: CPTextView.j:1521
id initWithObjects:forKeys:(CPArray objects, [forKeys] CPArray keyArray)
Definition: CPDictionary.j:216
void setEditable:(BOOL flag)
Definition: CPText.j:124
void moveLeftAndModifySelection:(id sender)
Definition: CPTextView.j:1213
CPSelectionAffinity selectionAffinity()
Definition: CPTextView.j:1770
CPRange function CPMakeRange(location, length)
Definition: CPRange.j:37
void insertTab:(id sender)
Definition: CPTextView.j:1555
void setFont:range:(CPFont font, [range] CPRange range)
Definition: CPTextView.j:1644
id pasteboardWithName:(CPString aName)
Definition: CPPasteboard.j:92
CPDictionary copy()
Definition: CPDictionary.j:292
void setString:(id aString)
Definition: CPTextView.j:590
CPNullMarker
void registerForDraggedTypes:(CPArray pasteboardTypes)
Definition: CPView.j:2423
void setRichText:(BOOL aValue)
Definition: CPText.j:395
CPDictionary typingAttributes()
Definition: CPTextView.j:2918
id alloc()
Definition: CPObject.j:130
Definition: CPView.j:136
var CPTextViewContainerKey
Definition: CPTextView.j:2193
id dictionaryWithObject:forKey:(id anObject, [forKey] id aKey)
Definition: CPDictionary.j:81
var kDelegateRespondsTo_textView_didChangeSelection
Definition: CPTextView.j:89
var CPTextViewAllowsUndoKey
Definition: CPTextView.j:2191
FrameUpdater prototype description
Definition: CPText.h:2