/*
 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#pragma push_macro("bad_alloc")
//"bad_alloc" would be introduced in STL as "std::zbad_alloc" and discarded by linker
//by this action we avoid the conflict with AWT implementation of "bad_alloc"
//we need <new> inclusion for STL "new" oprators set.
#define bad_alloc zbad_alloc
#include <new>

#if defined(_DEBUG) || defined(DEBUG)
extern void * operator new(size_t size, const char * filename, int linenumber);
void * operator new(size_t size) {return operator new(size, "stl", 1);}
#endif
#include <map>

#pragma pop_macro("bad_alloc")
//"bad_alloc" is undefined from here

#include <awt.h>
#include <shlobj.h>

#include "jlong.h"
#include "awt_DataTransferer.h"
#include "awt_DnDDS.h"
#include "awt_DnDDT.h"
#include "awt_Cursor.h"
#include "awt_Toolkit.h"
#include "awt_Component.h"

#include "java_awt_event_InputEvent.h"
#include "java_awt_dnd_DnDConstants.h"
#include "sun_awt_windows_WDragSourceContextPeer.h"

#include "awt_ole.h"
#include "awt_DCHolder.h"

bool operator < (const FORMATETC &fr, const FORMATETC &fl) {
    return memcmp(&fr, &fl, sizeof(FORMATETC)) < 0;
}

typedef std::map<FORMATETC, STGMEDIUM> CDataMap;

#define GALLOCFLG (GMEM_DDESHARE | GMEM_MOVEABLE | GMEM_ZEROINIT)
#define JAVA_BUTTON_MASK (java_awt_event_InputEvent_BUTTON1_DOWN_MASK | \
                          java_awt_event_InputEvent_BUTTON2_DOWN_MASK | \
                          java_awt_event_InputEvent_BUTTON3_DOWN_MASK)

extern "C" {
DWORD __cdecl convertActionsToDROPEFFECT(jint actions);
jint  __cdecl convertDROPEFFECTToActions(DWORD effects);
}

class PictureDragHelper
{
private:
    static CDataMap st;
    static IDragSourceHelper *pHelper;
public:
    static HRESULT Create(
        JNIEnv* env,
        jintArray imageData,
        int imageWidth,
        int imageHeight,
        int anchorX,
        int anchorY,
        IDataObject *pIDataObject)
    {
        if (NULL == imageData) {
            return S_FALSE;
        }
        OLE_TRY
        OLE_HRT( CoCreateInstance(
            CLSID_DragDropHelper,
            NULL,
            CLSCTX_ALL,
            IID_IDragSourceHelper,
            (LPVOID*)&pHelper))

        jintArray ia = imageData;
        jsize iPointCoint = env->GetArrayLength(ia);

        DCHolder ph;
        ph.Create(NULL, imageWidth, imageHeight, TRUE);
        env->GetIntArrayRegion(ia, 0, iPointCoint, (jint*)ph.m_pPoints);

        SHDRAGIMAGE sdi;
        sdi.sizeDragImage.cx = imageWidth;
        sdi.sizeDragImage.cy = imageHeight;
        sdi.ptOffset.x = anchorX;
        sdi.ptOffset.y = anchorY;
        sdi.crColorKey = 0xFFFFFFFF;
        sdi.hbmpDragImage = ph;

        // this call assures that the bitmap will be dragged around
        OLE_HR = pHelper->InitializeFromBitmap(
            &sdi,
            pIDataObject
        );
        // in case of an error we need to destroy the image, else the helper object takes ownership
        if (FAILED(OLE_HR)) {
            DeleteObject(sdi.hbmpDragImage);
        }
        OLE_CATCH
        OLE_RETURN_HR
    }

    static void Destroy()
    {
        if (NULL!=pHelper) {
            CleanFormatMap();
            pHelper->Release();
            pHelper = NULL;
        }
    }

    static void CleanFormatMap()
    {
        for (CDataMap::iterator i = st.begin(); st.end() != i; i = st.erase(i)) {
            ::ReleaseStgMedium(&i->second);
        }
    }
    static void SetData(const FORMATETC &format, const STGMEDIUM &medium)
    {
        CDataMap::iterator i = st.find(format);
        if (st.end() != i) {
            ::ReleaseStgMedium(&i->second);
            i->second = medium;
        } else {
            st[format] = medium;
        }
    }
    static const FORMATETC *FindFormat(const FORMATETC &format)
    {
        static FORMATETC fm = {0};
        CDataMap::iterator i = st.find(format);
        if (st.end() != i) {
            return &i->first;
        }
        for (i = st.begin(); st.end() != i; ++i) {
            if (i->first.cfFormat==format.cfFormat) {
                return &i->first;
            }
        }
        return NULL;
    }
    static STGMEDIUM *FindData(const FORMATETC &format)
    {
        CDataMap::iterator i = st.find(format);
        if (st.end() != i) {
            return &i->second;
        }
        for (i = st.begin(); st.end() != i; ++i) {
            const FORMATETC &f = i->first;
            if (f.cfFormat==format.cfFormat && (f.tymed == (f.tymed & format.tymed))) {
                return &i->second;
            }
        }
        return NULL;
    }
};


CDataMap PictureDragHelper::st;
IDragSourceHelper *PictureDragHelper::pHelper = NULL;

extern const CLIPFORMAT CF_PERFORMEDDROPEFFECT = ::RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT);
extern const CLIPFORMAT CF_FILEGROUPDESCRIPTORW = ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
extern const CLIPFORMAT CF_FILEGROUPDESCRIPTORA = ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
extern const CLIPFORMAT CF_FILECONTENTS = ::RegisterClipboardFormat(CFSTR_FILECONTENTS);

typedef struct {
    AwtDragSource* dragSource;
    jobject        cursor;
    jintArray      imageData;
    jint           imageWidth;
    jint           imageHeight;
    jint           x;
    jint           y;
} StartDragRec;

/**
 * StartDrag
 */

void AwtDragSource::StartDrag(
    AwtDragSource* self,
    jobject cursor,
    jintArray imageData,
    jint imageWidth,
    jint imageHeight,
    jint x,
    jint y)
{
    StartDragRec* sdrp = new StartDragRec;
    sdrp->dragSource = self;
    sdrp->imageData = imageData;
    sdrp->cursor = cursor;
    sdrp->imageWidth = imageWidth;
    sdrp->imageHeight = imageHeight;
    sdrp->x = x;
    sdrp->y = y;

    AwtToolkit::GetInstance().WaitForSingleObject(self->m_mutex);

    AwtToolkit::GetInstance().InvokeFunctionLater((void (*)(void *))&AwtDragSource::_DoDragDrop, (void *)sdrp);

    self->WaitUntilSignalled(FALSE);
}

/**
 * DoDragDrop - called from message pump thread
 */

