//----------------------------------------------------------------------------
//  ObjectWindow - OWL NExt
//  Copyright 1999. Yura Bidus. All Rights reserved.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.
//
//  OVERVIEW
//  ~~~~~~~~
//  DragDrop support.
//----------------------------------------------------------------------------
 
#include <coolprj/pch.h>
#pragma hdrstop
 
#include <owl/scroller.h>
#include <coolprj/dragdrop.h>
 
using namespace owl;
using namespace std;
 
inline void __NoTrace(LPCTSTR /*lpszFormat*/, ...){}
extern void __Trace(LPCTSTR lpszFormat, ...);
//------------------------------------------------------------------------------
 
#if 0
#define _TRACE __Trace
#else
#define _TRACE __NoTrace
#endif
 
//-------------------------------------------------------------
// class TOleInitializer
// ~~~~~ ~~~~~~~~~~~~~~~
//
void TOleInitializer::Initialize()
{
  static TOleInitializer init;
}
//
TOleInitializer::TOleInitializer()
{
  // OLE DragDrop
  ::OleInitialize(NULL);
  //_TRACE("TOleInitializer()\n");
}
//
TOleInitializer::~TOleInitializer()
{
  ::OleFlushClipboard();
  // OLE DragDrop
  ::OleUninitialize();
  //_TRACE("~TOleInitializer()\n");
}
//-------------------------------------------------------------
// class TFormatEtc
// ~~~~~ ~~~~~~~~~~
//
TFormatEtc::TFormatEtc()
{
  memset(this, 0, sizeof(TFormatEtc));
  dwAspect = DVASPECT_CONTENT;
  lindex   = -1;
}
//
TFormatEtc::TFormatEtc(const TFormatEtc& etc)
{
  memcpy(this, &etc, sizeof(FORMATETC));
}
//
TFormatEtc::TFormatEtc(const FORMATETC& etc)
{
  memcpy(this, &etc, sizeof(FORMATETC));
}
//
TFormatEtc::TFormatEtc(CLIPFORMAT cfmt, DWORD tmd)
{
  memset(this, 0, sizeof(FORMATETC));
  cfFormat  = cfmt;
  tymed      = tmd;
  dwAspect  = DVASPECT_CONTENT;
  lindex    = -1;
}
//
bool TFormatEtc::operator ==(const TFormatEtc& other) const
{
  return cfFormat == other.cfFormat && (tymed & other.tymed) &&
         dwAspect == other.dwAspect;
}
//
bool TFormatEtc::operator !=(const TFormatEtc& other) const
{
  return !operator==(other);
}
//-------------------------------------------------------------
// class TStorageMedium
// ~~~~~ ~~~~~~~~~~~~~~
//
TStorageMedium::TStorageMedium()
{
  memset(this, 0, sizeof(TStorageMedium));
}
//-------------------------------------------------------------
// class TDragDropProxy
// ~~~~~ ~~~~~~~~~~~~~~
//
uint TDragDropProxy::ScrollInset = 0;
uint TDragDropProxy::ScrollDelay = 0;
TDragDropProxy::TDragDropProxy(TWindow*  parent)
:
  DataArray(0),
  DataSize(0),
  ItemsReserved(0),
  Parent(parent),
  DataObject(0)
{
  if(!ScrollInset)
    ScrollInset = GetProfileInt(_T("windows"), _T("DragScrollInset"), DD_DEFSCROLLINSET);
  if(!ScrollDelay)
    ScrollDelay = GetProfileInt(_T("windows"), _T("DragScrollDelay"), DD_DEFSCROLLDELAY);
  //_TRACE("TDragDropProxy::TDragDropProxy()\n");
}
//
TDragDropProxy::~TDragDropProxy()
{
  //_TRACE("TDragDropProxy::~TDragDropProxy()\n");
  ResetDragData();
}
//
void TDragDropProxy::ResetDragData()
{
  //_TRACE("TDragDropProxy::ResetDragData() START\n");
  for(int i = 0; i < (int)DataSize; i++){
    // cleanup current entry and return it
    CoTaskMemFree(DataArray[i].FormatEtc.ptd);
    ::ReleaseStgMedium(&DataArray[i].StgMedium);
  }
  delete[] DataArray;
  DataArray      = 0;
  DataSize      = 0;
  ItemsReserved  = 0;
  //_TRACE("TDragDropProxy::ResetDragData() END\n");
}
//
TDragDropProxy::TDataEntry*
TDragDropProxy::CreateDataEntry(TFormatEtc& formatEtc)
{
  //_TRACE("TDragDropProxy::CreateDataEntry()\n");
  if(!DataArray){
    ItemsReserved = 5;
    DataArray = new TDataEntry[ItemsReserved];
    memset(DataArray, 0, sizeof(TDataEntry)*ItemsReserved);
  }
  DataSize++;
  if(DataSize >= ItemsReserved){
    TDataEntry* tmp = new TDataEntry[ItemsReserved+5];
    memcpy(tmp,DataArray, ItemsReserved*sizeof(TDataEntry));
    ItemsReserved += 5;
    delete DataArray;
    DataArray = tmp;
  }
  DataArray[DataSize-1].FormatEtc = formatEtc;
  return &DataArray[DataSize-1];
}
//
TDragDropProxy::TDataEntry*
TDragDropProxy::GetDataEntry(TFormatEtc& formatEtc)
{
  if(!DataArray){
    //_TRACE("TDragDropProxy::CreateDataEntry() No DataArray!!!!!!!!!\n");
    return 0;
  }
  for(int i = 0; i < (int)DataSize;i++){
    TFormatEtc& fc = DataArray[i].FormatEtc;
    if (fc == formatEtc && (DataArray[i].StgMedium.tymed == TYMED_NULL ||
        fc.lindex == formatEtc.lindex)){
      CoTaskMemFree(fc.ptd);
      fc.ptd = 0;
      ::ReleaseStgMedium(&DataArray[i].StgMedium);
      //_TRACE("TDragDropProxy::CreateDataEntry() Found\n");
      return &DataArray[i];
    }
  }
  //_TRACE("TDragDropProxy::CreateDataEntry() NotFound !!!!!!!!!\n");
  return 0;
}
//
void TDragDropProxy::SetDragData(CLIPFORMAT cfFormat, HGLOBAL hGlobal)
{
  CHECK(hGlobal);
  //_TRACE("TDragDropProxy::SetDragData()\n");
 
  // fill in FORMATETC struct
  FORMATETC formatEtc;
  formatEtc.cfFormat  = cfFormat;
  formatEtc.ptd        = 0;
  formatEtc.dwAspect  = DVASPECT_CONTENT;
  formatEtc.lindex    = -1;
  formatEtc.tymed      = TYMED_HGLOBAL;
 
  // add it to the cache
  TFormatEtc entry(formatEtc);
  TDataEntry* pEntry = GetDataEntry(entry);
  if(!pEntry)
    pEntry = CreateDataEntry(entry);
  pEntry->StgMedium.tymed            = TYMED_HGLOBAL;
  pEntry->StgMedium.hGlobal          = hGlobal;
  pEntry->StgMedium.pUnkForRelease  = 0;
}
//
uint
TDragDropProxy::IsDroppable(const TPoint& point, const TRect& clientRect)
{
  //Check for at least a client area hit.
  if(!clientRect.Contains(point))
    return UDROP_NONE;
 
  uint retVal = UDROP_CLIENT;
 
  //Check horizontal inset
  if(point.x <= clientRect.left+(int)ScrollInset)
    retVal |= UDROP_INSETLEFT;
  else if(point.x >= clientRect.right-(int)ScrollInset)
    retVal |= UDROP_INSETRIGHT;
 
  //Check vertical inset
  if(point.y <= clientRect.top+(int)ScrollInset)
    retVal |= UDROP_INSETTOP;
  else if (point.y >= clientRect.bottom-(int)ScrollInset)
    retVal |= UDROP_INSETBOTTOM;
 
  return retVal;
}
//
bool
TDragDropProxy::PerformDrop(IDataObject*, const TPoint&, DROPEFFECT)
{
  return true;
}
//
bool
TDragDropProxy::StartDragDrop()
{
  // peek for next input message
  MSG msg;
  if (PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE) ||
    PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)){
    // check for button cancellation (any button down will cancel)
    if (msg.message == WM_LBUTTONUP || msg.message == WM_RBUTTONUP ||
      msg.message == WM_LBUTTONDOWN || msg.message == WM_RBUTTONDOWN)
      return false;
 
    // check for keyboard cancellation
    if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
      return false;
  }
  return true;
}
//
void
TDragDropProxy::EndDragDrop(DROPEFFECT)
{
  // end AutoMode loop in Scroller
  if(Parent && Parent->GetScroller())
  {
    MSG msg;
    PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE);
    Parent->PostMessage(WM_MOUSEMOVE);
    Parent->PostMessage(WM_LBUTTONUP);
  }
}
/*
 * TDropSource::QueryDragContinue
 *
 * Purpose:
 *  Determines whether to continue a drag operation or cancel it.
 *
 * Parameters:
 *  fEsc            BOOL indicating that the ESC key was pressed.
 *  grfKeyState     DWORD providing states of keys and mouse buttons
 *
 * Return Value:
 *  HRESULT         DRAGDROP_S_CANCEL to stop the drag,
 *                  DRAGDROP_S_DROP to drop the data where it is,
 *                  or NOERROR to continue.
 */
