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