mirror of
https://github.com/rizonesoft/Notepad3.git
synced 2026-06-14 21:09:05 +08:00
554 lines
21 KiB
C++
554 lines
21 KiB
C++
// grepWin - regex search and replace for Windows
|
|
|
|
// Copyright (C) 2007-2015, 2017, 2020-2021 - Stefan Kueng
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software Foundation,
|
|
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
//
|
|
#include "stdafx.h"
|
|
#include "ShellContextMenu.h"
|
|
#include <shellapi.h>
|
|
#include "StringUtils.h"
|
|
#include "Registry.h"
|
|
#include "SearchInfo.h"
|
|
#include "LineData.h"
|
|
#include "ResString.h"
|
|
#include "resource.h"
|
|
#include <set>
|
|
#include <algorithm>
|
|
|
|
#define MIN_ID 6
|
|
#define MAX_ID 10000
|
|
|
|
IContextMenu2* g_iContext2 = nullptr;
|
|
IContextMenu3* g_iContext3 = nullptr;
|
|
WNDPROC g_oldWndProc = nullptr;
|
|
|
|
struct ICompare
|
|
{
|
|
bool operator()(const std::wstring& lhs, const std::wstring& rhs) const
|
|
{
|
|
return _wcsicmp(lhs.c_str(), rhs.c_str()) < 0;
|
|
}
|
|
};
|
|
|
|
CShellContextMenu::CShellContextMenu()
|
|
: m_nItems(0)
|
|
, bDelete(FALSE)
|
|
, m_menu(nullptr)
|
|
, m_psfFolder(nullptr)
|
|
, m_pidlArray(nullptr)
|
|
, m_pidlArrayItems(0)
|
|
, m_pFolderHook(nullptr)
|
|
{
|
|
}
|
|
|
|
CShellContextMenu::~CShellContextMenu()
|
|
{
|
|
// free all allocated data
|
|
delete m_pFolderHook;
|
|
if (m_psfFolder && bDelete)
|
|
m_psfFolder->Release();
|
|
m_psfFolder = nullptr;
|
|
FreePIDLArray(m_pidlArray, m_pidlArrayItems);
|
|
m_pidlArray = nullptr;
|
|
m_pidlArrayItems = 0;
|
|
|
|
if (m_menu)
|
|
DestroyMenu(m_menu);
|
|
}
|
|
|
|
// this functions determines which version of IContextMenu is available for those objects (always the highest one)
|
|
// and returns that interface
|
|
BOOL CShellContextMenu::GetContextMenu(HWND hWnd, void** ppContextMenu, int& iMenuType)
|
|
{
|
|
*ppContextMenu = nullptr;
|
|
if (m_pFolderHook)
|
|
return FALSE;
|
|
if (m_psfFolder == nullptr)
|
|
return FALSE;
|
|
if (m_strVector.empty())
|
|
return FALSE;
|
|
|
|
HKEY ahkeys[16];
|
|
SecureZeroMemory(ahkeys, _countof(ahkeys) * sizeof(HKEY));
|
|
int numkeys = 0;
|
|
if (RegOpenKey(HKEY_CLASSES_ROOT, L"*", &ahkeys[numkeys++]) != ERROR_SUCCESS)
|
|
numkeys--;
|
|
if (RegOpenKey(HKEY_CLASSES_ROOT, L"AllFileSystemObjects", &ahkeys[numkeys++]) != ERROR_SUCCESS)
|
|
numkeys--;
|
|
if (PathIsDirectory(m_strVector[0].filePath.c_str()))
|
|
{
|
|
if (RegOpenKey(HKEY_CLASSES_ROOT, L"Folder", &ahkeys[numkeys++]) != ERROR_SUCCESS)
|
|
numkeys--;
|
|
if (RegOpenKey(HKEY_CLASSES_ROOT, L"Directory", &ahkeys[numkeys++]) != ERROR_SUCCESS)
|
|
numkeys--;
|
|
}
|
|
// find extension
|
|
size_t dotPos = m_strVector[0].filePath.find_last_of('.');
|
|
std::wstring ext;
|
|
if (dotPos != std::string::npos)
|
|
{
|
|
ext = m_strVector[0].filePath.substr(dotPos);
|
|
if (RegOpenKey(HKEY_CLASSES_ROOT, ext.c_str(), &ahkeys[numkeys++]) == ERROR_SUCCESS)
|
|
{
|
|
WCHAR buf[MAX_PATH] = {0};
|
|
DWORD dwSize = MAX_PATH;
|
|
if (RegQueryValueEx(ahkeys[numkeys - 1], L"", nullptr, nullptr, reinterpret_cast<LPBYTE>(buf), &dwSize) == ERROR_SUCCESS)
|
|
{
|
|
if (RegOpenKey(HKEY_CLASSES_ROOT, buf, &ahkeys[numkeys++]) != ERROR_SUCCESS)
|
|
numkeys--;
|
|
}
|
|
}
|
|
}
|
|
|
|
delete m_pFolderHook;
|
|
m_pFolderHook = new CIShellFolderHook(m_psfFolder, this);
|
|
|
|
LPCONTEXTMENU icm1 = nullptr;
|
|
if (FAILED(CDefFolderMenu_Create2(NULL, hWnd, static_cast<UINT>(m_pidlArrayItems), const_cast<LPCITEMIDLIST*>(m_pidlArray), m_pFolderHook, dfmCallback, numkeys, ahkeys, &icm1)))
|
|
return FALSE;
|
|
for (int i = 0; i < numkeys; ++i)
|
|
RegCloseKey(ahkeys[i]);
|
|
|
|
if (icm1)
|
|
{ // since we got an IContextMenu interface we can now obtain the higher version interfaces via that
|
|
if (icm1->QueryInterface(IID_IContextMenu3, ppContextMenu) == S_OK)
|
|
iMenuType = 3;
|
|
else if (icm1->QueryInterface(IID_IContextMenu2, ppContextMenu) == S_OK)
|
|
iMenuType = 2;
|
|
|
|
if (*ppContextMenu)
|
|
icm1->Release(); // we can now release version 1 interface, cause we got a higher one
|
|
else
|
|
{
|
|
// since no higher versions were found
|
|
// redirect ppContextMenu to version 1 interface
|
|
iMenuType = 1;
|
|
*ppContextMenu = icm1;
|
|
}
|
|
}
|
|
else
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT CALLBACK CShellContextMenu::HookWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (message)
|
|
{
|
|
case WM_MENUCHAR: // only supported by IContextMenu3
|
|
if (g_iContext3)
|
|
{
|
|
LRESULT lResult = 0;
|
|
g_iContext3->HandleMenuMsg2(message, wParam, lParam, &lResult);
|
|
return (lResult);
|
|
}
|
|
break;
|
|
|
|
case WM_DRAWITEM:
|
|
[[fallthrough]];
|
|
case WM_MEASUREITEM:
|
|
if (wParam)
|
|
break; // if wParam != 0 then the message is not menu-related
|
|
|
|
case WM_INITMENU:
|
|
[[fallthrough]];
|
|
case WM_INITMENUPOPUP:
|
|
if (g_iContext3)
|
|
{
|
|
LRESULT lResult = 0;
|
|
g_iContext3->HandleMenuMsg2(message, wParam, lParam, &lResult);
|
|
return (lResult);
|
|
}
|
|
else if (g_iContext2)
|
|
g_iContext2->HandleMenuMsg(message, wParam, lParam);
|
|
|
|
return TRUE;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// call original WndProc of window to prevent undefined behavior of window
|
|
return ::CallWindowProc(g_oldWndProc, hWnd, message, wParam, lParam);
|
|
}
|
|
|
|
UINT CShellContextMenu::ShowContextMenu(HWND hWnd, POINT pt)
|
|
{
|
|
int iMenuType = 0; // to know which version of IContextMenu is supported
|
|
LPCONTEXTMENU pContextMenu; // common pointer to IContextMenu and higher version interface
|
|
|
|
if (GetWindowLongPtr(hWnd, GWLP_WNDPROC) == reinterpret_cast<LONG_PTR>(HookWndProc))
|
|
return 0;
|
|
|
|
if (!GetContextMenu(hWnd, reinterpret_cast<void**>(&pContextMenu), iMenuType))
|
|
return 0;
|
|
|
|
if (!m_menu)
|
|
{
|
|
DestroyMenu(m_menu);
|
|
m_menu = CreatePopupMenu();
|
|
}
|
|
|
|
CRegStdString regEditorCmd(L"Software\\grepWin\\editorcmd");
|
|
std::wstring editorCmd = regEditorCmd;
|
|
if (bPortable)
|
|
editorCmd = g_iniFile.GetValue(L"global", L"editorcmd", L"");
|
|
|
|
if (m_strVector.size() == 1)
|
|
{
|
|
if (!editorCmd.empty())
|
|
{
|
|
::InsertMenu(m_menu, 1, MF_BYPOSITION | MF_STRING, 5, TranslatedString(g_hInst, IDS_OPENWITHEDITOR).c_str());
|
|
::InsertMenu(m_menu, 5, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr);
|
|
}
|
|
|
|
::InsertMenu(m_menu, 1, MF_BYPOSITION | MF_STRING, 1, TranslatedString(g_hInst, IDS_OPENCONTAININGFOLDER).c_str());
|
|
::InsertMenu(m_menu, 2, MF_BYPOSITION | MF_STRING, 2, TranslatedString(g_hInst, IDS_COPYPATH).c_str());
|
|
::InsertMenu(m_menu, 3, MF_BYPOSITION | MF_STRING, 3, TranslatedString(g_hInst, IDS_COPYFILENAME).c_str());
|
|
if (!m_lineVector.empty())
|
|
::InsertMenu(m_menu, 4, MF_BYPOSITION | MF_STRING, 4, TranslatedString(g_hInst, IDS_COPYRESULT).c_str());
|
|
::InsertMenu(m_menu, 5, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr);
|
|
}
|
|
else if (m_strVector.size() > 1)
|
|
{
|
|
if (!editorCmd.empty())
|
|
{
|
|
::InsertMenu(m_menu, 1, MF_BYPOSITION | MF_STRING, 5, TranslatedString(g_hInst, IDS_OPENWITHEDITOR).c_str());
|
|
::InsertMenu(m_menu, 5, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr);
|
|
}
|
|
::InsertMenu(m_menu, 2, MF_BYPOSITION | MF_STRING, 2, TranslatedString(g_hInst, IDS_COPYPATHS).c_str());
|
|
::InsertMenu(m_menu, 3, MF_BYPOSITION | MF_STRING, 3, TranslatedString(g_hInst, IDS_COPYFILENAMES).c_str());
|
|
if (!m_lineVector.empty())
|
|
::InsertMenu(m_menu, 4, MF_BYPOSITION | MF_STRING, 4, TranslatedString(g_hInst, IDS_COPYRESULTS).c_str());
|
|
::InsertMenu(m_menu, 5, MF_SEPARATOR | MF_BYPOSITION, 0, nullptr);
|
|
}
|
|
// lets fill the our popup menu
|
|
pContextMenu->QueryContextMenu(m_menu, GetMenuItemCount(m_menu), MIN_ID, MAX_ID, CMF_NORMAL | CMF_EXPLORE);
|
|
|
|
// subclass window to handle menu related messages in CShellContextMenu
|
|
if (iMenuType > 1) // only subclass if its version 2 or 3
|
|
{
|
|
if (GetWindowLongPtr(hWnd, GWLP_WNDPROC) != reinterpret_cast<LONG_PTR>(HookWndProc))
|
|
g_oldWndProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(HookWndProc)));
|
|
if (iMenuType == 2)
|
|
g_iContext2 = static_cast<LPCONTEXTMENU2>(pContextMenu);
|
|
else // version 3
|
|
g_iContext3 = static_cast<LPCONTEXTMENU3>(pContextMenu);
|
|
}
|
|
else
|
|
g_oldWndProc = nullptr;
|
|
|
|
UINT idCommand = TrackPopupMenu(m_menu, TPM_RETURNCMD | TPM_LEFTALIGN, pt.x, pt.y, 0, hWnd, nullptr);
|
|
|
|
if (g_oldWndProc) // un-subclass
|
|
SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(g_oldWndProc));
|
|
|
|
if (idCommand >= MIN_ID && idCommand <= MAX_ID) // see if returned idCommand belongs to shell menu entries
|
|
{
|
|
InvokeCommand(pContextMenu, idCommand - MIN_ID); // execute related command
|
|
idCommand = 0;
|
|
}
|
|
else
|
|
{
|
|
switch (idCommand)
|
|
{
|
|
case 1:
|
|
{
|
|
// This is the command line for explorer which tells it to select the given file
|
|
std::wstring sFolder = L"/Select,\"" + m_strVector[0].filePath + L"\"";
|
|
|
|
// Prepare shell execution params
|
|
SHELLEXECUTEINFO shExecInfo = {0};
|
|
shExecInfo.cbSize = sizeof(shExecInfo);
|
|
shExecInfo.lpFile = L"explorer.exe";
|
|
shExecInfo.lpParameters = sFolder.c_str();
|
|
shExecInfo.nShow = SW_SHOWNORMAL;
|
|
shExecInfo.lpVerb = L"open"; // Context menu item
|
|
shExecInfo.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI;
|
|
|
|
// Select file in explorer
|
|
ShellExecuteEx(&shExecInfo);
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
std::wstring pathNames;
|
|
for (auto it = m_strVector.begin(); it != m_strVector.end(); ++it)
|
|
{
|
|
if (!pathNames.empty())
|
|
pathNames += L"\r\n";
|
|
pathNames += it->filePath;
|
|
}
|
|
WriteAsciiStringToClipboard(pathNames.c_str(), hWnd);
|
|
}
|
|
break;
|
|
case 3:
|
|
{
|
|
std::wstring pathNames;
|
|
for (auto it = m_strVector.begin(); it != m_strVector.end(); ++it)
|
|
{
|
|
if (!pathNames.empty())
|
|
pathNames += L"\r\n";
|
|
std::wstring p = it->filePath;
|
|
p = p.substr(p.find_last_of('\\') + 1);
|
|
pathNames += p;
|
|
}
|
|
WriteAsciiStringToClipboard(pathNames.c_str(), hWnd);
|
|
}
|
|
break;
|
|
case 4:
|
|
{
|
|
std::wstring lines;
|
|
for (auto it = m_lineVector.begin(); it != m_lineVector.end(); ++it)
|
|
{
|
|
if (!lines.empty())
|
|
lines += L"\r\n";
|
|
for (auto it2 = it->lines.cbegin(); it2 != it->lines.cend(); ++it2)
|
|
{
|
|
std::wstring l = it2->text;
|
|
CStringUtils::trim(l, L"\r\n");
|
|
std::replace(l.begin(), l.end(), '\n', ' ');
|
|
std::replace(l.begin(), l.end(), '\r', ' ');
|
|
|
|
lines += l;
|
|
}
|
|
}
|
|
WriteAsciiStringToClipboard(lines.c_str(), hWnd);
|
|
}
|
|
break;
|
|
case 5:
|
|
{
|
|
if (!m_lineVector.empty())
|
|
{
|
|
for (auto it = m_lineVector.cbegin(); it != m_lineVector.cend(); ++it)
|
|
{
|
|
for (auto it2 = it->lines.cbegin(); it2 != it->lines.cend(); ++it2)
|
|
{
|
|
std::wstring cmd = editorCmd;
|
|
SearchReplace(cmd, L"%path%", it->path.c_str());
|
|
wchar_t buf[40] = {0};
|
|
swprintf_s(buf, L"%ld", it2->number);
|
|
SearchReplace(cmd, L"%line%", buf);
|
|
|
|
STARTUPINFO startupInfo;
|
|
PROCESS_INFORMATION processInfo;
|
|
SecureZeroMemory(&startupInfo, sizeof(startupInfo));
|
|
startupInfo.cb = sizeof(STARTUPINFO);
|
|
SecureZeroMemory(&processInfo, sizeof(processInfo));
|
|
CreateProcess(nullptr, const_cast<wchar_t*>(cmd.c_str()), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startupInfo, &processInfo);
|
|
CloseHandle(processInfo.hThread);
|
|
CloseHandle(processInfo.hProcess);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto it = m_strVector.begin(); it != m_strVector.end(); ++it)
|
|
{
|
|
std::wstring cmd = editorCmd;
|
|
SearchReplace(cmd, L"%path%", it->filePath.c_str());
|
|
if (!it->matchLinesNumbers.empty())
|
|
{
|
|
wchar_t buf[40] = {0};
|
|
swprintf_s(buf, L"%ld", it->matchLinesNumbers[0]);
|
|
SearchReplace(cmd, L"%line%", buf);
|
|
}
|
|
else
|
|
SearchReplace(cmd, L"%line%", L"0");
|
|
|
|
STARTUPINFO startupInfo;
|
|
PROCESS_INFORMATION processInfo;
|
|
SecureZeroMemory(&startupInfo, sizeof(startupInfo));
|
|
startupInfo.cb = sizeof(STARTUPINFO);
|
|
SecureZeroMemory(&processInfo, sizeof(processInfo));
|
|
CreateProcess(nullptr, const_cast<wchar_t*>(cmd.c_str()), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startupInfo, &processInfo);
|
|
CloseHandle(processInfo.hThread);
|
|
CloseHandle(processInfo.hProcess);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
pContextMenu->Release();
|
|
g_iContext2 = nullptr;
|
|
g_iContext3 = nullptr;
|
|
delete m_pFolderHook;
|
|
m_pFolderHook = nullptr;
|
|
return (idCommand);
|
|
}
|
|
|
|
void CShellContextMenu::InvokeCommand(LPCONTEXTMENU pContextMenu, UINT idCommand)
|
|
{
|
|
CMINVOKECOMMANDINFO cmi = {0};
|
|
cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);
|
|
cmi.lpVerb = reinterpret_cast<LPSTR>(MAKEINTRESOURCE(idCommand));
|
|
cmi.nShow = SW_SHOWNORMAL;
|
|
|
|
pContextMenu->InvokeCommand(&cmi);
|
|
}
|
|
|
|
void CShellContextMenu::SetObjects(const std::vector<CSearchInfo>& strVector, const std::vector<LineData>& lineVector)
|
|
{
|
|
// free all allocated data
|
|
if (m_psfFolder && bDelete)
|
|
m_psfFolder->Release();
|
|
m_psfFolder = nullptr;
|
|
FreePIDLArray(m_pidlArray, m_pidlArrayItems);
|
|
m_pidlArray = nullptr;
|
|
|
|
// get IShellFolder interface of Desktop (root of shell namespace)
|
|
SHGetDesktopFolder(&m_psfFolder); // needed to obtain full qualified pidl
|
|
|
|
// ParseDisplayName creates a PIDL from a file system path relative to the IShellFolder interface
|
|
// but since we use the Desktop as our interface and the Desktop is the namespace root
|
|
// that means that it's a fully qualified PIDL, which is what we need
|
|
|
|
m_nItems = strVector.size();
|
|
m_pidlArray = static_cast<LPITEMIDLIST*>(CoTaskMemAlloc((m_nItems + 10) * sizeof(LPITEMIDLIST)));
|
|
SecureZeroMemory(m_pidlArray, (m_nItems + 10) * sizeof(LPITEMIDLIST));
|
|
m_pidlArrayItems = 0;
|
|
int succeededItems = 0;
|
|
LPITEMIDLIST pidl = nullptr;
|
|
m_strVector.clear();
|
|
m_lineVector.clear();
|
|
m_strVector.reserve(m_nItems);
|
|
m_lineVector.reserve(m_nItems);
|
|
|
|
size_t bufSize = 1024;
|
|
auto filePath = std::make_unique<WCHAR[]>(bufSize);
|
|
for (size_t i = 0; i < m_nItems; i++)
|
|
{
|
|
if (bufSize < strVector[i].filePath.size())
|
|
{
|
|
bufSize = strVector[i].filePath.size() + 3;
|
|
filePath = std::make_unique<WCHAR[]>(bufSize);
|
|
}
|
|
wcscpy_s(filePath.get(), bufSize, strVector[i].filePath.c_str());
|
|
if (SUCCEEDED(m_psfFolder->ParseDisplayName(NULL, nullptr, filePath.get(), NULL, &pidl, NULL)))
|
|
{
|
|
m_pidlArray[succeededItems++] = pidl; // copy pidl to pidlArray
|
|
m_strVector.push_back(strVector[i]);
|
|
if (lineVector.size() > static_cast<size_t>(i))
|
|
m_lineVector.push_back(lineVector[i]);
|
|
}
|
|
}
|
|
m_pidlArrayItems = succeededItems;
|
|
|
|
bDelete = TRUE; // indicates that m_psfFolder should be deleted by CShellContextMenu
|
|
}
|
|
|
|
void CShellContextMenu::FreePIDLArray(LPITEMIDLIST* pidlArray, int nItems)
|
|
{
|
|
if (!pidlArray)
|
|
return;
|
|
|
|
for (int i = 0; i < nItems; i++)
|
|
{
|
|
CoTaskMemFree(pidlArray[i]);
|
|
}
|
|
CoTaskMemFree(pidlArray);
|
|
}
|
|
|
|
HRESULT CShellContextMenu::dfmCallback(IShellFolder* /*psf*/, HWND /*hwnd*/, IDataObject* /*pdtobj*/, UINT uMsg, WPARAM /*wParam*/, LPARAM /*lParam*/)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case DFM_MERGECONTEXTMENU:
|
|
return S_OK;
|
|
case DFM_INVOKECOMMAND:
|
|
case DFM_INVOKECOMMANDEX:
|
|
case DFM_GETDEFSTATICID: // Required for Windows 7 to pick a default
|
|
return S_FALSE;
|
|
}
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CIShellFolderHook::GetUIObjectOf(HWND hwndOwner, UINT cidl, LPCITEMIDLIST* apidl, REFIID riid, UINT* rgfReserved, void** ppv)
|
|
{
|
|
if (InlineIsEqualGUID(riid, IID_IDataObject))
|
|
{
|
|
HRESULT hRes = m_iSf->GetUIObjectOf(hwndOwner, cidl, apidl, IID_IDataObject, nullptr, ppv);
|
|
if (FAILED(hRes))
|
|
return hRes;
|
|
|
|
IDataObject* iData = static_cast<LPDATAOBJECT>(*ppv);
|
|
// the IDataObject returned here doesn't have a HDROP, so we create one ourselves and add it to the IDataObject
|
|
// the HDROP is necessary for most context menu handlers
|
|
|
|
// it seems the paths in the HDROP need to be sorted, otherwise
|
|
// it might not work properly or even crash.
|
|
// to get the items sorted, we just add them to a set - that way we g
|
|
std::set<std::wstring, ICompare> sortedPaths;
|
|
for (auto it = m_pShellContextMenu->m_strVector.cbegin(); it != m_pShellContextMenu->m_strVector.cend(); ++it)
|
|
sortedPaths.insert(it->filePath);
|
|
|
|
int nLength = 0;
|
|
for (auto it = sortedPaths.cbegin(); it != sortedPaths.cend(); ++it)
|
|
{
|
|
nLength += static_cast<int>(it->size());
|
|
nLength += 1; // '\0' separator
|
|
}
|
|
int nBufferSize = sizeof(DROPFILES) + ((nLength + 5) * sizeof(wchar_t));
|
|
auto pBuffer = std::make_unique<char[]>(nBufferSize);
|
|
SecureZeroMemory(pBuffer.get(), nBufferSize);
|
|
DROPFILES* df = reinterpret_cast<DROPFILES*>(pBuffer.get());
|
|
df->pFiles = sizeof(DROPFILES);
|
|
df->fWide = 1;
|
|
wchar_t* pFileNames = reinterpret_cast<wchar_t*>(reinterpret_cast<BYTE*>(pBuffer.get()) + sizeof(DROPFILES));
|
|
wchar_t* pCurrentFilename = pFileNames;
|
|
|
|
for (auto it = sortedPaths.cbegin(); it != sortedPaths.cend(); ++it)
|
|
{
|
|
wcscpy_s(pCurrentFilename, it->size() + 1, it->c_str());
|
|
pCurrentFilename += it->size();
|
|
*pCurrentFilename = '\0'; // separator between file names
|
|
pCurrentFilename++;
|
|
}
|
|
*pCurrentFilename = '\0'; // terminate array
|
|
pCurrentFilename++;
|
|
*pCurrentFilename = '\0'; // terminate array
|
|
STGMEDIUM medium = {0};
|
|
medium.tymed = TYMED_HGLOBAL;
|
|
medium.hGlobal = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE, nBufferSize + 20);
|
|
if (medium.hGlobal)
|
|
{
|
|
LPVOID pMem = ::GlobalLock(medium.hGlobal);
|
|
if (pMem)
|
|
{
|
|
memcpy(pMem, pBuffer.get(), nBufferSize);
|
|
GlobalUnlock(medium.hGlobal);
|
|
FORMATETC formatEtc = {0};
|
|
formatEtc.cfFormat = CF_HDROP;
|
|
formatEtc.dwAspect = DVASPECT_CONTENT;
|
|
formatEtc.lindex = -1;
|
|
formatEtc.tymed = TYMED_HGLOBAL;
|
|
medium.pUnkForRelease = nullptr;
|
|
hRes = iData->SetData(&formatEtc, &medium, TRUE);
|
|
return hRes;
|
|
}
|
|
}
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
// just pass it on to the base object
|
|
return m_iSf->GetUIObjectOf(hwndOwner, cidl, apidl, riid, rgfReserved, ppv);
|
|
}
|
|
}
|