// Scintilla source code edit control
// ScintillaMacOSX.cxx - Mac OS X subclass of ScintillaBase
// Copyright 2003 by Evan Jones <ejones@uwaterloo.ca>
// Based on ScintillaGTK.cxx Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.


#include "ScintillaMacOSX.h"
#ifdef EXT_INPUT
// External Input Editor
#include "ExtInput.h"
#else
#include "UniConversion.h"
#endif

using namespace Scintilla;

const CFStringRef ScintillaMacOSX::kScintillaClassID = CFSTR( "org.scintilla.scintilla" );
const ControlKind ScintillaMacOSX::kScintillaKind = { 'ejon', 'Scin' };

extern "C" HIViewRef scintilla_calltip_new(void);

#ifndef WM_UNICHAR
#define WM_UNICHAR                      0x0109
#endif

// required for paste/dragdrop, see comment in paste function below
static int BOMlen(unsigned char *cstr) {
  switch(cstr[0]) { 
  case 0xEF: // BOM_UTF8
    if (cstr[1] == 0xBB && cstr[2] == 0xBF) {
      return 3;
    }
    break;
  case 0xFE:
    if (cstr[1] == 0xFF) {
      if (cstr[2] == 0x00 && cstr[3] == 0x00) {
        return 4;
      }
      return 2;
    }
    break;
  case 0xFF:
    if (cstr[1] == 0xFE) {
      if (cstr[2] == 0x00 && cstr[3] == 0x00) {
        return 4;
      }
      return 2;
    }
    break;
  case 0x00:
    if (cstr[1] == 0x00) {
      if (cstr[2] == 0xFE && cstr[3] == 0xFF) {
        return 4;
      }
      if (cstr[2] == 0xFF && cstr[3] == 0xFE) {
        return 4;
      }
      return 2;
    }
    break;
  }

  return 0;
}

#ifdef EXT_INPUT
#define SCI_CMD ( SCI_ALT | SCI_CTRL | SCI_SHIFT)

static const KeyToCommand macMapDefault[] = {
    {SCK_DOWN,		SCI_CMD,	SCI_DOCUMENTEND},
    {SCK_UP,		SCI_CMD,	SCI_DOCUMENTSTART},
    {SCK_LEFT,		SCI_CMD,	SCI_VCHOME},
    {SCK_RIGHT,		SCI_CMD,	SCI_LINEEND},
    {SCK_DOWN,		SCI_NORM,	SCI_LINEDOWN},
    {SCK_DOWN,		SCI_SHIFT,	SCI_LINEDOWNEXTEND},
    {SCK_DOWN,		SCI_CTRL,	SCI_LINESCROLLDOWN},
    {SCK_DOWN,		SCI_ASHIFT,	SCI_LINEDOWNRECTEXTEND},
    {SCK_UP,		SCI_NORM,	SCI_LINEUP},
    {SCK_UP,			SCI_SHIFT,	SCI_LINEUPEXTEND},
    {SCK_UP,			SCI_CTRL,	SCI_LINESCROLLUP},
    {SCK_UP,		SCI_ASHIFT,	SCI_LINEUPRECTEXTEND},
    {'[',			SCI_CTRL,		SCI_PARAUP},
    {'[',			SCI_CSHIFT,	SCI_PARAUPEXTEND},
    {']',			SCI_CTRL,		SCI_PARADOWN},
    {']',			SCI_CSHIFT,	SCI_PARADOWNEXTEND},
    {SCK_LEFT,		SCI_NORM,	SCI_CHARLEFT},
    {SCK_LEFT,		SCI_SHIFT,	SCI_CHARLEFTEXTEND},
    {SCK_LEFT,		SCI_ALT,	SCI_WORDLEFT},
    {SCK_LEFT,		SCI_CSHIFT,	SCI_WORDLEFTEXTEND},
    {SCK_LEFT,		SCI_ASHIFT,	SCI_CHARLEFTRECTEXTEND},
    {SCK_RIGHT,		SCI_NORM,	SCI_CHARRIGHT},
    {SCK_RIGHT,		SCI_SHIFT,	SCI_CHARRIGHTEXTEND},
    {SCK_RIGHT,		SCI_ALT,	SCI_WORDRIGHT},
    {SCK_RIGHT,		SCI_CSHIFT,	SCI_WORDRIGHTEXTEND},
    {SCK_RIGHT,		SCI_ASHIFT,	SCI_CHARRIGHTRECTEXTEND},
    {'/',		SCI_CTRL,		SCI_WORDPARTLEFT},
    {'/',		SCI_CSHIFT,	SCI_WORDPARTLEFTEXTEND},
    {'\\',		SCI_CTRL,		SCI_WORDPARTRIGHT},
    {'\\',		SCI_CSHIFT,	SCI_WORDPARTRIGHTEXTEND},
    {SCK_HOME,		SCI_NORM,	SCI_VCHOME},
    {SCK_HOME, 		SCI_SHIFT, 	SCI_VCHOMEEXTEND},
    {SCK_HOME, 		SCI_CTRL, 	SCI_DOCUMENTSTART},
    {SCK_HOME, 		SCI_CSHIFT, 	SCI_DOCUMENTSTARTEXTEND},
    {SCK_HOME, 		SCI_ALT, 	SCI_HOMEDISPLAY},
//    {SCK_HOME,		SCI_ASHIFT,	SCI_HOMEDISPLAYEXTEND},
    {SCK_HOME,		SCI_ASHIFT,	SCI_VCHOMERECTEXTEND},
    {SCK_END,	 	SCI_NORM,	SCI_LINEEND},
    {SCK_END,	 	SCI_SHIFT, 	SCI_LINEENDEXTEND},
    {SCK_END, 		SCI_CTRL, 	SCI_DOCUMENTEND},
    {SCK_END, 		SCI_CSHIFT, 	SCI_DOCUMENTENDEXTEND},
    {SCK_END, 		SCI_ALT, 	SCI_LINEENDDISPLAY},
//    {SCK_END,		SCI_ASHIFT,	SCI_LINEENDDISPLAYEXTEND},
    {SCK_END,		SCI_ASHIFT,	SCI_LINEENDRECTEXTEND},
    {SCK_PRIOR,		SCI_NORM,	SCI_PAGEUP},
    {SCK_PRIOR,		SCI_SHIFT, 	SCI_PAGEUPEXTEND},
    {SCK_PRIOR,		SCI_ASHIFT,	SCI_PAGEUPRECTEXTEND},
    {SCK_NEXT, 		SCI_NORM, 	SCI_PAGEDOWN},
    {SCK_NEXT, 		SCI_SHIFT, 	SCI_PAGEDOWNEXTEND},
    {SCK_NEXT,		SCI_ASHIFT,	SCI_PAGEDOWNRECTEXTEND},
    {SCK_DELETE, 	SCI_NORM,	SCI_CLEAR},
    {SCK_DELETE, 	SCI_SHIFT,	SCI_CUT},
    {SCK_DELETE, 	SCI_CTRL,	SCI_DELWORDRIGHT},
    {SCK_DELETE,	SCI_CSHIFT,	SCI_DELLINERIGHT},
    {SCK_INSERT, 		SCI_NORM,	SCI_EDITTOGGLEOVERTYPE},
    {SCK_INSERT, 		SCI_SHIFT,	SCI_PASTE},
    {SCK_INSERT, 		SCI_CTRL,	SCI_COPY},
    {SCK_ESCAPE,  	SCI_NORM,	SCI_CANCEL},
    {SCK_BACK,		SCI_NORM, 	SCI_DELETEBACK},
    {SCK_BACK,		SCI_SHIFT, 	SCI_DELETEBACK},
    {SCK_BACK,		SCI_CTRL, 	SCI_DELWORDLEFT},
    {SCK_BACK, 		SCI_ALT,	SCI_UNDO},
    {SCK_BACK,		SCI_CSHIFT,	SCI_DELLINELEFT},
    {'Z', 			SCI_CTRL,	SCI_UNDO},
    {'Y', 			SCI_CTRL,	SCI_REDO},
    {'X', 			SCI_CTRL,	SCI_CUT},
    {'C', 			SCI_CTRL,	SCI_COPY},
    {'V', 			SCI_CTRL,	SCI_PASTE},
    {'A', 			SCI_CTRL,	SCI_SELECTALL},
    {SCK_TAB,		SCI_NORM,	SCI_TAB},
    {SCK_TAB,		SCI_SHIFT,	SCI_BACKTAB},
    {SCK_RETURN, 	SCI_NORM,	SCI_NEWLINE},
    {SCK_RETURN, 	SCI_SHIFT,	SCI_NEWLINE},
    {SCK_ADD, 		SCI_CTRL,	SCI_ZOOMIN},
    {SCK_SUBTRACT,	SCI_CTRL,	SCI_ZOOMOUT},
    {SCK_DIVIDE,	SCI_CTRL,	SCI_SETZOOM},
    //'L', 			SCI_CTRL,		SCI_FORMFEED,
    {'L', 			SCI_CTRL,	SCI_LINECUT},
    {'L', 			SCI_CSHIFT,	SCI_LINEDELETE},
    {'T', 			SCI_CSHIFT,	SCI_LINECOPY},
    {'T', 			SCI_CTRL,	SCI_LINETRANSPOSE},
    {'D', 			SCI_CTRL,	SCI_SELECTIONDUPLICATE},
    {'U', 			SCI_CTRL,	SCI_LOWERCASE},
    {'U', 			SCI_CSHIFT,	SCI_UPPERCASE},
    {0,0,0},
};
#endif

