1/** <title>NSTextView</title>
2
3   <abstract>Categories which add user actions to NSTextView</abstract>
4
5   Copyright (C) 1996, 1998, 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
6
7   Originally moved here from NSTextView.m.
8
9   Author: Scott Christley <scottc@net-community.com>
10   Date: 1996
11
12   Author: Felipe A. Rodriguez <far@ix.netcom.com>
13   Date: July 1998
14
15   Author: Daniel B�hringer <boehring@biomed.ruhr-uni-bochum.de>
16   Date: August 1998
17
18   Author: Fred Kiefer <FredKiefer@gmx.de>
19   Date: March 2000, September 2000
20
21   Author: Nicola Pero <n.pero@mi.flashnet.it>
22   Date: 2000, 2001, 2002
23
24   Author: Pierre-Yves Rivaille <pyrivail@ens-lyon.fr>
25   Date: September 2002
26
27   Extensive reworking: Alexander Malmberg <alexander@malmberg.org>
28   Date: December 2002 - February 2003
29
30   This file is part of the GNUstep GUI Library.
31
32   This library is free software; you can redistribute it and/or
33   modify it under the terms of the GNU Lesser General Public
34   License as published by the Free Software Foundation; either
35   version 2 of the License, or (at your option) any later version.
36
37   This library is distributed in the hope that it will be useful,
38   but WITHOUT ANY WARRANTY; without even the implied warranty of
39   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
40   Lesser General Public License for more details.
41
42   You should have received a copy of the GNU Lesser General Public
43   License along with this library; see the file COPYING.LIB.
44   If not, see <http://www.gnu.org/licenses/> or write to the
45   Free Software Foundation, 51 Franklin Street, Fifth Floor,
46   Boston, MA 02110-1301, USA.
47*/
48
49#import <Foundation/NSNotification.h>
50#import <Foundation/NSValue.h>
51#import "AppKit/NSAttributedString.h"
52#import "AppKit/NSGraphics.h"
53#import "AppKit/NSLayoutManager.h"
54#import "AppKit/NSPasteboard.h"
55#import "AppKit/NSScrollView.h"
56#import "AppKit/NSTextStorage.h"
57#import "AppKit/NSTextView.h"
58#import "AppKit/NSParagraphStyle.h"
59
60/*
61These methods are for user actions, ie. they are normally called from
62-doCommandBySelector: (which is called by the input manager) in response
63to some key press or other user event.
64
65User actions that modify the text must check that a modification is allowed
66and make sure all necessary notifications are sent. This is done by sending
67-shouldChangeTextInRange:replacementString: before making any changes, and
68(if the change is allowed) -didChangeText after the changes have been made.
69
70All actions from NSResponder that make sense for a text view  should be
71implemented here, but this is _not_ the place to add new actions.
72
73When changing attributes, the range returned by
74rangeForUserCharacterAttributeChange or rangeForUserParagraphAttributeChange
75should be used. If the location is NSNotFound, nothing should be done (in
76particular, the typing attributes should _not_ be changed). Otherwise,
77-shouldChangeTextInRange:replacementString: should be called, and if it
78returns YES, the attributes of the range and the typing attributes should be
79changed, and -didChangeText should be called.
80
81In a non-rich-text text view, the typing attributes _must_always_ hold the
82attributes of the text. Thus, the typing attributes must always be changed
83in the same way that the attributes of the text are changed.
84
85TODO: can the selected range's location be NSNotFound? when?
86
87Not all user actions are here. Exceptions:
88
89  -copy:
90  -copyFont:
91  -copyRuler:
92  -paste:
93  -pasteFont:
94  -pasteRuler:
95  -pasteAsPlainText:
96  -pasteAsRichText:
97
98  -checkSpelling:
99  -showGuessPanel:
100
101  -selectAll: (implemented in NSText)
102
103Not all methods that handle user-induced text modifications are here.
104Exceptions:
105  (TODO)
106
107  -insertText:
108  -changeColor:
109  -changeFont: (action method?)
110  drag&drop handling methods
111  (others?)
112
113All other methods that modify text are for programmatic changes and do not
114send -shouldChangeTextInRange:replacementString: or -didChangeText.
115
116*/
117
118/* global kill buffer shared between all text views */
119/* Note: I'm not using an attributed string here because Apple apparently is
120   using a plain string either. Maybe this is because NeXT was using the X11
121   cut buffer for the kill buffer, which can hold only plain strings? */
122static NSString *killBuffer = @"";
123
124/** First some helpers **/
125
126@interface NSTextView (UserActionHelpers)
127
128-(void) _illegalMovement: (int)textMovement;
129
130-(void) _changeAttribute: (NSString *)name
131		 inRange: (NSRange)r
132		   using: (NSNumber*(*)(NSNumber*))func;
133
134@end
135
136
137@implementation NSTextView (UserActionHelpers)
138
139- (void) _illegalMovement: (int)textMovement
140{
141  /* This is similar to [self resignFirstResponder], with the
142     difference that in the notification we need to put the
143     NSTextMovement, which resignFirstResponder does not.  Also, if we
144     are ending editing, we are going to be removed, so it's useless
145     to update any drawing.  Please note that this ends up calling
146     resignFirstResponder anyway.  */
147  NSNumber *number;
148  NSDictionary *uiDictionary;
149
150  if ((_tf.is_editable)
151      && ([_delegate respondsToSelector:
152		       @selector(textShouldEndEditing:)])
153      && ([_delegate textShouldEndEditing: self] == NO))
154    return;
155
156  /* TODO: insertion point.
157  doesn't the -resignFirstResponder take care of that?
158  */
159
160  number = [NSNumber numberWithInt: textMovement];
161  uiDictionary = [NSDictionary dictionaryWithObject: number
162			       forKey: @"NSTextMovement"];
163  [[NSNotificationCenter defaultCenter]
164    postNotificationName: NSTextDidEndEditingNotification
165		  object: self
166		userInfo: uiDictionary];
167  /* The TextField will get the notification, and drop our first responder
168   * status if it's the case ... in that case, our -resignFirstResponder will
169   * be called!  */
170  return;
171}
172
173
174- (void) _changeAttribute: (NSString *)name
175		  inRange: (NSRange)r
176		    using: (NSNumber*(*)(NSNumber*))func
177{
178  NSUInteger i;
179  NSRange e, r2;
180  id current, new;
181
182  if (![self shouldChangeTextInRange: r replacementString: nil])
183    return;
184
185  [_textStorage beginEditing];
186  for (i = r.location; i < NSMaxRange(r);)
187    {
188      current = [_textStorage attribute: name
189				atIndex: i
190			 effectiveRange: &e];
191
192      r2 = NSMakeRange(i, NSMaxRange(e) - i);
193      r2 = NSIntersectionRange(r2, r);
194      i = NSMaxRange(e);
195
196      new = func(current);
197      if (new != current)
198	{
199	  if (!new)
200	    {
201	      [_textStorage removeAttribute: name
202				      range: r2];
203	    }
204	  else
205	    {
206	      [_textStorage addAttribute: name
207				   value: new
208				   range: r2];
209	    }
210	}
211    }
212  [_textStorage endEditing];
213
214  current = [_layoutManager->_typingAttributes objectForKey: name];
215  new = func(current);
216  if (new != current)
217    {
218      if (!new)
219	{
220	  [_layoutManager->_typingAttributes removeObjectForKey: name];
221	}
222      else
223	{
224	  [_layoutManager->_typingAttributes setObject: new  forKey: name];
225	}
226    }
227
228  [self didChangeText];
229}
230
231@end
232
233
234@implementation NSTextView (UserActions)
235
236/* Helpers used with _changeAttribute:inRange:using:. */
237static NSNumber *int_minus_one(NSNumber *cur)
238{
239  int value;
240
241  if (cur)
242    value = [cur intValue] - 1;
243  else
244    value = -1;
245
246  if (value)
247    return [NSNumber numberWithInt: value];
248  else
249    return nil;
250}
251
252static NSNumber *int_plus_one(NSNumber *cur)
253{
254  int value;
255
256  if (cur)
257    value = [cur intValue] + 1;
258  else
259    value = 1;
260
261  if (value)
262    return [NSNumber numberWithInt: value];
263  else
264    return nil;
265}
266
267static NSNumber *float_minus_one(NSNumber *cur)
268{
269  float value;
270
271  if (cur)
272    value = [cur floatValue] - 1;
273  else
274    value = -1;
275
276  if (value)
277    return [NSNumber numberWithFloat: value];
278  else
279    return nil;
280}
281
282static NSNumber *float_plus_one(NSNumber *cur)
283{
284  int value;
285
286  if (cur)
287    value = [cur floatValue] + 1;
288  else
289    value = 1;
290
291  if (value)
292    return [NSNumber numberWithFloat: value];
293  else
294    return nil;
295}
296
297
298- (void) subscript: (id)sender
299{
300  NSRange r = [self rangeForUserCharacterAttributeChange];
301
302  if (r.location == NSNotFound)
303    return;
304
305  [self _changeAttribute: NSSuperscriptAttributeName
306		 inRange: r
307		   using: int_minus_one];
308}
309
310- (void) superscript: (id)sender
311{
312  NSRange r = [self rangeForUserCharacterAttributeChange];
313
314  if (r.location == NSNotFound)
315    return;
316
317  [self _changeAttribute: NSSuperscriptAttributeName
318		 inRange: r
319		   using: int_plus_one];
320}
321
322- (void) lowerBaseline: (id)sender
323{
324  NSRange r = [self rangeForUserCharacterAttributeChange];
325
326  if (r.location == NSNotFound)
327    return;
328
329  [self _changeAttribute: NSBaselineOffsetAttributeName
330		 inRange: r
331		   using: float_plus_one];
332}
333
334- (void) raiseBaseline: (id)sender
335{
336  NSRange r = [self rangeForUserCharacterAttributeChange];
337
338  if (r.location == NSNotFound)
339    return;
340
341  [self _changeAttribute: NSBaselineOffsetAttributeName
342		 inRange: r
343		   using: float_minus_one];
344}
345
346- (void) unscript: (id)sender
347{
348  NSRange aRange = [self rangeForUserCharacterAttributeChange];
349
350  if (aRange.location == NSNotFound)
351    return;
352
353  if (![self shouldChangeTextInRange: aRange
354		   replacementString: nil])
355    return;
356
357  if (aRange.length)
358    {
359      [_textStorage beginEditing];
360      [_textStorage removeAttribute: NSSuperscriptAttributeName
361			      range: aRange];
362      [_textStorage removeAttribute: NSBaselineOffsetAttributeName
363			      range: aRange];
364      [_textStorage endEditing];
365    }
366
367  [_layoutManager->_typingAttributes removeObjectForKey: NSSuperscriptAttributeName];
368  [_layoutManager->_typingAttributes removeObjectForKey: NSBaselineOffsetAttributeName];
369  [self didChangeText];
370}
371
372
373- (void) underline: (id)sender
374{
375  BOOL doUnderline = YES;
376  NSRange aRange = [self rangeForUserCharacterAttributeChange];
377
378  if (aRange.location == NSNotFound)
379    return;
380
381  if ([[_textStorage attribute: NSUnderlineStyleAttributeName
382		     atIndex: aRange.location
383		     effectiveRange: NULL] intValue])
384    doUnderline = NO;
385
386  if (aRange.length)
387    {
388      if (![self shouldChangeTextInRange: aRange
389		 replacementString: nil])
390	return;
391      [_textStorage beginEditing];
392      [_textStorage addAttribute: NSUnderlineStyleAttributeName
393		    value: [NSNumber numberWithInt: doUnderline]
394		    range: aRange];
395      [_textStorage endEditing];
396      [self didChangeText];
397    }
398
399  [_layoutManager->_typingAttributes
400      setObject: [NSNumber numberWithInt: doUnderline]
401      forKey: NSUnderlineStyleAttributeName];
402}
403
404
405- (void) useStandardKerning: (id)sender
406{
407  NSRange aRange = [self rangeForUserCharacterAttributeChange];
408
409  if (aRange.location == NSNotFound)
410    return;
411  if (![self shouldChangeTextInRange: aRange
412	    replacementString: nil])
413    return;
414
415  [_textStorage removeAttribute: NSKernAttributeName
416		range: aRange];
417  [_layoutManager->_typingAttributes removeObjectForKey: NSKernAttributeName];
418  [self didChangeText];
419}
420
421- (void) turnOffKerning: (id)sender
422{
423  NSRange aRange = [self rangeForUserCharacterAttributeChange];
424
425  if (aRange.location == NSNotFound)
426    return;
427  if (![self shouldChangeTextInRange: aRange
428	    replacementString: nil])
429    return;
430
431  [_textStorage addAttribute: NSKernAttributeName
432		value: [NSNumber numberWithFloat: 0.0]
433		range: aRange];
434  [_layoutManager->_typingAttributes setObject: [NSNumber numberWithFloat: 0.0]
435    forKey: NSKernAttributeName];
436  [self didChangeText];
437}
438
439- (void) loosenKerning: (id)sender
440{
441  NSRange r = [self rangeForUserCharacterAttributeChange];
442
443  if (r.location == NSNotFound)
444    return;
445
446  [self _changeAttribute: NSKernAttributeName
447		 inRange: r
448		   using: float_plus_one];
449}
450
451- (void) tightenKerning: (id)sender
452{
453  NSRange r = [self rangeForUserCharacterAttributeChange];
454
455  if (r.location == NSNotFound)
456    return;
457
458  [self _changeAttribute: NSKernAttributeName
459		 inRange: r
460		   using: float_minus_one];
461}
462
463- (void) turnOffLigatures: (id)sender
464{
465  NSRange aRange = [self rangeForUserCharacterAttributeChange];
466
467  if (aRange.location == NSNotFound)
468    return;
469
470  if (![self shouldChangeTextInRange: aRange
471	    replacementString: nil])
472    return;
473  [_textStorage addAttribute: NSLigatureAttributeName
474		value: [NSNumber numberWithInt: 0]
475		range: aRange];
476  [_layoutManager->_typingAttributes setObject: [NSNumber numberWithInt: 0]
477    forKey: NSLigatureAttributeName];
478  [self didChangeText];
479}
480
481- (void) useStandardLigatures: (id)sender
482{
483  NSRange aRange = [self rangeForUserCharacterAttributeChange];
484
485  if (aRange.location == NSNotFound)
486    return;
487
488  if (![self shouldChangeTextInRange: aRange
489	    replacementString: nil])
490    return;
491
492  [_textStorage removeAttribute: NSLigatureAttributeName
493		range: aRange];
494  [_layoutManager->_typingAttributes removeObjectForKey: NSLigatureAttributeName];
495  [self didChangeText];
496}
497
498- (void) useAllLigatures: (id)sender
499{
500  NSRange aRange = [self rangeForUserCharacterAttributeChange];
501
502  if (aRange.location == NSNotFound)
503    return;
504
505  if (![self shouldChangeTextInRange: aRange
506	    replacementString: nil])
507    return;
508  [_textStorage addAttribute: NSLigatureAttributeName
509		value: [NSNumber numberWithInt: 2]
510		range: aRange];
511  [_layoutManager->_typingAttributes setObject: [NSNumber numberWithInt: 2]
512    forKey: NSLigatureAttributeName];
513  [self didChangeText];
514}
515
516- (void) toggleTraditionalCharacterShape: (id)sender
517{
518  // TODO
519  NSLog(@"Method %s is not implemented for class %s",
520	"toggleTraditionalCharacterShape:", "NSTextView");
521}
522
523
524- (void) insertNewline: (id)sender
525{
526  if (_tf.is_field_editor)
527    {
528      [self _illegalMovement: NSReturnTextMovement];
529      return;
530    }
531
532  [self insertText: @"\n"];
533}
534
535- (void) insertTab: (id)sender
536{
537  if (_tf.is_field_editor)
538    {
539      [self _illegalMovement: NSTabTextMovement];
540      return;
541    }
542
543  [self insertText: @"\t"];
544}
545
546- (void) insertBacktab: (id)sender
547{
548  if (_tf.is_field_editor)
549    {
550      [self _illegalMovement: NSBacktabTextMovement];
551      return;
552    }
553
554  /* TODO */
555  //[self insertText: @"\t"];
556}
557
558- (void) insertNewlineIgnoringFieldEditor: (id)sender
559{
560  [self insertText: @"\n"];
561}
562
563- (void) insertTabIgnoringFieldEditor: (id)sender
564{
565  [self insertText: @"\t"];
566}
567
568- (void) insertContainerBreak: (id)sender
569{
570  unichar ch = NSFormFeedCharacter;
571  [self insertText: [NSString stringWithCharacters: &ch length: 1]];
572}
573
574- (void) insertLineBreak: (id)sender
575{
576  unichar ch = NSLineSeparatorCharacter;
577  [self insertText: [NSString stringWithCharacters: &ch length: 1]];
578}
579
580- (void) deleteForward: (id)sender
581{
582  NSRange range = [self rangeForUserTextChange];
583  NSDictionary *attributes;
584
585  if (range.location == NSNotFound)
586    {
587      return;
588    }
589
590  /* Manage case of insertion point - implicitly means to delete following
591     character */
592  if (range.length == 0)
593    {
594      if (range.location != [_textStorage length])
595	{
596	  /* Not at the end of text -- delete following character */
597	  range.length = 1;
598	}
599      else
600	{
601	  /* At the end of text - TODO: Make beeping or not beeping
602	     configurable vie User Defaults */
603	  NSBeep ();
604	  return;
605	}
606    }
607  else if ([self smartInsertDeleteEnabled] &&
608	   [self selectionGranularity] == NSSelectByWord)
609    {
610      range = [self smartDeleteRangeForProposedRange: range];
611    }
612
613  if (![self shouldChangeTextInRange: range  replacementString: @""])
614    {
615      return;
616    }
617
618  attributes = RETAIN([_textStorage attributesAtIndex: range.location
619				       effectiveRange: NULL]);
620  [_textStorage beginEditing];
621  [_textStorage deleteCharactersInRange: range];
622  [_textStorage endEditing];
623  [self setTypingAttributes: attributes];
624  RELEASE(attributes);
625  [self didChangeText];
626}
627
628- (void) deleteBackward: (id)sender
629{
630  NSRange range = [self rangeForUserTextChange];
631  NSDictionary *attributes;
632
633  if (range.location == NSNotFound)
634    {
635      return;
636    }
637
638  /* Manage case of insertion point - implicitly means to delete
639     previous character */
640  if (range.length == 0)
641    {
642      if (range.location != 0)
643	{
644	  /* Not at the beginning of text -- delete previous character */
645	  range.location -= 1;
646	  range.length = 1;
647	}
648      else
649	{
650	  /* At the beginning of text - TODO: Make beeping or not
651	     beeping configurable via User Defaults */
652	  NSBeep ();
653	  return;
654	}
655    }
656  else if ([self smartInsertDeleteEnabled] &&
657	   [self selectionGranularity] == NSSelectByWord)
658    {
659      range = [self smartDeleteRangeForProposedRange: range];
660    }
661
662  if (![self shouldChangeTextInRange: range  replacementString: @""])
663    {
664      return;
665    }
666
667  attributes = RETAIN([_textStorage attributesAtIndex: range.location
668				       effectiveRange: NULL]);
669  [_textStorage beginEditing];
670  [_textStorage deleteCharactersInRange: range];
671  [_textStorage endEditing];
672  [self setTypingAttributes: attributes];
673  RELEASE(attributes);
674  [self didChangeText];
675}
676
677- (void) deleteToEndOfLine: (id)sender
678{
679  NSRange range = [self rangeForUserTextChange];
680  NSDictionary *attributes;
681
682  if (range.location == NSNotFound)
683    {
684      return;
685    }
686
687  /* If the selection is not empty delete it, otherwise delete up to the
688     next line end from the insertion point or the delete the line end
689     itself when the insertion point is already at the end of the line. */
690  if (range.length == 0)
691    {
692      NSUInteger start, end, contentsEnd;
693
694      [[_textStorage string] getLineStart: &start
695				      end: &end
696			      contentsEnd: &contentsEnd
697				 forRange: range];
698      if (range.location == contentsEnd)
699	{
700	  range = NSMakeRange(contentsEnd, end - contentsEnd);
701	}
702      else
703	{
704	  range.length = contentsEnd - range.location;
705	}
706      if (range.length == 0)
707	{
708	  return;
709	}
710    }
711
712  if (![self shouldChangeTextInRange: range  replacementString: @""])
713    {
714      return;
715    }
716
717  ASSIGN(killBuffer, [[_textStorage string] substringWithRange: range]);
718  attributes = RETAIN([_textStorage attributesAtIndex: range.location
719				       effectiveRange: NULL]);
720  [_textStorage beginEditing];
721  [_textStorage deleteCharactersInRange: range];
722  [_textStorage endEditing];
723  [self setTypingAttributes: attributes];
724  RELEASE(attributes);
725  [self didChangeText];
726}
727
728- (void) deleteToEndOfParagraph: (id)sender
729{
730  NSRange range = [self rangeForUserTextChange];
731  NSDictionary *attributes;
732
733  if (range.location == NSNotFound)
734    {
735      return;
736    }
737
738  /* If the selection is not empty delete it, otherwise delete up to
739     the next paragraph end from the insertion point or the delete the
740     paragraph end itself when the insertion point is already at the
741     end of the paragraph. */
742  if (range.length == 0)
743    {
744      NSUInteger start, end, contentsEnd;
745
746      [[_textStorage string] getParagraphStart: &start
747					   end: &end
748				   contentsEnd: &contentsEnd
749				      forRange: range];
750      if (range.location == contentsEnd)
751	{
752	  range = NSMakeRange(contentsEnd, end - contentsEnd);
753	}
754      else
755	{
756	  range.length = contentsEnd - range.location;
757	}
758      if (range.length == 0)
759	{
760	  return;
761	}
762    }
763
764  if (![self shouldChangeTextInRange: range  replacementString: @""])
765    {
766      return;
767    }
768
769  ASSIGN(killBuffer, [[_textStorage string] substringWithRange: range]);
770  attributes = RETAIN([_textStorage attributesAtIndex: range.location
771				       effectiveRange: NULL]);
772  [_textStorage beginEditing];
773  [_textStorage deleteCharactersInRange: range];
774  [_textStorage endEditing];
775  [self setTypingAttributes: attributes];
776  RELEASE(attributes);
777  [self didChangeText];
778}
779
780- (void) yank: (id)sender
781{
782  if ([killBuffer length] > 0)
783    {
784      [self insertText: killBuffer];
785    }
786}
787
788
789/*
790TODO: find out what affinity is supposed to mean
791
792My current assumption:
793
794Affinity deals with which direction we are selecting in, ie. which end of
795the selected range is the moving end, and which is the anchor.
796
797NSSelectionAffinityUpstream means that the minimum index of the selected
798range is moving (ie. _selected_range.location).
799
800NSSelectionAffinityDownstream means that the maximum index of the selected
801range is moving (ie. _selected_range.location+_selected_range.length).
802
803Thus, when moving and selecting, we use the affinity to find out which end
804of the selected range to move, and after moving, we compare the character
805index we moved to with the anchor and set the range and affinity.
806
807
808The affinity is important when making keyboard selection have sensible
809behavior. Example:
810
811If, in the string "abcd", the insertion point is between the "c" and the "d"
812(selected range is (3,0)), and the user hits shift-left twice, we select
813the "c" and "b" (1,2) and set the affinity to NSSelectionAffinityUpstream.
814If the user hits shift-right, only the "c" will be selected (2,1).
815
816If the insertion point is between the "a" and the "b" (1,0) and the user hits
817shift-right twice, we again select the "b" and "c" (1,2), but the affinity
818is NSSelectionAffinityDownstream. If the user hits shift-right, the "d" is
819added to the selection (1,3).
820
821*/
822
823- (unsigned int) _movementOrigin
824{
825  NSRange range = [self selectedRange];
826
827  if ([self selectionAffinity] == NSSelectionAffinityUpstream)
828    return range.location;
829  else
830    return NSMaxRange(range);
831}
832
833- (NSUInteger) _movementEnd
834{
835  NSRange range = [self selectedRange];
836
837  if ([self selectionAffinity] == NSSelectionAffinityDownstream)
838    return range.location;
839  else
840    return NSMaxRange(range);
841}
842
843- (void) _moveTo: (NSUInteger)cindex
844	  select: (BOOL)select
845{
846  if (select)
847    {
848      NSUInteger anchor = [self _movementEnd];
849
850      if (anchor < cindex)
851	{
852	  [self setSelectedRange: NSMakeRange(anchor, cindex - anchor)
853			affinity: NSSelectionAffinityDownstream
854		  stillSelecting: NO];
855	}
856      else
857 	{
858	  [self setSelectedRange: NSMakeRange(cindex, anchor - cindex)
859			affinity: NSSelectionAffinityUpstream
860		  stillSelecting: NO];
861	}
862    }
863  else
864    {
865      [self setSelectedRange: NSMakeRange(cindex, 0)];
866    }
867  [self scrollRangeToVisible: NSMakeRange(cindex, 0)];
868}
869
870- (void) _moveFrom: (NSUInteger)cindex
871	 direction: (GSInsertionPointMovementDirection)direction
872          distance: (CGFloat)distance
873	    select: (BOOL)select
874{
875  int new_direction;
876
877  if (direction == GSInsertionPointMoveUp ||
878      direction == GSInsertionPointMoveDown)
879    {
880      new_direction = 2;
881    }
882  else if (direction == GSInsertionPointMoveLeft ||
883	   direction == GSInsertionPointMoveRight)
884    {
885      new_direction = 1;
886    }
887  else
888    {
889      new_direction = 0;
890    }
891
892  if (new_direction != _currentInsertionPointMovementDirection ||
893      !new_direction)
894    {
895      _originalInsertionPointCharacterIndex = cindex;
896    }
897
898  cindex = [_layoutManager characterIndexMoving: direction
899	      fromCharacterIndex: cindex
900	      originalCharacterIndex: _originalInsertionPointCharacterIndex
901	      distance: distance];
902  [self _moveTo: cindex
903	 select: select];
904  /* Setting the selected range will clear out the current direction, but
905  not the index. Thus, we always set the direction here. */
906  _currentInsertionPointMovementDirection = new_direction;
907}
908
909- (void) _move: (GSInsertionPointMovementDirection)direction
910      distance: (CGFloat)distance
911	select: (BOOL)select
912{
913  [self _moveFrom: [self _movementOrigin]
914	direction: direction
915	 distance: distance
916	   select: select];
917}
918
919
920/*
921 * returns the character index for the left or right side of the selected text
922 * based upon the writing direction of the paragraph style.
923 * it should only be used when moving a literal direction such as left right
924 * up or down, not directions like forward, backward, beginning or end
925 */
926- (NSUInteger) _characterIndexForSelectedRange: (NSRange)range
927       direction: (GSInsertionPointMovementDirection)direction
928{
929  NSUInteger cIndex;
930  NSParagraphStyle *parStyle;
931  NSWritingDirection writingDirection;
932
933  parStyle = [[self typingAttributes]
934	  	objectForKey: NSParagraphStyleAttributeName];
935  writingDirection = [parStyle baseWritingDirection];
936
937  switch (writingDirection)
938    {
939      case NSWritingDirectionLeftToRight:
940        cIndex = (direction == GSInsertionPointMoveLeft
941		  || direction == GSInsertionPointMoveUp)
942	         ? range.location
943		 : NSMaxRange(range);
944        break;
945      case NSWritingDirectionRightToLeft:
946        cIndex = (direction == GSInsertionPointMoveLeft
947		  || direction == GSInsertionPointMoveUp)
948        	 ? NSMaxRange(range)
949		 : range.location;
950        break;
951      case NSWritingDirectionNaturalDirection:
952        // not sure if we should see this as it should resolve to either
953        // LeftToRight or RightToLeft in NSParagraphStyle
954        // for the users language.
955	//
956	// currently falls back to default..
957      default:
958	/* default to LeftToRight */
959        cIndex = (direction == GSInsertionPointMoveLeft
960		  || direction == GSInsertionPointMoveUp)
961	         ? range.location
962		 : NSMaxRange(range);
963        break;
964     }
965  return cIndex;
966}
967
968/*
969Insertion point movement actions.
970
971TODO: some of these used to do nothing if self is a field editor. should
972check if there was a reason for that.
973*/
974
975- (void) moveUp: (id)sender
976{
977  NSRange range = [self selectedRange];
978  NSUInteger cIndex = [self _characterIndexForSelectedRange:range
979	  			direction:GSInsertionPointMoveUp];
980
981  [self _moveFrom: cIndex
982	direction: GSInsertionPointMoveUp
983	distance: 0.0
984	select: NO];
985}
986
987- (void) moveUpAndModifySelection: (id)sender
988{
989  [self _move: GSInsertionPointMoveUp
990	distance: 0.0
991	select: YES];
992}
993
994- (void) moveDown: (id)sender
995{
996  NSRange range = [self selectedRange];
997  NSUInteger cIndex = [self _characterIndexForSelectedRange: range
998	  			direction: GSInsertionPointMoveDown];
999  [self _moveFrom: cIndex
1000	direction: GSInsertionPointMoveDown
1001	distance: 0.0
1002	select: NO];
1003}
1004
1005- (void) moveDownAndModifySelection: (id)sender
1006{
1007  [self _move: GSInsertionPointMoveDown
1008	distance: 0.0
1009	select: YES];
1010}
1011
1012- (void) moveLeft: (id)sender
1013{
1014  NSRange range = [self selectedRange];
1015
1016  if (range.length)
1017    {
1018      NSUInteger cIndex;
1019
1020      cIndex = [self _characterIndexForSelectedRange: range
1021	      		direction:GSInsertionPointMoveLeft];
1022      [self _moveTo: cIndex select: NO];
1023    }
1024  else
1025    {
1026      [self _move: GSInsertionPointMoveLeft
1027 	    distance: 0.0
1028	    select: NO];
1029    }
1030}
1031
1032- (void) moveLeftAndModifySelection: (id)sender
1033{
1034  NSParagraphStyle *parStyle;
1035  NSWritingDirection writingDirection;
1036
1037  parStyle = [[self typingAttributes]
1038	  	objectForKey: NSParagraphStyleAttributeName];
1039  writingDirection = [parStyle baseWritingDirection];
1040
1041  if (writingDirection == NSWritingDirectionRightToLeft)
1042    {
1043        [self moveForwardAndModifySelection: sender];
1044    }
1045  else
1046    {
1047        [self moveBackwardAndModifySelection: sender];
1048    }
1049}
1050
1051- (void) moveRight: (id)sender
1052{
1053  NSRange range = [self selectedRange];
1054
1055  if (range.length)
1056    {
1057      NSUInteger cIndex;
1058
1059      cIndex = [self _characterIndexForSelectedRange: range
1060	       		direction: GSInsertionPointMoveRight];
1061      [self _moveTo: cIndex select: NO];
1062    }
1063  else
1064    {
1065      [self _move: GSInsertionPointMoveRight
1066 	    distance: 0.0
1067	    select: NO];
1068    }
1069}
1070
1071- (void) moveRightAndModifySelection: (id)sender
1072{
1073  NSParagraphStyle *parStyle;
1074  NSWritingDirection writingDirection;
1075
1076  parStyle = [[self typingAttributes]
1077	  	objectForKey: NSParagraphStyleAttributeName];
1078  writingDirection = [parStyle baseWritingDirection];
1079
1080  if (writingDirection == NSWritingDirectionRightToLeft)
1081    {
1082      [self moveBackwardAndModifySelection: sender];
1083    }
1084  else
1085    {
1086      [self moveForwardAndModifySelection: sender];
1087    }
1088}
1089
1090- (void) moveBackward: (id)sender
1091{
1092  NSRange range = [self selectedRange];
1093  NSUInteger to = range.location;
1094
1095  if (range.length == 0 && to)
1096    {
1097      to--;
1098    }
1099
1100  [self _moveTo: to
1101	 select: NO];
1102}
1103
1104- (void) moveBackwardAndModifySelection: (id)sender
1105{
1106  NSUInteger to = [self _movementOrigin];
1107
1108  if (to == 0)
1109    return;
1110  to--;
1111  [self _moveTo: to
1112	 select: YES];
1113}
1114
1115- (void) moveForward: (id)sender
1116{
1117  NSRange range = [self selectedRange];
1118  NSUInteger to = NSMaxRange(range);
1119
1120  if (range.length == 0 && to != [_textStorage length])
1121    {
1122      to++;
1123    }
1124
1125  [self _moveTo: to
1126	 select: NO];
1127}
1128
1129- (void) moveForwardAndModifySelection: (id)sender
1130{
1131  NSUInteger to = [self _movementOrigin];
1132
1133  if (to == [_textStorage length])
1134    return;
1135  to++;
1136  [self _moveTo: to
1137	 select: YES];
1138}
1139
1140- (void) moveWordBackward: (id)sender
1141{
1142  NSRange range = [self selectedRange];
1143  NSUInteger newLocation;
1144  NSUInteger cIndex = range.location;
1145
1146  newLocation = [_textStorage nextWordFromIndex: cIndex
1147			      forward: NO];
1148  [self _moveTo: newLocation
1149	 select: NO];
1150}
1151
1152- (void) moveWordBackwardAndModifySelection: (id)sender
1153{
1154  NSUInteger newLocation;
1155
1156  newLocation = [_textStorage nextWordFromIndex: [self _movementOrigin]
1157			      forward: NO];
1158  [self _moveTo: newLocation
1159	 select: YES];
1160}
1161
1162- (void) moveWordForward: (id)sender
1163{
1164  NSUInteger newLocation;
1165  NSUInteger cIndex = NSMaxRange([self selectedRange]);
1166
1167  newLocation = [_textStorage nextWordFromIndex: cIndex
1168			      forward: YES];
1169  [self _moveTo: newLocation
1170	 select: NO];
1171}
1172
1173- (void) moveWordForwardAndModifySelection: (id)sender
1174{
1175  NSUInteger newLocation;
1176
1177  newLocation = [_textStorage nextWordFromIndex: [self _movementOrigin]
1178			      forward: YES];
1179  [self _moveTo: newLocation
1180	 select: YES];
1181}
1182
1183- (void) moveWordLeft: (id)sender
1184{
1185  NSParagraphStyle *parStyle;
1186  NSWritingDirection writingDirection;
1187
1188  parStyle = [[self typingAttributes]
1189	  	objectForKey: NSParagraphStyleAttributeName];
1190  writingDirection = [parStyle baseWritingDirection];
1191
1192  if (writingDirection == NSWritingDirectionRightToLeft)
1193    {
1194        [self moveWordForward: sender];
1195    }
1196  else
1197    {
1198        [self moveWordBackward: sender];
1199    }
1200}
1201
1202- (void) moveWordLeftAndModifySelection: (id)sender
1203{
1204  NSParagraphStyle *parStyle;
1205  NSWritingDirection writingDirection;
1206
1207  parStyle = [[self typingAttributes]
1208	  	objectForKey: NSParagraphStyleAttributeName];
1209  writingDirection = [parStyle baseWritingDirection];
1210
1211  if (writingDirection == NSWritingDirectionRightToLeft)
1212    {
1213        [self moveWordForwardAndModifySelection: sender];
1214    }
1215  else
1216    {
1217        [self moveWordBackwardAndModifySelection: sender];
1218    }
1219}
1220
1221- (void) moveWordRight: (id)sender
1222{
1223  NSParagraphStyle *parStyle;
1224  NSWritingDirection writingDirection;
1225
1226  parStyle = [[self typingAttributes]
1227	  	objectForKey: NSParagraphStyleAttributeName];
1228  writingDirection = [parStyle baseWritingDirection];
1229
1230  if (writingDirection == NSWritingDirectionRightToLeft)
1231    {
1232      [self moveWordBackward: sender];
1233    }
1234  else
1235    {
1236      [self moveWordForward: sender];
1237    }
1238}
1239
1240- (void) moveWordRightAndModifySelection: (id)sender
1241{
1242  NSParagraphStyle *parStyle;
1243  NSWritingDirection writingDirection;
1244
1245  parStyle = [[self typingAttributes]
1246	  	objectForKey: NSParagraphStyleAttributeName];
1247  writingDirection = [parStyle baseWritingDirection];
1248
1249  if (writingDirection == NSWritingDirectionRightToLeft)
1250    {
1251      [self moveWordBackwardAndModifySelection: sender];
1252    }
1253  else
1254    {
1255      [self moveWordForwardAndModifySelection: sender];
1256    }
1257}
1258
1259- (void) moveToBeginningOfDocument: (id)sender
1260{
1261  [self _moveTo: 0
1262	 select: NO];
1263}
1264
1265- (void) moveToBeginningOfDocumentAndModifySelection: (id)sender
1266{
1267  [self _moveTo: 0
1268	  select:YES];
1269}
1270
1271- (void) moveToEndOfDocument: (id)sender
1272{
1273  [self _moveTo: [_textStorage length]
1274	 select: NO];
1275}
1276
1277- (void) moveToEndOfDocumentAndModifySelection: (id)sender
1278{
1279  [self _moveTo: [_textStorage length]
1280	  select:YES];
1281}
1282
1283- (void) moveToBeginningOfParagraph: (id)sender
1284{
1285  NSRange aRange = [self selectedRange];
1286
1287  aRange = [[_textStorage string] lineRangeForRange:
1288				      NSMakeRange(aRange.location, 0)];
1289  [self _moveTo: aRange.location
1290	 select: NO];
1291}
1292
1293- (void) moveToBeginningOfParagraphAndModifySelection: (id)sender
1294{
1295  NSRange aRange;
1296
1297  aRange = [[_textStorage string] lineRangeForRange:
1298				      NSMakeRange([self _movementOrigin], 0)];
1299  [self _moveTo: aRange.location
1300	 select: YES];
1301}
1302
1303- (void) _moveToEndOfParagraph: (id)sender modify:(BOOL)flag
1304{
1305  NSRange aRange;
1306  NSUInteger newLocation;
1307  NSUInteger maxRange;
1308  NSUInteger cIndex;
1309
1310  if (flag)
1311    {
1312      cIndex = [self _movementOrigin];
1313    }
1314  else
1315    {
1316      cIndex = NSMaxRange([self selectedRange]);
1317    }
1318
1319  aRange = [[_textStorage string] lineRangeForRange:
1320				      NSMakeRange(cIndex, 0)];
1321  maxRange = NSMaxRange (aRange);
1322
1323  if (maxRange == 0)
1324    {
1325      /* Beginning of text is special only for technical reasons -
1326	 since maxRange is an unsigned, we can't safely subtract 1
1327	 from it if it is 0.  */
1328      newLocation = maxRange;
1329    }
1330  else if (maxRange == [_textStorage length])
1331    {
1332      /* End of text is special - we want the insertion point to
1333	 appear *after* the last character, which means as if before
1334	 the next (virtual) character after the end of text ... unless
1335	 the last character is a newline, and we are trying to go to
1336	 the end of the line which is displayed as the
1337	 one-before-the-last.  Please note (maxRange - 1) is a valid
1338	 char since the maxRange == 0 case has already been
1339	 eliminated.  */
1340      unichar u = [[_textStorage string] characterAtIndex: (maxRange - 1)];
1341      if (u == '\n'  ||  u == '\r')
1342	{
1343	  newLocation = maxRange - 1;
1344	}
1345      else
1346	{
1347	  newLocation = maxRange;
1348	}
1349    }
1350  else
1351    {
1352      /* Else, we want the insertion point to appear before the last
1353	 character in the paragraph range.  Normally the last
1354	 character in the paragraph range is a newline.  */
1355      newLocation = maxRange - 1;
1356    }
1357
1358  if (newLocation < aRange.location)
1359    {
1360      newLocation = aRange.location;
1361    }
1362
1363  [self _moveTo: newLocation
1364	 select: flag];
1365}
1366
1367- (void) moveToEndOfParagraph: (id)sender
1368{
1369  [self _moveToEndOfParagraph:sender modify:NO];
1370}
1371
1372- (void) moveToEndOfParagraphAndModifySelection: (id)sender
1373{
1374  [self _moveToEndOfParagraph:sender modify:YES];
1375}
1376
1377/* TODO: this is only the beginning and end of lines if lines are horizontal
1378and layout is left-to-right */
1379- (void) moveToBeginningOfLine: (id)sender
1380{
1381  NSRange range = [self selectedRange];
1382  NSUInteger cIndex = range.location;
1383
1384  [self _moveFrom: cIndex
1385	direction: GSInsertionPointMoveLeft
1386	distance: 1e8
1387	select: NO];
1388}
1389
1390- (void) moveToBeginningOfLineAndModifySelection: (id)sender
1391{
1392  [self _move: GSInsertionPointMoveLeft
1393	distance: 1e8
1394	select: YES];
1395}
1396
1397- (void) moveToEndOfLine: (id)sender
1398{
1399  NSUInteger cIndex = NSMaxRange([self selectedRange]);
1400
1401  [self _moveFrom: cIndex
1402	direction: GSInsertionPointMoveRight
1403	distance: 1e8
1404	select: NO];
1405}
1406
1407- (void) moveToEndOfLineAndModifySelection: (id)sender
1408{
1409  [self _move: GSInsertionPointMoveRight
1410	distance: 1e8
1411	select: YES];
1412}
1413
1414/**
1415 * Tries to move the selection/insertion point down one page of the
1416 * visible rect in the receiver while trying to maintain the
1417 * horizontal position of the last vertical movement.
1418 * If the receiver is a field editor, this method returns immediatly.
1419 */
1420- (void) _pageDown: (id)sender modify: (BOOL)flag
1421{
1422  CGFloat    scrollDelta;
1423  CGFloat    oldOriginY;
1424  CGFloat    newOriginY;
1425  NSUInteger cIndex;
1426
1427  if (flag)
1428    {
1429      cIndex = [self _movementOrigin];
1430    }
1431  else
1432    {
1433      cIndex = [self _characterIndexForSelectedRange: [self selectedRange]
1434	      		direction: GSInsertionPointMoveDown];
1435    }
1436
1437  /*
1438   * Scroll; also determine how far to move the insertion point.
1439   */
1440  oldOriginY = NSMinY([self visibleRect]);
1441  [[self enclosingScrollView] pageDown: sender];
1442  newOriginY = NSMinY([self visibleRect]);
1443  scrollDelta = newOriginY - oldOriginY;
1444
1445  if (scrollDelta == 0)
1446    {
1447      [self _moveTo:[_textStorage length] select:flag];
1448      return;
1449    }
1450
1451  [self _moveFrom: cIndex
1452	direction: GSInsertionPointMoveDown
1453	distance: scrollDelta
1454	select: flag];
1455}
1456
1457- (void) pageDown:(id)sender
1458{
1459  [self _pageDown:sender modify:NO];
1460}
1461
1462- (void) pageDownAndModifySelection:(id)sender
1463{
1464  [self _pageDown:sender modify:YES];
1465}
1466
1467/**
1468 * Tries to move the selection/insertion point up one page of the
1469 * visible rect in the receiver while trying to maintain the
1470 * horizontal position of the last vertical movement.
1471 * If the receiver is a field editor, this method returns immediatly.
1472 */
1473- (void) _pageUp: (id)sender modify:(BOOL)flag
1474{
1475  CGFloat    scrollDelta;
1476  CGFloat    oldOriginY;
1477  CGFloat    newOriginY;
1478  NSUInteger cIndex;
1479
1480  if (flag)
1481    {
1482      cIndex = [self _movementOrigin];
1483    }
1484  else
1485    {
1486      cIndex = [self _characterIndexForSelectedRange:[self selectedRange]
1487		        direction: GSInsertionPointMoveUp];
1488    }
1489  /*
1490   * Scroll; also determine how far to move the insertion point.
1491   */
1492  oldOriginY = NSMinY([self visibleRect]);
1493  [[self enclosingScrollView] pageUp: sender];
1494  newOriginY = NSMinY([self visibleRect]);
1495  scrollDelta = newOriginY - oldOriginY;
1496
1497  if (scrollDelta == 0)
1498    {
1499      [self _moveTo:0 select:flag];
1500      return;
1501    }
1502
1503  [self _moveFrom: cIndex
1504	direction: GSInsertionPointMoveUp
1505	distance: -scrollDelta
1506	select: flag];
1507}
1508
1509- (void) pageUp:(id)sender
1510{
1511  [self _pageUp:sender modify:NO];
1512}
1513
1514- (void) pageUpAndModifySelection:(id)sender
1515{
1516  [self _pageUp:sender modify:YES];
1517}
1518
1519- (void) scrollLineDown: (id)sender
1520{
1521  [[self enclosingScrollView] scrollLineDown: sender];
1522}
1523
1524- (void) scrollLineUp: (id)sender
1525{
1526  [[self enclosingScrollView] scrollLineUp: sender];
1527}
1528
1529- (void) scrollPageDown: (id)sender
1530{
1531  [[self enclosingScrollView] scrollPageDown: sender];
1532}
1533
1534- (void) scrollPageUp: (id)sender
1535{
1536  [[self enclosingScrollView] scrollPageUp: sender];
1537}
1538
1539- (void) scrollToBeginningOfDocument: (id)sender
1540{
1541  [[self enclosingScrollView] scrollToBeginningOfDocument: sender];
1542}
1543
1544- (void) scrollToEndOfDocument: (id)sender
1545{
1546  [[self enclosingScrollView] scrollToEndOfDocument: sender];
1547}
1548
1549- (void) centerSelectionInVisibleArea: (id)sender
1550{
1551  NSRange range;
1552  NSPoint new;
1553  NSRect rect, vRect;
1554
1555  vRect = [self visibleRect];
1556  range = [self selectedRange];
1557  if (range.length == 0)
1558    {
1559      rect =
1560          [_layoutManager insertionPointRectForCharacterIndex: range.location
1561                          inTextContainer: _textContainer];
1562    }
1563  else
1564    {
1565      range = [_layoutManager glyphRangeForCharacterRange: range
1566                              actualCharacterRange: NULL];
1567      rect = [_layoutManager boundingRectForGlyphRange: range
1568                             inTextContainer: _textContainer];
1569    }
1570
1571  if (NSWidth(_bounds) <= NSWidth(vRect))
1572    new.x = 0;
1573  else if (NSWidth(rect) > NSWidth(vRect))
1574    new.x = NSMinX(rect);
1575  else
1576    new.x = NSMinX(rect) - (NSWidth(vRect) - NSWidth(rect)) / 2;
1577
1578  if (NSHeight(_bounds) <= NSHeight(vRect))
1579    new.y = 0;
1580  else if (NSHeight(rect) > NSHeight(vRect))
1581    new.y = NSMinY(rect);
1582  else
1583    new.y = NSMinY(rect) - (NSHeight(vRect) - NSHeight(rect)) / 2;
1584
1585  [self scrollPoint: new];
1586}
1587
1588
1589/* -selectAll: inherited from NSText  */
1590
1591- (void) selectLine: (id)sender
1592{
1593  NSUInteger start, end, cindex;
1594
1595  cindex = [self _movementOrigin];
1596  start = [_layoutManager characterIndexMoving: GSInsertionPointMoveLeft
1597	     fromCharacterIndex: cindex
1598	     originalCharacterIndex: cindex
1599	     distance: 1e8];
1600  end = [_layoutManager characterIndexMoving: GSInsertionPointMoveRight
1601	   fromCharacterIndex: cindex
1602	   originalCharacterIndex: cindex
1603	   distance: 1e8];
1604  [self setSelectedRange: NSMakeRange(start, end - start)];
1605}
1606
1607
1608/* The following method is bound to 'Control-t', and works exactly like
1609 * pressing 'Control-t' inside Emacs, i.e., in general it swaps the
1610 * character immediately before and after the insertion point and moves
1611 * the insertion point forward by one character.  If, however, the
1612 * insertion point is at the end of a line, it swaps the two characters
1613 * before the insertion point and does not move the insertion point.
1614 * Note that Mac OS X does not implement the special case at the end
1615 * of a line, but I consider Emacs' behavior more useful.
1616 */
1617- (void) transpose: (id)sender
1618{
1619  NSRange range = [self selectedRange];
1620  NSString *string;
1621  NSString *replacementString;
1622  unichar chars[2];
1623
1624  /* Do nothing if the selection is not empty or if we are at the
1625   * beginning of text.  */
1626  if (range.length > 0 || range.location < 1)
1627    {
1628      return;
1629    }
1630
1631  range = NSMakeRange(range.location - 1, 2);
1632
1633  /* Eventually adjust the range if we are at the end of a line. */
1634  string = [_textStorage string];
1635  if (range.location + 1 == [string length]
1636      || [string characterAtIndex: range.location + 1] == '\n')
1637    {
1638      if (range.location == 0)
1639	return;
1640      range.location -= 1;
1641    }
1642
1643  /* Get the two chars and swap them.  */
1644  chars[1] = [string characterAtIndex: range.location];
1645  chars[0] = [string characterAtIndex: (range.location + 1)];
1646
1647  /* Replace the original chars with the swapped ones.  */
1648  replacementString = [NSString stringWithCharacters: chars  length: 2];
1649
1650  if ([self shouldChangeTextInRange: range
1651	replacementString: replacementString])
1652    {
1653      [self replaceCharactersInRange: range
1654        withString: replacementString];
1655      [self setSelectedRange: NSMakeRange(range.location + 2, 0)];
1656      [self didChangeText];
1657    }
1658}
1659
1660- (void) delete: (id)sender
1661{
1662  [self deleteForward: sender];
1663}
1664
1665
1666/* Helper for -align*: */
1667- (void) _alignUser: (NSTextAlignment)alignment
1668{
1669  NSRange r = [self rangeForUserParagraphAttributeChange];
1670
1671  if (r.location == NSNotFound)
1672    return;
1673  if (![self shouldChangeTextInRange: r
1674	 replacementString: nil])
1675    return;
1676
1677  [self setAlignment: alignment
1678    range: r];
1679  [self didChangeText];
1680}
1681
1682- (void) alignCenter: (id)sender
1683{
1684  [self _alignUser: NSCenterTextAlignment];
1685}
1686
1687- (void) alignLeft: (id)sender
1688{
1689  [self _alignUser: NSLeftTextAlignment];
1690}
1691
1692- (void) alignRight: (id)sender
1693{
1694  [self _alignUser: NSRightTextAlignment];
1695}
1696
1697- (void) alignJustified: (id)sender
1698{
1699  [self _alignUser: NSJustifiedTextAlignment];
1700}
1701
1702- (void) toggleContinuousSpellChecking: (id)sender
1703{
1704  [self setContinuousSpellCheckingEnabled:
1705	    ![self isContinuousSpellCheckingEnabled]];
1706}
1707
1708- (void) toggleRuler: (id)sender
1709{
1710  [self setRulerVisible: !_tf.is_ruler_visible];
1711}
1712
1713- (void) outline: (id)sender
1714{
1715  // FIXME
1716}
1717
1718- (void) setBaseWritingDirection: (NSWritingDirection)direction
1719                           range: (NSRange)range
1720{
1721  if (!_tf.is_rich_text)
1722    return;
1723
1724  [_textStorage setBaseWritingDirection: direction range: range];
1725}
1726
1727- (void) toggleBaseWritingDirection: (id)sender
1728{
1729  // FIXME
1730}
1731
1732@end
1733