diff options
Diffstat (limited to 'macosx/ScintillaMacOSX.cxx')
-rw-r--r-- | macosx/ScintillaMacOSX.cxx | 2118 |
1 files changed, 2118 insertions, 0 deletions
diff --git a/macosx/ScintillaMacOSX.cxx b/macosx/ScintillaMacOSX.cxx new file mode 100644 index 000000000..9ac746f4b --- /dev/null +++ b/macosx/ScintillaMacOSX.cxx @@ -0,0 +1,2118 @@ +// 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" +#include "UniConversion.h" + +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; +} + +ScintillaMacOSX::ScintillaMacOSX( void* windowid ) : + TView( reinterpret_cast<HIViewRef>( windowid ) ) +{ + 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); + assert( err == noErr ); +} + +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); +} + +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() { +#define DRAG_DROP_PASTEBOARD + if (currentPos == anchor) return; + + SelectionText selectedText; + CopySelectionRange(&selectedText); + + // some of this taken from copytoclipboard + if (selectedText.len == 0) + return; + + CFStringEncoding encoding = ( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingASCII); + + // 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 ); + assert( string != NULL ); + +#ifndef DRAG_DROP_PASTEBOARD + CFIndex numUniChars = CFStringGetLength( string ); + UniChar* buffer = new UniChar[ numUniChars ]; + CFStringGetCharacters( string, CFRangeMake( 0, numUniChars ), buffer ); + + // Create an c string byte buffer + CFIndex maximumByteLength = CFStringGetMaximumSizeForEncoding( numUniChars, encoding ) + 1; + char* cstring = new char[maximumByteLength]; + CFIndex usedBufferLength = 0; + CFIndex numCharsConverted; + numCharsConverted = CFStringGetBytes( string, CFRangeMake( 0, numUniChars ), encoding, + '?', false, reinterpret_cast<UInt8*>( cstring ), + maximumByteLength, &usedBufferLength ); + cstring[usedBufferLength] = '\0'; // null terminate the ASCII/UTF8 string + assert( numCharsConverted == numUniChars ); +#endif + + // calculate the bounds of the selection + PRectangle client = GetTextRectangle(); + int selStart = Platform::Minimum(anchor, currentPos); + int selEnd = Platform::Maximum(anchor, currentPos); + 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 ); + + 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); + +#ifdef DRAG_DROP_PASTEBOARD + PasteboardRef theClipboard; + PasteboardCreate( kPasteboardClipboard, &theClipboard ); + PasteboardClear( theClipboard ); + + CFDataRef data = NULL; + data = CFStringCreateExternalRepresentation ( kCFAllocatorDefault, string, kCFStringEncodingMacRoman, 0 ); + PasteboardPutItemFlavor( theClipboard, (PasteboardItemID)1, + CFSTR("com.apple.traditional-mac-plain-text"), + data, 0 ); + CFRelease(data); + data = CFStringCreateExternalRepresentation ( kCFAllocatorDefault, string, kCFStringEncodingUnicode, 0 ); + PasteboardPutItemFlavor( theClipboard, (PasteboardItemID)1, + CFSTR("public.utf16-plain-text"), + data, 0 ); + CFRelease(data); + NewDragWithPasteboard( theClipboard, &inDrag); +#else + NewDrag(&inDrag); + AddDragItemFlavor(inDrag, 1, 'utxt', buffer, sizeof( UniChar ) * numUniChars, 0); + AddDragItemFlavor(inDrag, 1, 'txt', cstring, sizeof( char ) * usedBufferLength, 0); +#endif + + // 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. + inDragSession = true; + OSErr error = TrackDrag(inDrag, &mouseDownEvent, dragRegion); + inDragSession = false; + + // 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); + CFRelease( string ); + + if (pixmap) { + CGImageRelease(image); + pixmap->Release(); + delete pixmap; + } + + // Done with the UniChar* buffer +#ifdef DRAG_DROP_PASTEBOARD + CFRelease( theClipboard ); +#else + delete[] buffer; + buffer = NULL; + delete[] cstring; + cstring = NULL; +#endif +} + +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); + QDGlobalToLocalPoint(GetWindowPort(GetOwner()), &globalMouse); + HIPoint hiPoint = {globalMouse.h, globalMouse.v}; + 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 == invalidPosition) { + RESET_SCROLL_TIMER(1); + return; + } + Point dragMouse = LocationFromPosition(posDrag); + int line = pdoc->LineFromPosition(posDrag); + 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(PositionFromLocation(dragMouse)); + +#undef RESET_SCROLL_TIMER +} + +bool ScintillaMacOSX::DragWithin(DragRef inDrag ) +{ + PasteboardRef pasteBoard; + OSStatus status = GetDragData(inDrag, pasteBoard, NULL); + if (status != noErr) { + return false; + } + + ::Point mouse, globalMouse; + GetDragMouse(inDrag, &mouse, &globalMouse); + QDGlobalToLocalPoint(GetWindowPort(GetOwner()), &globalMouse); + HIPoint globalHit = {globalMouse.h, globalMouse.v}; + // HIPoint localHit = {mouse.h, mouse.v}; + + if (!CGRectContainsPoint( Bounds(), globalHit )) { + return false; + } + + SetDragPosition(PositionFromLocation(Point(static_cast<int>(globalHit.x),static_cast<int>(globalHit.y)))); + SetDragCursor(inDrag); + + return true; +} + +bool ScintillaMacOSX::DragLeave(DragRef inDrag ) +{ + HideDragHilite( inDrag ); + SetDragPosition(invalidPosition); + WndProc(SCI_SETCURSOR, Window::cursorArrow, 0); + return true; +} + +OSStatus ScintillaMacOSX::GetDragData(DragRef inDrag, PasteboardRef &pasteBoard, CFStringRef *textString) +{ + // 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 dragNotAcceptedErr; + } + + // how many items in the pasteboard? + ItemCount i, itemCount; + status = PasteboardGetItemCount(pasteBoard, &itemCount); + if (status != noErr) { + return dragNotAcceptedErr; + } + + // 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 dragNotAcceptedErr; + } + + // how many flavors in this item? + status = PasteboardCopyItemFlavors(pasteBoard, itemID, &flavorTypeArray); + if (status != noErr) { + return dragNotAcceptedErr; + } + + 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) { + if (UTTypeConformsTo(flavorType, CFSTR("public.utf16-plain-text"))) // this is 'utxt' + { + // if we got a flavor match, and we have no textString, we just want + // to know that we can accept this drag data, so jump out now + if (textString == NULL) { + haveMatch = true; + goto DragDataRetrieved; + } + if (PasteboardCopyItemFlavorData(pasteBoard, itemID, flavorType, &flavorData) == noErr) + { + CFIndex flavorDataSize = CFDataGetLength(flavorData); + + // getting the text + *textString = CFStringCreateWithCharacters(NULL, + (UniChar *)CFDataGetBytePtr(flavorData), + flavorDataSize >> 1); + CFRelease(flavorData); + goto DragDataRetrieved; + } + } + } + } + } +DragDataRetrieved: + if (flavorTypeArray != NULL) CFRelease(flavorTypeArray); + if (haveMatch || textString != NULL && *textString != NULL) + return noErr; + return dragNotAcceptedErr; +} + +OSStatus ScintillaMacOSX::DragReceive(DragRef inDrag ) +{ + OSStatus status; + PasteboardRef pasteBoard; + CFStringRef textString = NULL; + status = GetDragData(inDrag, pasteBoard, &textString); + if (status != noErr) { + return dragNotAcceptedErr; + } + + // getting the length of the text and setting the value + if (textString == NULL) { + return noErr; + } + + // XXX the following is identical (ALMOST) to code in Paste + + // Allocate a buffer, plus the null byte + CFIndex numUniChars = CFStringGetLength( textString ); + CFStringEncoding encoding = ( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingASCII); + 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 + assert( numCharsConverted == numUniChars ); + + // Default allocator releases both the CFString and the UniChar buffer (text) + CFRelease( textString ); + textString = NULL; + + // 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 + int droppedLen = 0; + char *droppedText = Document::TransformLineEnds(&droppedLen, + cstring + bomLen, + usedBufferLength - bomLen, + pdoc->eolMode); + + pdoc->BeginUndoAction(); + + // figure out if this is a move or a paste + DragAttributes attributes; + SInt16 modifiers = 0; + GetDragAttributes( inDrag, &attributes ); + + int position = PositionFromLocation(GetDragPoint(inDrag)); + int selStart = Platform::Minimum(anchor, currentPos); + int selEnd = Platform::Maximum(anchor, currentPos); + if ( attributes & kDragInsideSenderWindow ) { + if (position >= selStart && position <= selEnd) { + // droping on top of what we dragged, we should ignore this + goto endDrag; + } + GetDragModifiers(inDrag, NULL, NULL, &modifiers); + switch (modifiers & ~btnState) // Filter out btnState (on for drop) + { + case optionKey: + // default is copy text + break; + + case cmdKey: + case cmdKey | optionKey: + default: + // what to do with these? rectangular drag? + position = selStart; + ClearSelection(); + break; + } + } else { + if (position >= selStart && position <= selEnd) { + // droping on top of a selection from another app or control, clear it + position = selStart; + ClearSelection(); + } + } + + // lets put the text in our document now + if ( pdoc->InsertString( position, droppedText, droppedLen ) ) + { + SetEmptySelection( currentPos + droppedLen ); + } + +endDrag: + delete[] droppedText; + delete[] cstring; + cstring = NULL; + + pdoc->EndUndoAction(); + NotifyChange(); + + // 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 ); + + return noErr; +} + +/** 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()) { + char utfval[4]; + wchar_t wcs[2] = {wParam, 0}; + unsigned int len = UTF8Length(wcs, 1); + UTF8FromUTF16(wcs, 1, utfval, len); + AddCharUTF(utfval, len); + 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) { + // XXX a bit of a hack to avoid excesive flashing when typing. + paintState = painting; + paintingAllText = true; + Paint(sw, rc); + // TODO: There is a chance that this causes an infinite drawing loop... + 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(); +} + +void ScintillaMacOSX::NotifyChange() { + // TODO: How should this be implemented on OS X? Should it be? +} + +pascal void ScintillaMacOSX::LiveScrollHandler( HIViewRef control, SInt16 part ) +{ + SInt16 currentValue = GetControl32BitValue( control ); + SInt16 min = GetControl32BitMinimum( control ); + SInt16 max = GetControl32BitMaximum( control ); + SInt16 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: + 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*/) { + // TODO: How should this be implemented on OS X? Should it be? +} + +typedef void (*SciNotifyFunc)(sptr_t *, long); +void ScintillaMacOSX::NotifyParent(SCNotification scn) { + OSStatus err; + sptr_t *ptr = NULL; + SciNotifyFunc fn = NULL; + + // XXX do this at some other point, or otherwise cache the results + err = GetControlProperty(GetViewRef(), + scintillaNotifyObject, 0, + sizeof( sptr_t * ), NULL, &ptr ); + if (err != noErr) return; + err = GetControlProperty(GetViewRef(), + scintillaNotifyFN, 0, + sizeof( SciNotifyFunc ), NULL, &fn ); + if (err != noErr || !fn) return; + + scn.nmhdr.hwndFrom = GetViewRef(); + scn.nmhdr.idFrom = (unsigned int)wMain.GetID(); + fn(ptr, (long int)&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); +} + +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); +} + +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 ) + { + // 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 ( SelectionEnd() - SelectionStart() > 0 ); +} + +bool ScintillaMacOSX::CanUndo() +{ + return pdoc->CanUndo(); +} + +bool ScintillaMacOSX::CanRedo() +{ + return pdoc->CanRedo(); +} + +bool ScintillaMacOSX::AlwaysTrue() +{ + return true; +} + +void ScintillaMacOSX::CopyToClipboard(const SelectionText &selectedText) { + if (selectedText.len == 0) + return; + + CFStringEncoding encoding = ( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingASCII); + + // 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 ); + assert( string != NULL ); + + CFIndex numUniChars = CFStringGetLength( string ); + UniChar* buffer = new UniChar[ numUniChars ]; + CFStringGetCharacters( string, CFRangeMake( 0, numUniChars ), buffer ); + + // Done with the CFString + CFRelease( string ); + string = NULL; + + OSStatus err; + err = ClearCurrentScrap(); + assert( err == noErr ); + + ScrapRef scrap = NULL; + err = GetCurrentScrap( &scrap ); + assert( err == noErr && scrap != NULL ); + + err = PutScrapFlavor( scrap, kScrapFlavorTypeUnicode, 0, sizeof( UniChar ) * numUniChars, buffer ); + assert( err == noErr ); + err = PutScrapFlavor( scrap, kScrapFlavorTypeText, 0, sizeof( char ) * selectedText.len, reinterpret_cast<UInt8*>( selectedText.s ) ); + assert( err == noErr ); + + // Done with the UniChar* buffer + delete[] buffer; + buffer = NULL; +} + +void ScintillaMacOSX::Copy() +{ + if (currentPos != anchor) { + SelectionText selectedText; + CopySelectionRange(&selectedText); + CopyToClipboard(selectedText); + } +} + +bool ScintillaMacOSX::CanPaste() +{ + ScrapRef scrap = NULL; + OSStatus err; + err = GetCurrentScrap( &scrap ); + assert( err == noErr && scrap != NULL ); + + ScrapFlavorFlags flavorFlags; + return GetScrapFlavorFlags ( scrap, kScrapFlavorTypeUnicode, &flavorFlags ) == noErr || + GetScrapFlavorFlags ( scrap, kScrapFlavorTypeText, &flavorFlags ) == noErr; +} + +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 isRectangular) +{ + // Make sure that we CAN paste + if ( ! CanPaste() ) return; + + // Get the clipboard reference + ScrapRef scrap = NULL; + OSStatus err; + err = GetCurrentScrap( &scrap ); + assert( err == noErr && scrap != NULL ); + + ScrapFlavorFlags flavorFlags; + Size bytes = 0; + CFStringRef string = NULL; + if (GetScrapFlavorFlags ( scrap, kScrapFlavorTypeUnicode, &flavorFlags ) == noErr) + { + // No error, we have unicode data in a Scrap. Find out how many bytes of data it is. + err = GetScrapFlavorSize( scrap, kScrapFlavorTypeUnicode, &bytes ); + assert( err == noErr && bytes != 0 ); + Size numUniChars = bytes / sizeof( UniChar ); + + // Allocate a buffer for the text using Core Foundation + UniChar* buffer = reinterpret_cast<UniChar*>( CFAllocatorAllocate( NULL, bytes, 0 ) ); + assert( buffer != NULL ); + + // Get a copy of the text + Size nextBytes = bytes; + err = GetScrapFlavorData( scrap, kScrapFlavorTypeUnicode, &nextBytes, buffer ); + assert( err == noErr && nextBytes == bytes ); + + // Create a CFString which wraps and takes ownership of the buffer + string = CFStringCreateWithCharactersNoCopy( NULL, buffer, numUniChars, NULL ); + assert( string != NULL ); + buffer = NULL; // string now owns this buffer + } else if (GetScrapFlavorFlags ( scrap, kScrapFlavorTypeText, &flavorFlags ) == noErr) { + // No error, we have unicode data in a Scrap. Find out how many bytes of data it is. + err = GetScrapFlavorSize( scrap, kScrapFlavorTypeText, &bytes ); + assert( err == noErr && bytes != 0 ); + + // Allocate a buffer for the text using Core Foundation + char* buffer = reinterpret_cast<char*>( CFAllocatorAllocate( NULL, bytes + 1, 0 ) ); + assert( buffer != NULL ); + + // Get a copy of the text + Size nextBytes = bytes; + err = GetScrapFlavorData( scrap, kScrapFlavorTypeText, &nextBytes, buffer ); + assert( err == noErr && nextBytes == bytes ); + buffer[bytes]=0; + // Create a CFString which wraps and takes ownership of the buffer + string = CFStringCreateWithCStringNoCopy( NULL, buffer, kCFStringEncodingMacRoman, NULL ); + assert( string != NULL ); + buffer = NULL; // string now owns this buffer + } else { + // a flavor we do not understand + return; + } + + + // Allocate a buffer, plus the null byte + CFIndex numUniChars = CFStringGetLength( string ); + CFStringEncoding encoding = ( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingASCII); + CFIndex maximumByteLength = CFStringGetMaximumSizeForEncoding( numUniChars, encoding ) + 1; + char* cstring = new char[maximumByteLength]; + CFIndex usedBufferLength = 0; + CFIndex numCharsConverted; + numCharsConverted = CFStringGetBytes( string, CFRangeMake( 0, numUniChars ), encoding, + '?', false, reinterpret_cast<UInt8*>( cstring ), + maximumByteLength, &usedBufferLength ); + cstring[usedBufferLength] = '\0'; // null terminate the ASCII/UTF8 string + assert( numCharsConverted == numUniChars ); + + // Default allocator releases both the CFString and the UniChar buffer (text) + CFRelease( string ); + string = NULL; + + // 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 + int newlen = 0; + char *pasted = Document::TransformLineEnds(&newlen, + cstring + bomLen, + usedBufferLength - bomLen, + pdoc->eolMode); + + pdoc->BeginUndoAction(); + ClearSelection(); + + if (isRectangular) { + int selStart = SelectionStart(); + PasteRectangular(selStart, pasted, newlen); + } else + if ( pdoc->InsertString( currentPos, pasted, newlen ) ) { + SetEmptySelection( currentPos + newlen ); + } + + delete[] pasted; + delete[] cstring; + cstring = NULL; + + pdoc->EndUndoAction(); + NotifyChange(); + Redraw(); +} + +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, kTextEncodingMacRoman ); + 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; + case kPageUpCharCode: + return SCK_PRIOR; + case kPageDownCharCode: + return SCK_NEXT; + 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; + 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; + } +} + +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); + + // 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; +} + +// 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 ); + + // 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(currentPos, buffer, usedBufferLength)) { + SetEmptySelection(currentPos + 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; +} + +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); + } + 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 ); + assert( err == noErr ); + 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(); +} +} |