STDMETHODIMP TDragDropProxy::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)
{
  //_TRACE("TDragDropProxy::QueryContinueDrag()\n");
  if (fEsc)
    return DRAGDROP_S_CANCEL;
 
  if(!(grfKeyState & MK_LBUTTON))
    return DRAGDROP_S_DROP;
 
  return NOERROR;
}
 
/*
 * TDropSource::GiveFeedback
 *
 * Purpose:
 *  Provides cursor feedback to the user since the source task
 *  always has the mouse capture.  We can also provide any other
 *  type of feedback above cursors if we so desire.
 *
 * Parameters:
 *  dwEffect        DWORD effect flags returned from the last target
 *
 * Return Value:
 *  HRESULT         NOERROR if you set a cursor yourself or
 *                  DRAGDROP_S_USEDEFAULTCURSORS to let OLE do
 *                  the work.
 */
 
STDMETHODIMP TDragDropProxy::GiveFeedback(DWORD)
{
  //_TRACE("TDragDropProxy::GiveFeedback()\n");
  return DRAGDROP_S_USEDEFAULTCURSORS;
}
 
/*
 * TDragDropProxy::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something in which we're interested.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the client space of
 *                  the document.
 *  pdwEffect       LPDWORD into which we'll place the appropriate
 *                  effect flag for this point.
 */
 
