PageRenderTime 58ms CodeModel.GetById 26ms app.highlight 29ms RepoModel.GetById 0ms app.codeStats 0ms

/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*/