summaryrefslogtreecommitdiffstats
path: root/scintilla/macosx/ScintillaMacOSX.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'scintilla/macosx/ScintillaMacOSX.cxx')
-rw-r--r--scintilla/macosx/ScintillaMacOSX.cxx2242
1 files changed, 2242 insertions, 0 deletions
diff --git a/scintilla/macosx/ScintillaMacOSX.cxx b/scintilla/macosx/ScintillaMacOSX.cxx
new file mode 100644
index 0000000..e4f2f8c
--- /dev/null
+++ b/scintilla/macosx/ScintillaMacOSX.cxx
@@ -0,0 +1,2242 @@
+// 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();
+}
+}