STDMETHODIMP TDragDropProxy::DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
{
  //_TRACE("TDragDropProxy::DragEnter()\n");
  *pdwEffect=DROPEFFECT_NONE;
 
  if(!Parent)
    return NOERROR;
 
  DataObject = pIDataSource;
  DataObject->AddRef();
 
  TPoint point(pt.x,pt.y);
  Parent->ScreenToClient(point);
 
  TRect clientRect;
  Parent->GetClientRect(clientRect);
  uint dropRes = IsDroppable(point, clientRect);
 
  LastTest = dropRes;
  if(dropRes == UDROP_NONE)
    *pdwEffect=DROPEFFECT_NONE;
  else{
    *pdwEffect = DROPEFFECT_MOVE;
    if (grfKeyState & MK_CONTROL)
      *pdwEffect = DROPEFFECT_COPY;
  }
 
  //Bring the document window up front, show what a drop will do.
  Parent->BringWindowToTop();
  Parent->UpdateWindow();
  SavedPos = point;
  VScrollCode = NOVALUE;
  HScrollCode = NOVALUE;
  PendingRepaint = false;
  ShowIndicator = false;
  if(dropRes != UDROP_NONE){
    ShowIndicator = true;
    ShowDropIndicator(point);
  }
  return NOERROR;
}
 