ScintillaMacOSX::ScintillaMacOSX( void* windowid ) :
        TView( reinterpret_cast<HIViewRef>( windowid ) )
{
    notifyObj = NULL;
    notifyProc = NULL;
    wMain = windowid;
    OSStatus err;
    err = GetThemeMetric( kThemeMetricScrollBarWidth, &scrollBarFixedSize );
    assert( err == noErr );

    mouseTrackingRef = NULL;
    mouseTrackingID.signature = scintillaMacOSType;
    mouseTrackingID.id = (SInt32)this;
    capturedMouse = false;

    // Enable keyboard events and mouse events
#if !defined(CONTAINER_HANDLES_EVENTS)
    ActivateInterface( kKeyboardFocus );
    ActivateInterface( kMouse );
    ActivateInterface( kDragAndDrop );
#endif
    ActivateInterface( kMouseTracking );

    Initialise();

    // Create some bounds rectangle which will just get reset to the correct rectangle later
    Rect tempScrollRect;
    tempScrollRect.top = -1;
    tempScrollRect.left = 400;
    tempScrollRect.bottom = 300;
    tempScrollRect.right = 450;

    // Create the scroll bar with fake values that will get set correctly later
    err = CreateScrollBarControl( this->GetOwner(), &tempScrollRect, 0, 0, 100, 100, true, LiveScrollHandler, &vScrollBar );
    assert( vScrollBar != NULL && err == noErr );
    err = CreateScrollBarControl( this->GetOwner(), &tempScrollRect, 0, 0, 100, 100, true, LiveScrollHandler, &hScrollBar );
    assert( hScrollBar != NULL && err == noErr );

    // Set a property on the scrollbars to store a pointer to the Scintilla object
    ScintillaMacOSX* objectPtr = this;
    err = SetControlProperty( vScrollBar, scintillaMacOSType, 0, sizeof( this ), &objectPtr );
    assert( err == noErr );
    err = SetControlProperty( hScrollBar, scintillaMacOSType, 0, sizeof( this ), &objectPtr );
    assert( err == noErr );

    // set this into our parent control so we can be retrieved easily at a later time 
    // (see scintilla_send below)
    err = SetControlProperty( reinterpret_cast<HIViewRef>( windowid ), scintillaMacOSType, 0, sizeof( this ), &objectPtr );
    assert( err == noErr );

    // Tell Scintilla not to buffer: Quartz buffers drawing for us
    // TODO: Can we disable this option on Mac OS X?
    WndProc( SCI_SETBUFFEREDDRAW, 0, 0 );
    // Turn on UniCode mode
    WndProc( SCI_SETCODEPAGE, SC_CP_UTF8, 0 );

    const EventTypeSpec commandEventInfo[] = {
        { kEventClassCommand, kEventProcessCommand },
        { kEventClassCommand, kEventCommandUpdateStatus },
    };

    err = InstallEventHandler( GetControlEventTarget( reinterpret_cast<HIViewRef>( windowid ) ), 
                   CommandEventHandler,
                   GetEventTypeCount( commandEventInfo ), 
                   commandEventInfo,
                   this, NULL);
#ifdef EXT_INPUT
    ExtInput::attach (GetViewRef());
    for (int i = 0; macMapDefault[i].key; i++) 
    {
        this->kmap.AssignCmdKey(macMapDefault[i].key, macMapDefault[i].modifiers, macMapDefault[i].msg);
    }
#endif
}

ScintillaMacOSX::~ScintillaMacOSX() {
    // If the window is closed and the timer is not removed,
    // A segment violation will occur when it attempts to fire the timer next.
    if ( mouseTrackingRef != NULL ) {
        ReleaseMouseTrackingRegion(mouseTrackingRef);
    }
    mouseTrackingRef = NULL;
    SetTicking(false);
#ifdef EXT_INPUT
    ExtInput::detach (GetViewRef());
#endif
}

void ScintillaMacOSX::Initialise() {
    // TODO: Do anything here? Maybe this stuff should be here instead of the constructor?
}

void ScintillaMacOSX::Finalise() {
    SetTicking(false);
    ScintillaBase::Finalise();
}

// --------------------------------------------------------------------------------------------------------------
//
// IsDropInFinderTrash - Returns true if the given dropLocation AEDesc is a descriptor of the Finder's Trash.
//
#pragma segment Drag

Boolean IsDropInFinderTrash(AEDesc *dropLocation)
{
    OSErr      result;
    AEDesc      dropSpec;
    FSSpec      *theSpec;
    CInfoPBRec    thePB;
    short      trashVRefNum;
    long      trashDirID;
  
    //  Coerce the dropLocation descriptor into an FSSpec. If there's no dropLocation or
    //  it can't be coerced into an FSSpec, then it couldn't have been the Trash.

  if ((dropLocation->descriptorType != typeNull) &&
    (AECoerceDesc(dropLocation, typeFSS, &dropSpec) == noErr)) 
    {
        unsigned char flags = HGetState((Handle)dropSpec.dataHandle);
        
        HLock((Handle)dropSpec.dataHandle);
        theSpec = (FSSpec *) *dropSpec.dataHandle;
        
        //  Get the directory ID of the given dropLocation object.
        
        thePB.dirInfo.ioCompletion = 0L;
        thePB.dirInfo.ioNamePtr = (StringPtr) &theSpec->name;
        thePB.dirInfo.ioVRefNum = theSpec->vRefNum;
        thePB.dirInfo.ioFDirIndex = 0;
        thePB.dirInfo.ioDrDirID = theSpec->parID;
        
        result = PBGetCatInfoSync(&thePB);
        
        HSetState((Handle)dropSpec.dataHandle, flags);
        AEDisposeDesc(&dropSpec);
        
        if (result != noErr)
            return false;
        
        //  If the result is not a directory, it must not be the Trash.
        
        if (!(thePB.dirInfo.ioFlAttrib & (1 << 4)))
            return false;
        
        //  Get information about the Trash folder.
        
        FindFolder(theSpec->vRefNum, kTrashFolderType, kCreateFolder, &trashVRefNum, &trashDirID);
        
        //  If the directory ID of the dropLocation object is the same as the directory ID
        //  returned by FindFolder, then the drop must have occurred into the Trash.
        
        if (thePB.dirInfo.ioDrDirID == trashDirID)
            return true;
    }

    return false;

} // IsDropInFinderTrash

HIPoint ScintillaMacOSX::GetLocalPoint(::Point pt)
{
    // get the mouse position so we can offset it
    Rect bounds;
    GetWindowBounds( GetOwner(), kWindowStructureRgn, &bounds );

    PRectangle hbounds = wMain.GetPosition();
    HIViewRef parent = HIViewGetSuperview(GetViewRef());
    Rect pbounds;
    GetControlBounds(parent, &pbounds);
      
    bounds.left += pbounds.left + hbounds.left;
    bounds.top += pbounds.top + hbounds.top;

    HIPoint offset = { pt.h - bounds.left, pt.v - bounds.top };
    return offset;
}

