///////////////////////////////////////////////////////////////////////////////
// Name:        src/mac/carbon/dnd.cpp
// Purpose:     wxDropTarget, wxDropSource implementations
// Author:      Stefan Csomor
// Modified by:
// Created:     1998-01-01
// RCS-ID:      $Id: dnd.cpp 46434 2007-06-13 04:20:40Z SC $
// Copyright:   (c) 1998 Stefan Csomor
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

#include "wx/wxprec.h"

#if wxUSE_DRAG_AND_DROP

#include "wx/dnd.h"

#ifndef WX_PRECOMP
    #include "wx/app.h"
    #include "wx/toplevel.h"
    #include "wx/gdicmn.h"
#endif // WX_PRECOMP

#include "wx/mac/private.h"

// ----------------------------------------------------------------------------
// globals
// ----------------------------------------------------------------------------

typedef struct
{
    wxWindow *m_currentTargetWindow;
    wxDropTarget *m_currentTarget;
    wxDropSource *m_currentSource;
    wxDragResult m_result;
    int m_flags;
} MacTrackingGlobals;

MacTrackingGlobals gTrackingGlobals;

void wxMacEnsureTrackingHandlersInstalled();

//----------------------------------------------------------------------------
// wxDropTarget
//----------------------------------------------------------------------------

wxDropTarget::wxDropTarget( wxDataObject *data )
            : wxDropTargetBase( data )
{
    wxMacEnsureTrackingHandlersInstalled();
}

wxDragResult wxDropTarget::OnDragOver(
    wxCoord WXUNUSED(x), wxCoord WXUNUSED(y),
    wxDragResult def )
{
    return CurrentDragHasSupportedFormat() ? def : wxDragNone;
}

bool wxDropTarget::OnDrop( wxCoord WXUNUSED(x), wxCoord WXUNUSED(y) )
{
    if (m_dataObject == NULL)
        return false;

    return CurrentDragHasSupportedFormat();
}

wxDragResult wxDropTarget::OnData(
    wxCoord WXUNUSED(x), wxCoord WXUNUSED(y),
    wxDragResult def )
{
    if (m_dataObject == NULL)
        return wxDragNone;

    if (!CurrentDragHasSupportedFormat())
        return wxDragNone;

    return GetData() ? def : wxDragNone;
}

bool wxDropTarget::CurrentDragHasSupportedFormat()
{
    bool supported = false;
    if (m_dataObject == NULL)
        return false;
        
    if ( gTrackingGlobals.m_currentSource != NULL )
    {
        wxDataObject* data = gTrackingGlobals.m_currentSource->GetDataObject();

        if ( data )
        {
            size_t formatcount = data->GetFormatCount();
            wxDataFormat *array = new wxDataFormat[formatcount];
            data->GetAllFormats( array );
            for (size_t i = 0; !supported && i < formatcount; i++)
            {
                wxDataFormat format = array[i];
                if ( m_dataObject->IsSupported( format ) )
                {
                    supported = true;
                    break;
                }
            }

            delete [] array;
        }
    }

    if ( !supported )
    {
        PasteboardRef   pasteboard;

        if ( GetDragPasteboard( (DragReference)m_currentDrag, &pasteboard ) == noErr )
        {
            supported = m_dataObject->HasDataInPasteboard( pasteboard );
        }
    }

    return supported;
}

bool wxDropTarget::GetData()
{
    if (m_dataObject == NULL)
        return false;

    if ( !CurrentDragHasSupportedFormat() )
        return false;

    bool transferred = false;
    if ( gTrackingGlobals.m_currentSource != NULL )
    {
        wxDataObject* data = gTrackingGlobals.m_currentSource->GetDataObject();

        if (data != NULL)
        {
            size_t formatcount = data->GetFormatCount();
            wxDataFormat *array = new wxDataFormat[formatcount];
            data->GetAllFormats( array );
            for (size_t i = 0; !transferred && i < formatcount; i++)
            {
                wxDataFormat format = array[i];
                if ( m_dataObject->IsSupported( format ) )
                {
                    int size = data->GetDataSize( format );
                    transferred = true;

                    if (size == 0)
                    {
                        m_dataObject->SetData( format, 0, 0 );
                    }
                    else
                    {
                        char *d = new char[size];
                        data->GetDataHere( format, (void*)d );
                        m_dataObject->SetData( format, size, d );
                        delete [] d;
                    }
                }
            }

            delete [] array;
        }
    }

    if ( !transferred )
    {
        PasteboardRef   pasteboard;

        if ( GetDragPasteboard(  (DragReference)m_currentDrag, &pasteboard ) == noErr )
        {
            transferred = m_dataObject->GetFromPasteboard( pasteboard );
        }
    }

    return transferred;
}

