// PanelDrag.cpp

#include "StdAfx.h"

#ifdef UNDER_CE
#include <winuserm.h>
#endif

#include "../../../../C/7zVersion.h"
#include "../../../../C/CpuArch.h"

#include "../../../Common/StringConvert.h"
#include "../../../Common/Wildcard.h"

#include "../../../Windows/COM.h"
#include "../../../Windows/MemoryGlobal.h"
#include "../../../Windows/Menu.h"
#include "../../../Windows/FileDir.h"
#include "../../../Windows/FileName.h"
#include "../../../Windows/Shell.h"

#include "../Common/ArchiveName.h"
#include "../Common/CompressCall.h"
#include "../Common/ExtractingFilePath.h"

#include "MessagesDialog.h"

#include "App.h"
#include "EnumFormatEtc.h"
#include "FormatUtils.h"
#include "LangUtils.h"

#include "resource.h"
#include "../Explorer/resource.h"

using namespace NWindows;
using namespace NFile;
using namespace NDir;

#ifndef _UNICODE
extern bool g_IsNT;
#endif

#define PRF(x)
#define PRF_W(x)
// #define PRF2(x)
#define PRF3(x)
#define PRF3_W(x)
#define PRF4(x)
// #define PRF4(x) OutputDebugStringA(x)
// #define PRF4_W(x) OutputDebugStringW(x)

// #define SHOW_DEBUG_DRAG

#ifdef SHOW_DEBUG_DRAG

#define PRF_(x) { x; }

static void Print_Point(const char *name, DWORD keyState, const POINTL &pt, DWORD effect)
{
  AString s (name);
  s += " x=";  s.Add_UInt32((unsigned)pt.x);
  s += " y=";  s.Add_UInt32((unsigned)pt.y);
  s += " k=";  s.Add_UInt32(keyState);
  s += " e=";  s.Add_UInt32(effect);
  PRF4(s);
}

#else

#define PRF_(x)

#endif


#define kTempDirPrefix  FTEXT("7zE")

// all versions: k_Format_7zip_SetTargetFolder format to transfer folder path from target to source
static LPCTSTR const k_Format_7zip_SetTargetFolder = TEXT("7-Zip::SetTargetFolder");
// new v23 formats:
static LPCTSTR const k_Format_7zip_SetTransfer = TEXT("7-Zip::SetTransfer");
static LPCTSTR const k_Format_7zip_GetTransfer = TEXT("7-Zip::GetTransfer");

/*
  Win10: clipboard formats.
  There are about 16K free ids (formats) per system that can be
  registered with RegisterClipboardFormat() with different names.
  Probably that 16K ids space is common for ids registering for both
  formats: RegisterClipboardFormat(), and registered window classes:
  RegisterClass(). But ids for window classes will be deleted from
  the list after process finishing. And registered clipboard
  formats probably will be deleted from the list only after reboot.
*/

// static bool const g_CreateArchive_for_Drag_from_7zip = false;
// static bool const g_CreateArchive_for_Drag_from_Explorer = true;
    // = false; // for debug

/*
How DoDragDrop() works:
{
  IDropSource::QueryContinueDrag()  (keyState & MK_LBUTTON) != 0
  IDropTarget::Enter()
    IDropSource::GiveFeedback()
  IDropTarget::DragOver()
    IDropSource::GiveFeedback()

  for()
  {
    IDropSource::QueryContinueDrag()  (keyState & MK_LBUTTON) != 0
    IDropTarget::DragOver()           (keyState & MK_LBUTTON) != 0
      IDropSource::GiveFeedback()
  }

  {
    // DoDragDrop() in Win10 before calling // QueryContinueDrag()
    // with (*(keyState & MK_LBUTTON) == 0) probably calls:
    //   1) IDropTarget::DragOver() with same point values (x,y), but (keyState & MK_LBUTTON) != 0)
    //   2) IDropSource::GiveFeedback().
    // so DropSource can know exact GiveFeedback(effect) mode just before LBUTTON releasing.

    if (IDropSource::QueryContinueDrag() for (keyState & MK_LBUTTON) == 0
      returns DRAGDROP_S_DROP), it will call
    IDropTarget::Drop()
  }
  or
  {
    IDropSource::QueryContinueDrag()
    IDropTarget::DragLeave()
    IDropSource::GiveFeedback(0)
  }
  or
  {
    if (IDropSource::QueryContinueDrag()
      returns DRAGDROP_S_CANCEL)
    IDropTarget::DragLeave()
  }
}
*/


// ---------- CDropTarget ----------

static const UInt32 k_Struct_Id_SetTranfer = 2;  // it's our selected id
static const UInt32 k_Struct_Id_GetTranfer = 3;  // it's our selected id

static const UInt64 k_Program_Id = 1; // "7-Zip"

enum E_Program_ISA
{
  k_Program_ISA_x86   = 2,
  k_Program_ISA_x64   = 3,
  k_Program_ISA_armt  = 4,
  k_Program_ISA_arm64 = 5,
  k_Program_ISA_arm32 = 6,
  k_Program_ISA_ia64  = 9
};

#define k_Program_Ver ((MY_VER_MAJOR << 16) | MY_VER_MINOR)


// k_SourceFlags_* are flags that are sent from Source to Target

static const UInt32 k_SourceFlags_DoNotProcessInTarget = 1 << 1;
/* Do not process in Target. Source will process operation instead of Target.
   By default Target processes Drop opearation. */
// static const UInt32 k_SourceFlags_ProcessInTarget      = 1 << 2;

static const UInt32 k_SourceFlags_DoNotWaitFinish   = 1 << 3;
static const UInt32 k_SourceFlags_WaitFinish        = 1 << 4;
/* usually Source needs WaitFinish, if temp files were created. */

static const UInt32 k_SourceFlags_TempFiles         = 1 << 6;
static const UInt32 k_SourceFlags_NamesAreParent    = 1 << 7;
/* if returned path list for GetData(CF_HDROP) contains
   path of parent temp folder instead of final paths of items
   that will be extracted later from archive */

static const UInt32 k_SourceFlags_SetTargetFolder   = 1 << 8;
/* SetData::("SetTargetFolder") was called (with empty or non-empty string) */

static const UInt32 k_SourceFlags_SetTargetFolder_NonEmpty  = 1 << 9;
/* SetData::("SetTargetFolder") was called with non-empty string */

static const UInt32 k_SourceFlags_NeedExtractOpToFs = 1 << 10;

static const UInt32 k_SourceFlags_Copy_WasCalled = 1 << 11;

static const UInt32 k_SourceFlags_LeftButton        = 1 << 14;
static const UInt32 k_SourceFlags_RightButton       = 1 << 15;


static const UInt32 k_TargetFlags_WasCanceled = 1 << 0;
static const UInt32 k_TargetFlags_MustBeProcessedBySource = 1 << 1;
static const UInt32 k_TargetFlags_WasProcessed    = 1 << 2;
static const UInt32 k_TargetFlags_DoNotWaitFinish = 1 << 3;
static const UInt32 k_TargetFlags_WaitFinish      = 1 << 4;
static const UInt32 k_TargetFlags_MenuWasShown    = 1 << 16;

struct CDataObject_TransferBase
{
  UInt32 Struct_Id;
  UInt32 Struct_Size;

  UInt64 Program_Id;
  UInt32 Program_Ver_Main;
  UInt32 Program_Ver_Build;
  UInt32 Program_ISA;
  UInt32 Program_Flags;

  UInt32 ProcessId;
  UInt32 _reserved1[7];

protected:
  void Init_Program();
};


void CDataObject_TransferBase::Init_Program()
{
  Program_Id = k_Program_Id;
  Program_ISA =
    #if defined(MY_CPU_AMD64)
      k_Program_ISA_x64
    #elif defined(MY_CPU_X86)
      k_Program_ISA_x86
    #elif defined(MY_CPU_ARM64)
      k_Program_ISA_arm64
    #elif defined(MY_CPU_ARM32)
      k_Program_ISA_arm32
    #elif defined(MY_CPU_ARMT) || defined(MY_CPU_ARM)
      k_Program_ISA_armt
    #elif defined(MY_CPU_IA64)
      k_Program_ISA_ia64
    #else
      0
    #endif
      ;
  Program_Flags = sizeof(size_t);
  Program_Ver_Main = k_Program_Ver;
  // Program_Ver_Build = 0;
  ProcessId = GetCurrentProcessId();
}


#if defined(__GNUC__) && !defined(__clang__)
/* 'void* memset(void*, int, size_t)' clearing an object
    of non-trivial type 'struct CDataObject_SetTransfer' */
#pragma GCC diagnostic ignored "-Wclass-memaccess"
#endif


struct CDataObject_GetTransfer:
public CDataObject_TransferBase
{
  UInt32 Flags;

  UInt32 _reserved2[11];

  CDataObject_GetTransfer()
  {
    memset(this, 0, sizeof(*this));
    Init_Program();
    Struct_Id = k_Struct_Id_GetTranfer;
    Struct_Size = sizeof(*this);
  }

  bool Check() const
  {
    return Struct_Size >= sizeof(*this) && Struct_Id == k_Struct_Id_GetTranfer;
  }
};


enum Enum_FolderType
{
  k_FolderType_None,
  k_FolderType_Unknown = 1,
  k_FolderType_Fs = 2,
  k_FolderType_AltStreams = 3,
  k_FolderType_Archive = 4
};

struct CTargetTransferInfo
{
  UInt32 Flags;
  UInt32 FuncType;
  
  UInt32 KeyState;
  UInt32 OkEffects;
  POINTL Point;

  UInt32 Cmd_Effect;
  UInt32 Cmd_Type;
  UInt32 FolderType;
  UInt32 _reserved3[3];

  CTargetTransferInfo()
  {
    memset(this, 0, sizeof(*this));
  }
};

struct CDataObject_SetTransfer:
public CDataObject_TransferBase
{
  CTargetTransferInfo Target;

  void Init()
  {
    memset(this, 0, sizeof(*this));
    Init_Program();
    Struct_Id = k_Struct_Id_SetTranfer;
    Struct_Size = sizeof(*this);
  }

  bool Check() const
  {
    return Struct_Size >= sizeof(*this) && Struct_Id == k_Struct_Id_SetTranfer;
  }
};





enum Enum_DragTargetMode
{
  k_DragTargetMode_None   = 0,
  k_DragTargetMode_Leave  = 1,
  k_DragTargetMode_Enter  = 2,
  k_DragTargetMode_Over   = 3,
  k_DragTargetMode_Drop_Begin = 4,
  k_DragTargetMode_Drop_End   = 5
};


// ---- menu ----

namespace NDragMenu {

enum Enum_CmdId
{
  k_None          = 0,
  k_Cancel        = 1,
  k_Copy_Base     = 2, // to fs
  k_Copy_ToArc    = 3,
  k_AddToArc      = 4
  /*
  k_OpenArc       = 8,
  k_TestArc       = 9,
  k_ExtractFiles  = 10,
  k_ExtractHere   = 11
  */
};

struct CCmdLangPair
{
  unsigned CmdId_and_Flags;
  unsigned LangId;
};

static const UInt32 k_MenuFlags_CmdMask = (1 << 7) - 1;
static const UInt32 k_MenuFlag_Copy = 1 << 14;
static const UInt32 k_MenuFlag_Move = 1 << 15;
// #define IDS_CANCEL (IDCANCEL + 400)
#define IDS_CANCEL 402

static const CCmdLangPair g_Pairs[] =
{
  { k_Copy_Base  | k_MenuFlag_Copy,  IDS_COPY },
  { k_Copy_Base  | k_MenuFlag_Move,  IDS_MOVE },
  { k_Copy_ToArc | k_MenuFlag_Copy,  IDS_COPY_TO },
  // { k_Copy_ToArc | k_MenuFlag_Move,  IDS_MOVE_TO }, // IDS_CONTEXT_COMPRESS_TO
  // { k_OpenArc,      IDS_CONTEXT_OPEN },
  // { k_ExtractFiles, IDS_CONTEXT_EXTRACT },
  // { k_ExtractHere,  IDS_CONTEXT_EXTRACT_HERE },
  // { k_TestArc,      IDS_CONTEXT_TEST },
  { k_AddToArc   | k_MenuFlag_Copy,  IDS_CONTEXT_COMPRESS },
  { k_Cancel, IDS_CANCEL }
};
 
}