/* TDropTarget::DragOver                                               */
/*                                                                     */
/* Purpose:                                                            */
/*  Indicates that the mouse was moved inside the window represented   */
/*  by this drop target.  This happens on every WM_MOUSEMOVE, so       */
/*  this function should be very efficient.                            */
/* Parameters:                                                         */
/*  grfKeyState     DWORD providing the current keyboard and           */
/*                  mouse states                                       */
/*  pt              POINTL where the mouse currently is.               */
/*  pdwEffect       LPDWORD in which to store the effect flag          */
/*                  for this point.                                    */
/* Return Value:                                                       */
/*  HRESULT         NOERROR                                            */
STDMETHODIMP TDragDropProxy::DragOver(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
{
  *pdwEffect=DROPEFFECT_NONE;
 
  if (!DataObject || !Parent)
    return NOERROR;
 
  TPoint point(pt.x,pt.y);
  Parent->ScreenToClient(point);
  TRect clientRect;
  Parent->GetClientRect(clientRect);
  uint testVal = IsDroppable(point, clientRect);
 
  if(testVal != UDROP_NONE){
    //Store these before possibly ORing in DROPEFFECT_SCROLL
    *pdwEffect=DROPEFFECT_MOVE;
    if (grfKeyState & MK_CONTROL)
      *pdwEffect=DROPEFFECT_COPY;
  }
 
  if(SavedPos == point && (LastTest&(UDROP_INSETHORZ|UDROP_INSETVERT))==0){
    return NOERROR;
  }
  /* Scrolling is a little tricky:  We get a DragOver pulse even   */
  /* if we didn't move.  First we have to delay scrolling for      */
  /* ppg->m_uScrollDelay clock ticks which we can determine using  */
  /* GetTickCount.  Timers do not work here since we may not be    */
  /* yielding to our message loop.                                 */
  /*                                                               */
  /* Once we know we are scrolling then we determine if we         */
  /* scroll again or if we reset the scrolling state.              */
  uint uLast = LastTest;
  LastTest   = testVal;
  if(Parent->GetScroller()){ // only if Window have Scroll
    if (UDROP_NONE==testVal){
      //If we're now an invalid point, better repaint as necessary
      if(PendingRepaint){
        Parent->UpdateWindow();
        PendingRepaint=false;
      }
 
      VScrollCode=NOVALUE;
      HScrollCode=NOVALUE;
      if (ShowIndicator)
        HideDropIndicator();
      ShowIndicator = false;
      return NOERROR;
    }
 
    if((UDROP_INSETHORZ&uLast) && !(UDROP_INSETHORZ&testVal))
      HScrollCode=NOVALUE;
 
    if (!(UDROP_INSETHORZ&uLast) && (UDROP_INSETHORZ&testVal)){
      TimeLast = GetTickCount();
      HScrollCode= (0!=(UDROP_INSETLEFT & testVal))
                    ? SB_LINELEFT : SB_LINERIGHT; //Same as UP & DOWN codes.
    }
 
    if ((UDROP_INSETVERT&uLast) && !(UDROP_INSETVERT&testVal))
      VScrollCode = NOVALUE;
 
    if (!(UDROP_INSETVERT&uLast) && (UDROP_INSETVERT&testVal)){
      TimeLast = GetTickCount();
      VScrollCode = (0!=(UDROP_INSETTOP&testVal))
                     ? SB_LINEUP : SB_LINEDOWN;
    }
    //Only change the last time if ALL scrolling stops.
    if (NOVALUE==HScrollCode && NOVALUE==VScrollCode)
      TimeLast=0L;
 
    //Set the scroll effect on any inset hit.
    if((UDROP_INSETHORZ | UDROP_INSETVERT)&testVal)
      *pdwEffect |= DROPEFFECT_SCROLL;
    int xPos = Parent->GetScroller()->XPos;
    int yPos = Parent->GetScroller()->YPos;
 
    //Has the delay elapsed?  We can scroll if so
    if(TimeLast != 0 && (GetTickCount() - TimeLast) > (uint32)ScrollDelay){
      if(NOVALUE != HScrollCode){
        PendingRepaint = true;
        Parent->HandleMessage(WM_HSCROLL, HScrollCode, 0L);
      }
      if(NOVALUE != VScrollCode){
        PendingRepaint=true;
        Parent->HandleMessage(WM_VSCROLL, VScrollCode, 0L);
      }
    }
 
    //If we didn't scroll but have a pending repaint, do it now.
    if(xPos==Parent->GetScroller()->XPos && yPos==Parent->GetScroller()->YPos && PendingRepaint){
      Parent->UpdateWindow();
      PendingRepaint=false;
    }
  }
  else if(PendingRepaint){
    Parent->UpdateWindow();
    PendingRepaint=false;
  }
 
  SavedPos = point;
  ShowIndicator = true;
  ShowDropIndicator(point);
  return NOERROR;
}
 
/*
 * TDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 */
STDMETHODIMP TDragDropProxy::DragLeave()
{
  //_TRACE("TDragDropProxy::DragLeave()\n");
  if (!DataObject || !Parent)
    return NOERROR;
 
  //Stop scrolling
  HScrollCode = NOVALUE;
  VScrollCode = NOVALUE;
  DataObject->Release();
  DataObject = 0;
 
  if (PendingRepaint)
    Parent->UpdateWindow();
  if(ShowIndicator)
    HideDropIndicator();
  ShowIndicator = false;
  return NOERROR;
}
 
/*
 * TDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste the data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD in which to store what you did.
 */
STDMETHODIMP TDragDropProxy::Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
{
  //_TRACE("TDragDropProxy::Drop()\n");
  *pdwEffect=DROPEFFECT_NONE;
 
  if(!DataObject || !Parent)
    return E_FAIL;
 
  TPoint point(pt.x,pt.y);
  Parent->ScreenToClient(point);
  TRect clientRect;
  Parent->GetClientRect(clientRect);
  if(UDROP_NONE == IsDroppable(point,clientRect))
    return E_FAIL;
 
  //Stop scrolling
  HScrollCode=NOVALUE;
  VScrollCode=NOVALUE;
 
  if(PendingRepaint)
    Parent->UpdateWindow();
 
  //2.  Remove the UI feedback
  if(ShowIndicator)
    HideDropIndicator();
  ShowIndicator = false;
 
  DataObject->Release();
  DataObject = 0;
 
  /* Check if we can do the paste, and if so, tell our pasting */
  /* mechanism exactly where to place us.                      */
 
  *pdwEffect = DROPEFFECT_MOVE;
  if (grfKeyState & MK_CONTROL)
    *pdwEffect = DROPEFFECT_COPY;
 
  // if Parent is source and target both it will return true
  if(PerformDrop(pIDataSource, point, *pdwEffect))
    *pdwEffect = DROPEFFECT_NONE;
  return NOERROR;
}
//
/*
 * TDragDropProxy::GetData
 *
 * Purpose:
 *  Retrieves data described by a specific FormatEtc into a StgMedium
 *  allocated by this function.  Used like GetClipboardData.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM in which to return the data.
 */
STDMETHODIMP TDragDropProxy::GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM)
{
  //_TRACE("TDragDropProxy::GetData() Start\n");
  if (NULL==DataArray || NULL==pFE || NULL==pSTM){
    //_TRACE("TDragDropProxy::GetData() == DATA_E_FORMATETC 1\n");
    return DATA_E_FORMATETC;
  }
 
  for(int i=0; i < (int)DataSize; i++){
    TDataEntry& de = DataArray[i];
    /* Check if the requested FORMATETC is the same as one   */
    /* that we already have. If so, then copy that STGMEDIUM */
    /* to pSTM and AddRef ourselves for pUnkForRelease.      */
    if (de.FormatEtc== *pFE){
      if(de.FormatEtc.tymed == TYMED_HGLOBAL){
        *pSTM = de.StgMedium;
        PRECONDITION(de.StgMedium.hGlobal);
        SIZE_T nSize = ::GlobalSize(de.StgMedium.hGlobal);
        pSTM->hGlobal = ::GlobalAlloc(GMEM_SHARE|GMEM_MOVEABLE, nSize);
        LPVOID lpSource = ::GlobalLock(de.StgMedium.hGlobal);
        LPVOID lpDest = ::GlobalLock(pSTM->hGlobal);
        CHECK(lpSource);
        CHECK(lpDest);
        memcpy(lpDest, lpSource, nSize);
        ::GlobalUnlock(pSTM->hGlobal);
        ::GlobalUnlock(de.StgMedium.hGlobal);
        return NOERROR;
      }
      /* ReleaseStgMedium will Release both storage  */
      /* and stream elements regardless of the value */
      /* of pUnkForRelease, so we have to AddRef the */
      /* element and bump our own ref count here.    */
      if (de.FormatEtc.tymed == TYMED_ISTORAGE)
        de.StgMedium.pstg->AddRef();
      else if (de.FormatEtc.tymed == TYMED_ISTREAM)
        de.StgMedium.pstm->AddRef();
      *pSTM = de.StgMedium;
      //_TRACE("TDragDropProxy::GetData() type(%d)== NOERROR\n",(int)de.FormatEtc.tymed);
      return NOERROR;
    }
  }
  //_TRACE("TDragDropProxy::GetData() == DATA_E_FORMATETC\n");
  return DATA_E_FORMATETC;
}
/*
 * TDragDropProxy::GetDataHere
 *
 * Purpose:
 *  Renders the specific FormatEtc into caller-allocated medium
 *  provided in pSTM.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM providing the medium into which
 *                  wer render the data.
 */