//-------------------------------------------------------------------------
// wxDropSource
//-------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// drag request

wxDropSource::wxDropSource(wxWindow *win,
                           const wxCursor &cursorCopy,
                           const wxCursor &cursorMove,
                           const wxCursor &cursorStop)
            : wxDropSourceBase(cursorCopy, cursorMove, cursorStop)
{
    wxMacEnsureTrackingHandlersInstalled();

    m_window = win;
}

wxDropSource::wxDropSource(wxDataObject& data,
                           wxWindow *win,
                           const wxCursor &cursorCopy,
                           const wxCursor &cursorMove,
                           const wxCursor &cursorStop)
            : wxDropSourceBase(cursorCopy, cursorMove, cursorStop)
{
    wxMacEnsureTrackingHandlersInstalled();

    SetData( data );
    m_window = win;
}

wxDropSource::~wxDropSource()
{
}

OSStatus wxMacPromiseKeeper( PasteboardRef inPasteboard, PasteboardItemID inItem, CFStringRef inFlavorType,
              void *inContext )
{
    OSStatus  err = noErr;
    
    // we might add promises here later, inContext is the wxDropSource*

    return err;
}

wxDragResult wxDropSource::DoDragDrop(int flags)
{
    wxASSERT_MSG( m_data, wxT("Drop source: no data") );

    if ((m_data == NULL) || (m_data->GetFormatCount() == 0))
        return (wxDragResult)wxDragNone;

    DragReference theDrag;
    RgnHandle dragRegion;
    OSStatus err = noErr;
    PasteboardRef   pasteboard;

    // add data to drag
 
    err = PasteboardCreate( kPasteboardUniqueName, &pasteboard );
    if ( err != noErr )
        return wxDragNone;
        
   // we add a dummy promise keeper because of strange messages when linking against carbon debug
	err = PasteboardSetPromiseKeeper( pasteboard, wxMacPromiseKeeper, this );
    if ( err != noErr )
    {
        CFRelease( pasteboard );
        return wxDragNone;
    }

	err = PasteboardClear( pasteboard );
    if ( err != noErr )
    {
        CFRelease( pasteboard );
        return wxDragNone;
    }
	PasteboardSynchronize( pasteboard );

    m_data->AddToPasteboard( pasteboard, 1 );
 
    if (NewDragWithPasteboard( pasteboard , &theDrag) != noErr)
    {
        CFRelease( pasteboard );
        return wxDragNone;
    }

    dragRegion = NewRgn();
    RgnHandle tempRgn = NewRgn();

    EventRecord rec;
    ConvertEventRefToEventRecord(  (EventRef) wxTheApp->MacGetCurrentEvent(), &rec );

    const short dragRegionOuterBoundary = 10;
    const short dragRegionInnerBoundary = 9;

    SetRectRgn(
        dragRegion,
        rec.where.h - dragRegionOuterBoundary,
        rec.where.v  - dragRegionOuterBoundary,
        rec.where.h + dragRegionOuterBoundary,
        rec.where.v + dragRegionOuterBoundary );

    SetRectRgn(
        tempRgn,
        rec.where.h - dragRegionInnerBoundary,
        rec.where.v - dragRegionInnerBoundary,
        rec.where.h + dragRegionInnerBoundary,
        rec.where.v + dragRegionInnerBoundary );

    DiffRgn( dragRegion, tempRgn, dragRegion );
    DisposeRgn( tempRgn );

    // TODO: work with promises in order to return data
    // only when drag was successfully completed

    gTrackingGlobals.m_currentSource = this;
    gTrackingGlobals.m_result = wxDragNone;
    gTrackingGlobals.m_flags = flags;

    err = TrackDrag( theDrag, &rec, dragRegion );
    
    DisposeRgn( dragRegion );
    DisposeDrag( theDrag );
    CFRelease( pasteboard );
    gTrackingGlobals.m_currentSource = NULL;

    return gTrackingGlobals.m_result;
}

