notepad-plus-plus/scintilla/macosx/ScintillaMacOSX.cxx

2245 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, true);
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, bool inDragDropSession)
{
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((inDragDropSession
? kPasteboardUniqueName
: 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::ClearSelectionSimple },
{ 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, false); // not in drag/drop
// 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();
}
}