void ScintillaMacOSX::StartDrag() {
    if (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;
    CFStringRef textString = NULL;
    bool isRectangular = selectedText ? selectedText->rectangular : false;
    ItemCount i, itemCount;
    OSStatus status = PasteboardGetItemCount(pasteBoard, &itemCount);
    if (status != noErr) {
        return false;
    }

    // as long as we didn't get our text, let's loop on the items. We stop as soon as we get it
    CFArrayRef flavorTypeArray = NULL;
    bool haveMatch = false;
    for (i = 1; i <= itemCount; i++)
    {
        PasteboardItemID itemID;
        CFIndex j, flavorCount = 0;

        status = PasteboardGetItemIdentifier(pasteBoard, i, &itemID);
        if (status != noErr) {
            return false;
        }

        // how many flavors in this item?
        status = PasteboardCopyItemFlavors(pasteBoard, itemID, &flavorTypeArray);
        if (status != noErr) {
            return false;
        }

        if (flavorTypeArray != NULL)
            flavorCount = CFArrayGetCount(flavorTypeArray);

        // as long as we didn't get our text, let's loop on the flavors. We stop as soon as we get it
        for(j = 0; j < flavorCount; j++)
        {
            CFDataRef flavorData;
            CFStringRef flavorType = (CFStringRef)CFArrayGetValueAtIndex(flavorTypeArray, j);
            if (flavorType != NULL) 
            {
                int format = kFormatBad;
                if (UTTypeConformsTo(flavorType, CFSTR("public.file-url"))) {
                    format = kFormatFile;
                    *isFileURL = true;
                }
                else if (UTTypeConformsTo(flavorType, CFSTR("com.scintilla.utf16-plain-text.rectangular"))) {
                    format = kFormatUnicode;
                    isRectangular = true;
                }
                else if (UTTypeConformsTo(flavorType, CFSTR("public.utf16-plain-text"))) { // this is 'utxt'
                    format = kFormatUnicode;
                }
                else if (UTTypeConformsTo(flavorType, CFSTR("public.utf8-plain-text"))) {
                    format = kFormatUTF8;
                }
                else if (UTTypeConformsTo(flavorType, CFSTR("com.apple.traditional-mac-plain-text"))) { // this is 'TEXT'
                    format = kFormatText;
                }
                if (format == kFormatBad)
                    continue;
                
                // if we got a flavor match, and we have no textString, we just want
                // to know that we can accept this data, so jump out now
                if (selectedText == NULL) {
                    haveMatch = true;
                    goto PasteboardDataRetrieved;
                }
                if (PasteboardCopyItemFlavorData(pasteBoard, itemID, flavorType, &flavorData) == noErr)
                {
                    CFIndex dataSize = CFDataGetLength (flavorData);
                    const UInt8* dataBytes = CFDataGetBytePtr (flavorData);
                    switch (format)
                    {
                        case kFormatFile:
                        case kFormatText:
                            data = CFDataCreate (NULL, dataBytes, dataSize);
                            textString = CFStringCreateFromExternalRepresentation (NULL, data, kCFStringEncodingMacRoman);
                            break;
                        case kFormatUnicode:
                            data = CFDataCreate (NULL, dataBytes, dataSize);
                            textString = CFStringCreateFromExternalRepresentation (NULL, data, kCFStringEncodingUnicode);
                            break;
                        case kFormatUTF8:
                            data = CFDataCreate (NULL, dataBytes, dataSize);
                            textString = CFStringCreateFromExternalRepresentation (NULL, data, kCFStringEncodingUTF8);
                            break;
                    }
                    CFRelease (flavorData);
                    goto PasteboardDataRetrieved;
                }
            }
        }
    }
PasteboardDataRetrieved:
    if (flavorTypeArray != NULL) CFRelease(flavorTypeArray);
        int newlen = 0;
    if (textString != NULL) {
        selectedText->s = GetStringFromCFString(textString, &selectedText->len);
        selectedText->rectangular = isRectangular;
        // Default allocator releases both the CFString and the UniChar buffer (text)
        CFRelease( textString );
        textString = NULL;
    }
    if (haveMatch || selectedText != NULL && selectedText->s != NULL) {
        return true;
    }
    return false;
}

char *ScintillaMacOSX::GetStringFromCFString(CFStringRef &textString, int *textLen)
{

    // Allocate a buffer, plus the null byte
    CFIndex numUniChars = CFStringGetLength( textString );
    CFStringEncoding encoding = ( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingMacRoman);
    CFIndex maximumByteLength = CFStringGetMaximumSizeForEncoding( numUniChars, encoding ) + 1;
    char* cstring = new char[maximumByteLength];
    CFIndex usedBufferLength = 0;
    CFIndex numCharsConverted;
    numCharsConverted = CFStringGetBytes( textString, CFRangeMake( 0, numUniChars ), encoding,
                              '?', false, reinterpret_cast<UInt8*>( cstring ),
                              maximumByteLength, &usedBufferLength );
    cstring[usedBufferLength] = '\0'; // null terminate the ASCII/UTF8 string

    // determine whether a BOM is in the string.  Apps like Emacs prepends a BOM
    // to the string, CFStrinGetBytes reflects that (though it may change in the conversion)
    // so we need to remove it before pasting into our buffer.  TextWrangler has no
    // problem dealing with BOM when pasting into it.
    int bomLen = BOMlen((unsigned char *)cstring);

    // convert line endings to the document line ending
    *textLen = 0;
    char *result = Document::TransformLineEnds(textLen,
                                      cstring + bomLen,
                                      usedBufferLength - bomLen,
                                      pdoc->eolMode);
    delete[] cstring;
    return result;
}

OSStatus ScintillaMacOSX::DragReceive(DragRef inDrag )
{
    // dragleave IS called, but for some reason (probably to do with inDrag)
    // the hide hilite does not happen unless we do it here
    HideDragHilite( inDrag );

    PasteboardRef pasteBoard;
    SelectionText selectedText;
    CFStringRef textString = NULL;
    bool isFileURL = false;
    if (!GetDragData(inDrag, pasteBoard, &selectedText, &isFileURL)) {
        return dragNotAcceptedErr;
    }

    if (isFileURL) {
        NotifyURIDropped(selectedText.s);
    } else {
        // figure out if this is a move or a paste
        DragAttributes attributes;
        SInt16 modifiers = 0; 
        GetDragAttributes( inDrag, &attributes );
        bool moving = true;
    
        int position = PositionFromLocation(GetDragPoint(inDrag));
        if ( attributes & kDragInsideSenderWindow ) {
            GetDragModifiers(inDrag, NULL, NULL, &modifiers);
            switch (modifiers & ~btnState)  // Filter out btnState (on for drop)
            {
            case optionKey:
                // default is copy text
                moving = false;
                break;
            case cmdKey:
            case cmdKey | optionKey:
            default:
                // what to do with these?  rectangular drag?
                break;
            }
        }

        DropAt(position, selectedText.s, moving, selectedText.rectangular);
    }

    return noErr;
}

// Extended UTF8-UTF6-conversion to handle surrogate pairs correctly (CL265070)
void ScintillaMacOSX::InsertCharacters (const UniChar* buf, int len)
{
    CFStringRef str = CFStringCreateWithCharactersNoCopy (NULL, buf, (UInt32) len, kCFAllocatorNull);
    CFStringEncoding encoding = ( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingMacRoman);
    CFRange range = { 0, len };
    CFIndex bufLen;
    CFStringGetBytes (str, range, encoding, '?', false, NULL, 0, &bufLen);
    UInt8* utf8buf = new UInt8 [bufLen];
    CFStringGetBytes (str, range, encoding, '?', false, utf8buf, bufLen, NULL);
    AddCharUTF ((char*) utf8buf, bufLen, false);
    delete [] utf8buf;
    CFRelease (str);
}

/** The simulated message loop. */
sptr_t ScintillaMacOSX::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
    switch (iMessage) {
    case SCI_GETDIRECTFUNCTION:
        Platform::DebugDisplay( "ScintillaMacOSX::WndProc: Returning DirectFunction address.\n" );
        return reinterpret_cast<sptr_t>( DirectFunction );
    
    case SCI_GETDIRECTPOINTER:
        Platform::DebugDisplay( "ScintillaMacOSX::WndProc: Returning Direct pointer address.\n" );
        return reinterpret_cast<sptr_t>( this );
    
    case SCI_GRABFOCUS:
        Platform::DebugDisplay( "ScintillaMacOSX::WndProc: Got an unhandled message. Ignoring it.\n" );
         break;
    case WM_UNICHAR:
        if (IsUnicodeMode()) {
            // Extended UTF8-UTF6-conversion to handle surrogate pairs correctly (CL265070)
            UniChar wcs[1] = { (UniChar) wParam};
            InsertCharacters(wcs, 1);
            return 1;
        } else {
            return 0;
        }

    default:
        unsigned int r = ScintillaBase::WndProc(iMessage, wParam, lParam);

        return r;
    }
    return 0l;
}

sptr_t ScintillaMacOSX::DefWndProc(unsigned int, uptr_t, sptr_t) {
    return 0;
}

void ScintillaMacOSX::SetTicking(bool on) {
    if (timer.ticking != on) {
        timer.ticking = on;
        if (timer.ticking) {
            // Scintilla ticks = milliseconds
            EventLoopTimerRef timerRef = NULL;
            InstallTimer( timer.tickSize * kEventDurationMillisecond, &timerRef );
            assert( timerRef != NULL );
            timer.tickerID = reinterpret_cast<TickerID>( timerRef );
        } else if ( timer.tickerID != NULL ) {
            RemoveEventLoopTimer( reinterpret_cast<EventLoopTimerRef>( timer.tickerID ) );
        }
    }
    timer.ticksToWait = caret.period;
}

bool ScintillaMacOSX::SetIdle(bool on) {
    if (on) {
        // Start idler, if it's not running.
        if (idler.state == false) {
            idler.state = true;
            EventLoopTimerRef idlTimer;
            InstallEventLoopIdleTimer(GetCurrentEventLoop(),
                                      timer.tickSize * kEventDurationMillisecond,
                                      75 * kEventDurationMillisecond,
                                      IdleTimerEventHandler, this, &idlTimer);
            idler.idlerID = reinterpret_cast<IdlerID>( idlTimer );
        }
    } else {
        // Stop idler, if it's running
        if (idler.state == true) {
            idler.state = false;
            if (idler.idlerID != NULL)
                RemoveEventLoopTimer( reinterpret_cast<EventLoopTimerRef>( idler.idlerID ) );
        }
    }
    return true;
}

pascal void ScintillaMacOSX::IdleTimerEventHandler( EventLoopTimerRef inTimer, 
                                                    EventLoopIdleTimerMessage inState,
                                                    void *scintilla )
{
    ScintillaMacOSX *sciThis = reinterpret_cast<ScintillaMacOSX*>( scintilla );
    bool ret = sciThis->Idle();
    if (ret == false) {
        sciThis->SetIdle(false);
    }
}