class CDropTarget Z7_final:
  public IDropTarget,
  public CMyUnknownImp
{
  Z7_COM_UNKNOWN_IMP_1_MT(IDropTarget)
  STDMETHOD(DragEnter)(IDataObject *dataObject, DWORD keyState, POINTL pt, DWORD *effect) Z7_override;
  STDMETHOD(DragOver)(DWORD keyState, POINTL pt, DWORD *effect) Z7_override;
  STDMETHOD(DragLeave)() Z7_override;
  STDMETHOD(Drop)(IDataObject *dataObject, DWORD keyState, POINTL pt, DWORD *effect) Z7_override;

  bool m_IsRightButton;
  bool m_GetTransfer_WasSuccess;
  bool m_DropIsAllowed;      // = true, if data IDataObject can return CF_HDROP (so we can get list of paths)
  bool m_PanelDropIsAllowed; // = false, if current target_panel is source_panel.
                             // check it only if m_DropIsAllowed == true
                             // we use it to show icon effect that drop is not allowed here.
  
  CMyComPtr<IDataObject> m_DataObject; // we set it in DragEnter()
  UStringVector m_SourcePaths;
  
  // int m_DropHighlighted_SelectionIndex;
  // int m_SubFolderIndex;      // realIndex of item in m_Panel list (if drop cursor to that item)
  // UString m_DropHighlighted_SubFolderName;   // name of folder in m_Panel list (if drop cursor to that folder)

  CPanel *m_Panel;
  bool m_IsAppTarget;        // true, if we want to drop to app window (not to panel)

  bool m_TargetPath_WasSent_ToDataObject;           // true, if TargetPath was sent
  bool m_TargetPath_NonEmpty_WasSent_ToDataObject;  // true, if non-empty TargetPath was sent
  bool m_Transfer_WasSent_ToDataObject;  // true, if Transfer was sent
  UINT m_Format_7zip_SetTargetFolder;
  UINT m_Format_7zip_SetTransfer;
  UINT m_Format_7zip_GetTransfer;

  UInt32 m_ProcessId; // for sending

  bool IsItSameDrive() const;

  // void Try_QueryGetData(IDataObject *dataObject);
  void LoadNames_From_DataObject(IDataObject *dataObject);

  UInt32 GetFolderType() const;
  bool IsFsFolderPath() const;
  DWORD GetEffect(DWORD keyState, POINTL pt, DWORD allowedEffect) const;
  void RemoveSelection();
  void PositionCursor(const POINTL &ptl);
  UString GetTargetPath() const;
  bool SendToSource_TargetPath_enable(IDataObject *dataObject, bool enablePath);
  bool SendToSource_UInt32(IDataObject *dataObject, UINT format, UInt32 value);
  bool SendToSource_TransferInfo(IDataObject *dataObject,
      const CTargetTransferInfo &info);
  void SendToSource_auto(IDataObject *dataObject,
      const CTargetTransferInfo &info);
  void SendToSource_Drag(CTargetTransferInfo &info)
  {
    SendToSource_auto(m_DataObject, info);
  }

  void ClearState();

public:
  CDropTarget();

  CApp *App;
  int SrcPanelIndex;     // index of D&D source_panel
  int TargetPanelIndex;  // what panel to use as target_panel of Application
};




// ---------- CDataObject ----------

/*
  Some programs (like Sticky Notes in Win10) do not like
  virtual non-existing items (files/dirs) in CF_HDROP format.
  So we use two versions of CF_HDROP data:
    m_hGlobal_HDROP_Pre   : the list contains only destination path of temp directory.
        That directory later will be filled with extracted items.
    m_hGlobal_HDROP_Final : the list contains paths of all root items that
        will be created in temp directory by archive extraction operation,
        or the list of existing fs items, if source is filesystem directory.
     
  The DRAWBACK: some programs (like Edge in Win10) can use names from IDataObject::GetData()
  call that was called before IDropSource::QueryContinueDrag() where we set (UseFinalGlobal = true)
  So such programs will use non-relevant m_hGlobal_HDROP_Pre item,
  instead of m_hGlobal_HDROP_Final items.
*/

class CDataObject Z7_final:
  public IDataObject,
  public CMyUnknownImp
{
  Z7_COM_UNKNOWN_IMP_1_MT(IDataObject)

  Z7_COMWF_B GetData(LPFORMATETC pformatetcIn, LPSTGMEDIUM medium) Z7_override;
  Z7_COMWF_B GetDataHere(LPFORMATETC pformatetc, LPSTGMEDIUM medium) Z7_override;
  Z7_COMWF_B QueryGetData(LPFORMATETC pformatetc) Z7_override;

  Z7_COMWF_B GetCanonicalFormatEtc(LPFORMATETC /* pformatetc */, LPFORMATETC pformatetcOut) Z7_override
  {
    if (!pformatetcOut)
      return E_INVALIDARG;
    pformatetcOut->ptd = NULL;
    return E_NOTIMPL;
  }

  Z7_COMWF_B SetData(LPFORMATETC etc, STGMEDIUM *medium, BOOL release) Z7_override;
  Z7_COMWF_B EnumFormatEtc(DWORD drection, LPENUMFORMATETC *enumFormatEtc) Z7_override;
  
  Z7_COMWF_B DAdvise(FORMATETC * /* etc */, DWORD /* advf */, LPADVISESINK /* pAdvSink */, DWORD * /* pdwConnection */) Z7_override
    { return OLE_E_ADVISENOTSUPPORTED; }
  Z7_COMWF_B DUnadvise(DWORD /* dwConnection */) Z7_override
    { return OLE_E_ADVISENOTSUPPORTED; }
  Z7_COMWF_B EnumDAdvise(LPENUMSTATDATA *ppenumAdvise) Z7_override
  {
    if (ppenumAdvise)
      *ppenumAdvise = NULL;
    return OLE_E_ADVISENOTSUPPORTED;
  }

  bool m_PerformedDropEffect_WasSet;
  bool m_LogicalPerformedDropEffect_WasSet;
  bool m_DestDirPrefix_FromTarget_WasSet;
public:
  bool m_Transfer_WasSet;
private:
  // GetData formats (source to target):
  FORMATETC m_Etc;
  // UINT m_Format_FileOpFlags;
  // UINT m_Format_PreferredDropEffect;

  // SetData() formats (target to source):
  // 7-Zip's format:
  UINT m_Format_7zip_SetTargetFolder;
  UINT m_Format_7zip_SetTransfer;
  UINT m_Format_7zip_GetTransfer; // for GetData()

  UINT m_Format_PerformedDropEffect;
  UINT m_Format_LogicalPerformedDropEffect;
  UINT m_Format_DisableDragText;
  UINT m_Format_IsShowingLayered;
  UINT m_Format_IsShowingText;
  UINT m_Format_DropDescription;
  UINT m_Format_TargetCLSID;

  DWORD m_PerformedDropEffect;
  DWORD m_LogicalPerformedDropEffect;

  void CopyFromPanelTo_Folder();
  HRESULT SetData2(const FORMATETC *formatetc, const STGMEDIUM *medium);

public:
  bool IsRightButton;
  bool IsTempFiles;

  bool UsePreGlobal;
  bool DoNotProcessInTarget;

  bool NeedCall_Copy;
  bool Copy_WasCalled;

  NMemory::CGlobal m_hGlobal_HDROP_Pre;
  NMemory::CGlobal m_hGlobal_HDROP_Final;
  // NMemory::CGlobal m_hGlobal_FileOpFlags;
  // NMemory::CGlobal m_hGlobal_PreferredDropEffect;
  
  CPanel *Panel;
  CRecordVector<UInt32> Indices;

  UString SrcDirPrefix_Temp; // FS directory with source files or Temp
  UString DestDirPrefix_FromTarget;
  /* destination Path that was sent by Target via SetData().
     it can be altstreams prefix.
     if (!DestDirPrefix_FromTarget.IsEmpty()) m_Panel->CompressDropFiles() was not called by Target.
     So we must do drop actions in Source */
  HRESULT Copy_HRESULT;
  UStringVector Messages;

  CDataObject();
public:
  CDataObject_SetTransfer m_Transfer;
};


// for old mingw:
#ifndef CFSTR_LOGICALPERFORMEDDROPEFFECT
#define CFSTR_LOGICALPERFORMEDDROPEFFECT    TEXT("Logical Performed DropEffect")
#endif
#ifndef CFSTR_TARGETCLSID
#define CFSTR_TARGETCLSID                   TEXT("TargetCLSID")                         // HGLOBAL with a CLSID of the drop target
#endif



CDataObject::CDataObject()
{
  // GetData formats (source to target):
  // and we use CF_HDROP format to transfer file paths from source to target:
  m_Etc.cfFormat = CF_HDROP;
  m_Etc.ptd = NULL;
  m_Etc.dwAspect = DVASPECT_CONTENT;
  m_Etc.lindex = -1;
  m_Etc.tymed = TYMED_HGLOBAL;

  // m_Format_FileOpFlags          = RegisterClipboardFormat(TEXT("FileOpFlags"));
  // m_Format_PreferredDropEffect  = RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT); // "Preferred DropEffect"

  // SetData() formats (target to source):
  m_Format_7zip_SetTargetFolder = RegisterClipboardFormat(k_Format_7zip_SetTargetFolder);
  m_Format_7zip_SetTransfer     = RegisterClipboardFormat(k_Format_7zip_SetTransfer);
  m_Format_7zip_GetTransfer     = RegisterClipboardFormat(k_Format_7zip_GetTransfer);
  
  m_Format_PerformedDropEffect  = RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT); // "Performed DropEffect"
  m_Format_LogicalPerformedDropEffect = RegisterClipboardFormat(CFSTR_LOGICALPERFORMEDDROPEFFECT); // "Logical Performed DropEffect"
  m_Format_DisableDragText      = RegisterClipboardFormat(TEXT("DisableDragText"));
  m_Format_IsShowingLayered     = RegisterClipboardFormat(TEXT("IsShowingLayered"));
  m_Format_IsShowingText        = RegisterClipboardFormat(TEXT("IsShowingText"));
  m_Format_DropDescription      = RegisterClipboardFormat(TEXT("DropDescription"));
  m_Format_TargetCLSID          = RegisterClipboardFormat(CFSTR_TARGETCLSID);

  m_PerformedDropEffect = 0;
  m_LogicalPerformedDropEffect = 0;

  m_PerformedDropEffect_WasSet = false;
  m_LogicalPerformedDropEffect_WasSet = false;
  
  m_DestDirPrefix_FromTarget_WasSet = false;
  m_Transfer_WasSet = false;

  IsRightButton = false;
  IsTempFiles = false;
  
  UsePreGlobal = false;
  DoNotProcessInTarget = false;

  NeedCall_Copy = false;
  Copy_WasCalled = false;

  Copy_HRESULT = S_OK;
}



void CDataObject::CopyFromPanelTo_Folder()
{
  try
  {
    CCopyToOptions options;
    options.folder = SrcDirPrefix_Temp;
    /* 15.13: fixed problem with mouse cursor for password window.
       DoDragDrop() probably calls SetCapture() to some hidden window.
       But it's problem, if we show some modal window, like MessageBox.
       So we return capture to our window.
       If you know better way to solve the problem, please notify 7-Zip developer.
    */
    // MessageBoxW(*Panel, L"test", L"test", 0);
    /* HWND oldHwnd = */ SetCapture(*Panel);
    Copy_WasCalled = true;
    Copy_HRESULT = E_FAIL;
    Copy_HRESULT = Panel->CopyTo(options, Indices, &Messages);
    // do we need to restore capture?
    // ReleaseCapture();
    // oldHwnd = SetCapture(oldHwnd);
  }
  catch(...)
  {
    Copy_HRESULT = E_FAIL;
  }
}


#ifdef SHOW_DEBUG_DRAG

static void PrintFormat2(AString &s, unsigned format)
{
  s += " ";
  s += "= format=";
  s.Add_UInt32(format);
  s += " ";
  const int k_len = 512;
  CHAR temp[k_len];
  if (GetClipboardFormatNameA(format, temp, k_len) && strlen(temp) != 0)
    s += temp;
}

static void PrintFormat(const char *title, unsigned format)
{
  AString s (title);
  PrintFormat2(s, format);
  PRF4(s);
}

static void PrintFormat_AndData(const char *title, unsigned format, const void *data, size_t size)
{
  AString s (title);
  PrintFormat2(s, format);
  s += " size=";
  s.Add_UInt32((UInt32)size);
  for (size_t i = 0; i < size && i < 16; i++)
  {
    s += " ";
    s.Add_UInt32(((const Byte *)data)[i]);
  }
  PRF4(s);
}

static void PrintFormat_GUIDToStringW(const void *p)
{
  const GUID *guid = (const GUID *)p;
  UString s;
  const unsigned kSize = 48;
  StringFromGUID2(*guid, s.GetBuf(kSize), kSize);
  s.ReleaseBuf_CalcLen(kSize);
  PRF3_W(s);
}

// Vista
typedef enum
{
  MY_DROPIMAGE_INVALID  = -1,                // no image preference (use default)
  MY_DROPIMAGE_NONE     = 0,                 // red "no" circle
  MY_DROPIMAGE_COPY     = DROPEFFECT_COPY,   // plus for copy
  MY_DROPIMAGE_MOVE     = DROPEFFECT_MOVE,   // movement arrow for move
  MY_DROPIMAGE_LINK     = DROPEFFECT_LINK,   // link arrow for link
  MY_DROPIMAGE_LABEL    = 6,                 // tag icon to indicate metadata will be changed
  MY_DROPIMAGE_WARNING  = 7,                 // yellow exclamation, something is amiss with the operation
  MY_DROPIMAGE_NOIMAGE  = 8                  // no image at all
} MY_DROPIMAGETYPE;

typedef struct {
  MY_DROPIMAGETYPE type;
  WCHAR szMessage[MAX_PATH];
  WCHAR szInsert[MAX_PATH];
} MY_DROPDESCRIPTION;

#endif


