home *** CD-ROM | disk | FTP | other *** search
/ OpenStep (Enterprise) / OpenStepENTCD.toast / OEDEV / DEV.Z / TextFinder.m < prev    next >
Encoding:
Text File  |  1996-05-03  |  9.0 KB  |  262 lines

  1. /*
  2.  *  TextFinder.m
  3.  *  Author: Ali Ozer
  4.  *  Created: Feb 21, 1995 for TextEdit
  5.  *  Modified: Jan 96 for Yap
  6.  *
  7.  *  Find and replace functionality with a minimal panel...
  8.  * 
  9.  *  In addition to including this class and FindPanel.nib in your app,
  10.  *  you probably need to hook up the following action methods in your
  11.  *  document object (or whatever object is first responder) to call the 
  12.  *  appropriate methods in [TextFinder sharedInstance]:
  13.  * 
  14.  *   orderFrontFindPanel:
  15.  *   findNext:
  16.  *   findPrevious:
  17.  *   enterSelection: (calls setFindString:)
  18.  *
  19.  *  You may freely copy, distribute and reuse the code in this example.
  20.  *  NeXT disclaims any warranty of any kind, expressed or implied,
  21.  *  as to its fitness for any particular use.
  22.  */
  23.  
  24. #import <AppKit/AppKit.h>
  25. #import "TextFinder.h"
  26.  
  27. @implementation TextFinder
  28.  
  29. - (id)init {
  30.     if (!(self = [super init])) return nil;
  31.     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidActivate:) name:NSApplicationDidBecomeActiveNotification object:[NSApplication sharedApplication]];
  32.     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(addWillDeactivate:) name:NSApplicationWillResignActiveNotification object:[NSApplication sharedApplication]];
  33.     [self setFindString:@""];
  34.     [self loadFindStringFromPasteboard];
  35.     return self;
  36. }
  37.  
  38. - (void)appDidActivate:(NSNotification *)notification {
  39.     [self loadFindStringFromPasteboard];
  40. }
  41.  
  42. - (void)addWillDeactivate:(NSNotification *)notification {
  43.     [self loadFindStringToPasteboard];
  44. }
  45.  
  46. - (void)loadFindStringFromPasteboard {
  47.     NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
  48.     if ([[pasteboard types] containsObject:NSStringPboardType]) {
  49.         NSString *string = [pasteboard stringForType:NSStringPboardType];
  50.         if (string && [string length]) {
  51.             [self setFindString:string];
  52.             findStringChangedSinceLastPasteboardUpdate = NO;
  53.         }
  54.     }
  55. }
  56.  
  57. - (void)loadFindStringToPasteboard {
  58.     NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
  59.     if (findStringChangedSinceLastPasteboardUpdate) {
  60.         [pasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
  61.         [pasteboard setString:[self findString] forType:NSStringPboardType];
  62.     findStringChangedSinceLastPasteboardUpdate = NO;
  63.     }
  64. }
  65.  
  66. static id sharedFindObject = nil;
  67.  
  68. + (id)sharedInstance {
  69.     if (!sharedFindObject) {
  70.         sharedFindObject = [[self allocWithZone:[[NSApplication sharedApplication] zone]] init];
  71.     }
  72.     return sharedFindObject;
  73. }
  74.  
  75. - (void)loadUI {
  76.     if (!findTextField) {
  77.         if (![NSBundle loadNibNamed:@"FindPanel" owner:self])  {
  78.             NSLog(@"Failed to load FindPanel.nib");
  79.             NSBeep();
  80.         }
  81.     if (self == sharedFindObject) [[findTextField window] setFrameAutosaveName:@"Find"];
  82.     }
  83.     [findTextField setStringValue:[self findString]];
  84. }
  85.  
  86. - (void)dealloc {
  87.     if (self != sharedFindObject) {
  88.         [findString release];
  89.         [super dealloc];
  90.     }
  91. }
  92.  
  93. - (NSString *)findString {
  94.     return findString;
  95. }
  96.  
  97. - (void)setFindString:(NSString *)string {
  98.     if ([string isEqualToString:findString]) return;
  99.     [findString autorelease];
  100.     findString = [string copyWithZone:[self zone]];
  101.     if (findTextField) {
  102.         [findTextField setStringValue:string];
  103.         [findTextField selectText:nil];
  104.     }
  105.     findStringChangedSinceLastPasteboardUpdate = YES;
  106. }
  107.  
  108. - (NSTextView *)textObjectToSearchIn {
  109.     id obj = [[NSApp mainWindow] firstResponder];
  110.     return (obj && [obj isKindOfClass:[NSTextView class]]) ? obj : nil;
  111. }
  112.  
  113. - (NSPanel *)findPanel {
  114.     if (!findTextField) [self loadUI];
  115.     return (NSPanel *)[findTextField window];
  116. }
  117.  
  118. /* The primitive for finding; this ends up setting the status field (and beeping if necessary)...
  119. */
  120. - (BOOL)find:(BOOL)direction {
  121.     NSTextView *text = [self textObjectToSearchIn];
  122.     lastFindWasSuccessful = NO;
  123.     if (text) {
  124.         NSString *textContents = [text string];
  125.         unsigned textLength;
  126.         if (textContents && (textLength = [textContents length])) {
  127.             NSRange range;
  128.             unsigned options = 0;
  129.         if (direction == Backward) options |= NSBackwardsSearch;
  130.             if ([ignoreCaseButton state]) options |= NSCaseInsensitiveSearch;
  131.             range = [textContents findString:[self findString] selectedRange:[text selectedRange] options:options wrap:YES];
  132.             if (range.length) {
  133.                 [text setSelectedRange:range];
  134.                 [text scrollRangeToVisible:range];
  135.                 lastFindWasSuccessful = YES;
  136.             }
  137.         }
  138.     }
  139.     if (!lastFindWasSuccessful) {
  140.         NSBeep();
  141.         [statusField setStringValue:NSLocalizedStringFromTable(@"Not found", @"FindPanel", @"Status displayed in find panel when the find string is not found.")];
  142.     } else {
  143.         [statusField setStringValue:@""];
  144.     }
  145.     return lastFindWasSuccessful;
  146. }
  147.  
  148. - (void)orderFrontFindPanel:(id)sender {
  149.     NSPanel *panel = [self findPanel];
  150.     [findTextField selectText:nil];
  151.     [panel makeKeyAndOrderFront:nil];
  152. }
  153.  
  154. /**** Action methods for gadgets in the find panel; these should all end up setting or clearing the status field ****/
  155.  
  156. - (void)findNextAndOrderFindPanelOut:(id)sender {
  157.     [findNextButton performClick:nil];
  158.     if (lastFindWasSuccessful) {
  159.         [[self findPanel] orderOut:sender];
  160.     } else {
  161.     [findTextField selectText:nil];
  162.     }
  163. }
  164.  
  165. - (void)findNext:(id)sender {
  166.     if (findTextField) [self setFindString:[findTextField stringValue]];    /* findTextField should be set */
  167.     (void)[self find:Forward];
  168. }
  169.  
  170. - (void)findPrevious:(id)sender {
  171.     if (findTextField) [self setFindString:[findTextField stringValue]];    /* findTextField should be set */
  172.     (void)[self find:Backward];
  173. }
  174.  
  175. - (void)replace:(id)sender {
  176.     NSTextView *text = [self textObjectToSearchIn];
  177.     if (!text) {
  178.         NSBeep();
  179.     } else {
  180.         [[text textStorage] replaceCharactersInRange:[text selectedRange] withString:[replaceTextField stringValue]];
  181.     [text didChangeText];
  182.     }
  183.     [statusField setStringValue:@""];
  184.  
  185. }
  186.  
  187. - (void)replaceAndFind:(id)sender {
  188.     [self replace:sender];
  189.     [self findNext:sender];
  190. }
  191.  
  192. #define ReplaceAllScopeEntireFile 42
  193. #define ReplaceAllScopeSelection 43
  194.  
  195. - (void)replaceAll:(id)sender {
  196.     NSTextView *text = [self textObjectToSearchIn];
  197.     if (!text) {
  198.         NSBeep();
  199.     } else {
  200.         NSTextStorage *textStorage = [text textStorage];
  201.         NSString *textContents = [text string];
  202.         BOOL entireFile = replaceAllScopeMatrix ? ([replaceAllScopeMatrix selectedTag] == ReplaceAllScopeEntireFile) : YES;
  203.         NSRange replaceRange = entireFile ? NSMakeRange(0, [textStorage length]) : [text selectedRange];
  204.         unsigned options = NSBackwardsSearch | ([ignoreCaseButton state] ? NSCaseInsensitiveSearch : 0);
  205.         unsigned replaced = 0;
  206.  
  207.         if (findTextField) [self setFindString:[findTextField stringValue]];
  208.  
  209.         [textStorage beginEditing];
  210.         while (1) {
  211.             NSRange foundRange = [textContents rangeOfString:[self findString] options:options range:replaceRange];
  212.             if (foundRange.length == 0) break;
  213.             replaced++;
  214.             [textStorage replaceCharactersInRange:foundRange withString:[replaceTextField stringValue]];
  215.             replaceRange.length = foundRange.location - replaceRange.location;
  216.         }
  217.         [textStorage endEditing];
  218.     [text didChangeText];
  219.         if (replaced == 0) {
  220.             NSBeep();
  221.             [statusField setStringValue:NSLocalizedStringFromTable(@"Not found", @"FindPanel", @"Status displayed in find panel when the find string is not found.")];
  222.         } else {
  223.             [statusField setStringValue:[NSString localizedStringWithFormat:NSLocalizedStringFromTable(@"%d replaced", @"FindPanel", @"Status displayed in find panel when indicated number of matches are replaced."), replaced]];
  224.         }
  225.     }
  226. }
  227.  
  228. @end
  229.  
  230.  
  231. @implementation NSString (StringTextFinding)
  232.  
  233. - (NSRange)findString:(NSString *)string selectedRange:(NSRange)selectedRange options:(unsigned)options wrap:(BOOL)wrap {
  234.     BOOL forwards = (options & NSBackwardsSearch) == 0;
  235.     unsigned length = [self length];
  236.     NSRange searchRange, range;
  237.  
  238.     if (forwards) {
  239.     searchRange.location = NSMaxRange(selectedRange);
  240.     searchRange.length = length - searchRange.location;
  241.     range = [self rangeOfString:string options:options range:searchRange];
  242.         if ((range.length == 0) && wrap) {    /* If not found look at the first part of the string */
  243.         searchRange.location = 0;
  244.             searchRange.length = selectedRange.location;
  245.             range = [self rangeOfString:string options:options range:searchRange];
  246.         }
  247.     } else {
  248.     searchRange.location = 0;
  249.     searchRange.length = selectedRange.location;
  250.         range = [self rangeOfString:string options:options range:searchRange];
  251.         if ((range.length == 0) && wrap) {
  252.             searchRange.location = NSMaxRange(selectedRange);
  253.             searchRange.length = length - searchRange.location;
  254.             range = [self rangeOfString:string options:options range:searchRange];
  255.         }
  256.     }
  257.     return range;
  258. }        
  259.  
  260. @end
  261.  
  262.