void ScintillaMacOSX::SetMouseCapture(bool on) {
    capturedMouse = on;
    if (mouseDownCaptures) {
        if (capturedMouse) {
            WndProc(SCI_SETCURSOR, Window::cursorArrow, 0);
        } else {
            // reset to normal, buttonmove will change for other area's in the editor
            WndProc(SCI_SETCURSOR, (long int)SC_CURSORNORMAL, 0);
        }
    }
}

bool ScintillaMacOSX::HaveMouseCapture() {
    return capturedMouse;
}

// The default GetClientRectangle calls GetClientPosition on wMain.
// We override it to return "view local" co-ordinates so we can draw properly
// plus we need to remove the space occupied by the scroll bars
PRectangle ScintillaMacOSX::GetClientRectangle() {
    PRectangle rc = wMain.GetClientPosition();
    if (verticalScrollBarVisible)
        rc.right -= scrollBarFixedSize + 1;
    if (horizontalScrollBarVisible && (wrapState == eWrapNone))
        rc.bottom -= scrollBarFixedSize + 1;
    // Move to origin
    rc.right -= rc.left;
    rc.bottom -= rc.top;
    rc.left = 0;
    rc.top = 0;
    return rc;
}

// Synchronously paint a rectangle of the window.
void ScintillaMacOSX::SyncPaint(void* gc, PRectangle rc) {
    paintState = painting;
    rcPaint = rc;
    PRectangle rcText = GetTextRectangle();
    paintingAllText = rcPaint.Contains(rcText);
    //Platform::DebugPrintf("ScintillaMacOSX::SyncPaint %0d,%0d %0d,%0d\n",
    //  rcPaint.left, rcPaint.top, rcPaint.right, rcPaint.bottom);
    Surface *sw = Surface::Allocate();
    if (sw) {
        sw->Init( gc, wMain.GetID() );
        Paint(sw, rc);
        if (paintState == paintAbandoned) {
          // do a FULL paint.
          rcPaint = GetClientRectangle();
          paintState = painting;
          paintingAllText = true;
          Paint(sw, rcPaint);
          wMain.InvalidateAll();
        }
        sw->Release();
        delete sw;
    }
    paintState = notPainting;
}

void ScintillaMacOSX::ScrollText(int /*linesToMove*/) {
    // This function will invalidate the correct regions of the view,
    // So shortly after this happens, draw will be called.
    // But I'm not quite sure how this works ...
    // I have a feeling that it is only supposed to work in conjunction with an HIScrollView.
    // TODO: Cook up my own bitblt scroll: Grab the bits on screen, blit them shifted, invalidate the remaining stuff
    //CGRect r = CGRectMake( 0, 0, rc.Width(), rc.Height() );
    //HIViewScrollRect( reinterpret_cast<HIViewRef>( wMain.GetID() ), NULL, 0, vs.lineHeight * linesToMove );
    wMain.InvalidateAll();
}

void ScintillaMacOSX::SetVerticalScrollPos() {
    SetControl32BitValue( vScrollBar, topLine );
}

void ScintillaMacOSX::SetHorizontalScrollPos() {
    SetControl32BitValue( hScrollBar, xOffset );
}

bool ScintillaMacOSX::ModifyScrollBars(int nMax, int nPage) {
    Platform::DebugPrintf( "nMax: %d nPage: %d hScroll (%d -> %d) page: %d\n", nMax, nPage, 0, scrollWidth, GetTextRectangle().Width() );
    // Minimum value = 0
    // TODO: This is probably not needed, since we set this when the scroll bars are created
    SetControl32BitMinimum( vScrollBar, 0 );
    SetControl32BitMinimum( hScrollBar, 0 );

    // Maximum vertical value = nMax + 1 - nPage (lines available to scroll)
    SetControl32BitMaximum( vScrollBar, Platform::Maximum( nMax + 1 - nPage, 0 ) );
    // Maximum horizontal value = scrollWidth - GetTextRectangle().Width() (pixels available to scroll)
    SetControl32BitMaximum( hScrollBar, Platform::Maximum( scrollWidth - GetTextRectangle().Width(), 0 ) );

    // Vertical page size = nPage
    SetControlViewSize( vScrollBar, nPage );
    // Horizontal page size = TextRectangle().Width()
    SetControlViewSize( hScrollBar, GetTextRectangle().Width() );

    // TODO: Verify what this return value is for
    // The scroll bar components will handle if they need to be rerendered or not
    return false;
}

void ScintillaMacOSX::ReconfigureScrollBars() {
    PRectangle rc = wMain.GetClientPosition();
    Resize(rc.Width(), rc.Height());
}

void ScintillaMacOSX::Resize(int width, int height) {
    // Get the horizontal/vertical size of the scroll bars
    GetThemeMetric( kThemeMetricScrollBarWidth, &scrollBarFixedSize );

    bool showSBHorizontal = horizontalScrollBarVisible && (wrapState == eWrapNone);
    HIRect scrollRect;
    if (verticalScrollBarVisible) {
        scrollRect.origin.x = width - scrollBarFixedSize;
        scrollRect.origin.y = 0;
        scrollRect.size.width = scrollBarFixedSize;
        if (showSBHorizontal) {
            scrollRect.size.height = Platform::Maximum(1, height - scrollBarFixedSize);
        } else {
            scrollRect.size.height = height;
        }

        HIViewSetFrame( vScrollBar, &scrollRect );
        if (HIViewGetSuperview(vScrollBar) == NULL) {
            HIViewSetDrawingEnabled( vScrollBar, true );
            HIViewSetVisible(vScrollBar, true);
            HIViewAddSubview(GetViewRef(), vScrollBar );
            Draw1Control(vScrollBar);
        }
    } else if (HIViewGetSuperview(vScrollBar) != NULL) {
        HIViewSetDrawingEnabled( vScrollBar, false );
        HIViewRemoveFromSuperview(vScrollBar);
    }

    if (showSBHorizontal) {
        scrollRect.origin.x = 0;
        // Always draw the scrollbar to avoid the "potiential" horizontal scroll bar and to avoid the resize box.
        // This should be "good enough". Best would be to avoid the resize box.
        // Even better would be to embed Scintilla inside an HIScrollView, which would handle this for us.
        scrollRect.origin.y = height - scrollBarFixedSize;
        if (verticalScrollBarVisible) {
            scrollRect.size.width = Platform::Maximum( 1, width - scrollBarFixedSize );
        } else {
            scrollRect.size.width = width;
        }
        scrollRect.size.height = scrollBarFixedSize;

        HIViewSetFrame( hScrollBar, &scrollRect );
        if (HIViewGetSuperview(hScrollBar) == NULL) {
            HIViewSetDrawingEnabled( hScrollBar, true );
            HIViewAddSubview( GetViewRef(), hScrollBar );
            Draw1Control(hScrollBar);
        }
    } else  if (HIViewGetSuperview(hScrollBar) != NULL) {
        HIViewSetDrawingEnabled( hScrollBar, false );
        HIViewRemoveFromSuperview(hScrollBar);
    }

    ChangeSize();

    // fixup mouse tracking regions, this causes mouseenter/exit to work
    if (HIViewGetSuperview(GetViewRef()) != NULL) {
        RgnHandle rgn = NewRgn();
        HIRect r;
        HIViewGetFrame( reinterpret_cast<HIViewRef>( GetViewRef() ), &r );
        SetRectRgn(rgn, short (r.origin.x), short (r.origin.y), 
                  short (r.origin.x + r.size.width - (verticalScrollBarVisible ? scrollBarFixedSize : 0)), 
                  short (r.origin.y + r.size.height - (showSBHorizontal ? scrollBarFixedSize : 0)));
        if (mouseTrackingRef == NULL) {
            CreateMouseTrackingRegion(GetOwner(), rgn, NULL, 
                                        kMouseTrackingOptionsLocalClip,
                                        mouseTrackingID, NULL, 
                                        GetControlEventTarget( GetViewRef() ), 
                                        &mouseTrackingRef);
        } else {
            ChangeMouseTrackingRegion(mouseTrackingRef, rgn, NULL);
        }
        DisposeRgn(rgn);
    } else {
        if (mouseTrackingRef != NULL) {
            ReleaseMouseTrackingRegion(mouseTrackingRef);
        }
        mouseTrackingRef = NULL;
    }
}