/*
IDataObject::SetData(LPFORMATETC etc, STGMEDIUM *medium, BOOL release)
======================================================================

  Main purpose of CDataObject is to transfer data from source to target
  of drag and drop operation.
  But also CDataObject can be used to transfer data in backward direction
  from target to source (even if target and source are different processes).
  There are some predefined Explorer's formats to transfer some data from target to source.
  And 7-Zip uses 7-Zip's format k_Format_7zip_SetTargetFolder to transfer
  destination directory path from target to source.

  Our CDataObject::SetData() function here is used only to transfer data from target to source.
  Usual source_to_target data is filled to m_hGlobal_* objects directly without SetData() calling.

The main problem of SetData() is ownership of medium for (release == TRUE) case.

SetData(,, release = TRUE) from different processes (DropSource and DropTarget)
===============================================================================
{
  MS DOCs about (STGMEDIUM *medium) ownership:
    The data object called does not take ownership of the data
    until it has successfully received it and no error code is returned.

  Each of processes (Source and Target) has own copy of medium allocated.
  Windows code creates proxy IDataObject object in Target process to transferr
  SetData() call between Target and Source processes via special proxies:
    DropTarget ->
    proxy_DataObject_in_Target ->
    proxy_in_Source ->
    DataObject_in_Source
  when Target calls SetData() with proxy_DataObject_in_Target,
  the system and proxy_in_Source
   - allocates proxy-medium-in-Source process
   - copies medium data from Target to that proxy-medium-in-Source
   - sends proxy-medium-in-Source to DataObject_in_Source->SetData().
  
  after returning from SetData() to Target process:
    Win10 proxy_DataObject_in_Target releases original medium in Target process,
    only if SetData() in Source returns S_OK. It's consistent with DOCs above.

  for unsupported cfFormat:
  [DropSource is 7-Zip 22.01 (old) : (etc->cfFormat != m_Format_7zip_SetTargetFolder && release == TRUE)]
  (DropSource is WinRAR case):
  Source doesn't release medium and returns error (for example, E_NOTIMPL)
  {
    Then Win10 proxy_in_Source also doesn't release proxy-medium-in-Source.
    So there is memory leak in Source process.
    Probably Win10 proxy_in_Source tries to avoid possible double releasing
    that can be more fatal than memory leak.
    
    Then Win10 proxy_DataObject_in_Target also doesn't release
    original medium, that was allocated by DropTarget.
    So if DropTarget also doesn't release medium, there is memory leak in
    DropTarget process too.
    DropTarget is Win10-Explorer probably doesn't release medium in that case.
  }

  [DropSource is 7-Zip 22.01 (old) : (etc->cfFormat == m_Format_7zip_SetTargetFolder && release == TRUE)]
  DropSource returns S_OK and doesn't release medium:
  {
    then there is memory leak in DropSource process only.
  }

  (DropSource is 7-Zip v23 (new)):
  (DropSource is Win10-Explorer case)
  {
    Win10-Explorer-DropSource probably always releases medium,
    and then it always returns S_OK.
    So Win10 proxy_DataObject_in_Target also releases
    original medium, that was allocated by DropTarget.
    So there is no memory leak in Source and Target processes.
  }

  if (DropTarget is Win10-Explorer)
  {
    Explorer Target uses SetData(,, (release = TRUE)) and
    Explorer Target probably doesn't free memory after SetData(),
      even if SetData(,, (release = TRUE)) returns E_NOTIMPL;
  }

  if (DropSource is Win10-Explorer)
  {
    (release == FALSE) doesn't work, and SetData() returns E_NOTIMPL;
    (release == TRUE)  works, and SetData() returns S_OK, and
                       it returns S_OK even for formats unsupported by Explorer.
  }
  
  To be more compatible with DOCs and Win10-Explorer and to avoid memory leaks,
  we use the following scheme for our IDataObject::SetData(,, release == TRUE)
  in DropSource code:
  if (release == TRUE) { our SetData() always releases medium
      with ReleaseStgMedium() and returns S_OK; }
  The DRAWBACK of that scheme:
    The caller always receives S_OK,
    so the caller doesn't know about any error in SetData() in that case.

for 7zip-Target to 7zip-Source calls:
  we use (release == FALSE)
  So we avoid (release == TRUE) memory leak problems,
  and we can get real return code from SetData().

for 7zip-Target to Explorer-Source calls:
  we use (release == TRUE).
  beacuse Explorer-Source doesn't accept (release == FALSE).
}
*/

/*
https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/shell/datascenarios.md
CFSTR_PERFORMEDDROPEFFECT:
  is used by the target to inform the data object through its
  IDataObject::SetData method of the outcome of a data transfer.
CFSTR_PREFERREDDROPEFFECT:
  is used by the source to specify whether its preferred method of data transfer is move or copy.
*/

Z7_COMWF_B CDataObject::SetData(LPFORMATETC etc, STGMEDIUM *medium, BOOL release)
{
  try {
  const HRESULT hres = SetData2(etc, medium);
  // PrintFormat(release ? "SetData RELEASE=TRUE" : "SetData RELEASE=FALSE" , etc->cfFormat);
  if (release)
  {
    /*
    const DWORD tymed = medium->tymed;
    IUnknown *pUnkForRelease = medium->pUnkForRelease;
    */
    // medium->tymed = NULL; // for debug
    // return E_NOTIMPL;  // for debug
    ReleaseStgMedium(medium);
    /* ReleaseStgMedium() will change STGMEDIUM::tymed to (TYMED_NULL = 0).
       but we also can clear (medium.hGlobal = NULL),
       to prevent some incorrect releasing, if the caller will try to release the data  */
    /*
    if (medium->tymed == TYMED_NULL && tymed == TYMED_HGLOBAL && !pUnkForRelease)
      medium->hGlobal = NULL;
    */
    // do we need return S_OK; for (tymed != TYMED_HGLOBAL) cases ?
    /* we return S_OK here to shows that we take ownership of the data in (medium),
       so the caller will not try to release (medium) */
    return S_OK; // to be more compatible with Win10-Explorer and DOCs.
  }
  return hres;
  } catch(...) { return E_FAIL; }
}



HRESULT CDataObject::SetData2(const FORMATETC *etc, const STGMEDIUM *medium)
{
  // PRF3("== CDataObject::SetData()");

  HRESULT hres = S_OK;

  if (etc->cfFormat == 0)
    return DV_E_FORMATETC;
  if (etc->tymed != TYMED_HGLOBAL)
    return E_NOTIMPL; // DV_E_TYMED;
  if (etc->dwAspect != DVASPECT_CONTENT)
    return E_NOTIMPL; // DV_E_DVASPECT;
  if (medium->tymed != TYMED_HGLOBAL)
    return E_NOTIMPL; // DV_E_TYMED;

  if (!medium->hGlobal)
    return S_OK;

  if (etc->cfFormat == m_Format_7zip_SetTargetFolder)
  {
    DestDirPrefix_FromTarget.Empty();
    m_DestDirPrefix_FromTarget_WasSet = true;
  }
  else if (etc->cfFormat == m_Format_7zip_SetTransfer)
    m_Transfer_WasSet = false;

  const size_t size = GlobalSize(medium->hGlobal);
  // GlobalLock() can return NULL, if memory block has a zero size
  if (size == 0)
    return S_OK;
  const void *src = (const Byte *)GlobalLock(medium->hGlobal);
  if (!src)
    return E_FAIL;

  PRF_(PrintFormat_AndData("SetData", etc->cfFormat, src, size))

  if (etc->cfFormat == m_Format_7zip_SetTargetFolder)
  {
    /* this is our registered k_Format_7zip_SetTargetFolder format.
       so it's call from 7-zip's CDropTarget */
    /* 7-zip's CDropTarget calls SetData() for m_Format_7zip_SetTargetFolder
       with (release == FALSE) */
    const size_t num = size / sizeof(wchar_t);
    if (size != num * sizeof(wchar_t))
      return E_FAIL;
    // if (num == 0) return S_OK;
    // GlobalLock() can return NULL, if memory block has a zero-byte size
    const wchar_t *s = (const wchar_t *)src;
    UString &dest = DestDirPrefix_FromTarget;
    for (size_t i = 0; i < num; i++)
    {
      const wchar_t c = s[i];
      if (c == 0)
        break;
      dest += c;
    }
    // PRF_(PrintFormat_AndData("SetData", etc->cfFormat, src, size))
    PRF3_W(DestDirPrefix_FromTarget);
  }
  else if (etc->cfFormat == m_Format_7zip_SetTransfer)
  {
    /* 7-zip's CDropTarget calls SetData() for m_Format_7zip_SetTransfer
       with (release == FALSE) */
    if (size < sizeof(CDataObject_SetTransfer))
      return E_FAIL;
    const CDataObject_SetTransfer *t = (const CDataObject_SetTransfer *)src;
    if (!t->Check())
      return E_FAIL;
    m_Transfer = *t;
    if (t->Target.FuncType != k_DragTargetMode_Leave)
      m_Transfer_WasSet = true;
    bool needProcessBySource = !DestDirPrefix_FromTarget.IsEmpty();
    if (t->Target.FuncType == k_DragTargetMode_Drop_Begin)
    {
      if (t->Target.Cmd_Type != NDragMenu::k_Copy_Base
          // || t->Target.Cmd_Effect != DROPEFFECT_COPY
          )
        needProcessBySource = false;
    }
    if (t->Target.FuncType == k_DragTargetMode_Drop_End)
    {
      if (t->Target.Flags & k_TargetFlags_MustBeProcessedBySource)
        needProcessBySource = true;
      else if (t->Target.Flags & k_TargetFlags_WasProcessed)
        needProcessBySource = false;
    }
    DoNotProcessInTarget = needProcessBySource;
  }
  else
  {
    // SetData() from Explorer Target:
    if (etc->cfFormat == m_Format_PerformedDropEffect)
    {
      m_PerformedDropEffect_WasSet = false;
      if (size == sizeof(DWORD))
      {
        m_PerformedDropEffect = *(const DWORD *)src;
        m_PerformedDropEffect_WasSet = true;
      }
    }
    else if (etc->cfFormat == m_Format_LogicalPerformedDropEffect)
    {
      m_LogicalPerformedDropEffect_WasSet = false;
      if (size == sizeof(DWORD))
      {
        m_LogicalPerformedDropEffect = *(const DWORD *)src;
        m_LogicalPerformedDropEffect_WasSet = true;
      }
    }
    else if (etc->cfFormat == m_Format_DropDescription)
    {
      // drop description contains only name of dest folder without full path
      #ifdef SHOW_DEBUG_DRAG
      if (size == sizeof(MY_DROPDESCRIPTION))
      {
        // const MY_DROPDESCRIPTION *s = (const MY_DROPDESCRIPTION *)src;
        // PRF3_W(s->szMessage);
        // PRF3_W(s->szInsert);
      }
      #endif
    }
    else if (etc->cfFormat == m_Format_TargetCLSID)
    {
      // it's called after call QueryContinueDrag() (keyState & MK_LBUTTON) == 0
      // Shell File System Folder (explorer) guid: F3364BA0-65B9-11CE-A9BA-00AA004AE837
      #ifdef SHOW_DEBUG_DRAG
      if (size == 16)
      {
        PrintFormat_GUIDToStringW((const Byte *)src);
      }
      #endif
    }
    else if (etc->cfFormat == m_Format_DisableDragText)
    {
      // (size == 4) (UInt32 value)
      //    value==0 : if drag to folder item or folder
      //    value==1 : if drag to file or non list_view */
    }
    else if (
        etc->cfFormat == m_Format_IsShowingLayered ||
        etc->cfFormat == m_Format_IsShowingText)
    {
      // (size == 4) (UInt32 value) value==0 :
    }
    else
      hres = DV_E_FORMATETC;
    // hres = E_NOTIMPL; // for debug
    // hres = DV_E_FORMATETC; // for debug
  }

  GlobalUnlock(medium->hGlobal);
  return hres;
}



static HGLOBAL DuplicateGlobalMem(HGLOBAL srcGlobal)
{
  /* GlobalSize() returns 0: If the specified handle
     is not valid or if the object has been discarded */
  const SIZE_T size = GlobalSize(srcGlobal);
  if (size == 0)
    return NULL;
  // GlobalLock() can return NULL, if memory block has a zero-byte size
  const void *src = GlobalLock(srcGlobal);
  if (!src)
    return NULL;
  HGLOBAL destGlobal = GlobalAlloc(GHND | GMEM_SHARE, size);
  if (destGlobal)
  {
    void *dest = GlobalLock(destGlobal);
    if (!dest)
    {
      GlobalFree(destGlobal);
      destGlobal = NULL;
    }
    else
    {
      memcpy(dest, src, size);
      GlobalUnlock(destGlobal);
    }
  }
  GlobalUnlock(srcGlobal);
  return destGlobal;
}


static bool Medium_CopyFrom(LPSTGMEDIUM medium, const void *data, size_t size)
{
  medium->tymed = TYMED_NULL;
  medium->pUnkForRelease = NULL;
  medium->hGlobal = NULL;
  const HGLOBAL global = GlobalAlloc(GHND | GMEM_SHARE, size);
  if (!global)
    return false;
  void *dest = GlobalLock(global);
  if (!dest)
  {
    GlobalFree(global);
    return false;
  }
  memcpy(dest, data, size);
  GlobalUnlock(global);
  medium->hGlobal = global;
  medium->tymed = TYMED_HGLOBAL;
  return true;
}


Z7_COMWF_B CDataObject::GetData(LPFORMATETC etc, LPSTGMEDIUM medium)
{
  try {
  PRF_(PrintFormat("-- GetData", etc->cfFormat))

  medium->tymed = TYMED_NULL;
  medium->pUnkForRelease = NULL;
  medium->hGlobal = NULL;

  if (NeedCall_Copy && !Copy_WasCalled)
    CopyFromPanelTo_Folder();

  // PRF3("+ CDataObject::GetData");
  // PrintFormat(etc->cfFormat);
  HGLOBAL global;
  RINOK(QueryGetData(etc))
  
  /*
  if (etc->cfFormat == m_Format_FileOpFlags)
    global = m_hGlobal_FileOpFlags;
  else if (etc->cfFormat == m_Format_PreferredDropEffect)
  {
    // Explorer requests PreferredDropEffect only if Move/Copy selection is possible:
    //   Shift is not pressed and Ctrl is not pressed
    PRF3("------ CDataObject::GetData() PreferredDropEffect");
    global = m_hGlobal_PreferredDropEffect;
  }
  else
  */
  if (etc->cfFormat == m_Etc.cfFormat) // CF_HDROP
    global = UsePreGlobal ? m_hGlobal_HDROP_Pre : m_hGlobal_HDROP_Final;
  else if (etc->cfFormat == m_Format_7zip_GetTransfer)
  {
    CDataObject_GetTransfer transfer;
    if (m_DestDirPrefix_FromTarget_WasSet)
    {
      transfer.Flags |= k_SourceFlags_SetTargetFolder;
    }
    if (!DestDirPrefix_FromTarget.IsEmpty())
    {
      transfer.Flags |= k_SourceFlags_SetTargetFolder_NonEmpty;
    }
    if (IsTempFiles)
    {
      transfer.Flags |= k_SourceFlags_TempFiles;
      transfer.Flags |= k_SourceFlags_WaitFinish;
      transfer.Flags |= k_SourceFlags_NeedExtractOpToFs;
      if (UsePreGlobal)
        transfer.Flags |= k_SourceFlags_NamesAreParent;
    }
    else
      transfer.Flags |= k_SourceFlags_DoNotWaitFinish;
    
    if (IsRightButton)
      transfer.Flags |= k_SourceFlags_RightButton;
    else
      transfer.Flags |= k_SourceFlags_LeftButton;

    if (DoNotProcessInTarget)
      transfer.Flags |= k_SourceFlags_DoNotProcessInTarget;
    if (Copy_WasCalled)
      transfer.Flags |= k_SourceFlags_Copy_WasCalled;

    if (Medium_CopyFrom(medium, &transfer, sizeof(transfer)))
      return S_OK;
    return E_OUTOFMEMORY;
  }
  else
    return DV_E_FORMATETC;
  
  if (!global)
    return DV_E_FORMATETC;
  medium->tymed = m_Etc.tymed;
  medium->hGlobal = DuplicateGlobalMem(global);
  if (!medium->hGlobal)
    return E_OUTOFMEMORY;
  return S_OK;
  } catch(...) { return E_FAIL; }
}

