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