STDMETHODIMP TDragDropProxy::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM)
{
  //_TRACE("TDragDropProxy::GetDataHere() Start\n");
  if (NULL==DataArray || NULL==pFE || NULL==pSTM){
    //_TRACE("TDragDropProxy::GetDataHere() DATA_E_FORMATETC 1\n");
    return DATA_E_FORMATETC;
  }
 
  for (int i = 0; i < (int)DataSize; i++){
    TDataEntry& de = DataArray[i];
    /* When we find a matching FORMATETC, we know we're    */
    /* only looking for IStorage or IStream (we checked    */
    /* above), so use IStorage::CopyTo or IStream::CopyTo  */
    /* to make the copy.                                   */
    if (de.FormatEtc == *pFE){
      if(TYMED_ISTORAGE==pFE->tymed){
        pSTM->tymed=TYMED_ISTORAGE;
        return de.StgMedium.pstg->CopyTo(NULL, NULL, NULL, pSTM->pstg);
      }
      else if(TYMED_ISTREAM==pFE->tymed){
        STATSTG  st;
        de.StgMedium.pstm->Stat(&st, STATFLAG_NONAME);
        pSTM->tymed=TYMED_ISTREAM;
        return de.StgMedium.pstm->CopyTo(pSTM->pstm, st.cbSize, NULL, NULL);
      }
      *pSTM = de.StgMedium;
      //_TRACE("TDragDropProxy::GetDataHere() NOERROR\n");
      return NOERROR;
    }
  }
  //_TRACE("TDragDropProxy::GetDataHere() DATA_E_FORMATETC\n");
  return DATA_E_FORMATETC;
}
//
/*
 * TDragDropProxy::SetData
 *
 * Purpose:
 *  Places data described by a FormatEtc and living in a StgMedium
 *  into the object.  The object may be responsible to clean up the
 *  StgMedium before exiting.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the data to set.
 *  pSTM            LPSTGMEDIUM containing the data.
 *  fRelease        BOOL indicating if this function is responsible
 *                  for freeing the data.
 */
STDMETHODIMP TDragDropProxy::SetData(LPFORMATETC, LPSTGMEDIUM, BOOL)
{
  //_TRACE("TDragDropProxy::SetData()\n");
  return DATA_E_FORMATETC;
}
/*
 * TDragDropProxy::QueryGetData
 *
 * Purpose:
 *  Tests if a call to GetData with this FormatEtc will provide
 *  any rendering; used like IsClipboardFormatAvailable.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 */
STDMETHODIMP TDragDropProxy::QueryGetData(LPFORMATETC pFE)
{
  //_TRACE("TDragDropProxy::QueryGetData()\n");
  if (NULL==DataArray || NULL==pFE){
    //_TRACE("TDragDropProxy::QueryGetData() S_FALSE 1\n");
    return S_FALSE;
  }
 
  for (int i=0; i < (int)DataSize; i++){
    TDataEntry& de = DataArray[i];
    /* Check if the requested FORMATETC is the same as one  */
    /* that we already have.                                */
    if (de.FormatEtc== *pFE){
      //_TRACE("TDragDropProxy::QueryGetData() NOERROR\n");
      return NOERROR;
    }
  }
  //_TRACE("TDragDropProxy::QueryGetData() S_FALSE\n");
  return S_FALSE;
}
/*
 * TDragDropProxy::GetCanonicalFormatEtc
 *
 * Purpose:
 *  Provides the caller with an equivalent FormatEtc to the one
 *  provided when different FormatEtcs will produce exactly the
 *  same renderings.
 *
 * Parameters:
 *  pFEIn            LPFORMATETC of the first description.
 *  pFEOut           LPFORMATETC of the equal description.
 */
STDMETHODIMP TDragDropProxy::GetCanonicalFormatEtc(LPFORMATETC, LPFORMATETC)
{
  //_TRACE("TDragDropProxy::GetCanonicalFormatEtc()\n");
  return DATA_S_SAMEFORMATETC;
}
/*
 * TDragDropProxy::EnumFormatEtc
 *
 * Purpose:
 *  Returns an IEnumFORMATETC object through which the caller can
 *  iterate to learn about all the data formats this object can
 *  provide through either GetData[Here] or SetData.
 *
 * Parameters:
 *  dwDir           DWORD describing a data direction, either
 *                  DATADIR_SET or DATADIR_GET.
 *  ppEnum          LPENUMFORMATETC * in which to return the
 *                  pointer to the enumerator.
 */
STDMETHODIMP TDragDropProxy::EnumFormatEtc(DWORD dwDir, LPENUMFORMATETC *ppEnum)
{
  //_TRACE("TDragDropProxy::EnumFormatEtc()\n");
  TEnumFormatEtc* pEnum;
  *ppEnum=0;
 
  /* From an external point of view there are no SET formats,   */
  /* because we want to allow the user of this component object */
  /* to be able to stuff ANY format in via Set.  Only external  */
  /* users will call EnumFormatEtc and they can only Get.       */
  switch (dwDir){
    case DATADIR_GET:
      pEnum = new TEnumFormatEtc;
      break;
 
    case DATADIR_SET:
    default:
      pEnum=0;
      break;
  }
 
  if(pEnum){
    //Let the enumerator copy our format list.
    if(!pEnum->Init(DataArray, DataSize)){
      delete pEnum;
      return E_FAIL;
    }
    pEnum->AddRef();
    *ppEnum=pEnum;
    //_TRACE("TDragDropProxy::EnumFormatEtc() NOERROR\n");
    return NOERROR;
  }
  //_TRACE("TDragDropProxy::EnumFormatEtc() E_FAIL\n");
  return E_FAIL;
}
/*
 * TDragDropProxy::DAdvise
 *
 * Purpose:
 *  Provides the data object with an IAdviseSink object that we are
 *  responsible to notify when the data changes.
 *
 * Parameters:
 *  ppFE            LPFORMATETC
 *  dwFlags         DWORD carrying flags indicating how the advise
 *                  sink wants to be treated.
 *  pIAdviseSink    LPADVISESINK to the object to notify
 *  pdwConn         LPDWORD into which we store a DWORD key
 *                  identifying the advise connection.
 */