pascal void ScintillaMacOSX::LiveScrollHandler( HIViewRef control, SInt16 part )
{
    int currentValue = GetControl32BitValue( control );
    int min = GetControl32BitMinimum( control );
    int max = GetControl32BitMaximum( control );
    int page = GetControlViewSize( control );

    // Get a reference to the Scintilla C++ object
    ScintillaMacOSX* scintilla = NULL;
    OSStatus err;
    err = GetControlProperty( control, scintillaMacOSType, 0, sizeof( scintilla ), NULL, &scintilla );
    assert( err == noErr && scintilla != NULL );

    int singleScroll = 0;
    if ( control == scintilla->vScrollBar )
    {
        // Vertical single scroll = one line
        // TODO: Is there a Scintilla preference for this somewhere?
        singleScroll = 1;
    } else {
        assert( control == scintilla->hScrollBar );
        // Horizontal single scroll = 20 pixels (hardcoded from ScintillaWin)
        // TODO: Is there a Scintilla preference for this somewhere?
        singleScroll = 20;
    }

    // Determine the new value
    int newValue = 0;
    switch ( part )
    {
    case kControlUpButtonPart:
        newValue = Platform::Maximum( currentValue - singleScroll, min );
        break;

    case kControlDownButtonPart:
        // the the user scrolls to the right, allow more scroll space
        if ( control == scintilla->hScrollBar && currentValue >= max) {
          // change the max value
          scintilla->scrollWidth += singleScroll;
          SetControl32BitMaximum( control,
                           Platform::Maximum( scintilla->scrollWidth - scintilla->GetTextRectangle().Width(), 0 ) );
          max = GetControl32BitMaximum( control );
          scintilla->SetScrollBars();
        }
        newValue =  Platform::Minimum( currentValue + singleScroll, max );
        break;

    case kControlPageUpPart:
        newValue = Platform::Maximum( currentValue - page, min );
        break;

    case kControlPageDownPart:
        newValue = Platform::Minimum( currentValue + page, max );
        break;

    case kControlIndicatorPart:
    case kControlNoPart:
        newValue = currentValue;
        break;

    default:
        assert( false );
        return;
    }

    // Set the new value
    if ( control == scintilla->vScrollBar )
    {
        scintilla->ScrollTo( newValue );
    } else {
        assert( control == scintilla->hScrollBar );
        scintilla->HorizontalScrollTo( newValue );
    }
}

bool ScintillaMacOSX::ScrollBarHit(HIPoint location) {
	// is this on our scrollbars?  If so, track them
	HIViewRef view;
	// view is null if on editor, otherwise on scrollbar
	HIViewGetSubviewHit(reinterpret_cast<ControlRef>(wMain.GetID()),
                            &location, true, &view);
	if (view) {
		HIViewPartCode part;

		// make the point local to a scrollbar 
                PRectangle client = GetClientRectangle();
		if (view == vScrollBar) {
			location.x -= client.Width();
		} else if (view == hScrollBar) {
			location.y -= client.Height();
		} else {
			fprintf(stderr, "got a subview hit, but not a scrollbar???\n");
                        return false;
		}
                
		HIViewGetPartHit(view, &location, &part);
	
		switch (part)
		{
			case kControlUpButtonPart:
			case kControlDownButtonPart:
			case kControlPageUpPart:
			case kControlPageDownPart:
			case kControlIndicatorPart:
                                ::Point p;
                                p.h = location.x;
                                p.v = location.y;
				// We are assuming Appearance 1.1 or later, so we
				// have the "live scroll" variant of the scrollbar,
				// which lets you pass the action proc to TrackControl
				// for the thumb (this was illegal in previous
				// versions of the defproc).
				isTracking = true;
				::TrackControl(view, p, ScintillaMacOSX::LiveScrollHandler);
				::HiliteControl(view, 0);
				isTracking = false;
				// The mouseup was eaten by TrackControl, however if we
				// do not get a mouseup in the scintilla xbl widget,
				// many bad focus issues happen.  Simply post a mouseup
				// and this firey pit becomes a bit cooler.
				PostEvent(mouseUp, 0);
				break;
			default:
				fprintf(stderr, "PlatformScrollBarHit part %d\n", part);
		}
		return true;
	}
	return false;
}

void ScintillaMacOSX::NotifyFocus(bool focus) {
#ifdef EXT_INPUT
    ExtInput::activate (GetViewRef(), focus);
#endif
    if (NULL != notifyProc)
        notifyProc (notifyObj, WM_COMMAND, 
                (uintptr_t) ((focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS) << 16),
                (uintptr_t) GetViewRef());
}

void ScintillaMacOSX::NotifyChange() {
    if (NULL != notifyProc)
        notifyProc (notifyObj, WM_COMMAND, 
                (uintptr_t) (SCEN_CHANGE << 16),
                (uintptr_t) GetViewRef());
}

void ScintillaMacOSX::registerNotifyCallback(intptr_t windowid, SciNotifyFunc callback) {
    notifyObj = windowid;
    notifyProc = callback;
}

void ScintillaMacOSX::NotifyParent(SCNotification scn) { 
    if (NULL != notifyProc) {
        scn.nmhdr.hwndFrom = (void*) this;
        scn.nmhdr.idFrom = (unsigned int)wMain.GetID();
        notifyProc (notifyObj, WM_NOTIFY, (uintptr_t) 0, (uintptr_t) &scn);
    }
}

void ScintillaMacOSX::NotifyKey(int key, int modifiers) {
    SCNotification scn;
    scn.nmhdr.code = SCN_KEY;
    scn.ch = key;
    scn.modifiers = modifiers;

    NotifyParent(scn);
}

void ScintillaMacOSX::NotifyURIDropped(const char *list) {
    SCNotification scn;
    scn.nmhdr.code = SCN_URIDROPPED;
    scn.text = list;

    NotifyParent(scn);
}

#ifndef EXT_INPUT
// Extended UTF8-UTF6-conversion to handle surrogate pairs correctly (CL265070)
int ScintillaMacOSX::KeyDefault(int key, int modifiers) {
    if (!(modifiers & SCI_CTRL) && !(modifiers & SCI_ALT) && (key < 256)) {
        AddChar(key);
        return 1;
    } else {
        // Pass up to container in case it is an accelerator
        NotifyKey(key, modifiers);
        return 0;
    }
    //Platform::DebugPrintf("SK-key: %d %x %x\n",key, modifiers);
}
#endif

template <class T, class U>
struct StupidMap
{
public:
    T key;
    U value;
};

template <class T, class U>
inline static U StupidMapFindFunction( const StupidMap<T, U>* elements, size_t length, const T& desiredKey )
{
    for ( size_t i = 0; i < length; ++ i )
    {
        if ( elements[i].key == desiredKey )
        {
            return elements[i].value;
        }
    }

    return NULL;
}

// NOTE: If this macro is used on a StupidMap that isn't defined by StupidMap x[] = ...
// The size calculation will fail!
#define StupidMapFind( x, y ) StupidMapFindFunction( x, sizeof(x)/sizeof(*x), y )

pascal OSStatus ScintillaMacOSX::CommandEventHandler( EventHandlerCallRef /*inCallRef*/, EventRef event, void* data )
{
    // TODO: Verify automatically that each constant only appears once?
    const StupidMap<UInt32, void (ScintillaMacOSX::*)()> processCommands[] = {
        { kHICommandCopy, &ScintillaMacOSX::Copy },
        { kHICommandPaste, &ScintillaMacOSX::Paste },
        { kHICommandCut, &ScintillaMacOSX::Cut },
        { kHICommandUndo, &ScintillaMacOSX::Undo },
        { kHICommandRedo, &ScintillaMacOSX::Redo },
        { kHICommandClear, &ScintillaMacOSX::ClearSelection },
        { kHICommandSelectAll, &ScintillaMacOSX::SelectAll },
    };
    const StupidMap<UInt32, bool (ScintillaMacOSX::*)()> canProcessCommands[] = {
        { kHICommandCopy, &ScintillaMacOSX::HasSelection },
        { kHICommandPaste, &ScintillaMacOSX::CanPaste },
        { kHICommandCut, &ScintillaMacOSX::HasSelection },
        { kHICommandUndo, &ScintillaMacOSX::CanUndo },
        { kHICommandRedo, &ScintillaMacOSX::CanRedo },
        { kHICommandClear, &ScintillaMacOSX::HasSelection },
        { kHICommandSelectAll, &ScintillaMacOSX::AlwaysTrue },
    };
    
    HICommand command;  
    OSStatus result = GetEventParameter( event, kEventParamDirectObject, typeHICommand, NULL, sizeof( command ), NULL, &command );
    assert( result == noErr );

    UInt32 kind = GetEventKind( event );
    Platform::DebugPrintf("ScintillaMacOSX::CommandEventHandler kind %d\n", kind);

    ScintillaMacOSX* scintilla = reinterpret_cast<ScintillaMacOSX*>( data );
    assert( scintilla != NULL );
    
    if ( kind == kEventProcessCommand )
    {
#ifdef EXT_INPUT
        // We are getting a HI command, so stop extended input
        ExtInput::stop (scintilla->GetViewRef());
#endif
        // Find the method pointer that matches this command
        void (ScintillaMacOSX::*methodPtr)() = StupidMapFind( processCommands, command.commandID );

        if ( methodPtr != NULL )
        {
            // Call the method if we found it, and tell the caller that we handled this event
            (scintilla->*methodPtr)();
            result = noErr;
        } else {
            // tell the caller that we did not handle the event
            result = eventNotHandledErr;
        }
    }
    // The default Mac OS X text editor does not handle these events to enable/disable menu items
    // Why not? I think it should, so Scintilla does.
    else if ( kind == kEventCommandUpdateStatus && ( command.attributes & kHICommandFromMenu ) )
    {
        // Find the method pointer that matches this command
        bool (ScintillaMacOSX::*methodPtr)() = StupidMapFind( canProcessCommands, command.commandID );

        if ( methodPtr != NULL ) {
            // Call the method if we found it: enabling/disabling menu items
            if ( (scintilla->*methodPtr)() ) {
                EnableMenuItem( command.menu.menuRef, command.menu.menuItemIndex );
            } else {
                DisableMenuItem( command.menu.menuRef, command.menu.menuItemIndex );
            }
            result = noErr;
        } else {
            // tell the caller that we did not handle the event
            result = eventNotHandledErr;
        }
    } else {
        // Unhandled event: We should never get here
        assert( false );
        result = eventNotHandledErr;
    }
    
    return result;
}