Z7_COMWF_B CDataObject::GetDataHere(LPFORMATETC /* etc */, LPSTGMEDIUM /* medium */)
{
  PRF3("CDataObject::GetDataHere()");
  // Seems Windows doesn't call it, so we will not implement it.
  return E_UNEXPECTED;
}


/*
  IDataObject::QueryGetData() Determines whether the data object is capable of
  rendering the data as specified. Objects attempting a paste or drop
  operation can call this method before calling IDataObject::GetData
  to get an indication of whether the operation may be successful.
  
  The client of a data object calls QueryGetData to determine whether
  passing the specified FORMATETC structure to a subsequent call to
  IDataObject::GetData is likely to be successful.

  we check Try_QueryGetData with CF_HDROP
*/

Z7_COMWF_B CDataObject::QueryGetData(LPFORMATETC etc)
{
  PRF3("-- CDataObject::QueryGetData()");
  if (    etc->cfFormat == m_Etc.cfFormat // CF_HDROP
      ||  etc->cfFormat == m_Format_7zip_GetTransfer
      // || (etc->cfFormat == m_Format_FileOpFlags && (HGLOBAL)m_hGlobal_FileOpFlags)
      // || (etc->cfFormat == m_Format_PreferredDropEffect && (HGLOBAL)m_hGlobal_PreferredDropEffect)
      )
  {
  }
  else
    return DV_E_FORMATETC;
  if (etc->dwAspect != m_Etc.dwAspect)
    return DV_E_DVASPECT;
  /* GetData(): It is possible to specify more than one medium by using the Boolean OR
     operator, allowing the method to choose the best medium among those specified. */
  if ((etc->tymed & m_Etc.tymed) == 0)
    return DV_E_TYMED;
  return S_OK;
}

Z7_COMWF_B CDataObject::EnumFormatEtc(DWORD direction, LPENUMFORMATETC FAR* enumFormatEtc)
{
  // we don't enumerate for DATADIR_SET. Seems it can work without it.
  if (direction != DATADIR_GET)
    return E_NOTIMPL;
  // we don't enumerate for m_Format_FileOpFlags also. Seems it can work without it.
  return CreateEnumFormatEtc(1, &m_Etc, enumFormatEtc);
}



////////////////////////////////////////////////////////

class CDropSource Z7_final:
  public IDropSource,
  public CMyUnknownImp
{
  Z7_COM_UNKNOWN_IMP_1_MT(IDropSource)
  STDMETHOD(QueryContinueDrag)(BOOL escapePressed, DWORD keyState) Z7_override;
  STDMETHOD(GiveFeedback)(DWORD effect) Z7_override;

  DWORD m_Effect;
public:
  CDataObject *DataObjectSpec;
  CMyComPtr<IDataObject> DataObject;
  
  HRESULT DragProcessing_HRESULT;
  bool DragProcessing_WasFinished;

  CDropSource():
      m_Effect(DROPEFFECT_NONE),
      // Panel(NULL),
      DragProcessing_HRESULT(S_OK),
      DragProcessing_WasFinished(false)
      {}
};

// static bool g_Debug = 0;


Z7_COMWF_B CDropSource::QueryContinueDrag(BOOL escapePressed, DWORD keyState)
{
  // try {

  /* Determines whether a drag-and-drop operation should be continued, canceled, or completed.
     escapePressed : Indicates whether the Esc key has been pressed
       since the previous call to QueryContinueDrag
       or to DoDragDrop if this is the first call to QueryContinueDrag:
         TRUE  : the end user has pressed the escape key;
         FALSE : it has not been pressed.
     keyState : The current state of the keyboard modifier keys on the keyboard.
      Possible values can be a combination of any of the flags:
      MK_CONTROL, MK_SHIFT, MK_ALT, MK_BUTTON, MK_LBUTTON, MK_MBUTTON, and MK_RBUTTON.
  */
  #ifdef SHOW_DEBUG_DRAG
  {
    AString s ("CDropSource::QueryContinueDrag()");
    s.Add_Space();
    s += "keystate=";
    s.Add_UInt32(keyState);
    PRF4(s);
  }
  #endif
    
  /*
  if ((keyState & MK_LBUTTON) == 0)
  {
    // PRF4("CDropSource::QueryContinueDrag() (keyState & MK_LBUTTON) == 0");
    g_Debug = true;
  }
  else
  {
    // PRF4("CDropSource::QueryContinueDrag() (keyState & MK_LBUTTON) != 0");
  }
  */

  if (escapePressed)
  {
    // The drag operation should be canceled with no drop operation occurring.
    DragProcessing_WasFinished = true;
    DragProcessing_HRESULT = DRAGDROP_S_CANCEL;
    return DRAGDROP_S_CANCEL;
  }

  if (DragProcessing_WasFinished)
    return DragProcessing_HRESULT;

  if ((keyState & MK_RBUTTON) != 0)
  {
    if (!DataObjectSpec->IsRightButton)
    {
      DragProcessing_WasFinished = true;
      DragProcessing_HRESULT = DRAGDROP_S_CANCEL;
      return DRAGDROP_S_CANCEL;
    }
    return S_OK;
  }
  
  if ((keyState & MK_LBUTTON) != 0)
  {
    if (DataObjectSpec->IsRightButton)
    {
      DragProcessing_WasFinished = true;
      DragProcessing_HRESULT = DRAGDROP_S_CANCEL;
      return DRAGDROP_S_CANCEL;
    }
    /* The drag operation should continue. This result occurs if no errors are detected,
       the mouse button starting the drag-and-drop operation has not been released,
       and the Esc key has not been detected. */
    return S_OK;
  }
  {
    // the mouse button starting the drag-and-drop operation has been released.
    
    /* Win10 probably calls DragOver()/GiveFeedback() just before LBUTTON releasing.
       so m_Effect is effect returned by DropTarget::DragOver()
       just before LBUTTON releasing.
       So here we can use Effect sent to last GiveFeedback() */

    if (m_Effect == DROPEFFECT_NONE)
    {
      DragProcessing_WasFinished = true;
      DragProcessing_HRESULT = DRAGDROP_S_CANCEL;
      // Drop target cannot accept the data. So we cancel drag and drop
      // maybe return DRAGDROP_S_DROP also OK here ?
      // return DRAGDROP_S_DROP; // for debug
      return DRAGDROP_S_CANCEL;
    }

    // we switch to real names for items that will be created in temp folder
    DataObjectSpec->UsePreGlobal = false;
    DataObjectSpec->Copy_HRESULT = S_OK;
    // MoveMode = (((keyState & MK_SHIFT) != 0) && MoveIsAllowed);
    /*
    if (DataObjectSpec->IsRightButton)
      return DRAGDROP_S_DROP;
    */
   
    if (DataObjectSpec->IsTempFiles)
    {
      if (!DataObjectSpec->DestDirPrefix_FromTarget.IsEmpty())
      {
        /* we know the destination Path.
           So we can copy or extract items later in Source with simpler code. */
        DataObjectSpec->DoNotProcessInTarget = true;
        // return DRAGDROP_S_CANCEL;
      }
      else
      {
        DataObjectSpec->NeedCall_Copy = true;
        /*
        if (Copy_HRESULT != S_OK || !Messages.IsEmpty())
        {
          DragProcessing_WasFinished = true;
          DragProcessing_HRESULT = DRAGDROP_S_CANCEL;
          return DRAGDROP_S_CANCEL;
        }
        */
      }
    }
    DragProcessing_HRESULT = DRAGDROP_S_DROP;
    DragProcessing_WasFinished = true;
    return DRAGDROP_S_DROP;
  }
  // } catch(...) { return E_FAIL; }
}


Z7_COMWF_B CDropSource::GiveFeedback(DWORD effect)
{
  // PRF3("CDropSource::GiveFeedback");
  /* Enables a source application to give visual feedback to the end user
     during a drag-and-drop operation by providing the DoDragDrop function
     with an enumeration value specifying the visual effect.
  in (effect):
     The DROPEFFECT value returned by the most recent call to
        IDropTarget::DragEnter,
        IDropTarget::DragOver,
     or DROPEFFECT_NONE after IDropTarget::DragLeave.
    0: DROPEFFECT_NONE
    1: DROPEFFECT_COPY
    2: DROPEFFECT_MOVE
    4: DROPEFFECT_LINK
    0x80000000: DROPEFFECT_SCROLL
    The dwEffect parameter can include DROPEFFECT_SCROLL, indicating that the
    source should put up the drag-scrolling variation of the appropriate pointer.
  */
  m_Effect = effect;

 #ifdef SHOW_DEBUG_DRAG
  AString w ("GiveFeedback effect=");
  if (effect & DROPEFFECT_SCROLL)
    w += " SCROLL ";
  w.Add_UInt32(effect & ~DROPEFFECT_SCROLL);
  // if (g_Debug)
  PRF4(w);
 #endif

  /* S_OK : no special drag and drop cursors.
            Maybe it's for case where we created custom custom cursors.
     DRAGDROP_S_USEDEFAULTCURSORS: Indicates successful completion of the method,
       and requests OLE to update the cursor using the OLE-provided default cursors. */
  // return S_OK; // for debug
  return DRAGDROP_S_USEDEFAULTCURSORS;
}



/*
static bool Global_SetUInt32(NMemory::CGlobal &hg, const UInt32 v)
{
  if (!hg.Alloc(GHND | GMEM_SHARE, sizeof(v)))
    return false;
  NMemory::CGlobalLock dropLock(hg);
  *(UInt32 *)dropLock.GetPointer() = v;
  return true;
}
*/

static bool CopyNamesToHGlobal(NMemory::CGlobal &hgDrop, const UStringVector &names)
{
  size_t totalLen = 1;

  #ifndef _UNICODE
  if (!g_IsNT)
  {
    AStringVector namesA;
    unsigned i;
    for (i = 0; i < names.Size(); i++)
      namesA.Add(GetSystemString(names[i]));
    for (i = 0; i < namesA.Size(); i++)
      totalLen += namesA[i].Len() + 1;
    
    if (!hgDrop.Alloc(GHND | GMEM_SHARE, totalLen * sizeof(CHAR) + sizeof(DROPFILES)))
      return false;
    
    NMemory::CGlobalLock dropLock(hgDrop);
    DROPFILES *dropFiles = (DROPFILES *)dropLock.GetPointer();
    if (!dropFiles)
      return false;
    dropFiles->fNC = FALSE;
    dropFiles->pt.x = 0;
    dropFiles->pt.y = 0;
    dropFiles->pFiles = sizeof(DROPFILES);
    dropFiles->fWide = FALSE;
    CHAR *p = (CHAR *) (void *) ((BYTE *)dropFiles + sizeof(DROPFILES));
    for (i = 0; i < namesA.Size(); i++)
    {
      const AString &s = namesA[i];
      const unsigned fullLen = s.Len() + 1;
      MyStringCopy(p, (const char *)s);
      p += fullLen;
      totalLen -= fullLen;
    }
    *p = 0;
  }
  else
  #endif
  {
    unsigned i;
    for (i = 0; i < names.Size(); i++)
      totalLen += names[i].Len() + 1;
    
    if (!hgDrop.Alloc(GHND | GMEM_SHARE, totalLen * sizeof(WCHAR) + sizeof(DROPFILES)))
      return false;
    
    NMemory::CGlobalLock dropLock(hgDrop);
    DROPFILES *dropFiles = (DROPFILES *)dropLock.GetPointer();
    if (!dropFiles)
      return false;
    /* fNC:
        TRUE  : pt specifies the screen coordinates of a point in a window's nonclient area.
        FALSE : pt specifies the client coordinates of a point in the client area.
    */
    dropFiles->fNC = FALSE;
    dropFiles->pt.x = 0;
    dropFiles->pt.y = 0;
    dropFiles->pFiles = sizeof(DROPFILES);
    dropFiles->fWide = TRUE;
    WCHAR *p = (WCHAR *) (void *) ((BYTE *)dropFiles + sizeof(DROPFILES));
    for (i = 0; i < names.Size(); i++)
    {
      const UString &s = names[i];
      const unsigned fullLen = s.Len() + 1;
      MyStringCopy(p, (const WCHAR *)s);
      p += fullLen;
      totalLen -= fullLen;
    }
    *p = 0;
  }
  // if (totalLen != 1) return false;
  return true;
}