STDMETHODIMP TDragDropProxy::DAdvise(LPFORMATETC, DWORD /*dwFlags*/, LPADVISESINK, LPDWORD /*pdwConn*/)
{
  //_TRACE("TDragDropProxy::DAdvise()\n");
  return E_OUTOFMEMORY;
}
/*
 * TDragDropProxy::DUnadvise
 *
 * Purpose:
 *  Turns off advising previously set up with Advise.
 *
 * Parameters:
 *  dwConn          DWORD connection key returned from Advise.
 */
STDMETHODIMP TDragDropProxy::DUnadvise(DWORD /*dwConn*/)
{
  //_TRACE("TDragDropProxy::DUnadvise()\n");
  return E_FAIL;
}
/*
 * TDragDropProxy::EnumDAdvise
 *
 * Purpose:
 *  Returns an enumerator object through which the caller can find
 *  all the agents currently receiving advises on this data object.
 *
 * Parameters:
 *  ppEnum          LPENUMSTATDATA * in which to return the
 *                  enumerator.
 */
STDMETHODIMP TDragDropProxy::EnumDAdvise(LPENUMSTATDATA*)
{
  //_TRACE("TDragDropProxy::EnumDAdvise()\n");
  return E_FAIL;
}
///////////////////////////////////////////////////////////
//
// TDragDropSupport
//
//
//
uint TDragDropSupport::DragDelay   = 0;
uint TDragDropSupport::DragMinDist = 0;
//
TDragDropSupport::TDragDropSupport(TDragDropProxy* proxy)
:
  RefCnt(0),
  Proxy(proxy)
{
  //_TRACE("TDragDropSupport::TDragDropSupport()\n");
  TOleInitializer::Initialize();
  AddRef();
  if(!DragDelay)
    DragDelay    = GetProfileInt(_T("windows"), _T("DragDelay"), DD_DEFDRAGDELAY);
  if(!DragMinDist)
    DragMinDist  = GetProfileInt(_T("windows"), _T("DragMinDist"), DD_DEFDRAGMINDIST);
}
//
TDragDropSupport::~TDragDropSupport()
{
  //_TRACE("TDragDropSupport::~TDragDropSupport()\n");
}
//
TDragDropProxy*
TDragDropSupport::SetProxy(TDragDropProxy* proxy)
{
  //_TRACE("TDragDropSupport::SetProxy()\n");
  TDragDropProxy* tmp = Proxy;
  Proxy = proxy;
  return tmp;
}
//
bool TDragDropSupport::Register(TWindow& parent)
{
  //_TRACE("TDragDropSupport::Register()\n");
  PRECONDITION(parent.GetHandle());
  HRESULT hr = ::RegisterDragDrop(parent.GetHandle(), &DropTarget);
  CoLockObjectExternal(&DropTarget, TRUE, FALSE);
  return hr == S_OK || hr == DRAGDROP_E_ALREADYREGISTERED;
}
//
bool TDragDropSupport::UnRegister(TWindow& parent)
{
  //_TRACE("TDragDropSupport::UnRegister()\n");
  PRECONDITION(parent.GetHandle());
  CoLockObjectExternal(&DropTarget, FALSE, TRUE);
  return ::RevokeDragDrop(parent.GetHandle()) == S_OK;
}
//
STDMETHODIMP TDragDropSupport::QueryInterface(REFIID riid, LPVOID* ppv)
{
  *ppv = 0;
 
  if (IID_IUnknown==riid || IID_IDropSource==riid)
    *ppv=(LPVOID)&DropSource;
 
  if (IID_IDropTarget == riid)
    *ppv=(LPVOID)&DropTarget;
 
  if (IID_IDataObject == riid)
    *ppv=(LPVOID)&DataObject;
 
  if(*ppv != 0){
    ((LPUNKNOWN)*ppv)->AddRef();
    return NOERROR;
  }
  return E_NOINTERFACE;
}
//
STDMETHODIMP_(ULONG)
TDragDropSupport::AddRef(void)
{
  return ++RefCnt;
}
//
STDMETHODIMP_(ULONG)
TDragDropSupport::Release()
{
  if(0L!=--RefCnt)
    return RefCnt;
  delete this;
  return 0;
}
//
bool
TDragDropSupport::CanDragDrop(const TPoint& startPoint, const TPoint& endPoint)
{
  TPoint delta = startPoint - endPoint;
  delta.x = delta.x > 0 ? delta.x : -delta.x;
  delta.y = delta.y > 0 ? delta.y : -delta.y;
  if(delta.x > (LONG)DragMinDist || delta.y > (LONG)DragMinDist)
    return true;
  return false;
}
//
DROPEFFECT
TDragDropSupport::DoDragDrop(DWORD dwEffects)
{
  DWORD dwEffect = DROPEFFECT_NONE;
  if(Proxy->StartDragDrop()){
    HRESULT hr = ::DoDragDrop(&DataObject, &DropSource, dwEffects, &dwEffect);
    if(hr != DRAGDROP_S_DROP)
      dwEffect = DROPEFFECT_NONE;
    Proxy->EndDragDrop(dwEffect);
  }
  return dwEffect;
}
 
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winvalid-offsetof"
#endif
 
