2243 lines
80 KiB
C++
2243 lines
80 KiB
C++
|
// Scintilla source code edit control
|
||
|
// ScintillaMacOSX.cxx - Mac OS X subclass of ScintillaBase
|
||
|
// 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.
|
||
|
|
||
|
|
||
|
#include "ScintillaMacOSX.h"
|
||
|
#ifdef EXT_INPUT
|
||
|
// External Input Editor
|
||
|
#include "ExtInput.h"
|
||
|
#else
|
||
|
#include "UniConversion.h"
|
||
|
#endif
|
||
|
|
||
|
using namespace Scintilla;
|
||
|
|
||
|
const CFStringRef ScintillaMacOSX::kScintillaClassID = CFSTR( "org.scintilla.scintilla" );
|
||
|
const ControlKind ScintillaMacOSX::kScintillaKind = { 'ejon', 'Scin' };
|
||
|
|
||
|
extern "C" HIViewRef scintilla_calltip_new(void);
|
||
|
|
||
|
#ifndef WM_UNICHAR
|
||
|
#define WM_UNICHAR 0x0109
|
||
|
#endif
|
||
|
|
||
|
// required for paste/dragdrop, see comment in paste function below
|
||
|
static int BOMlen(unsigned char *cstr) {
|
||
|
switch(cstr[0]) {
|
||
|
case 0xEF: // BOM_UTF8
|
||
|
if (cstr[1] == 0xBB && cstr[2] == 0xBF) {
|
||
|
return 3;
|
||
|
}
|
||
|
break;
|
||
|
case 0xFE:
|
||
|
if (cstr[1] == 0xFF) {
|
||
|
if (cstr[2] == 0x00 && cstr[3] == 0x00) {
|
||
|
return 4;
|
||
|
}
|
||
|
return 2;
|
||
|
}
|
||
|
break;
|
||
|
case 0xFF:
|
||
|
if (cstr[1] == 0xFE) {
|
||
|
if (cstr[2] == 0x00 && cstr[3] == 0x00) {
|
||
|
return 4;
|
||
|
}
|
||
|
return 2;
|
||
|
}
|
||
|
break;
|
||
|
case 0x00:
|
||
|
if (cstr[1] == 0x00) {
|
||
|
if (cstr[2] == 0xFE && cstr[3] == 0xFF) {
|
||
|
return 4;
|
||
|
}
|
||
|
if (cstr[2] == 0xFF && cstr[3] == 0xFE) {
|
||
|
return 4;
|
||
|
}
|
||
|
return 2;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#ifdef EXT_INPUT
|
||
|
#define SCI_CMD ( SCI_ALT | SCI_CTRL | SCI_SHIFT)
|
||
|
|
||
|
static const KeyToCommand macMapDefault[] = {
|
||
|
{SCK_DOWN, SCI_CMD, SCI_DOCUMENTEND},
|
||
|
{SCK_UP, SCI_CMD, SCI_DOCUMENTSTART},
|
||
|
{SCK_LEFT, SCI_CMD, SCI_VCHOME},
|
||
|
{SCK_RIGHT, SCI_CMD, SCI_LINEEND},
|
||
|
{SCK_DOWN, SCI_NORM, SCI_LINEDOWN},
|
||
|
{SCK_DOWN, SCI_SHIFT, SCI_LINEDOWNEXTEND},
|
||
|
{SCK_DOWN, SCI_CTRL, SCI_LINESCROLLDOWN},
|
||
|
{SCK_DOWN, SCI_ASHIFT, SCI_LINEDOWNRECTEXTEND},
|
||
|
{SCK_UP, SCI_NORM, SCI_LINEUP},
|
||
|
{SCK_UP, SCI_SHIFT, SCI_LINEUPEXTEND},
|
||
|
{SCK_UP, SCI_CTRL, 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_CSHIFT, 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_CSHIFT, 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_HOMEDISPLAYEXTEND},
|
||
|
{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_LINEENDDISPLAYEXTEND},
|
||
|
{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_CTRL, SCI_UNDO},
|
||
|
{'Y', SCI_CTRL, SCI_REDO},
|
||
|
{'X', SCI_CTRL, SCI_CUT},
|
||
|
{'C', SCI_CTRL, SCI_COPY},
|
||
|
{'V', SCI_CTRL, SCI_PASTE},
|
||
|
{'A', SCI_CTRL, 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_CTRL, SCI_ZOOMIN},
|
||
|
{SCK_SUBTRACT, SCI_CTRL, SCI_ZOOMOUT},
|
||
|
{SCK_DIVIDE, SCI_CTRL, SCI_SETZOOM},
|
||
|
//'L', SCI_CTRL, SCI_FORMFEED,
|
||
|
{'L', SCI_CTRL, 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},
|
||
|
};
|
||
|
#endif
|
||
|
|
||
|
ScintillaMacOSX::ScintillaMacOSX( void* windowid ) :
|
||
|
TView( reinterpret_cast<HIViewRef>( windowid ) )
|
||
|
{
|
||
|
notifyObj = NULL;
|
||
|
notifyProc = NULL;
|
||
|
wMain = windowid;
|
||
|
OSStatus err;
|
||
|
err = GetThemeMetric( kThemeMetricScrollBarWidth, &scrollBarFixedSize );
|
||
|
assert( err == noErr );
|
||
|
|
||
|
mouseTrackingRef = NULL;
|
||
|
mouseTrackingID.signature = scintillaMacOSType;
|
||
|
mouseTrackingID.id = (SInt32)this;
|
||
|
capturedMouse = false;
|
||
|
|
||
|
// Enable keyboard events and mouse events
|
||
|
#if !defined(CONTAINER_HANDLES_EVENTS)
|
||
|
ActivateInterface( kKeyboardFocus );
|
||
|
ActivateInterface( kMouse );
|
||
|
ActivateInterface( kDragAndDrop );
|
||
|
#endif
|
||
|
ActivateInterface( kMouseTracking );
|
||
|
|
||
|
Initialise();
|
||
|
|
||
|
// Create some bounds rectangle which will just get reset to the correct rectangle later
|
||
|
Rect tempScrollRect;
|
||
|
tempScrollRect.top = -1;
|
||
|
tempScrollRect.left = 400;
|
||
|
tempScrollRect.bottom = 300;
|
||
|
tempScrollRect.right = 450;
|
||
|
|
||
|
// Create the scroll bar with fake values that will get set correctly later
|
||
|
err = CreateScrollBarControl( this->GetOwner(), &tempScrollRect, 0, 0, 100, 100, true, LiveScrollHandler, &vScrollBar );
|
||
|
assert( vScrollBar != NULL && err == noErr );
|
||
|
err = CreateScrollBarControl( this->GetOwner(), &tempScrollRect, 0, 0, 100, 100, true, LiveScrollHandler, &hScrollBar );
|
||
|
assert( hScrollBar != NULL && err == noErr );
|
||
|
|
||
|
// Set a property on the scrollbars to store a pointer to the Scintilla object
|
||
|
ScintillaMacOSX* objectPtr = this;
|
||
|
err = SetControlProperty( vScrollBar, scintillaMacOSType, 0, sizeof( this ), &objectPtr );
|
||
|
assert( err == noErr );
|
||
|
err = SetControlProperty( hScrollBar, scintillaMacOSType, 0, sizeof( this ), &objectPtr );
|
||
|
assert( err == noErr );
|
||
|
|
||
|
// set this into our parent control so we can be retrieved easily at a later time
|
||
|
// (see scintilla_send below)
|
||
|
err = SetControlProperty( reinterpret_cast<HIViewRef>( windowid ), scintillaMacOSType, 0, sizeof( this ), &objectPtr );
|
||
|
assert( err == noErr );
|
||
|
|
||
|
// Tell Scintilla not to buffer: Quartz buffers drawing for us
|
||
|
// TODO: Can we disable this option on Mac OS X?
|
||
|
WndProc( SCI_SETBUFFEREDDRAW, 0, 0 );
|
||
|
// Turn on UniCode mode
|
||
|
WndProc( SCI_SETCODEPAGE, SC_CP_UTF8, 0 );
|
||
|
|
||
|
const EventTypeSpec commandEventInfo[] = {
|
||
|
{ kEventClassCommand, kEventProcessCommand },
|
||
|
{ kEventClassCommand, kEventCommandUpdateStatus },
|
||
|
};
|
||
|
|
||
|
err = InstallEventHandler( GetControlEventTarget( reinterpret_cast<HIViewRef>( windowid ) ),
|
||
|
CommandEventHandler,
|
||
|
GetEventTypeCount( commandEventInfo ),
|
||
|
commandEventInfo,
|
||
|
this, NULL);
|
||
|
#ifdef EXT_INPUT
|
||
|
ExtInput::attach (GetViewRef());
|
||
|
for (int i = 0; macMapDefault[i].key; i++)
|
||
|
{
|
||
|
this->kmap.AssignCmdKey(macMapDefault[i].key, macMapDefault[i].modifiers, macMapDefault[i].msg);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
ScintillaMacOSX::~ScintillaMacOSX() {
|
||
|
// If the window is closed and the timer is not removed,
|
||
|
// A segment violation will occur when it attempts to fire the timer next.
|
||
|
if ( mouseTrackingRef != NULL ) {
|
||
|
ReleaseMouseTrackingRegion(mouseTrackingRef);
|
||
|
}
|
||
|
mouseTrackingRef = NULL;
|
||
|
SetTicking(false);
|
||
|
#ifdef EXT_INPUT
|
||
|
ExtInput::detach (GetViewRef());
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::Initialise() {
|
||
|
// TODO: Do anything here? Maybe this stuff should be here instead of the constructor?
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::Finalise() {
|
||
|
SetTicking(false);
|
||
|
ScintillaBase::Finalise();
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------------------------------------------
|
||
|
//
|
||
|
// IsDropInFinderTrash - Returns true if the given dropLocation AEDesc is a descriptor of the Finder's Trash.
|
||
|
//
|
||
|
#pragma segment Drag
|
||
|
|
||
|
Boolean IsDropInFinderTrash(AEDesc *dropLocation)
|
||
|
{
|
||
|
OSErr result;
|
||
|
AEDesc dropSpec;
|
||
|
FSSpec *theSpec;
|
||
|
CInfoPBRec thePB;
|
||
|
short trashVRefNum;
|
||
|
long trashDirID;
|
||
|
|
||
|
// Coerce the dropLocation descriptor into an FSSpec. If there's no dropLocation or
|
||
|
// it can't be coerced into an FSSpec, then it couldn't have been the Trash.
|
||
|
|
||
|
if ((dropLocation->descriptorType != typeNull) &&
|
||
|
(AECoerceDesc(dropLocation, typeFSS, &dropSpec) == noErr))
|
||
|
{
|
||
|
unsigned char flags = HGetState((Handle)dropSpec.dataHandle);
|
||
|
|
||
|
HLock((Handle)dropSpec.dataHandle);
|
||
|
theSpec = (FSSpec *) *dropSpec.dataHandle;
|
||
|
|
||
|
// Get the directory ID of the given dropLocation object.
|
||
|
|
||
|
thePB.dirInfo.ioCompletion = 0L;
|
||
|
thePB.dirInfo.ioNamePtr = (StringPtr) &theSpec->name;
|
||
|
thePB.dirInfo.ioVRefNum = theSpec->vRefNum;
|
||
|
thePB.dirInfo.ioFDirIndex = 0;
|
||
|
thePB.dirInfo.ioDrDirID = theSpec->parID;
|
||
|
|
||
|
result = PBGetCatInfoSync(&thePB);
|
||
|
|
||
|
HSetState((Handle)dropSpec.dataHandle, flags);
|
||
|
AEDisposeDesc(&dropSpec);
|
||
|
|
||
|
if (result != noErr)
|
||
|
return false;
|
||
|
|
||
|
// If the result is not a directory, it must not be the Trash.
|
||
|
|
||
|
if (!(thePB.dirInfo.ioFlAttrib & (1 << 4)))
|
||
|
return false;
|
||
|
|
||
|
// Get information about the Trash folder.
|
||
|
|
||
|
FindFolder(theSpec->vRefNum, kTrashFolderType, kCreateFolder, &trashVRefNum, &trashDirID);
|
||
|
|
||
|
// If the directory ID of the dropLocation object is the same as the directory ID
|
||
|
// returned by FindFolder, then the drop must have occurred into the Trash.
|
||
|
|
||
|
if (thePB.dirInfo.ioDrDirID == trashDirID)
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
|
||
|
} // IsDropInFinderTrash
|
||
|
|
||
|
HIPoint ScintillaMacOSX::GetLocalPoint(::Point pt)
|
||
|
{
|
||
|
// get the mouse position so we can offset it
|
||
|
Rect bounds;
|
||
|
GetWindowBounds( GetOwner(), kWindowStructureRgn, &bounds );
|
||
|
|
||
|
PRectangle hbounds = wMain.GetPosition();
|
||
|
HIViewRef parent = HIViewGetSuperview(GetViewRef());
|
||
|
Rect pbounds;
|
||
|
GetControlBounds(parent, &pbounds);
|
||
|
|
||
|
bounds.left += pbounds.left + hbounds.left;
|
||
|
bounds.top += pbounds.top + hbounds.top;
|
||
|
|
||
|
HIPoint offset = { pt.h - bounds.left, pt.v - bounds.top };
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::StartDrag() {
|
||
|
if (sel.Empty()) return;
|
||
|
|
||
|
// 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;
|
||
|
int startPos, endPos, ep;
|
||
|
Rect rcSel;
|
||
|
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(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(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
|
||
|
PRectangle imageRect = PRectangle(rcSel.left, rcSel.top, rcSel.right, rcSel.bottom);
|
||
|
QDLocalToGlobalRect(GetWindowPort(GetOwner()), &rcSel);
|
||
|
|
||
|
// get the mouse position so we can offset it
|
||
|
HIPoint offset = GetLocalPoint(mouseDownEvent.where);
|
||
|
offset.y = (imageRect.top * 1.0) - offset.y;
|
||
|
offset.x = (imageRect.left * 1.0) - offset.x;
|
||
|
|
||
|
// 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;
|
||
|
|
||
|
if (sw) {
|
||
|
pixmap = new SurfaceImpl();
|
||
|
if (pixmap) {
|
||
|
client = GetClientRectangle();
|
||
|
paintState = painting;
|
||
|
sw->InitPixMap( client.Width(), client.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
|
||
|
//fprintf(stderr, "translate to %d\n", client.Height() );
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
// now we initiate the drag session
|
||
|
|
||
|
RgnHandle dragRegion = NewRgn();
|
||
|
RgnHandle tempRegion;
|
||
|
DragRef inDrag;
|
||
|
DragAttributes attributes;
|
||
|
AEDesc dropLocation;
|
||
|
SInt16 mouseDownModifiers, mouseUpModifiers;
|
||
|
bool copyText;
|
||
|
CGImageRef image = NULL;
|
||
|
|
||
|
RectRgn(dragRegion, &rcSel);
|
||
|
|
||
|
SelectionText selectedText;
|
||
|
CopySelectionRange(&selectedText);
|
||
|
PasteboardRef theClipboard;
|
||
|
SetPasteboardData(theClipboard, selectedText);
|
||
|
NewDragWithPasteboard( theClipboard, &inDrag);
|
||
|
CFRelease( theClipboard );
|
||
|
|
||
|
// Set the item's bounding rectangle in global coordinates.
|
||
|
SetDragItemBounds(inDrag, 1, &rcSel);
|
||
|
|
||
|
// Prepare the drag region.
|
||
|
tempRegion = NewRgn();
|
||
|
CopyRgn(dragRegion, tempRegion);
|
||
|
InsetRgn(tempRegion, 1, 1);
|
||
|
DiffRgn(dragRegion, tempRegion, dragRegion);
|
||
|
DisposeRgn(tempRegion);
|
||
|
|
||
|
// if we have a pixmap, lets use that
|
||
|
if (pixmap) {
|
||
|
image = pixmap->GetImage();
|
||
|
SetDragImageWithCGImage (inDrag, image, &offset, kDragStandardTranslucency);
|
||
|
}
|
||
|
|
||
|
// Drag the text. TrackDrag will return userCanceledErr if the drop whooshed back for any reason.
|
||
|
inDragDrop = ddDragging;
|
||
|
OSErr error = TrackDrag(inDrag, &mouseDownEvent, dragRegion);
|
||
|
inDragDrop = ddNone;
|
||
|
|
||
|
// Check to see if the drop occurred in the Finder's Trash. If the drop occurred
|
||
|
// in the Finder's Trash and a copy operation wasn't specified, delete the
|
||
|
// source selection. Note that we can continute to get the attributes, drop location
|
||
|
// modifiers, etc. of the drag until we dispose of it using DisposeDrag.
|
||
|
if (error == noErr) {
|
||
|
GetDragAttributes(inDrag, &attributes);
|
||
|
if (!(attributes & kDragInsideSenderApplication))
|
||
|
{
|
||
|
GetDropLocation(inDrag, &dropLocation);
|
||
|
|
||
|
GetDragModifiers(inDrag, 0L, &mouseDownModifiers, &mouseUpModifiers);
|
||
|
copyText = (mouseDownModifiers | mouseUpModifiers) & optionKey;
|
||
|
|
||
|
if ((!copyText) && (IsDropInFinderTrash(&dropLocation)))
|
||
|
{
|
||
|
// delete the selected text from the buffer
|
||
|
ClearSelection();
|
||
|
}
|
||
|
|
||
|
AEDisposeDesc(&dropLocation);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Dispose of this drag, 'cause we're done.
|
||
|
DisposeDrag(inDrag);
|
||
|
DisposeRgn(dragRegion);
|
||
|
|
||
|
if (pixmap) {
|
||
|
CGImageRelease(image);
|
||
|
pixmap->Release();
|
||
|
delete pixmap;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::SetDragCursor(DragRef inDrag)
|
||
|
{
|
||
|
DragAttributes attributes;
|
||
|
SInt16 modifiers = 0;
|
||
|
ThemeCursor cursor = kThemeCopyArrowCursor;
|
||
|
GetDragAttributes( inDrag, &attributes );
|
||
|
|
||
|
if ( attributes & kDragInsideSenderWindow ) {
|
||
|
GetDragModifiers(inDrag, &modifiers, NULL, NULL);
|
||
|
switch (modifiers & ~btnState) // Filter out btnState (on for drop)
|
||
|
{
|
||
|
case optionKey:
|
||
|
// it's a copy, leave it as a copy arrow
|
||
|
break;
|
||
|
|
||
|
case cmdKey:
|
||
|
case cmdKey | optionKey:
|
||
|
default:
|
||
|
// what to do with these? rectangular drag?
|
||
|
cursor = kThemeArrowCursor;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
SetThemeCursor(cursor);
|
||
|
}
|
||
|
|
||
|
bool ScintillaMacOSX::DragEnter(DragRef inDrag )
|
||
|
{
|
||
|
if (!DragWithin(inDrag))
|
||
|
return false;
|
||
|
|
||
|
DragAttributes attributes;
|
||
|
GetDragAttributes( inDrag, &attributes );
|
||
|
|
||
|
// only show the drag hilight if the drag has left the sender window per HI spec
|
||
|
if( attributes & kDragHasLeftSenderWindow )
|
||
|
{
|
||
|
HIRect textFrame;
|
||
|
RgnHandle hiliteRgn = NewRgn();
|
||
|
|
||
|
// get the text view's frame ...
|
||
|
HIViewGetFrame( GetViewRef(), &textFrame );
|
||
|
|
||
|
// ... and convert it into a region for ShowDragHilite
|
||
|
HIShapeRef textShape = HIShapeCreateWithRect( &textFrame );
|
||
|
HIShapeGetAsQDRgn( textShape, hiliteRgn );
|
||
|
CFRelease( textShape );
|
||
|
|
||
|
// add the drag hilight to the inside of the text view
|
||
|
ShowDragHilite( inDrag, hiliteRgn, true );
|
||
|
|
||
|
DisposeRgn( hiliteRgn );
|
||
|
}
|
||
|
SetDragCursor(inDrag);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Scintilla::Point ScintillaMacOSX::GetDragPoint(DragRef inDrag)
|
||
|
{
|
||
|
::Point mouse, globalMouse;
|
||
|
GetDragMouse(inDrag, &mouse, &globalMouse);
|
||
|
HIPoint hiPoint = GetLocalPoint (globalMouse);
|
||
|
return Point(static_cast<int>(hiPoint.x), static_cast<int>(hiPoint.y));
|
||
|
}
|
||
|
|
||
|
|
||
|
void ScintillaMacOSX::DragScroll()
|
||
|
{
|
||
|
#define RESET_SCROLL_TIMER(lines) \
|
||
|
scrollSpeed = (lines); \
|
||
|
scrollTicks = 2000;
|
||
|
|
||
|
if (!posDrag.IsValid()) {
|
||
|
RESET_SCROLL_TIMER(1);
|
||
|
return;
|
||
|
}
|
||
|
Point dragMouse = LocationFromPosition(posDrag);
|
||
|
int line = pdoc->LineFromPosition(posDrag.Position());
|
||
|
int currentVisibleLine = cs.DisplayFromDoc(line);
|
||
|
int lastVisibleLine = Platform::Minimum(topLine + LinesOnScreen() - 1, pdoc->LinesTotal() - 1);
|
||
|
|
||
|
if (currentVisibleLine <= topLine && topLine > 0) {
|
||
|
ScrollTo( topLine - scrollSpeed );
|
||
|
} else if (currentVisibleLine >= lastVisibleLine) {
|
||
|
ScrollTo( topLine + scrollSpeed );
|
||
|
} else {
|
||
|
RESET_SCROLL_TIMER(1);
|
||
|
return;
|
||
|
}
|
||
|
if (scrollSpeed == 1) {
|
||
|
scrollTicks -= timer.tickSize;
|
||
|
if (scrollTicks <= 0) {
|
||
|
RESET_SCROLL_TIMER(5);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SetDragPosition(SPositionFromLocation(dragMouse));
|
||
|
|
||
|
#undef RESET_SCROLL_TIMER
|
||
|
}
|
||
|
|
||
|
bool ScintillaMacOSX::DragWithin(DragRef inDrag )
|
||
|
{
|
||
|
PasteboardRef pasteBoard;
|
||
|
bool isFileURL = false;
|
||
|
if (!GetDragData(inDrag, pasteBoard, NULL, &isFileURL)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Point pt = GetDragPoint (inDrag);
|
||
|
SetDragPosition(SPositionFromLocation(pt));
|
||
|
SetDragCursor(inDrag);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool ScintillaMacOSX::DragLeave(DragRef inDrag )
|
||
|
{
|
||
|
HideDragHilite( inDrag );
|
||
|
SetDragPosition(SelectionPosition(invalidPosition));
|
||
|
WndProc(SCI_SETCURSOR, Window::cursorArrow, 0);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
kFormatBad,
|
||
|
kFormatText,
|
||
|
kFormatUnicode,
|
||
|
kFormatUTF8,
|
||
|
kFormatFile
|
||
|
};
|
||
|
|
||
|
bool ScintillaMacOSX::GetDragData(DragRef inDrag, PasteboardRef &pasteBoard,
|
||
|
SelectionText *selectedText, bool *isFileURL)
|
||
|
{
|
||
|
// TODO: add support for special flavors: flavorTypeHFS and flavorTypePromiseHFS so we
|
||
|
// can handle files being dropped on the editor
|
||
|
OSStatus status;
|
||
|
status = GetDragPasteboard(inDrag, &pasteBoard);
|
||
|
if (status != noErr) {
|
||
|
return false;
|
||
|
}
|
||
|
return GetPasteboardData(pasteBoard, selectedText, isFileURL);
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::SetPasteboardData(PasteboardRef &theClipboard, const SelectionText &selectedText)
|
||
|
{
|
||
|
if (selectedText.len == 0)
|
||
|
return;
|
||
|
|
||
|
CFStringEncoding encoding = ( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingMacRoman);
|
||
|
|
||
|
// Create a CFString from the ASCII/UTF8 data, convert it to UTF16
|
||
|
CFStringRef string = CFStringCreateWithBytes( NULL, reinterpret_cast<UInt8*>( selectedText.s ), selectedText.len - 1, encoding, false );
|
||
|
|
||
|
PasteboardCreate( kPasteboardClipboard, &theClipboard );
|
||
|
PasteboardClear( theClipboard );
|
||
|
|
||
|
CFDataRef data = NULL;
|
||
|
if (selectedText.rectangular) {
|
||
|
// This is specific to scintilla, allows us to drag rectangular selections
|
||
|
// around the document
|
||
|
data = CFStringCreateExternalRepresentation ( kCFAllocatorDefault, string, kCFStringEncodingUnicode, 0 );
|
||
|
if (data) {
|
||
|
PasteboardPutItemFlavor( theClipboard, (PasteboardItemID)1,
|
||
|
CFSTR("com.scintilla.utf16-plain-text.rectangular"),
|
||
|
data, 0 );
|
||
|
CFRelease(data);
|
||
|
}
|
||
|
}
|
||
|
data = CFStringCreateExternalRepresentation ( kCFAllocatorDefault, string, kCFStringEncodingUnicode, 0 );
|
||
|
if (data) {
|
||
|
PasteboardPutItemFlavor( theClipboard, (PasteboardItemID)1,
|
||
|
CFSTR("public.utf16-plain-text"),
|
||
|
data, 0 );
|
||
|
CFRelease(data);
|
||
|
data = NULL;
|
||
|
}
|
||
|
data = CFStringCreateExternalRepresentation ( kCFAllocatorDefault, string, kCFStringEncodingMacRoman, 0 );
|
||
|
if (data) {
|
||
|
PasteboardPutItemFlavor( theClipboard, (PasteboardItemID)1,
|
||
|
CFSTR("com.apple.traditional-mac-plain-text"),
|
||
|
data, 0 );
|
||
|
CFRelease(data);
|
||
|
data = NULL;
|
||
|
}
|
||
|
CFRelease(string);
|
||
|
}
|
||
|
|
||
|
bool ScintillaMacOSX::GetPasteboardData(PasteboardRef &pasteBoard,
|
||
|
SelectionText *selectedText,
|
||
|
bool *isFileURL)
|
||
|
{
|
||
|
// how many items in the pasteboard?
|
||
|
CFDataRef data;
|
||
|
CFStringRef textString = NULL;
|
||
|
bool isRectangular = selectedText ? selectedText->rectangular : false;
|
||
|
ItemCount i, itemCount;
|
||
|
OSStatus status = PasteboardGetItemCount(pasteBoard, &itemCount);
|
||
|
if (status != noErr) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// as long as we didn't get our text, let's loop on the items. We stop as soon as we get it
|
||
|
CFArrayRef flavorTypeArray = NULL;
|
||
|
bool haveMatch = false;
|
||
|
for (i = 1; i <= itemCount; i++)
|
||
|
{
|
||
|
PasteboardItemID itemID;
|
||
|
CFIndex j, flavorCount = 0;
|
||
|
|
||
|
status = PasteboardGetItemIdentifier(pasteBoard, i, &itemID);
|
||
|
if (status != noErr) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// how many flavors in this item?
|
||
|
status = PasteboardCopyItemFlavors(pasteBoard, itemID, &flavorTypeArray);
|
||
|
if (status != noErr) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (flavorTypeArray != NULL)
|
||
|
flavorCount = CFArrayGetCount(flavorTypeArray);
|
||
|
|
||
|
// as long as we didn't get our text, let's loop on the flavors. We stop as soon as we get it
|
||
|
for(j = 0; j < flavorCount; j++)
|
||
|
{
|
||
|
CFDataRef flavorData;
|
||
|
CFStringRef flavorType = (CFStringRef)CFArrayGetValueAtIndex(flavorTypeArray, j);
|
||
|
if (flavorType != NULL)
|
||
|
{
|
||
|
int format = kFormatBad;
|
||
|
if (UTTypeConformsTo(flavorType, CFSTR("public.file-url"))) {
|
||
|
format = kFormatFile;
|
||
|
*isFileURL = true;
|
||
|
}
|
||
|
else if (UTTypeConformsTo(flavorType, CFSTR("com.scintilla.utf16-plain-text.rectangular"))) {
|
||
|
format = kFormatUnicode;
|
||
|
isRectangular = true;
|
||
|
}
|
||
|
else if (UTTypeConformsTo(flavorType, CFSTR("public.utf16-plain-text"))) { // this is 'utxt'
|
||
|
format = kFormatUnicode;
|
||
|
}
|
||
|
else if (UTTypeConformsTo(flavorType, CFSTR("public.utf8-plain-text"))) {
|
||
|
format = kFormatUTF8;
|
||
|
}
|
||
|
else if (UTTypeConformsTo(flavorType, CFSTR("com.apple.traditional-mac-plain-text"))) { // this is 'TEXT'
|
||
|
format = kFormatText;
|
||
|
}
|
||
|
if (format == kFormatBad)
|
||
|
continue;
|
||
|
|
||
|
// if we got a flavor match, and we have no textString, we just want
|
||
|
// to know that we can accept this data, so jump out now
|
||
|
if (selectedText == NULL) {
|
||
|
haveMatch = true;
|
||
|
goto PasteboardDataRetrieved;
|
||
|
}
|
||
|
if (PasteboardCopyItemFlavorData(pasteBoard, itemID, flavorType, &flavorData) == noErr)
|
||
|
{
|
||
|
CFIndex dataSize = CFDataGetLength (flavorData);
|
||
|
const UInt8* dataBytes = CFDataGetBytePtr (flavorData);
|
||
|
switch (format)
|
||
|
{
|
||
|
case kFormatFile:
|
||
|
case kFormatText:
|
||
|
data = CFDataCreate (NULL, dataBytes, dataSize);
|
||
|
textString = CFStringCreateFromExternalRepresentation (NULL, data, kCFStringEncodingMacRoman);
|
||
|
break;
|
||
|
case kFormatUnicode:
|
||
|
data = CFDataCreate (NULL, dataBytes, dataSize);
|
||
|
textString = CFStringCreateFromExternalRepresentation (NULL, data, kCFStringEncodingUnicode);
|
||
|
break;
|
||
|
case kFormatUTF8:
|
||
|
data = CFDataCreate (NULL, dataBytes, dataSize);
|
||
|
textString = CFStringCreateFromExternalRepresentation (NULL, data, kCFStringEncodingUTF8);
|
||
|
break;
|
||
|
}
|
||
|
CFRelease (flavorData);
|
||
|
goto PasteboardDataRetrieved;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
PasteboardDataRetrieved:
|
||
|
if (flavorTypeArray != NULL) CFRelease(flavorTypeArray);
|
||
|
int newlen = 0;
|
||
|
if (textString != NULL) {
|
||
|
selectedText->s = GetStringFromCFString(textString, &selectedText->len);
|
||
|
selectedText->rectangular = isRectangular;
|
||
|
// Default allocator releases both the CFString and the UniChar buffer (text)
|
||
|
CFRelease( textString );
|
||
|
textString = NULL;
|
||
|
}
|
||
|
if (haveMatch || selectedText != NULL && selectedText->s != NULL) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
char *ScintillaMacOSX::GetStringFromCFString(CFStringRef &textString, int *textLen)
|
||
|
{
|
||
|
|
||
|
// Allocate a buffer, plus the null byte
|
||
|
CFIndex numUniChars = CFStringGetLength( textString );
|
||
|
CFStringEncoding encoding = ( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingMacRoman);
|
||
|
CFIndex maximumByteLength = CFStringGetMaximumSizeForEncoding( numUniChars, encoding ) + 1;
|
||
|
char* cstring = new char[maximumByteLength];
|
||
|
CFIndex usedBufferLength = 0;
|
||
|
CFIndex numCharsConverted;
|
||
|
numCharsConverted = CFStringGetBytes( textString, CFRangeMake( 0, numUniChars ), encoding,
|
||
|
'?', false, reinterpret_cast<UInt8*>( cstring ),
|
||
|
maximumByteLength, &usedBufferLength );
|
||
|
cstring[usedBufferLength] = '\0'; // null terminate the ASCII/UTF8 string
|
||
|
|
||
|
// determine whether a BOM is in the string. Apps like Emacs prepends a BOM
|
||
|
// to the string, CFStrinGetBytes reflects that (though it may change in the conversion)
|
||
|
// so we need to remove it before pasting into our buffer. TextWrangler has no
|
||
|
// problem dealing with BOM when pasting into it.
|
||
|
int bomLen = BOMlen((unsigned char *)cstring);
|
||
|
|
||
|
// convert line endings to the document line ending
|
||
|
*textLen = 0;
|
||
|
char *result = Document::TransformLineEnds(textLen,
|
||
|
cstring + bomLen,
|
||
|
usedBufferLength - bomLen,
|
||
|
pdoc->eolMode);
|
||
|
delete[] cstring;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::DragReceive(DragRef inDrag )
|
||
|
{
|
||
|
// dragleave IS called, but for some reason (probably to do with inDrag)
|
||
|
// the hide hilite does not happen unless we do it here
|
||
|
HideDragHilite( inDrag );
|
||
|
|
||
|
PasteboardRef pasteBoard;
|
||
|
SelectionText selectedText;
|
||
|
CFStringRef textString = NULL;
|
||
|
bool isFileURL = false;
|
||
|
if (!GetDragData(inDrag, pasteBoard, &selectedText, &isFileURL)) {
|
||
|
return dragNotAcceptedErr;
|
||
|
}
|
||
|
|
||
|
if (isFileURL) {
|
||
|
NotifyURIDropped(selectedText.s);
|
||
|
} else {
|
||
|
// figure out if this is a move or a paste
|
||
|
DragAttributes attributes;
|
||
|
SInt16 modifiers = 0;
|
||
|
GetDragAttributes( inDrag, &attributes );
|
||
|
bool moving = true;
|
||
|
|
||
|
SelectionPosition position = SPositionFromLocation(GetDragPoint(inDrag));
|
||
|
if ( attributes & kDragInsideSenderWindow ) {
|
||
|
GetDragModifiers(inDrag, NULL, NULL, &modifiers);
|
||
|
switch (modifiers & ~btnState) // Filter out btnState (on for drop)
|
||
|
{
|
||
|
case optionKey:
|
||
|
// default is copy text
|
||
|
moving = false;
|
||
|
break;
|
||
|
case cmdKey:
|
||
|
case cmdKey | optionKey:
|
||
|
default:
|
||
|
// what to do with these? rectangular drag?
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DropAt(position, selectedText.s, moving, selectedText.rectangular);
|
||
|
}
|
||
|
|
||
|
return noErr;
|
||
|
}
|
||
|
|
||
|
// Extended UTF8-UTF6-conversion to handle surrogate pairs correctly (CL265070)
|
||
|
void ScintillaMacOSX::InsertCharacters (const UniChar* buf, int len)
|
||
|
{
|
||
|
CFStringRef str = CFStringCreateWithCharactersNoCopy (NULL, buf, (UInt32) len, kCFAllocatorNull);
|
||
|
CFStringEncoding encoding = ( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingMacRoman);
|
||
|
CFRange range = { 0, len };
|
||
|
CFIndex bufLen;
|
||
|
CFStringGetBytes (str, range, encoding, '?', false, NULL, 0, &bufLen);
|
||
|
UInt8* utf8buf = new UInt8 [bufLen];
|
||
|
CFStringGetBytes (str, range, encoding, '?', false, utf8buf, bufLen, NULL);
|
||
|
AddCharUTF ((char*) utf8buf, bufLen, false);
|
||
|
delete [] utf8buf;
|
||
|
CFRelease (str);
|
||
|
}
|
||
|
|
||
|
/** The simulated message loop. */
|
||
|
sptr_t ScintillaMacOSX::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
|
||
|
switch (iMessage) {
|
||
|
case SCI_GETDIRECTFUNCTION:
|
||
|
Platform::DebugDisplay( "ScintillaMacOSX::WndProc: Returning DirectFunction address.\n" );
|
||
|
return reinterpret_cast<sptr_t>( DirectFunction );
|
||
|
|
||
|
case SCI_GETDIRECTPOINTER:
|
||
|
Platform::DebugDisplay( "ScintillaMacOSX::WndProc: Returning Direct pointer address.\n" );
|
||
|
return reinterpret_cast<sptr_t>( this );
|
||
|
|
||
|
case SCI_GRABFOCUS:
|
||
|
Platform::DebugDisplay( "ScintillaMacOSX::WndProc: Got an unhandled message. Ignoring it.\n" );
|
||
|
break;
|
||
|
case WM_UNICHAR:
|
||
|
if (IsUnicodeMode()) {
|
||
|
// Extended UTF8-UTF6-conversion to handle surrogate pairs correctly (CL265070)
|
||
|
UniChar wcs[1] = { (UniChar) wParam};
|
||
|
InsertCharacters(wcs, 1);
|
||
|
return 1;
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
unsigned int r = ScintillaBase::WndProc(iMessage, wParam, lParam);
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
return 0l;
|
||
|
}
|
||
|
|
||
|
sptr_t ScintillaMacOSX::DefWndProc(unsigned int, uptr_t, sptr_t) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::SetTicking(bool on) {
|
||
|
if (timer.ticking != on) {
|
||
|
timer.ticking = on;
|
||
|
if (timer.ticking) {
|
||
|
// Scintilla ticks = milliseconds
|
||
|
EventLoopTimerRef timerRef = NULL;
|
||
|
InstallTimer( timer.tickSize * kEventDurationMillisecond, &timerRef );
|
||
|
assert( timerRef != NULL );
|
||
|
timer.tickerID = reinterpret_cast<TickerID>( timerRef );
|
||
|
} else if ( timer.tickerID != NULL ) {
|
||
|
RemoveEventLoopTimer( reinterpret_cast<EventLoopTimerRef>( timer.tickerID ) );
|
||
|
}
|
||
|
}
|
||
|
timer.ticksToWait = caret.period;
|
||
|
}
|
||
|
|
||
|
bool ScintillaMacOSX::SetIdle(bool on) {
|
||
|
if (on) {
|
||
|
// Start idler, if it's not running.
|
||
|
if (idler.state == false) {
|
||
|
idler.state = true;
|
||
|
EventLoopTimerRef idlTimer;
|
||
|
InstallEventLoopIdleTimer(GetCurrentEventLoop(),
|
||
|
timer.tickSize * kEventDurationMillisecond,
|
||
|
75 * kEventDurationMillisecond,
|
||
|
IdleTimerEventHandler, this, &idlTimer);
|
||
|
idler.idlerID = reinterpret_cast<IdlerID>( idlTimer );
|
||
|
}
|
||
|
} else {
|
||
|
// Stop idler, if it's running
|
||
|
if (idler.state == true) {
|
||
|
idler.state = false;
|
||
|
if (idler.idlerID != NULL)
|
||
|
RemoveEventLoopTimer( reinterpret_cast<EventLoopTimerRef>( idler.idlerID ) );
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
pascal void ScintillaMacOSX::IdleTimerEventHandler( EventLoopTimerRef inTimer,
|
||
|
EventLoopIdleTimerMessage inState,
|
||
|
void *scintilla )
|
||
|
{
|
||
|
ScintillaMacOSX *sciThis = reinterpret_cast<ScintillaMacOSX*>( scintilla );
|
||
|
bool ret = sciThis->Idle();
|
||
|
if (ret == false) {
|
||
|
sciThis->SetIdle(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::SetMouseCapture(bool on) {
|
||
|
capturedMouse = on;
|
||
|
if (mouseDownCaptures) {
|
||
|
if (capturedMouse) {
|
||
|
WndProc(SCI_SETCURSOR, Window::cursorArrow, 0);
|
||
|
} else {
|
||
|
// reset to normal, buttonmove will change for other area's in the editor
|
||
|
WndProc(SCI_SETCURSOR, (long int)SC_CURSORNORMAL, 0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool ScintillaMacOSX::HaveMouseCapture() {
|
||
|
return capturedMouse;
|
||
|
}
|
||
|
|
||
|
// The default GetClientRectangle calls GetClientPosition on wMain.
|
||
|
// We override it to return "view local" co-ordinates so we can draw properly
|
||
|
// plus we need to remove the space occupied by the scroll bars
|
||
|
PRectangle ScintillaMacOSX::GetClientRectangle() {
|
||
|
PRectangle rc = wMain.GetClientPosition();
|
||
|
if (verticalScrollBarVisible)
|
||
|
rc.right -= scrollBarFixedSize + 1;
|
||
|
if (horizontalScrollBarVisible && (wrapState == eWrapNone))
|
||
|
rc.bottom -= scrollBarFixedSize + 1;
|
||
|
// Move to origin
|
||
|
rc.right -= rc.left;
|
||
|
rc.bottom -= rc.top;
|
||
|
rc.left = 0;
|
||
|
rc.top = 0;
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
// Synchronously paint a rectangle of the window.
|
||
|
void ScintillaMacOSX::SyncPaint(void* gc, PRectangle rc) {
|
||
|
paintState = painting;
|
||
|
rcPaint = rc;
|
||
|
PRectangle rcText = GetTextRectangle();
|
||
|
paintingAllText = rcPaint.Contains(rcText);
|
||
|
//Platform::DebugPrintf("ScintillaMacOSX::SyncPaint %0d,%0d %0d,%0d\n",
|
||
|
// rcPaint.left, rcPaint.top, rcPaint.right, rcPaint.bottom);
|
||
|
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);
|
||
|
wMain.InvalidateAll();
|
||
|
}
|
||
|
sw->Release();
|
||
|
delete sw;
|
||
|
}
|
||
|
paintState = notPainting;
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::ScrollText(int /*linesToMove*/) {
|
||
|
// This function will invalidate the correct regions of the view,
|
||
|
// So shortly after this happens, draw will be called.
|
||
|
// But I'm not quite sure how this works ...
|
||
|
// I have a feeling that it is only supposed to work in conjunction with an HIScrollView.
|
||
|
// TODO: Cook up my own bitblt scroll: Grab the bits on screen, blit them shifted, invalidate the remaining stuff
|
||
|
//CGRect r = CGRectMake( 0, 0, rc.Width(), rc.Height() );
|
||
|
//HIViewScrollRect( reinterpret_cast<HIViewRef>( wMain.GetID() ), NULL, 0, vs.lineHeight * linesToMove );
|
||
|
wMain.InvalidateAll();
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::SetVerticalScrollPos() {
|
||
|
SetControl32BitValue( vScrollBar, topLine );
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::SetHorizontalScrollPos() {
|
||
|
SetControl32BitValue( hScrollBar, xOffset );
|
||
|
}
|
||
|
|
||
|
bool ScintillaMacOSX::ModifyScrollBars(int nMax, int nPage) {
|
||
|
Platform::DebugPrintf( "nMax: %d nPage: %d hScroll (%d -> %d) page: %d\n", nMax, nPage, 0, scrollWidth, GetTextRectangle().Width() );
|
||
|
// Minimum value = 0
|
||
|
// TODO: This is probably not needed, since we set this when the scroll bars are created
|
||
|
SetControl32BitMinimum( vScrollBar, 0 );
|
||
|
SetControl32BitMinimum( hScrollBar, 0 );
|
||
|
|
||
|
// Maximum vertical value = nMax + 1 - nPage (lines available to scroll)
|
||
|
SetControl32BitMaximum( vScrollBar, Platform::Maximum( nMax + 1 - nPage, 0 ) );
|
||
|
// Maximum horizontal value = scrollWidth - GetTextRectangle().Width() (pixels available to scroll)
|
||
|
SetControl32BitMaximum( hScrollBar, Platform::Maximum( scrollWidth - GetTextRectangle().Width(), 0 ) );
|
||
|
|
||
|
// Vertical page size = nPage
|
||
|
SetControlViewSize( vScrollBar, nPage );
|
||
|
// Horizontal page size = TextRectangle().Width()
|
||
|
SetControlViewSize( hScrollBar, GetTextRectangle().Width() );
|
||
|
|
||
|
// TODO: Verify what this return value is for
|
||
|
// The scroll bar components will handle if they need to be rerendered or not
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::ReconfigureScrollBars() {
|
||
|
PRectangle rc = wMain.GetClientPosition();
|
||
|
Resize(rc.Width(), rc.Height());
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::Resize(int width, int height) {
|
||
|
// Get the horizontal/vertical size of the scroll bars
|
||
|
GetThemeMetric( kThemeMetricScrollBarWidth, &scrollBarFixedSize );
|
||
|
|
||
|
bool showSBHorizontal = horizontalScrollBarVisible && (wrapState == eWrapNone);
|
||
|
HIRect scrollRect;
|
||
|
if (verticalScrollBarVisible) {
|
||
|
scrollRect.origin.x = width - scrollBarFixedSize;
|
||
|
scrollRect.origin.y = 0;
|
||
|
scrollRect.size.width = scrollBarFixedSize;
|
||
|
if (showSBHorizontal) {
|
||
|
scrollRect.size.height = Platform::Maximum(1, height - scrollBarFixedSize);
|
||
|
} else {
|
||
|
scrollRect.size.height = height;
|
||
|
}
|
||
|
|
||
|
HIViewSetFrame( vScrollBar, &scrollRect );
|
||
|
if (HIViewGetSuperview(vScrollBar) == NULL) {
|
||
|
HIViewSetDrawingEnabled( vScrollBar, true );
|
||
|
HIViewSetVisible(vScrollBar, true);
|
||
|
HIViewAddSubview(GetViewRef(), vScrollBar );
|
||
|
Draw1Control(vScrollBar);
|
||
|
}
|
||
|
} else if (HIViewGetSuperview(vScrollBar) != NULL) {
|
||
|
HIViewSetDrawingEnabled( vScrollBar, false );
|
||
|
HIViewRemoveFromSuperview(vScrollBar);
|
||
|
}
|
||
|
|
||
|
if (showSBHorizontal) {
|
||
|
scrollRect.origin.x = 0;
|
||
|
// Always draw the scrollbar to avoid the "potiential" horizontal scroll bar and to avoid the resize box.
|
||
|
// This should be "good enough". Best would be to avoid the resize box.
|
||
|
// Even better would be to embed Scintilla inside an HIScrollView, which would handle this for us.
|
||
|
scrollRect.origin.y = height - scrollBarFixedSize;
|
||
|
if (verticalScrollBarVisible) {
|
||
|
scrollRect.size.width = Platform::Maximum( 1, width - scrollBarFixedSize );
|
||
|
} else {
|
||
|
scrollRect.size.width = width;
|
||
|
}
|
||
|
scrollRect.size.height = scrollBarFixedSize;
|
||
|
|
||
|
HIViewSetFrame( hScrollBar, &scrollRect );
|
||
|
if (HIViewGetSuperview(hScrollBar) == NULL) {
|
||
|
HIViewSetDrawingEnabled( hScrollBar, true );
|
||
|
HIViewAddSubview( GetViewRef(), hScrollBar );
|
||
|
Draw1Control(hScrollBar);
|
||
|
}
|
||
|
} else if (HIViewGetSuperview(hScrollBar) != NULL) {
|
||
|
HIViewSetDrawingEnabled( hScrollBar, false );
|
||
|
HIViewRemoveFromSuperview(hScrollBar);
|
||
|
}
|
||
|
|
||
|
ChangeSize();
|
||
|
|
||
|
// fixup mouse tracking regions, this causes mouseenter/exit to work
|
||
|
if (HIViewGetSuperview(GetViewRef()) != NULL) {
|
||
|
RgnHandle rgn = NewRgn();
|
||
|
HIRect r;
|
||
|
HIViewGetFrame( reinterpret_cast<HIViewRef>( GetViewRef() ), &r );
|
||
|
SetRectRgn(rgn, short (r.origin.x), short (r.origin.y),
|
||
|
short (r.origin.x + r.size.width - (verticalScrollBarVisible ? scrollBarFixedSize : 0)),
|
||
|
short (r.origin.y + r.size.height - (showSBHorizontal ? scrollBarFixedSize : 0)));
|
||
|
if (mouseTrackingRef == NULL) {
|
||
|
CreateMouseTrackingRegion(GetOwner(), rgn, NULL,
|
||
|
kMouseTrackingOptionsLocalClip,
|
||
|
mouseTrackingID, NULL,
|
||
|
GetControlEventTarget( GetViewRef() ),
|
||
|
&mouseTrackingRef);
|
||
|
} else {
|
||
|
ChangeMouseTrackingRegion(mouseTrackingRef, rgn, NULL);
|
||
|
}
|
||
|
DisposeRgn(rgn);
|
||
|
} else {
|
||
|
if (mouseTrackingRef != NULL) {
|
||
|
ReleaseMouseTrackingRegion(mouseTrackingRef);
|
||
|
}
|
||
|
mouseTrackingRef = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pascal void ScintillaMacOSX::LiveScrollHandler( HIViewRef control, SInt16 part )
|
||
|
{
|
||
|
int currentValue = GetControl32BitValue( control );
|
||
|
int min = GetControl32BitMinimum( control );
|
||
|
int max = GetControl32BitMaximum( control );
|
||
|
int page = GetControlViewSize( control );
|
||
|
|
||
|
// Get a reference to the Scintilla C++ object
|
||
|
ScintillaMacOSX* scintilla = NULL;
|
||
|
OSStatus err;
|
||
|
err = GetControlProperty( control, scintillaMacOSType, 0, sizeof( scintilla ), NULL, &scintilla );
|
||
|
assert( err == noErr && scintilla != NULL );
|
||
|
|
||
|
int singleScroll = 0;
|
||
|
if ( control == scintilla->vScrollBar )
|
||
|
{
|
||
|
// Vertical single scroll = one line
|
||
|
// TODO: Is there a Scintilla preference for this somewhere?
|
||
|
singleScroll = 1;
|
||
|
} else {
|
||
|
assert( control == scintilla->hScrollBar );
|
||
|
// Horizontal single scroll = 20 pixels (hardcoded from ScintillaWin)
|
||
|
// TODO: Is there a Scintilla preference for this somewhere?
|
||
|
singleScroll = 20;
|
||
|
}
|
||
|
|
||
|
// Determine the new value
|
||
|
int newValue = 0;
|
||
|
switch ( part )
|
||
|
{
|
||
|
case kControlUpButtonPart:
|
||
|
newValue = Platform::Maximum( currentValue - singleScroll, min );
|
||
|
break;
|
||
|
|
||
|
case kControlDownButtonPart:
|
||
|
// the the user scrolls to the right, allow more scroll space
|
||
|
if ( control == scintilla->hScrollBar && currentValue >= max) {
|
||
|
// change the max value
|
||
|
scintilla->scrollWidth += singleScroll;
|
||
|
SetControl32BitMaximum( control,
|
||
|
Platform::Maximum( scintilla->scrollWidth - scintilla->GetTextRectangle().Width(), 0 ) );
|
||
|
max = GetControl32BitMaximum( control );
|
||
|
scintilla->SetScrollBars();
|
||
|
}
|
||
|
newValue = Platform::Minimum( currentValue + singleScroll, max );
|
||
|
break;
|
||
|
|
||
|
case kControlPageUpPart:
|
||
|
newValue = Platform::Maximum( currentValue - page, min );
|
||
|
break;
|
||
|
|
||
|
case kControlPageDownPart:
|
||
|
newValue = Platform::Minimum( currentValue + page, max );
|
||
|
break;
|
||
|
|
||
|
case kControlIndicatorPart:
|
||
|
case kControlNoPart:
|
||
|
newValue = currentValue;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
assert( false );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Set the new value
|
||
|
if ( control == scintilla->vScrollBar )
|
||
|
{
|
||
|
scintilla->ScrollTo( newValue );
|
||
|
} else {
|
||
|
assert( control == scintilla->hScrollBar );
|
||
|
scintilla->HorizontalScrollTo( newValue );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool ScintillaMacOSX::ScrollBarHit(HIPoint location) {
|
||
|
// is this on our scrollbars? If so, track them
|
||
|
HIViewRef view;
|
||
|
// view is null if on editor, otherwise on scrollbar
|
||
|
HIViewGetSubviewHit(reinterpret_cast<ControlRef>(wMain.GetID()),
|
||
|
&location, true, &view);
|
||
|
if (view) {
|
||
|
HIViewPartCode part;
|
||
|
|
||
|
// make the point local to a scrollbar
|
||
|
PRectangle client = GetClientRectangle();
|
||
|
if (view == vScrollBar) {
|
||
|
location.x -= client.Width();
|
||
|
} else if (view == hScrollBar) {
|
||
|
location.y -= client.Height();
|
||
|
} else {
|
||
|
fprintf(stderr, "got a subview hit, but not a scrollbar???\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
HIViewGetPartHit(view, &location, &part);
|
||
|
|
||
|
switch (part)
|
||
|
{
|
||
|
case kControlUpButtonPart:
|
||
|
case kControlDownButtonPart:
|
||
|
case kControlPageUpPart:
|
||
|
case kControlPageDownPart:
|
||
|
case kControlIndicatorPart:
|
||
|
::Point p;
|
||
|
p.h = location.x;
|
||
|
p.v = location.y;
|
||
|
// We are assuming Appearance 1.1 or later, so we
|
||
|
// have the "live scroll" variant of the scrollbar,
|
||
|
// which lets you pass the action proc to TrackControl
|
||
|
// for the thumb (this was illegal in previous
|
||
|
// versions of the defproc).
|
||
|
isTracking = true;
|
||
|
::TrackControl(view, p, ScintillaMacOSX::LiveScrollHandler);
|
||
|
::HiliteControl(view, 0);
|
||
|
isTracking = false;
|
||
|
// The mouseup was eaten by TrackControl, however if we
|
||
|
// do not get a mouseup in the scintilla xbl widget,
|
||
|
// many bad focus issues happen. Simply post a mouseup
|
||
|
// and this firey pit becomes a bit cooler.
|
||
|
PostEvent(mouseUp, 0);
|
||
|
break;
|
||
|
default:
|
||
|
fprintf(stderr, "PlatformScrollBarHit part %d\n", part);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::NotifyFocus(bool focus) {
|
||
|
#ifdef EXT_INPUT
|
||
|
ExtInput::activate (GetViewRef(), focus);
|
||
|
#endif
|
||
|
if (NULL != notifyProc)
|
||
|
notifyProc (notifyObj, WM_COMMAND,
|
||
|
(uintptr_t) ((focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS) << 16),
|
||
|
(uintptr_t) GetViewRef());
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::NotifyChange() {
|
||
|
if (NULL != notifyProc)
|
||
|
notifyProc (notifyObj, WM_COMMAND,
|
||
|
(uintptr_t) (SCEN_CHANGE << 16),
|
||
|
(uintptr_t) GetViewRef());
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::registerNotifyCallback(intptr_t windowid, SciNotifyFunc callback) {
|
||
|
notifyObj = windowid;
|
||
|
notifyProc = callback;
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::NotifyParent(SCNotification scn) {
|
||
|
if (NULL != notifyProc) {
|
||
|
scn.nmhdr.hwndFrom = (void*) this;
|
||
|
scn.nmhdr.idFrom = (unsigned int)wMain.GetID();
|
||
|
notifyProc (notifyObj, WM_NOTIFY, (uintptr_t) 0, (uintptr_t) &scn);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::NotifyKey(int key, int modifiers) {
|
||
|
SCNotification scn;
|
||
|
scn.nmhdr.code = SCN_KEY;
|
||
|
scn.ch = key;
|
||
|
scn.modifiers = modifiers;
|
||
|
|
||
|
NotifyParent(scn);
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::NotifyURIDropped(const char *list) {
|
||
|
SCNotification scn;
|
||
|
scn.nmhdr.code = SCN_URIDROPPED;
|
||
|
scn.text = list;
|
||
|
|
||
|
NotifyParent(scn);
|
||
|
}
|
||
|
|
||
|
#ifndef EXT_INPUT
|
||
|
// Extended UTF8-UTF6-conversion to handle surrogate pairs correctly (CL265070)
|
||
|
int ScintillaMacOSX::KeyDefault(int key, int modifiers) {
|
||
|
if (!(modifiers & SCI_CTRL) && !(modifiers & SCI_ALT) && (key < 256)) {
|
||
|
AddChar(key);
|
||
|
return 1;
|
||
|
} else {
|
||
|
// Pass up to container in case it is an accelerator
|
||
|
NotifyKey(key, modifiers);
|
||
|
return 0;
|
||
|
}
|
||
|
//Platform::DebugPrintf("SK-key: %d %x %x\n",key, modifiers);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
template <class T, class U>
|
||
|
struct StupidMap
|
||
|
{
|
||
|
public:
|
||
|
T key;
|
||
|
U value;
|
||
|
};
|
||
|
|
||
|
template <class T, class U>
|
||
|
inline static U StupidMapFindFunction( const StupidMap<T, U>* elements, size_t length, const T& desiredKey )
|
||
|
{
|
||
|
for ( size_t i = 0; i < length; ++ i )
|
||
|
{
|
||
|
if ( elements[i].key == desiredKey )
|
||
|
{
|
||
|
return elements[i].value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// NOTE: If this macro is used on a StupidMap that isn't defined by StupidMap x[] = ...
|
||
|
// The size calculation will fail!
|
||
|
#define StupidMapFind( x, y ) StupidMapFindFunction( x, sizeof(x)/sizeof(*x), y )
|
||
|
|
||
|
pascal OSStatus ScintillaMacOSX::CommandEventHandler( EventHandlerCallRef /*inCallRef*/, EventRef event, void* data )
|
||
|
{
|
||
|
// TODO: Verify automatically that each constant only appears once?
|
||
|
const StupidMap<UInt32, void (ScintillaMacOSX::*)()> processCommands[] = {
|
||
|
{ kHICommandCopy, &ScintillaMacOSX::Copy },
|
||
|
{ kHICommandPaste, &ScintillaMacOSX::Paste },
|
||
|
{ kHICommandCut, &ScintillaMacOSX::Cut },
|
||
|
{ kHICommandUndo, &ScintillaMacOSX::Undo },
|
||
|
{ kHICommandRedo, &ScintillaMacOSX::Redo },
|
||
|
{ kHICommandClear, &ScintillaMacOSX::ClearSelection },
|
||
|
{ kHICommandSelectAll, &ScintillaMacOSX::SelectAll },
|
||
|
};
|
||
|
const StupidMap<UInt32, bool (ScintillaMacOSX::*)()> canProcessCommands[] = {
|
||
|
{ kHICommandCopy, &ScintillaMacOSX::HasSelection },
|
||
|
{ kHICommandPaste, &ScintillaMacOSX::CanPaste },
|
||
|
{ kHICommandCut, &ScintillaMacOSX::HasSelection },
|
||
|
{ kHICommandUndo, &ScintillaMacOSX::CanUndo },
|
||
|
{ kHICommandRedo, &ScintillaMacOSX::CanRedo },
|
||
|
{ kHICommandClear, &ScintillaMacOSX::HasSelection },
|
||
|
{ kHICommandSelectAll, &ScintillaMacOSX::AlwaysTrue },
|
||
|
};
|
||
|
|
||
|
HICommand command;
|
||
|
OSStatus result = GetEventParameter( event, kEventParamDirectObject, typeHICommand, NULL, sizeof( command ), NULL, &command );
|
||
|
assert( result == noErr );
|
||
|
|
||
|
UInt32 kind = GetEventKind( event );
|
||
|
Platform::DebugPrintf("ScintillaMacOSX::CommandEventHandler kind %d\n", kind);
|
||
|
|
||
|
ScintillaMacOSX* scintilla = reinterpret_cast<ScintillaMacOSX*>( data );
|
||
|
assert( scintilla != NULL );
|
||
|
|
||
|
if ( kind == kEventProcessCommand )
|
||
|
{
|
||
|
#ifdef EXT_INPUT
|
||
|
// We are getting a HI command, so stop extended input
|
||
|
ExtInput::stop (scintilla->GetViewRef());
|
||
|
#endif
|
||
|
// Find the method pointer that matches this command
|
||
|
void (ScintillaMacOSX::*methodPtr)() = StupidMapFind( processCommands, command.commandID );
|
||
|
|
||
|
if ( methodPtr != NULL )
|
||
|
{
|
||
|
// Call the method if we found it, and tell the caller that we handled this event
|
||
|
(scintilla->*methodPtr)();
|
||
|
result = noErr;
|
||
|
} else {
|
||
|
// tell the caller that we did not handle the event
|
||
|
result = eventNotHandledErr;
|
||
|
}
|
||
|
}
|
||
|
// The default Mac OS X text editor does not handle these events to enable/disable menu items
|
||
|
// Why not? I think it should, so Scintilla does.
|
||
|
else if ( kind == kEventCommandUpdateStatus && ( command.attributes & kHICommandFromMenu ) )
|
||
|
{
|
||
|
// Find the method pointer that matches this command
|
||
|
bool (ScintillaMacOSX::*methodPtr)() = StupidMapFind( canProcessCommands, command.commandID );
|
||
|
|
||
|
if ( methodPtr != NULL ) {
|
||
|
// Call the method if we found it: enabling/disabling menu items
|
||
|
if ( (scintilla->*methodPtr)() ) {
|
||
|
EnableMenuItem( command.menu.menuRef, command.menu.menuItemIndex );
|
||
|
} else {
|
||
|
DisableMenuItem( command.menu.menuRef, command.menu.menuItemIndex );
|
||
|
}
|
||
|
result = noErr;
|
||
|
} else {
|
||
|
// tell the caller that we did not handle the event
|
||
|
result = eventNotHandledErr;
|
||
|
}
|
||
|
} else {
|
||
|
// Unhandled event: We should never get here
|
||
|
assert( false );
|
||
|
result = eventNotHandledErr;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
bool ScintillaMacOSX::HasSelection()
|
||
|
{
|
||
|
return ( !sel.Empty() );
|
||
|
}
|
||
|
|
||
|
bool ScintillaMacOSX::CanUndo()
|
||
|
{
|
||
|
return pdoc->CanUndo();
|
||
|
}
|
||
|
|
||
|
bool ScintillaMacOSX::CanRedo()
|
||
|
{
|
||
|
return pdoc->CanRedo();
|
||
|
}
|
||
|
|
||
|
bool ScintillaMacOSX::AlwaysTrue()
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::CopyToClipboard(const SelectionText &selectedText) {
|
||
|
PasteboardRef theClipboard;
|
||
|
SetPasteboardData(theClipboard, selectedText);
|
||
|
// Done with the CFString
|
||
|
CFRelease( theClipboard );
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::Copy()
|
||
|
{
|
||
|
if (!sel.Empty()) {
|
||
|
#ifdef EXT_INPUT
|
||
|
ExtInput::stop (GetViewRef());
|
||
|
#endif
|
||
|
SelectionText selectedText;
|
||
|
CopySelectionRange(&selectedText);
|
||
|
fprintf(stderr, "copied text is rectangular? %d\n", selectedText.rectangular);
|
||
|
CopyToClipboard(selectedText);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool ScintillaMacOSX::CanPaste()
|
||
|
{
|
||
|
if (!Editor::CanPaste())
|
||
|
return false;
|
||
|
|
||
|
PasteboardRef theClipboard;
|
||
|
bool isFileURL = false;
|
||
|
|
||
|
PasteboardCreate( kPasteboardClipboard, &theClipboard );
|
||
|
bool ok = GetPasteboardData(theClipboard, NULL, &isFileURL);
|
||
|
CFRelease( theClipboard );
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::Paste()
|
||
|
{
|
||
|
Paste(false);
|
||
|
}
|
||
|
|
||
|
// XXX there is no system flag (I can find) to tell us that a paste is rectangular, so
|
||
|
// applications must implement an additional command (eg. option-V like BBEdit)
|
||
|
// in order to provide rectangular paste
|
||
|
void ScintillaMacOSX::Paste(bool forceRectangular)
|
||
|
{
|
||
|
PasteboardRef theClipboard;
|
||
|
SelectionText selectedText;
|
||
|
selectedText.rectangular = forceRectangular;
|
||
|
bool isFileURL = false;
|
||
|
PasteboardCreate( kPasteboardClipboard, &theClipboard );
|
||
|
bool ok = GetPasteboardData(theClipboard, &selectedText, &isFileURL);
|
||
|
CFRelease( theClipboard );
|
||
|
fprintf(stderr, "paste is rectangular? %d\n", selectedText.rectangular);
|
||
|
if (!ok || !selectedText.s)
|
||
|
// no data or no flavor we support
|
||
|
return;
|
||
|
|
||
|
pdoc->BeginUndoAction();
|
||
|
ClearSelection();
|
||
|
if (selectedText.rectangular) {
|
||
|
SelectionPosition selStart = sel.RangeMain().Start();
|
||
|
PasteRectangular(selStart, selectedText.s, selectedText.len);
|
||
|
} else
|
||
|
if ( pdoc->InsertString( sel.RangeMain().caret.Position(), selectedText.s, selectedText.len ) ) {
|
||
|
SetEmptySelection( sel.RangeMain().caret.Position() + selectedText.len );
|
||
|
}
|
||
|
|
||
|
pdoc->EndUndoAction();
|
||
|
|
||
|
Redraw();
|
||
|
EnsureCaretVisible();
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::CreateCallTipWindow(PRectangle rc) {
|
||
|
// create a calltip window
|
||
|
if (!ct.wCallTip.Created()) {
|
||
|
WindowClass windowClass = kHelpWindowClass;
|
||
|
WindowAttributes attributes = kWindowNoAttributes;
|
||
|
Rect contentBounds;
|
||
|
WindowRef outWindow;
|
||
|
|
||
|
// convert PRectangle to Rect
|
||
|
// this adjustment gets the calltip window placed in the correct location relative
|
||
|
// to our editor window
|
||
|
Rect bounds;
|
||
|
OSStatus err;
|
||
|
err = GetWindowBounds( this->GetOwner(), kWindowGlobalPortRgn, &bounds );
|
||
|
assert( err == noErr );
|
||
|
contentBounds.top = rc.top + bounds.top;
|
||
|
contentBounds.bottom = rc.bottom + bounds.top;
|
||
|
contentBounds.right = rc.right + bounds.left;
|
||
|
contentBounds.left = rc.left + bounds.left;
|
||
|
|
||
|
// create our calltip hiview
|
||
|
HIViewRef ctw = scintilla_calltip_new();
|
||
|
CallTip* objectPtr = &ct;
|
||
|
ScintillaMacOSX* sciThis = this;
|
||
|
SetControlProperty( ctw, scintillaMacOSType, 0, sizeof( this ), &sciThis );
|
||
|
SetControlProperty( ctw, scintillaCallTipType, 0, sizeof( objectPtr ), &objectPtr );
|
||
|
|
||
|
CreateNewWindow(windowClass, attributes, &contentBounds, &outWindow);
|
||
|
ControlRef root;
|
||
|
CreateRootControl(outWindow, &root);
|
||
|
|
||
|
HIViewRef hiroot = HIViewGetRoot (outWindow);
|
||
|
HIViewAddSubview(hiroot, ctw);
|
||
|
|
||
|
HIRect boundsRect;
|
||
|
HIViewGetFrame(hiroot, &boundsRect);
|
||
|
HIViewSetFrame( ctw, &boundsRect );
|
||
|
|
||
|
// bind the size of the calltip to the size of it's container window
|
||
|
HILayoutInfo layout = {
|
||
|
kHILayoutInfoVersionZero,
|
||
|
{
|
||
|
{ NULL, kHILayoutBindTop, 0 },
|
||
|
{ NULL, kHILayoutBindLeft, 0 },
|
||
|
{ NULL, kHILayoutBindBottom, 0 },
|
||
|
{ NULL, kHILayoutBindRight, 0 }
|
||
|
},
|
||
|
{
|
||
|
{ NULL, kHILayoutScaleAbsolute, 0 },
|
||
|
{ NULL, kHILayoutScaleAbsolute, 0 }
|
||
|
|
||
|
},
|
||
|
{
|
||
|
{ NULL, kHILayoutPositionTop, 0 },
|
||
|
{ NULL, kHILayoutPositionLeft, 0 }
|
||
|
}
|
||
|
};
|
||
|
HIViewSetLayoutInfo(ctw, &layout);
|
||
|
|
||
|
ct.wCallTip = root;
|
||
|
ct.wDraw = ctw;
|
||
|
ct.wCallTip.SetWindow(outWindow);
|
||
|
HIViewSetVisible(ctw,true);
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::CallTipClick()
|
||
|
{
|
||
|
ScintillaBase::CallTipClick();
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::AddToPopUp( const char *label, int cmd, bool enabled )
|
||
|
{
|
||
|
// Translate stuff into menu item attributes
|
||
|
MenuItemAttributes attributes = 0;
|
||
|
if ( label[0] == '\0' ) attributes |= kMenuItemAttrSeparator;
|
||
|
if ( ! enabled ) attributes |= kMenuItemAttrDisabled;
|
||
|
|
||
|
// Translate Scintilla commands into Mac OS commands
|
||
|
// TODO: If I create an AEDesc, OS X may insert these standard
|
||
|
// text editing commands into the menu for me
|
||
|
MenuCommand macCommand;
|
||
|
switch( cmd )
|
||
|
{
|
||
|
case idcmdUndo:
|
||
|
macCommand = kHICommandUndo;
|
||
|
break;
|
||
|
case idcmdRedo:
|
||
|
macCommand = kHICommandRedo;
|
||
|
break;
|
||
|
case idcmdCut:
|
||
|
macCommand = kHICommandCut;
|
||
|
break;
|
||
|
case idcmdCopy:
|
||
|
macCommand = kHICommandCopy;
|
||
|
break;
|
||
|
case idcmdPaste:
|
||
|
macCommand = kHICommandPaste;
|
||
|
break;
|
||
|
case idcmdDelete:
|
||
|
macCommand = kHICommandClear;
|
||
|
break;
|
||
|
case idcmdSelectAll:
|
||
|
macCommand = kHICommandSelectAll;
|
||
|
break;
|
||
|
case 0:
|
||
|
macCommand = 0;
|
||
|
break;
|
||
|
default:
|
||
|
assert( false );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CFStringRef string = CFStringCreateWithCString( NULL, label, kCFStringEncodingUTF8 );
|
||
|
OSStatus err;
|
||
|
err = AppendMenuItemTextWithCFString( reinterpret_cast<MenuRef>( popup.GetID() ),
|
||
|
string, attributes, macCommand, NULL );
|
||
|
assert( err == noErr );
|
||
|
|
||
|
CFRelease( string );
|
||
|
string = NULL;
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::ClaimSelection() {
|
||
|
// Mac OS X does not have a primary selection
|
||
|
}
|
||
|
|
||
|
/** A wrapper function to permit external processes to directly deliver messages to our "message loop". */
|
||
|
sptr_t ScintillaMacOSX::DirectFunction(
|
||
|
ScintillaMacOSX *sciThis, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
|
||
|
return sciThis->WndProc(iMessage, wParam, lParam);
|
||
|
}
|
||
|
|
||
|
sptr_t scintilla_send_message(void* sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
|
||
|
HIViewRef control = reinterpret_cast<HIViewRef>(sci);
|
||
|
// Platform::DebugPrintf("scintilla_send_message %08X control %08X\n",sci,control);
|
||
|
// Get a reference to the Scintilla C++ object
|
||
|
ScintillaMacOSX* scintilla = NULL;
|
||
|
OSStatus err;
|
||
|
err = GetControlProperty( control, scintillaMacOSType, 0, sizeof( scintilla ), NULL, &scintilla );
|
||
|
assert( err == noErr && scintilla != NULL );
|
||
|
//Platform::DebugPrintf("scintilla_send_message scintilla %08X\n",scintilla);
|
||
|
|
||
|
return scintilla->WndProc(iMessage, wParam, lParam);
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::TimerFired( EventLoopTimerRef )
|
||
|
{
|
||
|
Tick();
|
||
|
DragScroll();
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::BoundsChanged( UInt32 /*inOptions*/, const HIRect& inOriginalBounds, const HIRect& inCurrentBounds, RgnHandle /*inInvalRgn*/ )
|
||
|
{
|
||
|
// If the width or height changed, modify the scroll bars and notify Scintilla
|
||
|
// This event is also delivered when the window moves, and we don't care about that
|
||
|
if ( inOriginalBounds.size.width != inCurrentBounds.size.width || inOriginalBounds.size.height != inCurrentBounds.size.height )
|
||
|
{
|
||
|
Resize( static_cast<int>( inCurrentBounds.size.width ), static_cast<int>( inCurrentBounds.size.height ) );
|
||
|
}
|
||
|
return noErr;
|
||
|
}
|
||
|
|
||
|
void ScintillaMacOSX::Draw( RgnHandle rgn, CGContextRef gc )
|
||
|
{
|
||
|
Rect invalidRect;
|
||
|
GetRegionBounds( rgn, &invalidRect );
|
||
|
|
||
|
// NOTE: We get draw events that include the area covered by the scroll bar. No fear: Scintilla correctly ignores them
|
||
|
SyncPaint( gc, PRectangle( invalidRect.left, invalidRect.top, invalidRect.right, invalidRect.bottom ) );
|
||
|
}
|
||
|
|
||
|
ControlPartCode ScintillaMacOSX::HitTest( const HIPoint& where )
|
||
|
{
|
||
|
if ( CGRectContainsPoint( Bounds(), where ) )
|
||
|
return 1;
|
||
|
else
|
||
|
return kControlNoPart;
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::SetFocusPart( ControlPartCode desiredFocus, RgnHandle /*invalidRgn*/, Boolean /*inFocusEverything*/, ControlPartCode* outActualFocus )
|
||
|
{
|
||
|
assert( outActualFocus != NULL );
|
||
|
|
||
|
if ( desiredFocus == 0 ) {
|
||
|
// We are losing the focus
|
||
|
SetFocusState(false);
|
||
|
} else {
|
||
|
// We are getting the focus
|
||
|
SetFocusState(true);
|
||
|
}
|
||
|
|
||
|
*outActualFocus = desiredFocus;
|
||
|
return noErr;
|
||
|
}
|
||
|
|
||
|
// Map Mac Roman character codes to their equivalent Scintilla codes
|
||
|
static inline int KeyTranslate( UniChar unicodeChar )
|
||
|
{
|
||
|
switch ( unicodeChar )
|
||
|
{
|
||
|
case kDownArrowCharCode:
|
||
|
return SCK_DOWN;
|
||
|
case kUpArrowCharCode:
|
||
|
return SCK_UP;
|
||
|
case kLeftArrowCharCode:
|
||
|
return SCK_LEFT;
|
||
|
case kRightArrowCharCode:
|
||
|
return SCK_RIGHT;
|
||
|
case kHomeCharCode:
|
||
|
return SCK_HOME;
|
||
|
case kEndCharCode:
|
||
|
return SCK_END;
|
||
|
#ifndef EXT_INPUT
|
||
|
case kPageUpCharCode:
|
||
|
return SCK_PRIOR;
|
||
|
case kPageDownCharCode:
|
||
|
return SCK_NEXT;
|
||
|
#endif
|
||
|
case kDeleteCharCode:
|
||
|
return SCK_DELETE;
|
||
|
// TODO: Is there an insert key in the mac world? My insert key is the "help" key
|
||
|
case kHelpCharCode:
|
||
|
return SCK_INSERT;
|
||
|
case kEnterCharCode:
|
||
|
case kReturnCharCode:
|
||
|
return SCK_RETURN;
|
||
|
#ifdef EXT_INPUT
|
||
|
// BP 2006-08-22: These codes below should not be translated. Otherwise TextInput() will fail for keys like SCK_ADD, which is '+'.
|
||
|
case kBackspaceCharCode:
|
||
|
return SCK_BACK;
|
||
|
case kFunctionKeyCharCode:
|
||
|
case kBellCharCode:
|
||
|
case kVerticalTabCharCode:
|
||
|
case kFormFeedCharCode:
|
||
|
case 14:
|
||
|
case 15:
|
||
|
case kCommandCharCode:
|
||
|
case kCheckCharCode:
|
||
|
case kAppleLogoCharCode:
|
||
|
case 21:
|
||
|
case 22:
|
||
|
case 23:
|
||
|
case 24:
|
||
|
case 25:
|
||
|
case 26:
|
||
|
case kEscapeCharCode:
|
||
|
return 0; // ignore
|
||
|
default:
|
||
|
return unicodeChar;
|
||
|
#else
|
||
|
case kEscapeCharCode:
|
||
|
return SCK_ESCAPE;
|
||
|
case kBackspaceCharCode:
|
||
|
return SCK_BACK;
|
||
|
case '\t':
|
||
|
return SCK_TAB;
|
||
|
case '+':
|
||
|
return SCK_ADD;
|
||
|
case '-':
|
||
|
return SCK_SUBTRACT;
|
||
|
case '/':
|
||
|
return SCK_DIVIDE;
|
||
|
case kFunctionKeyCharCode:
|
||
|
return kFunctionKeyCharCode;
|
||
|
default:
|
||
|
return 0;
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline UniChar GetCharacterWithoutModifiers( EventRef rawKeyboardEvent )
|
||
|
{
|
||
|
UInt32 keyCode;
|
||
|
// Get the key code from the raw key event
|
||
|
GetEventParameter( rawKeyboardEvent, kEventParamKeyCode, typeUInt32, NULL, sizeof( keyCode ), NULL, &keyCode );
|
||
|
|
||
|
// Get the current keyboard layout
|
||
|
// TODO: If this is a performance sink, we need to cache these values
|
||
|
SInt16 lastKeyLayoutID = GetScriptVariable( /*currentKeyScript*/ GetScriptManagerVariable(smKeyScript), smScriptKeys);
|
||
|
Handle uchrHandle = GetResource('uchr', lastKeyLayoutID);
|
||
|
|
||
|
if (uchrHandle) {
|
||
|
// Translate the key press ignoring ctrl and option
|
||
|
UInt32 ignoredDeadKeys = 0;
|
||
|
UInt32 ignoredActualLength = 0;
|
||
|
UniChar unicodeKey = 0;
|
||
|
// (((modifiers & shiftKey) >> 8) & 0xFF)
|
||
|
OSStatus err;
|
||
|
err = UCKeyTranslate( reinterpret_cast<UCKeyboardLayout*>( *uchrHandle ), keyCode, kUCKeyActionDown,
|
||
|
/* modifierKeyState */ 0, LMGetKbdType(), kUCKeyTranslateNoDeadKeysMask, &ignoredDeadKeys,
|
||
|
/* buffer length */ 1,
|
||
|
/* actual length */ &ignoredActualLength,
|
||
|
/* string */ &unicodeKey );
|
||
|
assert( err == noErr );
|
||
|
|
||
|
return unicodeKey;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// Text input is very annoying:
|
||
|
// If the control key is pressed, or if the key is a "special" key (eg. arrow keys, function keys, whatever)
|
||
|
// we let Scintilla handle it. If scintilla does not handle it, we do nothing (eventNotHandledErr).
|
||
|
// Otherwise, the event is just some text and we add it to the buffer
|
||
|
OSStatus ScintillaMacOSX::TextInput( TCarbonEvent& event )
|
||
|
{
|
||
|
// Obtain the number of bytes of text
|
||
|
UInt32 actualSize = 0;
|
||
|
OSStatus err;
|
||
|
err = event.GetParameterSize( kEventParamTextInputSendText, &actualSize );
|
||
|
assert( err == noErr );
|
||
|
assert( actualSize != 0 );
|
||
|
|
||
|
const int numUniChars = actualSize / sizeof( UniChar );
|
||
|
|
||
|
#ifdef EXT_INPUT
|
||
|
UniChar* text = new UniChar [numUniChars];
|
||
|
err = event.GetParameter( kEventParamTextInputSendText, typeUnicodeText, actualSize, text );
|
||
|
PLATFORM_ASSERT( err == noErr );
|
||
|
|
||
|
int modifiers = GetCurrentEventKeyModifiers();
|
||
|
|
||
|
// Loop over all characters in sequence
|
||
|
for (int i = 0; i < numUniChars; i++)
|
||
|
{
|
||
|
UniChar key = KeyTranslate( text[i] );
|
||
|
if (!key)
|
||
|
continue;
|
||
|
|
||
|
bool consumed = false;
|
||
|
|
||
|
// need to go here first so e.g. Tab indentation works
|
||
|
KeyDown ((int) key, (modifiers & shiftKey) != 0 || (modifiers & cmdKey) != 0, (modifiers & controlKey) != 0 || (modifiers & cmdKey) != 0,
|
||
|
(modifiers & optionKey) != 0 || (modifiers & cmdKey) != 0, &consumed);
|
||
|
|
||
|
// BP 2007-01-08: 1452623 Second Cmd+s to save doc inserts an "s" into the text on Mac.
|
||
|
// At this point we need to ignore all cmd/option keys with char value smaller than 32
|
||
|
if( !consumed )
|
||
|
consumed = ( modifiers & ( cmdKey | optionKey ) ) != 0 && text[i] < 32;
|
||
|
|
||
|
// If not consumed, insert the original key
|
||
|
if (!consumed)
|
||
|
InsertCharacters (text+i, 1);
|
||
|
}
|
||
|
|
||
|
delete[] text;
|
||
|
return noErr;
|
||
|
#else
|
||
|
// Allocate a buffer for the text using Core Foundation
|
||
|
UniChar* text = reinterpret_cast<UniChar*>( CFAllocatorAllocate( CFAllocatorGetDefault(), actualSize, 0 ) );
|
||
|
assert( text != NULL );
|
||
|
|
||
|
// Get a copy of the text
|
||
|
err = event.GetParameter( kEventParamTextInputSendText, typeUnicodeText, actualSize, text );
|
||
|
assert( err == noErr );
|
||
|
|
||
|
// TODO: This is a gross hack to ignore function keys
|
||
|
// Surely we can do better?
|
||
|
if ( numUniChars == 1 && text[0] == kFunctionKeyCharCode ) return eventNotHandledErr;
|
||
|
int modifiers = GetCurrentEventKeyModifiers();
|
||
|
int scintillaKey = KeyTranslate( text[0] );
|
||
|
|
||
|
// Create a CFString which wraps and takes ownership of the "text" buffer
|
||
|
CFStringRef string = CFStringCreateWithCharactersNoCopy( NULL, text, numUniChars, NULL );
|
||
|
assert( string != NULL );
|
||
|
//delete text;
|
||
|
text = NULL;
|
||
|
|
||
|
// If we have a single unicode character that is special or
|
||
|
// to process a command. Try to do some translation.
|
||
|
if ( numUniChars == 1 && ( modifiers & controlKey || scintillaKey != 0 ) ) {
|
||
|
// If we have a modifier, we need to get the character without modifiers
|
||
|
if ( modifiers & controlKey ) {
|
||
|
EventRef rawKeyboardEvent = NULL;
|
||
|
event.GetParameter(
|
||
|
kEventParamTextInputSendKeyboardEvent,
|
||
|
typeEventRef,
|
||
|
sizeof( EventRef ),
|
||
|
&rawKeyboardEvent );
|
||
|
assert( rawKeyboardEvent != NULL );
|
||
|
scintillaKey = GetCharacterWithoutModifiers( rawKeyboardEvent );
|
||
|
|
||
|
// Make sure that we still handle special characters correctly
|
||
|
int temp = KeyTranslate( scintillaKey );
|
||
|
if ( temp != 0 ) scintillaKey = temp;
|
||
|
|
||
|
// TODO: This is a gross Unicode hack: ASCII chars have a value < 127
|
||
|
if ( scintillaKey <= 127 ) {
|
||
|
scintillaKey = toupper( (char) scintillaKey );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Code taken from Editor::KeyDown
|
||
|
// It is copied here because we don't want to feed the key via
|
||
|
// KeyDefault if there is no special action
|
||
|
DwellEnd(false);
|
||
|
int scintillaModifiers = ( (modifiers & shiftKey) ? SCI_SHIFT : 0) | ( (modifiers & controlKey) ? SCI_CTRL : 0) |
|
||
|
( (modifiers & optionKey) ? SCI_ALT : 0);
|
||
|
int msg = kmap.Find( scintillaKey, scintillaModifiers );
|
||
|
if (msg) {
|
||
|
// The keymap has a special event for this key: perform the operation
|
||
|
WndProc(msg, 0, 0);
|
||
|
err = noErr;
|
||
|
} else {
|
||
|
// We do not handle this event
|
||
|
err = eventNotHandledErr;
|
||
|
}
|
||
|
} else {
|
||
|
CFStringEncoding encoding = ( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingASCII);
|
||
|
|
||
|
// Allocate the buffer (don't forget the null!)
|
||
|
CFIndex maximumByteLength = CFStringGetMaximumSizeForEncoding( numUniChars, encoding ) + 1;
|
||
|
char* buffer = new char[maximumByteLength];
|
||
|
|
||
|
CFIndex usedBufferLength = 0;
|
||
|
CFIndex numCharsConverted;
|
||
|
numCharsConverted = CFStringGetBytes( string, CFRangeMake( 0, numUniChars ), encoding,
|
||
|
'?', false, reinterpret_cast<UInt8*>( buffer ),
|
||
|
maximumByteLength, &usedBufferLength );
|
||
|
assert( numCharsConverted == numUniChars );
|
||
|
buffer[usedBufferLength] = '\0'; // null terminate
|
||
|
|
||
|
// Add all the characters to the document
|
||
|
// NOTE: OS X doesn't specify that text input events provide only a single character
|
||
|
// if we get a single character, add it as a character
|
||
|
// otherwise, we insert the entire string
|
||
|
if ( numUniChars == 1 ) {
|
||
|
AddCharUTF( buffer, usedBufferLength );
|
||
|
} else {
|
||
|
// WARNING: This is an untested code path as with my US keyboard, I only enter a single character at a time
|
||
|
if (pdoc->InsertString(sel.RangeMain().caret.Position(), buffer, usedBufferLength)) {
|
||
|
SetEmptySelection(sel.RangeMain().caret.Position() + usedBufferLength);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Free the buffer that was allocated
|
||
|
delete[] buffer;
|
||
|
buffer = NULL;
|
||
|
err = noErr;
|
||
|
}
|
||
|
|
||
|
// Default allocator releases both the CFString and the UniChar buffer (text)
|
||
|
CFRelease( string );
|
||
|
string = NULL;
|
||
|
|
||
|
return err;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
UInt32 ScintillaMacOSX::GetBehaviors()
|
||
|
{
|
||
|
return TView::GetBehaviors() | kControlGetsFocusOnClick | kControlSupportsEmbedding;
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::MouseEntered(HIPoint& location, UInt32 /*inKeyModifiers*/, EventMouseButton /*inMouseButton*/, UInt32 /*inClickCount*/ )
|
||
|
{
|
||
|
if (!HaveMouseCapture() && HIViewGetSuperview(GetViewRef()) != NULL) {
|
||
|
HIViewRef view;
|
||
|
HIViewGetSubviewHit(reinterpret_cast<ControlRef>(wMain.GetID()), &location, true, &view);
|
||
|
if (view) {
|
||
|
// the hit is on a subview (ie. scrollbars)
|
||
|
WndProc(SCI_SETCURSOR, Window::cursorArrow, 0);
|
||
|
} else {
|
||
|
// reset to normal, buttonmove will change for other area's in the editor
|
||
|
WndProc(SCI_SETCURSOR, (long int)SC_CURSORNORMAL, 0);
|
||
|
ButtonMove( Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ) );
|
||
|
}
|
||
|
return noErr;
|
||
|
}
|
||
|
return eventNotHandledErr;
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::MouseExited(HIPoint& location, UInt32 modifiers, EventMouseButton button, UInt32 clickCount )
|
||
|
{
|
||
|
if (HIViewGetSuperview(GetViewRef()) != NULL) {
|
||
|
if (HaveMouseCapture()) {
|
||
|
ButtonUp( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ),
|
||
|
static_cast<int>( GetCurrentEventTime() / kEventDurationMillisecond ),
|
||
|
(modifiers & controlKey) != 0 );
|
||
|
}
|
||
|
WndProc(SCI_SETCURSOR, Window::cursorArrow, 0);
|
||
|
return noErr;
|
||
|
}
|
||
|
return eventNotHandledErr;
|
||
|
}
|
||
|
|
||
|
|
||
|
OSStatus ScintillaMacOSX::MouseDown( HIPoint& location, UInt32 modifiers, EventMouseButton button, UInt32 clickCount , TCarbonEvent& inEvent)
|
||
|
{
|
||
|
ConvertEventRefToEventRecord( inEvent.GetEventRef(), &mouseDownEvent );
|
||
|
return MouseDown(location, modifiers, button, clickCount);
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::MouseDown( EventRecord *event )
|
||
|
{
|
||
|
HIPoint pt = GetLocalPoint(event->where);
|
||
|
int button = kEventMouseButtonPrimary;
|
||
|
mouseDownEvent = *event;
|
||
|
|
||
|
if ( event->modifiers & controlKey )
|
||
|
button = kEventMouseButtonSecondary;
|
||
|
return MouseDown(pt, event->modifiers, button, 1);
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::MouseDown( HIPoint& location, UInt32 modifiers, EventMouseButton button, UInt32 /*clickCount*/ )
|
||
|
{
|
||
|
// We only deal with the first mouse button
|
||
|
if ( button != kEventMouseButtonPrimary ) return eventNotHandledErr;
|
||
|
// TODO: Verify that Scintilla wants the time in milliseconds
|
||
|
if (!HaveMouseCapture() && HIViewGetSuperview(GetViewRef()) != NULL) {
|
||
|
if (ScrollBarHit(location)) return noErr;
|
||
|
}
|
||
|
ButtonDown( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ),
|
||
|
static_cast<int>( GetCurrentEventTime() / kEventDurationMillisecond ),
|
||
|
(modifiers & shiftKey) != 0,
|
||
|
(modifiers & controlKey) != 0,
|
||
|
(modifiers & cmdKey) );
|
||
|
#if !defined(CONTAINER_HANDLES_EVENTS)
|
||
|
OSStatus err;
|
||
|
err = SetKeyboardFocus( this->GetOwner(), this->GetViewRef(), 1 );
|
||
|
::SetUserFocusWindow(::HIViewGetWindow( this->GetViewRef() ));
|
||
|
return noErr;
|
||
|
#else
|
||
|
return eventNotHandledErr; // allow event to go to container
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::MouseUp( EventRecord *event )
|
||
|
{
|
||
|
HIPoint pt = GetLocalPoint(event->where);
|
||
|
int button = kEventMouseButtonPrimary;
|
||
|
if ( event->modifiers & controlKey )
|
||
|
button = kEventMouseButtonSecondary;
|
||
|
return MouseUp(pt, event->modifiers, button, 1);
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::MouseUp( HIPoint& location, UInt32 modifiers, EventMouseButton button, UInt32 /*clickCount*/ )
|
||
|
{
|
||
|
// We only deal with the first mouse button
|
||
|
if ( button != kEventMouseButtonPrimary ) return eventNotHandledErr;
|
||
|
ButtonUp( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ),
|
||
|
static_cast<int>( GetCurrentEventTime() / kEventDurationMillisecond ),
|
||
|
(modifiers & controlKey) != 0 );
|
||
|
|
||
|
#if !defined(CONTAINER_HANDLES_EVENTS)
|
||
|
return noErr;
|
||
|
#else
|
||
|
return eventNotHandledErr; // allow event to go to container
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::MouseDragged( EventRecord *event )
|
||
|
{
|
||
|
HIPoint pt = GetLocalPoint(event->where);
|
||
|
int button = 0;
|
||
|
if ( event->modifiers & btnStateBit ) {
|
||
|
button = kEventMouseButtonPrimary;
|
||
|
if ( event->modifiers & controlKey )
|
||
|
button = kEventMouseButtonSecondary;
|
||
|
}
|
||
|
return MouseDragged(pt, event->modifiers, button, 1);
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::MouseDragged( HIPoint& location, UInt32 modifiers, EventMouseButton button, UInt32 clickCount )
|
||
|
{
|
||
|
#if !defined(CONTAINER_HANDLES_EVENTS)
|
||
|
ButtonMove( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ) );
|
||
|
return noErr;
|
||
|
#else
|
||
|
if (HaveMouseCapture() && !inDragDrop) {
|
||
|
MouseTrackingResult mouseStatus = 0;
|
||
|
::Point theQDPoint;
|
||
|
UInt32 outModifiers;
|
||
|
EventTimeout inTimeout=0.1;
|
||
|
while (mouseStatus != kMouseTrackingMouseReleased) {
|
||
|
ButtonMove( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ) );
|
||
|
TrackMouseLocationWithOptions((GrafPtr)-1,
|
||
|
kTrackMouseLocationOptionDontConsumeMouseUp,
|
||
|
inTimeout,
|
||
|
&theQDPoint,
|
||
|
&outModifiers,
|
||
|
&mouseStatus);
|
||
|
location = GetLocalPoint(theQDPoint);
|
||
|
}
|
||
|
ButtonUp( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ),
|
||
|
static_cast<int>( GetCurrentEventTime() / kEventDurationMillisecond ),
|
||
|
(modifiers & controlKey) != 0 );
|
||
|
} else {
|
||
|
if (!HaveMouseCapture() && HIViewGetSuperview(GetViewRef()) != NULL) {
|
||
|
HIViewRef view;
|
||
|
HIViewGetSubviewHit(reinterpret_cast<ControlRef>(wMain.GetID()), &location, true, &view);
|
||
|
if (view) {
|
||
|
// the hit is on a subview (ie. scrollbars)
|
||
|
WndProc(SCI_SETCURSOR, Window::cursorArrow, 0);
|
||
|
return eventNotHandledErr;
|
||
|
} else {
|
||
|
// reset to normal, buttonmove will change for other area's in the editor
|
||
|
WndProc(SCI_SETCURSOR, (long int)SC_CURSORNORMAL, 0);
|
||
|
}
|
||
|
}
|
||
|
ButtonMove( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ) );
|
||
|
}
|
||
|
return eventNotHandledErr; // allow event to go to container
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::MouseWheelMoved( EventMouseWheelAxis axis, SInt32 delta, UInt32 modifiers )
|
||
|
{
|
||
|
if ( axis != 1 ) return eventNotHandledErr;
|
||
|
|
||
|
if ( modifiers & controlKey ) {
|
||
|
// 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 ( delta > 0 ) {
|
||
|
KeyCommand( SCI_ZOOMIN );
|
||
|
} else {
|
||
|
KeyCommand( SCI_ZOOMOUT );
|
||
|
}
|
||
|
} else {
|
||
|
// Decide if this should be optimized?
|
||
|
ScrollTo( topLine - delta );
|
||
|
}
|
||
|
|
||
|
return noErr;
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::ContextualMenuClick( HIPoint& location )
|
||
|
{
|
||
|
// convert screen coords to window relative
|
||
|
Rect bounds;
|
||
|
OSStatus err;
|
||
|
err = GetWindowBounds( this->GetOwner(), kWindowContentRgn, &bounds );
|
||
|
assert( err == noErr );
|
||
|
location.x += bounds.left;
|
||
|
location.y += bounds.top;
|
||
|
ContextMenu( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ) );
|
||
|
return noErr;
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::ActiveStateChanged()
|
||
|
{
|
||
|
// If the window is being deactivated, lose the focus and turn off the ticking
|
||
|
if ( ! this->IsActive() ) {
|
||
|
DropCaret();
|
||
|
//SetFocusState( false );
|
||
|
SetTicking( false );
|
||
|
} else {
|
||
|
ShowCaretAtCurrentPosition();
|
||
|
}
|
||
|
return noErr;
|
||
|
}
|
||
|
|
||
|
HIViewRef ScintillaMacOSX::Create()
|
||
|
{
|
||
|
// Register the HIView, if needed
|
||
|
static bool registered = false;
|
||
|
|
||
|
if ( not registered ) {
|
||
|
TView::RegisterSubclass( kScintillaClassID, Construct );
|
||
|
registered = true;
|
||
|
}
|
||
|
|
||
|
OSStatus err = noErr;
|
||
|
EventRef event = CreateInitializationEvent();
|
||
|
assert( event != NULL );
|
||
|
|
||
|
HIViewRef control = NULL;
|
||
|
err = HIObjectCreate( kScintillaClassID, event, reinterpret_cast<HIObjectRef*>( &control ) );
|
||
|
ReleaseEvent( event );
|
||
|
if ( err == noErr ) {
|
||
|
Platform::DebugPrintf("ScintillaMacOSX::Create control %08X\n",control);
|
||
|
return control;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
OSStatus ScintillaMacOSX::Construct( HIViewRef inControl, TView** outView )
|
||
|
{
|
||
|
*outView = new ScintillaMacOSX( inControl );
|
||
|
Platform::DebugPrintf("ScintillaMacOSX::Construct scintilla %08X\n",*outView);
|
||
|
if ( *outView != NULL )
|
||
|
return noErr;
|
||
|
else
|
||
|
return memFullErr; // could be a lie
|
||
|
}
|
||
|
|
||
|
extern "C" {
|
||
|
HIViewRef scintilla_new() {
|
||
|
return ScintillaMacOSX::Create();
|
||
|
}
|
||
|
}
|