bool ScintillaMacOSX::HasSelection()
{
    return ( 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) {
    PasteboardRef theClipboard;
    SetPasteboardData(theClipboard, selectedText);
    // Done with the CFString
    CFRelease( theClipboard );
}

void ScintillaMacOSX::Copy()
{
    if (currentPos != anchor) {
#ifdef EXT_INPUT
        ExtInput::stop (GetViewRef()); 
#endif
        SelectionText selectedText;
        CopySelectionRange(&selectedText);
        fprintf(stderr, "copied text is rectangular? %d\n", selectedText.rectangular);
        CopyToClipboard(selectedText);
    }
}

bool ScintillaMacOSX::CanPaste()
{
    if (!Editor::CanPaste())
        return false;

    PasteboardRef theClipboard;
    bool isFileURL = false;
    
    PasteboardCreate( kPasteboardClipboard, &theClipboard );
    bool ok = GetPasteboardData(theClipboard, NULL, &isFileURL);
    CFRelease( theClipboard );
    return ok;
}

void ScintillaMacOSX::Paste()
{
    Paste(false);
}

// XXX there is no system flag (I can find) to tell us that a paste is rectangular, so 
//     applications must implement an additional command (eg. option-V like BBEdit)
//     in order to provide rectangular paste
void ScintillaMacOSX::Paste(bool forceRectangular)
{
    PasteboardRef theClipboard;
    SelectionText selectedText;
    selectedText.rectangular = forceRectangular;
    bool isFileURL = false;
    PasteboardCreate( kPasteboardClipboard, &theClipboard );
    bool ok = GetPasteboardData(theClipboard, &selectedText, &isFileURL);
    CFRelease( theClipboard );
    fprintf(stderr, "paste is rectangular? %d\n", selectedText.rectangular);
    if (!ok || !selectedText.s)
        // no data or no flavor we support
        return;

    pdoc->BeginUndoAction();
    ClearSelection();
    if (selectedText.rectangular) {
        int selStart = SelectionStart();
        PasteRectangular(selStart, selectedText.s, selectedText.len);
    } else 
    if ( pdoc->InsertString( currentPos, selectedText.s, selectedText.len ) ) {
        SetEmptySelection( currentPos + selectedText.len );
    }

    pdoc->EndUndoAction();

    Redraw();
    EnsureCaretVisible();
}

void ScintillaMacOSX::CreateCallTipWindow(PRectangle rc) {
    // create a calltip window
    if (!ct.wCallTip.Created()) {
        WindowClass windowClass = kHelpWindowClass;
        WindowAttributes attributes = kWindowNoAttributes;
        Rect contentBounds;
        WindowRef outWindow;

        // convert PRectangle to Rect
        // this adjustment gets the calltip window placed in the correct location relative
        // to our editor window
        Rect bounds;
        OSStatus err;
        err = GetWindowBounds( this->GetOwner(), kWindowGlobalPortRgn, &bounds );
        assert( err == noErr );
        contentBounds.top = rc.top + bounds.top;
        contentBounds.bottom = rc.bottom + bounds.top;
        contentBounds.right = rc.right + bounds.left;
        contentBounds.left = rc.left + bounds.left;

        // create our calltip hiview
        HIViewRef ctw = scintilla_calltip_new();
        CallTip* objectPtr = &ct;
        ScintillaMacOSX* sciThis = this;
        SetControlProperty( ctw, scintillaMacOSType, 0, sizeof( this ), &sciThis );
        SetControlProperty( ctw, scintillaCallTipType, 0, sizeof( objectPtr ), &objectPtr );

        CreateNewWindow(windowClass, attributes, &contentBounds, &outWindow);
        ControlRef root;
        CreateRootControl(outWindow, &root);

        HIViewRef hiroot = HIViewGetRoot (outWindow);
        HIViewAddSubview(hiroot, ctw);

        HIRect boundsRect;
        HIViewGetFrame(hiroot, &boundsRect);
        HIViewSetFrame( ctw, &boundsRect );

        // bind the size of the calltip to the size of it's container window
        HILayoutInfo layout = {
            kHILayoutInfoVersionZero,
            {
              { NULL, kHILayoutBindTop, 0 },
              { NULL, kHILayoutBindLeft, 0 },
              { NULL, kHILayoutBindBottom, 0 },
              { NULL, kHILayoutBindRight, 0 }
            },
            {
                { NULL, kHILayoutScaleAbsolute, 0 },
                { NULL, kHILayoutScaleAbsolute, 0 }

            },
            {
                { NULL, kHILayoutPositionTop, 0 },
                { NULL, kHILayoutPositionLeft, 0 }
            }
        };
        HIViewSetLayoutInfo(ctw, &layout);

        ct.wCallTip = root;
        ct.wDraw = ctw;
        ct.wCallTip.SetWindow(outWindow);
        HIViewSetVisible(ctw,true);

    }
}

void ScintillaMacOSX::CallTipClick() 
{
    ScintillaBase::CallTipClick();
}

void ScintillaMacOSX::AddToPopUp( const char *label, int cmd, bool enabled )
{
    // Translate stuff into menu item attributes
    MenuItemAttributes attributes = 0;
    if ( label[0] == '\0' ) attributes |= kMenuItemAttrSeparator;
    if ( ! enabled ) attributes |= kMenuItemAttrDisabled;

    // Translate Scintilla commands into Mac OS commands
    // TODO: If I create an AEDesc, OS X may insert these standard
    // text editing commands into the menu for me
    MenuCommand macCommand;
    switch( cmd )
    {
    case idcmdUndo:
        macCommand = kHICommandUndo;
        break;
    case idcmdRedo:
        macCommand = kHICommandRedo;
        break;
    case idcmdCut:
        macCommand = kHICommandCut;
        break;
    case idcmdCopy:
        macCommand = kHICommandCopy;
        break;
    case idcmdPaste:
        macCommand = kHICommandPaste;
        break;
    case idcmdDelete:
        macCommand = kHICommandClear;
        break;
    case idcmdSelectAll:
        macCommand = kHICommandSelectAll;
        break;
    case 0:
        macCommand = 0;
        break;
    default:
        assert( false );
        return;
    }

    CFStringRef string = CFStringCreateWithCString( NULL, label,  kCFStringEncodingUTF8 );
    OSStatus err;
    err = AppendMenuItemTextWithCFString( reinterpret_cast<MenuRef>( popup.GetID() ),
                               string, attributes, macCommand, NULL );
    assert( err == noErr );

    CFRelease( string );
    string = NULL;
}

void ScintillaMacOSX::ClaimSelection() {
    // Mac OS X does not have a primary selection
}

/** A wrapper function to permit external processes to directly deliver messages to our "message loop". */
sptr_t ScintillaMacOSX::DirectFunction(
    ScintillaMacOSX *sciThis, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
    return sciThis->WndProc(iMessage, wParam, lParam);
}

sptr_t scintilla_send_message(void* sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
        HIViewRef control = reinterpret_cast<HIViewRef>(sci);
    // Platform::DebugPrintf("scintilla_send_message %08X control %08X\n",sci,control);
    // Get a reference to the Scintilla C++ object
    ScintillaMacOSX* scintilla = NULL;
    OSStatus err;
    err = GetControlProperty( control, scintillaMacOSType, 0, sizeof( scintilla ), NULL, &scintilla );
    assert( err == noErr && scintilla != NULL );
    //Platform::DebugPrintf("scintilla_send_message scintilla %08X\n",scintilla);

    return scintilla->WndProc(iMessage, wParam, lParam);
}

void ScintillaMacOSX::TimerFired( EventLoopTimerRef )
{
    Tick();
    DragScroll();
}

OSStatus ScintillaMacOSX::BoundsChanged( UInt32 /*inOptions*/, const HIRect& inOriginalBounds, const HIRect& inCurrentBounds, RgnHandle /*inInvalRgn*/ )
{
    // If the width or height changed, modify the scroll bars and notify Scintilla
    // This event is also delivered when the window moves, and we don't care about that
    if ( inOriginalBounds.size.width != inCurrentBounds.size.width || inOriginalBounds.size.height != inCurrentBounds.size.height )
    {
        Resize( static_cast<int>( inCurrentBounds.size.width ), static_cast<int>( inCurrentBounds.size.height ) );
    }
    return noErr;
}

void ScintillaMacOSX::Draw( RgnHandle rgn, CGContextRef gc )
{
    Rect invalidRect;
    GetRegionBounds( rgn, &invalidRect );

    // NOTE: We get draw events that include the area covered by the scroll bar. No fear: Scintilla correctly ignores them
    SyncPaint( gc, PRectangle( invalidRect.left, invalidRect.top, invalidRect.right, invalidRect.bottom ) );
}

ControlPartCode ScintillaMacOSX::HitTest( const HIPoint& where )
{
    if ( CGRectContainsPoint( Bounds(), where ) )
        return 1;
    else
        return kControlNoPart;
}

OSStatus ScintillaMacOSX::SetFocusPart( ControlPartCode desiredFocus, RgnHandle /*invalidRgn*/, Boolean /*inFocusEverything*/, ControlPartCode* outActualFocus )
{
    assert( outActualFocus != NULL );

    if ( desiredFocus == 0 ) {
        // We are losing the focus
        SetFocusState(false);
    } else {
        // We are getting the focus
        SetFocusState(true);
    }
    
    *outActualFocus = desiredFocus;
    return noErr;
}

// Map Mac Roman character codes to their equivalent Scintilla codes
static inline int KeyTranslate( UniChar unicodeChar )
{
    switch ( unicodeChar )
    {
    case kDownArrowCharCode:
        return SCK_DOWN;
    case kUpArrowCharCode:
        return SCK_UP;
    case kLeftArrowCharCode:
        return SCK_LEFT;
    case kRightArrowCharCode:
        return SCK_RIGHT;
    case kHomeCharCode:
        return SCK_HOME;
    case kEndCharCode:
        return SCK_END;
#ifndef EXT_INPUT
    case kPageUpCharCode:
        return SCK_PRIOR;
    case kPageDownCharCode:
        return SCK_NEXT;
#endif
    case kDeleteCharCode:
        return SCK_DELETE;
    // TODO: Is there an insert key in the mac world? My insert key is the "help" key
    case kHelpCharCode:
        return SCK_INSERT;
    case kEnterCharCode:
    case kReturnCharCode:
        return SCK_RETURN;
#ifdef EXT_INPUT
    // BP 2006-08-22: These codes below should not be translated. Otherwise TextInput() will fail for keys like SCK_ADD, which is '+'.
    case kBackspaceCharCode:
        return SCK_BACK;
    case kFunctionKeyCharCode:
    case kBellCharCode:
    case kVerticalTabCharCode:
    case kFormFeedCharCode:
    case 14:
    case 15:
    case kCommandCharCode:
    case kCheckCharCode:
    case kAppleLogoCharCode:
    case 21:
    case 22:
    case 23:
    case 24:
    case 25:
    case 26:
    case kEscapeCharCode:
        return 0; // ignore
    default:
        return unicodeChar;
#else
    case kEscapeCharCode:
        return SCK_ESCAPE;
    case kBackspaceCharCode:
        return SCK_BACK;
    case '\t':
         return SCK_TAB;
    case '+':
        return SCK_ADD;
    case '-':
        return SCK_SUBTRACT;
    case '/':
        return SCK_DIVIDE;
    case kFunctionKeyCharCode:
        return kFunctionKeyCharCode;
    default:
        return 0;
#endif
    }
}

static inline UniChar GetCharacterWithoutModifiers( EventRef rawKeyboardEvent )
{
    UInt32 keyCode;
    // Get the key code from the raw key event
    GetEventParameter( rawKeyboardEvent, kEventParamKeyCode, typeUInt32, NULL, sizeof( keyCode ), NULL, &keyCode );

    // Get the current keyboard layout
 // TODO: If this is a performance sink, we need to cache these values
    SInt16 lastKeyLayoutID = GetScriptVariable( /*currentKeyScript*/ GetScriptManagerVariable(smKeyScript), smScriptKeys);
    Handle uchrHandle = GetResource('uchr', lastKeyLayoutID);

    // 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 );

#ifdef EXT_INPUT
    UniChar* text = new UniChar [numUniChars];
    err = event.GetParameter( kEventParamTextInputSendText, typeUnicodeText, actualSize, text );
    PLATFORM_ASSERT( err == noErr );

    int modifiers = GetCurrentEventKeyModifiers();
    
    // Loop over all characters in sequence
    for (int i = 0; i < numUniChars; i++)
    {
        UniChar key = KeyTranslate( text[i] );
        if (!key)
            continue;

        bool consumed = false;

        // need to go here first so e.g. Tab indentation works
        KeyDown ((int) key, (modifiers & shiftKey) != 0 || (modifiers & cmdKey) != 0, (modifiers & controlKey) != 0 || (modifiers & cmdKey) != 0,
                 (modifiers & optionKey) != 0 || (modifiers & cmdKey) != 0, &consumed);

        // BP 2007-01-08: 1452623 Second Cmd+s to save doc inserts an "s" into the text on Mac.
        // At this point we need to ignore all cmd/option keys with char value smaller than 32
        if( !consumed )
            consumed = ( modifiers & ( cmdKey | optionKey ) ) != 0 && text[i] < 32;

        // If not consumed, insert the original key
        if (!consumed)
            InsertCharacters (text+i, 1);
    }

    delete[] text;
    return noErr;
#else
    // Allocate a buffer for the text using Core Foundation
    UniChar* text = reinterpret_cast<UniChar*>( CFAllocatorAllocate( CFAllocatorGetDefault(), actualSize, 0 ) );
    assert( text != NULL );

    // Get a copy of the text
    err = event.GetParameter( kEventParamTextInputSendText, typeUnicodeText, actualSize, text );
    assert( err == noErr );

    // TODO: This is a gross hack to ignore function keys
    // Surely we can do better?
    if ( numUniChars == 1 && text[0] == kFunctionKeyCharCode ) return eventNotHandledErr;
    int modifiers = GetCurrentEventKeyModifiers();
    int scintillaKey = KeyTranslate( text[0] );

    // Create a CFString which wraps and takes ownership of the "text" buffer
    CFStringRef string = CFStringCreateWithCharactersNoCopy( NULL, text, numUniChars, NULL );
    assert( string != NULL );
    //delete text;
    text = NULL;

    // If we have a single unicode character that is special or 
    // to process a command. Try to do some translation.
    if ( numUniChars == 1 && ( modifiers & controlKey || scintillaKey != 0 ) ) {
        // If we have a modifier, we need to get the character without modifiers
        if ( modifiers & controlKey ) {
            EventRef rawKeyboardEvent = NULL;
            event.GetParameter(
                      kEventParamTextInputSendKeyboardEvent,
                      typeEventRef,
                      sizeof( EventRef ),
                      &rawKeyboardEvent );
            assert( rawKeyboardEvent != NULL );
            scintillaKey = GetCharacterWithoutModifiers( rawKeyboardEvent );

            // Make sure that we still handle special characters correctly
            int temp = KeyTranslate( scintillaKey );
            if ( temp != 0 ) scintillaKey = temp;

            // TODO: This is a gross Unicode hack: ASCII chars have a value < 127
            if ( scintillaKey <= 127 ) {
                scintillaKey = toupper( (char) scintillaKey );
            }
        }
            
        // Code taken from Editor::KeyDown
        // It is copied here because we don't want to feed the key via
        // KeyDefault if there is no special action
        DwellEnd(false);
        int scintillaModifiers = ( (modifiers & shiftKey) ? SCI_SHIFT : 0) | ( (modifiers & controlKey) ? SCI_CTRL : 0) |
            ( (modifiers & optionKey) ? SCI_ALT : 0);
        int msg = kmap.Find( scintillaKey, scintillaModifiers );
        if (msg) {
            // The keymap has a special event for this key: perform the operation
            WndProc(msg, 0, 0);
            err = noErr;
        } else {
            // We do not handle this event
            err = eventNotHandledErr;
        }
    } else {
        CFStringEncoding encoding = ( IsUnicodeMode() ? kCFStringEncodingUTF8 : kCFStringEncodingASCII);

        // Allocate the buffer (don't forget the null!)
        CFIndex maximumByteLength = CFStringGetMaximumSizeForEncoding( numUniChars, encoding ) + 1;
        char* buffer = new char[maximumByteLength];

        CFIndex usedBufferLength = 0;
        CFIndex numCharsConverted;
        numCharsConverted = CFStringGetBytes( string, CFRangeMake( 0, numUniChars ), encoding,
                                                '?', false, reinterpret_cast<UInt8*>( buffer ),
                                                maximumByteLength, &usedBufferLength );
        assert( numCharsConverted == numUniChars );
        buffer[usedBufferLength] = '\0'; // null terminate
        
        // Add all the characters to the document
        // NOTE: OS X doesn't specify that text input events provide only a single character
        // if we get a single character, add it as a character
        // otherwise, we insert the entire string
        if ( numUniChars == 1 ) {
            AddCharUTF( buffer, usedBufferLength );
        } else {
            // WARNING: This is an untested code path as with my US keyboard, I only enter a single character at a time
            if (pdoc->InsertString(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;
#endif
}

UInt32 ScintillaMacOSX::GetBehaviors()
{
    return TView::GetBehaviors() | kControlGetsFocusOnClick | kControlSupportsEmbedding;
}

OSStatus ScintillaMacOSX::MouseEntered(HIPoint& location, UInt32 /*inKeyModifiers*/, EventMouseButton /*inMouseButton*/, UInt32 /*inClickCount*/ )
{
    if (!HaveMouseCapture() && HIViewGetSuperview(GetViewRef()) != NULL) {
        HIViewRef view;
        HIViewGetSubviewHit(reinterpret_cast<ControlRef>(wMain.GetID()), &location, true, &view);
        if (view) {
            // the hit is on a subview (ie. scrollbars)
            WndProc(SCI_SETCURSOR, Window::cursorArrow, 0);
        } else {
            // reset to normal, buttonmove will change for other area's in the editor
            WndProc(SCI_SETCURSOR, (long int)SC_CURSORNORMAL, 0);
            ButtonMove( Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ) );
        }
        return noErr;
    }
    return eventNotHandledErr;
}

OSStatus ScintillaMacOSX::MouseExited(HIPoint& location, UInt32 modifiers, EventMouseButton button, UInt32 clickCount )
{
    if (HIViewGetSuperview(GetViewRef()) != NULL) {
        if (HaveMouseCapture()) {
            ButtonUp( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ),
                      static_cast<int>( GetCurrentEventTime() / kEventDurationMillisecond ), 
                      (modifiers & controlKey) != 0 );
        }
        WndProc(SCI_SETCURSOR, Window::cursorArrow, 0);
        return noErr;
    }
    return eventNotHandledErr;
}


OSStatus ScintillaMacOSX::MouseDown( HIPoint& location, UInt32 modifiers, EventMouseButton button, UInt32 clickCount , TCarbonEvent& inEvent)
{
    ConvertEventRefToEventRecord( inEvent.GetEventRef(), &mouseDownEvent );
    return MouseDown(location, modifiers, button, clickCount);
}

OSStatus ScintillaMacOSX::MouseDown( EventRecord *event )
{
    HIPoint pt = GetLocalPoint(event->where);
    int button = kEventMouseButtonPrimary;
    mouseDownEvent = *event;
  
    if ( event->modifiers & controlKey )
        button = kEventMouseButtonSecondary;
    return MouseDown(pt, event->modifiers, button, 1);
}

OSStatus ScintillaMacOSX::MouseDown( HIPoint& location, UInt32 modifiers, EventMouseButton button, UInt32 /*clickCount*/ )
{
    // We only deal with the first mouse button
    if ( button != kEventMouseButtonPrimary ) return eventNotHandledErr;
    // TODO: Verify that Scintilla wants the time in milliseconds
    if (!HaveMouseCapture() && HIViewGetSuperview(GetViewRef()) != NULL) {
        if (ScrollBarHit(location)) return noErr;
    }
    ButtonDown( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ),
             static_cast<int>( GetCurrentEventTime() / kEventDurationMillisecond ),
             (modifiers & shiftKey) != 0, 
             (modifiers & controlKey) != 0, 
             (modifiers & cmdKey) );
#if !defined(CONTAINER_HANDLES_EVENTS)
    OSStatus err;
    err = SetKeyboardFocus( this->GetOwner(), this->GetViewRef(), 1 );
    ::SetUserFocusWindow(::HIViewGetWindow( this->GetViewRef() ));
    return noErr;
#else
    return eventNotHandledErr; // allow event to go to container
#endif
}

OSStatus ScintillaMacOSX::MouseUp( EventRecord *event )
{
    HIPoint pt = GetLocalPoint(event->where);
    int button = kEventMouseButtonPrimary;
    if ( event->modifiers & controlKey )
        button = kEventMouseButtonSecondary;
    return MouseUp(pt, event->modifiers, button, 1);
}

OSStatus ScintillaMacOSX::MouseUp( HIPoint& location, UInt32 modifiers, EventMouseButton button, UInt32 /*clickCount*/ )
{
    // We only deal with the first mouse button
    if ( button != kEventMouseButtonPrimary ) return eventNotHandledErr;
        ButtonUp( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ),
               static_cast<int>( GetCurrentEventTime() / kEventDurationMillisecond ), 
               (modifiers & controlKey) != 0 );
    
#if !defined(CONTAINER_HANDLES_EVENTS)
    return noErr;
#else
    return eventNotHandledErr; // allow event to go to container
#endif
}

OSStatus ScintillaMacOSX::MouseDragged( EventRecord *event )
{
    HIPoint pt = GetLocalPoint(event->where);
    int button = 0;
    if ( event->modifiers & btnStateBit ) {
        button = kEventMouseButtonPrimary;
        if ( event->modifiers & controlKey )
            button = kEventMouseButtonSecondary;
    }
    return MouseDragged(pt, event->modifiers, button, 1);
}

OSStatus ScintillaMacOSX::MouseDragged( HIPoint& location, UInt32 modifiers, EventMouseButton button, UInt32 clickCount )
{
#if !defined(CONTAINER_HANDLES_EVENTS)
    ButtonMove( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ) );
    return noErr;
#else
    if (HaveMouseCapture() && !inDragDrop) {
        MouseTrackingResult mouseStatus = 0;
        ::Point theQDPoint;
        UInt32 outModifiers;
        EventTimeout inTimeout=0.1;
        while (mouseStatus != kMouseTrackingMouseReleased) {
            ButtonMove( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ) );
            TrackMouseLocationWithOptions((GrafPtr)-1,
                                          kTrackMouseLocationOptionDontConsumeMouseUp,
                                          inTimeout,
                                          &theQDPoint,
                                          &outModifiers,
                                          &mouseStatus);
            location = GetLocalPoint(theQDPoint);
        }
        ButtonUp( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ),
                  static_cast<int>( GetCurrentEventTime() / kEventDurationMillisecond ), 
                  (modifiers & controlKey) != 0 );
    } else {
        if (!HaveMouseCapture() && HIViewGetSuperview(GetViewRef()) != NULL) {
            HIViewRef view;
            HIViewGetSubviewHit(reinterpret_cast<ControlRef>(wMain.GetID()), &location, true, &view);
            if (view) {
                // the hit is on a subview (ie. scrollbars)
                WndProc(SCI_SETCURSOR, Window::cursorArrow, 0);
                return eventNotHandledErr;
            } else {
                // reset to normal, buttonmove will change for other area's in the editor
                WndProc(SCI_SETCURSOR, (long int)SC_CURSORNORMAL, 0);
            }
        }
        ButtonMove( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ) );
    }
    return eventNotHandledErr; // allow event to go to container