//////////////////////////////////////////////////////////////////////////
//
STDMETHODIMP TDragDropSupport::TDropSource::QueryInterface(REFIID riid, LPVOID* ppv)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DropSource, theParent);
  return theParent->QueryInterface(riid, ppv);
}
//
STDMETHODIMP_(ULONG) TDragDropSupport::TDropSource::AddRef()
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DropSource, theParent);
  return theParent->AddRef();
}
//
STDMETHODIMP_(ULONG) TDragDropSupport::TDropSource::Release()
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DropSource, theParent);
  return theParent->Release();
}
//
STDMETHODIMP TDragDropSupport::TDropSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DropSource, theParent);
  return theParent->Proxy->QueryContinueDrag(fEsc, grfKeyState);
}
//
STDMETHODIMP TDragDropSupport::TDropSource::GiveFeedback(DWORD dwEffect)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DropSource, theParent);
  return theParent->Proxy->GiveFeedback(dwEffect);
}
//
STDMETHODIMP TDragDropSupport::TDropTarget::QueryInterface(REFIID riid, LPVOID *ppv)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DropTarget, theParent);
  return theParent->QueryInterface(riid, ppv);
}
//
STDMETHODIMP_(ULONG) TDragDropSupport::TDropTarget::AddRef()
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DropTarget, theParent);
  return theParent->AddRef();
}
//
STDMETHODIMP_(ULONG) TDragDropSupport::TDropTarget::Release()
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DropTarget, theParent);
  return theParent->Release();
}
//
STDMETHODIMP TDragDropSupport::TDropTarget::DragEnter(LPDATAOBJECT pIDataSource,
         DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DropTarget, theParent);
  return theParent->Proxy->DragEnter(pIDataSource, grfKeyState, pt, pdwEffect);
}
//
STDMETHODIMP TDragDropSupport::TDropTarget::DragOver(DWORD grfKeyState,
         POINTL pt, LPDWORD pdwEffect)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DropTarget, theParent);
  return theParent->Proxy->DragOver(grfKeyState, pt, pdwEffect);
}
//
STDMETHODIMP TDragDropSupport::TDropTarget::DragLeave()
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DropTarget, theParent);
  return theParent->Proxy->DragLeave();
}
//
STDMETHODIMP TDragDropSupport::TDropTarget::Drop(LPDATAOBJECT pIDataSource,
          DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DropTarget, theParent);
  return theParent->Proxy->Drop(pIDataSource, grfKeyState, pt, pdwEffect);
}
//
STDMETHODIMP TDragDropSupport::TDataObject::QueryInterface(REFIID riid, LPVOID *ppv)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DataObject, theParent);
  return theParent->QueryInterface(riid, ppv);
}
//
STDMETHODIMP_(ULONG) TDragDropSupport::TDataObject::AddRef()
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DataObject, theParent);
  return theParent->AddRef();
}
//
STDMETHODIMP_(ULONG) TDragDropSupport::TDataObject::Release()
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DataObject, theParent);
  return theParent->Release();
}
//
STDMETHODIMP TDragDropSupport::TDataObject::GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DataObject, theParent);
  HRESULT hr = theParent->Proxy->GetData(pFE, pSTM);
  if(hr==NOERROR)
    AddRef();
  return hr;
}
//
STDMETHODIMP TDragDropSupport::TDataObject::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DataObject, theParent);
 
  HRESULT hr = theParent->Proxy->GetDataHere(pFE, pSTM);
  if(hr==NOERROR)
    AddRef();
  return hr;
}
//
STDMETHODIMP TDragDropSupport::TDataObject::QueryGetData(LPFORMATETC pFE)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DataObject, theParent);
  return theParent->Proxy->QueryGetData(pFE);
}
//
STDMETHODIMP TDragDropSupport::TDataObject::GetCanonicalFormatEtc(LPFORMATETC pFEIn, LPFORMATETC pFEOut)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DataObject, theParent);
  return theParent->Proxy->GetCanonicalFormatEtc(pFEIn, pFEOut);
}
//
STDMETHODIMP TDragDropSupport::TDataObject::SetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM, BOOL fRelease)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DataObject, theParent);
  return theParent->Proxy->SetData(pFE, pSTM, fRelease);
}
//
STDMETHODIMP TDragDropSupport::TDataObject::EnumFormatEtc(DWORD dwDir, LPENUMFORMATETC *ppEnum)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DataObject, theParent);
  return theParent->Proxy->EnumFormatEtc(dwDir, ppEnum);
}
//
STDMETHODIMP TDragDropSupport::TDataObject::DAdvise(LPFORMATETC pFE, DWORD dwFlags, LPADVISESINK pIAdviseSink, LPDWORD pdwConn)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DataObject, theParent);
  return theParent->Proxy->DAdvise(pFE, dwFlags, pIAdviseSink, pdwConn);
}
//
STDMETHODIMP TDragDropSupport::TDataObject::DUnadvise(DWORD dwConn)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DataObject, theParent);
  return theParent->Proxy->DUnadvise(dwConn);
}
//
STDMETHODIMP TDragDropSupport::TDataObject::EnumDAdvise(LPENUMSTATDATA *ppEnum)
{
  MEMBER_METHOD_PROLOGUE(TDragDropSupport, DataObject, theParent);
  return theParent->Proxy->EnumDAdvise(ppEnum);
}
 
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
 
