<commit_before>// Scintilla source code edit control
// ScintillaMacOSX.cxx - Mac OS X subclass of ScintillaBase
// Copyright 2003 by <NAME> <<EMAIL>>
// Based on ScintillaGTK.cxx Copyright 1998-2002 by <NAME> <<EMAIL>>
// 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 (currentPos == anchor) return;

    // calculate the bounds of the selection
        PRectangle client = GetTextRectangle();
    int selStart = SelectionStart();
    int selEnd = SelectionEnd();
    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 == 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;
    bool isFileURL = false;
    if (!GetDragData(inDrag, pasteBoard, NULL, &isFileURL)) {
        return false;
    }

    Point pt = GetDragPoint (inDrag);
    SetDragPosition(PositionFromLocation(pt));
    SetDragCursor(inDrag);

    return true;
}

bool ScintillaMacOSX::DragLeave(DragRef inDrag )
{
    HideDragHilite( inDrag );
    SetDragPosition(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;<commit_msg>Fix for bug #2722617, Mac: wrong flavor written to clipboard<commit_after>// Scintilla source code edit control
// ScintillaMacOSX.cxx - Mac OS X subclass of ScintillaBase
// Copyright 2003 by <NAME> <<EMAIL>>
// Based on ScintillaGTK.cxx Copyright 1998-2002 by <NAME> <<EMAIL>>
// 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 (currentPos == anchor) return;

    // calculate the bounds of the selection
        PRectangle client = GetTextRectangle();
    int selStart = SelectionStart();
    int selEnd = SelectionEnd();
    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 == 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;
    bool isFileURL = false;
    if (!GetDragData(inDrag, pasteBoard, NULL, &isFileURL)) {
        return false;
    }

    Point pt = GetDragPoint (inDrag);
    SetDragPosition(PositionFromLocation(pt));
    SetDragCursor(inDrag);

    return true;
}

bool ScintillaMacOSX::DragLeave(DragRef inDrag )
{
    HideDragHilite( inDrag );
    SetDragPosition(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) {
        // Fixes http://bugs.activestate.com/show_bug.cgi?id=81340.
        // See the bug for details
        PasteboardPutItemFlavor( theClipboard, (PasteboardItemID)1, 
                                CFSTR("public.utf16-external-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;
{'fraction_non_alphanumeric': 0.10777158481438637, 'fraction_numerical': 0.007613839520454306, 'mean_word_length': 2.659355037327792, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 79, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7289514', 'n_tokens_mistral': 16851, 'n_tokens_neox': 16098, 'n_words': 3751}