WindowsXP-SP1/windows/oleacc/oleacc/outline.cpp
2020-09-30 16:53:49 +02:00

1381 lines
36 KiB
C++

// --------------------------------------------------------------------------
//
// OUTLINE.CPP
//
// Wrapper for COMCTL32's treeview control
//
// --------------------------------------------------------------------------
#include "oleacc_p.h"
#include "default.h"
#include "client.h"
#include "RemoteProxy6432.h"
#include "propmgr_util.h"
#define NOSTATUSBAR
#define NOUPDOWN
#define NOMENUHELP
#define NOTRACKBAR
#define NODRAGLIST
#define NOTOOLBAR
#define NOHOTKEY
#define NOPROGRESS
//#define NOLISTVIEW // INDEXTOSTATEIMAGEMASK needs LISTVIEW
#define NOANIMATE
#include <commctrl.h>
#include "Win64Helper.h"
#include "w95trace.h"
#include "outline.h"
struct MSAASTATEIMAGEMAPENT
{
DWORD dwRole;
DWORD dwState;
};
enum
{
TV_IMGIDX_Image,
TV_IMGIDX_State,
TV_IMGIDX_Overlay,
TV_IMGIDX_COUNT
};
BOOL TVGetImageIndex( HWND hwnd, HTREEITEM id, int aKeys[ TV_IMGIDX_COUNT ] );
extern "C" {
BOOL GetRoleFromStateImageMap( HWND hwnd, int iImage, DWORD * pdwRole );
BOOL GetStateFromStateImageMap( HWND hwnd, int iImage, DWORD * pdwState );
BOOL GetStateImageMapEnt_SameBitness( HWND hwnd, int iImage, DWORD * pdwState, DWORD * pdwRole );
}
// These convert between the DWORD childIDs and HTREEITEMS.
//
// Pre-win64, HTREEITEMS were cast to DWORDs, but that doesn't work on
// Win64 since HTREEITEMS are pointers, and no longer fit into a plain
// DWORD. Instead, the treeview supplies messages to map between
// an internal DWORD id and HTREEITEMS; these functions wrap that
// functionality.
HTREEITEM TVItemFromChildID( HWND hwnd, DWORD idChild );
DWORD ChildIDFromTVItem( HWND hwnd, HTREEITEM htvi );
// Template-based shared read/write/alloc
//
// Notes:
//
// Read/Write each have two versions; one reads/writes a single item,
// the other allows a count to be specified. Count specifies number
// of items, not the number of bytes (unless the type is actually byte!).
//
// Order or arguments is ( dest, souce ) - this is consistent with memcpy,
// strcpy and regular assignments (dest = source).
//
// In TSharedWrite, the source arg is an actual value, not a pointer to one.
// (This avoids having to use a dummy variable to contain the value you want
// to use.)
template< typename T >
BOOL TSharedWrite( T * pRemote, const T & Local, HANDLE hProcess )
{
return SharedWrite( const_cast< T * >( & Local ), pRemote, sizeof( T ), hProcess );
}
template< typename T >
BOOL TSharedRead( T * pLocal, const T * pRemote, HANDLE hProcess )
{
return SharedRead( const_cast< T * >( pRemote ), pLocal, sizeof( T ), hProcess );
}
template< typename T >
BOOL TSharedRead( T * pLocal, const T * pRemote, int count, HANDLE hProcess )
{
return SharedRead( const_cast< T * >( pRemote ), pLocal, sizeof( T ) * count, hProcess );
}
template< typename T >
T * TSharedAlloc( HWND hwnd, HANDLE * pProcessHandle )
{
return (T *) SharedAlloc( sizeof( T ), hwnd, pProcessHandle );
}
template< typename T >
T * TSharedAllocExtra( HWND hwnd, HANDLE * pProcessHandle, UINT cbExtra )
{
return (T *) SharedAlloc( sizeof( T ) + cbExtra, hwnd, pProcessHandle );
}
#define MAX_NAME_SIZE 255
// these are in a newer version of comctl.h
#ifndef TVM_GETITEMSTATE
#define TVM_GETITEMSTATE (TV_FIRST + 39)
#define TreeView_GetItemState(hwndTV, hti, mask) \
(UINT)SNDMSG((hwndTV), TVM_GETITEMSTATE, (WPARAM)hti, (LPARAM)mask)
#define TreeView_GetCheckState(hwndTV, hti) \
((((UINT)(SNDMSG((hwndTV), TVM_GETITEMSTATE, (WPARAM)hti, TVIS_STATEIMAGEMASK))) >> 12) -1)
#endif // ifndef TVM_GETITEMSTATE
// --------------------------------------------------------------------------
//
// CreateTreeViewClient()
//
// --------------------------------------------------------------------------
HRESULT CreateTreeViewClient(HWND hwnd, long idChildCur, REFIID riid,
void** ppvTreeView)
{
COutlineView32 * poutline;
HRESULT hr;
InitPv(ppvTreeView);
poutline = new COutlineView32(hwnd, idChildCur);
if (!poutline)
return(E_OUTOFMEMORY);
hr = poutline->QueryInterface(riid, ppvTreeView);
if (!SUCCEEDED(hr))
delete poutline;
return(hr);
}
// --------------------------------------------------------------------------
//
// COutlineView32::COutlineView32()
//
// --------------------------------------------------------------------------
COutlineView32::COutlineView32(HWND hwnd, long idChildCur)
: CClient( CLASS_TreeViewClient )
{
m_fUseLabel = TRUE;
Initialize(hwnd, idChildCur);
}
// --------------------------------------------------------------------------
//
// COutlineView32::SetupChildren()
//
// --------------------------------------------------------------------------
void COutlineView32::SetupChildren(void)
{
m_cChildren = SendMessageINT(m_hwnd, TVM_GETCOUNT, 0, 0);
}
// --------------------------------------------------------------------------
//
// COutlineView32::ValidateChild()
//
// We have no index-ID support in tree view. Hence, the HTREEITEM is the
// child ID, only thing we can do. We don't bother validating it except
// to make sure it is less than 0x80000000.
//
// --------------------------------------------------------------------------
BOOL COutlineView32::ValidateChild(VARIANT* pvar)
{
TryAgain:
switch (pvar->vt)
{
case VT_VARIANT | VT_BYREF:
VariantCopy(pvar, pvar->pvarVal);
goto TryAgain;
case VT_ERROR:
if (pvar->scode != DISP_E_PARAMNOTFOUND)
return(FALSE);
// FALL THRU
case VT_EMPTY:
pvar->vt = VT_I4;
pvar->lVal = 0;
break;
case VT_I4:
//BRENDANM - high bit set is valid, on 3G systems plus this can also happen on w64?
// if (pvar->lVal < 0)
// return(FALSE);
//
// Assume it's a valid HTREEITEM!
//
break;
default:
return(FALSE);
}
return(TRUE);
}
// --------------------------------------------------------------------------
//
// COutlineView32::NextLogicalItem()
//
// --------------------------------------------------------------------------
HTREEITEM COutlineView32::NextLogicalItem(HTREEITEM ht)
{
HTREEITEM htNext;
//
// We see if this item has a child. If so, we are done. If not,
// we get the next sibling. If that fails, we move back to the parent,
// and try the next sibling thing again. And so on until we reach the
// root.
//
htNext = TreeView_GetChild(m_hwnd, ht);
if (htNext)
return(htNext);
while (ht)
{
htNext = TreeView_GetNextSibling(m_hwnd, ht);
if (htNext)
return(htNext);
ht = TreeView_GetParent(m_hwnd, ht);
}
return(NULL);
}
// --------------------------------------------------------------------------
//
// COutlineView32::PrevLogicalItem()
//
// --------------------------------------------------------------------------
HTREEITEM COutlineView32::PrevLogicalItem(HTREEITEM ht)
{
HTREEITEM htPrev;
//
// If this item has no previous sibling return the parent.
// Then if the so, see if run done the first children.
// Then get the previous sibling has no children return that.
// Otherwise march down the tre find the last sibling of the last child
//
htPrev = TreeView_GetPrevSibling(m_hwnd, ht);
if ( !htPrev )
{
return TreeView_GetParent(m_hwnd, ht);
}
else
{
HTREEITEM htTest = TreeView_GetChild(m_hwnd, htPrev);
if ( !htTest )
{
return htPrev;
}
else
{
htPrev = htTest;
// We are at the first child of the previous sibling
for ( ;; )
{
htTest = TreeView_GetNextSibling(m_hwnd, htPrev);
if ( !htTest )
{
htTest = TreeView_GetChild(m_hwnd, htPrev);
if ( !htTest )
break;
}
htPrev = htTest;
}
return htPrev;
}
}
}
// --------------------------------------------------------------------------
//
// COutlineView32::get_accName()
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::get_accName(VARIANT varChild, BSTR* pszName)
{
TVITEM* lptvShared;
LPTSTR lpszShared;
HANDLE hProcess;
LPTSTR lpszLocal;
InitPv(pszName);
if (!ValidateChild(&varChild))
return E_INVALIDARG;
if (!varChild.lVal)
return CClient::get_accName(varChild, pszName);
HTREEITEM htItem = TVItemFromChildID( m_hwnd, varChild.lVal );
if( ! htItem )
{
return E_INVALIDARG;
}
//
// Try getting the item's text the easy way, by asking first. Since the
// file system limits us to 255 character names, assume items aren't
// bigger than that.
//
lptvShared = TSharedAllocExtra<TVITEM>( m_hwnd, & hProcess,
(MAX_NAME_SIZE+2)*sizeof(TCHAR) );
if (!lptvShared)
return(E_OUTOFMEMORY);
lpszLocal = (LPTSTR)LocalAlloc(LPTR,((MAX_NAME_SIZE+2)*sizeof(TCHAR)));
if (!lpszLocal)
{
SharedFree (lptvShared,hProcess);
return(E_OUTOFMEMORY);
}
lpszShared = (LPTSTR)(lptvShared+1);
// (UINT) cast converts plain int to same type as ->mask, which is UINT.
TSharedWrite( & lptvShared->mask, (UINT)TVIF_TEXT, hProcess );
TSharedWrite( & lptvShared->hItem, htItem, hProcess );
TSharedWrite( & lptvShared->pszText, lpszShared, hProcess );
TSharedWrite( & lptvShared->cchTextMax, MAX_NAME_SIZE + 1, hProcess );
if (TreeView_GetItem(m_hwnd, lptvShared))
{
TSharedRead( lpszLocal, lpszShared, MAX_NAME_SIZE + 2, hProcess );
if (*lpszLocal)
*pszName = TCharSysAllocString(lpszLocal);
}
SharedFree(lptvShared,hProcess);
LocalFree (lpszLocal);
return(*pszName ? S_OK : S_FALSE);
}
// --------------------------------------------------------------------------
//
// COutlineView32::get_accValue()
//
// This returns back the indent level for a child item.
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::get_accValue(VARIANT varChild, BSTR* pszValue)
{
InitPv(pszValue);
if (!ValidateChild(&varChild))
return E_INVALIDARG;
if (!varChild.lVal)
return E_NOT_APPLICABLE;
HTREEITEM htParent = TVItemFromChildID( m_hwnd, varChild.lVal );
if( ! htParent )
{
return E_INVALIDARG;
}
long lValue = 0;
while( htParent = TreeView_GetParent( m_hwnd, htParent ) )
{
lValue++;
}
return VarBstrFromI4( lValue, 0, 0, pszValue );
}
// --------------------------------------------------------------------------
//
// COutlineView32::get_accRole()
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::get_accRole(VARIANT varChild, VARIANT* pvarRole)
{
InitPvar(pvarRole);
if (!ValidateChild(&varChild))
return E_INVALIDARG;
pvarRole->vt = VT_I4;
if (varChild.lVal)
{
HTREEITEM htItem = TVItemFromChildID( m_hwnd, varChild.lVal );
if( ! htItem )
{
return E_INVALIDARG;
}
DWORD dwRole;
BOOL fGotRole = FALSE;
int aKeys[ TV_IMGIDX_COUNT ];
if( TVGetImageIndex( m_hwnd, htItem, aKeys ) )
{
if( CheckDWORDMap( m_hwnd, OBJID_CLIENT, CHILDID_SELF,
PROPINDEX_ROLEMAP,
aKeys, ARRAYSIZE( aKeys ),
& dwRole ) )
{
pvarRole->lVal = dwRole;
fGotRole = TRUE;
}
else if( GetRoleFromStateImageMap( m_hwnd, aKeys[ TV_IMGIDX_Image ], & dwRole ) )
{
pvarRole->lVal = dwRole;
fGotRole = TRUE;
}
}
if( ! fGotRole )
{
//
// Note that just because the treeview has TVS_CHECKBOXES
// doesn't mean that every item is itself a checkbox. We
// need to sniff at the item, too, to see if it has a state
// image.
//
if ((GetWindowLong (m_hwnd,GWL_STYLE) & TVS_CHECKBOXES) &&
TreeView_GetItemState(m_hwnd, htItem, TVIS_STATEIMAGEMASK))
{
pvarRole->lVal = ROLE_SYSTEM_CHECKBUTTON;
}
else
{
pvarRole->lVal = ROLE_SYSTEM_OUTLINEITEM;
}
}
}
else
{
pvarRole->lVal = ROLE_SYSTEM_OUTLINE;
}
return S_OK;
}
// --------------------------------------------------------------------------
//
// COutlineView32::get_accState()
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::get_accState(VARIANT varChild, VARIANT* pvarState)
{
LPTVITEM lptvShared;
HANDLE hProcess;
TVITEM tvLocal;
DWORD dwStyle;
InitPvar(pvarState);
if (!ValidateChild(&varChild))
return(E_INVALIDARG);
if (!varChild.lVal)
return(CClient::get_accState(varChild, pvarState));
HTREEITEM htItem = TVItemFromChildID( m_hwnd, varChild.lVal );
if( htItem == NULL )
{
return E_INVALIDARG;
}
pvarState->vt = VT_I4;
pvarState->lVal = 0;
if (MyGetFocus() == m_hwnd)
pvarState->lVal |= STATE_SYSTEM_FOCUSABLE;
if( IsClippedByWindow( this, varChild, m_hwnd ) )
{
pvarState->lVal |= STATE_SYSTEM_INVISIBLE | STATE_SYSTEM_OFFSCREEN;
}
lptvShared = TSharedAlloc< TVITEM >( m_hwnd, & hProcess );
if (!lptvShared)
return(E_OUTOFMEMORY);
// (UINT) cast converts plain int to same type as ->mask, which is UINT.
TSharedWrite( & lptvShared->mask, (UINT)(TVIF_STATE | TVIF_CHILDREN), hProcess );
TSharedWrite( & lptvShared->hItem, htItem, hProcess );
if (TreeView_GetItem(m_hwnd, lptvShared))
{
TSharedRead( & tvLocal, lptvShared, hProcess );
if (tvLocal.state & TVIS_SELECTED)
{
pvarState->lVal |= STATE_SYSTEM_SELECTED;
if (pvarState->lVal & STATE_SYSTEM_FOCUSABLE)
pvarState->lVal |= STATE_SYSTEM_FOCUSED;
}
pvarState->lVal |= STATE_SYSTEM_SELECTABLE;
if (tvLocal.state & TVIS_DROPHILITED)
pvarState->lVal |= STATE_SYSTEM_HOTTRACKED;
//
// If it isn't expanded and it has children, then it must be
// collapsed.
//
if (tvLocal.state & (TVIS_EXPANDED | TVIS_EXPANDPARTIAL))
pvarState->lVal |= STATE_SYSTEM_EXPANDED;
else if (tvLocal.cChildren)
pvarState->lVal |= STATE_SYSTEM_COLLAPSED;
// If the treeview has checkboxes, then see if it's checked.
// State 0 = no checkbox, State 1 = unchecked, State 2 = checked
dwStyle = GetWindowLong (m_hwnd,GWL_STYLE);
if ((dwStyle & TVS_CHECKBOXES) &&
(tvLocal.state & TVIS_STATEIMAGEMASK) == INDEXTOSTATEIMAGEMASK(2))
pvarState->lVal |= STATE_SYSTEM_CHECKED;
int aKeys[ TV_IMGIDX_COUNT ];
if( TVGetImageIndex( m_hwnd, htItem, aKeys ) )
{
DWORD dwState;
if( CheckDWORDMap( m_hwnd, OBJID_CLIENT, CHILDID_SELF,
PROPINDEX_STATEMAP,
aKeys, ARRAYSIZE( aKeys ), & dwState ) )
{
pvarState->lVal |= dwState;
}
else if( GetStateFromStateImageMap( m_hwnd, aKeys[ TV_IMGIDX_Image ], & dwState ) )
{
pvarState->lVal |= dwState;
}
}
}
SharedFree(lptvShared,hProcess);
return(S_OK);
}
// --------------------------------------------------------------------------
//
// COutlineView32::get_accDescription()
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::get_accDescription(VARIANT varChild, BSTR* pszDesc)
{
InitPv(pszDesc);
if (! ValidateChild(&varChild))
return E_INVALIDARG;
if (varChild.lVal)
{
HTREEITEM htItem = TVItemFromChildID( m_hwnd, varChild.lVal );
if( ! htItem )
{
return E_INVALIDARG;
}
int aKeys[ TV_IMGIDX_COUNT ];
if( TVGetImageIndex( m_hwnd, htItem, aKeys ) )
{
if( CheckStringMap( m_hwnd, OBJID_CLIENT, CHILDID_SELF, PROPINDEX_DESCRIPTIONMAP,
aKeys, ARRAYSIZE( aKeys ), pszDesc ) )
{
return S_OK;
}
}
}
return S_FALSE;
}
// --------------------------------------------------------------------------
//
// COutlineView32::get_accFocus()
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::get_accFocus(VARIANT* pvarFocus)
{
HRESULT hr;
//
// Do we have the focus?
//
hr = CClient::get_accFocus(pvarFocus);
if (!SUCCEEDED(hr) || (pvarFocus->vt != VT_I4) || (pvarFocus->lVal != 0))
return hr;
//
// We do. What item is focused?
//
return COutlineView32::get_accSelection(pvarFocus);
}
// --------------------------------------------------------------------------
//
// COutlineView32::get_accSelection()
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::get_accSelection(VARIANT* pvarSelection)
{
InitPvar(pvarSelection);
HTREEITEM ht = TreeView_GetSelection(m_hwnd);
if (ht)
{
pvarSelection->vt = VT_I4;
pvarSelection->lVal = ChildIDFromTVItem( m_hwnd, ht );
if( pvarSelection->lVal == 0 )
return E_FAIL;
return S_OK;
}
else
{
return S_FALSE;
}
}
// --------------------------------------------------------------------------
//
// COutlineView32::get_accDefaultAction()
//
// The default action of a node with children is:
// * Expand one level if it is fully collapsed
// * Collapse if it is partly or completely expanded
//
// The reason for not expanding fully is that it is slow and there is no
// keyboard shortcut or mouse click that will do it. You can use a menu
// command to do so if you want.
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::get_accDefaultAction(VARIANT varChild, BSTR* pszDefA)
{
VARIANT varState;
HRESULT hr;
InitPv(pszDefA);
if (!ValidateChild(&varChild))
return(E_INVALIDARG);
if (!varChild.lVal)
return(CClient::get_accDefaultAction(varChild, pszDefA));
//
// Get our state. NOTE that we will not get back STATE_SYSTEM_COLLAPSED
// if the item doesn't have children.
//
VariantInit(&varState);
hr = get_accState(varChild, &varState);
if (!SUCCEEDED(hr))
return(hr);
if (varState.lVal & STATE_SYSTEM_EXPANDED)
return(HrCreateString(STR_TREE_COLLAPSE, pszDefA));
else if (varState.lVal & STATE_SYSTEM_COLLAPSED)
return(HrCreateString(STR_TREE_EXPAND, pszDefA));
else
return(E_NOT_APPLICABLE);
}
// --------------------------------------------------------------------------
//
// COutlineView32::accSelect()
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::accSelect(long selFlags, VARIANT varChild)
{
if (!ValidateChild(&varChild) || !ValidateSelFlags(selFlags))
return E_INVALIDARG;
if (!varChild.lVal)
return CClient::accSelect(selFlags, varChild);
HTREEITEM htItem = TVItemFromChildID( m_hwnd, varChild.lVal );
if( htItem == NULL )
{
return E_INVALIDARG;
}
if (selFlags & SELFLAG_TAKEFOCUS)
{
MySetFocus(m_hwnd);
}
if ((selFlags & SELFLAG_TAKEFOCUS) || (selFlags & SELFLAG_TAKESELECTION))
{
TreeView_SelectItem(m_hwnd, htItem);
return S_OK;
}
else
{
return E_NOT_APPLICABLE;
}
}
// --------------------------------------------------------------------------
//
// COutlineView32::accLocation()
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::accLocation(long* pxLeft, long* pyTop, long* pcxWidth,
long* pcyHeight, VARIANT varChild)
{
InitAccLocation(pxLeft, pyTop, pcxWidth, pcyHeight);
if (!ValidateChild(&varChild))
return E_INVALIDARG;
if (!varChild.lVal)
return CClient::accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, varChild);
HTREEITEM htItem = TVItemFromChildID( m_hwnd, varChild.lVal );
if( htItem == NULL )
{
return E_INVALIDARG;
}
// Get the listview item rect.
HANDLE hProcess;
LPRECT lprcShared = TSharedAlloc< RECT >( m_hwnd, & hProcess );
if (!lprcShared)
return E_OUTOFMEMORY;
// can't use the TreeView_GetItemRect macro, because it does a behind-the-scenes
// assignment of the item id into the rect, which blows on shared memory.
// TVM_GETITEMRECT is weird: it's a ptr to a RECT, which, on input, contains
// the HTREEITEM of the item; on output it contains that item's rect.
TSharedWrite( (HTREEITEM *)lprcShared, htItem, hProcess);
if (SendMessage (m_hwnd, TVM_GETITEMRECT, TRUE, (LPARAM)lprcShared))
{
RECT rcLocal;
TSharedRead( & rcLocal, lprcShared, hProcess );
MapWindowPoints(m_hwnd, NULL, (LPPOINT)&rcLocal, 2);
*pxLeft = rcLocal.left;
*pyTop = rcLocal.top;
*pcxWidth = rcLocal.right - rcLocal.left;
*pcyHeight = rcLocal.bottom - rcLocal.top;
}
SharedFree(lprcShared,hProcess);
return S_OK;
}
// --------------------------------------------------------------------------
//
// COutlineView32::accNavigate()
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::accNavigate(long dwNavDir, VARIANT varStart,
VARIANT* pvarEnd)
{
HTREEITEM htItem;
HTREEITEM htNewItem = 0;
InitPvar(pvarEnd);
if (!ValidateChild(&varStart) ||
!ValidateNavDir(dwNavDir, varStart.lVal))
return(E_INVALIDARG);
if (dwNavDir >= NAVDIR_FIRSTCHILD)
{
htNewItem = TreeView_GetRoot(m_hwnd);
if ((dwNavDir == NAVDIR_LASTCHILD) && htNewItem)
{
HTREEITEM htNext;
// make sure we are at the last root sibling
htNext = TreeView_GetNextSibling(m_hwnd, htNewItem);
while (htNext)
{
htNewItem = htNext;
htNext = TreeView_GetNextSibling(m_hwnd, htNewItem);
}
RecurseAgain:
//
// Keep recursing down all the way to the last ancestor of the
// last item under the root.
//
htNext = TreeView_GetChild(m_hwnd, htNewItem);
if (htNext)
{
while (htNext)
{
htNewItem = htNext;
htNext = TreeView_GetNextSibling(m_hwnd, htNewItem);
}
goto RecurseAgain;
}
}
goto AllDone;
}
else if (!varStart.lVal)
{
return CClient::accNavigate(dwNavDir, varStart, pvarEnd);
}
htItem = TVItemFromChildID( m_hwnd, varStart.lVal );
if( htItem == NULL )
{
return E_INVALIDARG;
}
switch (dwNavDir)
{
case NAVDIR_NEXT:
// Next logical item, peer or child
htNewItem = NextLogicalItem(htItem);
break;
case NAVDIR_PREVIOUS:
// Previous logical item, peer or parent
htNewItem = PrevLogicalItem(htItem);
break;
case NAVDIR_UP:
// Previous sibling!
htNewItem = TreeView_GetPrevSibling(m_hwnd, htItem);
break;
case NAVDIR_DOWN:
// Next sibling!
htNewItem = TreeView_GetNextSibling(m_hwnd, htItem);
break;
case NAVDIR_LEFT:
// Get parent!
htNewItem = TreeView_GetParent(m_hwnd, htItem);
break;
case NAVDIR_RIGHT:
// Get first child!
htNewItem = TreeView_GetChild(m_hwnd, htItem);
break;
}
AllDone:
if (htNewItem)
{
pvarEnd->vt = VT_I4;
pvarEnd->lVal = ChildIDFromTVItem( m_hwnd, htNewItem );
if( pvarEnd->lVal == 0 )
return E_FAIL;
return S_OK;
}
else
{
return S_FALSE;
}
}
// --------------------------------------------------------------------------
//
// COutlineView32::accHitTest()
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::accHitTest(long x, long y, VARIANT* pvarHit)
{
HRESULT hr;
LPTVHITTESTINFO lptvhtShared;
HANDLE hProcess;
POINT ptLocal;
SetupChildren();
//
// Is the point in the listview at all?
//
hr = CClient::accHitTest(x, y, pvarHit);
// #11150, CWO, 1/27/97, Replaced !SUCCEEDED with !S_OK
if ((hr != S_OK) || (pvarHit->vt != VT_I4) || (pvarHit->lVal != 0))
return(hr);
//
// Now find out what item this point is on.
//
lptvhtShared = TSharedAlloc< TVHITTESTINFO >( m_hwnd, & hProcess );
if (!lptvhtShared)
return(E_OUTOFMEMORY);
// Cast keeps templates happy - NULL on its own is #define'd as 0 and has no type.
TSharedWrite( & lptvhtShared->hItem, (HTREEITEM)NULL, hProcess );
ptLocal.x = x;
ptLocal.y = y;
ScreenToClient(m_hwnd, &ptLocal);
TSharedWrite( & lptvhtShared->pt, ptLocal, hProcess );
SendMessage(m_hwnd, TVM_HITTEST, 0, (LPARAM)lptvhtShared);
HTREEITEM hItem;
TSharedRead( &hItem, & lptvhtShared->hItem, hProcess );
SharedFree(lptvhtShared,hProcess);
if( hItem )
{
pvarHit->lVal = ChildIDFromTVItem( m_hwnd, hItem );
if( pvarHit->lVal == 0 )
return E_FAIL;
}
else
{
// if hItem is NULL, then point is over the treeview itself
pvarHit->lVal = CHILDID_SELF;
}
return S_OK;
}
// --------------------------------------------------------------------------
//
// COutlineView32::accDoDefaultAction()
//
// This expands collapsed items and collapses expanded items.
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::accDoDefaultAction(VARIANT varChild)
{
VARIANT varState;
HRESULT hr;
UINT tve;
if (!ValidateChild(&varChild))
return(E_INVALIDARG);
if (!varChild.lVal)
return(CClient::accDoDefaultAction(varChild));
//
// Get the item's state.
//
VariantInit(&varState);
hr = get_accState(varChild, &varState);
if (!SUCCEEDED(hr))
return(hr);
if (varState.lVal & STATE_SYSTEM_COLLAPSED)
tve = TVE_EXPAND;
else if (varState.lVal & STATE_SYSTEM_EXPANDED)
tve = TVE_COLLAPSE;
else
return(E_NOT_APPLICABLE);
PostMessage(m_hwnd, TVM_EXPAND, tve, (LPARAM)varChild.lVal);
return(S_OK);
}
// --------------------------------------------------------------------------
//
// COutlineView32::Reset()
//
// Sets the "current" HTREEITEM to NULL so we know we are at the beginning.
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::Reset()
{
m_idChildCur = 0;
return S_OK;
}
// --------------------------------------------------------------------------
//
// COutlineView32::Next()
//
// We descend into children, among siblings, and back up as necessary.
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::Next(ULONG celt, VARIANT* rgvarFetch, ULONG* pceltFetch)
{
SetupChildren();
if (pceltFetch)
InitPv(pceltFetch);
HTREEITEM htCur;
HTREEITEM htNext;
if( m_idChildCur == 0 )
{
htCur = NULL;
htNext = TreeView_GetRoot(m_hwnd);
}
else
{
htCur = TVItemFromChildID( m_hwnd, m_idChildCur );
if( ! htCur )
{
return E_FAIL;
}
htNext = NextLogicalItem(htCur);
}
VARIANT * pvar = rgvarFetch;
ULONG cFetched = 0;
while( (cFetched < celt) && htNext )
{
htCur = htNext;
cFetched++;
pvar->vt = VT_I4;
pvar->lVal = ChildIDFromTVItem( m_hwnd, htCur );
if( pvar->lVal == 0 )
return E_FAIL;
pvar++;
htNext = NextLogicalItem(htCur);
}
// if htCur is still NULL, then the treeview has 0 items, and
// m_idChildCur is still 0, at the start of the (empty) list.
// - safe to leave as is.
if( htCur )
{
m_idChildCur = ChildIDFromTVItem( m_hwnd, htCur );
if( m_idChildCur == 0 )
return E_FAIL;
}
if (pceltFetch)
*pceltFetch = cFetched;
return (cFetched < celt) ? S_FALSE : S_OK;
}
// --------------------------------------------------------------------------
//
// COutlineView32::Skip()
//
// --------------------------------------------------------------------------
STDMETHODIMP COutlineView32::Skip(ULONG celtSkip)
{
SetupChildren();
HTREEITEM htCur;
HTREEITEM htNext;
if( m_idChildCur == 0 )
{
htCur = NULL;
htNext = TreeView_GetRoot(m_hwnd);
}
else
{
htCur = TVItemFromChildID( m_hwnd, m_idChildCur );
if( ! htCur )
{
return E_FAIL;
}
htNext = NextLogicalItem(htCur);
}
while ((celtSkip > 0) && htNext)
{
--celtSkip;
htCur = htNext;
htNext = NextLogicalItem(htCur);
}
// if htCur is still NULL, then the treeview has 0 items, and
// m_idChildCur is still 0, at the start of the (empty) list.
// - safe to leave as is.
if( htCur )
{
m_idChildCur = ChildIDFromTVItem( m_hwnd, htCur );
if( m_idChildCur == 0 )
return E_FAIL;
}
return htNext ? S_OK : S_FALSE;
}
BOOL TVGetImageIndex( HWND hwnd, HTREEITEM id, int aKeys[ TV_IMGIDX_COUNT ] )
{
HANDLE hProcess;
TVITEM * lptvShared = TSharedAlloc< TVITEM >( hwnd, & hProcess );
if (!lptvShared)
return FALSE;
// (UINT) cast converts plain int to same type as ->mask, which is UINT.
TSharedWrite( &lptvShared->mask, (UINT)(TVIF_IMAGE | LVIF_STATE), hProcess );
TSharedWrite( &lptvShared->hItem, id, hProcess );
BOOL fRet;
if (TreeView_GetItem(hwnd, lptvShared))
{
INT iImage;
UINT state;
TSharedRead( & iImage, & lptvShared->iImage, hProcess );
TSharedRead( & state, & lptvShared->state, hProcess );
aKeys[ TV_IMGIDX_Image ] = iImage;
aKeys[ TV_IMGIDX_Overlay ] = ( state >> 8 ) & 0x0F;
aKeys[ TV_IMGIDX_State ] = ( state >> 12 ) & 0x0F;
fRet = TRUE;
}
else
{
fRet = FALSE;
}
SharedFree( lptvShared, hProcess );
return fRet;
}
// This reads from the process associated with the given
// hwnd, and does the necessary OpenProcess/CloseHandle
// tidyup and checks....
BOOL ReadProcessMemoryHWND( HWND hwnd, void * pSrc, void * pDst, DWORD len )
{
DWORD idProcess = 0;
GetWindowThreadProcessId(hwnd, &idProcess);
if( ! idProcess )
return FALSE;
HANDLE hProcess = OpenProcess( PROCESS_VM_READ, FALSE, idProcess );
if( ! hProcess )
return FALSE;
SIZE_T cbActual = 0;
BOOL retval = ReadProcessMemory( hProcess, pSrc, pDst, len, & cbActual )
&& len == cbActual;
CloseHandle( hProcess );
return retval;
}
BOOL GetStateImageMapEnt_SameBitness( HWND hwnd, int iImage, DWORD * pdwState, DWORD * pdwRole )
{
void * pAddress = (void *) GetProp( hwnd, TEXT("MSAAStateImageMapAddr") );
if( ! pAddress )
return FALSE;
int NumStates = PtrToInt( GetProp( hwnd, TEXT("MSAAStateImageMapCount") ) );
if( NumStates == 0 )
return FALSE;
// <= used since number is a 1-based count, iImage is a 0-based index.
// If iImage is 0, should be at least one state.
if( NumStates <= iImage )
return FALSE;
// Adjust to iImage into array...
pAddress = (void*)( (MSAASTATEIMAGEMAPENT*)pAddress + iImage );
MSAASTATEIMAGEMAPENT ent;
if( ! ReadProcessMemoryHWND( hwnd, pAddress, & ent, sizeof(ent) ) )
return FALSE;
*pdwState = ent.dwState;
*pdwRole = ent.dwRole;
return TRUE;
}
BOOL GetStateImageMapEnt( HWND hwnd, int iImage, DWORD * pdwState, DWORD * pdwRole )
{
// Quick shortcut - if this property isn't present, then don't even bother
// going further...
if( ! GetProp( hwnd, TEXT("MSAAStateImageMapCount") ) )
return FALSE;
// First determine if hwnd is a process with the same bitness as this DLL
BOOL fIsSameBitness;
if (FAILED(SameBitness(hwnd, &fIsSameBitness)))
return FALSE; // this case should never happen
if( fIsSameBitness )
{
return GetStateImageMapEnt_SameBitness( hwnd, iImage, pdwState, pdwRole );
}
else
{
// The server (hwnd) is not the same bitness so get a remote proxy
// factory object and call GetRoleFromStateImageMap thru it.
IRemoteProxyFactory *p;
if (FAILED(GetRemoteProxyFactory(&p)))
{
return FALSE;
}
HRESULT hr = p->GetStateImageMapEnt(
HandleToLong( hwnd )
, iImage
, pdwState
, pdwRole );
p->Release();
return hr == S_OK;
}
}
BOOL GetRoleFromStateImageMap( HWND hwnd, int iImage, DWORD * pdwRole )
{
DWORD dwState;
return GetStateImageMapEnt( hwnd, iImage, & dwState, pdwRole );
}
BOOL GetStateFromStateImageMap( HWND hwnd, int iImage, DWORD * pdwState )
{
DWORD dwRole;
return GetStateImageMapEnt( hwnd, iImage, pdwState, & dwRole );
}
// These are defined in the latest commctrl.h...
#ifndef TVM_MAPACCIDTOHTREEITEM
#define TVM_MAPACCIDTOHTREEITEM (TV_FIRST + 42)
#define TreeView_MapAccIDToHTREEITEM(hwnd, id) \
(HTREEITEM)SNDMSG((hwnd), TVM_MAPACCIDTOHTREEITEM, id, 0)
#define TVM_MAPHTREEITEMTOACCID (TV_FIRST + 43)
#define TreeView_MapHTREEITEMToAccID(hwnd, htreeitem) \
(UINT)SNDMSG((hwnd), TVM_MAPHTREEITEMTOACCID, (WPARAM)htreeitem, 0)
#endif
// TODO - need to handle the case where the treeview is 64-bit, the
// client is 32. SendMessage will truncate the retuend HTREEITEM,
// and the 32-bit client has no way of sending a 64-bit value to the
// 64-bit tree anyhow.
// Need to detect that case, and get the 64-bit helper server to help
// out.
// This should work tree-client 32t-32c, 64t-64c and 32t-64c.
HTREEITEM TVItemFromChildID( HWND hwnd, DWORD idChild )
{
Assert( idChild );
if( idChild == 0 )
return NULL;
HTREEITEM hItem = TreeView_MapAccIDToHTREEITEM( hwnd, idChild );
if( hItem )
{
return hItem;
}
#ifdef _WIN64
return NULL;
#else
// Fallback for older 32-bit comctls that don't implement the mapping
// message
return (HTREEITEM) idChild;
#endif
}
DWORD ChildIDFromTVItem( HWND hwnd, HTREEITEM htvi )
{
Assert( htvi != NULL );
if( htvi == NULL )
return 0;
DWORD dwid = TreeView_MapHTREEITEMToAccID( hwnd, htvi );
if( dwid != 0 )
{
return dwid;
}
#ifdef _WIN64
return 0;
#else
// Fallback for older 32-bit comctls that don't implement the mapping
// message
return (DWORD) htvi;
#endif
}