nextcloud-desktop/shell_integration/windows/OCContextMenu/OCContextMenu.cpp
Jocelyn Turcotte 7a4daf799a shell/windows: Use a submenu to include private link actions
Refactor things a bit to be able to reuse some code and
clean things up.
2017-07-07 10:49:51 +02:00

315 lines
No EOL
9.1 KiB
C++

/**
* Copyright (c) 2015 Daniel Molkentin <danimo@owncloud.com>. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
#include "stdafx.h"
#include "OCContextMenu.h"
#include "OCClientInterface.h"
#include <shobjidl.h>
#include <shlwapi.h>
#include <shellapi.h>
#include <StringUtil.h>
extern HINSTANCE g_hInst;
extern long g_cDllRef;
#define IDM_SHARE 0
#define IDM_COPYLINK 1
#define IDM_EMAILLINK 2
OCContextMenu::OCContextMenu(void)
: m_cRef(1)
{
InterlockedIncrement(&g_cDllRef);
}
OCContextMenu::~OCContextMenu(void)
{
InterlockedDecrement(&g_cDllRef);
}
void OCContextMenu::OnVerbShare(HWND hWnd)
{
OCClientInterface::RequestShare(std::wstring(m_szSelectedFile));
}
void OCContextMenu::OnVerbCopyLink(HWND hWnd)
{
OCClientInterface::RequestCopyLink(std::wstring(m_szSelectedFile));
}
void OCContextMenu::OnVerbEmailLink(HWND hWnd)
{
OCClientInterface::RequestEmailLink(std::wstring(m_szSelectedFile));
}
#pragma region IUnknown
// Query to the interface the component supported.
IFACEMETHODIMP OCContextMenu::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(OCContextMenu, IContextMenu),
QITABENT(OCContextMenu, IShellExtInit),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
// Increase the reference count for an interface on an object.
IFACEMETHODIMP_(ULONG) OCContextMenu::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
// Decrease the reference count for an interface on an object.
IFACEMETHODIMP_(ULONG) OCContextMenu::Release()
{
ULONG cRef = InterlockedDecrement(&m_cRef);
if (0 == cRef) {
delete this;
}
return cRef;
}
#pragma endregion
#pragma region IShellExtInit
// Initialize the context menu handler.
IFACEMETHODIMP OCContextMenu::Initialize(
LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID)
{
if (!pDataObj) {
return E_INVALIDARG;
}
HRESULT hr = E_FAIL;
FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stm;
if (SUCCEEDED(pDataObj->GetData(&fe, &stm))) {
// Get an HDROP handle.
HDROP hDrop = static_cast<HDROP>(GlobalLock(stm.hGlobal));
if (hDrop) {
// Ignore multi-selections
UINT nFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
if (nFiles == 1) {
// Get the path of the file.
if (0 != DragQueryFile(hDrop, 0, m_szSelectedFile, ARRAYSIZE(m_szSelectedFile)))
{
hr = S_OK;
}
}
GlobalUnlock(stm.hGlobal);
}
ReleaseStgMedium(&stm);
}
// If any value other than S_OK is returned from the method, the context
// menu item is not displayed.
return hr;
}
#pragma endregion
#pragma region IContextMenu
void InsertSeperator(HMENU hMenu, UINT indexMenu)
{
// Add a separator.
MENUITEMINFO sep = { sizeof(sep) };
sep.fMask = MIIM_TYPE;
sep.fType = MFT_SEPARATOR;
InsertMenuItem(hMenu, indexMenu, TRUE, &sep);
}
IFACEMETHODIMP OCContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
// If uFlags include CMF_DEFAULTONLY then we should not do anything.
if (CMF_DEFAULTONLY & uFlags)
{
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
}
OCClientInterface::ContextMenuInfo info = OCClientInterface::FetchInfo();
bool skip = true;
size_t selectedFileLength = wcslen(m_szSelectedFile);
for (const std::wstring path : info.watchedDirectories) {
if (StringUtil::isDescendantOf(m_szSelectedFile, selectedFileLength, path)) {
skip = false;
break;
}
}
if (skip) {
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
}
InsertSeperator(hMenu, indexMenu++);
HMENU hSubmenu = CreateMenu();
{
MENUITEMINFO mii = { sizeof(mii) };
mii.fMask = MIIM_SUBMENU | MIIM_FTYPE | MIIM_STRING;
mii.hSubMenu = hSubmenu;
mii.fType = MFT_STRING;
mii.dwTypeData = &info.contextMenuTitle[0];
if (!InsertMenuItem(hMenu, indexMenu++, TRUE, &mii))
return HRESULT_FROM_WIN32(GetLastError());
}
InsertSeperator(hMenu, indexMenu++);
UINT indexSubMenu = 0;
{
assert(!info.shareMenuTitle.empty());
MENUITEMINFO mii = { sizeof(mii) };
mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING;
mii.wID = idCmdFirst + IDM_SHARE;
mii.fType = MFT_STRING;
mii.dwTypeData = &info.shareMenuTitle[0];
if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii))
return HRESULT_FROM_WIN32(GetLastError());
}
{
assert(!info.copyLinkMenuTitle.empty());
MENUITEMINFO mii = { sizeof(mii) };
mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING;
mii.wID = idCmdFirst + IDM_COPYLINK;
mii.fType = MFT_STRING;
mii.dwTypeData = &info.copyLinkMenuTitle[0];
if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii))
return HRESULT_FROM_WIN32(GetLastError());
}
{
assert(!info.emailLinkMenuTitle.empty());
MENUITEMINFO mii = { sizeof(mii) };
mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING;
mii.wID = idCmdFirst + IDM_EMAILLINK;
mii.fType = MFT_STRING;
mii.dwTypeData = &info.emailLinkMenuTitle[0];
if (!InsertMenuItem(hSubmenu, indexSubMenu++, TRUE, &mii))
return HRESULT_FROM_WIN32(GetLastError());
}
// Return an HRESULT value with the severity set to SEVERITY_SUCCESS.
// Set the code value to the offset of the largest command identifier
// that was assigned, plus one (1).
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(IDM_EMAILLINK + 1));
}
IFACEMETHODIMP OCContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
// For the Unicode case, if the high-order word is not zero, the
// command's verb string is in lpcmi->lpVerbW.
if (HIWORD(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW))
{
// Is the verb supported by this context menu extension?
if (StrCmpIW(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, L"ocshare") == 0) {
OnVerbShare(pici->hwnd);
}
else if (StrCmpIW(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, L"occopylink") == 0) {
OnVerbCopyLink(pici->hwnd);
}
else if (StrCmpIW(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, L"ocemaillink") == 0) {
OnVerbEmailLink(pici->hwnd);
}
else {
// If the verb is not recognized by the context menu handler, it
// must return E_FAIL to allow it to be passed on to the other
// context menu handlers that might implement that verb.
return E_FAIL;
}
}
// If the command cannot be identified through the verb string, then
// check the identifier offset.
else
{
// Is the command identifier offset supported by this context menu
// extension?
if (LOWORD(pici->lpVerb) == IDM_SHARE) {
OnVerbShare(pici->hwnd);
}
else if (LOWORD(pici->lpVerb) == IDM_COPYLINK) {
OnVerbCopyLink(pici->hwnd);
}
else if (LOWORD(pici->lpVerb) == IDM_EMAILLINK) {
OnVerbEmailLink(pici->hwnd);
}
else {
// If the verb is not recognized by the context menu handler, it
// must return E_FAIL to allow it to be passed on to the other
// context menu handlers that might implement that verb.
return E_FAIL;
}
}
return S_OK;
}
IFACEMETHODIMP OCContextMenu::GetCommandString(UINT_PTR idCommand,
UINT uFlags, UINT *pwReserved, LPSTR pszName, UINT cchMax)
{
HRESULT hr = E_INVALIDARG;
switch (idCommand) {
case IDM_SHARE:
if (uFlags == GCS_VERBW) {
// GCS_VERBW is an optional feature that enables a caller to
// discover the canonical name for the verb passed in through
// idCommand.
hr = StringCchCopy(reinterpret_cast<PWSTR>(pszName), cchMax,
L"OCShareViaOC");
}
break;
case IDM_COPYLINK:
if (uFlags == GCS_VERBW) {
hr = StringCchCopy(reinterpret_cast<PWSTR>(pszName), cchMax,
L"OCCopyLink");
}
break;
case IDM_EMAILLINK:
if (uFlags == GCS_VERBW) {
hr = StringCchCopy(reinterpret_cast<PWSTR>(pszName), cchMax,
L"OCEmailLink");
}
break;
default:
break;
}
// If the idCommand or uFlags is not supported by this context menu
// extension handler, return E_INVALIDARG.
return hr;
}
#pragma endregion