/System/Applications/TextEdit/TextFinder.m
Objective C | 339 lines | 242 code | 61 blank | 36 comment | 52 complexity | 9a5cdf348845c5850f940f45619a22ba MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
1/* 2 TextFinder.m 3 Copyright (c) 1995-1996, NeXT Software, Inc. 4 All rights reserved. 5 Author: Ali Ozer 6 7 You may freely copy, distribute and reuse the code in this example. 8 NeXT disclaims any warranty of any kind, expressed or implied, 9 as to its fitness for any particular use. 10 11 Find and replace functionality with a minimal panel... 12 13 In addition to including this class and FindPanel.nib in your app, you 14 probably need to hook up the following action methods in your document 15 object (or whatever object is first responder) to call the appropriate 16 methods in [TextFinder sharedInstance]: 17 18 orderFrontFindPanel: 19 findNext: 20 findPrevious: 21 enterSelection: (calls setFindString:) 22*/ 23 24#import <AppKit/AppKit.h> 25#import "TextFinder.h" 26 27@implementation TextFinder 28 29- (id) init 30{ 31 if (!(self = [super init])) 32 return nil; 33 34 [[NSNotificationCenter defaultCenter] addObserver: self 35 selector: @selector (appDidActivate:) 36 name: NSApplicationDidBecomeActiveNotification 37 object: [NSApplication sharedApplication]]; 38 [[NSNotificationCenter defaultCenter] addObserver: self 39 selector: @selector (addWillDeactivate:) 40 name: NSApplicationWillResignActiveNotification 41 object: [NSApplication sharedApplication]]; 42 43 [self setFindString: @""]; 44 [self loadFindStringFromPasteboard]; 45 46 return self; 47} 48 49- (void) appDidActivate: (NSNotification *)notification 50{ 51 [self loadFindStringFromPasteboard]; 52} 53 54- (void)addWillDeactivate: (NSNotification *)notification 55{ 56 [self loadFindStringToPasteboard]; 57} 58 59- (void)loadFindStringFromPasteboard 60{ 61 NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSFindPboard]; 62 63 if ([[pasteboard types] containsObject:NSStringPboardType]) { 64 NSString *string = [pasteboard stringForType:NSStringPboardType]; 65 if (string && [string length]) { 66 [self setFindString: string]; 67 findStringChangedSinceLastPasteboardUpdate = NO; 68 } 69 } 70} 71 72- (void) loadFindStringToPasteboard 73{ 74 NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName: NSFindPboard]; 75 76 if (findStringChangedSinceLastPasteboardUpdate) { 77 [pasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; 78 [pasteboard setString:[self findString] forType:NSStringPboardType]; 79 findStringChangedSinceLastPasteboardUpdate = NO; 80 } 81} 82 83static id sharedFindObject = nil; 84 85+ (id)sharedInstance 86{ 87 if (!sharedFindObject) { 88 sharedFindObject = [[self alloc] init]; 89 } 90 return sharedFindObject; 91} 92 93- (void) loadUI 94{ 95 if (!findTextField) { 96 if (![NSBundle loadNibNamed: @"FindPanel" owner: self]) { 97 NSLog (@"Failed to load FindPanel.nib"); 98 NSBeep (); 99 } 100 if (self == sharedFindObject) 101 [[findTextField window] setFrameAutosaveName: @"Find"]; 102 } 103 [findTextField setStringValue: [self findString]]; 104} 105 106- (void) dealloc 107{ 108 if (self != sharedFindObject) { 109 [findString release]; 110 [super dealloc]; 111 } 112} 113 114- (NSString *) findString 115{ 116 return findString; 117} 118 119- (void) setFindString: (NSString *)string 120{ 121 if ([string isEqualToString: findString]) 122 return; 123 [findString autorelease]; 124 findString = [string copy]; 125 126 if (findTextField) { 127 [findTextField setStringValue: string]; 128 [findTextField selectText: nil]; 129 } 130 131 findStringChangedSinceLastPasteboardUpdate = YES; 132} 133 134- (NSTextView *) textObjectToSearchIn 135{ 136 id obj = [[NSApp mainWindow] firstResponder]; 137 138 return (obj && [obj isKindOfClass: [NSTextView class]]) ? obj : nil; 139} 140 141- (NSPanel *) findPanel 142{ 143 if (!findTextField) 144 [self loadUI]; 145 146 return (NSPanel *) [findTextField window]; 147} 148 149/* 150 The primitive for finding; this ends up setting the status field (and 151 beeping if necessary)... 152*/ 153- (BOOL) find: (BOOL)direction 154{ 155 NSTextView *text = [self textObjectToSearchIn]; 156 157 lastFindWasSuccessful = NO; 158 159 if (text) { 160 NSString *textContents = [text string]; 161 unsigned int textLength; 162 163 if (textContents && (textLength = [textContents length])) { 164 NSRange range; 165 unsigned int options = 0; 166 167 if (direction == Backward) 168 options |= NSBackwardsSearch; 169 if ([ignoreCaseButton state]) 170 options |= NSCaseInsensitiveSearch; 171 172 range = [textContents findString: [self findString] selectedRange: [text selectedRange] options: options wrap: YES]; 173 if (range.length) { 174 [text setSelectedRange: range]; 175 [text scrollRangeToVisible: range]; 176 lastFindWasSuccessful = YES; 177 } 178 } 179 } 180 181 if (!lastFindWasSuccessful) { 182 NSBeep (); 183 [statusField setStringValue: NSLocalizedStringFromTable (@"Not found", @"FindPanel", @"Status displayed in find panel when the find string is not found.")]; 184 } else { 185 [statusField setStringValue: @""]; 186 } 187 return lastFindWasSuccessful; 188} 189 190- (void) orderFrontFindPanel: (id)sender 191{ 192 NSPanel *panel = [self findPanel]; 193 194 [findTextField selectText: nil]; 195 [panel makeKeyAndOrderFront: nil]; 196} 197 198/**** Action methods for gadgets in the find panel; these should all end up setting or clearing the status field ****/ 199 200- (void) findNextAndOrderFindPanelOut: (id)sender 201{ 202 [findNextButton performClick: nil]; 203 204 if (lastFindWasSuccessful) { 205 [[self findPanel] orderOut: sender]; 206 } else { 207 [findTextField selectText: nil]; 208 } 209} 210 211- (void) findNext: (id)sender 212{ 213 if (findTextField) 214 [self setFindString: [findTextField stringValue]]; /* findTextField should be set */ 215 216 [self find: Forward]; 217} 218 219- (void) findPrevious: (id)sender 220{ 221 if (findTextField) 222 [self setFindString:[findTextField stringValue]]; /* findTextField should be set */ 223 224 [self find: Backward]; 225} 226 227- (void) replace: (id)sender 228{ 229 NSTextView *text = [self textObjectToSearchIn]; 230 231 if (!text) { 232 NSBeep (); 233 } else { 234 [[text textStorage] replaceCharactersInRange: [text selectedRange] withString: [replaceTextField stringValue]]; 235 [text didChangeText]; 236 } 237 [statusField setStringValue: @""]; 238} 239 240- (void) replaceAndFind: (id)sender 241{ 242 [self replace: sender]; 243 [self findNext: sender]; 244} 245 246#define ReplaceAllScopeEntireFile 42 247#define ReplaceAllScopeSelection 43 248 249- (void) replaceAll: (id)sender 250{ 251 NSTextView *text = [self textObjectToSearchIn]; 252 253 if (!text) { 254 NSBeep(); 255 } else { 256 NSTextStorage *textStorage = [text textStorage]; 257 NSString *textContents = [text string]; 258 NSString *replaceString = [replaceTextField stringValue]; 259 BOOL entireFile = replaceAllScopeMatrix ? ([replaceAllScopeMatrix selectedTag] == ReplaceAllScopeEntireFile) : YES; 260 NSRange replaceRange = entireFile ? NSMakeRange (0, [textStorage length]) : [text selectedRange]; 261 unsigned int options = NSBackwardsSearch | ([ignoreCaseButton state] ? NSCaseInsensitiveSearch : 0); 262 unsigned int replaced = 0; 263 264 if (findTextField) 265 [self setFindString:[findTextField stringValue]]; 266 267 while (1) { 268 NSRange foundRange = [textContents rangeOfString: [self findString] options: options range: replaceRange]; 269 270 if (foundRange.length == 0) 271 break; 272 if ([text shouldChangeTextInRange: foundRange replacementString: replaceString]) { 273 if (replaced == 0) 274 [textStorage beginEditing]; 275 276 replaced++; 277 278 [textStorage replaceCharactersInRange: foundRange withString: replaceString]; 279 replaceRange.length = foundRange.location - replaceRange.location; 280 } 281 } 282 283 if (replaced > 0) { /* There was at least one replacement */ 284 [textStorage endEditing]; /* We need this to bracket the beginEditing */ 285 [text didChangeText]; /* We need one of these to terminate the shouldChange... methods we sent */ 286 [statusField setStringValue: [NSString localizedStringWithFormat: NSLocalizedStringFromTable (@"%d replaced", @"FindPanel", @"Status displayed in find panel when indicated number of matches are replaced."), replaced]]; 287 } else { /* No replacements were done... */ 288 NSBeep(); 289 [statusField setStringValue:NSLocalizedStringFromTable(@"Not found", @"FindPanel", @"Status displayed in find panel when the find string is not found.")]; 290 } 291 } 292} 293 294@end 295 296 297@implementation NSString (NSStringTextFinding) 298 299- (NSRange) findString: (NSString *)string selectedRange: (NSRange)selectedRange options: (unsigned)options wrap: (BOOL)wrap 300{ 301 BOOL forwards = (options & NSBackwardsSearch) == 0; 302 unsigned int length = [self length]; 303 NSRange searchRange, range; 304 305 if (forwards) { 306 searchRange.location = NSMaxRange(selectedRange); 307 searchRange.length = length - searchRange.location; 308 range = [self rangeOfString:string options:options range:searchRange]; 309 310 if ((range.length == 0) && wrap) { /* If not found look at the first part of the string */ 311 searchRange.location = 0; 312 searchRange.length = selectedRange.location; 313 range = [self rangeOfString:string options:options range:searchRange]; 314 } 315 } else { 316 searchRange.location = 0; 317 searchRange.length = selectedRange.location; 318 range = [self rangeOfString:string options:options range:searchRange]; 319 320 if ((range.length == 0) && wrap) { 321 searchRange.location = NSMaxRange(selectedRange); 322 searchRange.length = length - searchRange.location; 323 range = [self rangeOfString:string options:options range:searchRange]; 324 } 325 } 326 return range; 327} 328 329@end 330 331/* 332 333 2/21/95 aozer Created for Edit II. 334 2/24/95 aozer Find pasteboard support 335 8/16/95 aozer Replace functionality 336 10/4/95 aozer Status field update 33711/12/96 aozer Correctly send shouldChange... to the textview while doing a replace 338 339*/