aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornyamatongwe <nyamatongwe@gmail.com>2012-04-16 16:04:00 +1000
committernyamatongwe <nyamatongwe@gmail.com>2012-04-16 16:04:00 +1000
commita1d7176b8051738e3a718f0ecb97a4ba7185f311 (patch)
treeeffdf5e3fc864a907a7e178af265c6c739b8a4ae
parent37018ef28c480627860c5e7fafd9baec6f4211fe (diff)
downloadscintilla-mirror-a1d7176b8051738e3a718f0ecb97a4ba7185f311.tar.gz
Implemented find indicator with animation for OS X.
-rw-r--r--cocoa/ScintillaCocoa.h10
-rw-r--r--cocoa/ScintillaCocoa.mm259
-rw-r--r--cocoa/ScintillaFramework/ScintillaFramework.xcodeproj/project.pbxproj4
-rw-r--r--cocoa/ScintillaTest/AppController.mm5
-rw-r--r--doc/ScintillaDoc.html25
-rw-r--r--include/Scintilla.h3
-rw-r--r--include/Scintilla.iface9
7 files changed, 313 insertions, 2 deletions
diff --git a/cocoa/ScintillaCocoa.h b/cocoa/ScintillaCocoa.h
index 5ee1f149f..e4179b1bb 100644
--- a/cocoa/ScintillaCocoa.h
+++ b/cocoa/ScintillaCocoa.h
@@ -57,6 +57,8 @@ extern "C" NSString* ScintillaRecPboardType;
@class ScintillaView;
+@class FindHighlightLayer;
+
/**
* Helper class to be used as timer target (NSTimer).
*/
@@ -119,6 +121,8 @@ private:
NSTimer* tickTimer;
NSTimer* idleTimer;
+ FindHighlightLayer *layerFindIndicator;
+
protected:
PRectangle GetClientRectangle();
Point ConvertPoint(NSPoint point);
@@ -127,6 +131,7 @@ protected:
virtual void Finalise();
virtual CaseFolder *CaseFolderForEncoding();
virtual std::string CaseMapString(const std::string &s, int caseMapping);
+ virtual void CancelModes();
public:
NSView* ContentView();
@@ -209,6 +214,11 @@ public:
void HandleCommand(NSInteger command);
virtual void ActiveStateChanged(bool isActive);
+
+ // Find indicator
+ void ShowFindIndicatorForRange(NSRange charRange, BOOL retaining);
+ void MoveFindIndicatorWithBounce(BOOL bounce);
+ void HideFindIndicator();
};
diff --git a/cocoa/ScintillaCocoa.mm b/cocoa/ScintillaCocoa.mm
index 68590798b..7dc168012 100644
--- a/cocoa/ScintillaCocoa.mm
+++ b/cocoa/ScintillaCocoa.mm
@@ -15,6 +15,9 @@
*/
#import <Cocoa/Cocoa.h>
+#import <QuartzCore/CAGradientLayer.h>
+#import <QuartzCore/CAAnimation.h>
+#import <QuartzCore/CATransaction.h>
#import <Carbon/Carbon.h> // Temporary
@@ -134,6 +137,164 @@ static const KeyToCommand macMapDefault[] =
//--------------------------------------------------------------------------------------------------
+/**
+ * Class to display the animated gold roundrect used on OS X for matches.
+ */
+@interface FindHighlightLayer : CAGradientLayer
+{
+@private
+ NSString *sFind;
+ int positionFind;
+ BOOL retaining;
+ CGFloat widthText;
+ CGFloat heightLine;
+ NSString *sFont;
+ CGFloat fontSize;
+}
+
+@property (copy) NSString *sFind;
+@property (assign) int positionFind;
+@property (assign) BOOL retaining;
+@property (assign) CGFloat widthText;
+@property (assign) CGFloat heightLine;
+@property (copy) NSString *sFont;
+@property (assign) CGFloat fontSize;
+
+- (void) animateMatch: (CGPoint)ptText bounce:(BOOL)bounce;
+- (void) hideMatch;
+
+@end
+
+//--------------------------------------------------------------------------------------------------
+
+@implementation FindHighlightLayer
+
+@synthesize sFind, positionFind, retaining, widthText, heightLine, sFont, fontSize;
+
+-(id) init {
+ if (self = [super init]) {
+ [self setNeedsDisplayOnBoundsChange: YES];
+ // A gold to slightly redder gradient to match other applications
+ CGColorRef colGold = CGColorCreateGenericRGB(1.0, 1.0, 0, 1.0);
+ CGColorRef colGoldRed = CGColorCreateGenericRGB(1.0, 0.8, 0, 1.0);
+ self.colors = [NSArray arrayWithObjects:(id)colGoldRed, (id)colGold, nil];
+ CGColorRelease(colGoldRed);
+ CGColorRelease(colGold);
+
+ CGColorRef colGreyBorder = CGColorCreateGenericGray(0.756f, 0.5f);
+ self.borderColor = colGreyBorder;
+ CGColorRelease(colGreyBorder);
+
+ self.borderWidth = 1.0;
+ self.cornerRadius = 5.0f;
+ self.shadowRadius = 1.0f;
+ self.shadowOpacity = 0.9f;
+ self.shadowOffset = CGSizeMake(0.0f, -2.0f);
+ self.anchorPoint = CGPointMake(0.5, 0.5);
+ }
+ return self;
+
+}
+
+const CGFloat paddingHighlightX = 4;
+const CGFloat paddingHighlightY = 2;
+
+-(void) drawInContext:(CGContextRef)context {
+ if (!sFind || !sFont)
+ return;
+
+ CFStringRef str = CFStringRef(sFind);
+
+ CFMutableDictionaryRef styleDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 2,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ CGColorRef color = CGColorCreateGenericRGB(0.0, 0.0, 0.0, 1.0);
+ CFDictionarySetValue(styleDict, kCTForegroundColorAttributeName, color);
+ CTFontRef fontRef = ::CTFontCreateWithName((CFStringRef)sFont, fontSize, NULL);
+ CFDictionaryAddValue(styleDict, kCTFontAttributeName, fontRef);
+
+ CFAttributedStringRef attrString = ::CFAttributedStringCreate(NULL, str, styleDict);
+ CTLineRef textLine = ::CTLineCreateWithAttributedString(attrString);
+ // Indent from corner of bounds
+ CGContextSetTextPosition(context, paddingHighlightX, 3 + paddingHighlightY);
+ CTLineDraw(textLine, context);
+
+ CFRelease(textLine);
+ CFRelease(attrString);
+ CFRelease(fontRef);
+ CGColorRelease(color);
+ CFRelease(styleDict);
+}
+
+- (void) animateMatch: (CGPoint)ptText bounce:(BOOL)bounce {
+ if (!self.sFind || ![self.sFind length])
+ return;
+
+ CGFloat width = self.widthText + paddingHighlightX * 2;
+ CGFloat height = self.heightLine + paddingHighlightY * 2;
+
+ // Adjust for padding
+ ptText.x -= paddingHighlightX;
+ ptText.y += paddingHighlightY;
+
+ // Shift point to centre as expanding about centre
+ ptText.x += width / 2.0;
+ ptText.y -= height / 2.0;
+
+ [CATransaction begin];
+ [CATransaction setValue:[NSNumber numberWithFloat:0.0] forKey:kCATransactionAnimationDuration];
+ self.bounds = CGRectMake(0,0, width, height);
+ self.position = ptText;
+ if (bounce) {
+ // Do not reset visibility when just moving
+ self.hidden = NO;
+ self.opacity = 1.0;
+ }
+ [self setNeedsDisplay];
+ [CATransaction commit];
+
+ if (bounce) {
+ CABasicAnimation *animBounce = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
+ animBounce.duration = 0.15;
+ animBounce.autoreverses = YES;
+ animBounce.removedOnCompletion = NO;
+ animBounce.fromValue = [NSNumber numberWithFloat: 1.0];
+ animBounce.toValue = [NSNumber numberWithFloat: 1.25];
+
+ if (self.retaining) {
+
+ [self addAnimation: animBounce forKey:@"animateFound"];
+
+ } else {
+
+ CABasicAnimation *animFade = [CABasicAnimation animationWithKeyPath:@"opacity"];
+ animFade.duration = 0.1;
+ animFade.beginTime = 0.4;
+ animFade.removedOnCompletion = NO;
+ animFade.fromValue = [NSNumber numberWithFloat: 1.0];
+ animFade.toValue = [NSNumber numberWithFloat: 0.0];
+
+ CAAnimationGroup *group = [CAAnimationGroup animation];
+ [group setDuration:0.5];
+ group.removedOnCompletion = NO;
+ group.fillMode = kCAFillModeForwards;
+ [group setAnimations:[NSArray arrayWithObjects:animBounce, animFade, nil]];
+
+ [self addAnimation:group forKey:@"animateFound"];
+ }
+ }
+}
+
+- (void) hideMatch {
+ self.sFind = @"";
+ self.positionFind = INVALID_POSITION;
+ self.hidden = YES;
+}
+
+@end
+
+//--------------------------------------------------------------------------------------------------
+
@implementation TimerTarget
- (id) init: (void*) target
@@ -210,6 +371,7 @@ ScintillaCocoa::ScintillaCocoa(NSView* view)
{
wMain= [view retain];
timerTarget = [[[TimerTarget alloc] init: this] retain];
+ layerFindIndicator = NULL;
Initialise();
}
@@ -432,6 +594,16 @@ std::string ScintillaCocoa::CaseMapString(const std::string &s, int caseMapping)
//--------------------------------------------------------------------------------------------------
/**
+ * Cancel all modes, both for base class and any find indicator.
+ */
+void ScintillaCocoa::CancelModes() {
+ ScintillaBase::CancelModes();
+ HideFindIndicator();
+}
+
+//--------------------------------------------------------------------------------------------------
+
+/**
* Helper function to get the outer container which represents the Scintilla editor on application side.
*/
ScintillaView* ScintillaCocoa::TopContainer()
@@ -538,6 +710,18 @@ sptr_t ScintillaCocoa::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lPar
bufferedDraw = false;
break;
+ case SCI_FINDINDICATORSHOW:
+ ShowFindIndicatorForRange(NSMakeRange(wParam, lParam-wParam), YES);
+ return 0;
+
+ case SCI_FINDINDICATORFLASH:
+ ShowFindIndicatorForRange(NSMakeRange(wParam, lParam-wParam), NO);
+ return 0;
+
+ case SCI_FINDINDICATORHIDE:
+ HideFindIndicator();
+ return 0;
+
case WM_UNICHAR:
// Special case not used normally. Characters passed in this way will be inserted
// regardless of their value or modifier states. That means no command interpretation is
@@ -550,7 +734,7 @@ sptr_t ScintillaCocoa::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lPar
return 1;
}
return 0;
-
+
default:
sptr_t r = ScintillaBase::WndProc(iMessage, wParam, lParam);
@@ -1280,6 +1464,11 @@ void ScintillaCocoa::ScrollText(int linesToMove)
{
// Move those pixels
NSView *content = ContentView();
+ if ([content layer]) {
+ [content setNeedsDisplay: YES];
+ MoveFindIndicatorWithBounce(NO);
+ return;
+ }
[content lockFocus];
int diff = vs.lineHeight * linesToMove;
@@ -1341,6 +1530,7 @@ void ScintillaCocoa::SetHorizontalScrollPos()
// does *not* belong to the scroll range.
float relativePosition = (float) xOffset / (scrollWidth - textRect.Width());
[topContainer setHorizontalScrollPosition: relativePosition];
+ MoveFindIndicatorWithBounce(NO);
}
//--------------------------------------------------------------------------------------------------
@@ -1375,6 +1565,8 @@ bool ScintillaCocoa::ModifyScrollBars(int nMax, int nPage)
else
pageSize = scrollRange;
bool horizontalChange = [topContainer setHorizontalScrollRange: scrollRange page: pageSize];
+
+ MoveFindIndicatorWithBounce(NO);
return verticalChange || horizontalChange;
}
@@ -1842,3 +2034,68 @@ void ScintillaCocoa::ActiveStateChanged(bool isActive)
//--------------------------------------------------------------------------------------------------
+void ScintillaCocoa::ShowFindIndicatorForRange(NSRange charRange, BOOL retaining)
+{
+ NSView *content = ContentView();
+ if (!layerFindIndicator)
+ {
+ layerFindIndicator = [[FindHighlightLayer alloc] init];
+ [content setWantsLayer: YES];
+ [[content layer] addSublayer:layerFindIndicator];
+ }
+ [layerFindIndicator removeAnimationForKey:@"animateFound"];
+
+ if (charRange.length)
+ {
+ CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
+ vs.styles[STYLE_DEFAULT].characterSet);
+ std::vector<char> buffer(charRange.length);
+ pdoc->GetCharRange(&buffer[0], charRange.location, charRange.length);
+
+ CFStringRef cfsFind = CFStringCreateWithBytes(kCFAllocatorDefault,
+ reinterpret_cast<const UInt8 *>(&buffer[0]),
+ charRange.length, encoding, false);
+ layerFindIndicator.sFind = (NSString *)cfsFind;
+ CFRelease(cfsFind);
+ layerFindIndicator.retaining = retaining;
+ layerFindIndicator.positionFind = charRange.location;
+ int style = WndProc(SCI_GETSTYLEAT, charRange.location, 0);
+ std::vector<char> bufferFontName(WndProc(SCI_STYLEGETFONT, style, 0) + 1);
+ WndProc(SCI_STYLEGETFONT, style, (sptr_t)&bufferFontName[0]);
+ layerFindIndicator.sFont = [NSString stringWithUTF8String: &bufferFontName[0]];
+
+ layerFindIndicator.fontSize = WndProc(SCI_STYLEGETSIZEFRACTIONAL, style, 0) /
+ (float)SC_FONT_SIZE_MULTIPLIER;
+ layerFindIndicator.widthText = WndProc(SCI_POINTXFROMPOSITION, 0, charRange.location + charRange.length) -
+ WndProc(SCI_POINTXFROMPOSITION, 0, charRange.location);
+ layerFindIndicator.heightLine = WndProc(SCI_TEXTHEIGHT, 0, 0);
+ MoveFindIndicatorWithBounce(YES);
+ }
+ else
+ {
+ [layerFindIndicator hideMatch];
+ }
+}
+
+void ScintillaCocoa::MoveFindIndicatorWithBounce(BOOL bounce)
+{
+ if (layerFindIndicator)
+ {
+ NSView *content = ContentView();
+ NSRect rcBounds = [content bounds];
+ CGPoint ptText;
+ ptText.x = WndProc(SCI_POINTXFROMPOSITION, 0, layerFindIndicator.positionFind);
+ ptText.y = rcBounds.size.height - WndProc(SCI_POINTYFROMPOSITION, 0, layerFindIndicator.positionFind);
+ [layerFindIndicator animateMatch:ptText bounce:bounce];
+ }
+}
+
+void ScintillaCocoa::HideFindIndicator()
+{
+ if (layerFindIndicator)
+ {
+ [layerFindIndicator hideMatch];
+ }
+}
+
+
diff --git a/cocoa/ScintillaFramework/ScintillaFramework.xcodeproj/project.pbxproj b/cocoa/ScintillaFramework/ScintillaFramework.xcodeproj/project.pbxproj
index a5204a91c..5c772563d 100644
--- a/cocoa/ScintillaFramework/ScintillaFramework.xcodeproj/project.pbxproj
+++ b/cocoa/ScintillaFramework/ScintillaFramework.xcodeproj/project.pbxproj
@@ -158,6 +158,7 @@
114B6FEB11FA7645004FB6AB /* PropSetSimple.h in Headers */ = {isa = PBXBuildFile; fileRef = 114B6FE011FA7645004FB6AB /* PropSetSimple.h */; };
114B6FEC11FA7645004FB6AB /* StyleContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 114B6FE111FA7645004FB6AB /* StyleContext.h */; };
114B6FED11FA7645004FB6AB /* WordList.h in Headers */ = {isa = PBXBuildFile; fileRef = 114B6FE211FA7645004FB6AB /* WordList.h */; };
+ 1152A77315313E58000D4E1A /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1152A77215313E58000D4E1A /* QuartzCore.framework */; };
117ACE9114A29A1E002876F9 /* LexTCMD.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 117ACE9014A29A1E002876F9 /* LexTCMD.cxx */; };
119FF1BF13C9D1820007CE42 /* QuartzTextStyle.h in Headers */ = {isa = PBXBuildFile; fileRef = 119FF1BE13C9D1820007CE42 /* QuartzTextStyle.h */; };
11A0A8A1148602DF0018D143 /* LexCoffeeScript.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 11A0A8A0148602DF0018D143 /* LexCoffeeScript.cxx */; };
@@ -341,6 +342,7 @@
114B6FE011FA7645004FB6AB /* PropSetSimple.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PropSetSimple.h; path = ../../lexlib/PropSetSimple.h; sourceTree = SOURCE_ROOT; };
114B6FE111FA7645004FB6AB /* StyleContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StyleContext.h; path = ../../lexlib/StyleContext.h; sourceTree = SOURCE_ROOT; };
114B6FE211FA7645004FB6AB /* WordList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WordList.h; path = ../../lexlib/WordList.h; sourceTree = SOURCE_ROOT; };
+ 1152A77215313E58000D4E1A /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = ../../../../../../../../System/Library/Frameworks/QuartzCore.framework; sourceTree = "<group>"; };
117ACE9014A29A1E002876F9 /* LexTCMD.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LexTCMD.cxx; path = ../../lexers/LexTCMD.cxx; sourceTree = "<group>"; };
119FF1BE13C9D1820007CE42 /* QuartzTextStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QuartzTextStyle.h; path = ../QuartzTextStyle.h; sourceTree = "<group>"; };
11A0A8A0148602DF0018D143 /* LexCoffeeScript.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LexCoffeeScript.cxx; path = ../../lexers/LexCoffeeScript.cxx; sourceTree = "<group>"; };
@@ -376,6 +378,7 @@
buildActionMask = 2147483647;
files = (
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */,
+ 1152A77315313E58000D4E1A /* QuartzCore.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -443,6 +446,7 @@
1058C7B2FEA5585E11CA2CBB /* Other Frameworks */ = {
isa = PBXGroup;
children = (
+ 1152A77215313E58000D4E1A /* QuartzCore.framework */,
0867D6A5FE840307C02AAC07 /* AppKit.framework */,
D2F7E79907B2D74100F64583 /* CoreData.framework */,
0867D69BFE84028FC02AAC07 /* Foundation.framework */,
diff --git a/cocoa/ScintillaTest/AppController.mm b/cocoa/ScintillaTest/AppController.mm
index fc8c331c4..c28974109 100644
--- a/cocoa/ScintillaTest/AppController.mm
+++ b/cocoa/ScintillaTest/AppController.mm
@@ -262,6 +262,11 @@ static const char * box_xpm[] = {
wholeWord: NO
scrollTo: YES
wrap: YES];
+
+ long matchStart = [mEditor getGeneralProperty: SCI_GETSELECTIONSTART parameter: 0];
+ long matchEnd = [mEditor getGeneralProperty: SCI_GETSELECTIONEND parameter: 0];
+ [mEditor setGeneralProperty: SCI_FINDINDICATORFLASH parameter: matchStart value:matchEnd];
+
if ([[searchField stringValue] isEqualToString: @"XX"])
[self showAutocompletion];
}
diff --git a/doc/ScintillaDoc.html b/doc/ScintillaDoc.html
index ebd64760d..6fec8becb 100644
--- a/doc/ScintillaDoc.html
+++ b/doc/ScintillaDoc.html
@@ -79,7 +79,7 @@
<h1>Scintilla Documentation</h1>
- <p>Last edited 26/March/2012 NH</p>
+ <p>Last edited 16/April/2012 NH</p>
<p>There is <a class="jump" href="Design.html">an overview of the internal design of
Scintilla</a>.<br />
@@ -3856,6 +3856,29 @@ struct Sci_TextToFind {
Can be used to iterate through the document to discover all the indicator positions.
</p>
+ <h3 id="FindIndicators">OS X Find Indicator</h3>
+
+ <p>On OS X search matches are highlighted with an animated gold rounded rectangle.
+ The indicator shows, then briefly grows 25% and shrinks to the original size to draw the user's attention.
+ While this feature is currently only implemented on OS X, it may be implemented on other platforms
+ in the future.</p>
+
+ <p><b id="SCI_FINDINDICATORSHOW">SCI_FINDINDICATORSHOW(int start, int end)</b><br />
+ <b id="SCI_FINDINDICATORFLASH">SCI_FINDINDICATORFLASH(int start, int end)</b><br />
+ These two messages show and animate the find indicator. The indicator remains visible with
+ <code>SCI_FINDINDICATORSHOW</code> and fades out after showing for half a second with
+ <code>SCI_FINDINDICATORFLASH</code>.
+ <code>SCI_FINDINDICATORSHOW</code> behaves similarly to the OS X TextEdit and Safari applications
+ and is best suited to editing documentation where the search target is often a word.
+ <code>SCI_FINDINDICATORFLASH</code> is similar to Xcode and is suited to editing source code
+ where the match will often be located next to operators which would otherwise be hidden under the indicator's
+ padding.
+ </p>
+
+ <p><b id="SCI_FINDINDICATORHIDE">SCI_FINDINDICATORHIDE</b><br />
+ This message hides the find indicator.
+ </p>
+
<h3 id="StyleByteIndicators">Style Byte Indicators (deprecated)</h3>
<p>By default, Scintilla organizes the style byte associated with each text byte as 5 bits of
style information (for 32 styles) and 3 bits of indicator information for 3 independent
diff --git a/include/Scintilla.h b/include/Scintilla.h
index 2459c0317..0df0900de 100644
--- a/include/Scintilla.h
+++ b/include/Scintilla.h
@@ -837,6 +837,9 @@ typedef sptr_t (*SciFnDirect)(sptr_t ptr, unsigned int iMessage, uptr_t wParam,
#define SCI_SETTECHNOLOGY 2630
#define SCI_GETTECHNOLOGY 2631
#define SCI_CREATELOADER 2632
+#define SCI_FINDINDICATORSHOW 2640
+#define SCI_FINDINDICATORFLASH 2641
+#define SCI_FINDINDICATORHIDE 2642
#define SCI_STARTRECORD 3001
#define SCI_STOPRECORD 3002
#define SCI_SETLEXER 4001
diff --git a/include/Scintilla.iface b/include/Scintilla.iface
index 6dda631e4..b7f9eede8 100644
--- a/include/Scintilla.iface
+++ b/include/Scintilla.iface
@@ -2217,6 +2217,15 @@ get int GetTechnology=2631(,)
# Create an ILoader*.
fun int CreateLoader=2632(int bytes,)
+# On OS X, show a find indicator.
+fun void FindIndicatorShow=2640(position start, position end)
+
+# On OS X, flash a find indicator, then fade out.
+fun void FindIndicatorFlash=2641(position start, position end)
+
+# On OS X, hide the find indicator.
+fun void FindIndicatorHide=2642(,)
+
# Start notifying the container of all key presses and commands.
fun void StartRecord=3001(,)