bool wxDropSource::MacInstallDefaultCursor(wxDragResult effect)
{
    const wxCursor& cursor = GetCursor(effect);
    bool result = cursor.Ok();

    if ( result )
        cursor.MacInstall();

    return result;
}

bool gTrackingGlobalsInstalled = false;

// passing the globals via refcon is not needed by the CFM and later architectures anymore
// but I'll leave it in there, just in case...

pascal OSErr wxMacWindowDragTrackingHandler(
    DragTrackingMessage theMessage, WindowPtr theWindow,
    void *handlerRefCon, DragReference theDrag );
pascal OSErr wxMacWindowDragReceiveHandler(
    WindowPtr theWindow, void *handlerRefCon,
    DragReference theDrag );

void wxMacEnsureTrackingHandlersInstalled()
{
    if ( !gTrackingGlobalsInstalled )
    {
        OSStatus err;

        err = InstallTrackingHandler( NewDragTrackingHandlerUPP(wxMacWindowDragTrackingHandler), 0L, &gTrackingGlobals );
        verify_noerr( err );

        err = InstallReceiveHandler( NewDragReceiveHandlerUPP(wxMacWindowDragReceiveHandler), 0L, &gTrackingGlobals );
        verify_noerr( err );

        gTrackingGlobalsInstalled = true;
    }
}

