mirror of
https://github.com/Xevion/easy7zip.git
synced 2025-12-06 07:14:55 -06:00
3021 lines
91 KiB
C++
Executable File
3021 lines
91 KiB
C++
Executable File
// 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
|
|
Int64 SoleFolderIndex;
|
|
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;
|
|
options.soleFolderIndex = SoleFolderIndex;
|
|
/* 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);
|
|
}
|
|
*/
|
|
}
|
|
|
|
Int64 soleFolderIndex = -1LL; // initially unset value
|
|
{
|
|
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);
|
|
if (IsItem_Folder(index)) {
|
|
if (i == 0) {
|
|
soleFolderIndex = (Int64)index;
|
|
} else {
|
|
soleFolderIndex = -1LL;
|
|
}
|
|
} else {
|
|
soleFolderIndex = -1LL;
|
|
}
|
|
/*
|
|
// 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;
|
|
dataObjectSpec->SoleFolderIndex = soleFolderIndex;
|
|
|
|
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;
|
|
options.soleFolderIndex = soleFolderIndex;
|
|
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;
|
|
}
|