void AwtDragSource::_DoDragDrop(void* param) {
    StartDragRec*  sdrp         = (StartDragRec*)param;
    AwtDragSource* dragSource   = sdrp->dragSource;
    DWORD          effects      = DROPEFFECT_NONE;
    JNIEnv*        env          = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
    jobject        peer         = env->NewLocalRef(dragSource->GetPeer());

    if (sdrp->imageData) {
        PictureDragHelper::Create(
            env,
            sdrp->imageData,
            sdrp->imageWidth,
            sdrp->imageHeight,
            sdrp->x,
            sdrp->y,
            (IDataObject*)dragSource);
        env->DeleteGlobalRef(sdrp->imageData);
    }
    dragSource->SetCursor(sdrp->cursor);
    env->DeleteGlobalRef(sdrp->cursor);
    delete sdrp;

    HRESULT        res;

    // StartDrag has caused dragSource->m_mutex to be held by our thread now

    AwtDropTarget::SetCurrentDnDDataObject(dragSource);

    ::GetCursorPos(&dragSource->m_dragPoint);

    dragSource->Signal();

    res = ::DoDragDrop(dragSource,
                       dragSource,
                       convertActionsToDROPEFFECT(dragSource->m_actions),
                       &effects
          );

    if (effects == DROPEFFECT_NONE && dragSource->m_dwPerformedDropEffect != DROPEFFECT_NONE) {
        effects = dragSource->m_dwPerformedDropEffect;
    }
    dragSource->m_dwPerformedDropEffect = DROPEFFECT_NONE;

    call_dSCddfinished(env, peer, res == DRAGDROP_S_DROP && effects != DROPEFFECT_NONE,
                       convertDROPEFFECTToActions(effects),
                       dragSource->m_dragPoint.x, dragSource->m_dragPoint.y);

    env->DeleteLocalRef(peer);

    DASSERT(AwtDropTarget::IsCurrentDnDDataObject(dragSource));
    AwtDropTarget::SetCurrentDnDDataObject(NULL);

    PictureDragHelper::Destroy();
    dragSource->Release();
}

/**
 * constructor
 */

AwtDragSource::AwtDragSource(JNIEnv* env, jobject peer, jobject component,
                             jobject transferable, jobject trigger,
                             jint actions, jlongArray formats,
                             jobject formatMap) {
    m_peer      = env->NewGlobalRef(peer);

    m_refs      = 1;

    m_actions   = actions;

    m_ntypes    = 0;

    m_initmods  = 0;
    m_lastmods  = 0;

    m_droptarget   = NULL;
    m_enterpending = TRUE;

    m_cursor     = NULL;

    m_mutex      = ::CreateMutex(NULL, FALSE, NULL);

    m_component     = env->NewGlobalRef(component);
    m_transferable  = env->NewGlobalRef(transferable);
    m_formatMap     = env->NewGlobalRef(formatMap);

    m_dragPoint.x = 0;
    m_dragPoint.y = 0;

    m_fNC         = TRUE;
    m_dropPoint.x = 0;
    m_dropPoint.y = 0;

    m_dwPerformedDropEffect = DROPEFFECT_NONE;
    m_bRestoreNodropCustomCursor = FALSE;

    LoadCache(formats);
}

/**
 * destructor
 */

AwtDragSource::~AwtDragSource() {
    JNIEnv* env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

    // fix for 6212440: on application shutdown, this object's
    // destruction might be suppressed due to dangling COM references.
    // On destruction, VM might be shut down already, so we should make
    // a null check on env.
    if (env) {
        env->DeleteGlobalRef(m_peer);
        env->DeleteGlobalRef(m_component);
        env->DeleteGlobalRef(m_transferable);
        env->DeleteGlobalRef(m_formatMap);
    }

    ::CloseHandle(m_mutex);

    UnloadCache();
}

/**
 * _compar
 *
 * compare format's then tymed's .... only one tymed bit may be set
 * at any time in a FORMATETC in the cache.
 */

int AwtDragSource::_compar(const void* first, const void* second) {
    FORMATETC *fp = (FORMATETC *)first;
    FORMATETC *sp = (FORMATETC *)second;
    int      r  = fp->cfFormat - sp->cfFormat;

    return r != 0 ? r : fp->tymed - sp->tymed;
}

/**
 * LoadCache
 */

void AwtDragSource::LoadCache(jlongArray formats) {
    JNIEnv*      env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
    unsigned int items = 0;
    unsigned int i = 0;
    unsigned int idx = 0;

    if (m_types != (FORMATETC *)NULL) {
        UnloadCache();
    }

    items = env->GetArrayLength(formats);

    if (items == 0) {
        return;
    }

    jboolean isCopy;
    jlong *lFormats = env->GetLongArrayElements(formats, &isCopy),
        *saveFormats = lFormats;

    for (i = 0, m_ntypes = 0; i < items; i++, lFormats++) {
        // Warning C4244.
        // Cast from jlong to CLIPFORMAT (WORD).
        CLIPFORMAT fmt = (CLIPFORMAT)*lFormats;
        switch (fmt) {
        case CF_ENHMETAFILE:
            m_ntypes++;    // Only TYMED_ENHMF
            break;
        case CF_METAFILEPICT:
            m_ntypes++;    // Only TYMED_MFPICT
            break;
        case CF_HDROP:
            m_ntypes++;    // Only TYMED_HGLOBAL
            break;
        default:
            m_ntypes += 2; // TYMED_HGLOBAL and TYMED_ISTREAM
            break;
        }
    }

    try {
        m_types = (FORMATETC *)safe_Calloc(sizeof(FORMATETC), m_ntypes);
    } catch (std::bad_alloc&) {
        m_ntypes = 0;
        throw;
    }

    lFormats = saveFormats;

    for (i = 0, idx = 0; i < items; i++, lFormats++) {
        // Warning C4244.
        // Cast from jlong to CLIPFORMAT (WORD).
        CLIPFORMAT fmt = (CLIPFORMAT)*lFormats;

        m_types[idx].cfFormat = fmt;
        m_types[idx].dwAspect = DVASPECT_CONTENT;
        m_types[idx].lindex   = -1;

        switch (fmt) {
        default:
            m_types[idx].tymed = TYMED_ISTREAM;
            idx++;

            // now make a copy, but with a TYMED of HGLOBAL
            m_types[idx] = m_types[idx-1];
            m_types[idx].tymed = TYMED_HGLOBAL;
            idx++;
            break;
        case CF_HDROP:
            m_types[idx].tymed = TYMED_HGLOBAL;
            idx++;
            break;
        case CF_ENHMETAFILE:
            m_types[idx].tymed = TYMED_ENHMF;
            idx++;
            break;
        case CF_METAFILEPICT:
            m_types[idx].tymed = TYMED_MFPICT;
            idx++;
            break;
        }
    }
    DASSERT(idx == m_ntypes);

    env->ReleaseLongArrayElements(formats, saveFormats, 0);

    // sort them in ascending order of format
    qsort((void *)m_types, (size_t)m_ntypes, (size_t)sizeof(FORMATETC),
          _compar);
}

