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

754 lines
20 KiB
C++

// Copyright (c) 1996-1999 Microsoft Corporation
// --------------------------------------------------------------------------
//
// TABCTRL.CPP
//
// This knows how to talk to COMCTL32's tab control.
//
// --------------------------------------------------------------------------
#include "oleacc_p.h"
#include "default.h"
#include "client.h"
#include "tabctrl.h"
#define NOSTATUSBAR
#define NOUPDOWN
#define NOMENUHELP
#define NOTRACKBAR
#define NODRAGLIST
#define NOPROGRESS
#define NOHOTKEY
#define NOTREEVIEW
#define NOTOOLBAR
#define NOANIMATE
#include <commctrl.h>
#include "Win64Helper.h"
#include <tchar.h>
#define MAX_NAME_SIZE 128 // maximum size of name of tab control
// Used for Tab control code
// cbExtra for the tray is 8, so pick something even bigger, 16
#define CBEXTRA_TRAYTAB 16
// --------------------------------------------------------------------------
//
// CreateTabControlClient()
//
// EXTERNAL for CreateClientObject()
//
// --------------------------------------------------------------------------
HRESULT CreateTabControlClient(HWND hwnd, long idChildCur,REFIID riid, void** ppvTab)
{
CTabControl32 * ptab;
HRESULT hr;
InitPv(ppvTab);
ptab = new CTabControl32(hwnd, idChildCur);
if (! ptab)
return(E_OUTOFMEMORY);
hr = ptab->QueryInterface(riid, ppvTab);
if (!SUCCEEDED(hr))
delete ptab;
return(hr);
}
// --------------------------------------------------------------------------
//
// IsReallyVisible()
//
// Internal, used for wizard compat.
// Wizards have a tab control that is has 'visible' state; but is covered
// up by sibling dialogs (the actual sheets) so that to a user it is not
// visible.
//
// They (the comctl people who own the property sheet code) can't make it
// actually invisible for compat reasons...
//
// So we have to take this into account when calculating our 'visible'
// flag.
// If we didn't do this, Narrator and friends would think the control was
// visible, and would read it out when reading the window.
//
// --------------------------------------------------------------------------
BOOL IsReallyVisible( HWND hwnd )
{
// TODO - check own visible state...
RECT rc;
GetWindowRect( hwnd, & rc );
for( HWND hPrev = GetWindow( hwnd, GW_HWNDPREV ) ; hPrev ; hPrev = GetWindow( hPrev, GW_HWNDPREV ) )
{
// if window is visible, and covers own rectangle, then we're invisible...
if( IsWindowVisible( hPrev ) )
{
RECT rcSibling;
GetWindowRect( hPrev, & rcSibling );
if( rcSibling.left <= rc.left
&& rcSibling.right >= rc.right
&& rcSibling.top <= rc.top
&& rcSibling.bottom >= rc.bottom )
{
return FALSE;
}
}
}
// No predecessors whoch obscure us - visible by default...
return TRUE;
}
// --------------------------------------------------------------------------
//
// CTabControl32::CTabControl32()
//
// --------------------------------------------------------------------------
CTabControl32::CTabControl32(HWND hwnd, long idChild)
: CClient( CLASS_TabControlClient )
{
Initialize(hwnd, idChild);
}
// --------------------------------------------------------------------------
//
// CTabControl32::SetupChildren()
//
// --------------------------------------------------------------------------
void CTabControl32::SetupChildren(void)
{
m_cChildren = SendMessageINT(m_hwnd, TCM_GETITEMCOUNT, 0, 0L);
}
// --------------------------------------------------------------------------
//
// CTabControl32::get_accName()
//
// --------------------------------------------------------------------------
STDMETHODIMP CTabControl32::get_accName(VARIANT varChild, BSTR* pszName)
{
LPTSTR lpszName;
HRESULT hr;
InitPv(pszName);
if (!ValidateChild(&varChild))
return(E_INVALIDARG);
if (!varChild.lVal)
{
if (InTheShell(m_hwnd, SHELL_TRAY))
return(HrCreateString(STR_TRAY, pszName));
else
return(CClient::get_accName(varChild, pszName));
}
varChild.lVal--;
// Get the unstripped name
hr = GetTabControlString (varChild.lVal,&lpszName);
if( ! lpszName )
return hr; // could be S_FALSE or E_erro_code
if (*lpszName)
{
StripMnemonic(lpszName);
*pszName = TCharSysAllocString(lpszName);
}
LocalFree (lpszName);
return(*pszName ? S_OK : S_FALSE);
}
// --------------------------------------------------------------------------
//
// CTabControl32::get_accKeyboardShortcut()
//
// --------------------------------------------------------------------------
STDMETHODIMP CTabControl32::get_accKeyboardShortcut(VARIANT varChild, BSTR* pszShortcut)
{
TCHAR chMnemonic = 0;
LPTSTR lpszName;
HRESULT hr;
InitPv(pszShortcut);
if (!ValidateChild(&varChild))
return(E_INVALIDARG);
if (!varChild.lVal)
{
if (InTheShell(m_hwnd, SHELL_TRAY))
return(S_FALSE); // no shortcut keys for these
else
return(CClient::get_accKeyboardShortcut(varChild, pszShortcut));
}
varChild.lVal--;
// Get the unstripped name
hr = GetTabControlString (varChild.lVal,&lpszName);
if( ! lpszName )
return hr; // could be S_FALSE or E_error_code
if (*lpszName)
chMnemonic = StripMnemonic(lpszName);
LocalFree (lpszName);
//
// 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);
}
// --------------------------------------------------------------------------
//
// CTabControl32::GetTabControlString()
//
// Get the name of the tab. There are three ways to do this -
// You can just ask using the standard messages, if the tab is an
// owner drawn tab owned by the shell we happen to know that the
// item data is the hwnd, so we can just the hwnd's text, or if both
// of those fail, we can try to get it from a tooltip. Since we need to
// do this for both name and keyboard shortcut, we'll write a private
// method to get the unstripped name.
//
// Parameters:
// int ChildIndex - the zero based index of the tab we want to get
// LPTSTR* ppszName - pointer that will be LocalAlloc'ed and filled
// in with the name. Caller must LocalFree it.
//
// Returns:
//
// On Success:
// returns S_TRUE, *ppszName will be non-NULL, caller must LocalFree() it.
//
// On Failure:
// returns S_FALSE - no name available. *ppszName set to NULL.
// ...or...
// returns COM Failure code (including E_OUTOFMEMORY) - com/memory error.
// *ppszName set to NULL.
//
// Note: caller should take care if using "FAILED( hr )" to examine the return
// value of this method, since it does treats both S_OK and S_FALSE as 'success'.
// It may be better to check that *ppszName is non-NULL.
//
// --------------------------------------------------------------------------
STDMETHODIMP CTabControl32::GetTabControlString(int ChildIndex, LPTSTR* ppszName)
{
HWND hwndToolTip;
LPTSTR pszName = NULL;
// Set this to NULL now, in case we return an error code (or S_FALSE) later...
// (We'll set it to a valid return value later if we succeed...)
*ppszName = NULL;
// Try to get name of tab the easy way, by asking first.
// Allocate a TCITEM structure in the process that owns the window,
// so they can just read it/write into it when we send the message.
// Have to use SharedWrite to set up the structure.
TCHAR tchText[MAX_NAME_SIZE + 1] = {0};
TCITEM tci;
memset(&tci, 0, sizeof(TCITEM));
tci.mask = TCIF_TEXT;
tci.pszText = tchText;
tci.cchTextMax = MAX_NAME_SIZE;
if (SUCCEEDED(XSend_TabCtrl_GetItem(m_hwnd, TCM_GETITEM, ChildIndex, &tci)))
{
if (tci.pszText && *tci.pszText)
{
pszName = (LPTSTR)LocalAlloc (LPTR,(lstrlen(tci.pszText)+1)*sizeof(TCHAR));
if (!pszName)
return E_OUTOFMEMORY;
lstrcpy(pszName, tci.pszText);
*ppszName = pszName;
return S_OK;
}
}
// OK, Step 2 - another hack. If this is the tray, we know the item data
// is an HWND, so get the window's text. So the tricky code is
// really: Is this from the tray?
struct TCITEM_SHELL
{
TCITEM tci;
BYTE bExtra[ CBEXTRA_TRAYTAB ];
// Sending TCM_GETITEM with mask of TCIF_PARAM will overwrite bytes
// from tci.lParam onwards. The length is set to sizeof(LPARAM) by
// default, so it's usually not an issue. But the taskbar uses
// TCM_SETITEMEXTRA to reserve an extra DWORD - so TCM_GETIEEM+TCIF_PARAM
// sent to the taskbar will overwrite the lParam field plus the following
// DWORD. It's really really really important to have something that can
// take this, otherwise it's bye bye stack...
};
TCITEM_SHELL tcis;
pszName = NULL;
tcis.tci.mask = TCIF_PARAM;
tcis.tci.pszText = 0;
tcis.tci.cchTextMax = 0;
if (InTheShell(m_hwnd, SHELL_TRAY)
&& SUCCEEDED(XSend_TabCtrl_GetItem(m_hwnd, TCM_GETITEM, ChildIndex, &tcis.tci)))
{
hwndToolTip = (HWND)tcis.tci.lParam;
pszName = GetTextString (hwndToolTip,TRUE);
if (pszName && *pszName)
{
*ppszName = pszName;
return S_OK;
}
}
LocalFree (pszName);
//
// If we still don't have a name, try method 3 - get name of tab via
// tooltip method
//
// TODO (micw) Need to test getting name from tool tip
hwndToolTip = (HWND)SendMessage(m_hwnd, TCM_GETTOOLTIPS, 0, 0);
if (!hwndToolTip)
return(S_FALSE);
// See if there's tool tip text
tchText[0] = 0;
TOOLINFO ti;
memset(&ti, 0, SIZEOF_TOOLINFO );
ti.cbSize = SIZEOF_TOOLINFO;
ti.lpszText = tchText;
ti.hwnd = m_hwnd;
ti.uId = ChildIndex;
if (SUCCEEDED(XSend_ToolTip_GetItem(hwndToolTip, TTM_GETTEXT, 0, &ti, MAX_NAME_SIZE)))
{
if (*ti.lpszText)
{
pszName = (LPTSTR)LocalAlloc (LPTR,(lstrlen(ti.lpszText)+1)*sizeof(TCHAR));
if (!pszName)
return E_OUTOFMEMORY;
lstrcpy(pszName, ti.lpszText);
*ppszName = pszName;
return S_OK;
}
}
return S_FALSE;
}
// --------------------------------------------------------------------------
//
// CTabControl32::get_accRole()
//
// --------------------------------------------------------------------------
STDMETHODIMP CTabControl32::get_accRole(VARIANT varChild, VARIANT* pvarRole)
{
InitPvar(pvarRole);
if (!ValidateChild(&varChild))
return(E_INVALIDARG);
pvarRole->vt = VT_I4;
if (!varChild.lVal)
pvarRole->lVal = ROLE_SYSTEM_PAGETABLIST;
else
pvarRole->lVal = ROLE_SYSTEM_PAGETAB;
return(S_OK);
}
// --------------------------------------------------------------------------
//
// CTabControl32::get_accState()
//
// --------------------------------------------------------------------------
STDMETHODIMP CTabControl32::get_accState(VARIANT varChild, VARIANT* pvarState)
{
InitPvar(pvarState);
if( ! ValidateChild( & varChild ) )
return E_INVALIDARG;
if( !varChild.lVal )
{
// Workarond for wizard property sheets...
//
// Want to make the 'visible' but covered-up (so not visible to user)
// tab strip have a state of invisible, so Narrator and friends don't
// read it out.
//
// Do this by calling through to CClient::get_accState as usual,
// then add in the invisible bit after, if necessary.
HRESULT hr = CClient::get_accState( varChild, pvarState );
if( hr == S_OK
&& pvarState->vt == VT_I4
&& ! ( pvarState->lVal & STATE_SYSTEM_INVISIBLE ) )
{
if( ! IsReallyVisible( m_hwnd ) )
{
pvarState->lVal |= STATE_SYSTEM_INVISIBLE;
}
}
return hr;
}
pvarState->vt = VT_I4;
pvarState->lVal = 0;
TCITEM tci;
memset(&tci, 0, sizeof(TCITEM));
tci.mask = TCIF_STATE;
if (SUCCEEDED(XSend_TabCtrl_GetItem(m_hwnd, TCM_GETITEM, varChild.lVal-1, &tci)))
{
if (tci.dwState & TCIS_BUTTONPRESSED)
pvarState->lVal |= STATE_SYSTEM_PRESSED;
} else
{
pvarState->lVal |= STATE_SYSTEM_INVISIBLE;
}
if( IsClippedByWindow( this, varChild, m_hwnd ) )
{
pvarState->lVal |= STATE_SYSTEM_INVISIBLE | STATE_SYSTEM_OFFSCREEN;
}
// It is always selectable
pvarState->lVal |= STATE_SYSTEM_SELECTABLE;
// Is this the current one?
if (SendMessage(m_hwnd, TCM_GETCURSEL, 0, 0) == (varChild.lVal-1))
pvarState->lVal |= STATE_SYSTEM_SELECTED;
if (MyGetFocus() == m_hwnd)
{
pvarState->lVal |= STATE_SYSTEM_FOCUSABLE;
if (SendMessage(m_hwnd, TCM_GETCURFOCUS, 0, 0) == (varChild.lVal-1))
pvarState->lVal |= STATE_SYSTEM_FOCUSED;
}
return(S_OK);
}
// --------------------------------------------------------------------------
//
// CTabControl32::get_accFocus()
//
// --------------------------------------------------------------------------
STDMETHODIMP CTabControl32::get_accFocus(VARIANT* pvarChild)
{
HRESULT hr;
long lCur;
hr = CClient::get_accFocus(pvarChild);
if (!SUCCEEDED(hr) || (pvarChild->vt != VT_I4) || (pvarChild->lVal != 0))
return(hr);
//
// This window has the focus.
//
lCur = SendMessageINT(m_hwnd, TCM_GETCURFOCUS, 0, 0L);
pvarChild->lVal = lCur+1;
return(S_OK);
}
// --------------------------------------------------------------------------
//
// CTabControl32::get_accSelection()
//
// --------------------------------------------------------------------------
STDMETHODIMP CTabControl32::get_accSelection(VARIANT* pvarChild)
{
long lCur;
InitPvar(pvarChild);
lCur = SendMessageINT(m_hwnd, TCM_GETCURSEL, 0, 0L);
if (lCur != -1)
{
pvarChild->vt = VT_I4;
pvarChild->lVal = lCur+1;
return(S_OK);
}
else
return(S_FALSE);
}
// --------------------------------------------------------------------------
//
// CTabControl32::get_accDefaultAction()
//
// --------------------------------------------------------------------------
STDMETHODIMP CTabControl32::get_accDefaultAction(VARIANT varChild, BSTR* pszDefAction)
{
InitPv(pszDefAction);
if (!ValidateChild(&varChild))
return(E_INVALIDARG);
if (!varChild.lVal)
return(CClient::get_accDefaultAction(varChild, pszDefAction));
return(HrCreateString(STR_TAB_SWITCH, pszDefAction));
}
// --------------------------------------------------------------------------
//
// CTabControl32::accSelect()
//
// --------------------------------------------------------------------------
STDMETHODIMP CTabControl32::accSelect(long flags, VARIANT varChild)
{
if (!ValidateChild(&varChild) || !ValidateSelFlags(flags))
return(E_INVALIDARG);
if (!varChild.lVal)
return(CClient::accSelect(flags, varChild));
if (flags & ~(SELFLAG_TAKEFOCUS | SELFLAG_TAKESELECTION))
return(E_NOT_APPLICABLE);
if (flags & SELFLAG_TAKEFOCUS)
{
MySetFocus(m_hwnd);
SendMessage(m_hwnd, TCM_SETCURFOCUS, varChild.lVal-1, 0);
}
if (flags & SELFLAG_TAKESELECTION)
SendMessage(m_hwnd, TCM_SETCURSEL, varChild.lVal-1, 0);
return(S_OK);
}
// --------------------------------------------------------------------------
//
// CTabControl32::accLocation()
//
// --------------------------------------------------------------------------
STDMETHODIMP CTabControl32::accLocation(long* pxLeft, long* pyTop,
long* pcxWidth, long* pcyHeight, VARIANT varChild)
{
LPRECT lprcShared;
HANDLE hProcess;
RECT rcLocation;
InitAccLocation(pxLeft, pyTop, pcxWidth, pcyHeight);
if (!ValidateChild(&varChild))
return(E_INVALIDARG);
if (!varChild.lVal)
return(CClient::accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, varChild));
//
// Get the tab item rect
//
lprcShared = (LPRECT)SharedAlloc(sizeof(RECT),m_hwnd,&hProcess);
if (!lprcShared)
return(E_OUTOFMEMORY);
if (SendMessage(m_hwnd, TCM_GETITEMRECT, varChild.lVal-1, (LPARAM)lprcShared))
{
SharedRead (lprcShared,&rcLocation,sizeof(RECT),hProcess);
MapWindowPoints(m_hwnd, NULL, (LPPOINT)&rcLocation, 2);
*pxLeft = rcLocation.left;
*pyTop = rcLocation.top;
*pcxWidth = rcLocation.right - rcLocation.left;
*pcyHeight = rcLocation.bottom - rcLocation.top;
}
SharedFree(lprcShared,hProcess);
return(S_OK);
}
// --------------------------------------------------------------------------
//
// CTabControl32::accNavigate()
//
// BOGUS! Doesn't handle multiple row or vertical right now.
//
// --------------------------------------------------------------------------
STDMETHODIMP CTabControl32::accNavigate(long dwNavFlags, VARIANT varStart,
VARIANT* pvarEnd)
{
long lEnd;
InitPvar(pvarEnd);
if (!ValidateChild(&varStart) || !ValidateNavDir(dwNavFlags, varStart.lVal))
return(E_INVALIDARG);
if (dwNavFlags == NAVDIR_FIRSTCHILD)
dwNavFlags = NAVDIR_NEXT;
else if (dwNavFlags == NAVDIR_LASTCHILD)
{
varStart.lVal = m_cChildren + 1;
dwNavFlags = NAVDIR_PREVIOUS;
}
else if (!varStart.lVal)
return(CClient::accNavigate(dwNavFlags, varStart, pvarEnd));
switch (dwNavFlags)
{
case NAVDIR_NEXT:
case NAVDIR_RIGHT:
lEnd = varStart.lVal + 1;
if (lEnd > m_cChildren)
lEnd = 0;
break;
case NAVDIR_PREVIOUS:
case NAVDIR_LEFT:
lEnd = varStart.lVal - 1;
break;
default:
lEnd = 0;
break;
}
if (lEnd)
{
pvarEnd->vt = VT_I4;
pvarEnd->lVal = lEnd;
return(S_OK);
}
else
return(S_FALSE);
}
// --------------------------------------------------------------------------
//
// CTabControl32::accHitTest()
//
// --------------------------------------------------------------------------
STDMETHODIMP CTabControl32::accHitTest(long x, long y, VARIANT* pvarHit)
{
HRESULT hr;
long lItem;
LPTCHITTESTINFO lptchShared;
HANDLE hProcess;
POINT ptTest;
InitPvar(pvarHit);
// Is the point in us?
hr = CClient::accHitTest(x, y, pvarHit);
// #11150, CWO, 1/27/97, Replaced !SUCCEEDED with !S_OK
if ((hr != S_OK) || (pvarHit->vt != VT_I4))
return(hr);
// What item is this over?
lptchShared = (LPTCHITTESTINFO)SharedAlloc(sizeof(TCHITTESTINFO),m_hwnd,&hProcess);
if (!lptchShared)
return(E_OUTOFMEMORY);
ptTest.x = x;
ptTest.y = y;
ScreenToClient(m_hwnd, &ptTest);
SharedWrite(&ptTest,&lptchShared->pt,sizeof(POINT),hProcess);
lItem = SendMessageINT(m_hwnd, TCM_HITTEST, 0, (LPARAM)lptchShared);
SharedFree(lptchShared,hProcess);
// Note that if the point isn't over an item, TCM_HITTEST returns -1,
// and -1+1 is zero, which is self.
pvarHit->vt = VT_I4;
pvarHit->lVal = lItem+1;
return(S_OK);
}
// --------------------------------------------------------------------------
//
// CTabControl32::accDoDefaultAction()
//
// --------------------------------------------------------------------------
STDMETHODIMP CTabControl32::accDoDefaultAction(VARIANT varChild)
{
RECT rcLoc;
if (!ValidateChild(&varChild))
return(E_INVALIDARG);
if (!varChild.lVal)
return(CClient::accDoDefaultAction(varChild));
accLocation(&rcLoc.left,&rcLoc.top,&rcLoc.right,&rcLoc.bottom,varChild);
// this will check if WindowFromPoint at the click point is the same
// as m_hwnd, and if not, it won't click. Cool!
if (ClickOnTheRect(&rcLoc,m_hwnd,FALSE))
return (S_OK);
return(E_NOT_APPLICABLE);
}