Windows2000/private/shell/shell32/linkwnd.cpp
2020-09-30 17:12:32 +02:00

2938 lines
79 KiB
C++

// linkwnd.cpp - implementation of CLinkWindow
// [scotthan] - created 10/7/98
#include "shellprv.h"
#include "ids.h"
#include <oleacc.h>
#define IS_LINK(pBlock) ((pBlock) && (pBlock)->iLink != INVALID_LINK_INDEX)
#ifndef ARRAYSIZE
#define ARRAYSIZE(a) (sizeof(a)/sizeof(*a))
#endif
#ifndef POINTSPERRECT
#define POINTSPERRECT (sizeof(RECT)/sizeof(POINT))
#endif
#ifndef RECTWIDTH
#define RECTWIDTH(prc) ((prc)->right - (prc)->left)
#endif
#ifndef RECTHEIGHT
#define RECTHEIGHT(prc) ((prc)->bottom - (prc)->top)
#endif
#define TESTKEYSTATE(vk) ((GetKeyState(vk) & 0x8000)!=0)
#define LINKCOLOR_BKGND COLOR_WINDOW
#define LINKCOLOR_ENABLED GetSysColor( GetCOLOR_HOTLIGHT() )
#define LINKCOLOR_DISABLED GetSysColor( COLOR_GRAYTEXT )
#define CF_SETCAPTURE 0x0001
#define CF_SETFOCUS 0x0002
// KEYBOARDCUES helpes
#ifdef KEYBOARDCUES
void _InitializeUISTATE(IN HWND hwnd, IN OUT UINT* puFlags);
BOOL _HandleWM_UPDATEUISTATE(IN WPARAM wParam, IN LPARAM lParam, IN OUT UINT* puFlags);
#endif
// class CAccessibleBase
// common IAccessible implementation.
class CAccessibleBase : public IAccessible, public IOleWindow
{
public:
CAccessibleBase(const HWND& hwnd)
: _cRef(1), _ptiAcc(NULL), _hwnd(hwnd)
{
DllAddRef();
}
virtual ~CAccessibleBase()
{
ATOMICRELEASE(_ptiAcc);
}
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// IOleWindow
STDMETHODIMP GetWindow(HWND* phwnd);
STDMETHODIMP ContextSensitiveHelp(BOOL fEnterMode) { return E_NOTIMPL; }
// IDispatch
STDMETHODIMP GetTypeInfoCount(UINT* pctinfo);
STDMETHODIMP GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo** pptinfo);
STDMETHODIMP GetIDsOfNames(REFIID riid, OLECHAR** rgszNames, UINT cNames,
LCID lcid, DISPID* rgdispid);
STDMETHODIMP Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS* pdispparams, VARIANT* pvarResult,
EXCEPINFO* pexcepinfo, UINT* puArgErr);
// IAccessible
STDMETHODIMP get_accParent(IDispatch** ppdispParent);
STDMETHODIMP get_accChildCount(long* pcChildren);
STDMETHODIMP get_accChild(VARIANT varChildIndex, IDispatch** ppdispChild);
STDMETHODIMP get_accValue(VARIANT varChild, BSTR* pbstrValue);
STDMETHODIMP get_accDescription(VARIANT varChild, BSTR* pbstrDescription);
STDMETHODIMP get_accRole(VARIANT varChild, VARIANT* pvarRole);
STDMETHODIMP get_accState(VARIANT varChild, VARIANT* pvarState);
STDMETHODIMP get_accHelp(VARIANT varChild, BSTR* pbstrHelp);
STDMETHODIMP get_accHelpTopic(BSTR* pbstrHelpFile, VARIANT varChild, long* pidTopic);
STDMETHODIMP get_accKeyboardShortcut(VARIANT varChild, BSTR* pbstrKeyboardShortcut);
STDMETHODIMP get_accFocus(VARIANT FAR* pvarFocusChild);
STDMETHODIMP get_accSelection(VARIANT FAR* pvarSelectedChildren);
STDMETHODIMP get_accDefaultAction(VARIANT varChild, BSTR* pbstrDefaultAction);
STDMETHODIMP accSelect(long flagsSelect, VARIANT varChild);
STDMETHODIMP accLocation(long* pxLeft, long* pyTop, long* pcxWidth, long* pcyHeight, VARIANT varChild);
STDMETHODIMP accNavigate(long navDir, VARIANT varStart, VARIANT* pvarEndUpAt);
STDMETHODIMP accHitTest(long xLeft, long yTop, VARIANT* pvarChildAtPoint);
STDMETHODIMP put_accName(VARIANT varChild, BSTR bstrName);
STDMETHODIMP put_accValue(VARIANT varChild, BSTR bstrValue);
protected:
virtual UINT GetDefaultActionStringID() const = 0;
private:
ULONG _cRef;
ITypeInfo* _ptiAcc;
const HWND& _hwnd;
// Thunked OLEACC defs from winuser.h
#ifndef OBJID_WINDOW
#define OBJID_WINDOW 0x00000000
#endif//OBJID_WINDOW
#ifndef OBJID_TITLEBAR
#define OBJID_TITLEBAR 0xFFFFFFFE
#endif//OBJID_TITLEBAR
#ifndef OBJID_CLIENT
#define OBJID_CLIENT 0xFFFFFFFC
#endif//OBJID_CLIENT
#ifndef CHILDID_SELF
#define CHILDID_SELF 0
#endif//CHILDID_SELF
#define VALIDATEACCCHILD( varChild, idChild, hrFail ) \
if( !(VT_I4 == varChild.vt && idChild == varChild.lVal) ) {return hrFail;}
};
#define TEST_CAPTURE(fTest) ((_fCapture & fTest) != 0)
#define MODIFY_CAPTURE(fSet, fRemove) {if(fSet){_fCapture |= fSet;} if(fRemove){_fCapture &= ~fRemove;}}
#define RESET_CAPTURE() {_fCapture=0;}
// class CLinkWindow
class CLinkWindow : public CAccessibleBase
{
public:
CLinkWindow();
virtual ~CLinkWindow();
// IAccessible specialization
STDMETHODIMP get_accName(VARIANT varChild, BSTR* pbstrName);
STDMETHODIMP accDoDefaultAction(VARIANT varChild);
private:
// CAccessibleBase overrides
UINT GetDefaultActionStringID() const { return IDS_LINKWINDOW_DEFAULTACTION; }
// Private types
struct RECTLISTENTRY // rect list member
{
RECT rc;
RECTLISTENTRY* next;
};
struct TEXTBLOCK // text segment data
{
int iLink; // index of link (INVALID_LINK_INDEX if static text)
DWORD state; // state bits
TCHAR szID[MAX_LINKID_TEXT]; // link identifier.
TEXTBLOCK* next; // next block
RECTLISTENTRY* rgrle; // list of bounding rectangle(s)
TCHAR* pszText; // text
TCHAR* pszUrl; // URL.
TEXTBLOCK();
~TEXTBLOCK();
void AddRect(const RECT& rc);
void FreeRects();
};
// Utility methods
BOOL CreateFonts(BOOL bRecreate = FALSE);
void DestroyFonts();
HCURSOR GetLinkCursor();
void Parse(LPCTSTR pszText = NULL);
BOOL Add(TEXTBLOCK* pAdd);
TEXTBLOCK* FindLink(int iLink) const;
void FreeBlocks();
void SetText(LPCTSTR pszText);
int GetText(BOOL bForParsing, LPTSTR pszText, int cchText) const;
int GetTextW(BOOL bForParsing, LPWSTR pwszText, int cchText) const;
int GetTextLength(BOOL bForParsing) const;
void Paint(HDC hdc, IN OPTIONAL LPCRECT prcClient = NULL, LPCRECT prcClip = NULL);
int CalcIdealHeight(int cx);
int HitTest(const POINT& pt) const;
BOOL WantTab(BOOL* biFocus = NULL) const;
void AssignTabFocus(int nDirection);
int GetNextEnabledLink(int iStart, int nDir) const;
int StateCount(DWORD dwStateMask, DWORD dwState) const;
LONG EnableNotifications(BOOL bEnable);
static TEXTBLOCK* CreateBlock(LPCTSTR pszStart, LPCTSTR pszEnd, int iLink);
// Message handlers
static LRESULT WINAPI WndProc(HWND, UINT, WPARAM, LPARAM);
void OnButtonDown(WPARAM fwKeys, const POINT& pt);
void OnButtonUp(WPARAM fwKeys, const POINT& pt);
void OnCaptureLost(HWND hwndNew) { RESET_CAPTURE(); }
LRESULT OnFocus(HWND hwndPrev);
void OnKeyDown(UINT virtKey);
LRESULT SendNotify(UINT nCode, int iLink, LPCTSTR pszLinkID = NULL) const;
LRESULT GetItem(OUT LWITEM* pItem);
LRESULT SetItem(IN LWITEM* pItem);
// Data
TEXTBLOCK* _rgBlocks; // linked list of text blocks
int _cBlocks; // block count
int _cLinks; // link count
int _iFocus; // index of focus link
int _cyIdeal;
LPTSTR _pszCaption;
HWND _hwnd;
HFONT _hfStatic,
_hfLink;
UINT _fCapture;
UINT _fKeyboardCues;
POINT _ptCapture;
HCURSOR _hcurHand;
LONG _cNotifyLocks;
friend BOOL LinkWindow_RegisterClass();
};
LPTSTR _AllocAndCopy(LPTSTR& pszDest, LPCTSTR pszSrc)
{
if (pszDest) {
delete[] pszDest;
pszDest = NULL;
}
if (pszSrc && (pszDest = new TCHAR[lstrlen(pszSrc) + 1]) != NULL)
lstrcpy(pszDest, pszSrc);
return pszDest;
}
BOOL _AssignBit(const DWORD dwBit, DWORD& dwDest, const DWORD dwSrc) // returns TRUE if changed
{
if (((dwSrc & dwBit) != 0) != ((dwDest & dwBit) != 0)) {
if (((dwSrc & dwBit) != 0))
dwDest |= dwBit;
else
dwDest &= ~dwBit;
return TRUE;
}
return FALSE;
}
BOOL WINAPI LinkWindow_RegisterClass()
{
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.style = CS_GLOBALCLASS;
wc.lpfnWndProc = CLinkWindow::WndProc;
wc.hInstance = HINST_THISDLL;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(LINKCOLOR_BKGND + 1);
wc.lpszClassName = LINKWINDOW_CLASS;
return ::RegisterClassEx(&wc) != 0 ||
GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
}
BOOL WINAPI LinkWindow_UnregisterClass(HINSTANCE hInst)
{
return ::UnregisterClass(LINKWINDOW_CLASS, hInst);
}
CLinkWindow::CLinkWindow()
: CAccessibleBase(_hwnd),
_rgBlocks(NULL),
_cBlocks(0),
_cLinks(0),
_cyIdeal(0),
_hwnd(NULL),
_hfStatic(NULL),
_hfLink(NULL),
_iFocus(INVALID_LINK_INDEX),
_fCapture(0),
_pszCaption(NULL),
_fKeyboardCues(0),
_hcurHand(NULL),
_cNotifyLocks(0)
{
_ptCapture.x = _ptCapture.y = 0;
}
CLinkWindow::~CLinkWindow()
{
FreeBlocks();
DestroyFonts();
SetText(NULL);
}
// CLinkWindow IAccessible impl
// Note: Currently, this IAccessible implementation does not supports only
// single links; multiple links are not supported. All child delegation
// is to/from self. This allows us to blow off the IEnumVARIANT and IDispatch
// implementations.
// To shore this up the implementation, we need to implement each link
// as a child IAccessible object and delegate accordingly.
STDMETHODIMP CLinkWindow::get_accName(VARIANT varChild, BSTR* pbstrName)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
if (NULL == pbstrName)
return E_POINTER;
*pbstrName = 0;
int cch = GetTextLength(FALSE);
if ((*pbstrName = SysAllocStringLen(NULL, cch + 1)) != NULL) {
GetTextW(FALSE, *pbstrName, cch + 1);
return S_OK;
}
return E_OUTOFMEMORY;
}
STDMETHODIMP CLinkWindow::accDoDefaultAction(VARIANT varChild)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
SendNotify(NM_RETURN, _iFocus);
return S_OK;
}
// CLinkWindow window implementation
void CLinkWindow::FreeBlocks()
{
for (TEXTBLOCK* pBlock = _rgBlocks; pBlock; ) {
TEXTBLOCK* pNext = pBlock->next;
delete pBlock;
pBlock = pNext;
}
_rgBlocks = NULL;
_cBlocks = _cLinks = 0;
}
CLinkWindow::TEXTBLOCK* CLinkWindow::CreateBlock(LPCTSTR pszStart, LPCTSTR pszEnd, int iLink)
{
TEXTBLOCK* pBlock = NULL;
int cch = (int)(pszEnd - pszStart) + 1;
if (cch > 0) {
if ((pBlock = new TEXTBLOCK) != NULL) {
if ((pBlock->pszText = new TCHAR[cch]) == NULL) {
delete pBlock;
pBlock = NULL;
} else {
lstrcpyn(pBlock->pszText, pszStart, cch);
pBlock->iLink = iLink;
}
}
}
return pBlock;
}
BOOL CLinkWindow::CreateFonts(BOOL bRecreate)
{
if (_hfStatic && _hfLink && !bRecreate)
return TRUE;
BOOL bRet = FALSE;
HFONT hfStatic = NULL;
for (HWND hwnd = _hwnd; NULL == hfStatic && hwnd != NULL; hwnd = GetParent(hwnd))
hfStatic = (HFONT)::SendMessage(hwnd, WM_GETFONT, 0, 0L);
if (hfStatic) {
DestroyFonts();
LOGFONT lf;
if (GetObject(hfStatic, sizeof(lf), &lf)) {
// static text has no underline
lf.lfUnderline = FALSE;
_hfStatic = CreateFontIndirect(&lf);
// link text has underline
lf.lfUnderline = TRUE;
_hfLink = CreateFontIndirect(&lf);
bRet = _hfLink != NULL && _hfStatic != NULL;
}
}
return bRet;
}
HCURSOR CLinkWindow::GetLinkCursor()
{
if (!_hcurHand)
_hcurHand = LoadHandCursor(0);
return _hcurHand;
}
void CLinkWindow::DestroyFonts()
{
if (_hfStatic) {
DeleteObject(_hfStatic);
_hfStatic = NULL;
}
if (_hfLink) {
DeleteObject(_hfLink);
_hfLink = NULL;
}
}
void CLinkWindow::Parse(LPCTSTR pszText)
{
TEXTBLOCK* pBlock;
int cBlocks = 0, cLinks = 0;
LPCTSTR psz1, psz2, pszBlock;
LPTSTR pszBuf = NULL;
FreeBlocks(); // free existing blocks
if (!pszText) {
int cch = GetWindowTextLength(_hwnd) + 1;
if (cch <= 0 ||
(pszBuf = new TCHAR[cch + 1]) == NULL)
goto exit;
GetWindowText(_hwnd, pszBuf, cch);
} else
pszBuf = (LPTSTR)pszText;
if (!(pszBuf && *pszBuf))
goto exit;
#define LINKTAG1L TEXT("<a>")
#define LINKTAG1U TEXT("<A>")
#define cchLINKTAG1 3
#define LINKTAG2L TEXT("</a>")
#define LINKTAG2U TEXT("</A>")
#define cchLINKTAG2 4
for (pszBlock = pszBuf; pszBlock && *pszBlock; ) {
// Search for "<a>" tag
if (((psz1 = StrStrI(pszBlock, LINKTAG1L)) != NULL ||
(psz1 = StrStrI(pszBlock, LINKTAG1U)) != NULL)) {
// Add run between psz1 and pszBlock as static text
if (psz1 > pszBlock) {
if ((pBlock = CreateBlock(pszBlock, psz1, INVALID_LINK_INDEX)) != NULL) {
Add(pBlock);
cBlocks++;
}
}
// safe-skip over tag
for (int i = 0; i < cchLINKTAG1 && psz1 && *psz1;
i++, psz1 = CharNext(psz1));
pszBlock = psz1;
if (psz1 && *psz1) {
if ((psz2 = StrStrI(pszBlock, LINKTAG2L)) != NULL ||
(psz2 = StrStrI(pszBlock, LINKTAG2U)) != NULL) {
if ((pBlock = CreateBlock(psz1, psz2, cLinks)) != NULL) {
Add(pBlock);
cBlocks++;
cLinks++;
}
// safe-skip over tag
for (int i = 0;
i < cchLINKTAG2 && psz2 && *psz2;
i++, psz2 = CharNext(psz2));
pszBlock = psz2;
} else // syntax error; mark trailing run is static text.
{
psz2 = pszBlock + lstrlen(pszBlock);
if ((pBlock = CreateBlock(psz1, psz2, INVALID_LINK_INDEX)) != NULL) {
Add(pBlock);
cBlocks++;
}
pszBlock = psz2;
}
}
} else // no more tags. Mark the last run of static text
{
psz2 = pszBlock + lstrlen(pszBlock);
if ((pBlock = CreateBlock(pszBlock, psz2, INVALID_LINK_INDEX)) != NULL) {
Add(pBlock);
cBlocks++;
}
pszBlock = psz2;
}
}
ASSERT(cBlocks == _cBlocks);
ASSERT(cLinks == _cLinks);
exit:
if (!pszText && pszBuf) // delete text buffer if we had alloc'd it.
delete[] pszBuf;
}
BOOL CLinkWindow::Add(TEXTBLOCK* pAdd)
{
BOOL bAdded = FALSE;
pAdd->next = NULL;
if (!_rgBlocks) {
_rgBlocks = pAdd;
bAdded = TRUE;
} else {
for (TEXTBLOCK* pBlock = _rgBlocks; pBlock && !bAdded; pBlock = pBlock->next) {
if (!pBlock->next) {
pBlock->next = pAdd;
bAdded = TRUE;
}
}
}
if (bAdded) {
_cBlocks++;
if (IS_LINK(pAdd))
_cLinks++;
}
return bAdded;
}
CLinkWindow::TEXTBLOCK* CLinkWindow::FindLink(int iLink) const
{
if (iLink == INVALID_LINK_INDEX)
return NULL;
for (TEXTBLOCK* pBlock = _rgBlocks; pBlock; pBlock = pBlock->next) {
if (IS_LINK(pBlock) && pBlock->iLink == iLink)
return pBlock;
}
return NULL;
}
int _IsLineBreakChar(LPCTSTR psz, int ich, TCHAR chBreak, OUT BOOL* pbRemove)
{
LPTSTR pch;
*pbRemove = FALSE;
ASSERT(psz != NULL)
ASSERT(psz[ich] != 0);
// Try caller-provided break character (assumed a 'remove' break char).
if (psz[ich] == chBreak) {
*pbRemove = TRUE;
return ich;
}
#define MAX_LINEBREAK_RESOURCE 128
static TCHAR _szBreakRemove[MAX_LINEBREAK_RESOURCE] = {0};
static TCHAR _szBreakPreserve[MAX_LINEBREAK_RESOURCE] = {0};
#define LOAD_BREAKCHAR_RESOURCE( nIDS, buff ) \
if(0==*buff) { LoadString(HINST_THISDLL, nIDS, buff, ARRAYSIZE(buff)); }
// Try 'remove' break chars
LOAD_BREAKCHAR_RESOURCE(IDS_LINEBREAK_REMOVE, _szBreakRemove);
for (pch = _szBreakRemove; *pch; pch = CharNext(pch)) {
if (psz[ich] == *pch) {
*pbRemove = TRUE;
return ich;
}
}
// Try 'preserve prior' break chars:
LOAD_BREAKCHAR_RESOURCE(IDS_LINEBREAK_PRESERVE, _szBreakPreserve);
for (pch = _szBreakPreserve; *pch; pch = CharNext(pch)) {
if (psz[ich] == *pch)
return ++ich;
}
return -1;
}
BOOL _FindLastBreakChar(
IN LPCTSTR pszText,
IN int cchText,
IN TCHAR chBreak, // official break char (from TEXTMETRIC).
OUT int* piLast,
OUT BOOL* pbRemove)
{
*piLast = 0;
*pbRemove = FALSE;
for (int i = cchText - 1; i >= 0; i--) {
#ifndef UNICODE
if (IsDBCSLeadByte(pszText[i]))
continue;
#endif
int ich = _IsLineBreakChar(pszText, i, chBreak, pbRemove);
if (ich >= 0) {
*piLast = ich;
return TRUE;
}
}
return FALSE;
}
int CLinkWindow::CalcIdealHeight(int cx)
{
int cyRet = -1;
HDC hdc;
RECT rc;
SIZE sizeDC;
if (NULL == _rgBlocks || 0 == _cBlocks)
return -1;
GetClientRect(_hwnd, &rc);
if (cx <= 0)
cx = RECTWIDTH(&rc);
else
rc.right = cx;
if (cx <= 0)
return -1;
// Come up with a conservative estimate for the new height.
sizeDC.cy = MulDiv(RECTHEIGHT(&rc), cx, RECTWIDTH(&rc)) * 2;
sizeDC.cx = cx;
if ((hdc = GetDC(_hwnd)) != NULL) {
// prepare memory DC
HDC hdcMem;
if ((hdcMem = CreateCompatibleDC(hdc))) {
// [scotthan]: BUGBUG - Probably don't need anything but a monochrome DC
// but should test before removing the code.
HBITMAP hbm = CreateCompatibleBitmap(hdc, sizeDC.cx, sizeDC.cy);
HBITMAP hbmPrev = (HBITMAP)SelectObject(hdcMem, hbm);
int cyPrev = _cyIdeal; // push ideal
// paint into memory DC to determine height
SetRect(&rc, 0, 0, sizeDC.cx, sizeDC.cy);
Paint(hdcMem, &rc);
cyRet = _cyIdeal;
_cyIdeal = cyPrev; // pop ideal
SelectObject(hdcMem, hbmPrev);
DeleteObject(hbm);
DeleteDC(hdcMem);
}
ReleaseDC(_hwnd, hdc);
}
return cyRet;
}
void CLinkWindow::Paint(HDC hdcClient, LPCRECT prcClient, LPCRECT prcClip)
{
RECT rcClient;
if (!prcClient) {
GetClientRect(_hwnd, &rcClient);
prcClient = &rcClient;
}
if (RECTWIDTH(prcClient) <= 0 || RECTHEIGHT(prcClient) <= 0)
return;
HDC hdc = hdcClient ? hdcClient : GetDC(_hwnd);
TEXTBLOCK* pBlock;
TEXTMETRIC tm;
int iLine = 0, // current line index
cyLine = 0, // line height.
cyLeading = 0; // internal leading
COLORREF rgbOld = GetTextColor(hdc); // save text color
RECT rcFill,
rcDraw = *prcClient; // initialize line rect
const ULONG dwFlags = DT_TOP | DT_LEFT;
BOOL fFocus = GetFocus() == _hwnd;
// initialize background
SendMessage(GetParent(_hwnd), WM_CTLCOLORSTATIC,
(WPARAM)hdc, (LPARAM)_hwnd);
SetBkMode(hdc, OPAQUE);
_cyIdeal = 0;
// For each block of text...
for (pBlock = _rgBlocks; pBlock; pBlock = pBlock->next) {
BOOL bLink = IS_LINK(pBlock);
SelectObject(hdc, bLink ? _hfLink : _hfStatic);
int cchDraw = lstrlen(pBlock->pszText), // chars to draw, this block
cchDrawn = 0; // chars to draw, this block
LPTSTR pszText = &pBlock->pszText[cchDrawn];
pBlock->FreeRects(); // free hit/focus rects; we're going to recompute.
// Get font metrics
GetTextMetrics(hdc, &tm);
if (tm.tmExternalLeading > cyLeading)
cyLeading = tm.tmExternalLeading;
// initialize foreground color
if (bLink) {
BOOL bEnabled = pBlock->state & LWIS_ENABLED;
SetTextColor(hdc, bEnabled ? LINKCOLOR_ENABLED : LINKCOLOR_DISABLED);
} else
SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
// while text remains...
while (cchDraw > 0) {
// compute line height and maximum text width to rcBlock
RECT rcBlock;
BOOL bShy = FALSE;
int cchTry = cchDraw;
int cchFit = 0;
SIZE sizeFit;
BOOL bRemoveBreak = FALSE;
for (;;) {
if (!GetTextExtentExPoint(hdc, pszText, cchTry, RECTWIDTH(&rcDraw),
&cchFit, NULL, &sizeFit)) {
cchTry--;
continue;
} else if (cchFit < cchTry) {
BOOL fBreak = _FindLastBreakChar(pszText, cchFit, tm.tmBreakChar, &cchTry, &bRemoveBreak);
if (0 == cchTry) {
if (!fBreak && (0 == rcDraw.left /* nothing drawn on this line*/))
cchTry = cchFit; // no break character found, so force a break.
} else {
GetTextExtentExPoint(hdc, pszText, cchTry, RECTWIDTH(&rcDraw),
&cchFit, NULL, &sizeFit);
}
break;
} else
break;
}
cyLine = sizeFit.cy;
SetRect(&rcBlock, 0, 0, sizeFit.cx, sizeFit.cy);
OffsetRect(&rcBlock, rcDraw.left - rcBlock.left, 0);
// initialize drawing rectangle
rcDraw.right = min(rcDraw.left + RECTWIDTH(&rcBlock), prcClient->right);
rcDraw.bottom = rcDraw.top + cyLine;
// draw the text
ExtTextOut(hdc, rcDraw.left, rcDraw.top, ETO_OPAQUE | ETO_CLIPPED,
&rcDraw, pszText, cchTry, NULL);
// Add rectangle to block's list
if (bLink && cchTry)
pBlock->AddRect(rcDraw);
cchDrawn = cchTry;
if (cchTry < cchDraw) // we got clipped
{
// fill line to right boundary
SetRect(&rcFill, rcDraw.right, rcDraw.top, prcClient->right, rcDraw.bottom);
ExtTextOut(hdc, rcFill.left, rcFill.top, ETO_OPAQUE,
&rcFill, NULL, 0, NULL);
// adjust text
if (bRemoveBreak)
cchDrawn++;
pszText += cchDrawn;
// advance to next line
iLine++;
rcDraw.left = 0;
rcDraw.top = iLine * cyLine;
rcDraw.bottom = rcDraw.top + cyLine + cyLeading;
rcDraw.right = prcClient->right;
} else // we were able to draw the entire text
{
// adjust drawing rectangle
rcDraw.left += RECTWIDTH(&rcBlock);
rcDraw.right = prcClient->right;
// if this is the last block of text, fill line to right boundary
if (pBlock->next == NULL) {
rcFill = rcDraw;
rcFill.right = prcClient->right;
ExtTextOut(hdc, rcFill.left, rcFill.top, ETO_OPAQUE,
&rcFill, NULL, 0, NULL);
}
}
_cyIdeal = rcDraw.bottom;
cchDraw -= cchDrawn;
}
// Draw focus rect(s)
#ifdef KEYBOARDCUES
if (0 == (_fKeyboardCues & UISF_HIDEFOCUS))
#endif
{
if (fFocus && pBlock->iLink == _iFocus) {
HBRUSH hbr, hbrOld;
COLORREF rgbBkgnd = GetBkColor(hdc);
if ((hbr = CreateSolidBrush(rgbBkgnd))) {
hbrOld = (HBRUSH)SelectObject(hdc, hbr);
for (RECTLISTENTRY* prce = pBlock->rgrle; prce && hbr; prce = prce->next) {
RECT rc = prce->rc;
FrameRect(hdc, &rc, hbr);
SetTextColor(hdc, rgbOld);
DrawFocusRect(hdc, &rc);
}
SelectObject(hdc, hbrOld);
DeleteObject(hbr);
}
}
}
}
// Fill remainder of client rect.
RECT rcX;
rcFill = *prcClient;
rcFill.top = rcDraw.top + cyLine;
if (prcClip == NULL) {
ExtTextOut(hdc, rcFill.left, rcFill.right, ETO_OPAQUE,
&rcFill, NULL, 0, NULL);
} else if (IntersectRect(&rcX, prcClip, &rcFill)) {
ExtTextOut(hdc, rcX.left, rcX.right, ETO_OPAQUE,
&rcX, NULL, 0, NULL);
}
SetTextColor(hdc, rgbOld); // restore text color
if (NULL == hdcClient && hdc) // release DC if we acquired it.
ReleaseDC(_hwnd, hdc);
}
int CLinkWindow::HitTest(const POINT& pt) const
{
// Walk blocks until we find a link rect that contains the point
TEXTBLOCK* pBlock;
for (pBlock = _rgBlocks; pBlock; pBlock = pBlock->next) {
if (IS_LINK(pBlock) && (pBlock->state & LWIS_ENABLED) != 0) {
RECTLISTENTRY* prce;
for (prce = pBlock->rgrle; prce; prce = prce->next) {
if (PtInRect(&prce->rc, pt)) {
return pBlock->iLink;
}
}
}
}
return INVALID_LINK_INDEX;
}
LRESULT CLinkWindow::SetItem(IN LWITEM* pItem)
{
TEXTBLOCK* pBlock;
BOOL bRedraw = FALSE;
LRESULT lRet = 0L;
if (NULL == pItem || 0 == (pItem->mask & LWIF_ITEMINDEX))
return lRet; //BUGBUG: need to open up search keys to LWIF_ITEMID and LWIF_URL.
if ((pBlock = FindLink(pItem->iLink)) != NULL) {
if (pItem->mask & LWIF_STATE) {
if (pItem->stateMask & LWIS_ENABLED) {
bRedraw |= _AssignBit(LWIS_ENABLED, pBlock->state, pItem->state);
BOOL bEnabled = IsWindowEnabled(_hwnd);
int cEnabledLinks = StateCount(LWIS_ENABLED, LWIS_ENABLED);
if (bEnabled) {
if (bEnabled && 0 == cEnabledLinks)
EnableWindow(_hwnd, FALSE);
else if (!bEnabled && cEnabledLinks != 0)
EnableWindow(_hwnd, TRUE);
}
}
if (pItem->stateMask & LWIS_VISITED)
bRedraw |= _AssignBit(LWIS_VISITED, pBlock->state, pItem->state);
if (pItem->stateMask & LWIS_FOCUSED) {
// Focus assignment is handled differently;
// one and only one link can have focus...
if (pItem->state & LWIS_FOCUSED) {
bRedraw |= (_iFocus != pItem->iLink);
_iFocus = pItem->iLink;
} else {
bRedraw |= (_iFocus == pItem->iLink);
_iFocus = INVALID_LINK_INDEX;
}
}
}
if (pItem->mask & LWIF_ITEMID) {
lstrcpyn(pBlock->szID, pItem->szID, sizeof(pBlock->szID));
lRet = 1L;
}
if (pItem->mask & LWIF_URL) {
_AllocAndCopy(pBlock->pszUrl, pItem->szUrl);
lRet = 1L;
}
}
if (bRedraw) {
InvalidateRect(_hwnd, NULL, TRUE);
UpdateWindow(_hwnd);
}
return lRet;
}
LRESULT CLinkWindow::GetItem(OUT LWITEM* pItem)
{
TEXTBLOCK* pBlock;
LRESULT lRet = 0L;
if (NULL == pItem || 0 == (pItem->mask & LWIF_ITEMINDEX))
return lRet; //BUGBUG: need to open up search keys to LWIF_ITEMID and LWIF_URL.
if ((pBlock = FindLink(pItem->iLink)) != NULL) {
if (pItem->mask & LWIF_STATE) {
pItem->state = 0L;
if (pItem->stateMask & LWIS_FOCUSED) {
if (_iFocus == pItem->iLink)
pItem->state |= LWIS_FOCUSED;
}
if (pItem->stateMask & LWIS_ENABLED) {
if (pBlock->state & LWIS_ENABLED)
pItem->state |= LWIS_ENABLED;
}
if (pItem->stateMask & LWIS_VISITED) {
if (pBlock->state & LWIS_VISITED)
pItem->state |= LWIS_VISITED;
}
}
if (pItem->mask & LWIF_ITEMID) {
lstrcpyn(pItem->szID, pBlock->szID, sizeof(pBlock->szID));
lRet = 1L;
}
if (pItem->mask & LWIF_URL) {
*pItem->szUrl = 0;
if (pBlock->pszUrl)
lstrcpyn(pItem->szUrl, pBlock->pszUrl, sizeof(pItem->szUrl));
lRet = 1L;
}
}
return lRet;
}
void CLinkWindow::OnButtonDown(WPARAM fwKeys, const POINT& pt)
{
int iLink;
if ((iLink = HitTest(pt)) != INVALID_LINK_INDEX) {
SetCursor(GetLinkCursor());
_iFocus = iLink;
MODIFY_CAPTURE(CF_SETCAPTURE, 0);
_ptCapture = pt;
if (GetFocus() != _hwnd) {
MODIFY_CAPTURE(CF_SETFOCUS, 0);
EnableNotifications(FALSE); // so the host doesn't reposition the link.
SetFocus(_hwnd);
EnableNotifications(TRUE);
}
InvalidateRect(_hwnd, NULL, FALSE);
SetCapture(_hwnd);
}
}
void CLinkWindow::OnButtonUp(WPARAM fwKeys, const POINT& pt)
{
if (TEST_CAPTURE(CF_SETCAPTURE)) {
ReleaseCapture();
MODIFY_CAPTURE(0, CF_SETCAPTURE);
// if the focus link contains the point, we can
// notify the parent window of a click event.
TEXTBLOCK* pBlock;
if ((pBlock = FindLink(_iFocus)) != NULL &&
(pBlock->state & LWIS_ENABLED) != 0 &&
_iFocus == HitTest(pt)) {
SendNotify(NM_CLICK, _iFocus);
}
}
if (TEST_CAPTURE(CF_SETFOCUS)) {
MODIFY_CAPTURE(0, CF_SETFOCUS);
if (GetFocus() == _hwnd) // if we still have the focus...
SendNotify(NM_SETFOCUS, _iFocus);
}
}
// WM_SETTEXT handler
void CLinkWindow::SetText(LPCTSTR pszText)
{
if (pszText && _pszCaption && 0 == lstrcmp(pszText, _pszCaption))
return; // nothing to do.
if (_pszCaption) {
delete[] _pszCaption;
_pszCaption = NULL;
}
if (pszText && *pszText) {
int cch = lstrlen(pszText);
if ((_pszCaption = new TCHAR[cch + 1]) != NULL)
lstrcpy(_pszCaption, pszText);
}
}
// WM_GETTEXT handler
int CLinkWindow::GetText(BOOL bForParsing, LPTSTR pszText, int cchText) const
{
int cchRet = 0;
if (pszText && cchText) {
*pszText = 0;
if (bForParsing) {
if (_pszCaption && *_pszCaption &&
lstrcpyn(pszText, _pszCaption, cchText))
return lstrlen(pszText) + 1;
} else {
TEXTBLOCK* pBlock;
for (pBlock = _rgBlocks; cchText > 0 && pBlock; pBlock = pBlock->next) {
if (pBlock->pszText) {
int cchBlock = lstrlen(pBlock->pszText);
StrNCat(pszText, pBlock->pszText, min(cchBlock + 1, cchText));
cchRet += min(cchBlock, cchText);
cchText -= min(cchBlock, cchText);
}
}
if (cchRet)
cchRet++; // terminating NULL
}
}
return cchRet;
}
// WM_GETTEXT handler
int CLinkWindow::GetTextW(BOOL bForParsing, LPWSTR pwszText, int cchText) const
{
#ifdef UNICODE
return GetText(bForParsing, pwszText, cchText);
#else UNICODE
int cchRet = 0;
LPSTR pszText = new CHAR[cchText];
if (pszText) {
cchRet = GetText(bForParsing, pszText, cchText);
if (cchRet) {
SHAnsiToUnicode(pszText, pwszText, cchText);
}
delete[] pszText;
}
return cchRet;
#endif UNICODE
}
// WM_GETTEXTLENGTH handler
int CLinkWindow::GetTextLength(BOOL bForParsing) const
{
int cnt = 0;
if (bForParsing)
cnt = (_pszCaption && *_pszCaption) ? lstrlen(_pszCaption) : 0;
else {
TEXTBLOCK* pBlock;
for (pBlock = _rgBlocks; pBlock; pBlock = pBlock->next) {
cnt += (pBlock->pszText && *pBlock->pszText) ? lstrlen(pBlock->pszText) : 0;
}
}
return cnt;
}
LONG CLinkWindow::EnableNotifications(BOOL bEnable)
{
if (bEnable) {
if (_cNotifyLocks > 0)
_cNotifyLocks--;
} else
_cNotifyLocks++;
return _cNotifyLocks;
}
LRESULT CLinkWindow::SendNotify(UINT nCode, int iLink, LPCTSTR pszLinkID) const
{
if (0 == _cNotifyLocks) {
NMLINKWND nm;
ZeroMemory(&nm, sizeof(nm));
nm.hdr.hwndFrom = _hwnd;
nm.hdr.idFrom = (UINT_PTR)GetWindowLong(_hwnd, GWL_ID);
nm.hdr.code = nCode;
nm.item.iLink = iLink;
if (pszLinkID && *pszLinkID)
lstrcpyn(nm.item.szID, pszLinkID, ARRAYSIZE(nm.item.szID));
return SendMessage(GetParent(_hwnd), WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
}
return 0L;
}
inline void MakePoint(LPARAM lParam, OUT LPPOINT ppt)
{
POINTS pts = MAKEPOINTS(lParam);
ppt->x = pts.x;
ppt->y = pts.y;
}
int CLinkWindow::GetNextEnabledLink(int iStart, int nDir) const
{
ASSERT(-1 == nDir || 1 == nDir);
if (_cLinks > 0) {
if (INVALID_LINK_INDEX == iStart)
iStart = nDir > 0 ? -1 : _cLinks;
for (iStart += nDir; iStart >= 0; iStart += nDir) {
TEXTBLOCK* pBlock;
if (NULL == (pBlock = FindLink(iStart)))
return INVALID_LINK_INDEX;
if (pBlock->state & LWIS_ENABLED) {
ASSERT(iStart == pBlock->iLink);
return iStart;
}
}
}
return INVALID_LINK_INDEX;
}
int CLinkWindow::StateCount(DWORD dwStateMask, DWORD dwState) const
{
TEXTBLOCK* pBlock;
int cnt = 0;
for (pBlock = _rgBlocks; pBlock; pBlock = pBlock->next) {
if (IS_LINK(pBlock) &&
(pBlock->state & dwStateMask) == dwState)
cnt++;
}
return cnt;
}
BOOL CLinkWindow::WantTab(BOOL* biFocus) const
{
int nDir = TESTKEYSTATE(VK_SHIFT) ? -1 : 1;
int iFocus = GetNextEnabledLink(_iFocus, nDir);
if (INVALID_LINK_INDEX != iFocus) {
if (biFocus)
*biFocus = iFocus;
return TRUE;
}
return FALSE;
}
// WM_SETFOCUS handler
LRESULT CLinkWindow::OnFocus(HWND hwndPrev)
{
if (!TEST_CAPTURE(CF_SETCAPTURE)) // if we got focus by something other than a mouse click
{
int nDir = 0;
if (TESTKEYSTATE(VK_TAB))
nDir = TESTKEYSTATE(VK_SHIFT) ? -1 : 1;
AssignTabFocus(nDir); // move internal focus
}
InvalidateRect(_hwnd, NULL, FALSE);
SendNotify(NM_SETFOCUS, _iFocus); // notify parent
return 0L;
}
void CLinkWindow::AssignTabFocus(int nDirection)
{
if (_cLinks) {
if (0 == nDirection) {
if (INVALID_LINK_INDEX != _iFocus)
return;
nDirection = 1;
}
_iFocus = GetNextEnabledLink(_iFocus, nDirection);
}
}
// WM_KEYDOWN handler
void CLinkWindow::OnKeyDown(UINT virtKey)
{
switch (virtKey) {
case VK_TAB:
if (WantTab(&_iFocus))
InvalidateRect(_hwnd, NULL, FALSE);
break;
case VK_RETURN:
case VK_SPACE:
if (FindLink(_iFocus))
SendNotify(NM_RETURN, _iFocus);
break;
}
}
LRESULT WINAPI CLinkWindow::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lRet = 0L;
CLinkWindow* pThis = NULL;
if (uMsg == WM_NCCREATE) {
pThis = new CLinkWindow;
if (NULL == pThis) {
ASSERT(pThis);
return FALSE;
}
pThis->_hwnd = hwnd;
SetWindowPtr(hwnd, GWLP_USERDATA, pThis);
return TRUE;
} else {
pThis = (CLinkWindow*)GetWindowPtr(hwnd, GWLP_USERDATA);
ASSERT(pThis);
ASSERT(pThis->_hwnd == hwnd);
}
switch (uMsg) {
case WM_NCHITTEST:
{
POINT pt;
MakePoint(lParam, &pt);
MapWindowPoints(HWND_DESKTOP, hwnd, &pt, 1);
if (pThis->HitTest(pt) != INVALID_LINK_INDEX)
return HTCLIENT;
return HTTRANSPARENT;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc;
if ((hdc = BeginPaint(pThis->_hwnd, &ps)) != NULL) {
pThis->Paint(hdc);
EndPaint(pThis->_hwnd, &ps);
}
return lRet;
}
case WM_WINDOWPOSCHANGING:
{
WINDOWPOS* pwp = (WINDOWPOS*)lParam;
RECT rc;
GetClientRect(pThis->_hwnd, &rc);
if (0 == (pwp->flags & SWP_NOSIZE) &&
!(pwp->cx == RECTWIDTH(&rc) &&
pwp->cy == RECTHEIGHT(&rc))) {
// BUGBUG: implement LWS_AUTOHEIGHT style by
// calling CalcIdealHeight() to compute the height for
// the given width.
}
break;
}
case WM_SIZE:
{
pThis->Paint(NULL);
break;
}
case WM_CREATE:
{
if ((lRet = DefWindowProc(hwnd, uMsg, wParam, lParam)) == 0) {
CREATESTRUCT* pcs = (CREATESTRUCT*)lParam;
#ifdef KEYBOARDCUES
_InitializeUISTATE(hwnd, &pThis->_fKeyboardCues);
#endif//KEYBOARDCUES
pThis->CreateFonts();
pThis->SetText(pcs->lpszName);
pThis->Parse(pThis->_pszCaption);
}
return lRet;
}
case WM_SETTEXT:
pThis->SetText((LPCTSTR)lParam);
pThis->Parse(pThis->_pszCaption);
InvalidateRect(pThis->_hwnd, NULL, FALSE);
break;
case WM_GETTEXT:
return pThis->GetText(TRUE, (LPTSTR)lParam, (int)wParam);
case WM_GETTEXTLENGTH:
return pThis->GetTextLength(TRUE);
case WM_SETFOCUS:
return pThis->OnFocus((HWND)wParam);
case WM_KILLFOCUS:
pThis->SendNotify(NM_KILLFOCUS, pThis->_iFocus);
pThis->_iFocus = INVALID_LINK_INDEX;
InvalidateRect(pThis->_hwnd, NULL, FALSE);
return lRet;
case WM_LBUTTONDOWN:
{
POINT pt;
MakePoint(lParam, &pt);
pThis->OnButtonDown(wParam, pt);
break;
}
case WM_LBUTTONUP:
{
POINT pt;
MakePoint(lParam, &pt);
pThis->OnButtonUp(wParam, pt);
break;
}
case WM_MOUSEMOVE:
{
POINT pt;
MakePoint(lParam, &pt);
if (pThis->HitTest(pt) != INVALID_LINK_INDEX)
SetCursor(pThis->GetLinkCursor());
break;
}
case WM_CAPTURECHANGED:
if (lParam /* NULL if we called ReleaseCapture() */)
pThis->OnCaptureLost((HWND)lParam);
break;
case LWM_HITTEST: // wParam: n/a, lparam: LPLWITEM, ret: BOOL
{
LWHITTESTINFO* phti = (LWHITTESTINFO*)lParam;
if (phti) {
TEXTBLOCK* pBlock;
*phti->item.szID = 0;
if ((phti->item.iLink = pThis->HitTest(phti->pt)) != INVALID_LINK_INDEX &&
(pBlock = pThis->FindLink(phti->item.iLink)) != NULL) {
lstrcpyn(phti->item.szID, pBlock->szID, ARRAYSIZE(phti->item.szID));
return TRUE;
}
}
return lRet;
}
case LWM_SETITEM:
return pThis->SetItem((LWITEM*)lParam);
case LWM_GETITEM:
return pThis->GetItem((LWITEM*)lParam);
case LWM_GETIDEALHEIGHT: // wParam: cx, lparam: n/a, ret: cy
// force a recalc if we've never done so
return pThis->CalcIdealHeight((int)wParam);
case WM_NCDESTROY:
{
lRet = DefWindowProc(hwnd, uMsg, wParam, lParam);
SetWindowPtr(hwnd, GWLP_USERDATA, 0);
pThis->_hwnd = NULL;
pThis->Release();
return lRet;
}
case WM_GETDLGCODE:
{
MSG* pmsg;
lRet = DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON;
if ((pmsg = (MSG*)lParam)) {
if ((WM_KEYDOWN == pmsg->message || WM_KEYUP == pmsg->message)) {
switch (pmsg->wParam) {
case VK_TAB:
if (pThis->WantTab())
lRet |= DLGC_WANTTAB;
break;
case VK_RETURN:
case VK_SPACE:
lRet |= DLGC_WANTALLKEYS;
break;
}
} else if (WM_CHAR == pmsg->message && VK_RETURN == pmsg->wParam) {
// Eat VK_RETURN WM_CHARs; we don't want
// Dialog manager to beep when IsDialogMessage gets it.
return lRet |= DLGC_WANTMESSAGE;
}
}
return lRet;
}
case WM_KEYDOWN:
pThis->OnKeyDown((UINT)wParam);
case WM_KEYUP:
case WM_CHAR:
return lRet;
#ifdef KEYBOARDCUES
case WM_UPDATEUISTATE:
if (_HandleWM_UPDATEUISTATE(wParam, lParam, &pThis->_fKeyboardCues))
RedrawWindow(hwnd, NULL, NULL,
RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
break;
#endif
default:
// oleacc defs thunked for WINVER < 0x0500
if (IsWM_GETOBJECT(uMsg) && OBJID_CLIENT == lParam)
return LresultFromObject(IID_IAccessible, wParam, SAFECAST(pThis, IAccessible*));
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
CLinkWindow::TEXTBLOCK::TEXTBLOCK()
: iLink(INVALID_LINK_INDEX),
next(NULL),
state(LWIS_ENABLED),
pszText(NULL),
pszUrl(NULL),
rgrle(NULL)
{
*szID = 0;
}
CLinkWindow::TEXTBLOCK::~TEXTBLOCK()
{
// free block text
_AllocAndCopy(pszText, NULL);
_AllocAndCopy(pszUrl, NULL);
// free rectangle(s)
FreeRects();
}
void CLinkWindow::TEXTBLOCK::AddRect(const RECT& rc)
{
RECTLISTENTRY* prce;
if ((prce = new RECTLISTENTRY) != NULL) {
prce->rc = rc;
prce->next = NULL;
}
if (rgrle == NULL)
rgrle = prce;
else {
for (RECTLISTENTRY* p = rgrle; p; p = p->next) {
if (p->next == NULL) {
p->next = prce;
break;
}
}
}
}
void CLinkWindow::TEXTBLOCK::FreeRects()
{
for (RECTLISTENTRY* p = rgrle; p; ) {
RECTLISTENTRY* next = p->next;
delete p;
p = next;
}
rgrle = NULL;
}
#define GROUPBTN_BKCOLOR COLOR_WINDOW
#define CAPTION_VPADDING 3
#define CAPTION_HPADDING 2
#define GBM_SENDNOTIFY (GBM_LAST + 1)
// class CGroupBtn
class CGroupBtn : public CAccessibleBase
{ // all members private:
CGroupBtn(HWND hwnd);
~CGroupBtn();
// IAccessible specialization
STDMETHODIMP get_accName(VARIANT varChild, BSTR* pbstrName);
STDMETHODIMP accDoDefaultAction(VARIANT varChild);
// CAccessibleBase overrides
UINT GetDefaultActionStringID() const { return IDS_GROUPBTN_DEFAULTACTION; }
// window procedures
static LRESULT WINAPI WndProc(HWND, UINT, WPARAM, LPARAM);
static LRESULT WINAPI BuddyProc(HWND, UINT, WPARAM, LPARAM);
// message handlers
BOOL NcCreate(LPCREATESTRUCT lpcs);
LRESULT NcCalcSize(BOOL, LPNCCALCSIZE_PARAMS);
void NcPaint(HRGN);
LRESULT NcMouseMove(WPARAM, LONG, LONG);
LRESULT NcHitTest(LONG, LONG);
LRESULT NcButtonDown(UINT nMsg, WPARAM nHittest, const POINTS& pts);
LRESULT NcDblClick(UINT nMsg, WPARAM nHittest, LPARAM lParam);
LRESULT ButtonUp(UINT nMsg, WPARAM nHittest, const POINTS& pts);
void OnCaptureLost(HWND hwndNew) { RESET_CAPTURE(); }
LRESULT WindowPosChanging(LPWINDOWPOS);
LRESULT OnSize(WPARAM, LONG, LONG);
BOOL SetPlacement(PGBPLACEMENT);
BOOL SetBuddy(HWND, ULONG);
BOOL SetDropState(BOOL);
void SetText(LPCTSTR);
int GetText(LPTSTR, int);
int GetTextW(LPWSTR, int);
int GetTextLength();
void SetFont(HFONT);
HFONT GetFont();
// utility methods
static void _MapWindowRect(HWND hwnd, HWND hwndRelative, OUT LPRECT prcWindow);
void _MapWindowRect(HWND hwndRelative, OUT LPRECT prcWindow);
HCURSOR GetHandCursor();
void CalcCaptionSize();
BOOL CalcClientRect(IN OPTIONAL LPCRECT prcWindow, OUT LPRECT prcClient);
BOOL CalcWindowSizeForClient(IN OPTIONAL LPCRECT prcClient,
IN OPTIONAL LPCRECT prcWindow,
IN LPCRECT prcNewClient,
OUT LPSIZE psizeWindow);
void DoLayout(BOOL bNewBuddy = FALSE);
LONG EnableNotifications(BOOL bEnable);
LRESULT SendNotify(int nCode, IN OPTIONAL NMHDR* pnmh = NULL);
void PostNotify(int nCode);
// instance and static data
HWND _hwnd;
HWND _hwndBuddy;
WNDPROC _pfnBuddy;
ULONG _dwBuddyFlags;
SIZE _sizeBuddyMargin;
HFONT _hf;
static ATOM _atom;
LPTSTR _pszCaption;
SIZE _sizeCaption;
int _yDrop;
BOOL _fDropped : 1,
_fInLayout : 1;
UINT _fCapture;
UINT _fKeyboardCues;
HCURSOR _hcurHand;
LONG _cNotifyLocks;
friend ATOM GroupButton_RegisterClass();
friend HWND CreateGroupBtn(DWORD, LPCTSTR, DWORD,
int x, int y, HWND hwndParent, UINT nID);
};
ATOM GroupButton_RegisterClass()
{
if (CGroupBtn::_atom != 0)
return CGroupBtn::_atom;
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.style = CS_GLOBALCLASS;
wc.lpfnWndProc = CGroupBtn::WndProc;
wc.hInstance = HINST_THISDLL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(GROUPBTN_BKCOLOR + 1);
wc.lpszClassName = GROUPBUTTON_CLASS;
//wc.lpszMenuName
//wc.hIcon
//wc.hIconSm
return (CGroupBtn::_atom = RegisterClassEx(&wc));
}
BOOL GroupButton_UnregisterClass()
{
return UnregisterClass(GROUPBUTTON_CLASS, HINST_THISDLL);
}
CGroupBtn::CGroupBtn(HWND hwnd)
: CAccessibleBase(_hwnd),
_hwnd(hwnd),
_hwndBuddy(NULL),
_pfnBuddy(NULL),
_dwBuddyFlags(GBBF_HRESIZE | GBBF_VRESIZE),
_fInLayout(FALSE),
_hf(NULL),
_pszCaption(NULL),
_fDropped(TRUE),
_fKeyboardCues(0),
_yDrop(0),
_fCapture(0),
_hcurHand(NULL),
_cNotifyLocks(0)
{
_sizeCaption.cx = _sizeCaption.cy = 0;
_sizeBuddyMargin.cx = _sizeBuddyMargin.cy = 0;
}
ATOM CGroupBtn::_atom = 0;
CGroupBtn::~CGroupBtn()
{
SetFont(NULL);
SetText(NULL);
}
// CGroupBtn IAccessible impl
STDMETHODIMP CGroupBtn::get_accName(VARIANT varChild, BSTR* pbstrName)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
if (NULL == pbstrName)
return E_POINTER;
*pbstrName = 0;
int cch = GetTextLength();
if ((*pbstrName = SysAllocStringLen(NULL, cch + 1)) != NULL) {
GetTextW(*pbstrName, cch + 1);
return S_OK;
}
return E_OUTOFMEMORY;
}
STDMETHODIMP CGroupBtn::accDoDefaultAction(VARIANT varChild)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
SendNotify(NM_RETURN);
return S_OK;
}
// CGroupBtn window impl
// WM_SETTEXT handler
void CGroupBtn::SetText(LPCTSTR pszText)
{
if (_pszCaption) {
if (pszText && 0 == lstrcmp(_pszCaption, pszText))
return;
delete[] _pszCaption;
_pszCaption = NULL;
}
if (pszText && *pszText) {
if ((_pszCaption = new TCHAR[lstrlen(pszText) + 1]) != NULL)
lstrcpy(_pszCaption, pszText);
}
if (IsWindow(_hwnd))
CalcCaptionSize();
}
// WM_GETTEXT handler
int CGroupBtn::GetText(LPTSTR pszText, int cchText)
{
int cch = 0;
if (pszText && cchText > 0) {
*pszText = 0;
if (_pszCaption && lstrcpyn(pszText, _pszCaption, cchText))
cch = min(lstrlen(_pszCaption), cchText);
}
return cch;
}
int CGroupBtn::GetTextW(LPWSTR pwszText, int cchText)
{
#ifdef UNICODE
return GetText(pwszText, cchText);
#else //UNICODE
int cchRet = 0;
LPSTR pszText = new CHAR[cchText];
if (pszText) {
cchRet = GetText(pszText, cchText);
if (cchRet) {
SHAnsiToUnicode(pszText, pwszText, cchText);
}
delete[] pszText;
}
return cchRet;
#endif //UNICODE
}
// WM_GETTEXTLENGTH handler
int CGroupBtn::GetTextLength()
{
return (_pszCaption && *_pszCaption) ? lstrlen(_pszCaption) : 0;
}
// WM_SETFONT handler
void CGroupBtn::SetFont(HFONT hf)
{
if (_hf) {
DeleteObject(_hf);
_hf = NULL;
}
_hf = hf;
}
// WM_GETFONT handler
HFONT CGroupBtn::GetFont()
{
if (_hf == NULL) {
// if we don't have a font, use the parent's font
HFONT hfParent = (HFONT)SendMessage(GetParent(_hwnd), WM_GETFONT, 0, 0L);
if (hfParent) {
LOGFONT lf;
if (GetObject(hfParent, sizeof(LOGFONT), &lf) > 0)
_hf = CreateFontIndirect(&lf);
}
}
return _hf;
}
// Hand cursor load
HCURSOR CGroupBtn::GetHandCursor()
{
if (!_hcurHand)
_hcurHand = LoadHandCursor(0);
return _hcurHand;
}
// Retrieves the window rect in relative coords.
void CGroupBtn::_MapWindowRect(HWND hwnd, HWND hwndRelative, OUT LPRECT prcWindow)
{
ASSERT(IsWindow(hwnd));
GetWindowRect(hwnd, prcWindow);
MapWindowPoints(HWND_DESKTOP, hwndRelative, (LPPOINT)prcWindow, 2);
}
// Retrieves the window rect in relative coords.
inline void CGroupBtn::_MapWindowRect(HWND hwndRelative, OUT LPRECT prcWindow)
{
_MapWindowRect(_hwnd, hwndRelative, prcWindow);
}
// Caches the size of the caption 'bar'.
void CGroupBtn::CalcCaptionSize()
{
SIZE sizeCaption = {0,0};
LPCTSTR pszCaption = (_pszCaption && *_pszCaption) ? _pszCaption : TEXT("|");
HDC hdc;
// compute caption size based on window text:
if ((hdc = GetDC(_hwnd))) {
HFONT hf = GetFont(),
hfPrev = (HFONT)SelectObject(hdc, hf);
if (GetTextExtentPoint32(hdc, pszCaption, lstrlen(pszCaption),
&sizeCaption))
sizeCaption.cy += CAPTION_VPADDING; // add some vertical padding
SelectObject(hdc, hfPrev);
ReleaseDC(_hwnd, hdc);
}
_sizeCaption = sizeCaption;
}
// Computes the size and position of the client area
BOOL CGroupBtn::CalcClientRect(IN OPTIONAL LPCRECT prcWindow, OUT LPRECT prcClient)
{
DWORD dwStyle = GetWindowLong(_hwnd, GWL_STYLE);
RECT rcWindow;
if (!prcWindow) {
// Get parent-relative coords
_MapWindowRect(GetParent(_hwnd), &rcWindow);
prcWindow = &rcWindow;
}
*prcClient = *prcWindow;
// compute client rectangle:
// allow for border
if (dwStyle & WS_BORDER)
InflateRect(prcClient, -1, -1);
// allow for caption 'bar'
prcClient->top += _sizeCaption.cy;
// Normalize for NULL rect.
if (RECTWIDTH(prcWindow) <= 0)
prcClient->left = prcClient->right = prcWindow->left;
if (RECTHEIGHT(prcWindow) <= 0)
prcClient->bottom = prcClient->top = prcWindow->top;
return TRUE;
}
BOOL CGroupBtn::CalcWindowSizeForClient(
IN OPTIONAL LPCRECT prcClient,
IN OPTIONAL LPCRECT prcWindow,
IN LPCRECT prcNewClient,
OUT LPSIZE psizeWindow)
{
if (!(prcNewClient && psizeWindow)) {
ASSERT(FALSE);
return FALSE;
}
RECT rcWindow, rcClient;
if (NULL == prcWindow) {
GetWindowRect(_hwnd, &rcWindow);
prcWindow = &rcWindow;
}
if (NULL == prcClient) {
GetClientRect(_hwnd, &rcClient);
prcClient = &rcClient;
}
SIZE sizeDelta;
sizeDelta.cx = RECTWIDTH(prcWindow) - RECTWIDTH(prcClient);
sizeDelta.cy = RECTHEIGHT(prcWindow) - RECTHEIGHT(prcClient);
psizeWindow->cx = RECTWIDTH(prcNewClient) + sizeDelta.cx;
psizeWindow->cy = RECTHEIGHT(prcNewClient) + sizeDelta.cy;
return TRUE;
}
// WM_WINDOWPOSCHANGING handler
LRESULT CGroupBtn::WindowPosChanging(LPWINDOWPOS pwp)
{
if (pwp->flags & SWP_NOSIZE)
return DefWindowProc(_hwnd, WM_WINDOWPOSCHANGING, 0, (LPARAM)pwp);
// disallow sizing in buddy slave dimension(s).
if (IsWindow(_hwndBuddy) && _dwBuddyFlags & (GBBF_HSLAVE | GBBF_VSLAVE) && !_fInLayout) {
RECT rcWindow, rcClient;
BOOL fResizeBuddy = FALSE;
GetWindowRect(_hwnd, &rcWindow);
GetClientRect(_hwnd, &rcClient);
// Prepare a buddy size data block
GBNQUERYBUDDYSIZE qbs;
qbs.cy = pwp->cy - (RECTHEIGHT(&rcWindow) - RECTHEIGHT(&rcClient));
qbs.cx = pwp->cx - (RECTWIDTH(&rcWindow) - RECTWIDTH(&rcClient));
if (_dwBuddyFlags & GBBF_HSLAVE) // prevent external horz resizing
{
pwp->cx = RECTWIDTH(&rcWindow);
// If we're being resized in the vert dir, query for
// optimal buddy width for this height and adjust
if (_dwBuddyFlags & GBBF_VRESIZE && RECTHEIGHT(&rcWindow) != pwp->cy) {
if (SendNotify(GBN_QUERYBUDDYWIDTH, (NMHDR*)&qbs) && qbs.cx >= 0) {
// if the owner wants the buddy width to change, do it now.
LONG cxNew = qbs.cx + (RECTWIDTH(&rcWindow) - RECTWIDTH(&rcClient));
fResizeBuddy = cxNew != pwp->cx;
pwp->cx = cxNew;
}
}
}
if (_dwBuddyFlags & GBBF_VSLAVE) // prevent external vert resizing
{
pwp->cy = RECTHEIGHT(&rcWindow);
// If we're being resized in the horz dir, query for
// optimal buddy height for this horizontal and adjust
if (_dwBuddyFlags & GBBF_HRESIZE && RECTWIDTH(&rcWindow) != pwp->cx) {
if (SendNotify(GBN_QUERYBUDDYHEIGHT, (NMHDR*)&qbs) && qbs.cy >= 0) {
LONG cyNew = qbs.cy + (RECTHEIGHT(&rcWindow) - RECTHEIGHT(&rcClient));
fResizeBuddy = cyNew != pwp->cy;
pwp->cy = cyNew;
}
}
}
if (fResizeBuddy) {
_fInLayout = TRUE;
SetWindowPos(_hwndBuddy, NULL, 0, 0, qbs.cx, qbs.cy,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
_fInLayout = FALSE;
}
}
// enforce minimum height:
if (pwp->cy < _sizeCaption.cy)
pwp->cy = _sizeCaption.cy;
return 0L;
}
LRESULT CGroupBtn::OnSize(WPARAM flags, LONG cx, LONG cy)
{
DoLayout();
return 0L;
}
void CGroupBtn::DoLayout(BOOL bNewBuddy)
{
if (!_fInLayout && IsWindow(_hwndBuddy)) {
RECT rcWindow, rcThis, rcBuddy;
DWORD dwSwpBuddy = SWP_NOACTIVATE,
dwSwpThis = SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE;
BOOL fReposThis = FALSE;
SIZE sizeNew;
GetClientRect(_hwnd, &rcThis);
GetWindowRect(_hwnd, &rcWindow);
// get rectangles in parent coords
MapWindowPoints(_hwnd, GetParent(_hwnd), (LPPOINT)&rcThis, POINTSPERRECT);
MapWindowPoints(HWND_DESKTOP, GetParent(_hwnd), (LPPOINT)&rcWindow, POINTSPERRECT);
_MapWindowRect(_hwndBuddy, GetParent(_hwnd), &rcBuddy);
// If we need to reposition ourself to the buddy,
// calculate the new size now.
if (_dwBuddyFlags & (GBBF_HSLAVE | GBBF_VSLAVE))
CalcWindowSizeForClient(&rcThis, &rcWindow, &rcBuddy, &sizeNew);
// Resize buddy according to size.
if (_dwBuddyFlags & GBBF_HRESIZE) {
rcBuddy.right = rcBuddy.left + RECTWIDTH(&rcThis);
if (bNewBuddy && 0 == (_dwBuddyFlags & GBBF_VRESIZE)) {
// query height
GBNQUERYBUDDYSIZE qbs;
qbs.cx = RECTWIDTH(&rcThis);
qbs.cy = -1;
if (SendNotify(GBN_QUERYBUDDYHEIGHT, (NMHDR*)&qbs) && qbs.cy >= 0)
rcBuddy.bottom = rcBuddy.top + qbs.cy;
}
} else if (_dwBuddyFlags & GBBF_HSLAVE) {
rcWindow.right = rcWindow.left + sizeNew.cx;
fReposThis = TRUE;
}
if (_dwBuddyFlags & GBBF_VRESIZE) {
rcBuddy.bottom = rcBuddy.top + RECTHEIGHT(&rcThis);
if (bNewBuddy && 0 == (_dwBuddyFlags & GBBF_HRESIZE)) {
// query width
GBNQUERYBUDDYSIZE qbs;
qbs.cx = -1;
qbs.cy = RECTHEIGHT(&rcThis);
if (SendNotify(GBN_QUERYBUDDYWIDTH, (NMHDR*)&qbs) && qbs.cx >= 0)
rcBuddy.right = rcBuddy.left + qbs.cx;
}
} else if (_dwBuddyFlags & GBBF_VSLAVE) {
rcWindow.bottom = rcWindow.top + sizeNew.cy;
fReposThis = TRUE;
}
if (_dwBuddyFlags & GBBF_HSCROLL) {
/* not implemented */
}
if (_dwBuddyFlags & GBBF_VSCROLL) {
/* not implemented */
}
// reposition ourself and update our client rect.
if (fReposThis) {
_fInLayout = TRUE;
SetWindowPos(_hwnd, NULL, 0, 0,
RECTWIDTH(&rcWindow), RECTHEIGHT(&rcWindow), dwSwpThis);
_fInLayout = FALSE;
GetClientRect(_hwnd, &rcThis);
MapWindowPoints(_hwnd, GetParent(_hwnd), (LPPOINT)&rcThis, POINTSPERRECT);
}
// slide buddy into client area and reposition
OffsetRect(&rcBuddy, rcThis.left - rcBuddy.left, rcThis.top - rcBuddy.top);
_fInLayout = TRUE;
SetWindowPos(_hwndBuddy, _hwnd, rcBuddy.left, rcBuddy.top,
RECTWIDTH(&rcBuddy), RECTHEIGHT(&rcBuddy), dwSwpBuddy);
_fInLayout = FALSE;
}
}
// GBM_SETPLACEMENT handler
BOOL CGroupBtn::SetPlacement(PGBPLACEMENT pgbp)
{
RECT rcWindow, rcClient;
SIZE sizeDelta;
DWORD dwFlags = SWP_NOZORDER | SWP_NOACTIVATE;
ZeroMemory(&sizeDelta, sizeof(sizeDelta));
_MapWindowRect(GetParent(_hwnd), &rcWindow);
CalcClientRect(&rcWindow, &rcClient);
// establish whether we need to resize
if ((pgbp->x < 0 || pgbp->x == rcWindow.left) &&
(pgbp->y < 0 || pgbp->y == rcWindow.top))
dwFlags |= SWP_NOMOVE;
// compute horizontal placement
if (pgbp->x >= 0) // fixed horz origin requested
OffsetRect(&rcWindow, pgbp->x - rcWindow.left, 0);
if (pgbp->cx >= 0) // fixed width requested
rcWindow.right = rcWindow.left + pgbp->cx;
else {
if (pgbp->cxBuddy >= 0) // client width requested
sizeDelta.cx = pgbp->cxBuddy - RECTWIDTH(&rcClient);
rcWindow.right += sizeDelta.cx;
}
// compute vertical placement
if (pgbp->y >= 0) // fixed vert origin requested
OffsetRect(&rcWindow, 0, pgbp->y - rcWindow.top);
if (pgbp->cy >= 0) // fixed height requested
rcWindow.bottom = rcWindow.top + pgbp->cy;
else {
if (pgbp->cyBuddy >= 0) // client height requested
sizeDelta.cy = pgbp->cyBuddy - RECTHEIGHT(&rcClient);
rcWindow.bottom += sizeDelta.cy;
}
if (pgbp->hdwp && (-1 != (LONG_PTR)pgbp->hdwp))
DeferWindowPos(pgbp->hdwp, _hwnd, NULL, rcWindow.left, rcWindow.top,
RECTWIDTH(&rcWindow), RECTHEIGHT(&rcWindow),
dwFlags);
else
SetWindowPos(_hwnd, NULL, rcWindow.left, rcWindow.top,
RECTWIDTH(&rcWindow), RECTHEIGHT(&rcWindow),
dwFlags);
// stuff resulting rects
pgbp->rcWindow = rcWindow;
return CalcClientRect(&rcWindow, &pgbp->rcBuddy);
}
BOOL CGroupBtn::SetBuddy(HWND hwnd, ULONG dwFlags)
{
if (!IsWindow(hwnd))
hwnd = NULL;
if (hwnd && (dwFlags & (GBBF_HSLAVE | GBBF_VSLAVE))) {
// subclass the buddy
_pfnBuddy = (WNDPROC)SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)BuddyProc);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)this);
} else if (IsWindow(_hwndBuddy) && _pfnBuddy) {
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)NULL);
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)_pfnBuddy);
_pfnBuddy = NULL;
}
_hwndBuddy = hwnd;
_dwBuddyFlags = dwFlags;
DoLayout(TRUE);
return TRUE;
}
BOOL CGroupBtn::SetDropState(BOOL bDropped)
{
_fDropped = bDropped;
return TRUE;
}
// WM_NCCREATE handler
BOOL CGroupBtn::NcCreate(LPCREATESTRUCT lpcs)
{
// assign user data
SetWindowLongPtr(_hwnd, GWLP_USERDATA, (LONG_PTR)this);
// enforce window style bits
lpcs->style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
lpcs->dwExStyle |= WS_EX_TRANSPARENT;
SetWindowLong(_hwnd, GWL_STYLE, lpcs->style);
SetWindowLong(_hwnd, GWL_EXSTYLE, lpcs->dwExStyle);
// enforce min height
SetText(lpcs->lpszName);
if (lpcs->cy < _sizeCaption.cy) {
lpcs->cy = _sizeCaption.cy;
SetWindowPos(_hwnd, NULL, 0, 0, lpcs->cx, lpcs->cy,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}
return TRUE;
}
// WM_NCCALCSIZE handler
LRESULT CGroupBtn::NcCalcSize(BOOL fCalcValidRects, LPNCCALCSIZE_PARAMS pnccs)
{
LRESULT lRet = FALSE;
RECT rcClient;
if (fCalcValidRects && CalcClientRect(&pnccs->rgrc[0], &rcClient)) {
pnccs->rgrc[1] = pnccs->rgrc[2];
pnccs->rgrc[0] = pnccs->rgrc[2] = rcClient;
return WVR_VALIDRECTS;
}
return lRet;
}
// WM_NCPAINT handler
void CGroupBtn::NcPaint(HRGN hrgn)
{
RECT rcWindow;
DWORD dwStyle = GetWindowLong(_hwnd, GWL_STYLE);
HDC hdc;
GetWindowRect(_hwnd, &rcWindow);
OffsetRect(&rcWindow, -rcWindow.left, -rcWindow.top);
if ((hdc = GetWindowDC(_hwnd)) != NULL) {
if (dwStyle & WS_BORDER) {
HBRUSH hbr = CreateSolidBrush(COLOR_WINDOWFRAME);
FrameRect(hdc, &rcWindow, hbr);
DeleteObject(hbr);
}
rcWindow.bottom = rcWindow.top + _sizeCaption.cy;
SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
ExtTextOut(hdc, rcWindow.left, rcWindow.top,
ETO_OPAQUE, &rcWindow, NULL, 0, NULL);
InflateRect(&rcWindow, -CAPTION_HPADDING, -(CAPTION_VPADDING / 2));
HFONT hfPrev = (HFONT)SelectObject(hdc, GetFont());
ExtTextOut(hdc, rcWindow.left, rcWindow.top,
ETO_OPAQUE, &rcWindow, _pszCaption,
_pszCaption ? lstrlen(_pszCaption) : 0, NULL);
SelectObject(hdc, hfPrev);
#ifdef KEYBOARDCUES
if (0 == (_fKeyboardCues & UISF_HIDEFOCUS))
#endif
{
if (GetFocus() == _hwnd) {
rcWindow.right = rcWindow.left + _sizeCaption.cx + 1;
InflateRect(&rcWindow, 1, 0);
DrawFocusRect(hdc, &rcWindow);
}
}
ReleaseDC(_hwnd, hdc);
}
}
// WM_NCMOUSEMOVE handler
LRESULT CGroupBtn::NcMouseMove(WPARAM nHittest, LONG x, LONG y)
{
if (HTCAPTION == nHittest) {
RECT rc;
POINT pt;
GetWindowRect(_hwnd, &rc);
rc.bottom = rc.top + _sizeCaption.cy;
rc.right = rc.left + _sizeCaption.cx;
InflateRect(&rc, 0, -(CAPTION_VPADDING / 2));
pt.x = x;
pt.y = y;
if (PtInRect(&rc, pt)) {
HCURSOR hc = GetHandCursor();
if (hc != NULL) {
SetCursor(hc);
return 0L;
}
}
}
return DefWindowProc(_hwnd, WM_NCMOUSEMOVE, nHittest, MAKELPARAM(x, y));
}
// WM_NCHITTEST handler
LRESULT CGroupBtn::NcHitTest(LONG x, LONG y)
{
POINT pt;
RECT rc, rcClient;
DWORD dwStyle = GetWindowLong(_hwnd, GWL_STYLE);
pt.x = x;
pt.y = y;
GetWindowRect(_hwnd, &rc);
CalcClientRect(&rc, &rcClient);
if (PtInRect(&rcClient, pt))
return HTTRANSPARENT;
if (PtInRect(&rc, pt)) {
if (dwStyle & WS_BORDER) {
if (pt.x == rc.left ||
pt.x == rc.right ||
pt.y == rc.bottom)
return HTBORDER;
}
return HTCAPTION;
}
return HTNOWHERE;
}
LRESULT CGroupBtn::NcButtonDown(UINT nMsg, WPARAM nHittest, const POINTS& pts)
{
LRESULT lRet = 0L;
if (HTCAPTION == nHittest) {
SetCursor(GetHandCursor());
MODIFY_CAPTURE(CF_SETCAPTURE, 0);
if (GetFocus() != _hwnd) {
MODIFY_CAPTURE(CF_SETFOCUS, 0);
EnableNotifications(FALSE); // so the host doesn't reposition the link.
SetFocus(_hwnd);
EnableNotifications(TRUE);
}
SetCapture(_hwnd);
} else
lRet = DefWindowProc(_hwnd, nMsg, nHittest, MAKELONG(pts.x, pts.y));
return lRet;
}
LRESULT CGroupBtn::ButtonUp(UINT nMsg, WPARAM nHittest, const POINTS& pts)
{
if (TEST_CAPTURE(CF_SETCAPTURE)) {
ReleaseCapture();
MODIFY_CAPTURE(0, CF_SETCAPTURE);
POINT ptScrn;
ptScrn.x = pts.x;
ptScrn.y = pts.y;
MapWindowPoints(_hwnd, HWND_DESKTOP, &ptScrn, 1);
LRESULT nHittest = SendMessage(_hwnd, WM_NCHITTEST, 0, MAKELONG(ptScrn.x, ptScrn.y));
if (HTCAPTION == nHittest) {
switch (nMsg) {
case WM_LBUTTONUP:
SendNotify(NM_CLICK);
break;
case WM_RBUTTONUP:
SendNotify(NM_RCLICK);
break;
}
}
}
if (TEST_CAPTURE(CF_SETFOCUS)) {
MODIFY_CAPTURE(0, CF_SETFOCUS);
if (GetFocus() == _hwnd) // if we still have the focus...
SendNotify(NM_SETFOCUS);
}
return 0L;
}
// Non-client mouse click/dblclk handler
LRESULT CGroupBtn::NcDblClick(UINT nMsg, WPARAM nHittest, LPARAM lParam)
{
LRESULT lRet = 0L;
if (HTCAPTION == nHittest) {
SetFocus(_hwnd);
lRet = DefWindowProc(_hwnd, nMsg, HTCLIENT, lParam);
switch (nMsg) {
case WM_NCLBUTTONDBLCLK:
SendNotify(NM_DBLCLK);
break;
case WM_NCRBUTTONDBLCLK:
SendNotify(NM_RDBLCLK);
break;
}
} else
lRet = DefWindowProc(_hwnd, nMsg, nHittest, lParam);
return lRet;
}
LONG CGroupBtn::EnableNotifications(BOOL bEnable)
{
if (bEnable) {
if (_cNotifyLocks > 0)
_cNotifyLocks--;
} else
_cNotifyLocks++;
return _cNotifyLocks;
}
// WM_NOTIFY transmit helper
LRESULT CGroupBtn::SendNotify(int nCode, IN OPTIONAL NMHDR* pnmh)
{
if (0 == _cNotifyLocks) {
NMHDR hdr;
if (NULL == pnmh)
pnmh = &hdr;
pnmh->hwndFrom = _hwnd;
pnmh->idFrom = GetDlgCtrlID(_hwnd);
pnmh->code = nCode;
return SendMessage(GetParent(_hwnd), WM_NOTIFY, hdr.idFrom, (LPARAM)pnmh);
}
return 0;
}
// WM_NOTIFY transmit helper
void CGroupBtn::PostNotify(int nCode)
{
if (0 == _cNotifyLocks)
PostMessage(_hwnd, GBM_SENDNOTIFY, nCode, 0L);
}
LRESULT CGroupBtn::WndProc(HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
CGroupBtn* pThis = (CGroupBtn*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
LRESULT lRet = 0;
switch (nMsg) {
case WM_NCHITTEST:
{
POINTS pts = MAKEPOINTS(lParam);
return pThis->NcHitTest(pts.x, pts.y);
}
case WM_NCMOUSEMOVE:
{
POINTS pts = MAKEPOINTS(lParam);
return pThis->NcMouseMove(wParam, pts.x, pts.y);
}
case WM_NCCALCSIZE:
return pThis->NcCalcSize((BOOL)wParam, (LPNCCALCSIZE_PARAMS)lParam);
case WM_NCPAINT:
pThis->NcPaint((HRGN)wParam);
return 0;
case WM_WINDOWPOSCHANGING:
return pThis->WindowPosChanging((LPWINDOWPOS)lParam);
case WM_SIZE:
{
POINTS pts = MAKEPOINTS(lParam);
return pThis->OnSize(wParam, pts.x, pts.y);
}
case WM_DESTROY:
if (IsWindow(pThis->_hwndBuddy))
DestroyWindow(pThis->_hwndBuddy);
break;
case WM_ERASEBKGND:
return TRUE; // transparent: no erase bkgnd
case WM_NCLBUTTONDOWN:
case WM_NCRBUTTONDOWN:
{
POINTS pts = MAKEPOINTS(lParam);
return pThis->NcButtonDown(nMsg, wParam, pts);
}
case WM_LBUTTONUP:
case WM_RBUTTONUP:
{
POINTS pts = MAKEPOINTS(lParam);
return pThis->ButtonUp(nMsg, wParam, pts);
}
case WM_NCLBUTTONDBLCLK:
case WM_NCRBUTTONDBLCLK:
return pThis->NcDblClick(nMsg, wParam, lParam);
case WM_SHOWWINDOW:
if (IsWindow(pThis->_hwndBuddy))
ShowWindow(pThis->_hwndBuddy, wParam ? SW_SHOW : SW_HIDE);
break;
case WM_SETTEXT:
pThis->SetText((LPCTSTR)lParam);
return TRUE;
case WM_GETTEXT:
return pThis->GetText((LPTSTR)lParam, (int)wParam);
case WM_SETFONT:
pThis->SetFont((HFONT)wParam);
if (lParam /* fRedraw */)
InvalidateRect(hwnd, NULL, TRUE);
break;
case WM_CAPTURECHANGED:
if (lParam /* NULL if we called ReleaseCapture() */)
pThis->OnCaptureLost((HWND)lParam);
break;
case WM_SETFOCUS:
pThis->NcPaint((HRGN)1);
pThis->SendNotify(NM_SETFOCUS);
break;
case WM_KILLFOCUS:
pThis->NcPaint((HRGN)1);
pThis->SendNotify(NM_KILLFOCUS);
break;
case WM_GETDLGCODE:
{
MSG* pmsg;
lRet = DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON;
if ((pmsg = (MSG*)lParam)) {
if ((WM_KEYDOWN == pmsg->message || WM_KEYUP == pmsg->message)) {
switch (pmsg->wParam) {
case VK_RETURN:
case VK_SPACE:
lRet |= DLGC_WANTALLKEYS;
break;
}
} else if (WM_CHAR == pmsg->message && VK_RETURN == pmsg->wParam) {
// Eat VK_RETURN WM_CHARs; we don't want
// Dialog manager to beep when IsDialogMessage gets it.
return lRet |= DLGC_WANTMESSAGE;
}
}
return lRet;
}
case WM_KEYDOWN:
case WM_KEYUP:
case WM_CHAR:
switch (wParam) {
case VK_RETURN:
case VK_SPACE:
if (WM_KEYDOWN == nMsg)
pThis->SendNotify(NM_RETURN);
return 0L;
}
break;
#ifdef KEYBOARDCUES
case WM_UPDATEUISTATE:
if (_HandleWM_UPDATEUISTATE(wParam, lParam, &pThis->_fKeyboardCues))
SendMessage(hwnd, WM_NCPAINT, 1, 0L);
break;
#endif
case GBM_SETPLACEMENT:
if (lParam)
return pThis->SetPlacement((PGBPLACEMENT)lParam);
return 0L;
case GBM_SETDROPSTATE: // WPARAM: BOOL fDropped, LPARAM: n/a, return: BOOL
return 0L;
case GBM_GETDROPSTATE: // WPARAM: n/a, LPARAM: n/a, return: BOOL fDropped
return 0L;
case GBM_SENDNOTIFY:
pThis->SendNotify((int)wParam);
break;
case WM_NCCREATE:
{
LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
if ((pThis = new CGroupBtn(hwnd)) == NULL)
break;
if (!pThis->NcCreate((LPCREATESTRUCT)lParam))
return FALSE;
break;
}
case WM_NCDESTROY:
lRet = DefWindowProc(hwnd, nMsg, wParam, lParam);
SetWindowPtr(hwnd, GWLP_USERDATA, NULL);
pThis->_hwnd = NULL;
pThis->Release();
return lRet;
case WM_CREATE:
#ifdef KEYBOARDCUES
_InitializeUISTATE(hwnd, &pThis->_fKeyboardCues);
#endif//KEYBOARDCUES
pThis->SetText(((LPCREATESTRUCT)lParam)->lpszName);
break;
case GBM_SETBUDDY: // WPARAM: HWND hwndBuddy, LPARAM: MAKELPARAM(cxMargin, cyMargin), return: BOOL
return pThis->SetBuddy((HWND)wParam, (ULONG)lParam);
case GBM_GETBUDDY: // WPARAM: n/a, LPARAM: n/a, return: HWND
return (LRESULT)pThis->_hwndBuddy;
default:
// oleacc defs thunked for WINVER < 0x0500
if (IsWM_GETOBJECT(nMsg) && (OBJID_CLIENT == lParam || OBJID_TITLEBAR == lParam))
return LresultFromObject(IID_IAccessible, wParam, SAFECAST(pThis, IAccessible*));
break;
}
return DefWindowProc(hwnd, nMsg, wParam, lParam);
}
LRESULT CGroupBtn::BuddyProc(HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
CGroupBtn* pBtn = (CGroupBtn*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
ASSERT(pBtn);
switch (nMsg) {
case WM_SIZE:
{
LRESULT lRet = CallWindowProc(pBtn->_pfnBuddy, hwnd, nMsg, wParam, lParam);
if (!pBtn->_fInLayout)
pBtn->DoLayout();
return lRet;
}
case WM_DESTROY:
{
WNDPROC pfn = pBtn->_pfnBuddy;
pBtn->SetBuddy(NULL, 0);
return CallWindowProc(pfn, hwnd, nMsg, wParam, lParam);
}
break;
}
return pBtn->_pfnBuddy ? CallWindowProc(pBtn->_pfnBuddy, hwnd, nMsg, wParam, lParam) :
0L;
}
// CAccessibleBase IUnknown impl
STDMETHODIMP CAccessibleBase::QueryInterface(REFIID riid, void** ppvObj)
{
HRESULT hres;
static const QITAB qit[] =
{
QITABENT(CAccessibleBase, IDispatch),
QITABENT(CAccessibleBase, IAccessible),
QITABENT(CAccessibleBase, IOleWindow),
{ 0 },
};
hres = QISearch(this, (LPCQITAB)qit, riid, ppvObj);
return hres;
}
STDMETHODIMP_(ULONG) CAccessibleBase::AddRef()
{
return InterlockedIncrement((LONG*)&_cRef);
}
STDMETHODIMP_(ULONG) CAccessibleBase::Release()
{
ULONG cRef = InterlockedDecrement((LONG*)&_cRef);
if (cRef <= 0) {
DllRelease();
delete this;
}
return cRef;
}
// CAccessibleBase IOleWindow impl
STDMETHODIMP CAccessibleBase::GetWindow(HWND* phwnd)
{
*phwnd = _hwnd;
return IsWindow(_hwnd) ? S_OK : S_FALSE;
}
// CAccessibleBase IDispatch impl
static BOOL _accLoadTypeInfo(ITypeInfo** ppti)
{
ITypeLib* ptl;
HRESULT hr = LoadTypeLib(L"oleacc.dll", &ptl);
if (SUCCEEDED(hr)) {
hr = ptl->GetTypeInfoOfGuid(IID_IAccessible, ppti);
ATOMICRELEASE(ptl);
}
return hr;
}
STDMETHODIMP CAccessibleBase::GetTypeInfoCount(UINT* pctinfo)
{
*pctinfo = 1;
return S_OK;
}
STDMETHODIMP CAccessibleBase::GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo** pptinfo)
{
HRESULT hr = E_FAIL;
if (NULL == _ptiAcc && FAILED((hr = _accLoadTypeInfo(&_ptiAcc))))
return hr;
*pptinfo = _ptiAcc;
(*pptinfo)->AddRef();
return S_OK;
}
STDMETHODIMP CAccessibleBase::GetIDsOfNames(
REFIID riid,
OLECHAR** rgszNames,
UINT cNames,
LCID lcid, DISPID* rgdispid)
{
HRESULT hr = E_FAIL;
if (IID_NULL != riid && IID_IAccessible != riid)
return DISP_E_UNKNOWNINTERFACE;
if (NULL == _ptiAcc && FAILED((hr = _accLoadTypeInfo(&_ptiAcc))))
return hr;
return _ptiAcc->GetIDsOfNames(rgszNames, cNames, rgdispid);
}
STDMETHODIMP CAccessibleBase::Invoke(
DISPID dispidMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS* pdispparams,
VARIANT* pvarResult,
EXCEPINFO* pexcepinfo,
UINT* puArgErr)
{
HRESULT hr = E_FAIL;
if (IID_NULL != riid && IID_IAccessible != riid)
return DISP_E_UNKNOWNINTERFACE;
if (NULL == _ptiAcc && FAILED((hr = _accLoadTypeInfo(&_ptiAcc))))
return hr;
return _ptiAcc->Invoke(this, dispidMember, wFlags, pdispparams,
pvarResult, pexcepinfo, puArgErr);
}
STDMETHODIMP CAccessibleBase::get_accParent(IDispatch** ppdispParent)
{
*ppdispParent = NULL;
if (IsWindow(_hwnd))
return AccessibleObjectFromWindow(_hwnd, OBJID_WINDOW, IID_IDispatch, (void**)ppdispParent);
return S_FALSE;
}
STDMETHODIMP CAccessibleBase::get_accChildCount(long* pcChildren)
{
*pcChildren = 0;
return S_OK;
}
STDMETHODIMP CAccessibleBase::get_accChild(VARIANT varChildIndex, IDispatch** ppdispChild)
{
*ppdispChild = NULL;
return S_FALSE;
}
STDMETHODIMP CAccessibleBase::get_accValue(VARIANT varChild, BSTR* pbstrValue)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
*pbstrValue = NULL;
return S_FALSE;
}
STDMETHODIMP CAccessibleBase::get_accDescription(VARIANT varChild, BSTR* pbstrDescription)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
*pbstrDescription = NULL;
return S_FALSE;
}
STDMETHODIMP CAccessibleBase::get_accRole(VARIANT varChild, VARIANT* pvarRole)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
pvarRole->vt = VT_I4;
pvarRole->lVal = ROLE_SYSTEM_LINK;
return S_OK;
}
STDMETHODIMP CAccessibleBase::get_accState(VARIANT varChild, VARIANT* pvarState)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
pvarState->vt = VT_I4;
pvarState->lVal = STATE_SYSTEM_DEFAULT;
if (GetFocus() == _hwnd)
pvarState->lVal |= STATE_SYSTEM_FOCUSED;
else if (IsWindowEnabled(_hwnd))
pvarState->lVal |= STATE_SYSTEM_FOCUSABLE;
if (!IsWindowVisible(_hwnd))
pvarState->lVal |= STATE_SYSTEM_INVISIBLE;
return S_OK;
}
STDMETHODIMP CAccessibleBase::get_accHelp(VARIANT varChild, BSTR* pbstrHelp)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
*pbstrHelp = NULL;
return S_FALSE;
}
STDMETHODIMP CAccessibleBase::get_accHelpTopic(BSTR* pbstrHelpFile, VARIANT varChild, long* pidTopic)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
*pbstrHelpFile = NULL;
*pidTopic = -1;
return S_FALSE;
}
STDMETHODIMP CAccessibleBase::get_accKeyboardShortcut(VARIANT varChild, BSTR* pbstrKeyboardShortcut)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
*pbstrKeyboardShortcut = NULL;
return S_FALSE;
}
STDMETHODIMP CAccessibleBase::get_accFocus(VARIANT FAR* pvarFocusChild)
{
HWND hwndFocus;
if ((hwndFocus = GetFocus()) == _hwnd || IsChild(_hwnd, hwndFocus)) {
pvarFocusChild->vt = VT_I4;
pvarFocusChild->lVal = CHILDID_SELF;
return S_OK;
}
return S_FALSE;
}
STDMETHODIMP CAccessibleBase::get_accSelection(VARIANT FAR* pvarSelectedChildren)
{
return get_accFocus(pvarSelectedChildren); // implemented same as focus.
}
STDMETHODIMP CAccessibleBase::get_accDefaultAction(VARIANT varChild, BSTR* pbstrDefaultAction)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
WCHAR wsz[128];
if (LoadStringW(HINST_THISDLL, GetDefaultActionStringID(), wsz, ARRAYSIZE(wsz))) {
if (NULL == (*pbstrDefaultAction = SysAllocString(wsz)))
return E_OUTOFMEMORY;
return S_OK;
}
return E_FAIL;
}
STDMETHODIMP CAccessibleBase::accSelect(long flagsSelect, VARIANT varChild)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
if (flagsSelect & SELFLAG_TAKEFOCUS) {
SetFocus(_hwnd);
return S_OK;
}
return S_FALSE;
}
STDMETHODIMP CAccessibleBase::accLocation(long* pxLeft, long* pyTop, long* pcxWidth, long* pcyHeight, VARIANT varChild)
{
RECT rc;
GetWindowRect(_hwnd, &rc);
*pxLeft = rc.left;
*pyTop = rc.top;
*pcxWidth = RECTWIDTH(&rc);
*pcyHeight = RECTHEIGHT(&rc);
varChild.vt = VT_I4;
varChild.lVal = CHILDID_SELF;
return S_OK;
}
STDMETHODIMP CAccessibleBase::accNavigate(long navDir, VARIANT varStart, VARIANT* pvarEndUpAt)
{
return S_FALSE;
}
STDMETHODIMP CAccessibleBase::accHitTest(long xLeft, long yTop, VARIANT* pvarChildAtPoint)
{
pvarChildAtPoint->vt = VT_I4;
pvarChildAtPoint->lVal = CHILDID_SELF;
return S_OK;
}
STDMETHODIMP CAccessibleBase::put_accName(VARIANT varChild, BSTR bstrName)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
return S_FALSE;
}
STDMETHODIMP CAccessibleBase::put_accValue(VARIANT varChild, BSTR bstrValue)
{
VALIDATEACCCHILD(varChild, CHILDID_SELF, E_INVALIDARG);
return S_FALSE;
}
#ifdef KEYBOARDCUES
// KEYBOARDCUES helpes
BOOL _HandleWM_UPDATEUISTATE(
IN WPARAM wParam,
IN LPARAM lParam,
IN OUT UINT* puFlags)
{
UINT uFlags = *puFlags;
switch (LOWORD(wParam)) {
case UIS_CLEAR:
*puFlags &= ~(HIWORD(wParam));
break;
case UIS_SET:
*puFlags |= HIWORD(wParam);
break;
}
return uFlags != *puFlags;
}
void _InitializeUISTATE(IN HWND hwnd, IN OUT UINT* puFlags)
{
HWND hwndParent = GetParent(hwnd);
*puFlags = (UINT)SendMessage(hwndParent, WM_QUERYUISTATE, 0, 0L);
}
#endif//KEYBOARDCUES