pascal OSErr wxMacWindowDragTrackingHandler(
    DragTrackingMessage theMessage, WindowPtr theWindow,
    void *handlerRefCon, DragReference theDrag )
{
    MacTrackingGlobals* trackingGlobals = (MacTrackingGlobals*) handlerRefCon;

    Point mouse, localMouse;
    DragAttributes attributes;

    GetDragAttributes( theDrag, &attributes );

    wxTopLevelWindowMac* toplevel = wxFindWinFromMacWindow( theWindow );

    bool optionDown = GetCurrentKeyModifiers() & optionKey;
    wxDragResult result = optionDown ? wxDragCopy : wxDragMove;

    switch (theMessage)
    {
        case kDragTrackingEnterHandler:
        case kDragTrackingLeaveHandler:
            break;

        case kDragTrackingEnterWindow:
            if (trackingGlobals != NULL)
            {
                trackingGlobals->m_currentTargetWindow = NULL;
                trackingGlobals->m_currentTarget = NULL;
            }
            break;

        case kDragTrackingInWindow:
            if (trackingGlobals == NULL)
                break;
            if (toplevel == NULL)
                break;

            GetDragMouse( theDrag, &mouse, 0L );
            localMouse = mouse;
            wxMacGlobalToLocal( theWindow, &localMouse );

            {
                wxWindowMac *win = NULL;
                ControlPartCode controlPart;
                ControlRef control = wxMacFindControlUnderMouse(
                    toplevel, localMouse, theWindow, &controlPart );
                if ( control )
                    win = wxFindControlFromMacControl( control );
                else
                    win = toplevel;

                int localx, localy;
                localx = localMouse.h;
                localy = localMouse.v;

                if ( win )
                    win->MacRootWindowToWindow( &localx, &localy );
                if ( win != trackingGlobals->m_currentTargetWindow )
                {
                    if ( trackingGlobals->m_currentTargetWindow )
                    {
                        // this window is left
                        if ( trackingGlobals->m_currentTarget )
                        {
#ifndef __LP64__
                            HideDragHilite( theDrag );
#endif
                            trackingGlobals->m_currentTarget->SetCurrentDrag( theDrag );
                            trackingGlobals->m_currentTarget->OnLeave();
                            trackingGlobals->m_currentTarget = NULL;
                            trackingGlobals->m_currentTargetWindow = NULL;
                        }
                    }

                    if ( win )
                    {
                        // this window is entered
                        trackingGlobals->m_currentTargetWindow = (wxWindow*)win;
                        trackingGlobals->m_currentTarget = win->GetDropTarget();
                        {
                            if ( trackingGlobals->m_currentTarget )
                            {
                                trackingGlobals->m_currentTarget->SetCurrentDrag( theDrag );
                                result = trackingGlobals->m_currentTarget->OnEnter( localx, localy, result );
                            }

                            if ( result != wxDragNone )
                            {
                                int x, y;

                                x = y = 0;
                                win->MacWindowToRootWindow( &x, &y );
                                RgnHandle hiliteRgn = NewRgn();
                                Rect r = { y, x, y + win->GetSize().y, x + win->GetSize().x };
                                RectRgn( hiliteRgn, &r );
#ifndef __LP64__
                                ShowDragHilite( theDrag, hiliteRgn, true );
#endif
                                DisposeRgn( hiliteRgn );
                            }
                        }
                    }
                }
                else
                {
                    if ( trackingGlobals->m_currentTarget )
                    {
                        trackingGlobals->m_currentTarget->SetCurrentDrag( theDrag );
                        result = trackingGlobals->m_currentTarget->OnDragOver( localx, localy, result );
                    }
                }

                // set cursor for OnEnter and OnDragOver
                if ( trackingGlobals->m_currentSource && !trackingGlobals->m_currentSource->GiveFeedback( result ) )
                {
                  if ( !trackingGlobals->m_currentSource->MacInstallDefaultCursor( result ) )
                  {
                      int cursorID = wxCURSOR_NONE;

                      switch (result)
                      {
                          case wxDragCopy:
                              cursorID = wxCURSOR_COPY_ARROW;
                              break;

                          case wxDragMove:
                              cursorID = wxCURSOR_ARROW;
                              break;

                          case wxDragNone:
                              cursorID = wxCURSOR_NO_ENTRY;
                              break;

                          case wxDragError:
                          case wxDragLink:
                          case wxDragCancel:
                          default:
                              // put these here to make gcc happy
                              ;
                      }

                      if (cursorID != wxCURSOR_NONE)
                      {
                          wxCursor cursor( cursorID );
                          cursor.MacInstall();
                      }
                   }
                }
            }
            break;

        case kDragTrackingLeaveWindow:
            if (trackingGlobals == NULL)
                break;

            if (trackingGlobals->m_currentTarget)
            {
                trackingGlobals->m_currentTarget->SetCurrentDrag( theDrag );
                trackingGlobals->m_currentTarget->OnLeave();
#ifndef __LP64__
                HideDragHilite( theDrag );
#endif
                trackingGlobals->m_currentTarget = NULL;
            }
            trackingGlobals->m_currentTargetWindow = NULL;
            break;

        default:
            break;
    }

    return noErr;
}

pascal OSErr wxMacWindowDragReceiveHandler(
    WindowPtr theWindow,
    void *handlerRefCon,
    DragReference theDrag)
{
    MacTrackingGlobals* trackingGlobals = (MacTrackingGlobals*)handlerRefCon;
    if ( trackingGlobals->m_currentTarget )
    {
        Point mouse, localMouse;
        int localx, localy;

        trackingGlobals->m_currentTarget->SetCurrentDrag( theDrag );
        GetDragMouse( theDrag, &mouse, 0L );
        localMouse = mouse;
        wxMacGlobalToLocal( theWindow, &localMouse );
        localx = localMouse.h;
        localy = localMouse.v;

        // TODO : should we use client coordinates?
        if ( trackingGlobals->m_currentTargetWindow )
            trackingGlobals->m_currentTargetWindow->MacRootWindowToWindow( &localx, &localy );
        if ( trackingGlobals->m_currentTarget->OnDrop( localx, localy ) )
        {
            // the option key indicates copy in Mac UI, if it's not pressed do
            // move by default if it's allowed at all
            wxDragResult
                result = !(trackingGlobals->m_flags & wxDrag_AllowMove) ||
                            (GetCurrentKeyModifiers() & optionKey)
                            ? wxDragCopy
                            : wxDragMove;
            trackingGlobals->m_result =
                trackingGlobals->m_currentTarget->OnData( localx, localy, result );
        }
    }

    return noErr;
}

#endif // wxUSE_DRAG_AND_DROP