#endif
}

OSStatus ScintillaMacOSX::MouseWheelMoved( EventMouseWheelAxis axis, SInt32 delta, UInt32 modifiers )
{
    if ( axis != 1 ) return eventNotHandledErr;
    
    if ( modifiers & controlKey ) {
        // Zoom! We play with the font sizes in the styles.
        // Number of steps/line is ignored, we just care if sizing up or down
        if ( delta > 0 ) {
            KeyCommand( SCI_ZOOMIN );
        } else {
            KeyCommand( SCI_ZOOMOUT );
        }
    } else {
        // Decide if this should be optimized?
        ScrollTo( topLine - delta );
    }

    return noErr;
}

OSStatus ScintillaMacOSX::ContextualMenuClick( HIPoint& location )
{
    // convert screen coords to window relative
    Rect bounds;
    OSStatus err;
    err = GetWindowBounds( this->GetOwner(), kWindowContentRgn, &bounds );
    assert( err == noErr );
    location.x += bounds.left;
    location.y += bounds.top;
    ContextMenu( Scintilla::Point( static_cast<int>( location.x ), static_cast<int>( location.y ) ) );
    return noErr;   
}

OSStatus ScintillaMacOSX::ActiveStateChanged()
{
    // If the window is being deactivated, lose the focus and turn off the ticking
    if ( ! this->IsActive() ) {
        DropCaret();
        //SetFocusState( false );
        SetTicking( false );
    } else {
        ShowCaretAtCurrentPosition();
    }
    return noErr;
}

HIViewRef ScintillaMacOSX::Create()
{
    // Register the HIView, if needed
    static bool registered = false;

    if ( not registered ) {
        TView::RegisterSubclass( kScintillaClassID, Construct );
        registered = true;
    }

    OSStatus err = noErr;
    EventRef event = CreateInitializationEvent();
    assert( event != NULL );

    HIViewRef control = NULL;
    err = HIObjectCreate( kScintillaClassID, event, reinterpret_cast<HIObjectRef*>( &control ) );
    ReleaseEvent( event );
    if ( err == noErr ) {
        Platform::DebugPrintf("ScintillaMacOSX::Create control %08X\n",control);
        return control;
    }
    return NULL;    
}

OSStatus ScintillaMacOSX::Construct( HIViewRef inControl, TView** outView )
{
    *outView = new ScintillaMacOSX( inControl );
    Platform::DebugPrintf("ScintillaMacOSX::Construct scintilla %08X\n",*outView);
    if ( *outView != NULL )
        return noErr;
    else
        return memFullErr;  // could be a lie
}

extern "C" {
HIViewRef scintilla_new() {
    return ScintillaMacOSX::Create();
}
}
