diff options
author | nyamatongwe <nyamatongwe@gmail.com> | 2012-04-16 16:04:00 +1000 |
---|---|---|
committer | nyamatongwe <nyamatongwe@gmail.com> | 2012-04-16 16:04:00 +1000 |
commit | a1d7176b8051738e3a718f0ecb97a4ba7185f311 (patch) | |
tree | effdf5e3fc864a907a7e178af265c6c739b8a4ae | |
parent | 37018ef28c480627860c5e7fafd9baec6f4211fe (diff) | |
download | scintilla-mirror-a1d7176b8051738e3a718f0ecb97a4ba7185f311.tar.gz |
Implemented find indicator with animation for OS X.
-rw-r--r-- | cocoa/ScintillaCocoa.h | 10 | ||||
-rw-r--r-- | cocoa/ScintillaCocoa.mm | 259 | ||||
-rw-r--r-- | cocoa/ScintillaFramework/ScintillaFramework.xcodeproj/project.pbxproj | 4 | ||||
-rw-r--r-- | cocoa/ScintillaTest/AppController.mm | 5 | ||||
-rw-r--r-- | doc/ScintillaDoc.html | 25 | ||||
-rw-r--r-- | include/Scintilla.h | 3 | ||||
-rw-r--r-- | include/Scintilla.iface | 9 |
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(,) |