mirror of
https://github.com/scratchfoundation/bgfx.git
synced 2024-11-25 17:18:12 -05:00
2541 lines
82 KiB
Text
2541 lines
82 KiB
Text
|
|
||
|
/**
|
||
|
* Scintilla source code edit control
|
||
|
* ScintillaCocoa.mm - Cocoa subclass of ScintillaBase
|
||
|
*
|
||
|
* Written by Mike Lischke <mlischke@sun.com>
|
||
|
*
|
||
|
* Loosely based on ScintillaMacOSX.cxx.
|
||
|
* Copyright 2003 by Evan Jones <ejones@uwaterloo.ca>
|
||
|
* Based on ScintillaGTK.cxx Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
|
||
|
* The License.txt file describes the conditions under which this software may be distributed.
|
||
|
*
|
||
|
* Copyright (c) 2009, 2010 Sun Microsystems, Inc. All rights reserved.
|
||
|
* This file is dual licensed under LGPL v2.1 and the Scintilla license (http://www.scintilla.org/License.txt).
|
||
|
*/
|
||
|
|
||
|
#import <Cocoa/Cocoa.h>
|
||
|
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
|
||
|
#import <QuartzCore/CAGradientLayer.h>
|
||
|
#endif
|
||
|
#import <QuartzCore/CAAnimation.h>
|
||
|
#import <QuartzCore/CATransaction.h>
|
||
|
|
||
|
#import "Platform.h"
|
||
|
#import "ScintillaView.h"
|
||
|
#import "ScintillaCocoa.h"
|
||
|
#import "PlatCocoa.h"
|
||
|
|
||
|
using namespace Scintilla;
|
||
|
|
||
|
NSString* ScintillaRecPboardType = @"com.scintilla.utf16-plain-text.rectangular";
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
// Define keyboard shortcuts (equivalents) the Mac way.
|
||
|
#define SCI_CMD ( SCI_CTRL)
|
||
|
#define SCI_SCMD ( SCI_CMD | SCI_SHIFT)
|
||
|
#define SCI_SMETA ( SCI_META | SCI_SHIFT)
|
||
|
|
||
|
static const KeyToCommand macMapDefault[] =
|
||
|
{
|
||
|
// OS X specific
|
||
|
{SCK_DOWN, SCI_CTRL, SCI_DOCUMENTEND},
|
||
|
{SCK_DOWN, SCI_CSHIFT, SCI_DOCUMENTENDEXTEND},
|
||
|
{SCK_UP, SCI_CTRL, SCI_DOCUMENTSTART},
|
||
|
{SCK_UP, SCI_CSHIFT, SCI_DOCUMENTSTARTEXTEND},
|
||
|
{SCK_LEFT, SCI_CTRL, SCI_VCHOME},
|
||
|
{SCK_LEFT, SCI_CSHIFT, SCI_VCHOMEEXTEND},
|
||
|
{SCK_RIGHT, SCI_CTRL, SCI_LINEEND},
|
||
|
{SCK_RIGHT, SCI_CSHIFT, SCI_LINEENDEXTEND},
|
||
|
|
||
|
// Similar to Windows and GTK+
|
||
|
// Where equivalent clashes with OS X standard, use Meta instead
|
||
|
{SCK_DOWN, SCI_NORM, SCI_LINEDOWN},
|
||
|
{SCK_DOWN, SCI_SHIFT, SCI_LINEDOWNEXTEND},
|
||
|
{SCK_DOWN, SCI_META, SCI_LINESCROLLDOWN},
|
||
|
{SCK_DOWN, SCI_ASHIFT, SCI_LINEDOWNRECTEXTEND},
|
||
|
{SCK_UP, SCI_NORM, SCI_LINEUP},
|
||
|
{SCK_UP, SCI_SHIFT, SCI_LINEUPEXTEND},
|
||
|
{SCK_UP, SCI_META, SCI_LINESCROLLUP},
|
||
|
{SCK_UP, SCI_ASHIFT, SCI_LINEUPRECTEXTEND},
|
||
|
{'[', SCI_CTRL, SCI_PARAUP},
|
||
|
{'[', SCI_CSHIFT, SCI_PARAUPEXTEND},
|
||
|
{']', SCI_CTRL, SCI_PARADOWN},
|
||
|
{']', SCI_CSHIFT, SCI_PARADOWNEXTEND},
|
||
|
{SCK_LEFT, SCI_NORM, SCI_CHARLEFT},
|
||
|
{SCK_LEFT, SCI_SHIFT, SCI_CHARLEFTEXTEND},
|
||
|
{SCK_LEFT, SCI_ALT, SCI_WORDLEFT},
|
||
|
{SCK_LEFT, SCI_META, SCI_WORDLEFT},
|
||
|
{SCK_LEFT, SCI_SMETA, SCI_WORDLEFTEXTEND},
|
||
|
{SCK_LEFT, SCI_ASHIFT, SCI_CHARLEFTRECTEXTEND},
|
||
|
{SCK_RIGHT, SCI_NORM, SCI_CHARRIGHT},
|
||
|
{SCK_RIGHT, SCI_SHIFT, SCI_CHARRIGHTEXTEND},
|
||
|
{SCK_RIGHT, SCI_ALT, SCI_WORDRIGHT},
|
||
|
{SCK_RIGHT, SCI_META, SCI_WORDRIGHT},
|
||
|
{SCK_RIGHT, SCI_SMETA, SCI_WORDRIGHTEXTEND},
|
||
|
{SCK_RIGHT, SCI_ASHIFT, SCI_CHARRIGHTRECTEXTEND},
|
||
|
{'/', SCI_CTRL, SCI_WORDPARTLEFT},
|
||
|
{'/', SCI_CSHIFT, SCI_WORDPARTLEFTEXTEND},
|
||
|
{'\\', SCI_CTRL, SCI_WORDPARTRIGHT},
|
||
|
{'\\', SCI_CSHIFT, SCI_WORDPARTRIGHTEXTEND},
|
||
|
{SCK_HOME, SCI_NORM, SCI_VCHOME},
|
||
|
{SCK_HOME, SCI_SHIFT, SCI_VCHOMEEXTEND},
|
||
|
{SCK_HOME, SCI_CTRL, SCI_DOCUMENTSTART},
|
||
|
{SCK_HOME, SCI_CSHIFT, SCI_DOCUMENTSTARTEXTEND},
|
||
|
{SCK_HOME, SCI_ALT, SCI_HOMEDISPLAY},
|
||
|
{SCK_HOME, SCI_ASHIFT, SCI_VCHOMERECTEXTEND},
|
||
|
{SCK_END, SCI_NORM, SCI_LINEEND},
|
||
|
{SCK_END, SCI_SHIFT, SCI_LINEENDEXTEND},
|
||
|
{SCK_END, SCI_CTRL, SCI_DOCUMENTEND},
|
||
|
{SCK_END, SCI_CSHIFT, SCI_DOCUMENTENDEXTEND},
|
||
|
{SCK_END, SCI_ALT, SCI_LINEENDDISPLAY},
|
||
|
{SCK_END, SCI_ASHIFT, SCI_LINEENDRECTEXTEND},
|
||
|
{SCK_PRIOR, SCI_NORM, SCI_PAGEUP},
|
||
|
{SCK_PRIOR, SCI_SHIFT, SCI_PAGEUPEXTEND},
|
||
|
{SCK_PRIOR, SCI_ASHIFT, SCI_PAGEUPRECTEXTEND},
|
||
|
{SCK_NEXT, SCI_NORM, SCI_PAGEDOWN},
|
||
|
{SCK_NEXT, SCI_SHIFT, SCI_PAGEDOWNEXTEND},
|
||
|
{SCK_NEXT, SCI_ASHIFT, SCI_PAGEDOWNRECTEXTEND},
|
||
|
{SCK_DELETE, SCI_NORM, SCI_CLEAR},
|
||
|
{SCK_DELETE, SCI_SHIFT, SCI_CUT},
|
||
|
{SCK_DELETE, SCI_CTRL, SCI_DELWORDRIGHT},
|
||
|
{SCK_DELETE, SCI_CSHIFT, SCI_DELLINERIGHT},
|
||
|
{SCK_INSERT, SCI_NORM, SCI_EDITTOGGLEOVERTYPE},
|
||
|
{SCK_INSERT, SCI_SHIFT, SCI_PASTE},
|
||
|
{SCK_INSERT, SCI_CTRL, SCI_COPY},
|
||
|
{SCK_ESCAPE, SCI_NORM, SCI_CANCEL},
|
||
|
{SCK_BACK, SCI_NORM, SCI_DELETEBACK},
|
||
|
{SCK_BACK, SCI_SHIFT, SCI_DELETEBACK},
|
||
|
{SCK_BACK, SCI_CTRL, SCI_DELWORDLEFT},
|
||
|
{SCK_BACK, SCI_ALT, SCI_DELWORDLEFT},
|
||
|
{SCK_BACK, SCI_CSHIFT, SCI_DELLINELEFT},
|
||
|
{'z', SCI_CMD, SCI_UNDO},
|
||
|
{'z', SCI_SCMD, SCI_REDO},
|
||
|
{'x', SCI_CMD, SCI_CUT},
|
||
|
{'c', SCI_CMD, SCI_COPY},
|
||
|
{'v', SCI_CMD, SCI_PASTE},
|
||
|
{'a', SCI_CMD, SCI_SELECTALL},
|
||
|
{SCK_TAB, SCI_NORM, SCI_TAB},
|
||
|
{SCK_TAB, SCI_SHIFT, SCI_BACKTAB},
|
||
|
{SCK_RETURN, SCI_NORM, SCI_NEWLINE},
|
||
|
{SCK_RETURN, SCI_SHIFT, SCI_NEWLINE},
|
||
|
{SCK_ADD, SCI_CMD, SCI_ZOOMIN},
|
||
|
{SCK_SUBTRACT, SCI_CMD, SCI_ZOOMOUT},
|
||
|
{SCK_DIVIDE, SCI_CMD, SCI_SETZOOM},
|
||
|
{'l', SCI_CMD, SCI_LINECUT},
|
||
|
{'l', SCI_CSHIFT, SCI_LINEDELETE},
|
||
|
{'t', SCI_CSHIFT, SCI_LINECOPY},
|
||
|
{'t', SCI_CTRL, SCI_LINETRANSPOSE},
|
||
|
{'d', SCI_CTRL, SCI_SELECTIONDUPLICATE},
|
||
|
{'u', SCI_CTRL, SCI_LOWERCASE},
|
||
|
{'u', SCI_CSHIFT, SCI_UPPERCASE},
|
||
|
{0, 0, 0},
|
||
|
};
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
|
||
|
|
||
|
// Only implement FindHighlightLayer on OS X 10.6+
|
||
|
|
||
|
/**
|
||
|
* 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]) {
|
||
|
[self hideMatch];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CGFloat width = self.widthText + paddingHighlightX * 2;
|
||
|
CGFloat height = self.heightLine + paddingHighlightY * 2;
|
||
|
|
||
|
CGFloat flipper = self.geometryFlipped ? -1.0 : 1.0;
|
||
|
|
||
|
// Adjust for padding
|
||
|
ptText.x -= paddingHighlightX;
|
||
|
ptText.y += flipper * paddingHighlightY;
|
||
|
|
||
|
// Shift point to centre as expanding about centre
|
||
|
ptText.x += width / 2.0;
|
||
|
ptText.y -= flipper * 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
|
||
|
|
||
|
#endif
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
@implementation TimerTarget
|
||
|
|
||
|
- (id) init: (void*) target
|
||
|
{
|
||
|
self = [super init];
|
||
|
if (self != nil)
|
||
|
{
|
||
|
mTarget = target;
|
||
|
|
||
|
// Get the default notification queue for the thread which created the instance (usually the
|
||
|
// main thread). We need that later for idle event processing.
|
||
|
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
|
||
|
notificationQueue = [[NSNotificationQueue alloc] initWithNotificationCenter: center];
|
||
|
[center addObserver: self selector: @selector(idleTriggered:) name: @"Idle" object: nil];
|
||
|
}
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
- (void) dealloc
|
||
|
{
|
||
|
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
|
||
|
[center removeObserver:self];
|
||
|
[notificationQueue release];
|
||
|
[super dealloc];
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Method called by a timer installed by ScintillaCocoa. This two step approach is needed because
|
||
|
* a native Obj-C class is required as target for the timer.
|
||
|
*/
|
||
|
- (void) timerFired: (NSTimer*) timer
|
||
|
{
|
||
|
reinterpret_cast<ScintillaCocoa*>(mTarget)->TimerFired(timer);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Another timer callback for the idle timer.
|
||
|
*/
|
||
|
- (void) idleTimerFired: (NSTimer*) timer
|
||
|
{
|
||
|
#pragma unused(timer)
|
||
|
// Idle timer event.
|
||
|
// Post a new idle notification, which gets executed when the run loop is idle.
|
||
|
// Since we are coalescing on name and sender there will always be only one actual notification
|
||
|
// even for multiple requests.
|
||
|
NSNotification *notification = [NSNotification notificationWithName: @"Idle" object: self];
|
||
|
[notificationQueue enqueueNotification: notification
|
||
|
postingStyle: NSPostWhenIdle
|
||
|
coalesceMask: (NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender)
|
||
|
forModes: nil];
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Another step for idle events. The timer (for idle events) simply requests a notification on
|
||
|
* idle time. Only when this notification is send we actually call back the editor.
|
||
|
*/
|
||
|
- (void) idleTriggered: (NSNotification*) notification
|
||
|
{
|
||
|
#pragma unused(notification)
|
||
|
reinterpret_cast<ScintillaCocoa*>(mTarget)->IdleTimerFired();
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
|
||
|
//----------------- ScintillaCocoa -----------------------------------------------------------------
|
||
|
|
||
|
ScintillaCocoa::ScintillaCocoa(SCIContentView* view, SCIMarginView* viewMargin)
|
||
|
{
|
||
|
vs.marginInside = false;
|
||
|
wMain = view; // Don't retain since we're owned by view, which would cause a cycle
|
||
|
wMargin = viewMargin;
|
||
|
timerTarget = [[TimerTarget alloc] init: this];
|
||
|
lastMouseEvent = NULL;
|
||
|
delegate = NULL;
|
||
|
notifyObj = NULL;
|
||
|
notifyProc = NULL;
|
||
|
capturedMouse = false;
|
||
|
enteredSetScrollingSize = false;
|
||
|
scrollSpeed = 1;
|
||
|
scrollTicks = 2000;
|
||
|
tickTimer = NULL;
|
||
|
idleTimer = NULL;
|
||
|
observer = NULL;
|
||
|
layerFindIndicator = NULL;
|
||
|
imeInteraction = imeInline;
|
||
|
for (TickReason tr=tickCaret; tr<=tickPlatform; tr = static_cast<TickReason>(tr+1))
|
||
|
{
|
||
|
timers[tr] = nil;
|
||
|
}
|
||
|
Initialise();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
ScintillaCocoa::~ScintillaCocoa()
|
||
|
{
|
||
|
Finalise();
|
||
|
[timerTarget release];
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Core initialization of the control. Everything that needs to be set up happens here.
|
||
|
*/
|
||
|
void ScintillaCocoa::Initialise()
|
||
|
{
|
||
|
Scintilla_LinkLexers();
|
||
|
|
||
|
// Tell Scintilla not to buffer: Quartz buffers drawing for us.
|
||
|
WndProc(SCI_SETBUFFEREDDRAW, 0, 0);
|
||
|
|
||
|
// We are working with Unicode exclusively.
|
||
|
WndProc(SCI_SETCODEPAGE, SC_CP_UTF8, 0);
|
||
|
|
||
|
// Add Mac specific key bindings.
|
||
|
for (int i = 0; macMapDefault[i].key; i++)
|
||
|
kmap.AssignCmdKey(macMapDefault[i].key, macMapDefault[i].modifiers, macMapDefault[i].msg);
|
||
|
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* We need some clean up. Do it here.
|
||
|
*/
|
||
|
void ScintillaCocoa::Finalise()
|
||
|
{
|
||
|
ObserverRemove();
|
||
|
for (TickReason tr=tickCaret; tr<=tickPlatform; tr = static_cast<TickReason>(tr+1))
|
||
|
{
|
||
|
FineTickerCancel(tr);
|
||
|
}
|
||
|
ScintillaBase::Finalise();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::UpdateObserver(CFRunLoopObserverRef /* observer */, CFRunLoopActivity /* activity */, void *info) {
|
||
|
ScintillaCocoa* sci = reinterpret_cast<ScintillaCocoa*>(info);
|
||
|
sci->IdleWork();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Add an observer to the run loop to perform styling as high-priority idle task.
|
||
|
*/
|
||
|
|
||
|
void ScintillaCocoa::ObserverAdd() {
|
||
|
if (!observer) {
|
||
|
CFRunLoopObserverContext context;
|
||
|
context.version = 0;
|
||
|
context.info = this;
|
||
|
context.retain = NULL;
|
||
|
context.release = NULL;
|
||
|
context.copyDescription = NULL;
|
||
|
|
||
|
CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
|
||
|
observer = CFRunLoopObserverCreate(NULL, kCFRunLoopEntry | kCFRunLoopBeforeWaiting,
|
||
|
true, 0, UpdateObserver, &context);
|
||
|
CFRunLoopAddObserver(mainRunLoop, observer, kCFRunLoopCommonModes);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Remove the run loop observer.
|
||
|
*/
|
||
|
void ScintillaCocoa::ObserverRemove() {
|
||
|
if (observer) {
|
||
|
CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
|
||
|
CFRunLoopRemoveObserver(mainRunLoop, observer, kCFRunLoopCommonModes);
|
||
|
CFRelease(observer);
|
||
|
}
|
||
|
observer = NULL;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::IdleWork() {
|
||
|
Editor::IdleWork();
|
||
|
ObserverRemove();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::QueueIdleWork(WorkNeeded::workItems items, int upTo) {
|
||
|
Editor::QueueIdleWork(items, upTo);
|
||
|
ObserverAdd();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Convert a core foundation string into an array of bytes in a particular encoding
|
||
|
*/
|
||
|
|
||
|
static char *EncodedBytes(CFStringRef cfsRef, CFStringEncoding encoding) {
|
||
|
CFRange rangeAll = {0, CFStringGetLength(cfsRef)};
|
||
|
CFIndex usedLen = 0;
|
||
|
CFStringGetBytes(cfsRef, rangeAll, encoding, '?',
|
||
|
false, NULL, 0, &usedLen);
|
||
|
|
||
|
char *buffer = new char[usedLen+1];
|
||
|
CFStringGetBytes(cfsRef, rangeAll, encoding, '?',
|
||
|
false, (UInt8 *)buffer,usedLen, NULL);
|
||
|
buffer[usedLen] = '\0';
|
||
|
return buffer;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Convert a core foundation string into a std::string in a particular encoding
|
||
|
*/
|
||
|
|
||
|
static std::string EncodedBytesString(CFStringRef cfsRef, CFStringEncoding encoding) {
|
||
|
const CFRange rangeAll = {0, CFStringGetLength(cfsRef)};
|
||
|
CFIndex usedLen = 0;
|
||
|
CFStringGetBytes(cfsRef, rangeAll, encoding, '?', false,
|
||
|
NULL, 0, &usedLen);
|
||
|
|
||
|
std::string buffer(usedLen, '\0');
|
||
|
if (usedLen > 0) {
|
||
|
CFStringGetBytes(cfsRef, rangeAll, encoding, '?', false,
|
||
|
reinterpret_cast<UInt8 *>(&buffer[0]), usedLen, NULL);
|
||
|
}
|
||
|
return buffer;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Case folders.
|
||
|
*/
|
||
|
|
||
|
class CaseFolderDBCS : public CaseFolderTable {
|
||
|
CFStringEncoding encoding;
|
||
|
public:
|
||
|
explicit CaseFolderDBCS(CFStringEncoding encoding_) : encoding(encoding_) {
|
||
|
StandardASCII();
|
||
|
}
|
||
|
virtual size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) {
|
||
|
if ((lenMixed == 1) && (sizeFolded > 0)) {
|
||
|
folded[0] = mapping[static_cast<unsigned char>(mixed[0])];
|
||
|
return 1;
|
||
|
} else {
|
||
|
CFStringRef cfsVal = CFStringCreateWithBytes(kCFAllocatorDefault,
|
||
|
reinterpret_cast<const UInt8 *>(mixed),
|
||
|
lenMixed, encoding, false);
|
||
|
|
||
|
NSString *sMapped = [(NSString *)cfsVal stringByFoldingWithOptions:NSCaseInsensitiveSearch
|
||
|
locale:[NSLocale currentLocale]];
|
||
|
|
||
|
char *encoded = EncodedBytes((CFStringRef)sMapped, encoding);
|
||
|
|
||
|
size_t lenMapped = strlen(encoded);
|
||
|
if (lenMapped < sizeFolded) {
|
||
|
memcpy(folded, encoded, lenMapped);
|
||
|
} else {
|
||
|
folded[0] = '\0';
|
||
|
lenMapped = 1;
|
||
|
}
|
||
|
delete []encoded;
|
||
|
CFRelease(cfsVal);
|
||
|
return lenMapped;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
CaseFolder *ScintillaCocoa::CaseFolderForEncoding() {
|
||
|
if (pdoc->dbcsCodePage == SC_CP_UTF8) {
|
||
|
return new CaseFolderUnicode();
|
||
|
} else {
|
||
|
CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
|
||
|
vs.styles[STYLE_DEFAULT].characterSet);
|
||
|
if (pdoc->dbcsCodePage == 0) {
|
||
|
CaseFolderTable *pcf = new CaseFolderTable();
|
||
|
pcf->StandardASCII();
|
||
|
// Only for single byte encodings
|
||
|
for (int i=0x80; i<0x100; i++) {
|
||
|
char sCharacter[2] = "A";
|
||
|
sCharacter[0] = static_cast<char>(i);
|
||
|
CFStringRef cfsVal = CFStringCreateWithBytes(kCFAllocatorDefault,
|
||
|
reinterpret_cast<const UInt8 *>(sCharacter),
|
||
|
1, encoding, false);
|
||
|
if (!cfsVal)
|
||
|
continue;
|
||
|
|
||
|
NSString *sMapped = [(NSString *)cfsVal stringByFoldingWithOptions:NSCaseInsensitiveSearch
|
||
|
locale:[NSLocale currentLocale]];
|
||
|
|
||
|
char *encoded = EncodedBytes((CFStringRef)sMapped, encoding);
|
||
|
|
||
|
if (strlen(encoded) == 1) {
|
||
|
pcf->SetTranslation(sCharacter[0], encoded[0]);
|
||
|
}
|
||
|
|
||
|
delete []encoded;
|
||
|
CFRelease(cfsVal);
|
||
|
}
|
||
|
return pcf;
|
||
|
} else {
|
||
|
return new CaseFolderDBCS(encoding);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Case-fold the given string depending on the specified case mapping type.
|
||
|
*/
|
||
|
std::string ScintillaCocoa::CaseMapString(const std::string &s, int caseMapping)
|
||
|
{
|
||
|
if ((s.size() == 0) || (caseMapping == cmSame))
|
||
|
return s;
|
||
|
|
||
|
if (IsUnicodeMode()) {
|
||
|
std::string retMapped(s.length() * maxExpansionCaseConversion, 0);
|
||
|
size_t lenMapped = CaseConvertString(&retMapped[0], retMapped.length(), s.c_str(), s.length(),
|
||
|
(caseMapping == cmUpper) ? CaseConversionUpper : CaseConversionLower);
|
||
|
retMapped.resize(lenMapped);
|
||
|
return retMapped;
|
||
|
}
|
||
|
|
||
|
CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
|
||
|
vs.styles[STYLE_DEFAULT].characterSet);
|
||
|
CFStringRef cfsVal = CFStringCreateWithBytes(kCFAllocatorDefault,
|
||
|
reinterpret_cast<const UInt8 *>(s.c_str()),
|
||
|
s.length(), encoding, false);
|
||
|
|
||
|
NSString *sMapped;
|
||
|
switch (caseMapping)
|
||
|
{
|
||
|
case cmUpper:
|
||
|
sMapped = [(NSString *)cfsVal uppercaseString];
|
||
|
break;
|
||
|
case cmLower:
|
||
|
sMapped = [(NSString *)cfsVal lowercaseString];
|
||
|
break;
|
||
|
default:
|
||
|
sMapped = (NSString *)cfsVal;
|
||
|
}
|
||
|
|
||
|
// Back to encoding
|
||
|
char *encoded = EncodedBytes((CFStringRef)sMapped, encoding);
|
||
|
std::string result(encoded);
|
||
|
delete []encoded;
|
||
|
CFRelease(cfsVal);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Cancel all modes, both for base class and any find indicator.
|
||
|
*/
|
||
|
void ScintillaCocoa::CancelModes() {
|
||
|
ScintillaBase::CancelModes();
|
||
|
HideFindIndicator();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Helper function to get the scrolling view.
|
||
|
*/
|
||
|
NSScrollView* ScintillaCocoa::ScrollContainer() const {
|
||
|
NSView* container = static_cast<NSView*>(wMain.GetID());
|
||
|
return static_cast<NSScrollView*>([[container superview] superview]);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Helper function to get the inner container which represents the actual "canvas" we work with.
|
||
|
*/
|
||
|
SCIContentView* ScintillaCocoa::ContentView()
|
||
|
{
|
||
|
return static_cast<SCIContentView*>(wMain.GetID());
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Return the top left visible point relative to the origin point of the whole document.
|
||
|
*/
|
||
|
Scintilla::Point ScintillaCocoa::GetVisibleOriginInMain() const
|
||
|
{
|
||
|
NSScrollView *scrollView = ScrollContainer();
|
||
|
NSRect contentRect = [[scrollView contentView] bounds];
|
||
|
return Point(static_cast<XYPOSITION>(contentRect.origin.x), static_cast<XYPOSITION>(contentRect.origin.y));
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Instead of returning the size of the inner view we have to return the visible part of it
|
||
|
* in order to make scrolling working properly.
|
||
|
* The returned value is in document coordinates.
|
||
|
*/
|
||
|
PRectangle ScintillaCocoa::GetClientRectangle() const
|
||
|
{
|
||
|
NSScrollView *scrollView = ScrollContainer();
|
||
|
NSSize size = [[scrollView contentView] bounds].size;
|
||
|
Point origin = GetVisibleOriginInMain();
|
||
|
return PRectangle(origin.x, origin.y, static_cast<XYPOSITION>(origin.x+size.width),
|
||
|
static_cast<XYPOSITION>(origin.y + size.height));
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Allow for prepared rectangle
|
||
|
*/
|
||
|
PRectangle ScintillaCocoa::GetClientDrawingRectangle() {
|
||
|
#if MAC_OS_X_VERSION_MAX_ALLOWED > 1080
|
||
|
NSView *content = ContentView();
|
||
|
if ([content respondsToSelector: @selector(setPreparedContentRect:)]) {
|
||
|
NSRect rcPrepared = [content preparedContentRect];
|
||
|
if (!NSIsEmptyRect(rcPrepared))
|
||
|
return NSRectToPRectangle(rcPrepared);
|
||
|
}
|
||
|
#endif
|
||
|
return ScintillaCocoa::GetClientRectangle();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Converts the given point from base coordinates to local coordinates and at the same time into
|
||
|
* a native Point structure. Base coordinates are used for the top window used in the view hierarchy.
|
||
|
* Returned value is in view coordinates.
|
||
|
*/
|
||
|
Scintilla::Point ScintillaCocoa::ConvertPoint(NSPoint point)
|
||
|
{
|
||
|
NSView* container = ContentView();
|
||
|
NSPoint result = [container convertPoint: point fromView: nil];
|
||
|
Scintilla::Point ptOrigin = GetVisibleOriginInMain();
|
||
|
return Point(static_cast<XYPOSITION>(result.x - ptOrigin.x), static_cast<XYPOSITION>(result.y - ptOrigin.y));
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Do not clip like superclass as Cocoa is not reporting all of prepared area.
|
||
|
*/
|
||
|
void ScintillaCocoa::RedrawRect(PRectangle rc)
|
||
|
{
|
||
|
if (!rc.Empty())
|
||
|
wMain.InvalidateRectangle(rc);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::DiscardOverdraw()
|
||
|
{
|
||
|
#if MAC_OS_X_VERSION_MAX_ALLOWED > 1080
|
||
|
// If running on 10.9, reset prepared area to visible area
|
||
|
NSView *content = ContentView();
|
||
|
if ([content respondsToSelector: @selector(setPreparedContentRect:)]) {
|
||
|
content.preparedContentRect = [content visibleRect];
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Ensure all of prepared content is also redrawn.
|
||
|
*/
|
||
|
void ScintillaCocoa::Redraw()
|
||
|
{
|
||
|
wMargin.InvalidateAll();
|
||
|
DiscardOverdraw();
|
||
|
wMain.InvalidateAll();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* A function to directly execute code that would usually go the long way via window messages.
|
||
|
* However this is a Windows metaphor and not used here, hence we just call our fake
|
||
|
* window proc. The given parameters directly reflect the message parameters used on Windows.
|
||
|
*
|
||
|
* @param ptr The target which is to be called.
|
||
|
* @param iMessage A code that indicates which message was sent.
|
||
|
* @param wParam One of the two free parameters for the message. Traditionally a word sized parameter
|
||
|
* (hence the w prefix).
|
||
|
* @param lParam The other of the two free parameters. A signed long.
|
||
|
*/
|
||
|
sptr_t ScintillaCocoa::DirectFunction(sptr_t ptr, unsigned int iMessage, uptr_t wParam,
|
||
|
sptr_t lParam)
|
||
|
{
|
||
|
return reinterpret_cast<ScintillaCocoa *>(ptr)->WndProc(iMessage, wParam, lParam);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* This method is very similar to DirectFunction. On Windows it sends a message (not in the Obj-C sense)
|
||
|
* to the target window. Here we simply call our fake window proc.
|
||
|
*/
|
||
|
sptr_t scintilla_send_message(void* sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam)
|
||
|
{
|
||
|
ScintillaView *control = reinterpret_cast<ScintillaView*>(sci);
|
||
|
return [control message:iMessage wParam:wParam lParam:lParam];
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* That's our fake window procedure. On Windows each window has a dedicated procedure to handle
|
||
|
* commands (also used to synchronize UI and background threads), which is not the case in Cocoa.
|
||
|
*
|
||
|
* Messages handled here are almost solely for special commands of the backend. Everything which
|
||
|
* would be system messages on Windows (e.g. for key down, mouse move etc.) are handled by
|
||
|
* directly calling appropriate handlers.
|
||
|
*/
|
||
|
sptr_t ScintillaCocoa::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam)
|
||
|
{
|
||
|
switch (iMessage)
|
||
|
{
|
||
|
case SCI_GETDIRECTFUNCTION:
|
||
|
return reinterpret_cast<sptr_t>(DirectFunction);
|
||
|
|
||
|
case SCI_GETDIRECTPOINTER:
|
||
|
return reinterpret_cast<sptr_t>(this);
|
||
|
|
||
|
case SCI_TARGETASUTF8:
|
||
|
return TargetAsUTF8(reinterpret_cast<char*>(lParam));
|
||
|
|
||
|
case SCI_ENCODEDFROMUTF8:
|
||
|
return EncodedFromUTF8(reinterpret_cast<char*>(wParam),
|
||
|
reinterpret_cast<char*>(lParam));
|
||
|
|
||
|
case SCI_SETIMEINTERACTION:
|
||
|
// Only inline IME supported on Cocoa
|
||
|
break;
|
||
|
|
||
|
case SCI_GRABFOCUS:
|
||
|
[[ContentView() window] makeFirstResponder:ContentView()];
|
||
|
break;
|
||
|
|
||
|
case SCI_SETBUFFEREDDRAW:
|
||
|
// Buffered drawing not supported on Cocoa
|
||
|
view.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;
|
||
|
|
||
|
default:
|
||
|
sptr_t r = ScintillaBase::WndProc(iMessage, wParam, lParam);
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
return 0l;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* In Windows lingo this is the handler which handles anything that wasn't handled in the normal
|
||
|
* window proc which would usually send the message back to generic window proc that Windows uses.
|
||
|
*/
|
||
|
sptr_t ScintillaCocoa::DefWndProc(unsigned int, uptr_t, sptr_t)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Handle any ScintillaCocoa-specific ticking or call superclass.
|
||
|
*/
|
||
|
void ScintillaCocoa::TickFor(TickReason reason)
|
||
|
{
|
||
|
if (reason == tickPlatform)
|
||
|
{
|
||
|
DragScroll();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Editor::TickFor(reason);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Report that this Editor subclass has a working implementation of FineTickerStart.
|
||
|
*/
|
||
|
bool ScintillaCocoa::FineTickerAvailable()
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Is a particular timer currently running?
|
||
|
*/
|
||
|
bool ScintillaCocoa::FineTickerRunning(TickReason reason)
|
||
|
{
|
||
|
return timers[reason] != nil;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Start a fine-grained timer.
|
||
|
*/
|
||
|
void ScintillaCocoa::FineTickerStart(TickReason reason, int millis, int tolerance)
|
||
|
{
|
||
|
FineTickerCancel(reason);
|
||
|
NSTimer *fineTimer = [NSTimer scheduledTimerWithTimeInterval: millis / 1000.0
|
||
|
target: timerTarget
|
||
|
selector: @selector(timerFired:)
|
||
|
userInfo: nil
|
||
|
repeats: YES];
|
||
|
if (tolerance && [fineTimer respondsToSelector: @selector(setTolerance:)])
|
||
|
{
|
||
|
[fineTimer setTolerance: tolerance / 1000.0];
|
||
|
}
|
||
|
timers[reason] = fineTimer;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Cancel a fine-grained timer.
|
||
|
*/
|
||
|
void ScintillaCocoa::FineTickerCancel(TickReason reason)
|
||
|
{
|
||
|
if (timers[reason])
|
||
|
{
|
||
|
[timers[reason] invalidate];
|
||
|
timers[reason] = nil;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
bool ScintillaCocoa::SetIdle(bool on)
|
||
|
{
|
||
|
if (idler.state != on)
|
||
|
{
|
||
|
idler.state = on;
|
||
|
if (idler.state)
|
||
|
{
|
||
|
// Scintilla ticks = milliseconds
|
||
|
idleTimer = [NSTimer scheduledTimerWithTimeInterval: timer.tickSize / 1000.0
|
||
|
target: timerTarget
|
||
|
selector: @selector(idleTimerFired:)
|
||
|
userInfo: nil
|
||
|
repeats: YES];
|
||
|
idler.idlerID = reinterpret_cast<IdlerID>(idleTimer);
|
||
|
}
|
||
|
else
|
||
|
if (idler.idlerID != NULL)
|
||
|
{
|
||
|
[reinterpret_cast<NSTimer*>(idler.idlerID) invalidate];
|
||
|
idler.idlerID = 0;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::CopyToClipboard(const SelectionText &selectedText)
|
||
|
{
|
||
|
SetPasteboardData([NSPasteboard generalPasteboard], selectedText);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::Copy()
|
||
|
{
|
||
|
if (!sel.Empty())
|
||
|
{
|
||
|
SelectionText selectedText;
|
||
|
CopySelectionRange(&selectedText);
|
||
|
CopyToClipboard(selectedText);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
bool ScintillaCocoa::CanPaste()
|
||
|
{
|
||
|
if (!Editor::CanPaste())
|
||
|
return false;
|
||
|
|
||
|
return GetPasteboardData([NSPasteboard generalPasteboard], NULL);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::Paste()
|
||
|
{
|
||
|
Paste(false);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Pastes data from the paste board into the editor.
|
||
|
*/
|
||
|
void ScintillaCocoa::Paste(bool forceRectangular)
|
||
|
{
|
||
|
SelectionText selectedText;
|
||
|
bool ok = GetPasteboardData([NSPasteboard generalPasteboard], &selectedText);
|
||
|
if (forceRectangular)
|
||
|
selectedText.rectangular = forceRectangular;
|
||
|
|
||
|
if (!ok || selectedText.Empty())
|
||
|
// No data or no flavor we support.
|
||
|
return;
|
||
|
|
||
|
pdoc->BeginUndoAction();
|
||
|
ClearSelection(false);
|
||
|
InsertPasteShape(selectedText.Data(), static_cast<int>(selectedText.Length()),
|
||
|
selectedText.rectangular ? pasteRectangular : pasteStream);
|
||
|
pdoc->EndUndoAction();
|
||
|
|
||
|
Redraw();
|
||
|
EnsureCaretVisible();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::CTPaint(void* gc, NSRect rc) {
|
||
|
#pragma unused(rc)
|
||
|
Surface *surfaceWindow = Surface::Allocate(SC_TECHNOLOGY_DEFAULT);
|
||
|
if (surfaceWindow) {
|
||
|
surfaceWindow->Init(gc, wMain.GetID());
|
||
|
surfaceWindow->SetUnicodeMode(SC_CP_UTF8 == ct.codePage);
|
||
|
surfaceWindow->SetDBCSMode(ct.codePage);
|
||
|
ct.PaintCT(surfaceWindow);
|
||
|
surfaceWindow->Release();
|
||
|
delete surfaceWindow;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@interface CallTipView : NSControl {
|
||
|
ScintillaCocoa *sci;
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
|
||
|
@implementation CallTipView
|
||
|
|
||
|
- (NSView*) initWithFrame: (NSRect) frame {
|
||
|
self = [super initWithFrame: frame];
|
||
|
|
||
|
if (self) {
|
||
|
sci = NULL;
|
||
|
}
|
||
|
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
- (void) dealloc {
|
||
|
[super dealloc];
|
||
|
}
|
||
|
|
||
|
- (BOOL) isFlipped {
|
||
|
return YES;
|
||
|
}
|
||
|
|
||
|
- (void) setSci: (ScintillaCocoa *) sci_ {
|
||
|
sci = sci_;
|
||
|
}
|
||
|
|
||
|
- (void) drawRect: (NSRect) needsDisplayInRect {
|
||
|
if (sci) {
|
||
|
CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
|
||
|
sci->CTPaint(context, needsDisplayInRect);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void) mouseDown: (NSEvent *) event {
|
||
|
if (sci) {
|
||
|
sci->CallTipMouseDown([event locationInWindow]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// On OS X, only the key view should modify the cursor so the calltip can't.
|
||
|
// This view does not become key so resetCursorRects never called.
|
||
|
- (void) resetCursorRects {
|
||
|
//[super resetCursorRects];
|
||
|
//[self addCursorRect: [self bounds] cursor: [NSCursor arrowCursor]];
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
|
||
|
void ScintillaCocoa::CallTipMouseDown(NSPoint pt) {
|
||
|
NSRect rectBounds = [(NSView *)(ct.wDraw.GetID()) bounds];
|
||
|
Point location(static_cast<XYPOSITION>(pt.x),
|
||
|
static_cast<XYPOSITION>(rectBounds.size.height - pt.y));
|
||
|
ct.MouseClick(location);
|
||
|
CallTipClick();
|
||
|
}
|
||
|
|
||
|
static bool HeightDifferent(WindowID wCallTip, PRectangle rc) {
|
||
|
NSWindow *callTip = (NSWindow *)wCallTip;
|
||
|
CGFloat height = NSHeight([callTip frame]);
|
||
|
return height != rc.Height();
|
||
|
}
|
||
|
|
||
|
void ScintillaCocoa::CreateCallTipWindow(PRectangle rc) {
|
||
|
if (ct.wCallTip.Created() && HeightDifferent(ct.wCallTip.GetID(), rc)) {
|
||
|
ct.wCallTip.Destroy();
|
||
|
}
|
||
|
if (!ct.wCallTip.Created()) {
|
||
|
NSRect ctRect = NSMakeRect(rc.top,rc.bottom, rc.Width(), rc.Height());
|
||
|
NSWindow *callTip = [[NSWindow alloc] initWithContentRect: ctRect
|
||
|
styleMask: NSBorderlessWindowMask
|
||
|
backing: NSBackingStoreBuffered
|
||
|
defer: NO];
|
||
|
[callTip setLevel:NSFloatingWindowLevel];
|
||
|
[callTip setHasShadow:YES];
|
||
|
NSRect ctContent = NSMakeRect(0,0, rc.Width(), rc.Height());
|
||
|
CallTipView *caption = [[CallTipView alloc] initWithFrame: ctContent];
|
||
|
[caption setAutoresizingMask: NSViewWidthSizable | NSViewMaxYMargin];
|
||
|
[caption setSci: this];
|
||
|
[[callTip contentView] addSubview: caption];
|
||
|
[callTip orderFront:caption];
|
||
|
ct.wCallTip = callTip;
|
||
|
ct.wDraw = caption;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ScintillaCocoa::AddToPopUp(const char *label, int cmd, bool enabled)
|
||
|
{
|
||
|
NSMenuItem* item;
|
||
|
ScintillaContextMenu *menu= reinterpret_cast<ScintillaContextMenu*>(popup.GetID());
|
||
|
[menu setOwner: this];
|
||
|
[menu setAutoenablesItems: NO];
|
||
|
|
||
|
if (cmd == 0) {
|
||
|
item = [NSMenuItem separatorItem];
|
||
|
} else {
|
||
|
item = [[[NSMenuItem alloc] init] autorelease];
|
||
|
[item setTitle: [NSString stringWithUTF8String: label]];
|
||
|
}
|
||
|
[item setTarget: menu];
|
||
|
[item setAction: @selector(handleCommand:)];
|
||
|
[item setTag: cmd];
|
||
|
[item setEnabled: enabled];
|
||
|
|
||
|
[menu addItem: item];
|
||
|
}
|
||
|
|
||
|
// -------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::ClaimSelection()
|
||
|
{
|
||
|
// Mac OS X does not have a primary selection.
|
||
|
}
|
||
|
|
||
|
// -------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Returns the current caret position (which is tracked as an offset into the entire text string)
|
||
|
* as a row:column pair. The result is zero-based.
|
||
|
*/
|
||
|
NSPoint ScintillaCocoa::GetCaretPosition()
|
||
|
{
|
||
|
const int line = pdoc->LineFromPosition(sel.RangeMain().caret.Position());
|
||
|
NSPoint result;
|
||
|
|
||
|
result.y = line;
|
||
|
result.x = sel.RangeMain().caret.Position() - pdoc->LineStart(line);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
// -------------------------------------------------------------------------------------------------
|
||
|
|
||
|
#pragma mark Drag
|
||
|
|
||
|
/**
|
||
|
* Triggered by the tick timer on a regular basis to scroll the content during a drag operation.
|
||
|
*/
|
||
|
void ScintillaCocoa::DragScroll()
|
||
|
{
|
||
|
if (!posDrag.IsValid())
|
||
|
{
|
||
|
scrollSpeed = 1;
|
||
|
scrollTicks = 2000;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// TODO: does not work for wrapped lines, fix it.
|
||
|
int line = pdoc->LineFromPosition(posDrag.Position());
|
||
|
int currentVisibleLine = cs.DisplayFromDoc(line);
|
||
|
int lastVisibleLine = Platform::Minimum(topLine + LinesOnScreen(), cs.LinesDisplayed()) - 2;
|
||
|
|
||
|
if (currentVisibleLine <= topLine && topLine > 0)
|
||
|
ScrollTo(topLine - scrollSpeed);
|
||
|
else
|
||
|
if (currentVisibleLine >= lastVisibleLine)
|
||
|
ScrollTo(topLine + scrollSpeed);
|
||
|
else
|
||
|
{
|
||
|
scrollSpeed = 1;
|
||
|
scrollTicks = 2000;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// TODO: also handle horizontal scrolling.
|
||
|
|
||
|
if (scrollSpeed == 1)
|
||
|
{
|
||
|
scrollTicks -= timer.tickSize;
|
||
|
if (scrollTicks <= 0)
|
||
|
{
|
||
|
scrollSpeed = 5;
|
||
|
scrollTicks = 2000;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
//----------------- DragProviderSource -------------------------------------------------------
|
||
|
|
||
|
@interface DragProviderSource : NSObject <NSPasteboardItemDataProvider>
|
||
|
{
|
||
|
SelectionText selectedText;
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
|
||
|
@implementation DragProviderSource
|
||
|
|
||
|
- (id)initWithSelectedText:(const SelectionText *)other
|
||
|
{
|
||
|
self = [super init];
|
||
|
|
||
|
if (self) {
|
||
|
selectedText.Copy(*other);
|
||
|
}
|
||
|
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
- (void)pasteboard:(NSPasteboard *)pasteboard item:(NSPasteboardItem *)item provideDataForType:(NSString *)type
|
||
|
{
|
||
|
if (selectedText.Length() == 0)
|
||
|
return;
|
||
|
|
||
|
if (([type compare: NSPasteboardTypeString] != NSOrderedSame) &&
|
||
|
([type compare: ScintillaRecPboardType] != NSOrderedSame))
|
||
|
return;
|
||
|
|
||
|
CFStringEncoding encoding = EncodingFromCharacterSet(selectedText.codePage == SC_CP_UTF8,
|
||
|
selectedText.characterSet);
|
||
|
CFStringRef cfsVal = CFStringCreateWithBytes(kCFAllocatorDefault,
|
||
|
reinterpret_cast<const UInt8 *>(selectedText.Data()),
|
||
|
selectedText.Length(), encoding, false);
|
||
|
|
||
|
if ([type compare: NSPasteboardTypeString] == NSOrderedSame)
|
||
|
{
|
||
|
[pasteboard setString:(NSString *)cfsVal forType: NSStringPboardType];
|
||
|
}
|
||
|
else if ([type compare: ScintillaRecPboardType] == NSOrderedSame)
|
||
|
{
|
||
|
// This is specific to scintilla, allows us to drag rectangular selections around the document.
|
||
|
if (selectedText.rectangular)
|
||
|
[pasteboard setString:(NSString *)cfsVal forType: ScintillaRecPboardType];
|
||
|
}
|
||
|
|
||
|
if (cfsVal)
|
||
|
CFRelease(cfsVal);
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Called when a drag operation was initiated from within Scintilla.
|
||
|
*/
|
||
|
void ScintillaCocoa::StartDrag()
|
||
|
{
|
||
|
if (sel.Empty())
|
||
|
return;
|
||
|
|
||
|
inDragDrop = ddDragging;
|
||
|
|
||
|
FineTickerStart(tickPlatform, timer.tickSize, 0);
|
||
|
|
||
|
// Put the data to be dragged on the drag pasteboard.
|
||
|
SelectionText selectedText;
|
||
|
NSPasteboard* pasteboard = [NSPasteboard pasteboardWithName: NSDragPboard];
|
||
|
CopySelectionRange(&selectedText);
|
||
|
SetPasteboardData(pasteboard, selectedText);
|
||
|
|
||
|
// calculate the bounds of the selection
|
||
|
PRectangle client = GetTextRectangle();
|
||
|
int selStart = sel.RangeMain().Start().Position();
|
||
|
int selEnd = sel.RangeMain().End().Position();
|
||
|
int startLine = pdoc->LineFromPosition(selStart);
|
||
|
int endLine = pdoc->LineFromPosition(selEnd);
|
||
|
Point pt;
|
||
|
long startPos, endPos, ep;
|
||
|
PRectangle rcSel;
|
||
|
|
||
|
if (startLine==endLine && WndProc(SCI_GETWRAPMODE, 0, 0) != SC_WRAP_NONE) {
|
||
|
// Komodo bug http://bugs.activestate.com/show_bug.cgi?id=87571
|
||
|
// Scintilla bug https://sourceforge.net/tracker/?func=detail&atid=102439&aid=3040200&group_id=2439
|
||
|
// If the width on a wrapped-line selection is negative,
|
||
|
// find a better bounding rectangle.
|
||
|
|
||
|
Point ptStart, ptEnd;
|
||
|
startPos = WndProc(SCI_GETLINESELSTARTPOSITION, startLine, 0);
|
||
|
endPos = WndProc(SCI_GETLINESELENDPOSITION, startLine, 0);
|
||
|
// step back a position if we're counting the newline
|
||
|
ep = WndProc(SCI_GETLINEENDPOSITION, startLine, 0);
|
||
|
if (endPos > ep) endPos = ep;
|
||
|
ptStart = LocationFromPosition(static_cast<int>(startPos));
|
||
|
ptEnd = LocationFromPosition(static_cast<int>(endPos));
|
||
|
if (ptStart.y == ptEnd.y) {
|
||
|
// We're just selecting part of one visible line
|
||
|
rcSel.left = ptStart.x;
|
||
|
rcSel.right = ptEnd.x < client.right ? ptEnd.x : client.right;
|
||
|
} else {
|
||
|
// Find the bounding box.
|
||
|
startPos = WndProc(SCI_POSITIONFROMLINE, startLine, 0);
|
||
|
rcSel.left = LocationFromPosition(static_cast<int>(startPos)).x;
|
||
|
rcSel.right = client.right;
|
||
|
}
|
||
|
rcSel.top = ptStart.y;
|
||
|
rcSel.bottom = ptEnd.y + vs.lineHeight;
|
||
|
if (rcSel.bottom > client.bottom) {
|
||
|
rcSel.bottom = client.bottom;
|
||
|
}
|
||
|
} else {
|
||
|
rcSel.top = rcSel.bottom = rcSel.right = rcSel.left = -1;
|
||
|
for (int l = startLine; l <= endLine; l++) {
|
||
|
startPos = WndProc(SCI_GETLINESELSTARTPOSITION, l, 0);
|
||
|
endPos = WndProc(SCI_GETLINESELENDPOSITION, l, 0);
|
||
|
if (endPos == startPos) continue;
|
||
|
// step back a position if we're counting the newline
|
||
|
ep = WndProc(SCI_GETLINEENDPOSITION, l, 0);
|
||
|
if (endPos > ep) endPos = ep;
|
||
|
pt = LocationFromPosition(static_cast<int>(startPos)); // top left of line selection
|
||
|
if (pt.x < rcSel.left || rcSel.left < 0) rcSel.left = pt.x;
|
||
|
if (pt.y < rcSel.top || rcSel.top < 0) rcSel.top = pt.y;
|
||
|
pt = LocationFromPosition(static_cast<int>(endPos)); // top right of line selection
|
||
|
pt.y += vs.lineHeight; // get to the bottom of the line
|
||
|
if (pt.x > rcSel.right || rcSel.right < 0) {
|
||
|
if (pt.x > client.right)
|
||
|
rcSel.right = client.right;
|
||
|
else
|
||
|
rcSel.right = pt.x;
|
||
|
}
|
||
|
if (pt.y > rcSel.bottom || rcSel.bottom < 0) {
|
||
|
if (pt.y > client.bottom)
|
||
|
rcSel.bottom = client.bottom;
|
||
|
else
|
||
|
rcSel.bottom = pt.y;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// must convert to global coordinates for drag regions, but also save the
|
||
|
// image rectangle for further calculations and copy operations
|
||
|
|
||
|
// Prepare drag image.
|
||
|
NSRect selectionRectangle = PRectangleToNSRect(rcSel);
|
||
|
|
||
|
SCIContentView* content = ContentView();
|
||
|
|
||
|
// To get a bitmap of the text we're dragging, we just use Paint on a pixmap surface.
|
||
|
SurfaceImpl *sw = new SurfaceImpl();
|
||
|
SurfaceImpl *pixmap = NULL;
|
||
|
|
||
|
bool lastHideSelection = view.hideSelection;
|
||
|
view.hideSelection = true;
|
||
|
if (sw)
|
||
|
{
|
||
|
pixmap = new SurfaceImpl();
|
||
|
if (pixmap)
|
||
|
{
|
||
|
PRectangle imageRect = rcSel;
|
||
|
paintState = painting;
|
||
|
sw->InitPixMap(static_cast<int>(client.Width()), static_cast<int>(client.Height()), NULL, NULL);
|
||
|
paintingAllText = true;
|
||
|
// Have to create a new context and make current as text drawing goes
|
||
|
// to the current context, not a passed context.
|
||
|
CGContextRef gcsw = sw->GetContext();
|
||
|
NSGraphicsContext *nsgc = [NSGraphicsContext graphicsContextWithGraphicsPort: gcsw
|
||
|
flipped: YES];
|
||
|
[NSGraphicsContext setCurrentContext:nsgc];
|
||
|
CGContextTranslateCTM(gcsw, -client.left, -client.top);
|
||
|
Paint(sw, client);
|
||
|
paintState = notPainting;
|
||
|
|
||
|
pixmap->InitPixMap(static_cast<int>(imageRect.Width()), static_cast<int>(imageRect.Height()), NULL, NULL);
|
||
|
|
||
|
CGContextRef gc = pixmap->GetContext();
|
||
|
// To make Paint() work on a bitmap, we have to flip our coordinates and translate the origin
|
||
|
CGContextTranslateCTM(gc, 0, imageRect.Height());
|
||
|
CGContextScaleCTM(gc, 1.0, -1.0);
|
||
|
|
||
|
pixmap->CopyImageRectangle(*sw, imageRect, PRectangle(0.0f, 0.0f, imageRect.Width(), imageRect.Height()));
|
||
|
// XXX TODO: overwrite any part of the image that is not part of the
|
||
|
// selection to make it transparent. right now we just use
|
||
|
// the full rectangle which may include non-selected text.
|
||
|
}
|
||
|
sw->Release();
|
||
|
delete sw;
|
||
|
}
|
||
|
view.hideSelection = lastHideSelection;
|
||
|
|
||
|
NSBitmapImageRep* bitmap = NULL;
|
||
|
if (pixmap)
|
||
|
{
|
||
|
CGImageRef imagePixmap = pixmap->GetImage();
|
||
|
if (imagePixmap)
|
||
|
bitmap = [[[NSBitmapImageRep alloc] initWithCGImage: imagePixmap] autorelease];
|
||
|
CGImageRelease(imagePixmap);
|
||
|
pixmap->Release();
|
||
|
delete pixmap;
|
||
|
}
|
||
|
|
||
|
NSImage* image = [[[NSImage alloc] initWithSize: selectionRectangle.size] autorelease];
|
||
|
[image addRepresentation: bitmap];
|
||
|
|
||
|
NSImage* dragImage = [[[NSImage alloc] initWithSize: selectionRectangle.size] autorelease];
|
||
|
[dragImage setBackgroundColor: [NSColor clearColor]];
|
||
|
[dragImage lockFocus];
|
||
|
[image drawAtPoint: NSZeroPoint fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 0.5];
|
||
|
[dragImage unlockFocus];
|
||
|
|
||
|
NSPoint startPoint;
|
||
|
startPoint.x = selectionRectangle.origin.x + client.left;
|
||
|
startPoint.y = selectionRectangle.origin.y + selectionRectangle.size.height + client.top;
|
||
|
|
||
|
NSPasteboardItem *pbItem = [NSPasteboardItem new];
|
||
|
DragProviderSource *dps = [[[DragProviderSource alloc] initWithSelectedText:&selectedText] autorelease];
|
||
|
|
||
|
NSArray *pbTypes = selectedText.rectangular ?
|
||
|
@[NSPasteboardTypeString, ScintillaRecPboardType] :
|
||
|
@[NSPasteboardTypeString];
|
||
|
[pbItem setDataProvider:dps forTypes:pbTypes];
|
||
|
NSDraggingItem *dragItem = [[NSDraggingItem alloc ]initWithPasteboardWriter:pbItem];
|
||
|
[pbItem release];
|
||
|
|
||
|
NSScrollView *scrollContainer = ScrollContainer();
|
||
|
NSRect contentRect = [[scrollContainer contentView] bounds];
|
||
|
NSRect draggingRect = NSOffsetRect(selectionRectangle, contentRect.origin.x, contentRect.origin.y);
|
||
|
[dragItem setDraggingFrame:draggingRect contents:dragImage];
|
||
|
NSDraggingSession *dragSession =
|
||
|
[content beginDraggingSessionWithItems:@[[dragItem autorelease]]
|
||
|
event:lastMouseEvent
|
||
|
source:content];
|
||
|
dragSession.animatesToStartingPositionsOnCancelOrFail = YES;
|
||
|
dragSession.draggingFormation = NSDraggingFormationNone;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Called when a drag operation reaches the control which was initiated outside.
|
||
|
*/
|
||
|
NSDragOperation ScintillaCocoa::DraggingEntered(id <NSDraggingInfo> info)
|
||
|
{
|
||
|
FineTickerStart(tickPlatform, timer.tickSize, 0);
|
||
|
return DraggingUpdated(info);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Called frequently during a drag operation if we are the target. Keep telling the caller
|
||
|
* what drag operation we accept and update the drop caret position to indicate the
|
||
|
* potential insertion point of the dragged data.
|
||
|
*/
|
||
|
NSDragOperation ScintillaCocoa::DraggingUpdated(id <NSDraggingInfo> info)
|
||
|
{
|
||
|
// Convert the drag location from window coordinates to view coordinates and
|
||
|
// from there to a text position to finally set the drag position.
|
||
|
Point location = ConvertPoint([info draggingLocation]);
|
||
|
SetDragPosition(SPositionFromLocation(location));
|
||
|
|
||
|
NSDragOperation sourceDragMask = [info draggingSourceOperationMask];
|
||
|
if (sourceDragMask == NSDragOperationNone)
|
||
|
return sourceDragMask;
|
||
|
|
||
|
NSPasteboard* pasteboard = [info draggingPasteboard];
|
||
|
|
||
|
// Return what type of operation we will perform. Prefer move over copy.
|
||
|
if ([[pasteboard types] containsObject: NSStringPboardType] ||
|
||
|
[[pasteboard types] containsObject: ScintillaRecPboardType])
|
||
|
return (sourceDragMask & NSDragOperationMove) ? NSDragOperationMove : NSDragOperationCopy;
|
||
|
|
||
|
if ([[pasteboard types] containsObject: NSFilenamesPboardType])
|
||
|
return (sourceDragMask & NSDragOperationGeneric);
|
||
|
return NSDragOperationNone;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Resets the current drag position as we are no longer the drag target.
|
||
|
*/
|
||
|
void ScintillaCocoa::DraggingExited(id <NSDraggingInfo> info)
|
||
|
{
|
||
|
#pragma unused(info)
|
||
|
SetDragPosition(SelectionPosition(invalidPosition));
|
||
|
FineTickerCancel(tickPlatform);
|
||
|
inDragDrop = ddNone;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Here is where the real work is done. Insert the text from the pasteboard.
|
||
|
*/
|
||
|
bool ScintillaCocoa::PerformDragOperation(id <NSDraggingInfo> info)
|
||
|
{
|
||
|
NSPasteboard* pasteboard = [info draggingPasteboard];
|
||
|
|
||
|
if ([[pasteboard types] containsObject: NSFilenamesPboardType])
|
||
|
{
|
||
|
NSArray* files = [pasteboard propertyListForType: NSFilenamesPboardType];
|
||
|
for (NSString* uri in files)
|
||
|
NotifyURIDropped([uri UTF8String]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SelectionText text;
|
||
|
GetPasteboardData(pasteboard, &text);
|
||
|
|
||
|
if (text.Length() > 0)
|
||
|
{
|
||
|
NSDragOperation operation = [info draggingSourceOperationMask];
|
||
|
bool moving = (operation & NSDragOperationMove) != 0;
|
||
|
|
||
|
DropAt(posDrag, text.Data(), text.Length(), moving, text.rectangular);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::SetPasteboardData(NSPasteboard* board, const SelectionText &selectedText)
|
||
|
{
|
||
|
if (selectedText.Length() == 0)
|
||
|
return;
|
||
|
|
||
|
CFStringEncoding encoding = EncodingFromCharacterSet(selectedText.codePage == SC_CP_UTF8,
|
||
|
selectedText.characterSet);
|
||
|
CFStringRef cfsVal = CFStringCreateWithBytes(kCFAllocatorDefault,
|
||
|
reinterpret_cast<const UInt8 *>(selectedText.Data()),
|
||
|
selectedText.Length(), encoding, false);
|
||
|
|
||
|
NSArray *pbTypes = selectedText.rectangular ?
|
||
|
[NSArray arrayWithObjects: NSStringPboardType, ScintillaRecPboardType, nil] :
|
||
|
[NSArray arrayWithObjects: NSStringPboardType, nil];
|
||
|
[board declareTypes:pbTypes owner:nil];
|
||
|
|
||
|
if (selectedText.rectangular)
|
||
|
{
|
||
|
// This is specific to scintilla, allows us to drag rectangular selections around the document.
|
||
|
[board setString: (NSString *)cfsVal forType: ScintillaRecPboardType];
|
||
|
}
|
||
|
|
||
|
[board setString: (NSString *)cfsVal forType: NSStringPboardType];
|
||
|
|
||
|
if (cfsVal)
|
||
|
CFRelease(cfsVal);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Helper method to retrieve the best fitting alternative from the general pasteboard.
|
||
|
*/
|
||
|
bool ScintillaCocoa::GetPasteboardData(NSPasteboard* board, SelectionText* selectedText)
|
||
|
{
|
||
|
NSArray* supportedTypes = [NSArray arrayWithObjects: ScintillaRecPboardType,
|
||
|
NSStringPboardType,
|
||
|
nil];
|
||
|
NSString *bestType = [board availableTypeFromArray: supportedTypes];
|
||
|
NSString* data = [board stringForType: bestType];
|
||
|
|
||
|
if (data != nil)
|
||
|
{
|
||
|
if (selectedText != nil)
|
||
|
{
|
||
|
CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
|
||
|
vs.styles[STYLE_DEFAULT].characterSet);
|
||
|
CFRange rangeAll = {0, static_cast<CFIndex>([data length])};
|
||
|
CFIndex usedLen = 0;
|
||
|
CFStringGetBytes((CFStringRef)data, rangeAll, encoding, '?',
|
||
|
false, NULL, 0, &usedLen);
|
||
|
|
||
|
std::vector<UInt8> buffer(usedLen);
|
||
|
|
||
|
CFStringGetBytes((CFStringRef)data, rangeAll, encoding, '?',
|
||
|
false, buffer.data(),usedLen, NULL);
|
||
|
|
||
|
bool rectangular = bestType == ScintillaRecPboardType;
|
||
|
|
||
|
std::string dest(reinterpret_cast<const char *>(buffer.data()), usedLen);
|
||
|
|
||
|
selectedText->Copy(dest, pdoc->dbcsCodePage,
|
||
|
vs.styles[STYLE_DEFAULT].characterSet , rectangular, false);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
// Returns the target converted to UTF8.
|
||
|
// Return the length in bytes.
|
||
|
int ScintillaCocoa::TargetAsUTF8(char *text)
|
||
|
{
|
||
|
const int targetLength = targetEnd - targetStart;
|
||
|
if (IsUnicodeMode())
|
||
|
{
|
||
|
if (text)
|
||
|
pdoc->GetCharRange(text, targetStart, targetLength);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Need to convert
|
||
|
const CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
|
||
|
vs.styles[STYLE_DEFAULT].characterSet);
|
||
|
const std::string s = RangeText(targetStart, targetEnd);
|
||
|
CFStringRef cfsVal = CFStringCreateWithBytes(kCFAllocatorDefault,
|
||
|
reinterpret_cast<const UInt8 *>(s.c_str()),
|
||
|
s.length(), encoding, false);
|
||
|
|
||
|
const std::string tmputf = EncodedBytesString(cfsVal, kCFStringEncodingUTF8);
|
||
|
|
||
|
if (text)
|
||
|
memcpy(text, tmputf.c_str(), tmputf.length());
|
||
|
CFRelease(cfsVal);
|
||
|
return tmputf.length();
|
||
|
}
|
||
|
return targetLength;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
// Translates a UTF8 string into the document encoding.
|
||
|
// Return the length of the result in bytes.
|
||
|
int ScintillaCocoa::EncodedFromUTF8(char *utf8, char *encoded) const
|
||
|
{
|
||
|
const int inputLength = (lengthForEncode >= 0) ? lengthForEncode : strlen(utf8);
|
||
|
if (IsUnicodeMode())
|
||
|
{
|
||
|
if (encoded)
|
||
|
memcpy(encoded, utf8, inputLength);
|
||
|
return inputLength;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Need to convert
|
||
|
const CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
|
||
|
vs.styles[STYLE_DEFAULT].characterSet);
|
||
|
|
||
|
CFStringRef cfsVal = CFStringCreateWithBytes(kCFAllocatorDefault,
|
||
|
reinterpret_cast<const UInt8 *>(utf8),
|
||
|
inputLength, kCFStringEncodingUTF8, false);
|
||
|
const std::string sEncoded = EncodedBytesString(cfsVal, encoding);
|
||
|
if (encoded)
|
||
|
memcpy(encoded, sEncoded.c_str(), sEncoded.length());
|
||
|
CFRelease(cfsVal);
|
||
|
return sEncoded.length();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::SetMouseCapture(bool on)
|
||
|
{
|
||
|
capturedMouse = on;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
bool ScintillaCocoa::HaveMouseCapture()
|
||
|
{
|
||
|
return capturedMouse;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Synchronously paint a rectangle of the window.
|
||
|
*/
|
||
|
bool ScintillaCocoa::SyncPaint(void* gc, PRectangle rc)
|
||
|
{
|
||
|
paintState = painting;
|
||
|
rcPaint = rc;
|
||
|
PRectangle rcText = GetTextRectangle();
|
||
|
paintingAllText = rcPaint.Contains(rcText);
|
||
|
bool succeeded = true;
|
||
|
Surface *sw = Surface::Allocate(SC_TECHNOLOGY_DEFAULT);
|
||
|
if (sw)
|
||
|
{
|
||
|
CGContextSetAllowsAntialiasing((CGContextRef)gc,
|
||
|
vs.extraFontFlag != SC_EFF_QUALITY_NON_ANTIALIASED);
|
||
|
CGContextSetAllowsFontSmoothing((CGContextRef)gc,
|
||
|
vs.extraFontFlag == SC_EFF_QUALITY_LCD_OPTIMIZED);
|
||
|
CGContextSetAllowsFontSubpixelPositioning((CGContextRef)gc,
|
||
|
vs.extraFontFlag == SC_EFF_QUALITY_DEFAULT ||
|
||
|
vs.extraFontFlag == SC_EFF_QUALITY_LCD_OPTIMIZED);
|
||
|
sw->Init(gc, wMain.GetID());
|
||
|
Paint(sw, rc);
|
||
|
succeeded = paintState != paintAbandoned;
|
||
|
sw->Release();
|
||
|
delete sw;
|
||
|
}
|
||
|
paintState = notPainting;
|
||
|
if (!succeeded)
|
||
|
{
|
||
|
NSView *marginView = static_cast<NSView*>(wMargin.GetID());
|
||
|
[marginView setNeedsDisplay:YES];
|
||
|
}
|
||
|
return succeeded;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Paint the margin into the SCIMarginView space.
|
||
|
*/
|
||
|
void ScintillaCocoa::PaintMargin(NSRect aRect)
|
||
|
{
|
||
|
CGContextRef gc = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
|
||
|
|
||
|
PRectangle rc = NSRectToPRectangle(aRect);
|
||
|
rcPaint = rc;
|
||
|
Surface *sw = Surface::Allocate(SC_TECHNOLOGY_DEFAULT);
|
||
|
if (sw)
|
||
|
{
|
||
|
CGContextSetAllowsAntialiasing(gc,
|
||
|
vs.extraFontFlag != SC_EFF_QUALITY_NON_ANTIALIASED);
|
||
|
CGContextSetAllowsFontSmoothing(gc,
|
||
|
vs.extraFontFlag == SC_EFF_QUALITY_LCD_OPTIMIZED);
|
||
|
CGContextSetAllowsFontSubpixelPositioning(gc,
|
||
|
vs.extraFontFlag == SC_EFF_QUALITY_DEFAULT ||
|
||
|
vs.extraFontFlag == SC_EFF_QUALITY_LCD_OPTIMIZED);
|
||
|
sw->Init(gc, wMargin.GetID());
|
||
|
PaintSelMargin(sw, rc);
|
||
|
sw->Release();
|
||
|
delete sw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Prepare for drawing.
|
||
|
*
|
||
|
* @param rect The area that will be drawn, given in the sender's coordinate system.
|
||
|
*/
|
||
|
void ScintillaCocoa::WillDraw(NSRect rect)
|
||
|
{
|
||
|
RefreshStyleData();
|
||
|
PRectangle rcWillDraw = NSRectToPRectangle(rect);
|
||
|
int positionAfterRect = PositionAfterArea(rcWillDraw);
|
||
|
pdoc->EnsureStyledTo(positionAfterRect);
|
||
|
NotifyUpdateUI();
|
||
|
if (WrapLines(wsVisible)) {
|
||
|
// Wrap may have reduced number of lines so more lines may need to be styled
|
||
|
positionAfterRect = PositionAfterArea(rcWillDraw);
|
||
|
pdoc->EnsureStyledTo(positionAfterRect);
|
||
|
// The wrapping process has changed the height of some lines so redraw all.
|
||
|
Redraw();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* ScrollText is empty because scrolling is handled by the NSScrollView.
|
||
|
*/
|
||
|
void ScintillaCocoa::ScrollText(int)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Modifies the vertical scroll position to make the current top line show up as such.
|
||
|
*/
|
||
|
void ScintillaCocoa::SetVerticalScrollPos()
|
||
|
{
|
||
|
NSScrollView *scrollView = ScrollContainer();
|
||
|
if (scrollView) {
|
||
|
NSClipView *clipView = [scrollView contentView];
|
||
|
NSRect contentRect = [clipView bounds];
|
||
|
[clipView scrollToPoint: NSMakePoint(contentRect.origin.x, topLine * vs.lineHeight)];
|
||
|
[scrollView reflectScrolledClipView:clipView];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Modifies the horizontal scroll position to match xOffset.
|
||
|
*/
|
||
|
void ScintillaCocoa::SetHorizontalScrollPos()
|
||
|
{
|
||
|
PRectangle textRect = GetTextRectangle();
|
||
|
|
||
|
int maxXOffset = scrollWidth - static_cast<int>(textRect.Width());
|
||
|
if (maxXOffset < 0)
|
||
|
maxXOffset = 0;
|
||
|
if (xOffset > maxXOffset)
|
||
|
xOffset = maxXOffset;
|
||
|
NSScrollView *scrollView = ScrollContainer();
|
||
|
if (scrollView) {
|
||
|
NSClipView * clipView = [scrollView contentView];
|
||
|
NSRect contentRect = [clipView bounds];
|
||
|
[clipView scrollToPoint: NSMakePoint(xOffset, contentRect.origin.y)];
|
||
|
[scrollView reflectScrolledClipView:clipView];
|
||
|
}
|
||
|
MoveFindIndicatorWithBounce(NO);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Used to adjust both scrollers to reflect the current scroll range and position in the editor.
|
||
|
* Arguments no longer used as NSScrollView handles details of scroll bar sizes.
|
||
|
*
|
||
|
* @param nMax Number of lines in the editor.
|
||
|
* @param nPage Number of lines per scroll page.
|
||
|
* @return True if there was a change, otherwise false.
|
||
|
*/
|
||
|
bool ScintillaCocoa::ModifyScrollBars(int nMax, int nPage)
|
||
|
{
|
||
|
#pragma unused(nMax, nPage)
|
||
|
return SetScrollingSize();
|
||
|
}
|
||
|
|
||
|
bool ScintillaCocoa::SetScrollingSize(void) {
|
||
|
bool changes = false;
|
||
|
SCIContentView *inner = ContentView();
|
||
|
if (!enteredSetScrollingSize) {
|
||
|
enteredSetScrollingSize = true;
|
||
|
NSScrollView *scrollView = ScrollContainer();
|
||
|
NSClipView *clipView = [ScrollContainer() contentView];
|
||
|
NSRect clipRect = [clipView bounds];
|
||
|
CGFloat docHeight = cs.LinesDisplayed() * vs.lineHeight;
|
||
|
if (!endAtLastLine)
|
||
|
docHeight += (int([scrollView bounds].size.height / vs.lineHeight)-3) * vs.lineHeight;
|
||
|
// Allow extra space so that last scroll position places whole line at top
|
||
|
int clipExtra = int(clipRect.size.height) % vs.lineHeight;
|
||
|
docHeight += clipExtra;
|
||
|
// Ensure all of clipRect covered by Scintilla drawing
|
||
|
if (docHeight < clipRect.size.height)
|
||
|
docHeight = clipRect.size.height;
|
||
|
CGFloat docWidth = scrollWidth;
|
||
|
bool showHorizontalScroll = horizontalScrollBarVisible &&
|
||
|
!Wrapping();
|
||
|
if (!showHorizontalScroll)
|
||
|
docWidth = clipRect.size.width;
|
||
|
NSRect contentRect = {{0, 0}, {docWidth, docHeight}};
|
||
|
NSRect contentRectNow = [inner frame];
|
||
|
changes = (contentRect.size.width != contentRectNow.size.width) ||
|
||
|
(contentRect.size.height != contentRectNow.size.height);
|
||
|
if (changes) {
|
||
|
[inner setFrame: contentRect];
|
||
|
}
|
||
|
[scrollView setHasVerticalScroller: verticalScrollBarVisible];
|
||
|
[scrollView setHasHorizontalScroller: showHorizontalScroll];
|
||
|
SetVerticalScrollPos();
|
||
|
enteredSetScrollingSize = false;
|
||
|
}
|
||
|
[inner.owner setMarginWidth: vs.fixedColumnWidth];
|
||
|
return changes;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::Resize()
|
||
|
{
|
||
|
SetScrollingSize();
|
||
|
ChangeSize();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Update fields to match scroll position after receiving a notification that the user has scrolled.
|
||
|
*/
|
||
|
void ScintillaCocoa::UpdateForScroll() {
|
||
|
Point ptOrigin = GetVisibleOriginInMain();
|
||
|
xOffset = static_cast<int>(ptOrigin.x);
|
||
|
int newTop = Platform::Minimum(static_cast<int>(ptOrigin.y / vs.lineHeight), MaxScrollPos());
|
||
|
SetTopLine(newTop);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Register a delegate that will be called for notifications and commands.
|
||
|
* This provides similar functionality to RegisterNotifyCallback but in an
|
||
|
* Objective C way.
|
||
|
*
|
||
|
* @param delegate_ A pointer to an object that implements ScintillaNotificationProtocol.
|
||
|
*/
|
||
|
|
||
|
void ScintillaCocoa::SetDelegate(id<ScintillaNotificationProtocol> delegate_)
|
||
|
{
|
||
|
delegate = delegate_;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Used to register a callback function for a given window. This is used to emulate the way
|
||
|
* Windows notifies other controls (mainly up in the view hierarchy) about certain events.
|
||
|
*
|
||
|
* @param windowid A handle to a window. That value is generic and can be anything. It is passed
|
||
|
* through to the callback.
|
||
|
* @param callback The callback function to be used for future notifications. If NULL then no
|
||
|
* notifications will be sent anymore.
|
||
|
*/
|
||
|
void ScintillaCocoa::RegisterNotifyCallback(intptr_t windowid, SciNotifyFunc callback)
|
||
|
{
|
||
|
notifyObj = windowid;
|
||
|
notifyProc = callback;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::NotifyChange()
|
||
|
{
|
||
|
if (notifyProc != NULL)
|
||
|
notifyProc(notifyObj, WM_COMMAND, Platform::LongFromTwoShorts(static_cast<short>(GetCtrlID()), SCEN_CHANGE),
|
||
|
(uintptr_t) this);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::NotifyFocus(bool focus)
|
||
|
{
|
||
|
if (notifyProc != NULL)
|
||
|
notifyProc(notifyObj, WM_COMMAND, Platform::LongFromTwoShorts(static_cast<short>(GetCtrlID()),
|
||
|
(focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS)),
|
||
|
(uintptr_t) this);
|
||
|
|
||
|
Editor::NotifyFocus(focus);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Used to send a notification (as WM_NOTIFY call) to the procedure, which has been set by the call
|
||
|
* to RegisterNotifyCallback (so it is not necessarily the parent window).
|
||
|
*
|
||
|
* @param scn The notification to send.
|
||
|
*/
|
||
|
void ScintillaCocoa::NotifyParent(SCNotification scn)
|
||
|
{
|
||
|
scn.nmhdr.hwndFrom = (void*) this;
|
||
|
scn.nmhdr.idFrom = GetCtrlID();
|
||
|
if (notifyProc != NULL)
|
||
|
notifyProc(notifyObj, WM_NOTIFY, GetCtrlID(), (uintptr_t) &scn);
|
||
|
if (delegate)
|
||
|
[delegate notification:&scn];
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::NotifyURIDropped(const char *uri)
|
||
|
{
|
||
|
SCNotification scn;
|
||
|
scn.nmhdr.code = SCN_URIDROPPED;
|
||
|
scn.text = uri;
|
||
|
|
||
|
NotifyParent(scn);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
bool ScintillaCocoa::HasSelection()
|
||
|
{
|
||
|
return !sel.Empty();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
bool ScintillaCocoa::CanUndo()
|
||
|
{
|
||
|
return pdoc->CanUndo();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
bool ScintillaCocoa::CanRedo()
|
||
|
{
|
||
|
return pdoc->CanRedo();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::TimerFired(NSTimer* timer)
|
||
|
{
|
||
|
for (TickReason tr=tickCaret; tr<=tickPlatform; tr = static_cast<TickReason>(tr+1))
|
||
|
{
|
||
|
if (timers[tr] == timer)
|
||
|
{
|
||
|
TickFor(tr);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::IdleTimerFired()
|
||
|
{
|
||
|
bool more = Idle();
|
||
|
if (!more)
|
||
|
SetIdle(false);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Main entry point for drawing the control.
|
||
|
*
|
||
|
* @param rect The area to paint, given in the sender's coordinate system.
|
||
|
* @param gc The context we can use to paint.
|
||
|
*/
|
||
|
bool ScintillaCocoa::Draw(NSRect rect, CGContextRef gc)
|
||
|
{
|
||
|
return SyncPaint(gc, NSRectToPRectangle(rect));
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Helper function to translate OS X key codes to Scintilla key codes.
|
||
|
*/
|
||
|
static inline UniChar KeyTranslate(UniChar unicodeChar)
|
||
|
{
|
||
|
switch (unicodeChar)
|
||
|
{
|
||
|
case NSDownArrowFunctionKey:
|
||
|
return SCK_DOWN;
|
||
|
case NSUpArrowFunctionKey:
|
||
|
return SCK_UP;
|
||
|
case NSLeftArrowFunctionKey:
|
||
|
return SCK_LEFT;
|
||
|
case NSRightArrowFunctionKey:
|
||
|
return SCK_RIGHT;
|
||
|
case NSHomeFunctionKey:
|
||
|
return SCK_HOME;
|
||
|
case NSEndFunctionKey:
|
||
|
return SCK_END;
|
||
|
case NSPageUpFunctionKey:
|
||
|
return SCK_PRIOR;
|
||
|
case NSPageDownFunctionKey:
|
||
|
return SCK_NEXT;
|
||
|
case NSDeleteFunctionKey:
|
||
|
return SCK_DELETE;
|
||
|
case NSInsertFunctionKey:
|
||
|
return SCK_INSERT;
|
||
|
case '\n':
|
||
|
case 3:
|
||
|
return SCK_RETURN;
|
||
|
case 27:
|
||
|
return SCK_ESCAPE;
|
||
|
case 127:
|
||
|
return SCK_BACK;
|
||
|
case '\t':
|
||
|
case 25: // Shift tab, return to unmodified tab and handle that via modifiers.
|
||
|
return SCK_TAB;
|
||
|
default:
|
||
|
return unicodeChar;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Translate NSEvent modifier flags into SCI_* modifier flags.
|
||
|
*
|
||
|
* @param modifiers An integer bit set of NSSEvent modifier flags.
|
||
|
* @return A set of SCI_* modifier flags.
|
||
|
*/
|
||
|
static int TranslateModifierFlags(NSUInteger modifiers)
|
||
|
{
|
||
|
// Signal Control as SCI_META
|
||
|
return
|
||
|
(((modifiers & NSShiftKeyMask) != 0) ? SCI_SHIFT : 0) |
|
||
|
(((modifiers & NSCommandKeyMask) != 0) ? SCI_CTRL : 0) |
|
||
|
(((modifiers & NSAlternateKeyMask) != 0) ? SCI_ALT : 0) |
|
||
|
(((modifiers & NSControlKeyMask) != 0) ? SCI_META : 0);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Main keyboard input handling method. It is called for any key down event, including function keys,
|
||
|
* numeric keypad input and whatnot.
|
||
|
*
|
||
|
* @param event The event instance associated with the key down event.
|
||
|
* @return True if the input was handled, false otherwise.
|
||
|
*/
|
||
|
bool ScintillaCocoa::KeyboardInput(NSEvent* event)
|
||
|
{
|
||
|
// For now filter out function keys.
|
||
|
NSString* input = [event characters];
|
||
|
|
||
|
bool handled = false;
|
||
|
|
||
|
// Handle each entry individually. Usually we only have one entry anyway.
|
||
|
for (size_t i = 0; i < input.length; i++)
|
||
|
{
|
||
|
const UniChar originalKey = [input characterAtIndex: i];
|
||
|
UniChar key = KeyTranslate(originalKey);
|
||
|
|
||
|
bool consumed = false; // Consumed as command?
|
||
|
|
||
|
if (KeyDownWithModifiers(key, TranslateModifierFlags([event modifierFlags]), &consumed))
|
||
|
handled = true;
|
||
|
if (consumed)
|
||
|
handled = true;
|
||
|
}
|
||
|
|
||
|
return handled;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Used to insert already processed text provided by the Cocoa text input system.
|
||
|
*/
|
||
|
int ScintillaCocoa::InsertText(NSString* input)
|
||
|
{
|
||
|
CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
|
||
|
vs.styles[STYLE_DEFAULT].characterSet);
|
||
|
std::string encoded = EncodedBytesString((CFStringRef)input, encoding);
|
||
|
|
||
|
if (encoded.length() > 0)
|
||
|
{
|
||
|
AddCharUTF((char*) encoded.c_str(), static_cast<unsigned int>(encoded.length()), false);
|
||
|
}
|
||
|
return static_cast<int>(encoded.length());
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Convert from a range of characters to a range of bytes.
|
||
|
*/
|
||
|
NSRange ScintillaCocoa::PositionsFromCharacters(NSRange range) const
|
||
|
{
|
||
|
long start = pdoc->GetRelativePositionUTF16(0, range.location);
|
||
|
if (start == INVALID_POSITION)
|
||
|
start = pdoc->Length();
|
||
|
long end = pdoc->GetRelativePositionUTF16(start, range.length);
|
||
|
if (end == INVALID_POSITION)
|
||
|
end = pdoc->Length();
|
||
|
return NSMakeRange(start, end - start);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Convert from a range of characters from a range of bytes.
|
||
|
*/
|
||
|
NSRange ScintillaCocoa::CharactersFromPositions(NSRange range) const
|
||
|
{
|
||
|
const long start = pdoc->CountUTF16(0, range.location);
|
||
|
const long len = pdoc->CountUTF16(range.location, NSMaxRange(range));
|
||
|
return NSMakeRange(start, len);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Used to ensure that only one selection is active for input composition as composition
|
||
|
* does not support multi-typing.
|
||
|
*/
|
||
|
void ScintillaCocoa::SelectOnlyMainSelection()
|
||
|
{
|
||
|
sel.SetSelection(sel.RangeMain());
|
||
|
Redraw();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Convert virtual space before selection into real space.
|
||
|
*/
|
||
|
void ScintillaCocoa::ConvertSelectionVirtualSpace()
|
||
|
{
|
||
|
FillVirtualSpace();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Erase all selected text and return whether the selection is now empty.
|
||
|
* The selection may not be empty if the selection contained protected text.
|
||
|
*/
|
||
|
bool ScintillaCocoa::ClearAllSelections()
|
||
|
{
|
||
|
ClearSelection(true);
|
||
|
return sel.Empty();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Start composing for IME.
|
||
|
*/
|
||
|
void ScintillaCocoa::CompositionStart()
|
||
|
{
|
||
|
if (!sel.Empty())
|
||
|
{
|
||
|
NSLog(@"Selection not empty when starting composition");
|
||
|
}
|
||
|
pdoc->TentativeStart();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Commit the IME text.
|
||
|
*/
|
||
|
void ScintillaCocoa::CompositionCommit()
|
||
|
{
|
||
|
pdoc->TentativeCommit();
|
||
|
pdoc->decorations.SetCurrentIndicator(INDIC_IME);
|
||
|
pdoc->DecorationFillRange(0, 0, pdoc->Length());
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Remove the IME text.
|
||
|
*/
|
||
|
void ScintillaCocoa::CompositionUndo()
|
||
|
{
|
||
|
pdoc->TentativeUndo();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
/**
|
||
|
* When switching documents discard any incomplete character composition state as otherwise tries to
|
||
|
* act on the new document.
|
||
|
*/
|
||
|
void ScintillaCocoa::SetDocPointer(Document *document)
|
||
|
{
|
||
|
// Drop input composition.
|
||
|
NSTextInputContext *inctxt = [NSTextInputContext currentInputContext];
|
||
|
[inctxt discardMarkedText];
|
||
|
SCIContentView *inner = ContentView();
|
||
|
[inner unmarkText];
|
||
|
Editor::SetDocPointer(document);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Called by the owning view when the mouse pointer enters the control.
|
||
|
*/
|
||
|
void ScintillaCocoa::MouseEntered(NSEvent* event)
|
||
|
{
|
||
|
if (!HaveMouseCapture())
|
||
|
{
|
||
|
WndProc(SCI_SETCURSOR, (long int)SC_CURSORNORMAL, 0);
|
||
|
|
||
|
// Mouse location is given in screen coordinates and might also be outside of our bounds.
|
||
|
Point location = ConvertPoint([event locationInWindow]);
|
||
|
ButtonMove(location);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::MouseExited(NSEvent* /* event */)
|
||
|
{
|
||
|
// Nothing to do here.
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::MouseDown(NSEvent* event)
|
||
|
{
|
||
|
Point location = ConvertPoint([event locationInWindow]);
|
||
|
NSTimeInterval time = [event timestamp];
|
||
|
bool command = ([event modifierFlags] & NSCommandKeyMask) != 0;
|
||
|
bool shift = ([event modifierFlags] & NSShiftKeyMask) != 0;
|
||
|
bool alt = ([event modifierFlags] & NSAlternateKeyMask) != 0;
|
||
|
|
||
|
ButtonDown(Point(location.x, location.y), (int) (time * 1000), shift, command, alt);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::MouseMove(NSEvent* event)
|
||
|
{
|
||
|
lastMouseEvent = event;
|
||
|
|
||
|
ButtonMoveWithModifiers(ConvertPoint([event locationInWindow]), TranslateModifierFlags([event modifierFlags]));
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::MouseUp(NSEvent* event)
|
||
|
{
|
||
|
NSTimeInterval time = [event timestamp];
|
||
|
bool control = ([event modifierFlags] & NSControlKeyMask) != 0;
|
||
|
|
||
|
ButtonUp(ConvertPoint([event locationInWindow]), (int) (time * 1000), control);
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::MouseWheel(NSEvent* event)
|
||
|
{
|
||
|
bool command = ([event modifierFlags] & NSCommandKeyMask) != 0;
|
||
|
int dY = 0;
|
||
|
|
||
|
// In order to make scrolling with larger offset smoother we scroll less lines the larger the
|
||
|
// delta value is.
|
||
|
if ([event deltaY] < 0)
|
||
|
dY = -(int) sqrt(-10.0 * [event deltaY]);
|
||
|
else
|
||
|
dY = (int) sqrt(10.0 * [event deltaY]);
|
||
|
|
||
|
if (command)
|
||
|
{
|
||
|
// Zoom! We play with the font sizes in the styles.
|
||
|
// Number of steps/line is ignored, we just care if sizing up or down.
|
||
|
if (dY > 0.5)
|
||
|
KeyCommand(SCI_ZOOMIN);
|
||
|
else if (dY < -0.5)
|
||
|
KeyCommand(SCI_ZOOMOUT);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
// Helper methods for NSResponder actions.
|
||
|
|
||
|
void ScintillaCocoa::SelectAll()
|
||
|
{
|
||
|
Editor::SelectAll();
|
||
|
}
|
||
|
|
||
|
void ScintillaCocoa::DeleteBackward()
|
||
|
{
|
||
|
KeyDown(SCK_BACK, false, false, false, nil);
|
||
|
}
|
||
|
|
||
|
void ScintillaCocoa::Cut()
|
||
|
{
|
||
|
Editor::Cut();
|
||
|
}
|
||
|
|
||
|
void ScintillaCocoa::Undo()
|
||
|
{
|
||
|
Editor::Undo();
|
||
|
}
|
||
|
|
||
|
void ScintillaCocoa::Redo()
|
||
|
{
|
||
|
Editor::Redo();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Creates and returns a popup menu, which is then displayed by the Cocoa framework.
|
||
|
*/
|
||
|
NSMenu* ScintillaCocoa::CreateContextMenu(NSEvent* /* event */)
|
||
|
{
|
||
|
// Call ScintillaBase to create the context menu.
|
||
|
ContextMenu(Point(0, 0));
|
||
|
|
||
|
return reinterpret_cast<NSMenu*>(popup.GetID());
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* An intermediate function to forward context menu commands from the menu action handler to
|
||
|
* scintilla.
|
||
|
*/
|
||
|
void ScintillaCocoa::HandleCommand(NSInteger command)
|
||
|
{
|
||
|
Command(static_cast<int>(command));
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::ActiveStateChanged(bool isActive)
|
||
|
{
|
||
|
// If the window is being deactivated, lose the focus and turn off the ticking
|
||
|
if (!isActive) {
|
||
|
DropCaret();
|
||
|
//SetFocusState( false );
|
||
|
FineTickerCancel(tickCaret);
|
||
|
} else {
|
||
|
ShowCaretAtCurrentPosition();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If building with old SDK, need to define version number for 10.8
|
||
|
#ifndef NSAppKitVersionNumber10_8
|
||
|
#define NSAppKitVersionNumber10_8 1187
|
||
|
#endif
|
||
|
|
||
|
//--------------------------------------------------------------------------------------------------
|
||
|
|
||
|
void ScintillaCocoa::ShowFindIndicatorForRange(NSRange charRange, BOOL retaining)
|
||
|
{
|
||
|
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
|
||
|
NSView *content = ContentView();
|
||
|
if (!layerFindIndicator)
|
||
|
{
|
||
|
layerFindIndicator = [[FindHighlightLayer alloc] init];
|
||
|
[content setWantsLayer: YES];
|
||
|
layerFindIndicator.geometryFlipped = content.layer.geometryFlipped;
|
||
|
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_8)
|
||
|
{
|
||
|
// Content layer is unflipped on 10.9, but the indicator shows wrong unless flipped
|
||
|
layerFindIndicator.geometryFlipped = 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], static_cast<int>(charRange.location), static_cast<int>(charRange.length));
|
||
|
|
||
|
CFStringRef cfsFind = CFStringCreateWithBytes(kCFAllocatorDefault,
|
||
|
reinterpret_cast<const UInt8 *>(&buffer[0]),
|
||
|
charRange.length, encoding, false);
|
||
|
layerFindIndicator.sFind = (NSString *)cfsFind;
|
||
|
if (cfsFind)
|
||
|
CFRelease(cfsFind);
|
||
|
layerFindIndicator.retaining = retaining;
|
||
|
layerFindIndicator.positionFind = static_cast<int>(charRange.location);
|
||
|
long 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];
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void ScintillaCocoa::MoveFindIndicatorWithBounce(BOOL bounce)
|
||
|
{
|
||
|
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
|
||
|
if (layerFindIndicator)
|
||
|
{
|
||
|
CGPoint ptText = CGPointMake(
|
||
|
WndProc(SCI_POINTXFROMPOSITION, 0, layerFindIndicator.positionFind),
|
||
|
WndProc(SCI_POINTYFROMPOSITION, 0, layerFindIndicator.positionFind));
|
||
|
ptText.x = ptText.x - vs.fixedColumnWidth + xOffset;
|
||
|
ptText.y += topLine * vs.lineHeight;
|
||
|
if (!layerFindIndicator.geometryFlipped)
|
||
|
{
|
||
|
NSView *content = ContentView();
|
||
|
ptText.y = content.bounds.size.height - ptText.y;
|
||
|
}
|
||
|
[layerFindIndicator animateMatch:ptText bounce:bounce];
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void ScintillaCocoa::HideFindIndicator()
|
||
|
{
|
||
|
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
|
||
|
if (layerFindIndicator)
|
||
|
{
|
||
|
[layerFindIndicator hideMatch];
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|