You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1510 lines
31 KiB
1510 lines
31 KiB
// File: GenWindow.cpp
|
|
|
|
#include "precomp.h"
|
|
|
|
#include "GenWindow.h"
|
|
#include "GenContainers.h"
|
|
|
|
#include <windowsx.h>
|
|
|
|
// We need a different tooltip window for each top level window, or the tooltip
|
|
// will get hidden behind the window
|
|
struct TT_TopWindow
|
|
{
|
|
HWND hwndTop;
|
|
HWND hwndTooltip;
|
|
} ;
|
|
|
|
class CTopWindowArray
|
|
{
|
|
private:
|
|
enum { InitSize = 4 } ;
|
|
|
|
TT_TopWindow *m_pArray;
|
|
UINT m_nArrayLen;
|
|
|
|
int FindIndex(HWND hwndTop)
|
|
{
|
|
if (NULL == m_pArray)
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
// Just a linear search
|
|
int i;
|
|
for (i=m_nArrayLen-1; i>=0; --i)
|
|
{
|
|
if (m_pArray[i].hwndTop == hwndTop)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return(i);
|
|
}
|
|
|
|
public:
|
|
CTopWindowArray() :
|
|
m_pArray(NULL)
|
|
{
|
|
}
|
|
|
|
~CTopWindowArray()
|
|
{
|
|
delete[] m_pArray;
|
|
}
|
|
|
|
static HWND GetTopFrame(HWND hwnd)
|
|
{
|
|
HWND hwndParent;
|
|
while (NULL != (hwndParent = GetParent(hwnd)))
|
|
{
|
|
hwnd = hwndParent;
|
|
}
|
|
|
|
return(hwnd);
|
|
}
|
|
|
|
void GrowArray()
|
|
{
|
|
if (NULL == m_pArray)
|
|
{
|
|
m_nArrayLen = InitSize;
|
|
m_pArray = new TT_TopWindow[m_nArrayLen];
|
|
ZeroMemory(m_pArray, m_nArrayLen*sizeof(TT_TopWindow));
|
|
return;
|
|
}
|
|
|
|
// Grow exponentially
|
|
TT_TopWindow *pArray = new TT_TopWindow[m_nArrayLen*2];
|
|
if (NULL == pArray)
|
|
{
|
|
// very bad
|
|
return;
|
|
}
|
|
|
|
CopyMemory(pArray, m_pArray, m_nArrayLen*sizeof(TT_TopWindow));
|
|
ZeroMemory(pArray+m_nArrayLen, m_nArrayLen*sizeof(TT_TopWindow));
|
|
|
|
delete[] m_pArray;
|
|
m_pArray = pArray;
|
|
m_nArrayLen *= 2;
|
|
}
|
|
|
|
void Add(HWND hwndTop, HWND hwndTooltip)
|
|
{
|
|
hwndTop = GetTopFrame(hwndTop);
|
|
|
|
// I'm going to allow multiple adds of the same thing, but then you
|
|
// must have the corresponding number of removes
|
|
|
|
int i = FindIndex(NULL);
|
|
if (i < 0)
|
|
{
|
|
GrowArray();
|
|
i = FindIndex(NULL);
|
|
|
|
if (i < 0)
|
|
{
|
|
// Very bad
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_pArray[i].hwndTop = hwndTop;
|
|
m_pArray[i].hwndTooltip = hwndTooltip;
|
|
}
|
|
|
|
void Remove(HWND hwndTop)
|
|
{
|
|
hwndTop = GetTopFrame(hwndTop);
|
|
|
|
int i = FindIndex(hwndTop);
|
|
if (i >= 0)
|
|
{
|
|
// LAZYLAZY georgep: I'm never going to shrink the array
|
|
m_pArray[i].hwndTop = NULL;
|
|
m_pArray[i].hwndTooltip = NULL;
|
|
}
|
|
}
|
|
|
|
HWND Find(HWND hwndTop)
|
|
{
|
|
hwndTop = GetTopFrame(hwndTop);
|
|
|
|
int i = FindIndex(hwndTop);
|
|
if (i >= 0)
|
|
{
|
|
return(m_pArray[i].hwndTooltip);
|
|
}
|
|
return(NULL);
|
|
}
|
|
|
|
int GetCount()
|
|
{
|
|
if (NULL == m_pArray)
|
|
{
|
|
return(0);
|
|
}
|
|
|
|
int c = 0;
|
|
for (int i=m_nArrayLen-1; i>=0; --i)
|
|
{
|
|
if (NULL != m_pArray[i].hwndTop)
|
|
{
|
|
++c;
|
|
}
|
|
}
|
|
|
|
return(c);
|
|
}
|
|
} ;
|
|
|
|
static inline BOOL TT_AddToolInfo(HWND hwnd, TOOLINFO *pti)
|
|
{
|
|
return (BOOL)(SendMessage(hwnd, TTM_ADDTOOL, 0, reinterpret_cast<LPARAM>(pti)) != 0);
|
|
}
|
|
|
|
static inline void TT_DelToolInfo(HWND hwnd, TOOLINFO *pti)
|
|
{
|
|
SendMessage(hwnd, TTM_DELTOOL, 0, reinterpret_cast<LPARAM>(pti));
|
|
}
|
|
|
|
static inline BOOL TT_GetToolInfo(HWND hwnd, TOOLINFO *pti)
|
|
{
|
|
return (BOOL)(SendMessage(hwnd, TTM_GETTOOLINFO, 0, reinterpret_cast<LPARAM>(pti)) != 0);
|
|
}
|
|
|
|
static inline void TT_SetToolInfo(HWND hwnd, TOOLINFO *pti)
|
|
{
|
|
SendMessage(hwnd, TTM_SETTOOLINFO, 0, reinterpret_cast<LPARAM>(pti));
|
|
}
|
|
|
|
static inline int TT_GetToolCount(HWND hwnd)
|
|
{
|
|
return (int)(SendMessage(hwnd, TTM_GETTOOLCOUNT, 0, 0));
|
|
}
|
|
|
|
CGenWindow *CGenWindow::g_pCurHot = NULL;
|
|
|
|
const DWORD IGenWindow::c_msgFromHandle = RegisterWindowMessage(_TEXT("NetMeeting::FromHandle"));
|
|
|
|
IGenWindow *IGenWindow::FromHandle(HWND hwnd)
|
|
{
|
|
return(reinterpret_cast<IGenWindow*>(SendMessage(hwnd, c_msgFromHandle, 0, 0)));
|
|
}
|
|
|
|
// HACKHACK georgep: Need to make this larger than the largest DM_ message
|
|
enum
|
|
{
|
|
GWM_LAYOUT = WM_USER + 111,
|
|
GWM_CUSTOM,
|
|
} ;
|
|
|
|
CGenWindow::CGenWindow()
|
|
: m_hwnd(NULL), m_lUserData(0)
|
|
{
|
|
// Init the ref count to 1
|
|
REFCOUNT::AddRef();
|
|
// This marks this object for deletion when the ref count goes to 0.
|
|
REFCOUNT::Delete();
|
|
}
|
|
|
|
CGenWindow::~CGenWindow()
|
|
{
|
|
// I don't think the HWND can still exist, since the window proc does an AddRef
|
|
ASSERT(!m_hwnd);
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE CGenWindow::QueryInterface(REFGUID riid, LPVOID *ppv)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if ((__uuidof(IGenWindow) == riid) || (IID_IUnknown == riid))
|
|
{
|
|
*ppv = dynamic_cast<IGenWindow *>(this);
|
|
}
|
|
else if (__uuidof(CGenWindow) == riid)
|
|
{
|
|
*ppv = this;
|
|
}
|
|
else
|
|
{
|
|
hr = E_NOINTERFACE;
|
|
*ppv = NULL;
|
|
}
|
|
|
|
if (S_OK == hr)
|
|
{
|
|
AddRef();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
BOOL CGenWindow::Create(
|
|
HWND hWndParent, // Window parent
|
|
LPCTSTR szWindowName, // Window name
|
|
DWORD dwStyle, // Window style
|
|
DWORD dwEXStyle, // Extended window style
|
|
int x, // Window pos: x
|
|
int y, // Window pos: y
|
|
int nWidth, // Window size: width
|
|
int nHeight, // Window size: height
|
|
HINSTANCE hInst, // The hInstance to create the window on
|
|
HMENU hmMain, // Window menu
|
|
LPCTSTR szClassName // The class name to use
|
|
)
|
|
{
|
|
if (NULL != m_hwnd)
|
|
{
|
|
// Alread created
|
|
return(FALSE);
|
|
}
|
|
|
|
if (NULL == szClassName)
|
|
{
|
|
szClassName = TEXT("NMGenWindowClass");
|
|
}
|
|
|
|
if (!InitWindowClass(szClassName, hInst))
|
|
{
|
|
// Couldn't init the window class
|
|
return(FALSE);
|
|
}
|
|
|
|
BOOL ret = (NULL != CreateWindowEx(dwEXStyle, szClassName, szWindowName, dwStyle,
|
|
x, y, nWidth, nHeight, hWndParent, hmMain,
|
|
hInst, (LPVOID)this));
|
|
|
|
#ifdef DEBUG
|
|
if (!ret)
|
|
{
|
|
GetLastError();
|
|
}
|
|
#endif // DEBUG
|
|
|
|
return(ret);
|
|
}
|
|
|
|
BOOL CGenWindow::Create(
|
|
HWND hWndParent, // Window parent
|
|
INT_PTR nId, // ID of the child window
|
|
LPCTSTR szWindowName, // Window name
|
|
DWORD dwStyle, // Window style; WS_CHILD|WS_VISIBLE will be added to this
|
|
DWORD dwEXStyle // Extended window style
|
|
)
|
|
{
|
|
ASSERT(NULL != hWndParent);
|
|
|
|
// Child windows should default to visible
|
|
return(Create(
|
|
hWndParent, // Window parent
|
|
szWindowName, // Window name
|
|
dwStyle|WS_CHILD|WS_VISIBLE, // Window style
|
|
dwEXStyle, // Extended window style
|
|
0, // Window pos: x
|
|
0, // Window pos: y
|
|
10, // Window size: width
|
|
10, // Window size: height
|
|
reinterpret_cast<HINSTANCE>(GetWindowLongPtr(hWndParent, GWLP_HINSTANCE)),
|
|
reinterpret_cast<HMENU>(nId) // Window menu
|
|
));
|
|
}
|
|
|
|
BOOL CGenWindow::InitWindowClass(LPCTSTR szClassName, HINSTANCE hThis)
|
|
{
|
|
WNDCLASS wc;
|
|
|
|
// See if the class is already registered
|
|
if (GetClassInfo(hThis, szClassName, &wc))
|
|
{
|
|
ASSERT(RealWindowProc == wc.lpfnWndProc);
|
|
|
|
// Already registered
|
|
return(TRUE);
|
|
}
|
|
|
|
// If not, attempt to register it
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = 0;
|
|
// BUGBUG georgep: Hard-coding the background color for now
|
|
// wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
|
|
// wc.hbrBackground = CreateSolidBrush(RGB(0xA9, 0xA9, 0xA9));
|
|
wc.hbrBackground = NULL;
|
|
wc.hCursor = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
|
|
wc.hIcon = NULL;
|
|
wc.hInstance = hThis;
|
|
wc.lpfnWndProc = RealWindowProc;
|
|
wc.lpszClassName = szClassName;
|
|
wc.lpszMenuName = NULL;
|
|
wc.style = CS_DBLCLKS;
|
|
|
|
return(RegisterClass(&wc));
|
|
}
|
|
|
|
LRESULT CALLBACK CGenWindow::RealWindowProc(
|
|
HWND hWnd,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam
|
|
)
|
|
{
|
|
// Handle the WM_CREATE message
|
|
if (WM_NCCREATE == message)
|
|
{
|
|
HANDLE_WM_NCCREATE(hWnd, wParam, lParam, OnNCCreate);
|
|
}
|
|
|
|
// Get the "this" pointer and call the ProcessMessage virtual method
|
|
LRESULT ret = 0;
|
|
CGenWindow* pWnd = reinterpret_cast<CGenWindow*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
|
|
|
|
// 'pWnd' won't be valid for any messages that come before WM_NCCREATE or after WM_NCDESTROY
|
|
if(NULL != pWnd)
|
|
{
|
|
// Messages after WM_NCCREATE:
|
|
ret = pWnd->ProcessMessage(hWnd, message, wParam, lParam);
|
|
}
|
|
else
|
|
{
|
|
// Messages before WM_CREATE:
|
|
ret = DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
|
|
// Clean up on WM_NCDESTROY
|
|
if (WM_NCDESTROY == message && NULL != pWnd)
|
|
{
|
|
SetWindowLongPtr(hWnd, GWLP_USERDATA, 0);
|
|
pWnd->m_hwnd = NULL;
|
|
|
|
pWnd->OnMouseLeave();
|
|
pWnd->Release();
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
void CGenWindow::OnShowWindow(HWND hwnd, BOOL fShow, int fnStatus)
|
|
{
|
|
OnDesiredSizeChanged();
|
|
}
|
|
|
|
LRESULT CGenWindow::ProcessMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (message)
|
|
{
|
|
HANDLE_MSG(hwnd, WM_SIZE , OnSize);
|
|
HANDLE_MSG(hwnd, WM_ERASEBKGND, OnEraseBkgnd);
|
|
HANDLE_MSG(hwnd, WM_MOUSEMOVE , OnMouseMove);
|
|
HANDLE_MSG(hwnd, WM_SHOWWINDOW, OnShowWindow);
|
|
|
|
case WM_MOUSELEAVE:
|
|
OnMouseLeave();
|
|
break;
|
|
|
|
case GWM_LAYOUT:
|
|
Layout();
|
|
break;
|
|
|
|
case GWM_CUSTOM:
|
|
reinterpret_cast<InvokeProc>(lParam)(this, wParam);
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
RemoveTooltip();
|
|
break;
|
|
|
|
default:
|
|
if (c_msgFromHandle == message)
|
|
{
|
|
// Return the IGenWindow* for this object, as specified by the
|
|
// IGenWindow interface
|
|
return(reinterpret_cast<LRESULT>(dynamic_cast<IGenWindow*>(this)));
|
|
}
|
|
}
|
|
|
|
return(DefWindowProc(hwnd, message, wParam, lParam));
|
|
}
|
|
|
|
void CGenWindow::ScheduleLayout()
|
|
{
|
|
HWND hwnd = GetWindow();
|
|
|
|
MSG msg;
|
|
// I don't know why we are getting messages for windows other than our own,
|
|
// but it seems to happen for top level windows
|
|
if (PeekMessage(&msg, hwnd, GWM_LAYOUT, GWM_LAYOUT, PM_NOREMOVE|PM_NOYIELD)
|
|
&& (msg.hwnd == hwnd))
|
|
{
|
|
// Message already posted
|
|
return;
|
|
}
|
|
|
|
if (!PostMessage(hwnd, GWM_LAYOUT, 0, 0))
|
|
{
|
|
Layout();
|
|
}
|
|
}
|
|
|
|
BOOL CGenWindow::AsyncInvoke(InvokeProc proc, WPARAM wParam)
|
|
{
|
|
return(!PostMessage(GetWindow(), GWM_CUSTOM, wParam, reinterpret_cast<LPARAM>(proc)));
|
|
}
|
|
|
|
void CGenWindow::OnSize(HWND hwnd, UINT state, int cx, int cy)
|
|
{
|
|
// Call the virtual Layout, and then forward to DefWindowProc
|
|
ScheduleLayout();
|
|
|
|
// Update the Tooltip info
|
|
TOOLINFO ti;
|
|
TCHAR szTip[MAX_PATH];
|
|
BOOL bExist = InitToolInfo(&ti, szTip);
|
|
if (bExist)
|
|
{
|
|
GetClientRect(hwnd, &ti.rect);
|
|
|
|
HWND hwndTooltip = g_pTopArray->Find(hwnd);
|
|
TT_SetToolInfo(hwndTooltip, &ti);
|
|
}
|
|
|
|
FORWARD_WM_SIZE(hwnd, state, cx, cy, DefWindowProc);
|
|
}
|
|
|
|
BOOL CGenWindow::OnEraseBkgnd(HWND hwnd, HDC hdc)
|
|
{
|
|
HBRUSH hErase = GetBackgroundBrush();
|
|
if (NULL == hErase)
|
|
{
|
|
return(FORWARD_WM_ERASEBKGND(hwnd, hdc, DefWindowProc));
|
|
}
|
|
|
|
HPALETTE hOldPal = NULL;
|
|
HPALETTE hPal = GetPalette();
|
|
if (NULL != hPal)
|
|
{
|
|
hOldPal = SelectPalette(hdc, hPal, TRUE);
|
|
RealizePalette(hdc);
|
|
}
|
|
|
|
RECT rc;
|
|
GetClientRect(hwnd, &rc);
|
|
|
|
HBRUSH hOld = (HBRUSH)SelectObject(hdc, hErase);
|
|
PatBlt(hdc, 0, 0, rc.right, rc.bottom, PATCOPY);
|
|
SelectObject(hdc, hOld);
|
|
|
|
if (NULL != hOldPal)
|
|
{
|
|
SelectPalette(hdc, hOldPal, TRUE);
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
void CGenWindow::OnMouseLeave()
|
|
{
|
|
if (dynamic_cast<IGenWindow*>(this) == g_pCurHot)
|
|
{
|
|
SetHotControl(NULL);
|
|
}
|
|
}
|
|
|
|
void CGenWindow::OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags)
|
|
{
|
|
SetHotControl(this);
|
|
FORWARD_WM_MOUSEMOVE(hwnd, x, y, keyFlags, DefWindowProc);
|
|
}
|
|
|
|
// REVIEW georgep: Should this loop until it gets an IGenWindow?
|
|
HBRUSH CGenWindow::GetBackgroundBrush()
|
|
{
|
|
HWND parent = GetParent(GetWindow());
|
|
if (NULL == parent)
|
|
{
|
|
return(GetStandardBrush());
|
|
}
|
|
|
|
IGenWindow *pParent = FromHandle(parent);
|
|
if (pParent == NULL)
|
|
{
|
|
return(GetStandardBrush());
|
|
}
|
|
return(pParent->GetBackgroundBrush());
|
|
}
|
|
|
|
// REVIEW georgep: Should this loop until it gets an IGenWindow?
|
|
HPALETTE CGenWindow::GetPalette()
|
|
{
|
|
HWND parent = GetParent(GetWindow());
|
|
if (NULL == parent)
|
|
{
|
|
return(GetStandardPalette());
|
|
}
|
|
|
|
IGenWindow *pParent = FromHandle(parent);
|
|
if (pParent == NULL)
|
|
{
|
|
return(GetStandardPalette());
|
|
}
|
|
return(pParent->GetPalette());
|
|
}
|
|
|
|
BOOL CGenWindow::OnNCCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
|
|
{
|
|
// Store away the "this" pointer ahnd save off the window handle
|
|
CGenWindow* pWnd = NULL;
|
|
|
|
pWnd = (CGenWindow*) lpCreateStruct->lpCreateParams;
|
|
ASSERT(pWnd);
|
|
|
|
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) pWnd);
|
|
pWnd->AddRef();
|
|
|
|
TRACE_OUT(("CGenWindow::OnNCCreate"));
|
|
|
|
ASSERT(NULL == pWnd->m_hwnd);
|
|
pWnd->m_hwnd = hwnd;
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
void CGenWindow::GetDesiredSize(SIZE *ppt)
|
|
{
|
|
HWND hwnd = GetWindow();
|
|
|
|
RECT rcTemp = { 0, 0, 0, 0 };
|
|
AdjustWindowRectEx(&rcTemp, GetWindowLong(hwnd, GWL_STYLE), FALSE,
|
|
GetWindowLong(hwnd, GWL_EXSTYLE));
|
|
|
|
ppt->cx = rcTemp.right - rcTemp.left;
|
|
ppt->cy = rcTemp.bottom - rcTemp.top;
|
|
}
|
|
|
|
void CGenWindow::OnDesiredSizeChanged()
|
|
{
|
|
HWND parent = GetParent(GetWindow());
|
|
if (NULL != parent)
|
|
{
|
|
IGenWindow *pParent = FromHandle(parent);
|
|
if (NULL != pParent)
|
|
{
|
|
pParent->OnDesiredSizeChanged();
|
|
}
|
|
}
|
|
|
|
// Do this after telling the parents about the change, so their layouts
|
|
// will happen before this one
|
|
ScheduleLayout();
|
|
}
|
|
|
|
class GWTrackMouseLeave
|
|
{
|
|
private:
|
|
enum { DefIdTimer = 100 };
|
|
enum { DefTimeout = 500 };
|
|
|
|
static HWND m_hwnd;
|
|
static UINT_PTR m_idTimer;
|
|
static DWORD m_dwWhere;
|
|
|
|
static void CALLBACK OnTimer(HWND hwnd, UINT uMsg, UINT_PTR idTimer, DWORD dwTime)
|
|
{
|
|
RECT rc;
|
|
GetWindowRect(m_hwnd, &rc);
|
|
|
|
DWORD dwPos = GetMessagePos();
|
|
|
|
// If the mouse has not moved since this timer started, then leave it hot
|
|
// This allows a reasonable keyboard-only interface
|
|
if (m_dwWhere == dwPos)
|
|
{
|
|
return;
|
|
}
|
|
|
|
POINT ptPos = { GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos) };
|
|
|
|
if (!PtInRect(&rc, ptPos))
|
|
{
|
|
PostMessage(m_hwnd, WM_MOUSELEAVE, 0, 0);
|
|
}
|
|
}
|
|
|
|
public:
|
|
GWTrackMouseLeave() {}
|
|
|
|
static void Track(HWND hwnd, BOOL bTrack)
|
|
{
|
|
if (!bTrack)
|
|
{
|
|
if (NULL != m_hwnd && hwnd == m_hwnd)
|
|
{
|
|
KillTimer(NULL, m_idTimer);
|
|
m_hwnd = NULL;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Stop any previous tracking
|
|
Track(m_hwnd, FALSE);
|
|
|
|
m_hwnd = hwnd;
|
|
m_dwWhere = GetMessagePos();
|
|
m_idTimer = SetTimer(NULL, DefIdTimer, DefTimeout, OnTimer);
|
|
}
|
|
} ;
|
|
|
|
HWND GWTrackMouseLeave::m_hwnd = NULL;
|
|
DWORD GWTrackMouseLeave::m_dwWhere = 0;
|
|
UINT_PTR GWTrackMouseLeave::m_idTimer;
|
|
|
|
static void GWTrackMouseEvent(HWND hwnd, BOOL bTrack)
|
|
{
|
|
// I need to set up a timer to handle this
|
|
GWTrackMouseLeave::Track(hwnd, bTrack);
|
|
}
|
|
|
|
// Set the global Hot control
|
|
void CGenWindow::SetHotControl(CGenWindow *pHot)
|
|
{
|
|
CGenWindow *pGenWindow = NULL;
|
|
|
|
if (NULL != pHot)
|
|
{
|
|
for (HWND hwndHot=pHot->GetWindow(); ; hwndHot=GetParent(hwndHot))
|
|
{
|
|
if (NULL == hwndHot)
|
|
{
|
|
break;
|
|
}
|
|
|
|
IGenWindow *pWindow = FromHandle(hwndHot);
|
|
if (NULL == pWindow)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (SUCCEEDED(pWindow->QueryInterface(__uuidof(CGenWindow),
|
|
reinterpret_cast<LPVOID*>(&pGenWindow)))
|
|
&& NULL != pGenWindow)
|
|
{
|
|
pGenWindow->SetHot(TRUE);
|
|
|
|
// Not all windows may care about the hot state
|
|
BOOL bIsHot = pGenWindow->IsHot();
|
|
pGenWindow->Release();
|
|
|
|
if (bIsHot)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
pGenWindow = NULL;
|
|
}
|
|
}
|
|
|
|
if (g_pCurHot != pGenWindow)
|
|
{
|
|
if (NULL != g_pCurHot)
|
|
{
|
|
g_pCurHot->SetHot(FALSE);
|
|
GWTrackMouseEvent(g_pCurHot->GetWindow(), FALSE);
|
|
|
|
ULONG uRef = g_pCurHot->Release();
|
|
}
|
|
|
|
g_pCurHot = pGenWindow;
|
|
if (NULL!= g_pCurHot)
|
|
{
|
|
ULONG uRef = g_pCurHot->AddRef();
|
|
|
|
// Now we need to track the mouse leaving
|
|
GWTrackMouseEvent(g_pCurHot->GetWindow(), TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set this control to be hot
|
|
void CGenWindow::SetHot(BOOL bHot)
|
|
{
|
|
}
|
|
|
|
// Is this control currently hot
|
|
BOOL CGenWindow::IsHot()
|
|
{
|
|
return(FALSE);
|
|
}
|
|
|
|
LPARAM CGenWindow::GetUserData()
|
|
{
|
|
return(m_lUserData);
|
|
}
|
|
|
|
HPALETTE CGenWindow::g_hPal = NULL;
|
|
BOOL CGenWindow::g_bNeedPalette = TRUE;
|
|
HBRUSH CGenWindow::g_hBrush = NULL;
|
|
CTopWindowArray *CGenWindow::g_pTopArray = NULL;
|
|
|
|
// Not particularly robust: we give out our internal palette and trust everybody
|
|
// not to delete it
|
|
HPALETTE CGenWindow::GetStandardPalette()
|
|
{
|
|
#include "indeopal.h"
|
|
|
|
if (!g_bNeedPalette || NULL != g_hPal)
|
|
{
|
|
return(g_hPal);
|
|
}
|
|
|
|
HDC hDC = ::GetDC(NULL);
|
|
if (NULL != hDC)
|
|
{
|
|
// Use the Indeo palette
|
|
// Check out the video mode. We only care about 8 bit mode.
|
|
if (8 == ::GetDeviceCaps(hDC, BITSPIXEL) * ::GetDeviceCaps(hDC, PLANES))
|
|
{
|
|
#ifndef HALFTONE_PALETTE
|
|
LOGPALETTE_NM gIndeoPalette = gcLogPaletteIndeo;
|
|
if (SYSPAL_NOSTATIC != ::GetSystemPaletteUse(hDC))
|
|
{
|
|
// Preserve the static colors
|
|
int nStaticColors = ::GetDeviceCaps(hDC, NUMCOLORS) >> 1;
|
|
|
|
if (nStaticColors <= 128)
|
|
{
|
|
// Get the 10 first entries
|
|
::GetSystemPaletteEntries( hDC,
|
|
0,
|
|
nStaticColors,
|
|
&gIndeoPalette.aEntries[0]);
|
|
|
|
// Get the 10 last entries
|
|
::GetSystemPaletteEntries( hDC,
|
|
256 - nStaticColors,
|
|
nStaticColors,
|
|
&gIndeoPalette.aEntries[256 - nStaticColors]);
|
|
|
|
// Hammer the peFlags
|
|
for (; --nStaticColors + 1;)
|
|
{
|
|
gIndeoPalette.aEntries[nStaticColors].peFlags = 0;
|
|
gIndeoPalette.aEntries[255 - nStaticColors].peFlags = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build a palette
|
|
g_hPal = ::CreatePalette((LOGPALETTE *)&gIndeoPalette);
|
|
|
|
#else // HALFTONE_PALETTE
|
|
g_hPal = ::CreateHalftonePalette(hDC);
|
|
#endif // HALFTONE_PALETTE
|
|
}
|
|
::ReleaseDC(NULL, hDC);
|
|
}
|
|
|
|
g_bNeedPalette = (NULL != g_hPal);
|
|
return(g_hPal);
|
|
}
|
|
|
|
void CGenWindow::DeleteStandardPalette()
|
|
{
|
|
if (NULL != g_hPal)
|
|
{
|
|
DeleteObject(g_hPal);
|
|
g_hPal = NULL;
|
|
}
|
|
}
|
|
|
|
// Get the standard palette for drawing
|
|
HBRUSH CGenWindow::GetStandardBrush()
|
|
{
|
|
return(GetSysColorBrush(COLOR_3DFACE));
|
|
}
|
|
|
|
// Delete the standard palette for drawing
|
|
void CGenWindow::DeleteStandardBrush()
|
|
{
|
|
}
|
|
|
|
// Returns TRUE if the TT exists
|
|
BOOL CGenWindow::InitToolInfo(TOOLINFO *pti, LPTSTR pszText)
|
|
{
|
|
TCHAR szText[MAX_PATH];
|
|
if (NULL == pszText)
|
|
{
|
|
pszText = szText;
|
|
}
|
|
|
|
HWND hwnd = GetWindow();
|
|
HWND hwndTooltip = NULL == g_pTopArray ? NULL : g_pTopArray->Find(hwnd);
|
|
|
|
TOOLINFO &ti = *pti;
|
|
|
|
ti.cbSize = sizeof(TOOLINFO);
|
|
ti.hwnd = hwnd;
|
|
ti.hinst = GetWindowInstance(hwnd);
|
|
ti.lpszText = pszText;
|
|
|
|
GetClientRect(hwnd, &ti.rect);
|
|
|
|
ti.uId = reinterpret_cast<UINT_PTR>(hwnd);
|
|
ti.uFlags = TTF_SUBCLASS;
|
|
|
|
GetSharedTooltipInfo(&ti);
|
|
|
|
// HACKHACK georgep: The flags keep getting messed up by the tooltip window
|
|
UINT uFlags = ti.uFlags;
|
|
|
|
BOOL bExist = NULL == hwndTooltip ? FALSE : TT_GetToolInfo(hwndTooltip, &ti);
|
|
|
|
ti.uFlags = uFlags;
|
|
if (ti.lpszText == szText)
|
|
{
|
|
ti.lpszText = NULL;
|
|
}
|
|
|
|
return(bExist);
|
|
}
|
|
|
|
void CGenWindow::SetWindowtext(LPCTSTR pszTip)
|
|
{
|
|
HWND hwnd = GetWindow();
|
|
if(NULL != hwnd)
|
|
{
|
|
HWND child = (GetTopWindow(hwnd));
|
|
if (NULL != child)
|
|
{
|
|
::SetWindowText(child,pszTip);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the tooltip for this window
|
|
void CGenWindow::SetTooltip(LPCTSTR pszTip)
|
|
{
|
|
HWND hwnd = GetWindow();
|
|
|
|
if (NULL == g_pTopArray)
|
|
{
|
|
g_pTopArray = new CTopWindowArray;
|
|
if (NULL == g_pTopArray)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
HWND hwndTop = CTopWindowArray::GetTopFrame(hwnd);
|
|
HWND hwndTooltip = g_pTopArray->Find(hwndTop);
|
|
|
|
if (NULL == hwndTooltip)
|
|
{
|
|
hwndTooltip = CreateWindowEx(0,
|
|
TOOLTIPS_CLASS,
|
|
NULL,
|
|
0, // styles
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
hwndTop,
|
|
(HMENU) NULL,
|
|
GetWindowInstance(hwnd),
|
|
NULL);
|
|
if (NULL == hwndTooltip)
|
|
{
|
|
// Couldn't create the tooltip window
|
|
return;
|
|
}
|
|
|
|
g_pTopArray->Add(hwndTop, hwndTooltip);
|
|
}
|
|
|
|
TOOLINFO ti;
|
|
BOOL bExist = InitToolInfo(&ti);
|
|
|
|
ti.lpszText = const_cast<LPTSTR>(pszTip);
|
|
|
|
if (bExist)
|
|
{
|
|
TT_SetToolInfo(hwndTooltip, &ti);
|
|
}
|
|
else
|
|
{
|
|
TT_AddToolInfo(hwndTooltip, &ti);
|
|
}
|
|
}
|
|
|
|
// Remove the tooltip for this window
|
|
void CGenWindow::RemoveTooltip()
|
|
{
|
|
if (NULL == g_pTopArray)
|
|
{
|
|
// Nothing to do
|
|
return;
|
|
}
|
|
|
|
HWND hwndTop = CTopWindowArray::GetTopFrame(GetWindow());
|
|
HWND hwndTooltip = g_pTopArray->Find(hwndTop);
|
|
|
|
BOOL bIsWindow = NULL != hwndTooltip && IsWindow(hwndTooltip);
|
|
|
|
TOOLINFO ti;
|
|
BOOL bExist = bIsWindow && InitToolInfo(&ti);
|
|
|
|
if (bExist)
|
|
{
|
|
TT_DelToolInfo(hwndTooltip, &ti);
|
|
}
|
|
|
|
if (NULL != hwndTooltip && (!bIsWindow || 0 == TT_GetToolCount(hwndTooltip)))
|
|
{
|
|
if (bIsWindow)
|
|
{
|
|
DestroyWindow(hwndTooltip);
|
|
}
|
|
g_pTopArray->Remove(hwndTop);
|
|
|
|
if (0 == g_pTopArray->GetCount())
|
|
{
|
|
delete g_pTopArray;
|
|
g_pTopArray = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get the info necessary for displaying a tooltip
|
|
void CGenWindow::GetSharedTooltipInfo(TOOLINFO *pti)
|
|
{
|
|
}
|
|
|
|
// Just makes the first child fill the client area
|
|
void CFillWindow::Layout()
|
|
{
|
|
HWND child = GetChild();
|
|
if (NULL != child)
|
|
{
|
|
RECT rc;
|
|
GetClientRect(GetWindow(), &rc);
|
|
SetWindowPos(child, NULL, 0, 0, rc.right, rc.bottom, SWP_NOZORDER);
|
|
}
|
|
}
|
|
|
|
void CFillWindow::GetDesiredSize(SIZE *psize)
|
|
{
|
|
CGenWindow::GetDesiredSize(psize);
|
|
HWND child = GetChild();
|
|
|
|
if (NULL != child)
|
|
{
|
|
IGenWindow *pChild = FromHandle(child);
|
|
if (NULL != pChild)
|
|
{
|
|
SIZE sizeTemp;
|
|
pChild->GetDesiredSize(&sizeTemp);
|
|
psize->cx += sizeTemp.cx;
|
|
psize->cy += sizeTemp.cy;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get the info necessary for displaying a tooltip
|
|
void CFillWindow::GetSharedTooltipInfo(TOOLINFO *pti)
|
|
{
|
|
CGenWindow::GetSharedTooltipInfo(pti);
|
|
|
|
// Since the child covers this whole area, we need to change the HWND to
|
|
// hook
|
|
pti->hwnd = GetChild();
|
|
}
|
|
|
|
CEdgedWindow::CEdgedWindow() :
|
|
m_hMargin(0),
|
|
m_vMargin(0),
|
|
m_pHeader(NULL)
|
|
{
|
|
}
|
|
|
|
CEdgedWindow::~CEdgedWindow()
|
|
{
|
|
SetHeader(NULL);
|
|
}
|
|
|
|
BOOL CEdgedWindow::Create(HWND hwndParent)
|
|
{
|
|
return(CGenWindow::Create(
|
|
hwndParent, // Window parent
|
|
0, // ID of the child window
|
|
TEXT("NMEdgedWindow"), // Window name
|
|
WS_CLIPCHILDREN, // Window style; WS_CHILD|WS_VISIBLE will be added to this
|
|
WS_EX_CONTROLPARENT // Extended window style
|
|
));
|
|
}
|
|
|
|
HWND CEdgedWindow::GetContentWindow()
|
|
{
|
|
// If we are hosting an IGenWindow, add on its desired size
|
|
HWND child = GetFirstChild(GetWindow());
|
|
if (NULL == child)
|
|
{
|
|
return(NULL);
|
|
}
|
|
if (NULL != m_pHeader && child == m_pHeader->GetWindow())
|
|
{
|
|
child = ::GetWindow(child, GW_HWNDNEXT);
|
|
}
|
|
|
|
return(child);
|
|
}
|
|
|
|
static const int LeftIndent = 20;
|
|
|
|
// Just makes the first child fill the client area - the border
|
|
void CEdgedWindow::Layout()
|
|
{
|
|
int nBorder = GetBorderWidth();
|
|
|
|
int hBorder = m_hMargin + nBorder;
|
|
int vBorder = m_vMargin + nBorder;
|
|
|
|
HWND hwnd = GetWindow();
|
|
RECT rc;
|
|
GetClientRect(hwnd, &rc);
|
|
|
|
CGenWindow *pHeader = GetHeader();
|
|
if (NULL != pHeader)
|
|
{
|
|
SIZE sizeTemp;
|
|
pHeader->GetDesiredSize(&sizeTemp);
|
|
|
|
SetWindowPos(pHeader->GetWindow(), NULL, rc.left+LeftIndent, rc.top,
|
|
sizeTemp.cx, sizeTemp.cy, SWP_NOZORDER|SWP_NOACTIVATE);
|
|
|
|
rc.top += sizeTemp.cy;
|
|
}
|
|
|
|
HWND child = GetContentWindow();
|
|
if (NULL != child)
|
|
{
|
|
SetWindowPos(child, NULL, rc.left+hBorder, rc.top+vBorder,
|
|
rc.right-rc.left-2*hBorder, rc.bottom-rc.top-2*vBorder, SWP_NOZORDER|SWP_NOACTIVATE);
|
|
}
|
|
}
|
|
|
|
void CEdgedWindow::GetDesiredSize(SIZE *psize)
|
|
{
|
|
int nBorder = GetBorderWidth();
|
|
|
|
int hBorder = m_hMargin + nBorder;
|
|
int vBorder = m_vMargin + nBorder;
|
|
|
|
CGenWindow::GetDesiredSize(psize);
|
|
psize->cx += 2*hBorder;
|
|
psize->cy += 2*vBorder;
|
|
|
|
// If we are hosting an IGenWindow, add on its desired size
|
|
HWND child = GetContentWindow();
|
|
if (NULL == child)
|
|
{
|
|
return;
|
|
}
|
|
IGenWindow *pChild = FromHandle(child);
|
|
if (NULL == pChild)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SIZE size;
|
|
pChild->GetDesiredSize(&size);
|
|
psize->cx += size.cx;
|
|
psize->cy += size.cy;
|
|
|
|
CGenWindow *pHeader = GetHeader();
|
|
if (NULL != pHeader)
|
|
{
|
|
SIZE sizeTemp;
|
|
pHeader->GetDesiredSize(&sizeTemp);
|
|
psize->cy += sizeTemp.cy;
|
|
psize->cx = max(psize->cx, sizeTemp.cx+LeftIndent+hBorder);
|
|
}
|
|
}
|
|
|
|
void CEdgedWindow::OnPaint(HWND hwnd)
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(hwnd, &ps);
|
|
|
|
RECT rc;
|
|
GetClientRect(hwnd, &rc);
|
|
|
|
CGenWindow *pHeader = GetHeader();
|
|
if (NULL != pHeader)
|
|
{
|
|
SIZE sizeTemp;
|
|
pHeader->GetDesiredSize(&sizeTemp);
|
|
|
|
// Make the etch go through the middle of the header
|
|
rc.top += (sizeTemp.cy-GetBorderWidth()) / 2;
|
|
}
|
|
|
|
DrawEdge(hdc, &rc, EDGE_ETCHED, BF_RECT);
|
|
|
|
EndPaint(hwnd, &ps);
|
|
}
|
|
|
|
LRESULT CEdgedWindow::ProcessMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (message)
|
|
{
|
|
HANDLE_MSG(hwnd, WM_PAINT, OnPaint);
|
|
|
|
case WM_DESTROY:
|
|
SetHeader(NULL);
|
|
break;
|
|
|
|
case WM_SIZE:
|
|
// Need to invalidate if we bacame larger to redraw the border in the
|
|
// right place
|
|
InvalidateRect(hwnd, NULL, TRUE);
|
|
break;
|
|
}
|
|
|
|
return(CGenWindow::ProcessMessage(hwnd, message, wParam, lParam));
|
|
}
|
|
|
|
void CEdgedWindow::SetHeader(CGenWindow *pHeader)
|
|
{
|
|
if (NULL != m_pHeader)
|
|
{
|
|
m_pHeader->Release();
|
|
}
|
|
|
|
m_pHeader = pHeader;
|
|
if (NULL != m_pHeader)
|
|
{
|
|
m_pHeader->AddRef();
|
|
}
|
|
}
|
|
|
|
BOOL CFrame::Create(
|
|
HWND hWndOwner, // Window owner
|
|
LPCTSTR szWindowName, // Window name
|
|
DWORD dwStyle, // Window style
|
|
DWORD dwEXStyle, // Extended window style
|
|
int x, // Window pos: x
|
|
int y, // Window pos: y
|
|
int nWidth, // Window size: width
|
|
int nHeight, // Window size: height
|
|
HINSTANCE hInst, // The hInstance to create the window on
|
|
HICON hIcon, // The icon for the window
|
|
HMENU hmMain, // Window menu
|
|
LPCTSTR szClassName // The class name to use
|
|
)
|
|
{
|
|
if (!CFillWindow::Create(hWndOwner, szWindowName, dwStyle, dwEXStyle,
|
|
x, y, nWidth, nHeight, hInst, hmMain, szClassName))
|
|
{
|
|
return(FALSE);
|
|
}
|
|
|
|
if (NULL != hIcon)
|
|
{
|
|
SendMessage(GetWindow(), WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon));
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
void CFrame::Resize()
|
|
{
|
|
Resize(this, 0);
|
|
}
|
|
|
|
void CFrame::Resize(CGenWindow *pThis, WPARAM wParam)
|
|
{
|
|
SIZE size;
|
|
pThis->GetDesiredSize(&size);
|
|
SetWindowPos(pThis->GetWindow(), NULL, 0, 0, size.cx, size.cy,
|
|
SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
|
|
}
|
|
|
|
void CFrame::OnDesiredSizeChanged()
|
|
{
|
|
// I should probably look at the window style and only do this if it is
|
|
// not resizable. But then that would be wrong sometimes too, so just
|
|
// override this if you want different behavior.
|
|
AsyncInvoke(Resize, 0);
|
|
}
|
|
|
|
LRESULT CFrame::ProcessMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
HANDLE_MSG(hwnd, WM_PALETTECHANGED , OnPaletteChanged);
|
|
HANDLE_MSG(hwnd, WM_QUERYNEWPALETTE, OnQueryNewPalette);
|
|
}
|
|
|
|
return(CFillWindow::ProcessMessage(hwnd, uMsg, wParam, lParam));
|
|
}
|
|
|
|
void CFrame::OnPaletteChanged(HWND hwnd, HWND hwndPaletteChange)
|
|
{
|
|
SelAndRealizePalette(TRUE);
|
|
::RedrawWindow(GetWindow(), NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN);
|
|
}
|
|
|
|
BOOL CFrame::SelAndRealizePalette(BOOL bBackground)
|
|
{
|
|
BOOL bRet = FALSE;
|
|
|
|
HPALETTE hPal = GetPalette();
|
|
if (NULL == hPal)
|
|
{
|
|
return(bRet);
|
|
}
|
|
|
|
HWND hwnd = GetWindow();
|
|
|
|
HDC hdc = ::GetDC(hwnd);
|
|
if (NULL != hdc)
|
|
{
|
|
::SelectPalette(hdc, hPal, bBackground);
|
|
bRet = (GDI_ERROR != ::RealizePalette(hdc));
|
|
|
|
::ReleaseDC(hwnd, hdc);
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
BOOL CFrame::OnQueryNewPalette(HWND hwnd)
|
|
{
|
|
return(SelAndRealizePalette(FALSE));
|
|
}
|
|
|
|
BOOL CFrame::SetForeground()
|
|
{
|
|
BOOL bRet = FALSE;
|
|
|
|
HWND hwnd = GetWindow();
|
|
|
|
if (NULL != hwnd)
|
|
{
|
|
WINDOWPLACEMENT wp;
|
|
wp.length = sizeof(wp);
|
|
|
|
if (::GetWindowPlacement(hwnd, &wp) &&
|
|
((SW_MINIMIZE == wp.showCmd) || (SW_SHOWMINIMIZED == wp.showCmd)))
|
|
{
|
|
// The window is minimized - restore it:
|
|
::ShowWindow(hwnd, SW_RESTORE);
|
|
}
|
|
else
|
|
{
|
|
::ShowWindow(hwnd, SW_SHOW);
|
|
}
|
|
|
|
// Bring it to the foreground
|
|
SetForegroundWindow(hwnd);
|
|
bRet = TRUE;
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
void CFrame::MoveEnsureVisible(int x, int y)
|
|
{
|
|
static const int MinVis = 16;
|
|
|
|
RECT rcThis;
|
|
GetWindowRect(GetWindow(), &rcThis);
|
|
// Change to width and height
|
|
rcThis.right -= rcThis.left;
|
|
rcThis.bottom -= rcThis.top;
|
|
|
|
RECT rcDesktop;
|
|
SystemParametersInfo(SPI_GETWORKAREA, 0, &rcDesktop, 0);
|
|
|
|
if ((x+rcThis.right < rcDesktop.left+MinVis) || (x > rcDesktop.right-MinVis))
|
|
{
|
|
x = (rcDesktop.left + rcDesktop.right - rcThis.right) / 2;
|
|
}
|
|
|
|
if ((y+rcThis.bottom < rcDesktop.top+MinVis) || (y > rcDesktop.bottom-MinVis))
|
|
{
|
|
y = (rcDesktop.top + rcDesktop.bottom - rcThis.bottom) / 2;
|
|
}
|
|
|
|
SetWindowPos(GetWindow(), NULL, x, y, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
|
|
}
|
|
|
|
BOOL IsWindowActive(HWND hwnd)
|
|
{
|
|
HWND hwndFocus = GetFocus();
|
|
|
|
while (NULL != hwndFocus)
|
|
{
|
|
if (hwndFocus == hwnd)
|
|
{
|
|
return(TRUE);
|
|
}
|
|
|
|
HWND hwndParent = GetParent(hwndFocus);
|
|
if (NULL == hwndParent)
|
|
{
|
|
hwndFocus = GetWindow(hwndFocus, GW_OWNER);
|
|
}
|
|
else
|
|
{
|
|
hwndFocus = hwndParent;
|
|
}
|
|
}
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
static BOOL ShouldTry(HWND child)
|
|
{
|
|
return((WS_VISIBLE) == (GetWindowStyle(child) & (WS_DISABLED|WS_VISIBLE)));
|
|
}
|
|
|
|
static BOOL IsTabbable(HWND child)
|
|
{
|
|
return((WS_TABSTOP|WS_VISIBLE) == (GetWindowStyle(child) & (WS_TABSTOP|WS_DISABLED|WS_VISIBLE)));
|
|
}
|
|
|
|
|
|
HWND NextControl(HWND hwndTop, HWND hwndFocus)
|
|
{
|
|
// Loop detection stuff
|
|
BOOL bGotToTop = FALSE;
|
|
|
|
// We'll loop to avoid really deep recursion
|
|
while (TRUE)
|
|
{
|
|
// First try the children of hwndFocus
|
|
if (hwndFocus == hwndTop || ShouldTry(hwndFocus))
|
|
{
|
|
HWND next = GetFirstChild(hwndFocus);
|
|
if (NULL != next)
|
|
{
|
|
if (IsTabbable(next))
|
|
{
|
|
return(next);
|
|
}
|
|
|
|
hwndFocus = next;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (hwndFocus == hwndTop)
|
|
{
|
|
// Apparently hwndTop has no children
|
|
return(NULL);
|
|
}
|
|
|
|
HWND next;
|
|
while (NULL == (next = GetNextSibling(hwndFocus)))
|
|
{
|
|
hwndFocus = GetParent(hwndFocus);
|
|
if (NULL == hwndFocus)
|
|
{
|
|
// Invalid params
|
|
return(NULL);
|
|
}
|
|
|
|
if (hwndTop == hwndFocus)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hwndTop == hwndFocus)
|
|
{
|
|
// Detect if we have looped back to the top again
|
|
if (bGotToTop)
|
|
{
|
|
return(NULL);
|
|
}
|
|
|
|
bGotToTop = TRUE;
|
|
continue;
|
|
}
|
|
|
|
if (IsTabbable(next))
|
|
{
|
|
return(next);
|
|
}
|
|
|
|
hwndFocus = next;
|
|
}
|
|
|
|
// We looped back to the beginning, so I guess nobody can take the focus
|
|
return(NULL);
|
|
}
|
|
|
|
// Determine the previous control in the tab order
|
|
HWND PrevControl(HWND hwndTop, HWND hwndFocus)
|
|
{
|
|
// In case hwndFocus is not focusable for some reason, we still need to
|
|
// detect the loop
|
|
HWND hwndStart = NextControl(hwndTop, hwndFocus);
|
|
|
|
// HACK for combo boxes: go from the edit control to the combo box control
|
|
while (NULL != hwndFocus
|
|
&& hwndTop != hwndFocus
|
|
&& !IsTabbable(hwndFocus)
|
|
)
|
|
{
|
|
hwndFocus = GetParent(hwndFocus);
|
|
}
|
|
|
|
HWND ret = hwndStart;
|
|
while (TRUE)
|
|
{
|
|
HWND next = NextControl(hwndTop, ret);
|
|
if (NULL == next)
|
|
{
|
|
// Oops!
|
|
return(NULL);
|
|
}
|
|
|
|
if (hwndFocus == next
|
|
|| hwndStart == next
|
|
)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ret = next;
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
|
|
void ShiftFocus(HWND hwndTop, BOOL bForward)
|
|
{
|
|
HWND hwndFocus = GetFocus();
|
|
|
|
if (!IsWindowActive(hwndTop))
|
|
{
|
|
hwndFocus = hwndTop;
|
|
}
|
|
|
|
HWND next = bForward ? NextControl(hwndTop, hwndFocus) : PrevControl(hwndTop, hwndFocus);
|
|
if (NULL != next)
|
|
{
|
|
SetFocus(next);
|
|
}
|
|
else
|
|
{
|
|
MessageBeep(MB_ICONHAND);
|
|
}
|
|
}
|
|
|
|
|