/**
 * UnloadCache
 */

void AwtDragSource::UnloadCache() {
    if (m_ntypes == 0) {
        return;
    }

    free((void*)m_types);
    m_ntypes = 0;
    m_types  = (FORMATETC *)NULL;
}

/**
 * ChangeCursor
 */
HRESULT AwtDragSource::ChangeCursor()
{
    if (m_cursor != NULL) {
        ::SetCursor(m_cursor->GetHCursor());
        return S_OK;
    }
    return DRAGDROP_S_USEDEFAULTCURSORS;
}

/**
 * SetCursor
 */
void AwtDragSource::SetCursor(jobject cursor) {
    JNIEnv* env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

    if (JNU_IsNull(env, cursor)) {
        m_cursor = NULL;
        return;
    }

    jlong pData = env->GetLongField(cursor, AwtCursor::pDataID);
    // Warning C4312.
    // Cast jlong (__int64) to pointer.
    m_cursor = (AwtCursor*)pData;

    if (m_cursor == NULL) {
        m_cursor = AwtCursor::CreateSystemCursor(cursor);
    }
}

/**
 * MatchFormatEtc
 */

HRESULT __stdcall
AwtDragSource::MatchFormatEtc(FORMATETC __RPC_FAR *pFormatEtcIn,
                              FORMATETC *cacheEnt) {
    TRY;

    const FORMATETC *pFormat = PictureDragHelper::FindFormat(*pFormatEtcIn);
    if (NULL != pFormat) {
        if (NULL != cacheEnt) {
            *cacheEnt = *pFormat;
        }
        return S_OK;
    }

    if ((pFormatEtcIn->tymed & (TYMED_HGLOBAL | TYMED_ISTREAM | TYMED_ENHMF |
                                TYMED_MFPICT)) == 0) {
        return DV_E_TYMED;
    } else if (pFormatEtcIn->lindex != -1) {
        return DV_E_LINDEX;
    } else if (pFormatEtcIn->dwAspect != DVASPECT_CONTENT) {
        return DV_E_DVASPECT;
    }

    FORMATETC tmp = *pFormatEtcIn;

    static const DWORD supportedTymeds[] =
        { TYMED_ISTREAM, TYMED_HGLOBAL, TYMED_ENHMF, TYMED_MFPICT };
    static const int nSupportedTymeds = 4;

    for (int i = 0; i < nSupportedTymeds; i++) {
        /*
         * Fix for BugTraq Id 4426805.
         * Match only if the tymed is supported by the requester.
         */
        if ((pFormatEtcIn->tymed & supportedTymeds[i]) == 0) {
            continue;
        }

        tmp.tymed = supportedTymeds[i];
        pFormat = (const FORMATETC *)bsearch((const void *)&tmp,
                                             (const void *)m_types,
                                             (size_t)      m_ntypes,
                                             (size_t)      sizeof(FORMATETC),
                                                           _compar
                                             );
        if (NULL != pFormat) {
            if (cacheEnt != (FORMATETC *)NULL) {
                *cacheEnt = *pFormat;
            }
            return S_OK;
        }
    }

    return DV_E_FORMATETC;

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * QueryInterface
 */

HRESULT __stdcall AwtDragSource::QueryInterface(REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject) {
    TRY;

    if (riid == IID_IUnknown) {
        *ppvObject = (void __RPC_FAR *__RPC_FAR)(IUnknown*)(IDropSource*)this;
        AddRef();
        return S_OK;
    } else if (riid == IID_IDropSource) {
        *ppvObject = (void __RPC_FAR *__RPC_FAR)(IDropSource*)this;
        AddRef();
        return S_OK;
    } else if (riid == IID_IDataObject) {
        *ppvObject = (void __RPC_FAR *__RPC_FAR)(IDataObject*)this;
        AddRef();
        return S_OK;
    } else {
        *ppvObject = (void __RPC_FAR *__RPC_FAR)NULL;
        return E_NOINTERFACE;
    }

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * AddRef
 */

ULONG __stdcall AwtDragSource::AddRef() {
    return (ULONG)++m_refs;
}

/**
 * Release
 */

ULONG __stdcall AwtDragSource::Release() {
    int refs;

    if ((refs = --m_refs) == 0) delete this;

    return (ULONG)refs;
}

/**
 * QueryContinueDrag
 */

HRESULT __stdcall  AwtDragSource::QueryContinueDrag(BOOL fEscapeKeyPressed, DWORD grfKeyState) {
    TRY;

    JNIEnv* env       = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

    if (fEscapeKeyPressed)
        return DRAGDROP_S_CANCEL;

    jint modifiers = AwtComponent::GetJavaModifiers();

    POINT dragPoint;

    ::GetCursorPos(&dragPoint);

    if ( (dragPoint.x != m_dragPoint.x || dragPoint.y != m_dragPoint.y) &&
         m_lastmods == modifiers) {//cannot move before cursor change
        call_dSCmouseMoved(env, m_peer,
                           m_actions, modifiers, dragPoint.x, dragPoint.y);
        JNU_CHECK_EXCEPTION_RETURN(env, E_UNEXPECTED);
        m_dragPoint = dragPoint;
    }

    if ((modifiers & JAVA_BUTTON_MASK) == 0) {
        return DRAGDROP_S_DROP;
    } else if (m_initmods == 0) {
        m_initmods = modifiers;
    } else if ((modifiers & JAVA_BUTTON_MASK) != (m_initmods & JAVA_BUTTON_MASK)) {
        return DRAGDROP_S_CANCEL;
    } else if (m_lastmods != modifiers) {
        call_dSCchanged(env, m_peer,
                        m_actions, modifiers, dragPoint.x, dragPoint.y);
        m_bRestoreNodropCustomCursor = TRUE;
    }

    m_lastmods = modifiers;

    //CR 6480706 - MS Bug on hold
    HCURSOR hNeedCursor;
    if (
        m_bRestoreNodropCustomCursor &&
        m_cursor != NULL &&
        (hNeedCursor = m_cursor->GetHCursor()) != ::GetCursor() )
    {
        ChangeCursor();
        m_bRestoreNodropCustomCursor = FALSE;
    }
    return S_OK;

   CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * GiveFeedback
 */

HRESULT __stdcall  AwtDragSource::GiveFeedback(DWORD dwEffect) {
    TRY;

    JNIEnv* env       = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
    jint    modifiers = 0;
    SHORT   mods = 0;

    m_actions = convertDROPEFFECTToActions(dwEffect);

    if (::GetKeyState(VK_LBUTTON) & 0xff00) {
        mods |= MK_LBUTTON;
    } else if (::GetKeyState(VK_MBUTTON) & 0xff00) {
        mods |= MK_MBUTTON;
    } else if (::GetKeyState(VK_RBUTTON) & 0xff00) {
        mods |= MK_RBUTTON;
    }

    if (::GetKeyState(VK_SHIFT)   & 0xff00)
        mods |= MK_SHIFT;
    if (::GetKeyState(VK_CONTROL) & 0xff00)
        mods |= MK_CONTROL;
    if (::GetKeyState(VK_MENU) & 0xff00)
        mods |= MK_ALT;

    modifiers = AwtComponent::GetJavaModifiers();

    POINT curs;

    ::GetCursorPos(&curs);

    m_droptarget = ::WindowFromPoint(curs);

    int invalid = (dwEffect == DROPEFFECT_NONE);

    if (invalid) {
        // Don't call dragExit if dragEnter and dragOver haven't been called.
        if (!m_enterpending) {
            call_dSCexit(env, m_peer, curs.x, curs.y);
        }
        m_droptarget = (HWND)NULL;
        m_enterpending = TRUE;
    } else if (m_droptarget != NULL) {
        (*(m_enterpending ? call_dSCenter : call_dSCmotion))
            (env, m_peer, m_actions, modifiers, curs.x, curs.y);

        m_enterpending = FALSE;
    }

    if (m_droptarget != NULL) {
        RECT  rect;
        POINT client = curs;
        VERIFY(::ScreenToClient(m_droptarget, &client));
        VERIFY(::GetClientRect(m_droptarget, &rect));
        if (::PtInRect(&rect, client)) {
            m_fNC = FALSE;
            m_dropPoint = client;
        } else {
            m_fNC = TRUE;
            m_dropPoint = curs;
        }
    } else {
        m_fNC = TRUE;
        m_dropPoint.x = 0;
        m_dropPoint.y = 0;
    }

    m_bRestoreNodropCustomCursor = (dwEffect == DROPEFFECT_NONE);

    return ChangeCursor();

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}


/**
 * GetData
 */

HRESULT __stdcall AwtDragSource::GetData(FORMATETC __RPC_FAR *pFormatEtc,
                                         STGMEDIUM __RPC_FAR *pmedium) {
    TRY;
    STGMEDIUM *pPicMedia = PictureDragHelper::FindData(*pFormatEtc);
    if (NULL != pPicMedia) {
        *pmedium = *pPicMedia;
        //return  outside, so AddRef the instance of pstm or hGlobal!
        if (pmedium->tymed == TYMED_ISTREAM) {
            pmedium->pstm->AddRef();
            pmedium->pUnkForRelease = (IUnknown *)NULL;
        } else if (pmedium->tymed == TYMED_HGLOBAL) {
            AddRef();
            pmedium->pUnkForRelease = (IDropSource *)this;
        }
        return S_OK;
    }

    HRESULT res = GetProcessId(pFormatEtc, pmedium);
    if (res == S_OK) {
        return res;
    }

    FORMATETC matchedFormatEtc;
    res = MatchFormatEtc(pFormatEtc, &matchedFormatEtc);
    if (res != S_OK) {
        return res;
    }

    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

    if (env->PushLocalFrame(2) < 0) {
        return E_OUTOFMEMORY;
    }

    jbyteArray bytes =
        AwtDataTransferer::ConvertData(env, m_component, m_transferable,
                                       (jlong)matchedFormatEtc.cfFormat,
                                       m_formatMap);
    if (!JNU_IsNull(env, safe_ExceptionOccurred(env))) {
        env->ExceptionDescribe();
        env->ExceptionClear();
        env->PopLocalFrame(NULL);
        return E_UNEXPECTED;
    }
    if (bytes == NULL) {
        env->PopLocalFrame(NULL);
        return E_UNEXPECTED;
    }

    jint nBytes = env->GetArrayLength(bytes);

    if ((matchedFormatEtc.tymed & TYMED_ISTREAM) != 0) {
        ADSIStreamProxy *istream = new ADSIStreamProxy(this, bytes, nBytes);

        if (!JNU_IsNull(env, safe_ExceptionOccurred(env))) {
            env->ExceptionDescribe();
            env->ExceptionClear();
            env->PopLocalFrame(NULL);
            return E_UNEXPECTED;
        }

        pmedium->tymed = TYMED_ISTREAM;
        pmedium->pstm = istream;
        pmedium->pUnkForRelease = (IUnknown *)NULL;

        env->PopLocalFrame(NULL);
        return S_OK;
    } else if ((matchedFormatEtc.tymed & TYMED_HGLOBAL) != 0) {
        HGLOBAL copy = ::GlobalAlloc(GALLOCFLG, nBytes +
                                     ((matchedFormatEtc.cfFormat == CF_HDROP)
                                          ? sizeof(DROPFILES)
                                          : 0));
        if (copy == NULL) {
            env->PopLocalFrame(NULL);
            throw std::bad_alloc();
        }

        char *dataout = (char *)::GlobalLock(copy);

        if (matchedFormatEtc.cfFormat == CF_HDROP) {
            DROPFILES *dropfiles = (DROPFILES *)dataout;
            dropfiles->pFiles = sizeof(DROPFILES);
            dropfiles->pt.x = m_dropPoint.x;
            dropfiles->pt.y = m_dropPoint.y;
            dropfiles->fNC = m_fNC;
            dropfiles->fWide = TRUE; // we publish only Unicode
            dataout += sizeof(DROPFILES);
        }

        env->GetByteArrayRegion(bytes, 0, nBytes, (jbyte *)dataout);
        ::GlobalUnlock(copy);

        pmedium->tymed = TYMED_HGLOBAL;
        pmedium->hGlobal = copy;
        pmedium->pUnkForRelease = (IUnknown *)NULL;

        env->PopLocalFrame(NULL);
        return S_OK;
    } else if ((matchedFormatEtc.tymed & TYMED_ENHMF) != 0) {
        LPBYTE lpbEmfBuffer =
            (LPBYTE)env->GetPrimitiveArrayCritical(bytes, NULL);
        if (lpbEmfBuffer == NULL) {
            env->PopLocalFrame(NULL);
            throw std::bad_alloc();
        }

        HENHMETAFILE hemf = ::SetEnhMetaFileBits(nBytes, lpbEmfBuffer);

        env->ReleasePrimitiveArrayCritical(bytes, (LPVOID)lpbEmfBuffer, JNI_ABORT);

        if (hemf == NULL) {
            env->PopLocalFrame(NULL);
            return E_UNEXPECTED;
        }

        pmedium->tymed = TYMED_ENHMF;
        pmedium->hEnhMetaFile = hemf;
        pmedium->pUnkForRelease = (IUnknown *)NULL;

        env->PopLocalFrame(NULL);
        return S_OK;
    } else if ((matchedFormatEtc.tymed & TYMED_MFPICT) != 0) {
        LPBYTE lpbMfpBuffer =
            (LPBYTE)env->GetPrimitiveArrayCritical(bytes, NULL);
        if (lpbMfpBuffer == NULL) {
            env->PopLocalFrame(NULL);
            throw std::bad_alloc();
        }

        HMETAFILE hmf = ::SetMetaFileBitsEx(nBytes - sizeof(METAFILEPICT),
                                         lpbMfpBuffer + sizeof(METAFILEPICT));
        if (hmf == NULL) {
            env->ReleasePrimitiveArrayCritical(bytes, (LPVOID)lpbMfpBuffer, JNI_ABORT);
            env->PopLocalFrame(NULL);
            return E_UNEXPECTED;
        }

        LPMETAFILEPICT lpMfpOld = (LPMETAFILEPICT)lpbMfpBuffer;

        HMETAFILEPICT hmfp = ::GlobalAlloc(GALLOCFLG, sizeof(METAFILEPICT));
        if (hmfp == NULL) {
            VERIFY(::DeleteMetaFile(hmf));
            env->ReleasePrimitiveArrayCritical(bytes, (LPVOID)lpbMfpBuffer, JNI_ABORT);
            env->PopLocalFrame(NULL);
            throw std::bad_alloc();
        }

        LPMETAFILEPICT lpMfp = (LPMETAFILEPICT)::GlobalLock(hmfp);
        lpMfp->mm = lpMfpOld->mm;
        lpMfp->xExt = lpMfpOld->xExt;
        lpMfp->yExt = lpMfpOld->yExt;
        lpMfp->hMF = hmf;
        ::GlobalUnlock(hmfp);

        env->ReleasePrimitiveArrayCritical(bytes, (LPVOID)lpbMfpBuffer, JNI_ABORT);

        pmedium->tymed = TYMED_MFPICT;
        pmedium->hMetaFilePict = hmfp;
        pmedium->pUnkForRelease = (IUnknown *)NULL;

        env->PopLocalFrame(NULL);
        return S_OK;
    }

    env->PopLocalFrame(NULL);
    return DV_E_TYMED;

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * GetDataHere
 */

HRESULT __stdcall AwtDragSource::GetDataHere(FORMATETC __RPC_FAR *pFormatEtc,
                                             STGMEDIUM __RPC_FAR *pmedium) {
    TRY;

    if (pmedium->pUnkForRelease != (IUnknown *)NULL) {
        return E_INVALIDARG;
    }

    HRESULT res = GetProcessId(pFormatEtc, pmedium);
    if (res == S_OK) {
        return res;
    }

    FORMATETC matchedFormatEtc;
    res = MatchFormatEtc(pFormatEtc, &matchedFormatEtc);
    if (res != S_OK) {
        return res;
    }

    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

    if (env->PushLocalFrame(2) < 0) {
        return E_OUTOFMEMORY;
    }

    jbyteArray bytes =
        AwtDataTransferer::ConvertData(env, m_component, m_transferable,
                                       (jlong)matchedFormatEtc.cfFormat,
                                       m_formatMap);
    if (!JNU_IsNull(env, safe_ExceptionOccurred(env))) {
        env->ExceptionDescribe();
        env->ExceptionClear();
        env->PopLocalFrame(NULL);
        return E_UNEXPECTED;
    }
    if (bytes == NULL) {
        env->PopLocalFrame(NULL);
        return E_UNEXPECTED;
    }

    jint nBytes = env->GetArrayLength(bytes);

    // NOTE: TYMED_ENHMF and TYMED_MFPICT are not valid for GetDataHere().
    if ((matchedFormatEtc.tymed & TYMED_ISTREAM) != 0) {
        jboolean isCopy;
        jbyte *bBytes = env->GetByteArrayElements(bytes, &isCopy);
        if (bBytes == NULL) {
            env->PopLocalFrame(NULL);
            return E_UNEXPECTED;
        }

        ULONG act;
        HRESULT res = pmedium->pstm->Write((const void *)bBytes, (ULONG)nBytes,
                                           &act);

        env->ReleaseByteArrayElements(bytes, bBytes, JNI_ABORT);

        env->PopLocalFrame(NULL);
        return S_OK;
    } else if ((matchedFormatEtc.tymed & TYMED_HGLOBAL) != 0) {
        ::SetLastError(0); // clear error
        // Warning C4244.
        SIZE_T mBytes = ::GlobalSize(pmedium->hGlobal);
        if (::GetLastError() != 0) {
            env->PopLocalFrame(NULL);
            return E_UNEXPECTED;
        }

        if (nBytes + ((matchedFormatEtc.cfFormat == CF_HDROP)
                        ? sizeof(DROPFILES) : 0) > mBytes) {
            env->PopLocalFrame(NULL);
            return STG_E_MEDIUMFULL;
        }

        char *dataout = (char *)::GlobalLock(pmedium->hGlobal);

        if (matchedFormatEtc.cfFormat == CF_HDROP) {
            DROPFILES *dropfiles = (DROPFILES *)dataout;
            dropfiles->pFiles = sizeof(DROPFILES);
            dropfiles->pt.x = m_dropPoint.x;
            dropfiles->pt.y = m_dropPoint.y;
            dropfiles->fNC = m_fNC;
            dropfiles->fWide = TRUE; // good guess!
            dataout += sizeof(DROPFILES);
        }

        env->GetByteArrayRegion(bytes, 0, nBytes, (jbyte *)dataout);
        ::GlobalUnlock(pmedium->hGlobal);

        env->PopLocalFrame(NULL);
        return S_OK;
    }

    env->PopLocalFrame(NULL);
    return DV_E_TYMED;

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * QueryGetData
 */

HRESULT __stdcall  AwtDragSource::QueryGetData(FORMATETC __RPC_FAR *pFormatEtc) {
    TRY;

    return MatchFormatEtc(pFormatEtc, (FORMATETC *)NULL);

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}


/**
 * GetCanonicalFormatEtc
 */

HRESULT __stdcall  AwtDragSource::GetCanonicalFormatEtc(FORMATETC __RPC_FAR *pFormatEtcIn, FORMATETC __RPC_FAR *pFormatEtcOut) {
    TRY;

    HRESULT   res = MatchFormatEtc(pFormatEtcIn, (FORMATETC *)NULL);

    if (res != S_OK) return res;

    *pFormatEtcOut = *pFormatEtcIn;

    pFormatEtcOut->ptd = NULL;

    return DATA_S_SAMEFORMATETC;

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * SetData
 */

HRESULT __stdcall AwtDragSource::SetData(FORMATETC __RPC_FAR *pFormatEtc, STGMEDIUM __RPC_FAR *pmedium, BOOL fRelease) {
    if (pFormatEtc->cfFormat == CF_PERFORMEDDROPEFFECT && pmedium->tymed == TYMED_HGLOBAL) {
        m_dwPerformedDropEffect = *(DWORD*)::GlobalLock(pmedium->hGlobal);
        ::GlobalUnlock(pmedium->hGlobal);
        if (fRelease) {
            ::ReleaseStgMedium(pmedium);
        }
        return S_OK;
    }

    if (fRelease) {
        //we are copying pmedium as a structure for further use, so no any release!
        PictureDragHelper::SetData(*pFormatEtc, *pmedium);
        return S_OK;
    }
    return E_UNEXPECTED;
}

/**
 * EnumFormatEtc
 */

HRESULT __stdcall  AwtDragSource::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC *__RPC_FAR *ppenumFormatEtc) {
    TRY;

    *ppenumFormatEtc = new ADSIEnumFormatEtc(this);
    return S_OK;

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * DAdvise
 */

HRESULT __stdcall  AwtDragSource::DAdvise(FORMATETC __RPC_FAR *pFormatEtc, DWORD advf, IAdviseSink __RPC_FAR *pAdvSink, DWORD __RPC_FAR *pdwConnection) {
    return E_NOTIMPL;
}

/**
 * DUnadvise
 */

HRESULT __stdcall  AwtDragSource::DUnadvise(DWORD dwConnection) {
    return OLE_E_ADVISENOTSUPPORTED;
}

/**
 * EnumAdvise
 */

HRESULT __stdcall  AwtDragSource::EnumDAdvise(IEnumSTATDATA __RPC_FAR *__RPC_FAR *ppenumAdvise) {
    return OLE_E_ADVISENOTSUPPORTED;
}

const UINT AwtDragSource::PROCESS_ID_FORMAT =
    ::RegisterClipboardFormat(TEXT("_SUNW_JAVA_AWT_PROCESS_ID"));

HRESULT __stdcall AwtDragSource::GetProcessId(FORMATETC __RPC_FAR *pFormatEtc, STGMEDIUM __RPC_FAR *pmedium) {

    if ((pFormatEtc->tymed & TYMED_HGLOBAL) == 0) {
        return DV_E_TYMED;
    } else if (pFormatEtc->lindex != -1) {
        return DV_E_LINDEX;
    } else if (pFormatEtc->dwAspect != DVASPECT_CONTENT) {
        return DV_E_DVASPECT;
    } else if (pFormatEtc->cfFormat != PROCESS_ID_FORMAT) {
        return DV_E_FORMATETC;
    }

    DWORD id = ::CoGetCurrentProcess();

    HGLOBAL copy = ::GlobalAlloc(GALLOCFLG, sizeof(id));

    if (copy == NULL) {
        throw std::bad_alloc();
    }

    char *dataout = (char *)::GlobalLock(copy);

    memcpy(dataout, &id, sizeof(id));
    ::GlobalUnlock(copy);

    pmedium->tymed = TYMED_HGLOBAL;
    pmedium->hGlobal = copy;
    pmedium->pUnkForRelease = (IUnknown *)NULL;

    return S_OK;
}

DECLARE_JAVA_CLASS(dSCClazz, "sun/awt/windows/WDragSourceContextPeer")

void
AwtDragSource::call_dSCenter(JNIEnv* env, jobject self, jint targetActions,
                             jint modifiers, jint x, jint y) {
    DECLARE_VOID_JAVA_METHOD(dSCenter, dSCClazz, "dragEnter", "(IIII)V");
    DASSERT(!JNU_IsNull(env, self));
    env->CallVoidMethod(self, dSCenter, targetActions, modifiers, x, y);
    if (!JNU_IsNull(env, safe_ExceptionOccurred(env))) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
}

void
AwtDragSource::call_dSCmotion(JNIEnv* env, jobject self, jint targetActions,
                              jint modifiers, jint x, jint y) {
    DECLARE_VOID_JAVA_METHOD(dSCmotion, dSCClazz, "dragMotion", "(IIII)V");
    DASSERT(!JNU_IsNull(env, self));
    env->CallVoidMethod(self, dSCmotion, targetActions, modifiers, x, y);
    if (!JNU_IsNull(env, safe_ExceptionOccurred(env))) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
}

void
AwtDragSource::call_dSCchanged(JNIEnv* env, jobject self, jint targetActions,
                               jint modifiers, jint x, jint y) {
    DECLARE_VOID_JAVA_METHOD(dSCchanged, dSCClazz, "operationChanged",
                             "(IIII)V");
    DASSERT(!JNU_IsNull(env, self));
    env->CallVoidMethod(self, dSCchanged, targetActions, modifiers, x, y);
    if (!JNU_IsNull(env, safe_ExceptionOccurred(env))) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
}

void
AwtDragSource::call_dSCexit(JNIEnv* env, jobject self, jint x, jint y) {
    DECLARE_VOID_JAVA_METHOD(dSCexit, dSCClazz, "dragExit", "(II)V");
    DASSERT(!JNU_IsNull(env, self));
    env->CallVoidMethod(self, dSCexit, x, y);
    if (!JNU_IsNull(env, safe_ExceptionOccurred(env))) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
}

void
AwtDragSource::call_dSCddfinished(JNIEnv* env, jobject self, jboolean success,
                                  jint operations, jint x, jint y) {
    DECLARE_VOID_JAVA_METHOD(dSCddfinished, dSCClazz, "dragDropFinished", "(ZIII)V");
    DASSERT(!JNU_IsNull(env, self));
    env->CallVoidMethod(self, dSCddfinished, success, operations, x, y);
    if (!JNU_IsNull(env, safe_ExceptionOccurred(env))) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
}

void
AwtDragSource::call_dSCmouseMoved(JNIEnv* env, jobject self, jint targetActions,
                                  jint modifiers, jint x, jint y) {
    DECLARE_VOID_JAVA_METHOD(dSCmouseMoved, dSCClazz, "dragMouseMoved",
                             "(IIII)V");
    DASSERT(!JNU_IsNull(env, self));
    env->CallVoidMethod(self, dSCmouseMoved, targetActions, modifiers, x, y);
    if (!JNU_IsNull(env, safe_ExceptionOccurred(env))) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
}

DECLARE_JAVA_CLASS(awtIEClazz, "java/awt/event/InputEvent")

/**
 * Constructor
 */

AwtDragSource::ADSIEnumFormatEtc::ADSIEnumFormatEtc(AwtDragSource* parent) {
    m_parent = parent;
    m_idx    = 0;

    m_refs   = 0;

    m_parent->AddRef();

    AddRef();
}

/**
 * Destructor
 */

AwtDragSource::ADSIEnumFormatEtc::~ADSIEnumFormatEtc() {
    m_parent->Release();
}

/**
 * QueryInterface
 */

HRESULT __stdcall  AwtDragSource::ADSIEnumFormatEtc::QueryInterface(REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject) {
    TRY;

    if (riid == IID_IUnknown) {
        *ppvObject = (void __RPC_FAR *__RPC_FAR)(IUnknown*)this;
        AddRef();
        return S_OK;
    } else if (riid == IID_IEnumFORMATETC) {
        *ppvObject = (void __RPC_FAR *__RPC_FAR)(IEnumFORMATETC*)this;
        AddRef();
        return S_OK;
    } else {
        *ppvObject = (void __RPC_FAR *__RPC_FAR)NULL;
        return E_NOINTERFACE;
    }

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * AddRef
 */

ULONG __stdcall  AwtDragSource::ADSIEnumFormatEtc::AddRef(void) {
    return (ULONG)++m_refs;
}

/**
 * Release
 */

ULONG __stdcall  AwtDragSource::ADSIEnumFormatEtc::Release(void) {
    int refs;

    if ((refs = --m_refs) == 0) delete this;

    return (ULONG)refs;
}

/**
 * Next
 */

HRESULT _stdcall AwtDragSource::ADSIEnumFormatEtc::Next(ULONG celt, FORMATETC __RPC_FAR *rgelt, ULONG __RPC_FAR *pceltFetched) {
    TRY;

    unsigned int len = m_parent->getNTypes();
    unsigned int i;

    for (i = 0; i < celt && m_idx < len; i++, m_idx++) {
        FORMATETC fetc = m_parent->getType(m_idx);
        rgelt[i] = fetc;
    }

    if (pceltFetched != NULL) *pceltFetched = i;

    return i == celt ? S_OK : S_FALSE;

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * Skip
 */

HRESULT __stdcall  AwtDragSource::ADSIEnumFormatEtc::Skip(ULONG celt) {
    TRY;

    unsigned int len = m_parent->getNTypes();
    unsigned int tmp = m_idx + celt;

    if (tmp < len) {
        m_idx = tmp;

        return S_OK;
    } else {
        m_idx = len;

        return S_FALSE;
    }

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * Reset
 */

HRESULT __stdcall  AwtDragSource::ADSIEnumFormatEtc::Reset(void) {
    m_idx = 0;

    return S_OK;
}

/**
 * Clone
 */

HRESULT __stdcall  AwtDragSource::ADSIEnumFormatEtc::Clone(IEnumFORMATETC  __RPC_FAR *__RPC_FAR *ppenum) {
    TRY;

    *ppenum = new ADSIEnumFormatEtc(m_parent);
    (*ppenum)->Skip(m_idx);
    return S_OK;

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * constructor
 */

AwtDragSource::ADSIStreamProxy::ADSIStreamProxy(AwtDragSource* parent, jbyteArray buffer, jint blen) {
    JNIEnv* env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

    m_parent = parent;

    m_buffer = (signed char *)safe_Calloc(sizeof(signed char), m_blen = blen);

    env->GetByteArrayRegion(buffer, 0, blen, m_buffer);

    if (!JNU_IsNull(env, safe_ExceptionOccurred(env))) return;

    m_off     = 0;

    m_cloneof = (ADSIStreamProxy*)NULL;

    m_refs    = 0;

    FILETIME now;

    ::CoFileTimeNow(&now);

    m_statstg.pwcsName          = (LPWSTR)NULL;
    m_statstg.type              = STGTY_STREAM;
    m_statstg.cbSize.HighPart   = 0;
    m_statstg.cbSize.LowPart    = m_blen;
    m_statstg.mtime             = now;
    m_statstg.ctime             = now;
    m_statstg.atime             = now;
    m_statstg.grfMode           = STGM_READ;
    m_statstg.grfLocksSupported = FALSE;
    m_statstg.clsid             = CLSID_NULL;
    m_statstg.grfStateBits      = 0;
    m_statstg.reserved          = 0;

    m_parent->AddRef();

    AddRef();
}

/**
 * constructor (clone)
 */

AwtDragSource::ADSIStreamProxy::ADSIStreamProxy(ADSIStreamProxy* cloneof) {
    m_cloneof = cloneof;

    m_parent  = cloneof->m_parent;

    m_buffer  = cloneof->m_buffer;
    m_blen    = cloneof->m_blen;
    m_off     = cloneof->m_off;

    m_statstg = cloneof->m_statstg;

    m_refs    = 0;

    m_parent->AddRef();
    m_cloneof->AddRef();
}

/**
 * destructor
 */

AwtDragSource::ADSIStreamProxy::~ADSIStreamProxy() {
    if (m_cloneof == (ADSIStreamProxy*)NULL)
        free((void *)m_buffer);
    else {
        m_cloneof->Release();
    }

    m_parent->Release();
}

/**
 * QueryInterface
 */

HRESULT __stdcall  AwtDragSource::ADSIStreamProxy::QueryInterface(REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject) {
    TRY;

    if (riid == IID_IUnknown) {
        *ppvObject = (void __RPC_FAR *__RPC_FAR)(IUnknown*)this;
        AddRef();
        return S_OK;
    } else if (riid == IID_IStream) {
        *ppvObject = (void __RPC_FAR *__RPC_FAR)(IStream*)this;
        AddRef();
        return S_OK;
    } else {
        *ppvObject = (void __RPC_FAR *__RPC_FAR)NULL;
        return E_NOINTERFACE;
    }

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * AddRef
 */

ULONG __stdcall  AwtDragSource::ADSIStreamProxy::AddRef(void) {
    return (ULONG)++m_refs;
}

/**
 * Release
 */

ULONG __stdcall  AwtDragSource::ADSIStreamProxy::Release(void) {
    int refs;

    if ((refs = --m_refs) == 0) delete this;

    return (ULONG)refs;
}

/**
 * Read
 */

HRESULT __stdcall  AwtDragSource::ADSIStreamProxy::Read(void __RPC_FAR *pv, ULONG cb, ULONG __RPC_FAR *pcbRead) {
    TRY;

    unsigned int rem  = m_blen - m_off;
    int          read = cb > rem ? rem : cb;

    if (read > 0) memmove(pv, (void *)(m_buffer + m_off), read);

    m_off += read;

    if (pcbRead != (ULONG __RPC_FAR *)NULL) {
        *pcbRead = read;
    }

    FILETIME now; ::CoFileTimeNow(&now); m_statstg.atime = now;

    return S_OK;

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * Write
 */

HRESULT __stdcall  AwtDragSource::ADSIStreamProxy::Write(const void __RPC_FAR *pv, ULONG cb, ULONG __RPC_FAR *pcbWritten) {
    TRY;

    if (pcbWritten != (ULONG __RPC_FAR *)NULL) {
        *pcbWritten = 0;
    }

    FILETIME now; ::CoFileTimeNow(&now); m_statstg.atime = now;

    return STG_E_CANTSAVE; // don't support writing

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * Seek
 */

HRESULT __stdcall  AwtDragSource::ADSIStreamProxy::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER __RPC_FAR *plibNewPosition) {
    TRY;

    if (dlibMove.HighPart != 0) return STG_E_INVALIDPOINTER;

    if (plibNewPosition != (ULARGE_INTEGER __RPC_FAR *)NULL) {
        plibNewPosition->HighPart = 0;
        plibNewPosition->LowPart  = 0;
    }

    switch (dwOrigin) {
        case STREAM_SEEK_SET: {
            if (dlibMove.HighPart != 0 || dlibMove.LowPart >= m_blen) return STG_E_INVALIDPOINTER;

            m_off = dlibMove.LowPart;
        }
        break;

        case STREAM_SEEK_CUR:
        case STREAM_SEEK_END: {
            if (dlibMove.HighPart > 0) return STG_E_INVALIDPOINTER;

            long newoff = (dwOrigin == STREAM_SEEK_END ? m_blen : m_off) + dlibMove.LowPart;

            if (newoff < 0 || newoff >= (long)m_blen)
                return STG_E_INVALIDPOINTER;
            else
                m_off = newoff;
        }
        break;

        default: return STG_E_INVALIDFUNCTION;
    }

    if (plibNewPosition != (ULARGE_INTEGER __RPC_FAR *)NULL)
        plibNewPosition->LowPart = m_off;

    FILETIME now; ::CoFileTimeNow(&now); m_statstg.atime = now;

    return S_OK;

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * SetSize
 */

HRESULT __stdcall  AwtDragSource::ADSIStreamProxy::SetSize(ULARGE_INTEGER libNewSize) {
    return STG_E_INVALIDFUNCTION;
}

/**
 * CopyTo
 */

HRESULT __stdcall  AwtDragSource::ADSIStreamProxy::CopyTo(IStream __RPC_FAR *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER __RPC_FAR *pcbRead, ULARGE_INTEGER __RPC_FAR *pcbWritten) {
    TRY;

    ULONG written = 0;

    pcbWritten->HighPart = (ULONG)0;
    pcbWritten->LowPart  = (ULONG)0;

    pcbRead->HighPart     = (ULONG)0;

    unsigned int rem     = m_blen - m_off;
    int          ovrflow = cb.LowPart >= rem;


    if (cb.HighPart != 0) return STG_E_INVALIDPOINTER;

    ULONG nbytes = pcbRead->LowPart = (ULONG)(ovrflow ? rem : cb.LowPart);

    HRESULT res = pstm->Write((const void *)(m_buffer + m_off), nbytes, &written);

    pcbWritten->LowPart = written;

    FILETIME now; ::CoFileTimeNow(&now); m_statstg.atime = now;

    return res;

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * Commit
 */

HRESULT __stdcall  AwtDragSource::ADSIStreamProxy::Commit(DWORD grfCommitFlags) {
    return S_OK;
}

/**
 * Revert
 */

HRESULT __stdcall  AwtDragSource::ADSIStreamProxy::Revert() {
    return S_OK;
}

/**
 * LockRegion
 */

HRESULT __stdcall  AwtDragSource::ADSIStreamProxy::LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) {
    return STG_E_INVALIDFUNCTION;
}

/**
 * UnlockRegion
 */

HRESULT __stdcall  AwtDragSource::ADSIStreamProxy::UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) {
    return STG_E_INVALIDFUNCTION;
}

/**
 * Stat
 */

HRESULT __stdcall  AwtDragSource::ADSIStreamProxy::Stat(STATSTG __RPC_FAR *pstatstg, DWORD grfStatFlag) {
    TRY;

    *pstatstg = m_statstg;

    FILETIME now; ::CoFileTimeNow(&now); m_statstg.atime = now;

    return S_OK;

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/**
 * Clone
 */

HRESULT __stdcall  AwtDragSource::ADSIStreamProxy::Clone(IStream __RPC_FAR *__RPC_FAR *ppstm) {
    TRY;

    *ppstm = new ADSIStreamProxy(this);
    return S_OK;

    CATCH_BAD_ALLOC_RET(E_OUTOFMEMORY);
}

/*****************************************************************************/

extern "C" {

/**
 * setNativeCursor
 */

JNIEXPORT void JNICALL
Java_sun_awt_windows_WDragSourceContextPeer_setNativeCursor(JNIEnv* env,
                                                            jobject self,
                                                            jlong nativeCtxt,
                                                            jobject cursor,
                                                            jint type) {
    TRY;

    AwtDragSource* ds = (AwtDragSource*)nativeCtxt;
    if (ds != NULL) {
        ds->SetCursor(cursor);
    }

    CATCH_BAD_ALLOC;
}

/**
 * createDragSource
 */

JNIEXPORT jlong JNICALL
Java_sun_awt_windows_WDragSourceContextPeer_createDragSource(
    JNIEnv* env, jobject self, jobject component, jobject transferable,
    jobject trigger, jint actions,
    jlongArray formats, jobject formatMap)
{
    TRY;

    if (!AwtDropTarget::IsCurrentDnDDataObject(NULL)) {
        JNU_ThrowByName(env, "java/awt/dnd/InvalidDnDOperationException",
                        "Drag and drop is in progress");
        return (jlong)NULL;
    }

    AwtDragSource* ds = new AwtDragSource(env, self, component,
                                          transferable, trigger, actions,
                                          formats, formatMap);

    DASSERT(AwtDropTarget::IsLocalDataObject(ds));

    return (jlong)ds;

    CATCH_BAD_ALLOC_RET(0);
}

/**
 * doDragDrop
 */

JNIEXPORT void JNICALL Java_sun_awt_windows_WDragSourceContextPeer_doDragDrop(
    JNIEnv* env,
    jobject self,
    jlong nativeCtxt,
    jobject cursor,
    jintArray imageData,
    jint imageWidth, jint imageHeight,
    jint x, jint y)
{
    TRY;

    cursor = env->NewGlobalRef(cursor);
    if (NULL != imageData) {
        imageData = (jintArray)env->NewGlobalRef(imageData);
    }

    AwtDragSource::StartDrag(
        (AwtDragSource*)nativeCtxt,
        cursor,
        imageData,
        imageWidth, imageHeight,
        x, y);

    CATCH_BAD_ALLOC;
}

} /* extern "C" */