void CPanel::OnDrag(LPNMLISTVIEW /* nmListView */, bool isRightButton)
{
  PRF("CPanel::OnDrag");
  if (!DoesItSupportOperations())
    return;

  CDisableTimerProcessing disableTimerProcessing2(*this);
  
  CRecordVector<UInt32> indices;
  Get_ItemIndices_Operated(indices);
  if (indices.Size() == 0)
    return;

  // CSelectedState selState;
  // SaveSelectedState(selState);

  const bool isFSFolder = IsFSFolder();
  // why we don't allow drag with rightButton from archive?
  if (!isFSFolder && isRightButton)
    return;

  UString dirPrefix;
  CTempDir tempDirectory;

  CDataObject *dataObjectSpec = new CDataObject;
  CMyComPtr<IDataObject> dataObject = dataObjectSpec;
  dataObjectSpec->IsRightButton = isRightButton;

  {
    /* we can change confirmation mode and another options.
       Explorer target requests that FILEOP_FLAGS value. */
    /*
    const FILEOP_FLAGS fopFlags =
        FOF_NOCONFIRMATION
      | FOF_NOCONFIRMMKDIR
      | FOF_NOERRORUI
      | FOF_SILENT;
      // | FOF_SIMPLEPROGRESS; // it doesn't work as expected in Win10
    Global_SetUInt32(dataObjectSpec->m_hGlobal_FileOpFlags, fopFlags);
    // dataObjectSpec->m_hGlobal_FileOpFlags.Free(); // for debug : disable these options
    */
  }
  {
    /* we can change Preferred DropEffect.
       Explorer target requests that FILEOP_FLAGS value. */
    /*
    const DWORD effect = DROPEFFECT_MOVE; // DROPEFFECT_COPY;
    Global_SetUInt32(dataObjectSpec->m_hGlobal_PreferredDropEffect, effect);
    */
  }
  if (isFSFolder)
  {
    dirPrefix = GetFsPath(); // why this in 22.01 ?
    dataObjectSpec->UsePreGlobal = false;
    // dataObjectSpec->IsTempFiles = false;
  }
  else
  {
    if (!tempDirectory.Create(kTempDirPrefix))
    {
      MessageBox_Error(L"Can't create temp folder");
      return;
    }
    dirPrefix = fs2us(tempDirectory.GetPath());
    {
      UStringVector names;
      names.Add(dirPrefix);
      dataObjectSpec->IsTempFiles = true;
      dataObjectSpec->UsePreGlobal = true;
      if (!CopyNamesToHGlobal(dataObjectSpec->m_hGlobal_HDROP_Pre, names))
        return;
    }
    NFile::NName::NormalizeDirPathPrefix(dirPrefix);
    /*
    {
      FString path2 = dirPrefix;
      path2 += "1.txt";
      CopyFileW(L"C:\\1\\1.txt", path2, FALSE);
    }
    */
  }

  {
    UStringVector names;
    // names variable is     USED for drag and drop from 7-zip to Explorer or to 7-zip archive folder.
    // names variable is NOT USED for drag and drop from 7-zip to 7-zip File System folder.
    FOR_VECTOR (i, indices)
    {
      const UInt32 index = indices[i];
      UString s;
      if (isFSFolder)
        s = GetItemRelPath(index);
      else
      {
        s = GetItemName(index);
        /*
        // We use (keepAndReplaceEmptyPrefixes = true) in CAgentFolder::Extract
        // So the following code is not required.
        // Maybe we also can change IFolder interface and send some flag also.
        if (s.IsEmpty())
        {
          // Correct_FsFile_Name("") returns "_".
          // If extracting code removes empty folder prefixes from path (as it was in old version),
          // Explorer can't find "_" folder in temp folder.
          // We can ask Explorer to copy parent temp folder "7zE" instead.
          names.Clear();
          names.Add(dirPrefix2);
          break;
        }
        */
        s = Get_Correct_FsFile_Name(s);
      }
      names.Add(dirPrefix + s);
    }
    if (!CopyNamesToHGlobal(dataObjectSpec->m_hGlobal_HDROP_Final, names))
      return;
  }
  
  CDropSource *dropSourceSpec = new CDropSource;
  CMyComPtr<IDropSource> dropSource = dropSourceSpec;
  dataObjectSpec->Panel = this;
  dataObjectSpec->Indices = indices;
  dataObjectSpec->SrcDirPrefix_Temp = dirPrefix;

  dropSourceSpec->DataObjectSpec = dataObjectSpec;
  dropSourceSpec->DataObject = dataObjectSpec;

 
  /*
  CTime - file creation timestamp.
  There are two operations in Windows with Drag and Drop:
    COPY_OPERATION : icon with    Plus sign : CTime will be set as current_time.
    MOVE_OPERATION : icon without Plus sign : CTime will be preserved.

  Note: if we call DoDragDrop() with (effectsOK = DROPEFFECT_MOVE), then
    it will use MOVE_OPERATION and CTime will be preserved.
    But MoveFile() function doesn't preserve CTime, if different volumes are used.
    Why it's so?
    Does DoDragDrop() use some another function (not MoveFile())?

  if (effectsOK == DROPEFFECT_COPY) it works as COPY_OPERATION
   
  if (effectsOK == DROPEFFECT_MOVE) drag works as MOVE_OPERATION

  if (effectsOK == (DROPEFFECT_COPY | DROPEFFECT_MOVE))
  {
    if we drag file to same volume, then Windows suggests:
       CTRL      - COPY_OPERATION
       [default] - MOVE_OPERATION
    
    if we drag file to another volume, then Windows suggests
       [default] - COPY_OPERATION
       SHIFT     - MOVE_OPERATION
  }

  We want to use MOVE_OPERATION for extracting from archive (open in 7-Zip) to Explorer:
  It has the following advantages:
    1) it uses fast MOVE_OPERATION instead of slow COPY_OPERATION and DELETE, if same volume.
    2) it preserves CTime

  Some another programs support only COPY_OPERATION.
  So we can use (DROPEFFECT_COPY | DROPEFFECT_MOVE)

  Also another program can return from DoDragDrop() before
  files using. But we delete temp folder after DoDragDrop(),
  and another program can't open input files in that case.

  We create objects:
    IDropSource *dropSource
    IDataObject *dataObject
  if DropTarget is 7-Zip window, then 7-Zip's
    IDropTarget::DragOver() sets DestDirPrefix_FromTarget in IDataObject.
  and
    IDropSource::QueryContinueDrag() sets DoNotProcessInTarget, if DestDirPrefix_FromTarget is not empty.
  So we can detect destination path after DoDragDrop().
  Now we don't know any good way to detect destination path for D&D to Explorer.
  */

  /*
  DWORD effectsOK = DROPEFFECT_COPY;
  if (moveIsAllowed)
    effectsOK |= DROPEFFECT_MOVE;
  */
  const bool moveIsAllowed = isFSFolder;
  _panelCallback->DragBegin();
  PRF("=== DoDragDrop()");
  DWORD effect = 0;
  // 18.04: was changed
  const DWORD effectsOK = DROPEFFECT_MOVE | DROPEFFECT_COPY;
  // effectsOK |= (1 << 8); // for debug
  HRESULT res = ::DoDragDrop(dataObject, dropSource, effectsOK, &effect);
  PRF("=== After DoDragDrop()");
  _panelCallback->DragEnd();

  /*
    Win10 drag and drop to Explorer:
      DoDragDrop() output variables:
       for MOVE operation:
       {
         effect == DROPEFFECT_NONE;
         dropSourceSpec->m_PerformedDropEffect == DROPEFFECT_MOVE;
       }
       for COPY operation:
       {
         effect == DROPEFFECT_COPY;
         dropSourceSpec->m_PerformedDropEffect == DROPEFFECT_COPY;
       }
    DOCs: The source inspects the two values that can be returned by the target.
       If both are set to DROPEFFECT_MOVE, it completes the unoptimized move
       by deleting the original data. Otherwise, the target did an optimized
       move and the original data has been deleted.

    We didn't see "unoptimized move" case (two values of DROPEFFECT_MOVE),
    where we still need to delete source files.
    So we don't delete files after DoDragDrop().

    Also DOCs say for "optimized move":
      The target also calls the data object's IDataObject::SetData method and passes
      it a CFSTR_PERFORMEDDROPEFFECT format identifier set to DROPEFFECT_NONE.
    but actually in Win10 we always have
      (dropSourceSpec->m_PerformedDropEffect == DROPEFFECT_MOVE)
    for any MOVE operation.
  */

  const bool canceled = (res == DRAGDROP_S_CANCEL);
  
  CDisableNotify disableNotify(*this);
  
  if (res == DRAGDROP_S_DROP)
  {
    /* DRAGDROP_S_DROP is returned. It means that
         - IDropTarget::Drop() was called,
         - IDropTarget::Drop() returned (ret_code >= 0)
    */
    res = dataObjectSpec->Copy_HRESULT;
    bool need_Process = dataObjectSpec->DoNotProcessInTarget;
    if (dataObjectSpec->m_Transfer_WasSet)
    {
      if (dataObjectSpec->m_Transfer.Target.FuncType == k_DragTargetMode_Drop_End)
      {
        if (dataObjectSpec->m_Transfer.Target.Flags & k_TargetFlags_MustBeProcessedBySource)
          need_Process = true;
      }
    }

    if (need_Process)
      if (!dataObjectSpec->DestDirPrefix_FromTarget.IsEmpty())
      {
        if (!NFile::NName::IsAltStreamPrefixWithColon(dataObjectSpec->DestDirPrefix_FromTarget))
          NFile::NName::NormalizeDirPathPrefix(dataObjectSpec->DestDirPrefix_FromTarget);
        CCopyToOptions options;
        options.folder = dataObjectSpec->DestDirPrefix_FromTarget;
        // if MOVE is not allowed, we just use COPY operation
        /* it was 7-zip's Target that set non-empty dataObjectSpec->DestDirPrefix_FromTarget.
           it means that target didn't completed operation,
           and we can use (effect) value returned by target via DoDragDrop().
           as indicator of type of operation
        */
        // options.moveMode = (moveIsAllowed && effect == DROPEFFECT_MOVE) // before v23.00:
        options.moveMode = moveIsAllowed;
        if (moveIsAllowed)
        {
          if (dataObjectSpec->m_Transfer_WasSet)
            options.moveMode = (
              dataObjectSpec->m_Transfer.Target.Cmd_Effect == DROPEFFECT_MOVE);
          else
            options.moveMode = (effect == DROPEFFECT_MOVE);
            // we expect (DROPEFFECT_MOVE) as indicator of move operation for Drag&Drop MOVE ver 22.01.
        }
        res = CopyTo(options, indices, &dataObjectSpec->Messages);
      }
    /*
    if (effect & DROPEFFECT_MOVE)
      RefreshListCtrl(selState);
    */
  }
  else
  {
    // we ignore E_UNEXPECTED that is returned if we drag file to printer
    if (res != DRAGDROP_S_CANCEL
        && res != S_OK
        && res != E_UNEXPECTED)
      MessageBox_Error_HRESULT(res);
    res = dataObjectSpec->Copy_HRESULT;
  }

  if (!dataObjectSpec->Messages.IsEmpty())
  {
    CMessagesDialog messagesDialog;
    messagesDialog.Messages = &dataObjectSpec->Messages;
    messagesDialog.Create((*this));
  }
  
  if (res != S_OK && res != E_ABORT)
  {
    // we restore Notify before MessageBox_Error_HRESULT. So we will see files selection
    disableNotify.Restore();
    // SetFocusToList();
    MessageBox_Error_HRESULT(res);
  }
  if (res == S_OK && dataObjectSpec->Messages.IsEmpty() && !canceled)
    KillSelection();
}





CDropTarget::CDropTarget():
      m_IsRightButton(false),
      m_GetTransfer_WasSuccess(false),
      m_DropIsAllowed(false),
      m_PanelDropIsAllowed(false),
      // m_DropHighlighted_SelectionIndex(-1),
      // m_SubFolderIndex(-1),
      m_Panel(NULL),
      m_IsAppTarget(false),
      m_TargetPath_WasSent_ToDataObject(false),
      m_TargetPath_NonEmpty_WasSent_ToDataObject(false),
      m_Transfer_WasSent_ToDataObject(false),
      App(NULL),
      SrcPanelIndex(-1),
      TargetPanelIndex(-1)
{
  m_Format_7zip_SetTargetFolder = RegisterClipboardFormat(k_Format_7zip_SetTargetFolder);
  m_Format_7zip_SetTransfer     = RegisterClipboardFormat(k_Format_7zip_SetTransfer);
  m_Format_7zip_GetTransfer     = RegisterClipboardFormat(k_Format_7zip_GetTransfer);

  m_ProcessId = GetCurrentProcessId();
  // m_TransactionId = ((UInt64)m_ProcessId << 32) + 1;
  // ClearState();
}

// clear internal state
void CDropTarget::ClearState()
{
  m_DataObject.Release();
  m_SourcePaths.Clear();

  m_IsRightButton = false;

  m_GetTransfer_WasSuccess = false;
  m_DropIsAllowed = false;

  m_PanelDropIsAllowed = false;
  // m_SubFolderIndex = -1;
  // m_DropHighlighted_SubFolderName.Empty();
  m_Panel = NULL;
  m_IsAppTarget = false;
  m_TargetPath_WasSent_ToDataObject = false;
  m_TargetPath_NonEmpty_WasSent_ToDataObject = false;
  m_Transfer_WasSent_ToDataObject = false;
}

