notepad-plus-plus-legacy/scintilla/cocoa/ScintillaCocoa.mm
Don Ho 10dc4e06d7 [UPDATE] Update Scintilla to 2.27.
git-svn-id: svn://svn.tuxfamily.org/svnroot/notepadplus/repository/trunk@791 f5eea248-9336-0410-98b8-ebc06183d4e3
2011-07-17 22:30:49 +00:00

1679 lines
55 KiB
Plaintext

/**
* 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>
#import <Carbon/Carbon.h> // Temporary
#include "ScintillaView.h"
#include "PlatCocoa.h"
using namespace Scintilla;
#ifndef WM_UNICHAR
#define WM_UNICHAR 0x0109
#endif
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_META ( SCMOD_META )
#define SCI_SMETA ( SCI_META | SCI_SHIFT)
static const KeyToCommand macMapDefault[] =
{
// OS X specific
{SCK_DOWN, SCI_CMD, SCI_DOCUMENTEND},
{SCK_UP, SCI_CMD, SCI_DOCUMENTSTART},
{SCK_LEFT, SCI_CMD, SCI_VCHOME},
{SCK_LEFT, SCI_SCMD, SCI_VCHOMEEXTEND},
{SCK_RIGHT, SCI_CMD, SCI_LINEEND},
{SCK_RIGHT, SCI_SCMD, 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_UNDO},
{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},
};
//--------------------------------------------------------------------------------------------------
@implementation TimerTarget
- (id) init: (void*) target
{
[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
{
[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
{
// 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
{
reinterpret_cast<ScintillaCocoa*>(mTarget)->IdleTimerFired();
}
@end
//----------------- ScintillaCocoa -----------------------------------------------------------------
ScintillaCocoa::ScintillaCocoa(NSView* view)
{
wMain= [view retain];
timerTarget = [[[TimerTarget alloc] init: this] retain];
Initialise();
}
//--------------------------------------------------------------------------------------------------
ScintillaCocoa::~ScintillaCocoa()
{
SetTicking(false);
[timerTarget release];
NSView* container = ContentView();
[container release];
}
//--------------------------------------------------------------------------------------------------
/**
* Core initialization of the control. Everything that needs to be set up happens here.
*/
void ScintillaCocoa::Initialise()
{
static bool initedLexers = false;
if (!initedLexers)
{
initedLexers = true;
Scintilla_LinkLexers();
}
notifyObj = NULL;
notifyProc = NULL;
capturedMouse = false;
// 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()
{
SetTicking(false);
ScintillaBase::Finalise();
}
//--------------------------------------------------------------------------------------------------
/**
* 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;
}
//--------------------------------------------------------------------------------------------------
/**
* Case folders.
*/
class CaseFolderUTF8 : public CaseFolderTable {
public:
CaseFolderUTF8() {
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, kCFStringEncodingUTF8, false);
NSString *sMapped = [(NSString *)cfsVal stringByFoldingWithOptions:NSCaseInsensitiveSearch
locale:[NSLocale currentLocale]];
const char *cpMapped = [sMapped UTF8String];
size_t lenMapped = strlen(cpMapped);
if (lenMapped < sizeFolded) {
memcpy(folded, cpMapped, lenMapped);
} else {
lenMapped = 0;
}
CFRelease(cfsVal);
return lenMapped;
}
}
};
class CaseFolderDBCS : public CaseFolderTable {
CFStringEncoding encoding;
public:
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;
}
// Something failed so return a single NUL byte
folded[0] = '\0';
return 1;
}
};
CaseFolder *ScintillaCocoa::CaseFolderForEncoding() {
if (pdoc->dbcsCodePage == SC_CP_UTF8) {
return new CaseFolderUTF8();
} 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] = i;
CFStringRef cfsVal = CFStringCreateWithBytes(kCFAllocatorDefault,
reinterpret_cast<const UInt8 *>(sCharacter),
1, encoding, false);
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)
{
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 copy];
}
// Back to encoding
char *encoded = EncodedBytes((CFStringRef)sMapped, encoding);
std::string result(encoded);
delete []encoded;
CFRelease(cfsVal);
return result;
}
//--------------------------------------------------------------------------------------------------
/**
* Helper function to get the outer container which represents the Scintilla editor on application side.
*/
ScintillaView* ScintillaCocoa::TopContainer()
{
NSView* container = static_cast<NSView*>(wMain.GetID());
return static_cast<ScintillaView*>([container superview]);
}
//--------------------------------------------------------------------------------------------------
/**
* Helper function to get the inner container which represents the actual "canvas" we work with.
*/
NSView* ScintillaCocoa::ContentView()
{
return static_cast<NSView*>(wMain.GetID());
}
//--------------------------------------------------------------------------------------------------
/**
* 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.
*/
PRectangle ScintillaCocoa::GetClientRectangle()
{
NSView* host = ContentView();
NSSize size = [host frame].size;
return PRectangle(0, 0, size.width, size.height);
}
//--------------------------------------------------------------------------------------------------
/**
* 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.
*/
Scintilla::Point ScintillaCocoa::ConvertPoint(NSPoint point)
{
NSView* container = ContentView();
NSPoint result = [container convertPoint: point fromView: nil];
return Point(result.x, result.y);
}
//--------------------------------------------------------------------------------------------------
/**
* A function to directly execute code that would usually go the long way via window messages.
* However this is a Windows metapher and not used here, hence we just call our fake
* window proc. The given parameters directly reflect the message parameters used on Windows.
*
* @param sciThis 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(ScintillaCocoa *sciThis, unsigned int iMessage, uptr_t wParam,
sptr_t lParam)
{
return sciThis->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);
ScintillaCocoa* scintilla = [control backend];
return scintilla->WndProc(iMessage, wParam, 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 sytem 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_GRABFOCUS:
[[ContentView() window] makeFirstResponder:ContentView()];
break;
break;
case WM_UNICHAR:
// Special case not used normally. Characters passed in this way will be inserted
// regardless of their value or modifier states. That means no command interpretation is
// performed.
if (IsUnicodeMode())
{
NSString* input = [[NSString stringWithCharacters: (const unichar*) &wParam length: 1] autorelease];
const char* utf8 = [input UTF8String];
AddCharUTF((char*) utf8, strlen(utf8), false);
return 1;
}
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;
}
//--------------------------------------------------------------------------------------------------
/**
* Enables or disables a timer that can trigger background processing at a regular interval, like
* drag scrolling or caret blinking.
*/
void ScintillaCocoa::SetTicking(bool on)
{
if (timer.ticking != on)
{
timer.ticking = on;
if (timer.ticking)
{
// Scintilla ticks = milliseconds
// Using userInfo as flag to distinct between tick and idle timer.
NSTimer* tickTimer = [NSTimer scheduledTimerWithTimeInterval: timer.tickSize / 1000.0
target: timerTarget
selector: @selector(timerFired:)
userInfo: nil
repeats: YES];
timer.tickerID = reinterpret_cast<TickerID>(tickTimer);
}
else
if (timer.tickerID != NULL)
{
[reinterpret_cast<NSTimer*>(timer.tickerID) invalidate];
timer.tickerID = 0;
}
}
timer.ticksToWait = caret.period;
}
//--------------------------------------------------------------------------------------------------
bool ScintillaCocoa::SetIdle(bool on)
{
if (idler.state != on)
{
idler.state = on;
if (idler.state)
{
// Scintilla ticks = milliseconds
NSTimer* 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.s)
// No data or no flavor we support.
return;
pdoc->BeginUndoAction();
ClearSelection(false);
int length = selectedText.len - 1; // One less to avoid inserting the terminating 0 character.
if (selectedText.rectangular)
{
SelectionPosition selStart = sel.RangeMain().Start();
PasteRectangular(selStart, selectedText.s, length);
}
else
if (pdoc->InsertString(sel.RangeMain().caret.Position(), selectedText.s, length))
SetEmptySelection(sel.RangeMain().caret.Position() + length);
pdoc->EndUndoAction();
Redraw();
EnsureCaretVisible();
}
//--------------------------------------------------------------------------------------------------
void ScintillaCocoa::CTPaint(void* gc, NSRect rc) {
Surface *surfaceWindow = Surface::Allocate();
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(pt.x, rectBounds.size.height - pt.y);
ct.MouseClick(location);
CallTipClick();
}
void ScintillaCocoa::CreateCallTipWindow(PRectangle rc) {
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];
[caption 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];
[item setTarget: menu];
[item setAction: @selector(handleCommand:)];
[item setTag: cmd];
[item setTitle: [NSString stringWithUTF8String: label]];
[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()
{
NSPoint result;
result.y = pdoc->LineFromPosition(sel.RangeMain().caret.Position());
result.x = sel.RangeMain().caret.Position() - pdoc->LineStart(result.y);
return result;
}
// -------------------------------------------------------------------------------------------------
#pragma segment 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;
}
}
}
//--------------------------------------------------------------------------------------------------
/**
* Called when a drag operation was initiated from within Scintilla.
*/
void ScintillaCocoa::StartDrag()
{
if (sel.Empty())
return;
// Put the data to be dragged on the drag pasteboard.
SelectionText selectedText;
NSPasteboard* pasteboard = [NSPasteboard pasteboardWithName: NSDragPboard];
CopySelectionRange(&selectedText);
SetPasteboardData(pasteboard, selectedText);
// Prepare drag image.
PRectangle localRectangle = RectangleFromRange(sel.RangeMain().Start().Position(), sel.RangeMain().End().Position());
NSRect selectionRectangle = PRectangleToNSRect(localRectangle);
NSView* content = ContentView();
#if 0 // TODO: fix initialization of the drag image with CGImageRef.
// 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 = hideSelection;
hideSelection = true;
if (sw)
{
pixmap = new SurfaceImpl();
if (pixmap)
{
PRectangle client = GetClientRectangle();
PRectangle imageRect = NSRectToPRectangle(selectionRectangle);
paintState = painting;
//sw->InitPixMap(client.Width(), client.Height(), NULL, NULL);
sw->InitPixMap(imageRect.Width(), imageRect.Height(), NULL, NULL);
paintingAllText = true;
Paint(sw, imageRect);
paintState = notPainting;
pixmap->InitPixMap(imageRect.Width(), 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, 0, 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;
}
hideSelection = lastHideSelection;
NSBitmapImageRep* bitmap = NULL;
if (pixmap)
{
bitmap = [[[NSBitmapImageRep alloc] initWithCGImage: pixmap->GetImage()] autorelease];
pixmap->Release();
delete pixmap;
}
#else
// Poor man's drag image: take a snapshot of the content view.
[content lockFocus];
NSBitmapImageRep* bitmap = [[[NSBitmapImageRep alloc] initWithFocusedViewRect: selectionRectangle] autorelease];
[bitmap setColorSpaceName: NSDeviceRGBColorSpace];
[content unlockFocus];
#endif
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 dissolveToPoint: NSMakePoint(0.0, 0.0) fraction: 0.5];
[dragImage unlockFocus];
NSPoint startPoint;
startPoint.x = selectionRectangle.origin.x;
startPoint.y = selectionRectangle.origin.y + selectionRectangle.size.height;
[content dragImage: dragImage
at: startPoint
offset: NSZeroSize
event: lastMouseEvent // Set in MouseMove.
pasteboard: pasteboard
source: content
slideBack: YES];
}
//--------------------------------------------------------------------------------------------------
/**
* Called when a drag operation reaches the control which was initiated outside.
*/
NSDragOperation ScintillaCocoa::DraggingEntered(id <NSDraggingInfo> info)
{
inDragDrop = ddDragging;
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)
{
SetDragPosition(SelectionPosition(invalidPosition));
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.len > 0)
{
NSDragOperation operation = [info draggingSourceOperationMask];
bool moving = (operation & NSDragOperationMove) != 0;
DropAt(posDrag, text.s, moving, text.rectangular);
};
}
return true;
}
//--------------------------------------------------------------------------------------------------
void ScintillaCocoa::SetPasteboardData(NSPasteboard* board, const SelectionText &selectedText)
{
if (selectedText.len == 0)
return;
NSString *string;
string = [NSString stringWithUTF8String: selectedText.s];
[board declareTypes:[NSArray arrayWithObjects:
NSStringPboardType,
selectedText.rectangular ? ScintillaRecPboardType : nil,
nil] owner:nil];
if (selectedText.rectangular)
{
// This is specific to scintilla, allows us to drag rectangular selections around the document.
[board setString: string forType: ScintillaRecPboardType];
}
[board setString: string forType: NSStringPboardType];
}
//--------------------------------------------------------------------------------------------------
/**
* 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)
{
char* text = (char*) [data UTF8String];
bool rectangular = bestType == ScintillaRecPboardType;
selectedText->Copy(text, strlen(text) + 1, SC_CP_UTF8, SC_CHARSET_DEFAULT , rectangular, false);
}
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------
void ScintillaCocoa::SetMouseCapture(bool on)
{
capturedMouse = on;
/*
if (mouseDownCaptures)
{
if (capturedMouse)
WndProc(SCI_SETCURSOR, Window::cursorArrow, 0);
else
// Reset to normal. Actual image will be set on mouse move.
WndProc(SCI_SETCURSOR, (unsigned int) SC_CURSORNORMAL, 0);
}
*/
}
//--------------------------------------------------------------------------------------------------
bool ScintillaCocoa::HaveMouseCapture()
{
return capturedMouse;
}
//--------------------------------------------------------------------------------------------------
/**
* Synchronously paint a rectangle of the window.
*/
void ScintillaCocoa::SyncPaint(void* gc, PRectangle rc)
{
paintState = painting;
rcPaint = rc;
PRectangle rcText = GetTextRectangle();
paintingAllText = rcPaint.Contains(rcText);
Surface *sw = Surface::Allocate();
if (sw)
{
sw->Init(gc, wMain.GetID());
Paint(sw, rc);
if (paintState == paintAbandoned)
{
// Do a full paint.
rcPaint = GetClientRectangle();
paintState = painting;
paintingAllText = true;
Paint(sw, rcPaint);
}
sw->Release();
delete sw;
}
paintState = notPainting;
}
//--------------------------------------------------------------------------------------------------
/**
* Modfies the vertical scroll position to make the current top line show up as such.
*/
void ScintillaCocoa::SetVerticalScrollPos()
{
ScintillaView* topContainer = TopContainer();
// Convert absolute coordinate into the range [0..1]. Keep in mind that the visible area
// does *not* belong to the scroll range.
float relativePosition = (float) topLine / MaxScrollPos();
[topContainer setVerticalScrollPosition: relativePosition];
}
//--------------------------------------------------------------------------------------------------
void ScintillaCocoa::SetHorizontalScrollPos()
{
ScintillaView* topContainer = TopContainer();
PRectangle textRect = GetTextRectangle();
// Convert absolute coordinate into the range [0..1]. Keep in mind that the visible area
// does *not* belong to the scroll range.
float relativePosition = (float) xOffset / (scrollWidth - textRect.Width());
[topContainer setHorizontalScrollPosition: relativePosition];
}
//--------------------------------------------------------------------------------------------------
/**
* Used to adjust both scrollers to reflect the current scroll range and position in the editor.
*
* @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)
{
// Input values are given in lines, not pixels, so we have to convert.
int lineHeight = WndProc(SCI_TEXTHEIGHT, 0, 0);
PRectangle bounds = GetTextRectangle();
ScintillaView* topContainer = TopContainer();
// Set page size to the same value as the scroll range to hide the scrollbar.
int scrollRange = lineHeight * (nMax + 1); // +1 because the caller subtracted one.
int pageSize;
if (verticalScrollBarVisible)
pageSize = bounds.Height();
else
pageSize = scrollRange;
bool verticalChange = [topContainer setVerticalScrollRange: scrollRange page: pageSize];
scrollRange = scrollWidth;
if (horizontalScrollBarVisible)
pageSize = bounds.Width();
else
pageSize = scrollRange;
bool horizontalChange = [topContainer setHorizontalScrollRange: scrollRange page: pageSize];
return verticalChange || horizontalChange;
}
//--------------------------------------------------------------------------------------------------
void ScintillaCocoa::Resize()
{
ChangeSize();
}
//--------------------------------------------------------------------------------------------------
/**
* Called by the frontend control when the user manipulates one of the scrollers.
*
* @param position The relative position of the scroller in the range of [0..1].
* @param part Specifies which part was clicked on by the user, so we can handle thumb tracking
* as well as page and line scrolling.
* @param horizontal True if the horizontal scroller was hit, otherwise false.
*/
void ScintillaCocoa::DoScroll(float position, NSScrollerPart part, bool horizontal)
{
// If the given scroller part is not the knob (or knob slot) then the given position is not yet
// current and we have to update it.
if (horizontal)
{
// Horizontal offset is given in pixels.
PRectangle textRect = GetTextRectangle();
int offset = (int) (position * (scrollWidth - textRect.Width()));
int smallChange = (int) (textRect.Width() / 30);
if (smallChange < 5)
smallChange = 5;
switch (part)
{
case NSScrollerDecrementLine:
offset -= smallChange;
break;
case NSScrollerDecrementPage:
offset -= textRect.Width();
break;
case NSScrollerIncrementLine:
offset += smallChange;
break;
case NSScrollerIncrementPage:
offset += textRect.Width();
break;
};
HorizontalScrollTo(offset);
}
else
{
// VerticalScrolling is by line. If the user is scrolling using the knob we can directly
// set the new scroll position. Otherwise we have to compute it first.
if (part == NSScrollerKnob)
ScrollTo(position * MaxScrollPos(), false);
else
{
switch (part)
{
case NSScrollerDecrementLine:
ScrollTo(topLine - 1, true);
break;
case NSScrollerDecrementPage:
ScrollTo(topLine - LinesOnScreen(), true);
break;
case NSScrollerIncrementLine:
ScrollTo(topLine + 1, true);
break;
case NSScrollerIncrementPage:
ScrollTo(topLine + LinesOnScreen(), true);
break;
};
}
}
}
//--------------------------------------------------------------------------------------------------
/**
* Used to register a callback function for a given window. This is used to emulate the way
* Windows notfies 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, (uintptr_t) (SCEN_CHANGE << 16), (uintptr_t) this);
}
//--------------------------------------------------------------------------------------------------
void ScintillaCocoa::NotifyFocus(bool focus)
{
if (notifyProc != NULL)
notifyProc(notifyObj, WM_COMMAND, (uintptr_t) ((focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS) << 16), (uintptr_t) this);
}
//--------------------------------------------------------------------------------------------------
/**
* 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)
{
if (notifyProc != NULL)
{
scn.nmhdr.hwndFrom = (void*) this;
scn.nmhdr.idFrom = GetCtrlID();
notifyProc(notifyObj, WM_NOTIFY, (uintptr_t) 0, (uintptr_t) &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)
{
Tick();
DragScroll();
}
//--------------------------------------------------------------------------------------------------
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.
*/
void ScintillaCocoa::Draw(NSRect rect, CGContextRef gc)
{
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;
}
}
//--------------------------------------------------------------------------------------------------
/**
* 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.
NSUInteger modifiers = [event modifierFlags];
NSString* input = [event characters];
bool control = (modifiers & NSControlKeyMask) != 0;
bool shift = (modifiers & NSShiftKeyMask) != 0;
bool command = (modifiers & NSCommandKeyMask) != 0;
bool alt = (modifiers & NSAlternateKeyMask) != 0;
bool handled = false;
// Handle each entry individually. Usually we only have one entry anway.
for (int i = 0; i < input.length; i++)
{
const UniChar originalKey = [input characterAtIndex: i];
UniChar key = KeyTranslate(originalKey);
bool consumed = false; // Consumed as command?
// Signal Control as SCMOD_META
int modifierKeys =
(shift ? SCI_SHIFT : 0) |
(command ? SCI_CTRL : 0) |
(alt ? SCI_ALT : 0) |
(control ? SCI_META : 0);
if (KeyDownWithModifiers(key, modifierKeys, &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)
{
const char* utf8 = [input UTF8String];
AddCharUTF((char*) utf8, strlen(utf8), false);
return true;
}
//--------------------------------------------------------------------------------------------------
/**
* 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;
ButtonMove(ConvertPoint([event locationInWindow]));
}
//--------------------------------------------------------------------------------------------------
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 dX = 0;
int dY = 0;
dX = 10 * [event deltaX]; // Arbitrary scale factor.
// 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
{
HorizontalScrollTo(xOffset - dX);
ScrollTo(topLine - dY, true);
}
}
//--------------------------------------------------------------------------------------------------
// 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(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 );
SetTicking( false );
} else {
ShowCaretAtCurrentPosition();
}
}
//--------------------------------------------------------------------------------------------------