Implement a standard find panel for NSTextView.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@31068 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Wolfgang Lux 2010-08-02 18:57:00 +00:00
parent 72a86ed519
commit 86ff2f4099
9 changed files with 686 additions and 5 deletions

View file

@ -1,3 +1,19 @@
2010-08-02 Wolfgang Lux <wolfgang.lux@gmail.com>
* Source/NSTextView.m (-validateUserInterfaceItem:,
-performFindPanelAction:): Implement find panel support.
* Source/GNUmakefile:
* Source/GSTextFinder.h:
* Source/GSTextFinder.m:
* Panels/GNUmakefile:
* Panels/English.lproj/GSFindPanel.gorm:
New text finder and associated find panel.
* Source/NSTextView.m(currentVersion, -initWithTextView:,
-encodeWithCoder:, -initWithCoder:): Archive the uses find panel
attribute. This required updating the NSTextView version to 3.
2010-08-02 Fred Kiefer <FredKiefer@gmx.de>
* Source/NSButton.m,

View file

@ -0,0 +1,31 @@
{
"## Comment" = "Do NOT change this file, Gorm maintains it";
FirstResponder = {
Actions = (
"findNext:",
"findPrevious:",
"replace:",
"replaceAll:",
"replaceAndFind:"
);
Super = NSObject;
};
GSTextFinder = {
Actions = (
"replaceAll:",
"findNext:",
"findPrevious:",
"replace:",
"replaceAndFind:"
);
Outlets = (
findText,
ignoreCaseButton,
messageText,
panel,
replaceScopeMatrix,
replaceText
);
Super = NSObject;
};
}

Binary file not shown.

Binary file not shown.

View file

@ -34,7 +34,8 @@ LOCALIZED_RESOURCE_COMPONENTS = \
GSPageLayout.gorm \
GSPrintPanel.gorm \
GSToolbarCustomizationPalette.gorm \
GSSpellPanel.gorm
GSSpellPanel.gorm \
GSFindPanel.gorm
-include GNUmakefile.preamble

View file

@ -217,6 +217,7 @@ GSKeyBindingAction.m \
GSKeyBindingTable.m \
NSTextView.m \
NSTextView_actions.m \
GSTextFinder.m \
GSLayoutManager.m \
GSTypesetter.m \
GSHorizontalTypesetter.m \

82
Source/GSTextFinder.h Normal file
View file

@ -0,0 +1,82 @@
/* -*-objc-*-
GSTextFinder.h
The private text finder class for NSTextView
Copyright (C) 2010 Free Software Foundation, Inc.
Author: Wolfgang Lux <wolfgang.lux@gmail.com>
Date: 2010
This file is part of the GNUstep GUI Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef _GS_TEXT_FINDER_H
#define _GS_TEXT_FINDER_H
#import <Foundation/NSObject.h>
@class NSString;
@class NSButton;
@class NSMatrix;
@class NSPanel;
@class NSTextField;
@interface GSTextFinder : NSObject
{
// local attributes
NSString *findString;
NSString *replaceString;
// GUI
NSPanel *panel;
NSTextField *findText;
NSTextField *replaceText;
NSTextField *messageText;
NSMatrix *replaceScopeMatrix;
NSButton *ignoreCaseButton;
}
// return shared panel instance
+ (GSTextFinder *) sharedTextFinder;
// UI actions
- (void) findNext: (id)sender;
- (void) findPrevious: (id)sender;
- (void) replaceAndFind: (id)sender;
- (void) replace: (id)sender;
- (void) replaceAll: (id)sender;
- (void) performFindPanelAction: (id)sender;
- (void) performFindPanelAction: (id)sender
withTextView: (NSTextView *)aTextView;
- (BOOL) validateFindPanelAction: (id)sender
withTextView: (NSTextView *)aTextView;
// text finder methods
- (void) showFindPanel;
- (void) takeFindStringFromTextView: (NSText *)aTextView;
- (BOOL) findStringInTextView: (NSText *)aTextView forward: (BOOL)forward;
- (void) replaceStringInTextView: (NSTextView *)aTextView;
- (void) replaceAllInTextView: (NSTextView *)aTextView
onlyInSelection: (BOOL)flag;
- (NSTextView *) targetView: (NSTextView *)aTextView;
@end
#endif /* _GS_TEXT_FINDER_H */