//
TEnumFormatEtc::TEnumFormatEtc()
:
  RefCnt(0),
  DataSize(0),
  DataArray(0)
{
  //_TRACE("TEnumFormatEtc::TEnumFormatEtc()\n");
  m_iCur = 0;
}
//
TEnumFormatEtc::~TEnumFormatEtc()
{
  //_TRACE("TEnumFormatEtc::~TEnumFormatEtc()\n");
  delete[] DataArray;
}
//
bool TEnumFormatEtc::Init(TDragDropProxy::TDataEntry* array, uint size)
{
  //_TRACE("TEnumFormatEtc::Init()\n");
  DataSize  = size;
  try{
    DataArray =  new TFormatEtc[DataSize];
  }
  catch(...){
    return false;
  }
  for(int i=0; i < (int)DataSize; i++)//Copy just the FORMATETC
    DataArray[i] = array[i].FormatEtc;
  return true;
}
/*
 * TEnumFormatEtc::QueryInterface
 * TEnumFormatEtc::AddRef
 * TEnumFormatEtc::Release
 *
 * Purpose:
 *  IUnknown members for TEnumFormatEtc object.  For QueryInterface
 *  we only return out own interfaces and not those of the data
 *  object.  However, since enumerating formats only makes sense
 *  when the data object is around, we insure that it stays as
 *  long as we stay by calling an outer IUnknown for AddRef and
 *  Release.  But since we are not controlled by the lifetime of
 *  the outer object, we still keep our own reference count in
 *  order to free ourselves.
 */
STDMETHODIMP TEnumFormatEtc::QueryInterface(REFIID riid, void** ppv)
{
  *ppv = 0;
 
  /*
   * Enumerators are separate objects, not the data object, so
   * we only need to support out IUnknown and IEnumFORMATETC
   * interfaces here with no concern for aggregation.
   */
  if (IID_IUnknown==riid || IID_IEnumFORMATETC==riid)
    *ppv=this;
 
  if(*ppv){
    ((LPUNKNOWN)*ppv)->AddRef();
    return NOERROR;
  }
  return E_NOINTERFACE;
}
//
STDMETHODIMP_(ULONG) TEnumFormatEtc::AddRef()
{
  return ++RefCnt;
}
//
STDMETHODIMP_(ULONG) TEnumFormatEtc::Release()
{
  if (0!=--RefCnt)
    return RefCnt;
 
  delete this;
  return 0;
}
/*
 * CEnumFormatEtc::Next
 * CEnumFormatEtc::Skip
 * CEnumFormatEtc::Reset
 * CEnumFormatEtc::Clone
 *
 * Standard enumerator members for IEnumFORMATETC
 */
STDMETHODIMP TEnumFormatEtc::Next(ULONG cFE, LPFORMATETC pFE, ULONG *pulFE)
{
  //_TRACE("TEnumFormatEtc::Next()\n");
  ULONG cReturn = 0L;
 
  if(!DataArray)
    return S_FALSE;
 
  if(!pulFE){
    if(1L != cFE)
      return E_POINTER;
  }
  else
    *pulFE = 0L;
  if(NULL==pFE || m_iCur >= DataSize)
    return S_FALSE;
 
  while (m_iCur < DataSize && cFE > 0){
    *pFE++ = DataArray[m_iCur++];
    cReturn++;
    cFE--;
  }
 
  if(NULL!=pulFE)
    *pulFE = cReturn;
 
  return NOERROR;
}
//
STDMETHODIMP TEnumFormatEtc::Skip(ULONG cSkip)
{
  //_TRACE("TEnumFormatEtc::Skip() START\n");
  if ((m_iCur+cSkip) >= DataSize){
    //_TRACE("TEnumFormatEtc::Skip() FALSE\n");
    return S_FALSE;
  }
 
  m_iCur += cSkip;
  //_TRACE("TEnumFormatEtc::Skip() NOERROR\n");
  return NOERROR;
}
//
STDMETHODIMP TEnumFormatEtc::Reset()
{
  //_TRACE("TEnumFormatEtc::Reset()\n");
  m_iCur=0;
  return NOERROR;
}
//
STDMETHODIMP TEnumFormatEtc::Clone(IEnumFORMATETC** ppEnum)
{
  //_TRACE("TEnumFormatEtc::Clone()\n");
  TEnumFormatEtc*  pNew = new TEnumFormatEtc;
 
  pNew->DataSize  = DataSize;
  try{
    pNew->DataArray =  new TFormatEtc[DataSize];
  }
  catch(...){
    return E_OUTOFMEMORY;
  }
  for(int i=0; i < (int)DataSize; i++)
    pNew->DataArray[i] = DataArray[i];
 
  pNew->m_iCur = m_iCur;
  pNew->AddRef();
 
  *ppEnum=pNew;
 
  return NOERROR;
}
/////////////////////////////////////////////////////////////////////////////////

V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] DataArray;'.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: LastTest, TimeLast, HScrollCode, VScrollCode, PendingRepaint, ShowIndicator.

V773 The function was exited without releasing the 'pNew' pointer. A memory leak is possible.

V1004 The 'lpDest' pointer was used unsafely after it was verified against nullptr. Check lines: 630, 631.

V1004 The 'lpSource' pointer was used unsafely after it was verified against nullptr. Check lines: 629, 631.

V807 Decreased performance. Consider creating a pointer to avoid using the 'Parent->GetScroller()' expression repeatedly.