/*
  IDataObject::QueryGetData() Determines whether the data object is capable of
  rendering the data as specified. Objects attempting a paste or drop
  operation can call this method before calling IDataObject::GetData
  to get an indication of whether the operation may be successful.
  
  The client of a data object calls QueryGetData to determine whether
  passing the specified FORMATETC structure to a subsequent call to
  IDataObject::GetData is likely to be successful.

  We check Try_QueryGetData with CF_HDROP
*/
/*
void CDropTarget::Try_QueryGetData(IDataObject *dataObject)
{
  FORMATETC etc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
  m_DropIsAllowed = (dataObject->QueryGetData(&etc) == S_OK);
}
*/

static void ListView_SetItemState_DropHighlighted(
    NControl::CListView &listView, int index, bool highlighted)
{
  // LVIS_DROPHILITED : The item is highlighted as a drag-and-drop target
  /*
  LVITEM item;
  item.mask = LVIF_STATE;
  item.iItem = index;
  item.iSubItem = 0;
  item.state = enable ? LVIS_DROPHILITED : 0;
  item.stateMask = LVIS_DROPHILITED;
  item.pszText = NULL;
  listView.SetItem(&item);
  */
  listView.SetItemState(index, highlighted ? LVIS_DROPHILITED : 0, LVIS_DROPHILITED);
}

// Removes DropHighlighted state in ListView item, if it was set before
void CDropTarget::RemoveSelection()
{
  if (m_Panel)
  {
    m_Panel->m_DropHighlighted_SubFolderName.Empty();
    if (m_Panel->m_DropHighlighted_SelectionIndex >= 0)
    {
      ListView_SetItemState_DropHighlighted(m_Panel->_listView,
          m_Panel->m_DropHighlighted_SelectionIndex, false);
      m_Panel->m_DropHighlighted_SelectionIndex = -1;
    }
  }
}

#ifdef UNDER_CE
#define ChildWindowFromPointEx(hwndParent, pt, uFlags) ChildWindowFromPoint(hwndParent, pt)
#endif


/*
   PositionCursor() function sets m_Panel under cursor drop, and
   m_SubFolderIndex/m_DropHighlighted_SubFolderName, if drop to some folder in Panel list.
*/
/*
PositionCursor() uses as input variables:
  m_DropIsAllowed must be set before PositionCursor()
  if (m_DropHighlighted_SelectionIndex >= 0 && m_Panel) it uses m_Panel and removes previous selection
PositionCursor() sets
  m_PanelDropIsAllowed
  m_Panel
  m_IsAppTarget
  m_SubFolderIndex
  m_DropHighlighted_SubFolderName
  m_DropHighlighted_SelectionIndex
*/
void CDropTarget::PositionCursor(const POINTL &ptl)
{
  RemoveSelection();

  // m_SubFolderIndex = -1;
  // m_DropHighlighted_SubFolderName.Empty();
  m_IsAppTarget = true;
  m_Panel = NULL;
  m_PanelDropIsAllowed = false;

  if (!m_DropIsAllowed)
    return;

  POINT pt;
  pt.x = ptl.x;
  pt.y = ptl.y;
  {
    POINT pt2 = pt;
    if (App->_window.ScreenToClient(&pt2))
      for (unsigned i = 0; i < kNumPanelsMax; i++)
        if (App->IsPanelVisible(i))
        {
          CPanel *panel = &App->Panels[i];
          if (panel->IsEnabled())
          if (::ChildWindowFromPointEx(App->_window, pt2,
              CWP_SKIPINVISIBLE | CWP_SKIPDISABLED) == (HWND)*panel)
          {
            m_Panel = panel;
            m_IsAppTarget = false;
            if ((int)i == SrcPanelIndex)
              return; // we don't allow to drop to source panel
            break;
          }
        }
  }

  m_PanelDropIsAllowed = true;

  if (!m_Panel)
  {
    if (TargetPanelIndex >= 0)
      m_Panel = &App->Panels[TargetPanelIndex];
    // we don't need to find item in panel
    return;
  }

  // we will try to find and highlight drop folder item in listView under cursor
  /*
  m_PanelDropIsAllowed = m_Panel->DoesItSupportOperations();
  if (!m_PanelDropIsAllowed)
    return;
  */
  /* now we don't allow drop to subfolder under cursor, if dest panel is archive.
     Another code must be fixed for that case, where we must use m_SubFolderIndex/m_DropHighlighted_SubFolderName */
  if (!m_Panel->IsFsOrPureDrivesFolder())
    return;

  if (::WindowFromPoint(pt) != (HWND)m_Panel->_listView)
    return;

  LVHITTESTINFO info;
  m_Panel->_listView.ScreenToClient(&pt);
  info.pt = pt;
  const int index = ListView_HitTest(m_Panel->_listView, &info);
  if (index < 0)
    return;
  const unsigned realIndex = m_Panel->GetRealItemIndex(index);
  if (realIndex == kParentIndex)
    return;
  if (!m_Panel->IsItem_Folder(realIndex))
    return;
  // m_SubFolderIndex = (int)realIndex;
  m_Panel->m_DropHighlighted_SubFolderName = m_Panel->GetItemName(realIndex);
  ListView_SetItemState_DropHighlighted(m_Panel->_listView, index, true);
  m_Panel->m_DropHighlighted_SelectionIndex = index;
}


/* returns true, if !m_IsAppTarget
   and target is FS folder or altStream folder
*/

UInt32 CDropTarget::GetFolderType() const
{
  if (m_IsAppTarget || !m_Panel)
    return k_FolderType_None;
  if (m_Panel->IsFSFolder() ||
      (m_Panel->IsFSDrivesFolder()
       && m_Panel->m_DropHighlighted_SelectionIndex >= 0))
    return k_FolderType_Fs;
  if (m_Panel->IsAltStreamsFolder())
    return k_FolderType_AltStreams;
  if (m_Panel->IsArcFolder())
    return k_FolderType_Archive;
  return k_FolderType_Unknown;
}

bool CDropTarget::IsFsFolderPath() const
{
  if (m_IsAppTarget || !m_Panel)
    return false;
  if (m_Panel->IsFSFolder())
    return true;
  if (m_Panel->IsAltStreamsFolder())
    return true;
  return m_Panel->IsFSDrivesFolder() &&
      m_Panel->m_DropHighlighted_SelectionIndex >= 0;
}


#define INIT_FORMATETC_HGLOBAL(type) { (type), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }

static bool DataObject_GetData_GetTransfer(IDataObject *dataObject,
    UINT a_Format_7zip_GetTransfer, CDataObject_GetTransfer &transfer)
{
  FORMATETC etc = INIT_FORMATETC_HGLOBAL((CLIPFORMAT)a_Format_7zip_GetTransfer);
  NCOM::CStgMedium medium;
  const HRESULT res = dataObject->GetData(&etc, &medium);
  if (res != S_OK)
    return false;
  if (medium.tymed != TYMED_HGLOBAL)
    return false;
  const size_t size = GlobalSize(medium.hGlobal);
  if (size < sizeof(transfer))
    return false;
  NMemory::CGlobalLock dropLock(medium.hGlobal);
  const CDataObject_GetTransfer *t = (const CDataObject_GetTransfer *)dropLock.GetPointer();
  if (!t)
    return false;
  if (!t->Check()) // isSetData
    return false;
  transfer = *t;
  return true;
}

/*
  returns true, if all m_SourcePaths[] items are same drive
  as destination drop path in m_Panel
*/
bool CDropTarget::IsItSameDrive() const
{
  if (!m_Panel)
    return false;
  if (!IsFsFolderPath())
    return false;

  UString drive;
  
  if (m_Panel->IsFSFolder())
  {
    drive = m_Panel->GetDriveOrNetworkPrefix();
    if (drive.IsEmpty())
      return false;
  }
  else if (m_Panel->IsFSDrivesFolder()
      && m_Panel->m_DropHighlighted_SelectionIndex >= 0)
  {
    drive = m_Panel->m_DropHighlighted_SubFolderName;
    drive.Add_PathSepar();
  }
  else
    return false;

  if (m_SourcePaths.Size() == 0)
    return false;
  
  FOR_VECTOR (i, m_SourcePaths)
  {
    if (!m_SourcePaths[i].IsPrefixedBy_NoCase(drive))
      return false;
  }
  return true;
}


/*
  There are 2 different actions, when we drag to 7-Zip:
  1) if target panel is "7-Zip" FS and any of the 2 cases:
     - Drag from any non "7-Zip" program;
     or
     - Drag from "7-Zip" to non-panel area of "7-Zip".
     We want to create new archive for that operation with "Add to Archive" window.
  2) all another operations work as usual file COPY/MOVE
    - Drag from "7-Zip" FS to "7-Zip" FS.
        COPY/MOVE are supported.
    - Drag to open archive in 7-Zip.
        We want to update archive.
        We replace COPY to MOVE.
    - Drag from "7-Zip" archive to "7-Zip" FS.
        We replace COPY to MOVE.
*/

// we try to repeat Explorer's effects.
// out: 0 - means that use default effect
static DWORD GetEffect_ForKeys(DWORD keyState)
{
  if (keyState & MK_CONTROL)
  {
    if (keyState & MK_ALT)
      return 0;
    if (keyState & MK_SHIFT)
      return DROPEFFECT_LINK; // CONTROL + SHIFT
    return DROPEFFECT_COPY;   // CONTROL
  }
  // no CONTROL
  if (keyState & MK_SHIFT)
  {
    if (keyState & MK_ALT)
      return 0;
    return DROPEFFECT_MOVE; // SHIFT
  }
  // no CONTROL, no SHIFT
  if (keyState & MK_ALT)
    return DROPEFFECT_LINK; // ALT
  return 0;
}


/* GetEffect() uses m_TargetPath_WasSentToDataObject
   to disale MOVE operation, if Source is not 7-Zip
*/
DWORD CDropTarget::GetEffect(DWORD keyState, POINTL /* pt */, DWORD allowedEffect) const
{
  // (DROPEFFECT_NONE == 0)
  if (!m_DropIsAllowed || !m_PanelDropIsAllowed)
    return 0;
  if (!IsFsFolderPath() || !m_TargetPath_WasSent_ToDataObject)
  {
    // we don't allow MOVE, if Target is archive or Source is not 7-Zip
    // disabled for debug:
    // allowedEffect &= ~DROPEFFECT_MOVE;
  }
  DWORD effect;
  {
    effect = GetEffect_ForKeys(keyState);
    if (effect == DROPEFFECT_LINK)
      return 0;
    effect &= allowedEffect;
  }
  if (effect == 0)
  {
    if (allowedEffect & DROPEFFECT_COPY)
      effect = DROPEFFECT_COPY;
    if (allowedEffect & DROPEFFECT_MOVE)
    {
      /* MOVE operation can be optimized. So MOVE is preferred way
         for default action, if Source and Target are at same drive */
      if (IsItSameDrive())
        effect = DROPEFFECT_MOVE;
    }
  }
  return effect;
}


/* returns:
    - target folder path prefix, if target is FS folder
    - empty string, if target is not FS folder
*/
UString CDropTarget::GetTargetPath() const
{
  if (!IsFsFolderPath())
    return UString();
  UString path = m_Panel->GetFsPath();
  if (/* m_SubFolderIndex >= 0 && */
      !m_Panel->m_DropHighlighted_SubFolderName.IsEmpty())
  {
    path += m_Panel->m_DropHighlighted_SubFolderName;
    path.Add_PathSepar();
  }
  return path;
}


/*
if IDropSource is Win10-Explorer
--------------------------------
  As in MS DOCs:
  The source inspects the two (effect) values that can be returned by the target:
    1) SetData(CFSTR_PERFORMEDDROPEFFECT)
    2) returned value  (*effect) by
        CDropTarget::Drop(IDataObject *dataObject, DWORD keyState,
        POINTL pt, DWORD *effect)
  If both are set to DROPEFFECT_MOVE, Explorer completes the unoptimized move by deleting
  the original data.
  // Otherwise, the target did an optimized move and the original data has been deleted.
*/


/*
  Send targetPath from target to dataObject (to Source)
  input: set (enablePath = false) to send empty path
  returns true,  if SetData()         returns S_OK : (source is 7-zip)
  returns false, if SetData() doesn't return  S_OK : (source is Explorer)
*/
bool CDropTarget::SendToSource_TargetPath_enable(IDataObject *dataObject, bool enablePath)
{
  m_TargetPath_NonEmpty_WasSent_ToDataObject = false;
  UString path;
  if (enablePath)
    path = GetTargetPath();
  PRF("CDropTarget::SetPath");
  PRF_W(path);
  if (!dataObject || m_Format_7zip_SetTargetFolder == 0)
    return false;
  FORMATETC etc = INIT_FORMATETC_HGLOBAL((CLIPFORMAT)m_Format_7zip_SetTargetFolder);
  STGMEDIUM medium;
  medium.tymed = etc.tymed;
  medium.pUnkForRelease = NULL;
  const size_t num = path.Len() + 1; // + (1 << 19) // for debug
  medium.hGlobal = GlobalAlloc(GHND | GMEM_SHARE, num * sizeof(wchar_t));
  if (!medium.hGlobal)
    return false;
  // Sleep(1000);
  wchar_t *dest = (wchar_t *)GlobalLock(medium.hGlobal);
  // Sleep(1000);
  bool res = false;
  if (dest)
  {
    MyStringCopy(dest, (const wchar_t *)path);
    GlobalUnlock(medium.hGlobal);
    // OutputDebugString("m_DataObject->SetData");
    const BOOL release = FALSE; // that way is more simple for correct releasing.
        // TRUE; // for debug : is not good for some cases.
    /* If DropSource is Win10-Explorer, dataObject->SetData() returns E_NOTIMPL; */
    const HRESULT hres = dataObject->SetData(&etc, &medium, release);
    // Sleep(1000);
    res = (hres == S_OK);
  }

  ReleaseStgMedium(&medium);
  if (res && !path.IsEmpty())
    m_TargetPath_NonEmpty_WasSent_ToDataObject = true;
  // Sleep(1000);
  return res;
}