527
Source/GSTextFinder.m Normal file
View file

@ -0,0 +1,527 @@
/** <title>GSTextFinder</title>
Copyright (C) 2010 Free Software Foundation, Inc.
Author: Wolfgang Lux <wolfgang.lux@gmail.com>
Date: July 2010
This file is part of the GNUstep GUI Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#import "config.h"
#import <Foundation/NSString.h>
#import <Foundation/NSNotification.h>
#import "AppKit/NSApplication.h"
#import "AppKit/NSButton.h"
#import "AppKit/NSEvent.h"
#import "AppKit/NSGraphics.h"
#import "AppKit/NSMatrix.h"
#import "AppKit/NSNib.h"
#import "AppKit/NSNibLoading.h"
#import "AppKit/NSPanel.h"
#import "AppKit/NSPasteboard.h"
#import "AppKit/NSTextField.h"
#import "AppKit/NSTextView.h"
#import "AppKit/NSWindow.h"
#import "GSGuiPrivate.h"
#import "GSTextFinder.h"
@interface GSTextFinder(PrivateMethods)
- (BOOL) _loadPanel;
- (void) _applicationDidBecomeActive: (NSNotification *)notification;
- (void) _updateFindStringFromPanel: (unsigned *)options
putToPasteboard: (BOOL)flag;
- (void) _updateReplaceStringFromPanel;
- (void) _getFindStringFromPasteboard;
- (void) _putFindStringToPasteboard;
@end
@implementation GSTextFinder
static GSTextFinder *sharedTextFinder;
+ (GSTextFinder *) sharedTextFinder
{
if (sharedTextFinder == nil)
{
sharedTextFinder = [[self alloc] init];
}
return sharedTextFinder;
}
- (id) init
{
if ((self = [super init]) != nil)
{
// make sure our search and replace strings are never nil
findString = @"";
replaceString = @"";
// update find string from pasteboard whenever the application is
// activated
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(_applicationDidBecomeActive:)
name: NSApplicationDidBecomeActiveNotification
object: NSApp];
[self _applicationDidBecomeActive: nil];
}
return self;
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter]
removeObserver: self
name: NSApplicationDidBecomeActiveNotification
object: NSApp];
DESTROY(findString);
DESTROY(replaceString);
[super dealloc];
}
// UI actions
- (void) findNext: (id)sender
{
if ([self findStringInTextView: [self targetView: nil] forward: YES])
{
// Special case here: If the user edits the find string and then presses
// the Return key while the find text field (rather the panel's field
// editor) is first responder, close the panel when a match was found.
// This behavior is compatible with OpenStep and Mac OS X. However, in
// contrast to Mac OS X, this cannot be implemented by associating a
// dedicated action with the find text field, since the event is already
// processed by the panel's default button before the field editor has a
// chance of looking at it.
// NB I assume here that the only keyboard event that can trigger the
// Next button's action while the field editor is active is a key down
// event from the Return key.
if ([[panel currentEvent] type] == NSKeyDown &&
[findText currentEditor] != nil)
{
[panel close];
}
}
}
- (void) findPrevious: (id)sender
{
[self findStringInTextView: [self targetView: nil] forward: NO];
}
- (void) replaceAndFind: (id)sender
{
NSTextView *targetView = [self targetView: nil];
[self replaceStringInTextView: targetView];
[self findStringInTextView: targetView forward: YES];
}
- (void) replace: (id)sender
{
[self replaceStringInTextView: [self targetView: nil]];
}
- (void) replaceAll: (id)sender
{
// NB In contrast to -performFindPanelAction: this UI action takes the current
// selection in the Replace All Scope matrix into account
[self replaceAllInTextView: [self targetView: nil]
onlyInSelection: [replaceScopeMatrix selectedTag] != 0];
}
- (void) performFindPanelAction: (id)sender
{
[self performFindPanelAction: sender withTextView: nil];
}
- (void) performFindPanelAction: (id)sender
withTextView: (NSTextView *)aTextView
{
aTextView = [self targetView: aTextView];
switch ([sender tag])
{
case NSFindPanelActionShowFindPanel:
[self showFindPanel];
break;
case NSFindPanelActionNext:
[self findStringInTextView: aTextView forward: YES];
break;
case NSFindPanelActionPrevious:
[self findStringInTextView: aTextView forward: NO];
break;
case NSFindPanelActionReplaceAll:
[self replaceAllInTextView: aTextView onlyInSelection: NO];
break;
case NSFindPanelActionReplace:
[self replaceStringInTextView: aTextView];
break;
case NSFindPanelActionReplaceAndFind:
[self replaceStringInTextView: aTextView];
[self findStringInTextView: aTextView forward: YES];
break;
case NSFindPanelActionSetFindString:
[self takeFindStringFromTextView: aTextView];
break;
case NSFindPanelActionReplaceAllInSelection:
[self replaceAllInTextView: aTextView onlyInSelection: YES];
break;
case NSFindPanelActionSelectAll:
NSLog(@"NSFindPanelActionSelectAll not supported");
break;
case NSFindPanelActionSelectAllInSelection:
NSLog(@"NSFindPanelActionSelectAllInSelection not supported");
break;
default:
NSLog(@"Unknown find panel action (%u)", [sender tag]);
}
}
- (BOOL) validateFindPanelAction: (id)sender
withTextView: (NSTextView *)aTextView
{
aTextView = [self targetView: aTextView];
switch ([sender tag])
{
case NSFindPanelActionShowFindPanel:
return YES;
case NSFindPanelActionReplace:
return aTextView != nil;
case NSFindPanelActionSetFindString:
return aTextView && [aTextView selectedRange].length > 0;
case NSFindPanelActionNext:
case NSFindPanelActionPrevious:
case NSFindPanelActionReplaceAll:
case NSFindPanelActionReplaceAndFind:
#if 0 // NSTextView does not support discontinuous selections at present
case NSFindPanelActionSelectAll:
#endif
return aTextView && [findString length] > 0;
case NSFindPanelActionReplaceAllInSelection:
#if 0 // NSTextView does not support discontinuous selections at present
case NSFindPanelActionSelectAllInSelection:
#endif
return [findString length] > 0
&& aTextView && [aTextView selectedRange].length > 0;
default:
break;
}
// disable everything else
return NO;
}
// text finder methods
- (void) showFindPanel
{
if (panel == nil && [self _loadPanel] == NO)
{
return;
}
[messageText setStringValue: @""];
[panel makeKeyAndOrderFront: self];
[findText selectText: self];
}
- (void) takeFindStringFromTextView: (NSText *)aTextView
{
[messageText setStringValue: @""];
if (aTextView != nil)
{
NSRange range = [aTextView selectedRange];
if (range.length)
{
NSString *string = [[aTextView string] substringFromRange: range];
ASSIGNCOPY(findString, string);
[findText setStringValue: string];
[findText selectText: self];
[self _putFindStringToPasteboard];
}
}
}
- (BOOL) findStringInTextView: (NSText *)aTextView
forward: (BOOL)forward
{
NSRange range;
NSRange selectedRange;
NSString *string;
unsigned int options = NSLiteralSearch | NSCaseInsensitiveSearch;
[messageText setStringValue: @""];
if (aTextView == nil)
{
return NO;
}
string = [aTextView string];
selectedRange = [aTextView selectedRange];
[self _updateFindStringFromPanel: &options putToPasteboard: YES];
if (forward)
{
range = NSMakeRange(NSMaxRange(selectedRange),
[string length] - NSMaxRange(selectedRange));
range = [string rangeOfString: findString
options: options
range: range];
if (range.location == NSNotFound)
{
range = NSMakeRange(0, selectedRange.location);
range = [string rangeOfString: findString
options: options
range: range];
}
}
else
{
options |= NSBackwardsSearch;
range = NSMakeRange(0, selectedRange.location);
range = [string rangeOfString: findString
options: options
range: range];
if (range.location == NSNotFound)
{
range = NSMakeRange(NSMaxRange(selectedRange),
[string length] - NSMaxRange(selectedRange));
range = [string rangeOfString: findString
options: options
range: range];
}
}
if (range.location != NSNotFound)
{
[aTextView setSelectedRange: range];
[aTextView scrollRangeToVisible: range];
}
else
{
[messageText setStringValue: _(@"Not found")];
NSBeep();
}
return range.location != NSNotFound;
}
- (void) replaceStringInTextView: (NSTextView *)aTextView
{
[messageText setStringValue: @""];
if (aTextView != nil)
{
NSRange range = [aTextView selectedRange];
if ([aTextView shouldChangeTextInRange: range
replacementString: replaceString])
{
[self _updateReplaceStringFromPanel];
[aTextView replaceCharactersInRange: range
withString: replaceString];
[aTextView didChangeText];
[aTextView scrollRangeToVisible: range];
}
}
}
- (void) replaceAllInTextView: (NSTextView *)aTextView
onlyInSelection: (BOOL)flag
{
int n;
NSRange range, replaceRange;
NSString *format;
NSString *string;
unsigned int options = NSLiteralSearch | NSCaseInsensitiveSearch;
[messageText setStringValue: @""];
if (aTextView == nil)
return;
[self _updateFindStringFromPanel: &options putToPasteboard: YES];
[self _updateReplaceStringFromPanel];
string = [aTextView string];
replaceRange =
flag ? [aTextView selectedRange] : NSMakeRange(0, [string length]);
// look for a first match in the range
range = [string rangeOfString: findString
options: options
range: replaceRange];
if (range.location == NSNotFound)
{
[messageText setStringValue: _(@"Not found")];
NSBeep();
return;
}
n = 0;
do
{
if ([aTextView shouldChangeTextInRange: range
replacementString: replaceString])
{
[aTextView replaceCharactersInRange: range
withString: replaceString];
[aTextView didChangeText];
n++;
}
replaceRange =
NSMakeRange(range.location + [replaceString length],
NSMaxRange(replaceRange) -
(range.location + [findString length]));
range = [string rangeOfString: findString
options: options
range: replaceRange];
}
while (range.location != NSNotFound);
format = _(@"%d replaced");
[messageText setStringValue: [NSString stringWithFormat: format, n]];
// set insertion point to the end of the last match
range = NSMakeRange(replaceRange.location, 0);
[aTextView setSelectedRange: range];
[aTextView scrollRangeToVisible: range];
}
- (NSTextView *) targetView: (NSTextView *)aTextView
{
// If aTextView is equal to the find panel's field editor use the default
// target view
if (aTextView == [panel fieldEditor: NO forObject: [panel firstResponder]])
{
aTextView = nil;
}
if (aTextView == nil)
{
// The default target is the first responder of the main window
// provided that it is a text view
id aResponder = [[NSApp mainWindow] firstResponder];
if ([aResponder isKindOfClass: [NSTextView class]])
{
aTextView = aResponder;
}
}
return aTextView;
}
@end
@implementation GSTextFinder(PrivateMethods)
- (void) _applicationDidBecomeActive: (NSNotification *)notification
{
[self _getFindStringFromPasteboard];
}
- (BOOL) _loadPanel
{
NSDictionary *table =
[NSDictionary dictionaryWithObject: self forKey: NSNibOwner];
if (![GSGuiBundle() loadNibFile: @"GSFindPanel"
externalNameTable: table
withZone: [self zone]])
{
NSLog(@"Model file load failed for GSFindPanel");
return NO;
}
[findText setStringValue: findString];
[replaceText setStringValue: replaceString];
[messageText setStringValue: @""];
// FIXME Setting this in gorm does not have an effect
[panel setFrameAutosaveName: @"NSFindPanel"];
// Make sure the Find menu is enabled when the panel's field editor is
// first responder
[(NSTextView *)[panel fieldEditor: YES forObject: nil] setUsesFindPanel: YES];
return YES;
}
- (void) _updateFindStringFromPanel: (unsigned int *)options
putToPasteboard: (BOOL)flag
{
if (panel)
{
ASSIGN(findString, [findText stringValue]);
if ([ignoreCaseButton state] != NSOffState)
{
*options |= NSCaseInsensitiveSearch;
}
else
{
*options &= ~NSCaseInsensitiveSearch;
}
}
if (flag)
{
[self _putFindStringToPasteboard];
}
}
- (void) _updateReplaceStringFromPanel
{
if (panel)
{
ASSIGN(replaceString, [replaceText stringValue]);
}
}
- (void) _getFindStringFromPasteboard
{
NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSFindPboard];
if ([[pboard types] containsObject:NSStringPboardType])
{
NSString *string = [pboard stringForType:NSStringPboardType];
if ([string length] && ![string isEqualToString:findString])
{
ASSIGN(findString, string);
[findText setStringValue: string];
[findText selectText: self];
}
}
}
- (void) _putFindStringToPasteboard
{
NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSFindPboard];
[pboard declareTypes: [NSArray arrayWithObject:NSStringPboardType]
owner: nil];
[pboard setString: findString forType: NSStringPboardType];
}
@end

View file

@ -86,6 +86,7 @@
#import "AppKit/NSTextView.h"
#import "AppKit/NSWindow.h"
#import "GSGuiPrivate.h"
#import "GSTextFinder.h"
/*
@ -187,7 +188,8 @@ Interface for a bunch of internal methods that need to be cleaned up.
([tv usesRuler]?0x100:0) |
([tv smartInsertDeleteEnabled]?0x200:0) |
([tv allowsUndo]?0x400:0) |
([tv drawsBackground]?0x800:0));
([tv drawsBackground]?0x800:0) |
([tv usesFindPanel]?0x2000:0));
ASSIGN(backgroundColor, [tv backgroundColor]);
ASSIGN(paragraphStyle, [tv defaultParagraphStyle]);
@ -297,7 +299,7 @@ Interface for a bunch of internal methods that need to be cleaned up.
/**** Misc. helpers and stuff ****/
static const int currentVersion = 2;
static const int currentVersion = 3;
static BOOL noLayoutManagerException(void)
{
@ -831,6 +833,8 @@ that makes decoding and encoding compatible with the old code.
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
flag = _tf.allows_undo;
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
flag = _tf.uses_find_panel;
[aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag];
[aCoder encodeObject: _insertionPointColor];
[aCoder encodeValueOfObjCType: @encode(NSSize) at: &containerSize];
flag = [_textContainer widthTracksTextView];
@ -901,6 +905,7 @@ that makes decoding and encoding compatible with the old code.
_tf.smart_insert_delete = ((0x200 & flags) > 0);
_tf.allows_undo = ((0x400 & flags) > 0);
_tf.draws_background = ((0x800 & flags) > 0);
_tf.uses_find_panel = ((0x2000 & flags) > 0);
}
if ([aDecoder containsValueForKey: @"NSTVFlags"])
@ -968,6 +973,11 @@ that makes decoding and encoding compatible with the old code.
_tf.smart_insert_delete = flag;
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
_tf.allows_undo = flag;
if (version >= 3)
{
[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag];
_tf.uses_find_panel = flag;
}
/* build up the rest of the text system, which doesn't get stored
<doesn't even implement the Coding protocol>. */
@ -976,7 +986,7 @@ that makes decoding and encoding compatible with the old code.
/* See initWithFrame: for comments on this RELEASE */
RELEASE(self);
if (version == currentVersion)
if (version >= 2)
{
NSSize containerSize;
@ -2966,6 +2976,17 @@ Scroll so that the beginning of the range is visible.
|| sel_eq(action, @selector(centerSelectionInVisibleArea:)))
return [self isSelectable];
if (sel_eq(action, @selector(performFindPanelAction:)))
{
if ([self usesFindPanel] == NO)
{
return NO;
}
return [[GSTextFinder sharedTextFinder]
validateFindPanelAction: item
withTextView: self];
}
return YES;
}
@ -5496,7 +5517,9 @@ configuation! */
- (void) performFindPanelAction: (id)sender
{
// FIXME
[[GSTextFinder sharedTextFinder]
performFindPanelAction: sender
withTextView: self];
}
@end