997 lines
28 KiB
C++
997 lines
28 KiB
C++
//+-------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 2000
|
|
//
|
|
// File: cplnkele.cpp
|
|
//
|
|
// This module implements a 'link' element in the Control Panel's DUI
|
|
// view. Link elements have a title, infotip, icon and an associated
|
|
// command that is invoked when the link is selected. The CLinkElement
|
|
// object is an extension of the DUI::Button class. Direct UI automatically
|
|
// creates an instance of CLinkElement when a 'linkelement' item from
|
|
// cpview.ui is instantiated.
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
#include "shellprv.h"
|
|
|
|
#include "cpviewp.h"
|
|
#include "cpaction.h"
|
|
#include "cpduihlp.h"
|
|
#include "cpguids.h"
|
|
#include "cpuiele.h"
|
|
#include "cplnkele.h"
|
|
#include "cputil.h"
|
|
#include "defviewp.h"
|
|
#include "dobjutil.h"
|
|
#include "ids.h"
|
|
|
|
using namespace CPL;
|
|
|
|
|
|
CLinkElement::CLinkElement(
|
|
void
|
|
) : m_pUiCommand(NULL),
|
|
m_eIconSize(eCPIMGSIZE(-1)),
|
|
m_hwndInfotip(NULL),
|
|
m_idTitle(0),
|
|
m_idIcon(0),
|
|
m_iDragState(DRAG_IDLE)
|
|
{
|
|
TraceMsg(TF_LIFE, "CLinkElement::CLinkElement, this = 0x%x", this);
|
|
|
|
SetRect(&m_rcDragBegin, 0, 0, 0, 0);
|
|
}
|
|
|
|
|
|
|
|
CLinkElement::~CLinkElement(
|
|
void
|
|
)
|
|
{
|
|
TraceMsg(TF_LIFE, "CLinkElement::~CLinkElement, this = 0x%x", this);
|
|
_Destroy();
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// This is called by the DUI engine when the link element is
|
|
// created.
|
|
//
|
|
HRESULT
|
|
CLinkElement::Create( // [static]
|
|
DUI::Element **ppElement
|
|
)
|
|
{
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
CLinkElement *ple = DUI::HNewAndZero<CLinkElement>();
|
|
if (NULL != ple)
|
|
{
|
|
hr = ple->_Initialize();
|
|
if (FAILED(hr))
|
|
{
|
|
ple->Destroy();
|
|
ple = NULL;
|
|
}
|
|
}
|
|
*ppElement = ple;
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
//
|
|
// This is called by the Control Panel UI code creating
|
|
// the link element.
|
|
//
|
|
HRESULT
|
|
CLinkElement::Initialize(
|
|
IUICommand *pUiCommand,
|
|
eCPIMGSIZE eIconSize
|
|
)
|
|
{
|
|
ASSERT(NULL == m_pUiCommand);
|
|
ASSERT(NULL != pUiCommand);
|
|
|
|
(m_pUiCommand = pUiCommand)->AddRef();
|
|
|
|
m_eIconSize = eIconSize;
|
|
|
|
HRESULT hr = _CreateElementTitle();
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// We don't fail element creation if the icon
|
|
// cannot be created. We want to display the
|
|
// title without an icon so that we know there's
|
|
// a problem retrieving the icon.
|
|
//
|
|
THR(_CreateElementIcon());
|
|
}
|
|
|
|
//
|
|
// Note that we don't fail element creation if accessibility
|
|
// initialization fails.
|
|
//
|
|
THR(_InitializeAccessibility());
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
ATOMICRELEASE(m_pUiCommand);
|
|
}
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
HRESULT
|
|
CLinkElement::_InitializeAccessibility(
|
|
void
|
|
)
|
|
{
|
|
HRESULT hr = THR(SetAccessible(true));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = THR(SetAccRole(ROLE_SYSTEM_LINK));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LPWSTR pszTitle;
|
|
hr = THR(_GetTitleText(&pszTitle));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = THR(SetAccName(pszTitle));
|
|
CoTaskMemFree(pszTitle);
|
|
pszTitle = NULL;
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LPWSTR pszInfotip;
|
|
hr = THR(_GetInfotipText(&pszInfotip));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = THR(SetAccDesc(pszInfotip));
|
|
CoTaskMemFree(pszInfotip);
|
|
pszInfotip = NULL;
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
TCHAR szDefAction[80];
|
|
if (0 < LoadString(HINST_THISDLL,
|
|
IDS_CP_LINK_ACCDEFACTION,
|
|
szDefAction,
|
|
ARRAYSIZE(szDefAction)))
|
|
{
|
|
hr = THR(SetAccDefAction(szDefAction));
|
|
}
|
|
else
|
|
{
|
|
hr = THR(ResultFromLastError());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
void
|
|
CLinkElement::OnDestroy(
|
|
void
|
|
)
|
|
{
|
|
_Destroy();
|
|
DUI::Button::OnDestroy();
|
|
}
|
|
|
|
|
|
void
|
|
CLinkElement::OnInput(
|
|
DUI::InputEvent *pev
|
|
)
|
|
{
|
|
if (GINPUT_MOUSE == pev->nDevice)
|
|
{
|
|
//
|
|
// Use a set of states to control our handling of
|
|
// the mouse inputs for drag/drop.
|
|
//
|
|
// DRAG_IDLE - We have not yet detected any drag activity.
|
|
// DRAG_HITTESTING - Waiting to see if user drags cursor a minimum distance.
|
|
// DRAG_DRAGGING - User did drag cursor a minimum distance and we're now
|
|
// inside the drag loop.
|
|
//
|
|
//
|
|
// START -+-> DRAG_IDLE --> [ GMOUSE_DRAG ] --> DRAG_HITTESTING --+
|
|
// | |
|
|
// | [ GMOUSE_DRAG + |
|
|
// | moved SM_CXDRAG |
|
|
// | or SM_CYDRAG ] |
|
|
// | |
|
|
// +-<--------------- [ GMOUSE_UP ] <--- DRAG_DRAGGING <---+
|
|
//
|
|
DUI::MouseEvent *pmev = (DUI::MouseEvent *)pev;
|
|
switch(pev->nCode)
|
|
{
|
|
case GMOUSE_UP:
|
|
m_iDragState = DRAG_IDLE;
|
|
break;
|
|
|
|
case GMOUSE_DRAG:
|
|
switch(m_iDragState)
|
|
{
|
|
case DRAG_IDLE:
|
|
{
|
|
//
|
|
// This is the same way comctl's listview calculates
|
|
// the begin-drag rect.
|
|
//
|
|
int dxClickRect = GetSystemMetrics(SM_CXDRAG);
|
|
int dyClickRect = GetSystemMetrics(SM_CYDRAG);
|
|
|
|
if (4 > dxClickRect)
|
|
{
|
|
dxClickRect = dyClickRect = 4;
|
|
}
|
|
|
|
//
|
|
// Remember where the mouse pointer is on our first
|
|
// indication that a drag operation is starting.
|
|
//
|
|
SetRect(&m_rcDragBegin,
|
|
pmev->ptClientPxl.x - dxClickRect,
|
|
pmev->ptClientPxl.y - dyClickRect,
|
|
pmev->ptClientPxl.x + dxClickRect,
|
|
pmev->ptClientPxl.y + dyClickRect);
|
|
|
|
m_iDragState = DRAG_HITTESTING;
|
|
break;
|
|
}
|
|
|
|
case DRAG_HITTESTING:
|
|
if (!PtInRect(&m_rcDragBegin, pmev->ptClientPxl))
|
|
{
|
|
//
|
|
// Begin the drag/drop operation only if we've moved the mouse
|
|
// outside the "drag begin" rectangle. This prevents us from
|
|
// confusing a normal click with a drag/drop operation.
|
|
//
|
|
m_iDragState = DRAG_DRAGGING;
|
|
//
|
|
// Position the drag point at the middle of the item's image.
|
|
//
|
|
UINT cxIcon = 32;
|
|
UINT cyIcon = 32;
|
|
CPL::ImageDimensionsFromDesiredSize(m_eIconSize, &cxIcon, &cyIcon);
|
|
|
|
_BeginDrag(cxIcon / 2, cyIcon / 2);
|
|
}
|
|
break;
|
|
|
|
case DRAG_DRAGGING:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
Button::OnInput(pev);
|
|
}
|
|
|
|
|
|
void
|
|
CLinkElement::OnEvent(
|
|
DUI::Event *pev
|
|
)
|
|
{
|
|
if (DUI::Button::Click == pev->uidType)
|
|
{
|
|
pev->fHandled = true;
|
|
|
|
DUI::ButtonClickEvent * pbe = (DUI::ButtonClickEvent *) pev;
|
|
if (1 != pbe->nCount)
|
|
{
|
|
return; // ingore additional clicks - don't forward.
|
|
}
|
|
_OnSelected();
|
|
}
|
|
else if (DUI::Button::Context == pev->uidType)
|
|
{
|
|
DUI::ButtonContextEvent *peButton = reinterpret_cast<DUI::ButtonContextEvent *>(pev);
|
|
_OnContextMenu(peButton);
|
|
pev->fHandled = true;
|
|
}
|
|
Button::OnEvent(pev);
|
|
}
|
|
|
|
|
|
void
|
|
CLinkElement::OnPropertyChanged(
|
|
DUI::PropertyInfo *ppi,
|
|
int iIndex,
|
|
DUI::Value *pvOld,
|
|
DUI::Value *pvNew
|
|
)
|
|
{
|
|
//
|
|
// Don't trace this function. It's called very often.
|
|
//
|
|
// Perform default processing.
|
|
//
|
|
Button::OnPropertyChanged(ppi, iIndex, pvOld, pvNew);
|
|
|
|
if (IsProp(MouseWithin))
|
|
{
|
|
_OnMouseOver(pvNew);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Called to begin a drag-drop operation from the control panel.
|
|
// This is used for dragging CPL applet icons to shell folders
|
|
// for shortcut creation.
|
|
//
|
|
HRESULT
|
|
CLinkElement::_BeginDrag(
|
|
int iClickPosX,
|
|
int iClickPosY
|
|
)
|
|
{
|
|
DBG_ENTER(FTF_CPANEL, "CLinkElement::_BeginDrag");
|
|
|
|
HRESULT hr = E_FAIL;
|
|
HRESULT hrCoInit = SHCoInitialize();
|
|
if (SUCCEEDED(hrCoInit))
|
|
{
|
|
hr = hrCoInit;
|
|
|
|
IDataObject *pdtobj;
|
|
hr = _GetDragDropData(&pdtobj);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// Ignore any failure to set the drag image. Drag images
|
|
// are not supported on some video configurations.
|
|
// In these cases, we still want to be able to create a shortcut.
|
|
//
|
|
THR(_SetDragImage(pdtobj, iClickPosX, iClickPosY));
|
|
|
|
HWND hwndRoot;
|
|
hr = THR(Dui_GetElementRootHWND(this, &hwndRoot));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
DWORD dwEffect = DROPEFFECT_LINK;
|
|
hr = THR(SHDoDragDrop(hwndRoot, pdtobj, NULL, dwEffect, &dwEffect));
|
|
}
|
|
pdtobj->Release();
|
|
}
|
|
SHCoUninitialize(hrCoInit);
|
|
}
|
|
DBG_EXIT_HRES(FTF_CPANEL, "CLinkElement::_BeginDrag", hr);
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
//
|
|
// Get and prepare the data object used in a drag-drop operation.
|
|
// The returned data object is suitable for use by SHDoDragDrop.
|
|
//
|
|
HRESULT
|
|
CLinkElement::_GetDragDropData(
|
|
IDataObject **ppdtobj
|
|
)
|
|
{
|
|
DBG_ENTER(FTF_CPANEL, "CLinkElement::_GetDragDropData");
|
|
ASSERT(NULL != ppdtobj);
|
|
ASSERT(!IsBadWritePtr(ppdtobj, sizeof(*ppdtobj)));
|
|
ASSERT(NULL != m_pUiCommand);
|
|
|
|
*ppdtobj = NULL;
|
|
|
|
ICpUiCommand *puic;
|
|
HRESULT hr = m_pUiCommand->QueryInterface(IID_PPV_ARG(ICpUiCommand, &puic));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// Note that this call will fail with E_NOTIMPL for links that don't
|
|
// provide drag-drop data. Only CPL applet links provide data.
|
|
// This is how we limit drag-drop to only CPL applets.
|
|
//
|
|
IDataObject *pdtobj;
|
|
hr = THR(puic->GetDataObject(&pdtobj));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _SetPreferredDropEffect(pdtobj, DROPEFFECT_LINK);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
(*ppdtobj = pdtobj)->AddRef();
|
|
}
|
|
pdtobj->Release();
|
|
}
|
|
puic->Release();
|
|
}
|
|
DBG_EXIT_HRES(FTF_CPANEL, "CLinkElement::_GetDragDropData", hr);
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
HRESULT
|
|
CLinkElement::_SetPreferredDropEffect(
|
|
IDataObject *pdtobj,
|
|
DWORD dwEffect
|
|
)
|
|
{
|
|
DBG_ENTER(FTF_CPANEL, "CLinkElement::_SetPreferredDropEffect");
|
|
|
|
HRESULT hr = S_OK;
|
|
static CLIPFORMAT cf;
|
|
if ((CLIPFORMAT)0 == cf)
|
|
{
|
|
cf = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
|
|
if ((CLIPFORMAT)0 == cf)
|
|
{
|
|
hr = THR(ResultFromLastError());
|
|
}
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = THR(DataObj_SetDWORD(pdtobj, cf, dwEffect));
|
|
}
|
|
|
|
DBG_EXIT_HRES(FTF_CPANEL, "CLinkElement::_SetPreferredDropEffect", hr);
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
//
|
|
// Set up the drag image in the data object so that our icon is
|
|
// displayed during the drag operation.
|
|
//
|
|
// I took this code from the old webvw project's fldricon.cpp
|
|
// implementation (shell\ext\webvw\fldricon.cpp). It seems to
|
|
// work just fine.
|
|
//
|
|
HRESULT
|
|
CLinkElement::_SetDragImage(
|
|
IDataObject *pdtobj,
|
|
int iClickPosX,
|
|
int iClickPosY
|
|
)
|
|
{
|
|
DBG_ENTER(FTF_CPANEL, "CLinkElement::_SetDragImage");
|
|
|
|
ASSERT(NULL != pdtobj);
|
|
|
|
HRESULT hr = S_OK;
|
|
HDC hdc = CreateCompatibleDC(NULL);
|
|
if (NULL == hdc)
|
|
{
|
|
hr = THR(ResultFromLastError());
|
|
}
|
|
else
|
|
{
|
|
HBITMAP hbm;
|
|
LONG lBitmapWidth;
|
|
LONG lBitmapHeight;
|
|
hr = _GetDragImageBitmap(&hbm, &lBitmapWidth, &lBitmapHeight);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
IDragSourceHelper *pdsh;
|
|
hr = CoCreateInstance(CLSID_DragDropHelper,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_PPV_ARG(IDragSourceHelper, &pdsh));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
BITMAPINFOHEADER bmi = {0};
|
|
BITMAP bm = {0};
|
|
UINT uBufferOffset = 0;
|
|
//
|
|
// This is a screwy procedure to use GetDIBits.
|
|
// See knowledge base Q80080
|
|
//
|
|
if (GetObject(hbm, sizeof(BITMAP), &bm))
|
|
{
|
|
bmi.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.biWidth = bm.bmWidth;
|
|
bmi.biHeight = bm.bmHeight;
|
|
bmi.biPlanes = 1;
|
|
bmi.biBitCount = bm.bmPlanes * bm.bmBitsPixel;
|
|
//
|
|
// This needs to be one of these 4 values
|
|
//
|
|
if (bmi.biBitCount <= 1)
|
|
bmi.biBitCount = 1;
|
|
else if (bmi.biBitCount <= 4)
|
|
bmi.biBitCount = 4;
|
|
else if (bmi.biBitCount <= 8)
|
|
bmi.biBitCount = 8;
|
|
else
|
|
bmi.biBitCount = 24;
|
|
|
|
bmi.biCompression = BI_RGB;
|
|
//
|
|
// Total size of buffer for info struct and color table
|
|
//
|
|
uBufferOffset = sizeof(BITMAPINFOHEADER) +
|
|
((bmi.biBitCount == 24) ? 0 : ((1 << bmi.biBitCount) * sizeof(RGBQUAD)));
|
|
//
|
|
// Buffer for bitmap bits, so we can copy them.
|
|
//
|
|
BYTE *psBits = (BYTE *)SHAlloc(uBufferOffset);
|
|
|
|
if (NULL == psBits)
|
|
{
|
|
hr = THR(E_OUTOFMEMORY);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Put bmi into the memory block
|
|
//
|
|
CopyMemory(psBits, &bmi, sizeof(BITMAPINFOHEADER));
|
|
//
|
|
// Get the size of the buffer needed for bitmap bits
|
|
//
|
|
if (!GetDIBits(hdc, hbm, 0, 0, NULL, (BITMAPINFO *) psBits, DIB_RGB_COLORS))
|
|
{
|
|
hr = THR(ResultFromLastError());
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Realloc our buffer to be big enough
|
|
//
|
|
psBits = (BYTE *)SHRealloc(psBits, uBufferOffset + ((BITMAPINFOHEADER *) psBits)->biSizeImage);
|
|
|
|
if (NULL == psBits)
|
|
{
|
|
hr = THR(E_OUTOFMEMORY);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Fill the buffer
|
|
//
|
|
if (!GetDIBits(hdc,
|
|
hbm,
|
|
0,
|
|
bmi.biHeight,
|
|
(void *)(psBits + uBufferOffset),
|
|
(BITMAPINFO *)psBits,
|
|
DIB_RGB_COLORS))
|
|
{
|
|
hr = THR(ResultFromLastError());
|
|
}
|
|
else
|
|
{
|
|
SHDRAGIMAGE shdi; // Drag images struct
|
|
|
|
shdi.hbmpDragImage = CreateBitmapIndirect(&bm);
|
|
if (NULL == shdi.hbmpDragImage)
|
|
{
|
|
hr = THR(ResultFromLastError());
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Set the drag image bitmap
|
|
//
|
|
if (SetDIBits(hdc,
|
|
shdi.hbmpDragImage,
|
|
0,
|
|
lBitmapHeight,
|
|
(void *)(psBits + uBufferOffset),
|
|
(BITMAPINFO *)psBits,
|
|
DIB_RGB_COLORS))
|
|
{
|
|
//
|
|
// Populate the drag image structure
|
|
//
|
|
shdi.sizeDragImage.cx = lBitmapWidth;
|
|
shdi.sizeDragImage.cy = lBitmapHeight;
|
|
shdi.ptOffset.x = iClickPosX;
|
|
shdi.ptOffset.y = iClickPosY;
|
|
shdi.crColorKey = 0;
|
|
//
|
|
// Set the drag image
|
|
//
|
|
hr = pdsh->InitializeFromBitmap(&shdi, pdtobj);
|
|
}
|
|
else
|
|
{
|
|
hr = THR(ResultFromLastError());
|
|
}
|
|
if (FAILED(hr))
|
|
{
|
|
DeleteObject(shdi.hbmpDragImage);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (NULL != psBits)
|
|
{
|
|
SHFree(psBits);
|
|
}
|
|
}
|
|
}
|
|
pdsh->Release();
|
|
}
|
|
DeleteObject(hbm);
|
|
}
|
|
DeleteDC(hdc);
|
|
}
|
|
DBG_EXIT_HRES(FTF_CPANEL, "CLinkElement::_SetDragImage", hr);
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
HRESULT
|
|
CLinkElement::_GetDragImageBitmap(
|
|
HBITMAP *phbm,
|
|
LONG *plWidth,
|
|
LONG *plHeight
|
|
)
|
|
{
|
|
DBG_ENTER(FTF_CPANEL, "CLinkElement::_GetDragImageBitmap");
|
|
|
|
ASSERT(NULL != phbm);
|
|
ASSERT(!IsBadWritePtr(phbm, sizeof(*phbm)));
|
|
ASSERT(NULL != plWidth);
|
|
ASSERT(!IsBadWritePtr(plWidth, sizeof(*plWidth)));
|
|
ASSERT(NULL != plHeight);
|
|
ASSERT(!IsBadWritePtr(plHeight, sizeof(*plHeight)));
|
|
|
|
*phbm = NULL;
|
|
*plWidth = 0;
|
|
*plHeight = 0;
|
|
|
|
HICON hIcon;
|
|
HRESULT hr = _GetElementIcon(&hIcon);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ICONINFO iconinfo;
|
|
|
|
if (GetIconInfo(hIcon, &iconinfo))
|
|
{
|
|
BITMAP bm;
|
|
if (GetObject(iconinfo.hbmColor, sizeof(bm), &bm))
|
|
{
|
|
*plWidth = bm.bmWidth;
|
|
*plHeight = bm.bmHeight;
|
|
*phbm = iconinfo.hbmColor;
|
|
}
|
|
else
|
|
{
|
|
DeleteObject(iconinfo.hbmColor);
|
|
hr = THR(ResultFromLastError());
|
|
}
|
|
DeleteObject(iconinfo.hbmMask);
|
|
}
|
|
else
|
|
{
|
|
hr = THR(ResultFromLastError());
|
|
}
|
|
DestroyIcon(hIcon);
|
|
}
|
|
DBG_EXIT_HRES(FTF_CPANEL, "CLinkElement::_GetDragImageBitmap", hr);
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
|
|
HRESULT
|
|
CLinkElement::_Initialize(
|
|
void
|
|
)
|
|
{
|
|
HRESULT hr = Button::Initialize(AE_Mouse | AE_Keyboard);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _AddOrDeleteAtoms(true);
|
|
}
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
void
|
|
CLinkElement::_Destroy(
|
|
void
|
|
)
|
|
{
|
|
if (NULL != m_hwndInfotip && IsWindow(m_hwndInfotip))
|
|
{
|
|
SHDestroyInfotipWindow(&m_hwndInfotip);
|
|
}
|
|
|
|
ATOMICRELEASE(m_pUiCommand);
|
|
|
|
_AddOrDeleteAtoms(false);
|
|
}
|
|
|
|
|
|
HRESULT
|
|
CLinkElement::_AddOrDeleteAtoms(
|
|
bool bAdd
|
|
)
|
|
{
|
|
struct CPL::ATOMINFO rgAtomInfo[] = {
|
|
{ L"title", &m_idTitle },
|
|
{ L"icon", &m_idIcon },
|
|
};
|
|
|
|
HRESULT hr = Dui_AddOrDeleteAtoms(rgAtomInfo, ARRAYSIZE(rgAtomInfo), bAdd);
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
|
|
HRESULT
|
|
CLinkElement::_CreateElementTitle(
|
|
void
|
|
)
|
|
{
|
|
LPWSTR pszTitle;
|
|
HRESULT hr = _GetTitleText(&pszTitle);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = Dui_SetDescendentElementText(this, L"title", pszTitle);
|
|
CoTaskMemFree(pszTitle);
|
|
}
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
|
|
HRESULT
|
|
CLinkElement::_CreateElementIcon(
|
|
void
|
|
)
|
|
{
|
|
HICON hIcon;
|
|
HRESULT hr = _GetElementIcon(&hIcon);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = Dui_SetDescendentElementIcon(this, L"icon", hIcon);
|
|
if (FAILED(hr))
|
|
{
|
|
DestroyIcon(hIcon);
|
|
}
|
|
}
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
HRESULT
|
|
CLinkElement::_GetElementIcon(
|
|
HICON *phIcon
|
|
)
|
|
{
|
|
ASSERT(NULL != phIcon);
|
|
ASSERT(!IsBadWritePtr(phIcon, sizeof(*phIcon)));
|
|
ASSERT(NULL != m_pUiCommand);
|
|
|
|
*phIcon = NULL;
|
|
|
|
ICpUiElementInfo *pei;
|
|
HRESULT hr = m_pUiCommand->QueryInterface(IID_PPV_ARG(ICpUiElementInfo, &pei));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pei->LoadIcon(m_eIconSize, phIcon);
|
|
pei->Release();
|
|
}
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
HRESULT
|
|
CLinkElement::_OnContextMenu(
|
|
DUI::ButtonContextEvent *peButton
|
|
)
|
|
{
|
|
DBG_ENTER(FTF_CPANEL, "CLinkElement::_OnContextMenu");
|
|
|
|
ICpUiCommand *pcmd;
|
|
HRESULT hr = m_pUiCommand->QueryInterface(IID_PPV_ARG(ICpUiCommand, &pcmd));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
HWND hwndRoot;
|
|
hr = Dui_GetElementRootHWND(this, &hwndRoot);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (-1 == peButton->pt.x)
|
|
{
|
|
//
|
|
// Keyboard context menu.
|
|
//
|
|
SIZE size;
|
|
hr = Dui_GetElementExtent(this, &size);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
peButton->pt.x = size.cx / 2;
|
|
peButton->pt.y = size.cy / 2;
|
|
}
|
|
}
|
|
POINT pt;
|
|
hr = Dui_MapElementPointToRootHWND(this, peButton->pt, &pt);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (ClientToScreen(hwndRoot, &pt))
|
|
{
|
|
//
|
|
// InvokeContextMenu returns S_FALSE if the command doesn't
|
|
// provide a context menu.
|
|
//
|
|
hr = pcmd->InvokeContextMenu(hwndRoot, &pt);
|
|
}
|
|
else
|
|
{
|
|
hr = CPL::ResultFromLastError();
|
|
}
|
|
}
|
|
}
|
|
pcmd->Release();
|
|
}
|
|
else if (E_NOINTERFACE == hr)
|
|
{
|
|
hr = S_FALSE;
|
|
}
|
|
|
|
DBG_EXIT_HRES(FTF_CPANEL, "CLinkElement::_OnContextMenu", hr);
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
|
|
HRESULT
|
|
CLinkElement::_OnSelected(
|
|
void
|
|
)
|
|
{
|
|
ASSERT(NULL != m_pUiCommand);
|
|
|
|
//
|
|
// Delay navigation until double-click time times out occurs.
|
|
//
|
|
// KB: gpease 05-APR-2001 Fix for Whistler Bug #338552 (and others)
|
|
//
|
|
// Delaying this prevents the "second click" from being applied
|
|
// to the newly navigated frame. Previously, if there happen to
|
|
// be a new link in the new frame at the same mouse point at
|
|
// which the previous navigation occured, the new link would have
|
|
// received the 2nd click and we'd navigate that link as well. This
|
|
// causes the current frame to get the 2nd click which we ignore
|
|
// since we only care about the single click (see OnEvent above).
|
|
//
|
|
|
|
HWND hwndRoot;
|
|
HRESULT hr = Dui_GetElementRootHWND(this, &hwndRoot);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
SendMessage(hwndRoot, WM_USER_DELAY_NAVIGATION, (WPARAM) NULL, (LPARAM) m_pUiCommand);
|
|
}
|
|
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CLinkElement::_OnMouseOver(
|
|
DUI::Value *pvNewMouseWithin
|
|
)
|
|
{
|
|
_ShowInfotipWindow(pvNewMouseWithin->GetBool());
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Retrieve the title text for the element.
|
|
// Caller must free returned buffer using CoTaskMemFree.
|
|
//
|
|
HRESULT
|
|
CLinkElement::_GetTitleText(
|
|
LPWSTR *ppszTitle
|
|
)
|
|
{
|
|
ASSERT(NULL != m_pUiCommand);
|
|
ASSERT(NULL != ppszTitle);
|
|
ASSERT(!IsBadWritePtr(ppszTitle, sizeof(*ppszTitle)));
|
|
|
|
*ppszTitle = NULL;
|
|
|
|
ICpUiElementInfo *pei;
|
|
HRESULT hr = m_pUiCommand->QueryInterface(IID_PPV_ARG(ICpUiElementInfo, &pei));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pei->LoadName(ppszTitle);
|
|
pei->Release();
|
|
}
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
//
|
|
// Retrieve the infotip text for the element.
|
|
// Caller must free returned buffer using CoTaskMemFree.
|
|
//
|
|
HRESULT
|
|
CLinkElement::_GetInfotipText(
|
|
LPWSTR *ppszInfotip
|
|
)
|
|
{
|
|
ASSERT(NULL != m_pUiCommand);
|
|
ASSERT(NULL != ppszInfotip);
|
|
ASSERT(!IsBadWritePtr(ppszInfotip, sizeof(*ppszInfotip)));
|
|
|
|
*ppszInfotip = NULL;
|
|
|
|
ICpUiElementInfo *pei;
|
|
HRESULT hr = m_pUiCommand->QueryInterface(IID_PPV_ARG(ICpUiElementInfo, &pei));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pei->LoadTooltip(ppszInfotip);
|
|
pei->Release();
|
|
}
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
|
|
HRESULT
|
|
CLinkElement::_ShowInfotipWindow(
|
|
bool bShow
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
if (bShow)
|
|
{
|
|
if (NULL == m_hwndInfotip)
|
|
{
|
|
HWND hwndRoot;
|
|
hr = THR(Dui_GetElementRootHWND(this, &hwndRoot));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LPWSTR pszInfotip;
|
|
hr = THR(_GetInfotipText(&pszInfotip));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = THR(SHCreateInfotipWindow(hwndRoot, pszInfotip, &m_hwndInfotip));
|
|
CoTaskMemFree(pszInfotip);
|
|
}
|
|
}
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = THR(SHShowInfotipWindow(m_hwndInfotip, TRUE));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (NULL != m_hwndInfotip)
|
|
{
|
|
hr = THR(SHDestroyInfotipWindow(&m_hwndInfotip));
|
|
}
|
|
}
|
|
return THR(hr);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// ClassInfo (must appear after property definitions).
|
|
//
|
|
DUI::IClassInfo *CLinkElement::Class = NULL;
|
|
HRESULT CLinkElement::Register()
|
|
{
|
|
return DUI::ClassInfo<CLinkElement,DUI::Button>::Register(L"linkelement", NULL, 0);
|
|
}
|
|
|