1300 lines
34 KiB
C++
1300 lines
34 KiB
C++
// Copyright (c) 1996-1999 Microsoft Corporation
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CLIENT.CPP
|
|
//
|
|
// Window client class.
|
|
//
|
|
// This handles navigation to other frame elements, and does its best
|
|
// to manage the client area. We recognize special classes, like listboxes,
|
|
// and those have their own classes to do stuff.
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
|
|
#include "oleacc_p.h"
|
|
#include "default.h"
|
|
#include "classmap.h"
|
|
#include "ctors.h"
|
|
#include "window.h"
|
|
#include "client.h"
|
|
#include "debug.h"
|
|
|
|
|
|
#define CH_PREFIX ((TCHAR)'&')
|
|
#define CCH_WINDOW_SHORTCUTMAX 32
|
|
#define CCH_SHORTCUT 16
|
|
|
|
|
|
extern HRESULT DirNavigate(HWND, long, VARIANT *);
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CreateClientObject()
|
|
//
|
|
// EXTERNAL function for CreatStdOle...
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
HRESULT CreateClientObject(HWND hwnd, long idObject, REFIID riid, void** ppvObject)
|
|
{
|
|
UNUSED(idObject);
|
|
|
|
InitPv(ppvObject);
|
|
|
|
if (!IsWindow(hwnd))
|
|
return(E_FAIL);
|
|
|
|
// Look for (and create) a suitable proxy/handler if one
|
|
// exists. Use CreateClient as default if none found.
|
|
// (FALSE => use client, as opposed to window, classes)
|
|
return FindAndCreateWindowClass( hwnd, FALSE, CLASS_ClientObject,
|
|
OBJID_CLIENT, 0, riid, ppvObject );
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CreateClient()
|
|
//
|
|
// INTERNAL function for CreateClientObject() and ::Clone()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
HRESULT CreateClient(HWND hwnd, long idChildCur, REFIID riid, void** ppvObject)
|
|
{
|
|
CClient * pclient;
|
|
HRESULT hr;
|
|
|
|
pclient = new CClient();
|
|
if (!pclient)
|
|
return(E_OUTOFMEMORY);
|
|
|
|
pclient->Initialize(hwnd, idChildCur);
|
|
|
|
hr = pclient->QueryInterface(riid, ppvObject);
|
|
if (!SUCCEEDED(hr))
|
|
delete pclient;
|
|
|
|
return(hr);
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::Initialize()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
void CClient::Initialize(HWND hwnd, long idChildCur)
|
|
{
|
|
m_hwnd = hwnd;
|
|
m_idChildCur = idChildCur;
|
|
|
|
// If this is a comboex32, we want to pick up a preceeding
|
|
// label, if one exists (just like we do for regular combos -
|
|
// which set m_fUseLabel to TRUE in their own ::Initialize().
|
|
// The combo will ask the parent comboex32 for its name, and
|
|
// it in turn will look for a label.
|
|
if( IsComboEx( m_hwnd ) )
|
|
{
|
|
m_fUseLabel = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::ValidateHwnd()
|
|
//
|
|
// This will validate VARIANTs for both HWND-children clients and normal
|
|
// clients. If m_cChildren is non-zero,
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
BOOL CClient::ValidateHwnd(VARIANT* pvar)
|
|
{
|
|
HWND hwndChild;
|
|
switch (pvar->vt)
|
|
{
|
|
case VT_ERROR:
|
|
if (pvar->scode != DISP_E_PARAMNOTFOUND)
|
|
return(FALSE);
|
|
// FALL THRU
|
|
|
|
case VT_EMPTY:
|
|
pvar->vt = VT_I4;
|
|
pvar->lVal = 0;
|
|
break;
|
|
|
|
#ifdef VT_I2_IS_VALID // It should not be valid. That's why this is removed.
|
|
case VT_I2:
|
|
pvar->vt = VT_I4;
|
|
pvar->lVal = (long)pvar->iVal;
|
|
// FALL THROUGH
|
|
#endif
|
|
|
|
case VT_I4:
|
|
if (pvar->lVal == 0)
|
|
break;
|
|
|
|
hwndChild = HwndFromHWNDID(m_hwnd, pvar->lVal);
|
|
|
|
// This works for top-level AND child windows
|
|
if (MyGetAncestor(hwndChild, GA_PARENT) != m_hwnd)
|
|
return(FALSE);
|
|
break;
|
|
|
|
default:
|
|
return(FALSE);
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::get_accChildCount()
|
|
//
|
|
// This handles both non-HWND and HWND children.
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::get_accChildCount(long *pcCount)
|
|
{
|
|
HWND hwndChild;
|
|
HRESULT hr;
|
|
|
|
hr = CAccessible::get_accChildCount(pcCount);
|
|
if (!SUCCEEDED(hr))
|
|
return hr;
|
|
|
|
// popup menus (CMenuPopup) can have a NULL hwnd if created for an 'invisible'
|
|
// menu. We probably shouldn't create them in the first place, but don't want
|
|
// to change what objects we expose at this stage - so instead special case
|
|
// NULL. This is to avoid calling GetWindow( NULL ), which would produce
|
|
// debug output complaining, and those annoy the stress team.
|
|
if( m_hwnd != NULL )
|
|
{
|
|
for (hwndChild = ::GetWindow(m_hwnd, GW_CHILD); hwndChild; hwndChild = ::GetWindow(hwndChild, GW_HWNDNEXT))
|
|
++(*pcCount);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::get_accName()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::get_accName(VARIANT varChild, BSTR *pszName)
|
|
{
|
|
InitPv(pszName);
|
|
|
|
//
|
|
// Validate--this does NOT accept a child ID.
|
|
//
|
|
if (! ValidateChild(&varChild))
|
|
return(E_INVALIDARG);
|
|
|
|
return(HrGetWindowName(m_hwnd, m_fUseLabel, pszName));
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::get_accRole()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::get_accRole(VARIANT varChild, VARIANT *pvarRole)
|
|
{
|
|
InitPvar(pvarRole);
|
|
|
|
//
|
|
// Validate--this does NOT accept a child ID.
|
|
//
|
|
if (!ValidateChild(&varChild))
|
|
return(E_INVALIDARG);
|
|
|
|
pvarRole->vt = VT_I4;
|
|
pvarRole->lVal = ROLE_SYSTEM_CLIENT;
|
|
|
|
return(S_OK);
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::get_accState()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::get_accState(VARIANT varChild, VARIANT *pvarState)
|
|
{
|
|
WINDOWINFO wi;
|
|
HWND hwndActive;
|
|
|
|
InitPvar(pvarState);
|
|
|
|
//
|
|
// Validate--this does NOT accept a child ID.
|
|
//
|
|
if (!ValidateChild(&varChild))
|
|
return(E_INVALIDARG);
|
|
|
|
pvarState->vt = VT_I4;
|
|
pvarState->lVal = 0;
|
|
|
|
//
|
|
// Are we the focus? Are we enabled, visible, etc?
|
|
//
|
|
if (!MyGetWindowInfo(m_hwnd, &wi))
|
|
{
|
|
pvarState->lVal |= STATE_SYSTEM_INVISIBLE;
|
|
return(S_OK);
|
|
}
|
|
|
|
if (!(wi.dwStyle & WS_VISIBLE))
|
|
pvarState->lVal |= STATE_SYSTEM_INVISIBLE;
|
|
|
|
if (wi.dwStyle & WS_DISABLED)
|
|
pvarState->lVal |= STATE_SYSTEM_UNAVAILABLE;
|
|
|
|
if (MyGetFocus() == m_hwnd)
|
|
pvarState->lVal |= STATE_SYSTEM_FOCUSED;
|
|
|
|
hwndActive = GetForegroundWindow();
|
|
|
|
if (hwndActive == MyGetAncestor(m_hwnd, GA_ROOT))
|
|
pvarState->lVal |= STATE_SYSTEM_FOCUSABLE;
|
|
|
|
return(S_OK);
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::get_accKeyboardShortcut()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::get_accKeyboardShortcut(VARIANT varChild, BSTR* pszShortcut)
|
|
{
|
|
InitPv(pszShortcut);
|
|
|
|
//
|
|
// Validate--this does NOT accept a child ID
|
|
//
|
|
if (!ValidateChild(&varChild))
|
|
return(E_INVALIDARG);
|
|
|
|
// reject child elements - shortcut key only applies to the overall
|
|
// control.
|
|
if ( varChild.lVal != 0 )
|
|
return(E_NOT_APPLICABLE);
|
|
|
|
return(HrGetWindowShortcut(m_hwnd, m_fUseLabel, pszShortcut));
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::get_accFocus()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::get_accFocus(VARIANT *pvarFocus)
|
|
{
|
|
HWND hwndFocus;
|
|
|
|
InitPvar(pvarFocus);
|
|
|
|
//
|
|
// This RETURNS a child ID.
|
|
//
|
|
hwndFocus = MyGetFocus();
|
|
|
|
//
|
|
// Is the current focus a child of us?
|
|
//
|
|
if (m_hwnd == hwndFocus)
|
|
{
|
|
pvarFocus->vt = VT_I4;
|
|
pvarFocus->lVal = 0;
|
|
}
|
|
else if (IsChild(m_hwnd, hwndFocus))
|
|
return(GetWindowObject(hwndFocus, pvarFocus));
|
|
|
|
return(S_OK);
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::accLocation()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::accLocation(long* pxLeft, long* pyTop,
|
|
long* pcxWidth, long* pcyHeight, VARIANT varChild)
|
|
{
|
|
RECT rc;
|
|
|
|
InitAccLocation(pxLeft, pyTop, pcxWidth, pcyHeight);
|
|
|
|
//
|
|
// Validate--this does NOT take a child ID
|
|
//
|
|
if (!ValidateChild(&varChild))
|
|
return(E_INVALIDARG);
|
|
|
|
MyGetRect(m_hwnd, &rc, FALSE);
|
|
MapWindowPoints(m_hwnd, NULL, (LPPOINT)&rc, 2);
|
|
|
|
*pxLeft = rc.left;
|
|
*pyTop = rc.top;
|
|
*pcxWidth = rc.right - rc.left;
|
|
*pcyHeight = rc.bottom - rc.top;
|
|
|
|
return(S_OK);
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::accSelect()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::accSelect( long lSelFlags, VARIANT varChild )
|
|
{
|
|
if( ! ValidateChild( & varChild ) ||
|
|
! ValidateSelFlags( lSelFlags ) )
|
|
return E_INVALIDARG;
|
|
|
|
if( lSelFlags != SELFLAG_TAKEFOCUS )
|
|
return E_NOT_APPLICABLE;
|
|
|
|
if( varChild.lVal )
|
|
return S_FALSE;
|
|
|
|
MySetFocus( m_hwnd );
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::accNavigate()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::accNavigate(long dwNavDir, VARIANT varStart, VARIANT * pvarEnd)
|
|
{
|
|
HWND hwndChild;
|
|
int gww;
|
|
|
|
InitPvar(pvarEnd);
|
|
|
|
//
|
|
// Validate--this accepts an HWND id.
|
|
//
|
|
if (!ValidateHwnd(&varStart) ||
|
|
!ValidateNavDir(dwNavDir, varStart.lVal))
|
|
return(E_INVALIDARG);
|
|
|
|
if (dwNavDir == NAVDIR_FIRSTCHILD)
|
|
{
|
|
gww = GW_HWNDNEXT;
|
|
hwndChild = ::GetWindow(m_hwnd, GW_CHILD);
|
|
if (!hwndChild)
|
|
return(S_FALSE);
|
|
|
|
goto NextPrevChild;
|
|
}
|
|
else if (dwNavDir == NAVDIR_LASTCHILD)
|
|
{
|
|
gww = GW_HWNDPREV;
|
|
|
|
hwndChild = ::GetWindow(m_hwnd, GW_CHILD);
|
|
if (!hwndChild)
|
|
return(S_FALSE);
|
|
|
|
// Start at the end and work backwards
|
|
hwndChild = ::GetWindow(hwndChild, GW_HWNDLAST);
|
|
|
|
goto NextPrevChild;
|
|
}
|
|
else if (!varStart.lVal)
|
|
return(GetParentToNavigate(OBJID_CLIENT, m_hwnd, OBJID_WINDOW,
|
|
dwNavDir, pvarEnd));
|
|
|
|
hwndChild = HwndFromHWNDID(m_hwnd, varStart.lVal);
|
|
|
|
if ((dwNavDir == NAVDIR_NEXT) || (dwNavDir == NAVDIR_PREVIOUS))
|
|
{
|
|
gww = ((dwNavDir == NAVDIR_NEXT) ? GW_HWNDNEXT : GW_HWNDPREV);
|
|
|
|
while (hwndChild = ::GetWindow(hwndChild, gww))
|
|
{
|
|
NextPrevChild:
|
|
if (IsWindowVisible(hwndChild))
|
|
return(GetWindowObject(hwndChild, pvarEnd));
|
|
}
|
|
}
|
|
else
|
|
return(DirNavigate(hwndChild, dwNavDir, pvarEnd));
|
|
|
|
return(S_FALSE);
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::accHitTest()
|
|
//
|
|
// This ALWAYS returns a real object.
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::accHitTest(long xLeft, long yTop, VARIANT *pvarHit)
|
|
{
|
|
HWND hwndChild;
|
|
POINT pt;
|
|
|
|
InitPvar(pvarHit);
|
|
|
|
pt.x = xLeft;
|
|
pt.y = yTop;
|
|
ScreenToClient(m_hwnd, &pt);
|
|
|
|
hwndChild = MyRealChildWindowFromPoint(m_hwnd, pt);
|
|
if (hwndChild)
|
|
{
|
|
if (hwndChild == m_hwnd)
|
|
{
|
|
pvarHit->vt = VT_I4;
|
|
pvarHit->lVal = 0;
|
|
return(S_OK);
|
|
}
|
|
else
|
|
return(GetWindowObject(hwndChild, pvarHit));
|
|
}
|
|
else
|
|
{
|
|
// Null window means point isn't in us at all...
|
|
return(S_FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::Next()
|
|
//
|
|
// This loops through non-HWND children first, then HWND children.
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::Next(ULONG celt, VARIANT *rgvar, ULONG* pceltFetched)
|
|
{
|
|
HWND hwndChild;
|
|
VARIANT* pvar;
|
|
long cFetched;
|
|
HRESULT hr;
|
|
|
|
if( m_idChildCur == -1 )
|
|
{
|
|
// If we're at the end, can't return any more...
|
|
*pceltFetched = 0;
|
|
return celt == 0 ? S_OK : S_FALSE;
|
|
}
|
|
|
|
SetupChildren();
|
|
|
|
// Can be NULL
|
|
if (pceltFetched)
|
|
*pceltFetched = 0;
|
|
|
|
// Grab the non-HWND dudes first.
|
|
if (!IsHWNDID(m_idChildCur) && (m_idChildCur < m_cChildren))
|
|
{
|
|
cFetched = 0;
|
|
|
|
hr = CAccessible::Next(celt, rgvar, (ULONG*)&cFetched);
|
|
if (!SUCCEEDED(hr))
|
|
return hr;
|
|
|
|
celt -= cFetched;
|
|
rgvar += cFetched;
|
|
|
|
if (pceltFetched)
|
|
*pceltFetched += cFetched;
|
|
|
|
if (!celt)
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
pvar = rgvar;
|
|
cFetched = 0;
|
|
|
|
if (!IsHWNDID(m_idChildCur))
|
|
{
|
|
Assert(m_idChildCur == m_cChildren);
|
|
hwndChild = ::GetWindow(m_hwnd, GW_CHILD);
|
|
}
|
|
else
|
|
{
|
|
hwndChild = HwndFromHWNDID(m_hwnd, m_idChildCur);
|
|
}
|
|
|
|
//
|
|
// Loop through our HWND children now
|
|
//
|
|
while (hwndChild && (cFetched < (long)celt))
|
|
{
|
|
hr = GetWindowObject(hwndChild, pvar);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
++pvar;
|
|
++cFetched;
|
|
}
|
|
else
|
|
{
|
|
// Failed - skip this one - but keep going.
|
|
TraceWarningHR( hr, TEXT("CClient::Next - GetWindowObject failed on hwnd 0x%p, skipping"), hwndChild );
|
|
}
|
|
|
|
hwndChild = ::GetWindow(hwndChild, GW_HWNDNEXT);
|
|
}
|
|
|
|
// Remember current position
|
|
// Have to special-case NULL - GetWindow(...) returns NULL
|
|
// when we reach the end - have to store a special value
|
|
// so we know that we're at the end the next time we're
|
|
// called.
|
|
if( hwndChild == NULL )
|
|
m_idChildCur = -1;
|
|
else
|
|
m_idChildCur = HWNDIDFromHwnd(m_hwnd, hwndChild);
|
|
|
|
//
|
|
// Fill in the number fetched
|
|
//
|
|
if (pceltFetched)
|
|
*pceltFetched += cFetched;
|
|
|
|
//
|
|
// Return S_FALSE if we grabbed fewer items than requested
|
|
//
|
|
return (cFetched < (long)celt) ? S_FALSE : S_OK;
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::Skip()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::Skip(ULONG celt)
|
|
{
|
|
HWND hwndT;
|
|
|
|
if( m_idChildCur == -1 )
|
|
{
|
|
// If we're at the end, can't return any more...
|
|
return celt == 0 ? S_FALSE : S_OK;
|
|
}
|
|
|
|
SetupChildren();
|
|
|
|
// Skip non-HWND items
|
|
if (!IsHWNDID(m_idChildCur) && (m_idChildCur < m_cChildren))
|
|
{
|
|
long dAway;
|
|
|
|
dAway = m_cChildren - m_idChildCur;
|
|
if (celt >= (DWORD)dAway)
|
|
{
|
|
celt -= dAway;
|
|
m_idChildCur = m_cChildren;
|
|
}
|
|
else
|
|
{
|
|
m_idChildCur += celt;
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
// Skip the HWND children next
|
|
if (!IsHWNDID(m_idChildCur))
|
|
{
|
|
Assert(m_idChildCur == m_cChildren);
|
|
hwndT = ::GetWindow(m_hwnd, GW_CHILD);
|
|
}
|
|
else
|
|
hwndT = HwndFromHWNDID(m_hwnd, m_idChildCur);
|
|
|
|
while (hwndT && (celt-- > 0))
|
|
{
|
|
hwndT = ::GetWindow(hwndT, GW_HWNDNEXT);
|
|
}
|
|
|
|
// Remember current position
|
|
// Have to special-case NULL - GetWindow(...) returns NULL
|
|
// when we reach the end - have to store a special value
|
|
// so we know that we're at the end the next time we're
|
|
// called.
|
|
if( hwndT == NULL )
|
|
m_idChildCur = -1;
|
|
else
|
|
m_idChildCur = HWNDIDFromHwnd(m_hwnd, hwndT);
|
|
|
|
return celt ? S_FALSE : S_OK;
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::Reset()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::Reset(void)
|
|
{
|
|
m_idChildCur = 0;
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CClient::Clone()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
STDMETHODIMP CClient::Clone(IEnumVARIANT** ppenum)
|
|
{
|
|
InitPv(ppenum);
|
|
|
|
// Look for (and create) a suitable proxy/handler if one
|
|
// exists. Use CreateClient as default if none found.
|
|
// (FALSE => use client, as opposed to window, classes)
|
|
return FindAndCreateWindowClass( m_hwnd, FALSE, CLASS_ClientObject,
|
|
OBJID_CLIENT, m_idChildCur, IID_IEnumVARIANT, (void **)ppenum );
|
|
}
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// GetTextString()
|
|
//
|
|
// Parameters: hwnd of the window to get the text from, and a boolean
|
|
// that indicates whether or not we should always allocate memory to
|
|
// return. I.E., if the window says the size of the text is 0, and
|
|
// fAllocIfEmpty is TRUE, then we'll still allocate 1 byte (size+1).
|
|
//
|
|
// This contains a bit of a hack. The way it was originally written, this
|
|
// will try to get the ENTIRE text of say, a RichEdit control, even if that
|
|
// document is HUGE. Eventually we want to support that, but we are going to
|
|
// need to do better than LocalAlloc. With a big document, we would page
|
|
// fault sometimes, because even though the memory is allocated, it
|
|
// may not be able to be paged in. JeffBog suggested that the way to
|
|
// check is to try to read/write both ends of the allocated space, and
|
|
// assume that if that works everything in between is OK too.
|
|
//
|
|
// So here's the temporary hack (BOGUS!)
|
|
// I am putting an artificial limit of 4096 bytes on the allocation.
|
|
// I am also going to do IsBadWritePtr on the thing, instead of just
|
|
// checking if the pointer returned by alloc is null. duh.
|
|
// --------------------------------------------------------------------------
|
|
LPTSTR GetTextString(HWND hwnd, BOOL fAllocIfEmpty)
|
|
{
|
|
UINT cchText;
|
|
LPTSTR lpText;
|
|
#define MAX_TEXT_SIZE 4096
|
|
|
|
//
|
|
// Look for a name property!
|
|
//
|
|
|
|
lpText = NULL;
|
|
|
|
if (!IsWindow(hwnd))
|
|
return (NULL);
|
|
//
|
|
// Barring that, use window text.
|
|
// BOGUS! Strip out the '&'.
|
|
//
|
|
cchText = SendMessageINT(hwnd, WM_GETTEXTLENGTH, 0, 0);
|
|
|
|
// hack
|
|
cchText = (cchText > MAX_TEXT_SIZE ? MAX_TEXT_SIZE : cchText);
|
|
|
|
// Allocate a buffer
|
|
if (cchText || fAllocIfEmpty)
|
|
{
|
|
lpText = (LPTSTR)LocalAlloc(LPTR, (cchText+1)*sizeof(TCHAR));
|
|
if (IsBadWritePtr (lpText,cchText+1))
|
|
return(NULL);
|
|
|
|
|
|
if (cchText)
|
|
SendMessage(hwnd, WM_GETTEXT, cchText+1, (LPARAM)lpText);
|
|
}
|
|
|
|
return(lpText);
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// GetLabelString()
|
|
//
|
|
// This walks backwards among peer windows to find a static field. It stops
|
|
// if it gets to the front or hits a group/tabstop, just like the dialog
|
|
// manager does.
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
LPTSTR GetLabelString(HWND hwnd)
|
|
{
|
|
HWND hwndLabel;
|
|
LONG lStyle;
|
|
LRESULT lResult;
|
|
LPTSTR lpszLabel;
|
|
|
|
lpszLabel = NULL;
|
|
|
|
if (!IsWindow(hwnd))
|
|
return (NULL);
|
|
|
|
hwndLabel = hwnd;
|
|
|
|
while (hwndLabel = ::GetWindow(hwndLabel, GW_HWNDPREV))
|
|
{
|
|
lStyle = GetWindowLong(hwndLabel, GWL_STYLE);
|
|
|
|
//
|
|
// Is this a static dude?
|
|
//
|
|
lResult = SendMessage(hwndLabel, WM_GETDLGCODE, 0, 0L);
|
|
if (lResult & DLGC_STATIC)
|
|
{
|
|
//
|
|
// Great, we've found our label.
|
|
//
|
|
lpszLabel = GetTextString(hwndLabel, FALSE);
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// Skip if invisible
|
|
// Note that we do this after checking if its a staic,
|
|
// so that we give invisible statics a chance. Using invisible
|
|
// statics is a easy workaround to add names to controls
|
|
// without changing the visual UI.
|
|
//
|
|
if (!(lStyle & WS_VISIBLE))
|
|
continue;
|
|
|
|
|
|
//
|
|
// Is this a tabstop or group? If so, bail out now.
|
|
//
|
|
if (lStyle & (WS_GROUP | WS_TABSTOP))
|
|
break;
|
|
}
|
|
|
|
return(lpszLabel);
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// HrGetWindowName()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
HRESULT HrGetWindowName(HWND hwnd, BOOL fLookForLabel, BSTR* pszName)
|
|
{
|
|
LPTSTR lpText;
|
|
|
|
lpText = NULL;
|
|
if (!IsWindow(hwnd))
|
|
return (E_INVALIDARG);
|
|
|
|
//
|
|
// Look for a name property!
|
|
//
|
|
|
|
//
|
|
// If use a label, do that instead
|
|
//
|
|
if (!fLookForLabel)
|
|
{
|
|
//
|
|
// Try using a label anyway if this control has no window text
|
|
// and the parent is a dialog.
|
|
//
|
|
lpText = GetTextString(hwnd, FALSE);
|
|
if (!lpText)
|
|
{
|
|
HWND hwndParent = MyGetAncestor( hwnd, GA_PARENT );
|
|
if( hwndParent && CLASS_DialogClient == GetWindowClass( hwndParent ) )
|
|
{
|
|
fLookForLabel = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fLookForLabel)
|
|
lpText = GetLabelString(hwnd);
|
|
|
|
if (! lpText)
|
|
return(S_FALSE);
|
|
|
|
//
|
|
// Strip out the mnemonic.
|
|
//
|
|
StripMnemonic(lpText);
|
|
|
|
// Get a BSTR
|
|
*pszName = TCharSysAllocString(lpText);
|
|
|
|
// Free our buffer
|
|
LocalFree((HANDLE)lpText);
|
|
|
|
// Did the BSTR succeed?
|
|
if (! *pszName)
|
|
return(E_OUTOFMEMORY);
|
|
|
|
return(S_OK);
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// HrGetWindowShortcut()
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
HRESULT HrGetWindowShortcut(HWND hwnd, BOOL fUseLabel, BSTR* pszShortcut)
|
|
{
|
|
//
|
|
// Get the window text, and see if the '&' character is in it.
|
|
//
|
|
LPTSTR lpText;
|
|
TCHAR chMnemonic;
|
|
|
|
if (!IsWindow(hwnd))
|
|
return (E_INVALIDARG);
|
|
|
|
lpText = NULL;
|
|
|
|
if (! fUseLabel)
|
|
{
|
|
//
|
|
// Try using a label anyway if this control has no window text
|
|
// and the parent is a dialog.
|
|
//
|
|
lpText = GetTextString(hwnd, FALSE);
|
|
if (!lpText)
|
|
{
|
|
HWND hwndParent = MyGetAncestor( hwnd, GA_PARENT );
|
|
if( hwndParent && CLASS_DialogClient == GetWindowClass( hwndParent ) )
|
|
{
|
|
fUseLabel = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fUseLabel)
|
|
lpText = GetLabelString(hwnd);
|
|
|
|
if (! lpText)
|
|
return(S_FALSE);
|
|
|
|
chMnemonic = StripMnemonic(lpText);
|
|
LocalFree((HANDLE)lpText);
|
|
|
|
//
|
|
// Is there a mnemonic?
|
|
//
|
|
if (chMnemonic)
|
|
{
|
|
//
|
|
// Make a string of the form "Alt+ch".
|
|
//
|
|
TCHAR szKey[2];
|
|
|
|
*szKey = chMnemonic;
|
|
*(szKey+1) = 0;
|
|
|
|
return(HrMakeShortcut(szKey, pszShortcut));
|
|
}
|
|
|
|
return(S_FALSE);
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// HrMakeShortcut()
|
|
//
|
|
// This takes a string for the hotkey, then combines it with the "Alt+%s"
|
|
// shortcut format to make the real string combination. If asked, it will
|
|
// free the hotkey string passed in.
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
HRESULT HrMakeShortcut(LPTSTR lpszKey, BSTR* pszShortcut)
|
|
{
|
|
TCHAR szFormat[CCH_SHORTCUT];
|
|
TCHAR szResult[CCH_WINDOW_SHORTCUTMAX];
|
|
|
|
// Get the format string
|
|
LoadString(hinstResDll, STR_MENU_SHORTCUT_FORMAT, szFormat,
|
|
ARRAYSIZE(szFormat));
|
|
|
|
// Make the result
|
|
wsprintf(szResult, szFormat, lpszKey);
|
|
|
|
// Alloc a BSTR of the result
|
|
*pszShortcut = TCharSysAllocString(szResult);
|
|
|
|
// Should we free the key string?
|
|
// Did the allocation fail?
|
|
if (!*pszShortcut)
|
|
return(E_OUTOFMEMORY);
|
|
else
|
|
return(S_OK);
|
|
}
|
|
|
|
|
|
|
|
// 'Slide' string along by one char, in-place, to effectively remove the
|
|
// char pointed to be pStr.
|
|
// eg. if pStr points to the 'd' of 'abcdefg', the string
|
|
// will be transformed to 'abcefg'.
|
|
// Note: the char pointed to by pStr is assumed to be a single-byte
|
|
// char (if compiled under ANSI - not an issue if compiled inder UNICODE)
|
|
// Note: Makes use of the fact that no DBCS char has NUL as the trail byte.
|
|
void SlideStrAndRemoveChar( LPTSTR pStr )
|
|
{
|
|
LPTSTR pLead = pStr + 1;
|
|
// Checking the trailing pStr ptr means that we continue until we've
|
|
// copied (not just encountered) the terminating NUL.
|
|
while( *pStr )
|
|
*pStr++ = *pLead++;
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// StripMnemonic()
|
|
//
|
|
// This removes the mnemonic prefix. However, if we see '&&', we keep
|
|
// one '&'.
|
|
//
|
|
//
|
|
// Modified to be DBCS 'aware' - uses CharNext() instead of ptr++ to
|
|
// advance through the string. Will only return shortcut char if its a
|
|
// single byte char, though. (Would have to change all usages of this
|
|
// function to allow return of a potentially DBCS char.) Will remove this
|
|
// restriction in the planned-for-future fully-UNICODE OLEACC.
|
|
// This restriction should not be much of a problem, because DBCS chars,
|
|
// which typically require an IME to compose, are very unlikely to be
|
|
// used as 'shortcut' chars. eg. Japanese Windows uses underlined roman
|
|
// chars as shortcut chars.
|
|
// (This will all be replaced by simpler code when we go UNICODE...)
|
|
// --------------------------------------------------------------------------
|
|
TCHAR StripMnemonic(LPTSTR lpszText)
|
|
{
|
|
TCHAR ch;
|
|
TCHAR chNext = 0;
|
|
|
|
while( *lpszText == (TCHAR)' ' )
|
|
lpszText = CharNext( lpszText );
|
|
|
|
while( ch = *lpszText )
|
|
{
|
|
lpszText = CharNext( lpszText );
|
|
|
|
if (ch == CH_PREFIX)
|
|
{
|
|
// Get the next character.
|
|
chNext = *lpszText;
|
|
|
|
// If it too is '&', then this isn't a mnemonic, it's the
|
|
// actual '&' character.
|
|
if (chNext == CH_PREFIX)
|
|
chNext = 0;
|
|
|
|
// Skip 'n' strip the '&' character
|
|
SlideStrAndRemoveChar( lpszText - 1 );
|
|
|
|
#ifdef UNICODE
|
|
CharLowerBuff(&chNext, 1);
|
|
#else
|
|
if( IsDBCSLeadByte( chNext ) )
|
|
{
|
|
// We're ignoring DBCS chars as shortcut chars
|
|
// - would need to change this func and all callers
|
|
// to handle a returned DB char otherwise.
|
|
// For the moment, we just ensure we don't return
|
|
// an 'orphaned' lead byte...
|
|
chNext = '\0';
|
|
}
|
|
else
|
|
{
|
|
CharLowerBuff(&chNext, 1);
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
return(chNext);
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// DirNavigate()
|
|
//
|
|
// Figures out which peer window is closest to us in the given direction.
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
HRESULT DirNavigate(HWND hwndSelf, long dwNavDir, VARIANT* pvarEnd)
|
|
{
|
|
HWND hwndPeer;
|
|
RECT rcSelf;
|
|
RECT rcPeer;
|
|
int dwClosest;
|
|
int dwT;
|
|
HWND hwndClosest;
|
|
|
|
if (!IsWindow(hwndSelf))
|
|
return (E_INVALIDARG);
|
|
|
|
MyGetRect(hwndSelf, &rcSelf, TRUE);
|
|
|
|
dwClosest = 0x7FFFFFFF;
|
|
hwndClosest = NULL;
|
|
|
|
for (hwndPeer = ::GetWindow(hwndSelf, GW_HWNDFIRST); hwndPeer;
|
|
hwndPeer = ::GetWindow(hwndPeer, GW_HWNDNEXT))
|
|
{
|
|
if ((hwndPeer == hwndSelf) || !IsWindowVisible(hwndPeer))
|
|
continue;
|
|
|
|
MyGetRect(hwndPeer, &rcPeer, TRUE);
|
|
|
|
dwT = 0x7FFFFFFF;
|
|
|
|
switch (dwNavDir)
|
|
{
|
|
case NAVDIR_LEFT:
|
|
//
|
|
// Bogus! Only try this one if it intersects us vertically
|
|
//
|
|
if (rcPeer.left < rcSelf.left)
|
|
dwT = rcSelf.left - rcPeer.left;
|
|
break;
|
|
|
|
case NAVDIR_UP:
|
|
//
|
|
// Bogus! Only try this one if it intersects us horizontally
|
|
//
|
|
if (rcPeer.top < rcSelf.top)
|
|
dwT = rcSelf.top - rcPeer.top;
|
|
break;
|
|
|
|
case NAVDIR_RIGHT:
|
|
//
|
|
// Bogus! Only try this one if it intersects us vertically
|
|
//
|
|
if (rcPeer.right > rcSelf.right)
|
|
dwT = rcPeer.right - rcSelf.right;
|
|
break;
|
|
|
|
case NAVDIR_DOWN:
|
|
//
|
|
// Bogus! Only try this one if it intersects us horizontally
|
|
//
|
|
if (rcPeer.bottom > rcSelf.bottom)
|
|
dwT = rcPeer.bottom - rcSelf.bottom;
|
|
break;
|
|
|
|
default:
|
|
AssertStr( TEXT("INVALID NAVDIR") );
|
|
}
|
|
|
|
if (dwT < dwClosest)
|
|
{
|
|
dwClosest = dwT;
|
|
hwndClosest = hwndPeer;
|
|
}
|
|
}
|
|
|
|
if (hwndClosest)
|
|
return(GetWindowObject(hwndClosest, pvarEnd));
|
|
else
|
|
return(S_FALSE);
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// InTheShell()
|
|
//
|
|
// Returns TRUE if the object is on the shell tray, desktop, or process.
|
|
//
|
|
// --------------------------------------------------------------------------
|
|
BOOL InTheShell(HWND hwnd, int nPart)
|
|
{
|
|
HWND hwndShell;
|
|
static TCHAR szShellTray[] = TEXT("Shell_TrayWnd");
|
|
DWORD idProcessUs;
|
|
DWORD idProcessShell;
|
|
|
|
hwndShell = GetShellWindow();
|
|
|
|
switch (nPart)
|
|
{
|
|
case SHELL_TRAY:
|
|
// Use the tray window instead.
|
|
hwndShell = FindWindowEx(NULL, NULL, szShellTray, NULL);
|
|
// Fall thru
|
|
|
|
case SHELL_DESKTOP:
|
|
if (!hwndShell)
|
|
return(FALSE);
|
|
return(MyGetAncestor(hwnd, GA_ROOT) == hwndShell);
|
|
|
|
case SHELL_PROCESS:
|
|
idProcessUs = NULL;
|
|
idProcessShell = NULL;
|
|
GetWindowThreadProcessId(hwnd, &idProcessUs);
|
|
GetWindowThreadProcessId(hwndShell, &idProcessShell);
|
|
return(idProcessUs && (idProcessUs == idProcessShell));
|
|
}
|
|
|
|
AssertStr( TEXT("GetShellWindow returned strange part") );
|
|
return(FALSE);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --- start of original comment ---
|
|
//
|
|
// We need a way for HWND and non-HWND children to live in the same
|
|
// namespace together. Since children pass up peer-to-peer navigation to
|
|
// their parent, we need a way for HWND children to identify themselves in
|
|
// the navigate call. Since HWND children are always objects, it is fine
|
|
// for the client parent to not accept HWND ids in all other methods. One
|
|
// can do it, but it is a lot of work.
|
|
//
|
|
// Examples to date of mixed:
|
|
// (1) Comboboxes (dropdown always a window, cur item may or may not be,
|
|
// button never is)
|
|
// (2) Toolbars (dropdown is a window, buttons aren't)
|
|
//
|
|
// We want the client manager to handle IEnumVARIANT, validation, etc.
|
|
//
|
|
// --- end of original comment ---
|
|
//
|
|
// A 'HWNDID' is basically a HWND squeezed (somehow) into a DWORD idChild.
|
|
//
|
|
// IsHWNDID checks if a idChild is one of these HWNDIDs, or just a regular
|
|
// idChild (ie. a 1-based child element index)
|
|
//
|
|
// HWNDIDFromHwnd and HwndFromHWNDID encode and decode HWNDs as idChilds.
|
|
//
|
|
// Previous versions of these didn't have a hwndParent parameter,
|
|
// and squeezed a HWND into bits 0..30, with bit31 set to 1 as the
|
|
// 'this is a HWND id' flag. That scheme doesn't work for HWNDs
|
|
// which have bt31 set... (these do exist on long-running systems -
|
|
// the top WORD of the HWND is a 'uniqueifier', which gets inc'd every
|
|
// time the slot - indicated by the bottom HWND - is reused. Of course,
|
|
// this implementation can change at any time in the furure, so we
|
|
// shouldn't rely on it, or rely on any bits being 'always 0' or
|
|
// otherwise.)
|
|
//
|
|
// The current sceheme still uses the high bit as a flag, but if set,
|
|
// the remaining bits are now a count into the parent window's children
|
|
// chain.
|
|
//
|
|
//
|
|
// It may be possible to remove these althogther - if the destination object
|
|
// corresponds to a full HWND, instead of returning a HWNDID, instead return
|
|
// the full IAccessible for that object. (Still have to figure out what happens
|
|
// when that IAccessible needs to navigate to one of its siblings, though.)
|
|
|
|
BOOL IsHWNDID( DWORD id )
|
|
{
|
|
// hight bit indicates that it represents a HWND.
|
|
return id & 0x80000000;
|
|
}
|
|
|
|
DWORD HWNDIDFromHwnd( HWND hwndParent, HWND hwnd )
|
|
{
|
|
// Traverse the child list, counting as we go, till we hit the HWND we want...
|
|
int i = 0;
|
|
HWND hChild = GetWindow( hwndParent, GW_CHILD );
|
|
|
|
while( hChild != NULL )
|
|
{
|
|
if( hChild == hwnd )
|
|
{
|
|
return i | 0x80000000;
|
|
}
|
|
|
|
i++;
|
|
hChild = GetWindow( hChild, GW_HWNDNEXT );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
HWND HwndFromHWNDID( HWND hwndParent, DWORD id )
|
|
{
|
|
// Traverse the child list, till we get to the one with this index...
|
|
int i = id & ~ 0x80000000;
|
|
|
|
HWND hChild = GetWindow( hwndParent, GW_CHILD );
|
|
|
|
while( i != 0 && hChild != NULL )
|
|
{
|
|
i--;
|
|
hChild = GetWindow( hChild, GW_HWNDNEXT );
|
|
}
|
|
|
|
return hChild;
|
|
}
|