void CDropTarget::SendToSource_auto(IDataObject *dataObject,
    const CTargetTransferInfo &info)
{
  /* we try to send target path to Source.
     If Source is 7-Zip, then it will accept k_Format_7zip_SetTargetFolder.
     That sent path will be non-Empty, if this target is FS folder and drop is allowed */
  bool need_Send = false;
  if (   info.FuncType == k_DragTargetMode_Enter
      || info.FuncType == k_DragTargetMode_Over
      || (info.FuncType == k_DragTargetMode_Drop_Begin
        // && targetOp_Cmd != NDragMenu::k_None
        && info.Cmd_Type != NDragMenu::k_Cancel))
  // if (!g_CreateArchive_for_Drag_from_7zip)
      need_Send = m_DropIsAllowed && m_PanelDropIsAllowed && IsFsFolderPath();
  m_TargetPath_WasSent_ToDataObject = SendToSource_TargetPath_enable(dataObject, need_Send);
  SendToSource_TransferInfo(dataObject, info);
}


bool CDropTarget::SendToSource_TransferInfo(IDataObject *dataObject,
    const CTargetTransferInfo &info)
{
  m_Transfer_WasSent_ToDataObject = false;
  PRF("CDropTarget::SendToSource_TransferInfo");

  if (!dataObject || m_Format_7zip_SetTransfer == 0)
    return false;
  FORMATETC etc = INIT_FORMATETC_HGLOBAL((CLIPFORMAT)m_Format_7zip_SetTransfer);
  STGMEDIUM medium;
  medium.tymed = etc.tymed;
  medium.pUnkForRelease = NULL;
  CDataObject_SetTransfer transfer;
  const size_t size = sizeof(transfer); // + (1 << 19) // for debug
  // OutputDebugString("GlobalAlloc");
  medium.hGlobal = GlobalAlloc(GHND | GMEM_SHARE, size);
  // Sleep(1000);
  if (!medium.hGlobal)
    return false;
  // OutputDebugString("GlobalLock");
  void *dest = (wchar_t *)GlobalLock(medium.hGlobal);
  // Sleep(1000);
  bool res = false;
  if (dest)
  {
    transfer.Init();
    transfer.Target = info;

    memcpy(dest, &transfer, sizeof(transfer));
    GlobalUnlock(medium.hGlobal);
    // OutputDebugString("m_DataObject->SetData");
    const BOOL release = FALSE; // that way is more simple for correct releasing.
        // TRUE; // for debug : is not good for some cases
    const HRESULT hres = dataObject->SetData(&etc, &medium, release);
    res = (hres == S_OK);
  }

  ReleaseStgMedium(&medium);
  if (res)
    m_Transfer_WasSent_ToDataObject = true;
  return res;
}


bool CDropTarget::SendToSource_UInt32(IDataObject *dataObject, UINT format, UInt32 value)
{
  PRF("CDropTarget::Send_UInt32 (Performed)");

  if (!dataObject || format == 0)
    return false;
  FORMATETC etc = INIT_FORMATETC_HGLOBAL((CLIPFORMAT)format);
  STGMEDIUM medium;
  medium.tymed = etc.tymed;
  medium.pUnkForRelease = NULL;
  const size_t size = 4;
  medium.hGlobal = GlobalAlloc(GHND | GMEM_SHARE, size);
  if (!medium.hGlobal)
    return false;
  void *dest = GlobalLock(medium.hGlobal);
  bool res = false;
  if (dest)
  {
    *(UInt32 *)dest = value;
    GlobalUnlock(medium.hGlobal);
    // OutputDebugString("m_DataObject->SetData");
    const BOOL release = TRUE;
        // FALSE; // for debug
    /* If DropSource is Win10-Explorer, then (release == FALSE) doesn't work
       and dataObject->SetData() returns E_NOTIMPL;
       So we use release = TRUE; here */
    const HRESULT hres = dataObject->SetData(&etc, &medium, release);
    // we return here without calling ReleaseStgMedium().
    return (hres == S_OK);
    // Sleep(1000);
    /*
      if (we use release = TRUE), we expect that
          - SetData() will release medium, and
          - SetData() will set STGMEDIUM::tymed to (TYMED_NULL = 0).
      but some "incorrect" SetData() implementations can keep STGMEDIUM::tymed unchanged.
      And it's not safe to call ReleaseStgMedium() here for that case,
      because DropSource also could release medium.
      We can reset (medium.tymed = TYMED_NULL) manually here to disable
      unsafe medium releasing in ReleaseStgMedium().
    */
    /*
    if (release)
    {
      medium.tymed = TYMED_NULL;
      medium.pUnkForRelease = NULL;
      medium.hGlobal = NULL;
    }
    res = (hres == S_OK);
    */
  }
  ReleaseStgMedium(&medium);
  return res;
}


void CDropTarget::LoadNames_From_DataObject(IDataObject *dataObject)
{
  // "\\\\.\\" prefix is possible for long names
  m_DropIsAllowed = NShell::DataObject_GetData_HDROP_or_IDLIST_Names(dataObject, m_SourcePaths) == S_OK;
}


Z7_COMWF_B CDropTarget::DragEnter(IDataObject *dataObject, DWORD keyState, POINTL pt, DWORD *effect)
{
  /* *(effect):
       - on input  : value of the dwOKEffects parameter of the DoDragDrop() function.
       - on return : must contain one of the DROPEFFECT flags, which indicates
                     what the result of the drop operation would be.
     (pt): the current cursor coordinates in screen coordinates.
  */
  PRF_(Print_Point("CDropTarget::DragEnter", keyState, pt, *effect))
  try {

  if ((keyState & (MK_RBUTTON | MK_MBUTTON)) != 0)
    m_IsRightButton = true;

  LoadNames_From_DataObject(dataObject);
  // Try_QueryGetData(dataObject);
  // we will use (m_DataObject) later in DragOver() and DragLeave().
  m_DataObject = dataObject;
  // return DragOver(keyState, pt, effect);
  PositionCursor(pt);
  CTargetTransferInfo target;
  target.FuncType = k_DragTargetMode_Enter;
  target.KeyState = keyState;
  target.Point = pt;
  target.OkEffects = *effect;
  SendToSource_Drag(target);

  CDataObject_GetTransfer transfer;
  m_GetTransfer_WasSuccess = DataObject_GetData_GetTransfer(
      dataObject, m_Format_7zip_GetTransfer, transfer);
  if (m_GetTransfer_WasSuccess)
  {
    if (transfer.Flags & k_SourceFlags_LeftButton)
      m_IsRightButton = false;
    else if (transfer.Flags & k_SourceFlags_RightButton)
      m_IsRightButton = true;
  }

  *effect = GetEffect(keyState, pt, *effect);
  return S_OK;
  } catch(...) { return E_FAIL; }
}


Z7_COMWF_B CDropTarget::DragOver(DWORD keyState, POINTL pt, DWORD *effect)
{
  PRF_(Print_Point("CDropTarget::DragOver", keyState, pt, *effect))
  /*
    For efficiency reasons, a data object is not passed in IDropTarget::DragOver.
    The data object passed in the most recent call to IDropTarget::DragEnter
    is available and can be used.
  
    When IDropTarget::DragOver has completed its operation, the DoDragDrop
    function calls IDropSource::GiveFeedback so the source application can display
    the appropriate visual feedback to the user.
  */
  /*
    we suppose that it's unexpected that (keyState) shows that mouse
    button is not pressed, because such cases will be processed by
    IDropSource::QueryContinueDrag() that returns DRAGDROP_S_DROP or DRAGDROP_S_CANCEL.
    So DragOver() will not be called.
  */

  if ((keyState & MK_LBUTTON) == 0)
  {
    PRF4("CDropTarget::DragOver() (keyState & MK_LBUTTON) == 0");
    // g_Debug = true;
  }

  try {
  /* we suppose that source names were not changed after DragEnter()
     so we don't request GetNames_From_DataObject() for each call of DragOver() */
  PositionCursor(pt);
  CTargetTransferInfo target;
  target.FuncType = k_DragTargetMode_Over;
  target.KeyState = keyState;
  target.Point = pt;
  target.OkEffects = *effect;
  SendToSource_Drag(target);
  *effect = GetEffect(keyState, pt, *effect);
  // *effect = 1 << 8; // for debug
  return S_OK;
  } catch(...) { return E_FAIL; }
}


Z7_COMWF_B CDropTarget::DragLeave()
{
  PRF4("CDropTarget::DragLeave");
  try {
  RemoveSelection();
  // we send empty TargetPath to 7-Zip Source to clear value of TargetPath that was sent before

  CTargetTransferInfo target;
  target.FuncType = k_DragTargetMode_Leave;
  /*
  target.KeyState = 0;
  target.Point = pt;
  pt.x = 0; // -1
  pt.y = 0; // -1
  target.Effect = 0;
  */
  SendToSource_Drag(target);
  ClearState();
  return S_OK;
  } catch(...) { return E_FAIL; }
}


static unsigned Drag_OnContextMenu(int xPos, int yPos, UInt32 cmdFlags);

