934 lines
25 KiB
C++
934 lines
25 KiB
C++
#include "stdafx.h"
|
||
#include "Lava.h"
|
||
#include "Spy.h"
|
||
|
||
#include "..\DUser\resource.h"
|
||
|
||
#if DBG
|
||
|
||
const UINT IDC_GADGETTREE = 1;
|
||
const int cxValue = 80;
|
||
const int cxBorder = 5;
|
||
const int cyBorder = 5;
|
||
|
||
/***************************************************************************\
|
||
*****************************************************************************
|
||
*
|
||
* class Spy
|
||
*
|
||
*****************************************************************************
|
||
\***************************************************************************/
|
||
|
||
PRID Spy::s_pridLink = 0;
|
||
ATOM Spy::s_atom = NULL;
|
||
HFONT Spy::s_hfntDesc = NULL;
|
||
HFONT Spy::s_hfntDescBold = NULL;
|
||
HBRUSH Spy::s_hbrOutline = NULL;
|
||
int Spy::s_cyLinePxl = 0;
|
||
DWORD Spy::g_tlsSpy = (DWORD) -1;
|
||
CritLock Spy::s_lockList;
|
||
GList<Spy> Spy::s_lstSpys;
|
||
|
||
static const GUID guidLink = { 0xd5818900, 0xaf18, 0x4c98, { 0x87, 0x20, 0x5a, 0x32, 0x47, 0xa3, 0x1, 0x78 } }; // {D5818900-AF18-4c98-8720-5A3247A30178}
|
||
|
||
//------------------------------------------------------------------------------
|
||
Spy::Spy()
|
||
{
|
||
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
Spy::~Spy()
|
||
{
|
||
s_lockList.Enter();
|
||
s_lstSpys.Unlink(this);
|
||
s_lockList.Leave();
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
BOOL
|
||
Spy::BuildSpy(HWND hwndParent, HGADGET hgadRoot, HGADGET hgadSelect)
|
||
{
|
||
BOOL fSuccess = FALSE;
|
||
Spy * pSpy, * pSpyCur;
|
||
|
||
s_lockList.Enter();
|
||
|
||
|
||
//
|
||
// Perform first-time initialization for Spy
|
||
//
|
||
|
||
if (g_tlsSpy == -1) {
|
||
//
|
||
// Allocate a TLS slot for Spy. This is DEBUG only, so we don't worry about
|
||
// the extra cost. However, if this ever becomes on in RETAIL, we need to
|
||
// create a SubTread for Lava and add a Spy slot.
|
||
//
|
||
|
||
g_tlsSpy = TlsAlloc();
|
||
if (g_tlsSpy == -1) {
|
||
goto Exit;
|
||
}
|
||
|
||
|
||
//
|
||
// Initialize CommCtrl.
|
||
//
|
||
|
||
INITCOMMONCONTROLSEX icc;
|
||
icc.dwSize = sizeof(icc);
|
||
icc.dwICC = ICC_TREEVIEW_CLASSES;
|
||
|
||
if (!InitCommonControlsEx(&icc)) {
|
||
goto Exit;
|
||
}
|
||
}
|
||
|
||
|
||
AssertMsg(::GetGadget(hgadRoot, GG_PARENT) == NULL, "Ensure Root Gadget");
|
||
|
||
//
|
||
// Each Gadget subtree can only be spied on once because there are
|
||
// back-pointers from each Gadget to the corresponding HTREEITEM's. Need to
|
||
// check if this Gadget subtree is already is being spied on.
|
||
//
|
||
|
||
pSpyCur = s_lstSpys.GetHead();
|
||
while (pSpyCur != NULL) {
|
||
if (pSpyCur->m_hgadRoot == hgadRoot) {
|
||
//
|
||
// Already exists, so don't open another Spy.
|
||
//
|
||
|
||
SetForegroundWindow(pSpyCur->m_hwnd);
|
||
goto Exit;
|
||
}
|
||
|
||
pSpyCur = pSpyCur->GetNext();
|
||
}
|
||
|
||
|
||
//
|
||
// Register a WNDCLASS to use
|
||
//
|
||
|
||
if (s_atom == NULL) {
|
||
WNDCLASSEX wcex;
|
||
|
||
ZeroMemory(&wcex, sizeof(wcex));
|
||
wcex.cbSize = sizeof(WNDCLASSEX);
|
||
|
||
wcex.style = CS_HREDRAW | CS_VREDRAW;
|
||
wcex.lpfnWndProc = RawSpyWndProc;
|
||
wcex.hInstance = g_hDll;
|
||
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||
wcex.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
|
||
wcex.lpszClassName = "GadgetSpy (Inside)";
|
||
|
||
s_atom = RegisterClassEx(&wcex);
|
||
if (s_atom == NULL) {
|
||
goto Exit;
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Create GDI objects used in painting
|
||
//
|
||
|
||
if (s_hfntDesc == NULL) {
|
||
s_hfntDesc = UtilBuildFont(L"Tahoma", 85, FS_NORMAL, NULL);
|
||
|
||
HDC hdc = GetGdiCache()->GetCompatibleDC();
|
||
HFONT hfntOld = (HFONT) SelectObject(hdc, s_hfntDesc);
|
||
|
||
TEXTMETRIC tm;
|
||
GetTextMetrics(hdc, &tm);
|
||
s_cyLinePxl = tm.tmHeight;
|
||
|
||
SelectObject(hdc, hfntOld);
|
||
GetGdiCache()->ReleaseCompatibleDC(hdc);
|
||
}
|
||
|
||
if (s_hfntDescBold == NULL) {
|
||
s_hfntDescBold = UtilBuildFont(L"Tahoma", 85, FS_BOLD, NULL);
|
||
}
|
||
|
||
if (s_hbrOutline == NULL) {
|
||
s_hbrOutline = CreateSolidBrush(GetSysColor(COLOR_3DSHADOW));
|
||
}
|
||
|
||
if (s_pridLink == 0) {
|
||
s_pridLink = RegisterGadgetProperty(&guidLink);
|
||
}
|
||
|
||
|
||
//
|
||
// Create a new Spy instance and HWND
|
||
//
|
||
|
||
pSpy = ProcessNew(Spy);
|
||
if (pSpy == NULL) {
|
||
goto Exit;
|
||
}
|
||
|
||
pSpy->m_hgadMsg = CreateGadget(NULL, GC_MESSAGE, RawEventProc, pSpy);
|
||
pSpy->m_hgadRoot = hgadRoot;
|
||
Verify(TlsSetValue(g_tlsSpy, pSpy));
|
||
|
||
{
|
||
RECT rcParentPxl;
|
||
GetWindowRect(hwndParent, &rcParentPxl);
|
||
|
||
HWND hwndSpy = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_CLIENTEDGE,
|
||
(LPCTSTR) s_atom, NULL,
|
||
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, rcParentPxl.left + 20, rcParentPxl.top + 20,
|
||
300, 500, hwndParent, NULL, g_hDll, NULL);
|
||
if (hwndSpy == NULL) {
|
||
ProcessDelete(Spy, pSpy);
|
||
goto Exit;
|
||
}
|
||
|
||
pSpy->UpdateTitle();
|
||
|
||
ShowWindow(hwndSpy, SW_SHOW);
|
||
UpdateWindow(hwndSpy);
|
||
}
|
||
|
||
|
||
//
|
||
// Select the specified Gadget as a starting point. We want to check
|
||
// if this HGADGET is actually a valid child since it may have been
|
||
// "grabbed" at an earlier time and no longer be valid.
|
||
//
|
||
|
||
if (hgadSelect) {
|
||
CheckIsChildData cicd;
|
||
cicd.hgadCheck = hgadSelect;
|
||
cicd.fChild = FALSE;
|
||
|
||
Verify(EnumGadgets(hgadRoot, EnumCheckIsChild, &cicd, GENUM_DEEPCHILD));
|
||
|
||
if (cicd.fChild) {
|
||
HTREEITEM htiSelect;
|
||
if (GetGadgetProperty(hgadSelect, s_pridLink, (void **) &htiSelect)) {
|
||
AssertMsg(htiSelect != NULL, "Must have valid HTREEITEM");
|
||
if (!TreeView_SelectItem(pSpy->m_hwndTree, htiSelect)) {
|
||
Trace("SPY: Unable to select default Gadget\n");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
s_lstSpys.Add(pSpy);
|
||
|
||
fSuccess = TRUE;
|
||
Exit:
|
||
s_lockList.Leave();
|
||
|
||
return fSuccess;
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
BOOL CALLBACK
|
||
Spy::EnumCheckIsChild(HGADGET hgad, void * pvData)
|
||
{
|
||
CheckIsChildData * pcicd = (CheckIsChildData *) pvData;
|
||
|
||
if (hgad == pcicd->hgadCheck) {
|
||
pcicd->fChild = TRUE;
|
||
return FALSE; // No longer need to enumerate
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
void
|
||
Spy::UpdateTitle()
|
||
{
|
||
TCHAR szTitle[256];
|
||
wsprintf(szTitle, "Root HGADGET = 0x%p, %d Gadgets", m_hgadRoot, m_cItems);
|
||
|
||
SetWindowText(m_hwnd, szTitle);
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
LRESULT CALLBACK
|
||
Spy::RawSpyWndProc(HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
|
||
{
|
||
Spy * pSpy = (Spy *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
||
if (pSpy == NULL) {
|
||
//
|
||
// Creating a new Spy HWND, so hook up to the Spy object that was
|
||
// previously created.
|
||
//
|
||
|
||
pSpy = reinterpret_cast<Spy *> (TlsGetValue(g_tlsSpy));
|
||
AssertMsg(pSpy != NULL, "Ensure already created new Spy instance");
|
||
TlsSetValue(g_tlsSpy, NULL);
|
||
pSpy->m_hwnd = hwnd;
|
||
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) pSpy);
|
||
}
|
||
|
||
AssertMsg(pSpy->m_hwnd == hwnd, "Ensure HWND's match");
|
||
return pSpy->SpyWndProc(nMsg, wParam, lParam);
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
LRESULT
|
||
Spy::SpyWndProc(UINT nMsg, WPARAM wParam, LPARAM lParam)
|
||
{
|
||
switch (nMsg)
|
||
{
|
||
case WM_CREATE:
|
||
if (DefWindowProc(m_hwnd, nMsg, wParam, lParam) == -1) {
|
||
return -1;
|
||
}
|
||
|
||
//
|
||
// Setup the window
|
||
//
|
||
|
||
AssertMsg(m_hgadRoot != NULL, "Must already have specified Gadget");
|
||
|
||
RECT rcClient;
|
||
GetClientRect(m_hwnd, &rcClient);
|
||
|
||
m_hwndTree = CreateWindowEx(0, _T("SysTreeView32"),
|
||
NULL, WS_CHILD | WS_VISIBLE | TVS_HASBUTTONS | TVS_HASLINES | TVS_SHOWSELALWAYS,
|
||
0, 0, rcClient.right, rcClient.bottom, m_hwnd, (HMENU)((UINT_PTR)IDC_GADGETTREE), g_hDll, NULL);
|
||
|
||
m_hilc = ImageList_LoadImage(g_hDll, MAKEINTRESOURCE(IDB_SPYICON),
|
||
16, 1, RGB(128, 0, 128), IMAGE_BITMAP, LR_SHARED);
|
||
if ((m_hwndTree == NULL) || (m_hilc == NULL)) {
|
||
return -1;
|
||
}
|
||
|
||
TreeView_SetImageList(m_hwndTree, m_hilc, TVSIL_NORMAL);
|
||
|
||
EnumData ed;
|
||
ed.pspy = this;
|
||
ed.htiParent = InsertTreeItem(TVI_ROOT, m_hgadRoot);
|
||
ed.nLevel = 1;
|
||
Verify(EnumGadgets(m_hgadRoot, EnumAddList, &ed, GENUM_SHALLOWCHILD));
|
||
m_fValid = TRUE;
|
||
|
||
TreeView_Expand(m_hwndTree, ed.htiParent, TVE_EXPAND);
|
||
m_hgadDetails = m_hgadRoot;
|
||
UpdateDetails();
|
||
|
||
break;
|
||
|
||
case WM_DESTROY:
|
||
SelectGadget(NULL);
|
||
::DeleteHandle(m_hgadMsg);
|
||
break;
|
||
|
||
case WM_NCDESTROY:
|
||
ProcessDelete(Spy, this);
|
||
goto CallDWP;
|
||
|
||
case WM_SIZE:
|
||
if ((wParam == SIZE_MAXIMIZED) || (wParam == SIZE_RESTORED)) {
|
||
UpdateLayout();
|
||
}
|
||
break;
|
||
|
||
case WM_PAINT:
|
||
{
|
||
PAINTSTRUCT ps;
|
||
HDC hdc = BeginPaint(m_hwnd, &ps);
|
||
OnPaint(hdc);
|
||
EndPaint(m_hwnd, &ps);
|
||
}
|
||
break;
|
||
|
||
case WM_NOTIFY:
|
||
if (wParam == IDC_GADGETTREE) {
|
||
NMHDR * pnm = (NMHDR *) lParam;
|
||
if (pnm->code == TVN_SELCHANGED) {
|
||
NMTREEVIEW * pnmtv = (NMTREEVIEW *) lParam;
|
||
if (m_fValid) {
|
||
SelectGadget(GetGadget(pnmtv->itemNew.hItem));
|
||
}
|
||
break;
|
||
} else if (pnm->code == TVN_KEYDOWN) {
|
||
NMTVKEYDOWN * pnmtvkd = (NMTVKEYDOWN *) lParam;
|
||
if (pnmtvkd->wVKey == VK_APPS) {
|
||
DisplayContextMenu(TRUE);
|
||
}
|
||
} else if (pnm->code == NM_RCLICK) {
|
||
DisplayContextMenu(FALSE);
|
||
}
|
||
}
|
||
goto CallDWP;
|
||
|
||
default:
|
||
CallDWP:
|
||
return DefWindowProc(m_hwnd, nMsg, wParam, lParam);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
HRESULT CALLBACK
|
||
Spy::RawEventProc(HGADGET hgadCur, void * pvCur, EventMsg * pmsg)
|
||
{
|
||
UNREFERENCED_PARAMETER(hgadCur);
|
||
|
||
Spy * pSpy = (Spy *) pvCur;
|
||
return pSpy->EventProc(pmsg);
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
BOOL
|
||
IsDescendant(
|
||
HGADGET hgadParent,
|
||
HGADGET hgadChild)
|
||
{
|
||
AssertMsg(hgadParent != NULL, "Must have valid parent");
|
||
|
||
if (hgadChild == hgadParent) {
|
||
return TRUE;
|
||
} if (hgadChild == NULL) {
|
||
return FALSE;
|
||
} else {
|
||
return IsDescendant(hgadParent, ::GetGadget(hgadChild, GG_PARENT));
|
||
}
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
HRESULT
|
||
Spy::EventProc(EventMsg * pmsg)
|
||
{
|
||
switch (GET_EVENT_DEST(pmsg))
|
||
{
|
||
case GMF_DIRECT:
|
||
//
|
||
// Our Listener is being destroyed. We need to detach from everything.
|
||
//
|
||
|
||
if (m_hgadRoot != NULL) {
|
||
Trace("SPY: Destroying Spy MsgGadget\n");
|
||
Verify(EnumGadgets(m_hgadRoot, EnumRemoveLink, NULL, GENUM_DEEPCHILD));
|
||
m_hgadRoot = NULL;
|
||
}
|
||
break;
|
||
|
||
case GMF_EVENT:
|
||
switch (pmsg->nMsg)
|
||
{
|
||
case GM_DESTROY:
|
||
{
|
||
GMSG_DESTROY * pmsgD = (GMSG_DESTROY *) pmsg;
|
||
if (pmsgD->nCode == GDESTROY_START) {
|
||
//
|
||
// Gadget is being destroyed
|
||
//
|
||
|
||
Trace("SPY: Destroying Gadget 0x%p\n", pmsg->hgadMsg);
|
||
HTREEITEM hti;
|
||
if (GetGadgetProperty(pmsg->hgadMsg, s_pridLink, (void **) &hti)) {
|
||
AssertMsg(hti != NULL, "Must have valid HTREEITEM");
|
||
Verify(EnumGadgets(pmsg->hgadMsg, EnumRemoveLink, NULL, GENUM_DEEPCHILD));
|
||
TreeView_DeleteItem(m_hwndTree, hti);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
|
||
case GM_CHANGERECT:
|
||
if (IsDescendant(pmsg->hgadMsg, m_hgadDetails)) {
|
||
UpdateDetails();
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
return DU_S_NOTHANDLED;
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
BOOL CALLBACK
|
||
Spy::EnumAddList(HGADGET hgad, void * pvData)
|
||
{
|
||
EnumData * ped = (EnumData *) pvData;
|
||
Spy * pSpy = ped->pspy;
|
||
HTREEITEM htiNew = pSpy->InsertTreeItem(ped->htiParent, hgad);
|
||
|
||
pSpy->m_cItems++;
|
||
|
||
if (::GetGadget(hgad, GG_TOPCHILD) != NULL) {
|
||
EnumData ed;
|
||
ed.pspy = pSpy;
|
||
ed.htiParent = htiNew;
|
||
ed.nLevel = ped->nLevel + 1;
|
||
Verify(EnumGadgets(hgad, EnumAddList, &ed, GENUM_SHALLOWCHILD));
|
||
|
||
if (ped->nLevel <= 2) {
|
||
TreeView_Expand(pSpy->m_hwndTree, htiNew, TVE_EXPAND);
|
||
}
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
BOOL CALLBACK
|
||
Spy::EnumRemoveLink(HGADGET hgad, void * pvData)
|
||
{
|
||
UNREFERENCED_PARAMETER(pvData);
|
||
|
||
RemoveGadgetProperty(hgad, s_pridLink);
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
void
|
||
Spy::SelectGadget(HGADGET hgad)
|
||
{
|
||
m_hgadDetails = hgad;
|
||
|
||
{
|
||
//
|
||
// We are bypassinging the normal API's to directly call a
|
||
// DEBUG-only function. Need to lock the Context and do FULL handle
|
||
// validation.
|
||
//
|
||
|
||
ContextLock cl;
|
||
if (!cl.LockNL(ContextLock::edDefer)) {
|
||
return;
|
||
}
|
||
|
||
DuVisual * pgadTree = ValidateVisual(hgad);
|
||
DuVisual::DEBUG_SetOutline(pgadTree);
|
||
}
|
||
|
||
UpdateDetails();
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
HTREEITEM
|
||
Spy::InsertTreeItem(HTREEITEM htiParent, HGADGET hgad)
|
||
{
|
||
TCHAR szName[1024];
|
||
|
||
GMSG_QUERYDESC msg;
|
||
msg.cbSize = sizeof(msg);
|
||
msg.hgadMsg = hgad;
|
||
msg.nMsg = GM_QUERY;
|
||
msg.nCode = GQUERY_DESCRIPTION;
|
||
msg.szName[0] = '\0';
|
||
msg.szType[0] = '\0';
|
||
|
||
if (DUserSendEvent(&msg, 0) == DU_S_COMPLETE) {
|
||
if (msg.szName[0] != '\0') {
|
||
wsprintf(szName, "0x%p %S: \"%S\"", hgad, msg.szType, msg.szName);
|
||
} else {
|
||
wsprintf(szName, "0x%p %S", hgad, msg.szType);
|
||
}
|
||
} else {
|
||
wsprintf(szName, "0x%p", hgad);
|
||
}
|
||
|
||
TVINSERTSTRUCT tvis;
|
||
tvis.hParent = htiParent;
|
||
tvis.hInsertAfter = TVI_LAST;
|
||
tvis.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE;
|
||
tvis.item.pszText = szName;
|
||
tvis.item.iImage = iGadget;
|
||
tvis.item.lParam = (LPARAM) hgad;
|
||
|
||
HTREEITEM htiNew = TreeView_InsertItem(m_hwndTree, &tvis);
|
||
if (htiNew != NULL) {
|
||
if (AddGadgetMessageHandler(hgad, 0, m_hgadMsg)) {
|
||
Verify(SetGadgetProperty(hgad, s_pridLink, htiNew));
|
||
} else {
|
||
Trace("WARNING: Spy unable to attach handler on 0x%p\n", hgad);
|
||
}
|
||
}
|
||
return htiNew;
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
HGADGET
|
||
Spy::GetGadget(HTREEITEM hti)
|
||
{
|
||
TVITEM tvi;
|
||
tvi.hItem = hti;
|
||
tvi.mask = TVIF_PARAM | TVIF_HANDLE;
|
||
|
||
if (TreeView_GetItem(m_hwndTree, &tvi)) {
|
||
HGADGET hgadItem = (HGADGET) tvi.lParam;
|
||
AssertMsg(hgadItem != NULL, "All items in the tree should have a Gadget");
|
||
|
||
return hgadItem;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
void
|
||
Spy::DisplayContextMenu(BOOL fViaKbd)
|
||
{
|
||
//
|
||
// Locate TreeView item
|
||
//
|
||
|
||
POINT ptPopup;
|
||
ZeroMemory(&ptPopup, sizeof(ptPopup));
|
||
|
||
HTREEITEM hti;
|
||
|
||
if (fViaKbd) {
|
||
//
|
||
// Keyboard driven
|
||
//
|
||
|
||
hti = TreeView_GetSelection(m_hwndTree);
|
||
if (hti != NULL) {
|
||
RECT rc;
|
||
|
||
TreeView_GetItemRect(m_hwndTree, hti, &rc, TRUE);
|
||
|
||
ptPopup.x = rc.left;
|
||
ptPopup.y = rc.bottom;
|
||
ClientToScreen(m_hwndTree, &ptPopup);
|
||
}
|
||
} else {
|
||
//
|
||
// Mouse driven
|
||
//
|
||
|
||
TVHITTESTINFO tvht;
|
||
|
||
DWORD dwPos = GetMessagePos();
|
||
|
||
ptPopup.x = GET_X_LPARAM(dwPos);
|
||
ptPopup.y = GET_Y_LPARAM(dwPos);
|
||
|
||
tvht.pt = ptPopup;
|
||
ScreenToClient(m_hwndTree, &tvht.pt);
|
||
|
||
hti = TreeView_HitTest(m_hwndTree, &tvht);
|
||
}
|
||
|
||
|
||
//
|
||
// Now have tree item and popup position
|
||
//
|
||
|
||
if (hti != NULL) {
|
||
//
|
||
// Get Gadget associated with this item
|
||
//
|
||
|
||
HGADGET hgad = GetGadget(hti);
|
||
|
||
|
||
//
|
||
// Create popup menu template
|
||
//
|
||
|
||
HMENU hMenu = CreatePopupMenu();
|
||
if (hMenu != NULL) {
|
||
|
||
BOOL fRes;
|
||
|
||
const int cmdDetails = 10;
|
||
|
||
fRes = AppendMenu(hMenu, MF_STRING, cmdDetails, "Details...");
|
||
if (fRes) {
|
||
|
||
UINT nCmd = TrackPopupMenu(hMenu, TPM_RETURNCMD | TPM_RIGHTBUTTON, ptPopup.x, ptPopup.y, 0, m_hwndTree, NULL);
|
||
|
||
DestroyMenu(hMenu);
|
||
|
||
|
||
//
|
||
// Invoke commands
|
||
//
|
||
|
||
switch (nCmd)
|
||
{
|
||
case cmdDetails:
|
||
{
|
||
GMSG_QUERYDETAILS msg;
|
||
msg.cbSize = sizeof(msg);
|
||
msg.hgadMsg = hgad;
|
||
msg.nMsg = GM_QUERY;
|
||
msg.nCode = GQUERY_DETAILS;
|
||
msg.nType = GQDT_HWND;
|
||
msg.hOwner = m_hwndTree;
|
||
|
||
DUserSendEvent(&msg, 0);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
int
|
||
Spy::NumLines(int cyPxl) const
|
||
{
|
||
return (cyPxl - 1) / s_cyLinePxl + 1;
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
void
|
||
Spy::UpdateDetails()
|
||
{
|
||
if (m_hgadDetails == NULL) {
|
||
return;
|
||
}
|
||
|
||
RECT rcPxl;
|
||
GetGadgetRect(m_hgadDetails, &rcPxl, SGR_CONTAINER);
|
||
|
||
wsprintf(m_szRect, "(%d, %d)-(%d, %d) %d<> %d",
|
||
rcPxl.left, rcPxl.top, rcPxl.right, rcPxl.bottom,
|
||
rcPxl.right - rcPxl.left, rcPxl.bottom - rcPxl.top);
|
||
|
||
|
||
GMSG_QUERYDESC msg;
|
||
msg.cbSize = sizeof(msg);
|
||
msg.hgadMsg = m_hgadDetails;
|
||
msg.nMsg = GM_QUERY;
|
||
msg.nCode = GQUERY_DESCRIPTION;
|
||
msg.szName[0] = '\0';
|
||
msg.szType[0] = '\0';
|
||
|
||
if (DUserSendEvent(&msg, 0) == DU_S_COMPLETE) {
|
||
CopyString(m_szName, msg.szName, _countof(m_szName));
|
||
CopyString(m_szType, msg.szType, _countof(m_szType));
|
||
} else {
|
||
m_szName[0] = '\0';
|
||
m_szType[0] = '\0';
|
||
}
|
||
|
||
|
||
//
|
||
// We are bypassinging the normal API's to directly call a
|
||
// DEBUG-only function. Need to lock the Context and do FULL handle
|
||
// validation.
|
||
//
|
||
|
||
ContextLock cl;
|
||
if (cl.LockNL(ContextLock::edNone)) {
|
||
DuVisual * pgadTree = ValidateVisual(m_hgadDetails);
|
||
AssertMsg(pgadTree != NULL, "Should be a valid DuVisual for Spy");
|
||
pgadTree->DEBUG_GetStyleDesc(m_szStyle, _countof(m_szStyle));
|
||
|
||
UpdateLayoutDesc(FALSE);
|
||
InvalidateRect(m_hwnd, NULL, TRUE);
|
||
}
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
void
|
||
Spy::UpdateLayout()
|
||
{
|
||
RECT rcClient;
|
||
GetClientRect(m_hwnd, &rcClient);
|
||
m_sizeWndPxl.cx = rcClient.right;
|
||
m_sizeWndPxl.cy = rcClient.bottom;
|
||
|
||
UpdateLayoutDesc(TRUE);
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
void
|
||
Spy::UpdateLayoutDesc(BOOL fForceLayoutDesc)
|
||
{
|
||
//
|
||
// Compute the number of needed lines
|
||
//
|
||
|
||
int cOldLines = m_cLines;
|
||
m_cLines = 4;
|
||
|
||
RECT rcStyle;
|
||
rcStyle.left = cxBorder + cxValue;
|
||
rcStyle.top = 0;
|
||
rcStyle.right = m_sizeWndPxl.cx - cxBorder;
|
||
rcStyle.bottom = 10000;
|
||
|
||
HDC hdc = GetGdiCache()->GetTempDC();
|
||
HFONT hfntOld = (HFONT) SelectObject(hdc, s_hfntDesc);
|
||
int nHeight = OS()->DrawText(hdc, m_szStyle, (int) wcslen(m_szStyle), &rcStyle,
|
||
DT_CALCRECT | DT_LEFT | DT_TOP | DT_WORDBREAK);
|
||
SelectObject(hdc, hfntOld);
|
||
GetGdiCache()->ReleaseTempDC(hdc);
|
||
|
||
m_cLines += NumLines(nHeight);
|
||
|
||
|
||
//
|
||
// Move the Tree to provide space for the description
|
||
//
|
||
|
||
if ((cOldLines != m_cLines) || fForceLayoutDesc) {
|
||
m_cyDescPxl = s_cyLinePxl * m_cLines + 10;
|
||
m_fShowDesc = m_sizeWndPxl.cy > m_cyDescPxl;
|
||
|
||
SIZE sizeTree;
|
||
sizeTree.cx = m_sizeWndPxl.cx;
|
||
sizeTree.cy = m_fShowDesc ? (m_sizeWndPxl.cy - m_cyDescPxl) : m_sizeWndPxl.cy;
|
||
|
||
MoveWindow(m_hwndTree, 0, 0, sizeTree.cx, sizeTree.cy, TRUE);
|
||
}
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
void
|
||
Spy::OnPaint(HDC hdc)
|
||
{
|
||
HFONT hfntOld = (HFONT) SelectObject(hdc, s_hfntDesc);
|
||
int nOldMode = SetBkMode(hdc, TRANSPARENT);
|
||
|
||
RECT rcOutline;
|
||
rcOutline.left = 2;
|
||
rcOutline.top = m_sizeWndPxl.cy - m_cyDescPxl + 2;
|
||
rcOutline.right = m_sizeWndPxl.cx - 1;
|
||
rcOutline.bottom = m_sizeWndPxl.cy - 1;
|
||
GdDrawOutlineRect(hdc, &rcOutline, s_hbrOutline);
|
||
|
||
POINT pt;
|
||
pt.x = cxBorder;
|
||
pt.y = m_sizeWndPxl.cy - m_cyDescPxl + cyBorder;
|
||
|
||
// NOTE: m_cLines should equal the number of lines displayed here
|
||
|
||
PaintLine(hdc, &pt, "HGADGET: ", m_hgadDetails);
|
||
PaintLine(hdc, &pt, "Name: ", m_szName, FALSE, s_hfntDescBold);
|
||
PaintLine(hdc, &pt, "Type: ", m_szType);
|
||
PaintLine(hdc, &pt, "Rectangle: ", m_szRect);
|
||
PaintLine(hdc, &pt, "Style: ", m_szStyle, TRUE);
|
||
|
||
SetBkMode(hdc, nOldMode);
|
||
SelectObject(hdc, hfntOld);
|
||
}
|
||
|
||
|
||
class CTempSelectFont
|
||
{
|
||
public:
|
||
CTempSelectFont(HDC hdc, HFONT hfnt)
|
||
{
|
||
m_hdc = hdc;
|
||
m_fSelect = (hfnt != NULL);
|
||
if (m_fSelect) {
|
||
m_hfntOld = (HFONT) SelectObject(m_hdc, hfnt);
|
||
}
|
||
}
|
||
|
||
~CTempSelectFont()
|
||
{
|
||
if (m_fSelect) {
|
||
SelectObject(m_hdc, m_hfntOld);
|
||
}
|
||
}
|
||
|
||
BOOL m_fSelect;
|
||
HDC m_hdc;
|
||
HFONT m_hfntOld;
|
||
};
|
||
|
||
//------------------------------------------------------------------------------
|
||
void
|
||
Spy::PaintLine(HDC hdc, POINT * pptOffset, LPCTSTR pszName, LPCTSTR pszText, HFONT hfnt)
|
||
{
|
||
TextOut(hdc, pptOffset->x, pptOffset->y, pszName, (int) _tcslen(pszName));
|
||
|
||
CTempSelectFont tsf(hdc, hfnt);
|
||
TextOut(hdc, pptOffset->x + cxValue, pptOffset->y, pszText, (int) _tcslen(pszText));
|
||
|
||
pptOffset->y += s_cyLinePxl;
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
void
|
||
Spy::PaintLine(HDC hdc, POINT * pptOffset, LPCTSTR pszName, LPCWSTR pszText, BOOL fMultiline, HFONT hfnt)
|
||
{
|
||
TextOut(hdc, pptOffset->x, pptOffset->y, pszName, (int) _tcslen(pszName));
|
||
|
||
CTempSelectFont tsf(hdc, hfnt);
|
||
|
||
if (fMultiline) {
|
||
RECT rcStyle;
|
||
rcStyle.left = pptOffset->x + cxValue;
|
||
rcStyle.top = pptOffset->y;
|
||
rcStyle.right = m_sizeWndPxl.cx - cxBorder;
|
||
rcStyle.bottom = 10000;
|
||
|
||
int nHeight = OS()->DrawText(hdc, pszText, (int) wcslen(pszText), &rcStyle,
|
||
DT_LEFT | DT_TOP | DT_WORDBREAK);
|
||
pptOffset->y += NumLines(nHeight) * s_cyLinePxl;
|
||
} else {
|
||
OS()->TextOut(hdc, pptOffset->x + cxValue, pptOffset->y, pszText, (int) wcslen(pszText));
|
||
pptOffset->y += s_cyLinePxl;
|
||
}
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
void
|
||
Spy::PaintLine(HDC hdc, POINT * pptOffset, LPCTSTR pszName, int nValue, HFONT hfnt)
|
||
{
|
||
TextOut(hdc, pptOffset->x, pptOffset->y, pszName, (int) _tcslen(pszName));
|
||
|
||
CTempSelectFont tsf(hdc, hfnt);
|
||
TCHAR szValue[20];
|
||
_itot(nValue, szValue, 10);
|
||
TextOut(hdc, pptOffset->x + cxValue, pptOffset->y, szValue, (int) _tcslen(szValue));
|
||
|
||
pptOffset->y += s_cyLinePxl;
|
||
}
|
||
|
||
|
||
//------------------------------------------------------------------------------
|
||
void
|
||
Spy::PaintLine(HDC hdc, POINT * pptOffset, LPCTSTR pszName, void * pvValue, HFONT hfnt)
|
||
{
|
||
TextOut(hdc, pptOffset->x, pptOffset->y, pszName, (int) _tcslen(pszName));
|
||
|
||
CTempSelectFont tsf(hdc, hfnt);
|
||
TCHAR szValue[20];
|
||
wsprintf(szValue, "0x%p", pvValue);
|
||
TextOut(hdc, pptOffset->x + cxValue, pptOffset->y, szValue, (int) _tcslen(szValue));
|
||
|
||
pptOffset->y += s_cyLinePxl;
|
||
}
|
||
|
||
|
||
#endif // DBG
|