/*
  We suppose that there was DragEnter/DragOver for same (POINTL pt) before Drop().
  But we can work without DragEnter/DragOver too.
*/
Z7_COMWF_B CDropTarget::Drop(IDataObject *dataObject, DWORD keyState,
    POINTL pt, DWORD *effect)
{
  PRF_(Print_Point("CDropTarget::Drop", keyState, pt, *effect))
  /* Drop() is called after SourceDrop::QueryContinueDrag() returned DRAGDROP_S_DROP.
     So it's possible that Source have done some operations already.
  */
  HRESULT hres = S_OK;
  bool needDrop_by_Source = false;
  DWORD opEffect = DROPEFFECT_NONE;

  try {
  // we don't need m_DataObject reference anymore, because we use local (dataObject)
  m_DataObject.Release();

  /* in normal case : we called LoadNames_From_DataObject() in DragEnter() already.
     But if by some reason DragEnter() was not called,
     we need to call LoadNames_From_DataObject() before PositionCursor().
  */
  if (!m_DropIsAllowed) LoadNames_From_DataObject(dataObject);
  PositionCursor(pt);
  
  CPanel::CDisableTimerProcessing2 disableTimerProcessing(m_Panel);
  // CDisableNotify disableNotify2(m_Panel);

  UInt32 cmd = NDragMenu::k_None;
  UInt32 cmdEffect = DROPEFFECT_NONE;
  bool menu_WasShown = false;
  if (m_IsRightButton && m_Panel)
  {
    UInt32 flagsMask;
    if (m_Panel->IsArcFolder())
      flagsMask = (UInt32)1 << NDragMenu::k_Copy_ToArc;
    else
    {
      flagsMask = (UInt32)1 << NDragMenu::k_AddToArc;
      if (IsFsFolderPath())
        flagsMask |= (UInt32)1 << NDragMenu::k_Copy_Base;
    }
    // flagsMask |= (UInt32)1 << NDragMenu::k_Cancel;
    const UInt32 cmd32 = Drag_OnContextMenu(pt.x, pt.y, flagsMask);
    cmd = cmd32 & NDragMenu::k_MenuFlags_CmdMask;
    if (cmd32 & NDragMenu::k_MenuFlag_Copy)
      cmdEffect = DROPEFFECT_COPY;
    else if (cmd32 & NDragMenu::k_MenuFlag_Move)
      cmdEffect = DROPEFFECT_MOVE;
    opEffect = cmdEffect;
    menu_WasShown = true;
  }
  else
  {
    opEffect = GetEffect(keyState, pt, *effect);
    if (m_IsAppTarget)
      cmd = NDragMenu::k_AddToArc;
    else if (m_Panel)
    {
      if (IsFsFolderPath())
      {
        const bool is7zip = m_TargetPath_WasSent_ToDataObject;
        bool createNewArchive = false;
        if (is7zip)
          createNewArchive = false; // g_CreateArchive_for_Drag_from_7zip;
        else
          createNewArchive = true; // g_CreateArchive_for_Drag_from_Explorer;
        
        if (createNewArchive)
          cmd = NDragMenu::k_AddToArc;
        else
        {
          if (opEffect != 0)
            cmd = NDragMenu::k_Copy_Base;
          cmdEffect = opEffect;
        }
      }
      else
      {
        /* if we are inside open archive:
           if archive support operations         -> we will call operations
           if archive doesn't support operations -> we will create new archove
        */
        if (m_Panel->IsArcFolder()
            || m_Panel->DoesItSupportOperations())
        {
          cmd = NDragMenu::k_Copy_ToArc;
          // we don't want move to archive operation here.
          // so we force to DROPEFFECT_COPY.
          if (opEffect != DROPEFFECT_NONE)
            opEffect = DROPEFFECT_COPY;
          cmdEffect = opEffect;
        }
        else
          cmd = NDragMenu::k_AddToArc;
      }
    }
  }

  if (cmd == 0)
    cmd = NDragMenu::k_AddToArc;

  if (cmd == NDragMenu::k_AddToArc)
  {
    opEffect = DROPEFFECT_COPY;
    cmdEffect = DROPEFFECT_COPY;
  }

  if (m_Panel)
  if (cmd == NDragMenu::k_Copy_ToArc)
  {
    const UString title = LangString(IDS_CONFIRM_FILE_COPY);
    UString s = LangString(cmdEffect == DROPEFFECT_MOVE ?
        IDS_MOVE_TO : IDS_COPY_TO);
    s.Add_LF();
    s += "\'";
    s += m_Panel->_currentFolderPrefix;
    s += "\'";
    s.Add_LF();
    s += LangString(IDS_WANT_TO_COPY_FILES);
    s += " ?";
    const int res = ::MessageBoxW(*m_Panel, s, title, MB_YESNOCANCEL | MB_ICONQUESTION);
    if (res != IDYES)
      cmd = NDragMenu::k_Cancel;
  }

  CTargetTransferInfo target;
  target.FuncType = k_DragTargetMode_Drop_Begin;
  target.KeyState = keyState;
  target.Point = pt;
  target.OkEffects = *effect;
  target.Flags = 0;

  target.Cmd_Effect = cmdEffect;
  target.Cmd_Type = cmd;
  target.FolderType = GetFolderType();

  if (cmd == NDragMenu::k_Cancel)
    target.Flags |= k_TargetFlags_WasCanceled;
  if (menu_WasShown)
    target.Flags |= k_TargetFlags_MenuWasShown;
 
  SendToSource_auto(dataObject, target);

  CDataObject_GetTransfer transfer;
  m_GetTransfer_WasSuccess = DataObject_GetData_GetTransfer(
      dataObject, m_Format_7zip_GetTransfer, transfer);

  /* The Source (for example, 7-zip) could change file names when drop was confirmed.
     So we must reload source file paths here */
  if (cmd != NDragMenu::k_Cancel)
    LoadNames_From_DataObject(dataObject);

  if (cmd == NDragMenu::k_Cancel)
  {
    opEffect = DROPEFFECT_NONE;
    cmdEffect = DROPEFFECT_NONE;
  }
  else
  {
    if (m_GetTransfer_WasSuccess)
      needDrop_by_Source = ((transfer.Flags & k_SourceFlags_DoNotProcessInTarget) != 0);
    if (!needDrop_by_Source)
    {
      bool moveMode = (cmdEffect == DROPEFFECT_MOVE);
      bool needDrop = false;
      if (m_IsRightButton && m_Panel)
        needDrop = true;
      if (m_DropIsAllowed && m_PanelDropIsAllowed)
      {
        /* if non-empty TargetPath was sent successfully to DataObject,
           then the Source is 7-Zip, and that 7zip-Source can copy to FS operation.
           So we can disable Drop operation here for such case.
        */
        needDrop_by_Source = (cmd != NDragMenu::k_AddToArc
            && m_TargetPath_WasSent_ToDataObject
            && m_TargetPath_NonEmpty_WasSent_ToDataObject);
        needDrop = !(needDrop_by_Source);
      }
      if (needDrop)
      {
        UString path = GetTargetPath();
        if (m_IsAppTarget && m_Panel)
          if (m_Panel->IsFSFolder())
            path = m_Panel->GetFsPath();

        UInt32 sourceFlags = 0;
        if (m_GetTransfer_WasSuccess)
          sourceFlags = transfer.Flags;

        if (menu_WasShown)
          target.Flags |= k_TargetFlags_MenuWasShown;

        target.Flags |= k_TargetFlags_WasProcessed;

        RemoveSelection();
        // disableTimerProcessing.Restore();
        m_Panel->CompressDropFiles(m_SourcePaths, path,
            (cmd == NDragMenu::k_AddToArc),  // createNewArchive,
            moveMode, sourceFlags,
            target.Flags
            );
      }
    }
  } // end of if (cmd != NDragMenu::k_Cancel)
  {
    /* note that, if (we send CFSTR_PERFORMEDDROPEFFECT as DROPEFFECT_MOVE
            and Drop() returns (*effect == DROPEFFECT_MOVE), then
       Win10-Explorer-Source will try to remove files just after Drop() exit.
       But our CompressFiles() could be run without waiting finishing.
       DOCs say, that we must send CFSTR_PERFORMEDDROPEFFECT
         - DROPEFFECT_NONE : for   optimized move
         - DROPEFFECT_MOVE : for unoptimized move.
       But actually Win10-Explorer-Target sends (DROPEFFECT_MOVE) for move operation.
       And it still works as in optimized mode, because "unoptimized" deleting by Source will be performed
       if both conditions are met:
          1) DROPEFFECT_MOVE is sent to (CFSTR_PERFORMEDDROPEFFECT) and
          2) (*effect == DROPEFFECT_MOVE) is returend by Drop().
       We don't want to send DROPEFFECT_MOVE here to protect from
       deleting file by Win10-Explorer.
       We are not sure that allfile fieree processed by move.
    */

    // for debug: we test the case when source tries to delete original files
    // bool res;
    // only CFSTR_PERFORMEDDROPEFFECT affects file removing in Win10-Explorer.
    // res = SendToSource_UInt32(dataObject, RegisterClipboardFormat(CFSTR_LOGICALPERFORMEDDROPEFFECT), DROPEFFECT_MOVE); // for debug
    /* res = */ SendToSource_UInt32(dataObject,
        RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT),
        cmd == NDragMenu::k_Cancel ? DROPEFFECT_NONE : DROPEFFECT_COPY);
    // res = res;
  }
  RemoveSelection();

  target.FuncType = k_DragTargetMode_Drop_End;
  target.Cmd_Type = cmd;
  if (needDrop_by_Source)
    target.Flags |= k_TargetFlags_MustBeProcessedBySource;

  SendToSource_TransferInfo(dataObject, target);
  } catch(...) { hres = E_FAIL; }
  
  ClearState();
  // *effect |= (1 << 10); // for debug
  // *effect = DROPEFFECT_COPY; // for debug

  /*
    if we return (*effect == DROPEFFECT_MOVE) here,
    Explorer-Source at some conditions can treat it as (unoptimized move) mode,
    and Explorer-Source will remove source files after DoDragDrop()
    in that (unoptimized move) mode.
    We want to avoid such (unoptimized move) cases.
    So we don't return (*effect == DROPEFFECT_MOVE), here if Source is not 7-Zip.
    If source is 7-Zip that will do acual opeartion, then we can return DROPEFFECT_MOVE.
  */
  if (hres != S_OK || (opEffect == DROPEFFECT_MOVE && !needDrop_by_Source))
  {
    // opEffect = opEffect;
    // opEffect = DROPEFFECT_NONE; // for debug disabled
  }

  *effect = opEffect;
  /* if (hres <  0), DoDragDrop() also will return (hres).
     if (hres >= 0), DoDragDrop() will return DRAGDROP_S_DROP;
  */
  return hres;
}



// ---------- CPanel ----------


static bool Is_Path1_Prefixed_by_Path2(const UString &path, const UString &prefix)
{
  const unsigned len = prefix.Len();
  if (path.Len() < len)
    return false;
  return CompareFileNames(path.Left(len), prefix) == 0;
}

static bool IsFolderInTemp(const UString &path)
{
  FString tempPathF;
  if (!MyGetTempPath(tempPathF))
    return false;
  const UString tempPath = fs2us(tempPathF);
  if (tempPath.IsEmpty())
    return false;
  return Is_Path1_Prefixed_by_Path2(path, tempPath);
}

static bool AreThereNamesFromTemp(const UStringVector &filePaths)
{
  FString tempPathF;
  if (!MyGetTempPath(tempPathF))
    return false;
  const UString tempPath = fs2us(tempPathF);
  if (tempPath.IsEmpty())
    return false;
  FOR_VECTOR (i, filePaths)
    if (Is_Path1_Prefixed_by_Path2(filePaths[i], tempPath))
      return true;
  return false;
}


/*
  empty folderPath means create new Archive to path of first fileName.
  createNewArchive == true : show "Add to archive ..." dialog with external program
    folderPath.IsEmpty()  : create archive in folder of filePaths[0].
  createNewArchive == false :
    folderPath.IsEmpty()  : copy to archive folder that is open in panel
    !folderPath.IsEmpty()  : CopyFsItems() to folderPath.
*/
void CPanel::CompressDropFiles(
    const UStringVector &filePaths,
    const UString &folderPath,
    bool createNewArchive,
    bool moveMode,
    UInt32 sourceFlags,
    UInt32 &targetFlags
    )
{
  if (filePaths.Size() == 0)
    return;
  // createNewArchive = false; // for debug

  if (createNewArchive)
  {
    UString folderPath2 = folderPath;
    // folderPath2.Empty(); // for debug
    if (folderPath2.IsEmpty())
    {
      {
        FString folderPath2F;
        GetOnlyDirPrefix(us2fs(filePaths.Front()), folderPath2F);
        folderPath2 = fs2us(folderPath2F);
      }
      if (IsFolderInTemp(folderPath2))
      {
        /* we don't want archive to be created in temp directory.
           so we change the path to root folder (non-temp) */
        folderPath2 = ROOT_FS_FOLDER;
      }
    }

    UString arcName_base;
    const UString arcName = CreateArchiveName(filePaths,
        false, // isHash
        NULL,  // CFileInfo *fi
        arcName_base);
    
    bool needWait;
    if (sourceFlags & k_SourceFlags_WaitFinish)
      needWait = true;
    else if (sourceFlags & k_SourceFlags_DoNotWaitFinish)
      needWait = false;
    else if (sourceFlags & k_SourceFlags_TempFiles)
      needWait = true;
    else
      needWait = AreThereNamesFromTemp(filePaths);

    targetFlags |= (needWait ?
        k_TargetFlags_WaitFinish :
        k_TargetFlags_DoNotWaitFinish);

    CompressFiles(folderPath2, arcName,
        L"",      // arcType
        true,     // addExtension
        filePaths,
        false,    // email
        true,     // showDialog
        needWait);
  }
  else
  {
    targetFlags |= k_TargetFlags_WaitFinish;
    if (!folderPath.IsEmpty())
    {
      CCopyToOptions options;
      options.moveMode = moveMode;
      options.folder = folderPath;
      options.showErrorMessages = true; // showErrorMessages is not used for this operation
      options.NeedRegistryZone = false;
      options.ZoneIdMode = NExtract::NZoneIdMode::kNone;
      // maybe we need more options here: FIXME
      /* HRESULT hres = */ CopyFsItems(options,
          filePaths,
          NULL // UStringVector *messages
          );
      // hres = hres;
    }
    else
    {
      CopyFromNoAsk(moveMode, filePaths);
    }
  }
}



static unsigned Drag_OnContextMenu(int xPos, int yPos, UInt32 cmdFlags)
{
  CMenu menu;
  CMenuDestroyer menuDestroyer(menu);
  /*
    Esc key in shown menu doesn't work if we call Drag_OnContextMenu from ::Drop().
    We call SetFocus() tp solve that problem.
    But the focus will be changed to Target Window after Drag and Drop.
    Is it OK to use SetFocus() here ?
    Is there another way to enable Esc key ?
  */
  // _listView.SetFocus(); // for debug
  ::SetFocus(g_HWND);
  menu.CreatePopup();
  /*
  int defaultCmd; // = NDragMenu::k_Move;
  defaultCmd = NDragMenu::k_None;
  */
  for (unsigned i = 0; i < Z7_ARRAY_SIZE(NDragMenu::g_Pairs); i++)
  {
    const NDragMenu::CCmdLangPair &pair = NDragMenu::g_Pairs[i];
    const UInt32 cmdAndFlags = pair.CmdId_and_Flags;
    const UInt32 cmdId = cmdAndFlags & NDragMenu::k_MenuFlags_CmdMask;
    if (cmdId != NDragMenu::k_Cancel)
    if ((cmdFlags & ((UInt32)1 << cmdId)) == 0)
      continue;
    const UINT flags = MF_STRING;
    /*
    if (prop.IsVisible)
      flags |= MF_CHECKED;
    if (i == 0)
      flags |= MF_GRAYED;
    */
    // MF_DEFAULT doesn't work
    // if (i == 2) flags |= MF_DEFAULT;
    // if (i == 4) flags |= MF_HILITE;
    // if (cmd == defaultCmd) flags |= MF_HILITE;
    UString name = LangString(pair.LangId);
    if (name.IsEmpty())
    {
      if (cmdId == NDragMenu::k_Cancel)
        name = "Cancel";
      else
        name.Add_UInt32(pair.LangId);
    }
    if (cmdId == NDragMenu::k_Copy_ToArc)
    {
      // UString destPath = _currentFolderPrefix;
      /*
      UString destPath = LangString(IDS_CONTEXT_ARCHIVE);
      name = MyFormatNew(name, destPath);
      */
      name.Add_Space();
      name += LangString(IDS_CONTEXT_ARCHIVE);
    }
    if (cmdId == NDragMenu::k_Cancel)
      menu.AppendItem(MF_SEPARATOR, 0, (LPCTSTR)NULL);
    menu.AppendItem(flags, cmdAndFlags, name);
  }
  /*
  if (defaultCmd != 0)
    SetMenuDefaultItem(menu, (unsigned)defaultCmd,
        FALSE); // byPos
  */
  int menuResult = menu.Track(
      TPM_LEFTALIGN | TPM_RETURNCMD | TPM_NONOTIFY,
      xPos, yPos,
      g_HWND
      // _listView // for debug
      );
  /* menu.Track() return value is zero, if the user cancels
     the menu without making a selection, or if an error occurs */
  if (menuResult <= 0)
    menuResult = NDragMenu::k_Cancel;
  return (unsigned)menuResult;
}



void CApp::CreateDragTarget()
{
  _dropTargetSpec = new CDropTarget();
  _dropTarget = _dropTargetSpec;
  _dropTargetSpec->App = (this);
}

void CApp::SetFocusedPanel(unsigned index)
{
  LastFocusedPanel = index;
  _dropTargetSpec->TargetPanelIndex = (int)LastFocusedPanel;
}

void CApp::DragBegin(unsigned panelIndex)
{
  _dropTargetSpec->TargetPanelIndex = (int)(NumPanels > 1 ? 1 - panelIndex : panelIndex);
  _dropTargetSpec->SrcPanelIndex = (int)panelIndex;
}

void CApp::DragEnd()
{
  _dropTargetSpec->TargetPanelIndex = (int)LastFocusedPanel;
  _dropTargetSpec->SrcPanelIndex = -1;
}
