// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and Microsoft
// QuickHelp and/or WinHelp documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

#include "stdafx.h"

#ifdef AFX_CORE5_SEG
#pragma code_seg(AFX_CORE5_SEG)
#endif

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

/////////////////////////////////////////////////////////////////////////////
// Property sheet global data

static HBITMAP NEAR hbmScroll;
static CSize NEAR sizeScroll;

// amount to inflate the selected tab
static const CSize NEAR sizeSelTab(2, 2);

// extra width & height for a tab past text width
static const CSize NEAR sizeTabTextMargin(10, 3);

void AFXAPI AfxDeleteObject(HGDIOBJ* pObject)
{
	ASSERT(pObject != NULL);
	if (*pObject != NULL)
	{
		DeleteObject(*pObject);
		*pObject = NULL;
	}
}

struct _AFX_DLGPROP_TERM
{
	~_AFX_DLGPROP_TERM()
	{
		AfxDeleteObject((HGDIOBJ*)&hbmScroll);
	}
};

static const _AFX_DLGPROP_TERM NEAR dlgpropTerm;

static UINT NEAR standardButtons[4] = { IDOK, IDCANCEL, ID_APPLY_NOW, ID_HELP };

/////////////////////////////////////////////////////////////////////////////
// implementation helpers

static void AFXAPI SetCtrlFocus(HWND hWnd)
{
	if (::SendMessage(hWnd, WM_GETDLGCODE, 0, 0L) & DLGC_HASSETSEL)
		::SendMessage(hWnd, EM_SETSEL, 0, -1);
	::SetFocus(hWnd);
}

static void AFXAPI EnableDlgItem(HWND hWnd, UINT nID, BOOL bEnable,
	int nIDFocus = -1)
{
	HWND hWndItem = ::GetDlgItem(hWnd, nID);
	if (::GetFocus() == hWndItem && nIDFocus != -1)
	{
		HWND hWndTemp = ::GetDlgItem(hWnd, nIDFocus);
		if (hWndTemp != NULL)
			::SetFocus(hWndTemp);
	}
	if (hWndItem != NULL)
		::EnableWindow(hWndItem, bEnable);
}

static BOOL IsCharAfterAmpersand(LPTSTR lpsz, TCHAR chFind)
{
	ASSERT(AfxIsValidString(lpsz));

	AnsiLowerBuff(&chFind, 1);
	while (*lpsz != '\0')
	{
		if (*lpsz == '&')
		{
			++lpsz; // Note: '&' is not lead-byte
			if (*lpsz != '&')
			{
				TCHAR ch = *lpsz;
				AnsiLowerBuff(&ch, 1);
				return ch == chFind;
			}
		}
		lpsz = AnsiNext(lpsz);
	}
	return FALSE;
}

static HWND GetFirstLevelChild(HWND hWndDlg, HWND hWndLevel)
{
	if ((hWndLevel == hWndDlg) ||
		!(::GetWindowLong(hWndLevel,GWL_STYLE) & WS_CHILD))
	{
		return NULL;
	}

	HWND hWnd = hWndLevel;
	do
	{
		if (hWndLevel == hWndDlg)
			break;
		if (!(::GetWindowLong(hWndLevel,GWL_STYLE) & WS_CHILD))
			break;
		hWnd = hWndLevel;
	} while ((hWndLevel = ::GetParent(hWndLevel)) != NULL);

	return hWnd;
}

static HWND FindNextControl(HWND hWndDlg, HWND hWnd, TCHAR ch)
{
	ASSERT(hWndDlg != NULL);
	TCHAR szText[256];
	HWND hWndStart;
	HWND hWndFirst;
	DWORD dwDlgCode;

	// Check if we are in a group box so we can find local mnemonics.
	hWndStart = GetFirstLevelChild(hWndDlg, hWnd);
	hWndFirst = ::GetNextDlgGroupItem(hWndDlg, hWndStart, FALSE);
	hWndFirst = ::GetNextDlgGroupItem(hWndDlg, hWndFirst, TRUE);
	while ((hWndStart = ::GetNextDlgGroupItem(hWndDlg, hWndStart, FALSE)) != NULL)
	{
		if (hWndStart == hWnd || hWndStart == hWndFirst)
			break;

		// Only check for matching mnemonic if control doesn't want characters
		// and control isn't a static control with SS_NOPREFIX
		dwDlgCode = (DWORD) ::SendMessage(hWndStart, WM_GETDLGCODE, 0, 0L);
		if (!(dwDlgCode & DLGC_WANTCHARS) && (!(dwDlgCode & DLGC_STATIC) ||
			!(::GetWindowLong(hWndStart,GWL_STYLE)&& SS_NOPREFIX)))
		{
			::GetWindowText(hWndStart, szText, sizeof(szText));
			if (IsCharAfterAmpersand(szText, ch))
				return hWndStart;
		}
	}

	hWnd = hWndStart = GetFirstLevelChild(hWndDlg, hWnd);
	for (;;)
	{
		hWnd = ::GetWindow(hWnd,GW_HWNDNEXT);
		if (hWnd == NULL)
			hWnd = ::GetWindow(hWndDlg, GW_CHILD);

		// Only check for matching mnemonic if control doesn't want characters
		// and control isn't a static control with SS_NOPREFIX
		dwDlgCode = (DWORD) ::SendMessage(hWnd, WM_GETDLGCODE, 0, 0L);
		if (!(dwDlgCode & DLGC_WANTCHARS) && (!(dwDlgCode & DLGC_STATIC) ||
			!(::GetWindowLong(hWnd,GWL_STYLE) & SS_NOPREFIX)))
		{
			::GetWindowText(hWnd, szText, sizeof(szText));
			if (IsCharAfterAmpersand(szText, ch))
				break;
		}

		if (hWnd == hWndStart)
			return NULL;
	}

	return hWnd;
}

static BOOL PASCAL WalkPreTranslateTree(HWND hWndStop, MSG* pMsg)
{
	ASSERT(hWndStop == NULL || ::IsWindow(hWndStop));
	ASSERT(pMsg != NULL);

	// walk from the target window up to the hWndStop window checking
	//  if any window wants to translate this message

	for (HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd))
	{
		CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
		if (pWnd != NULL)
		{
			// target window is a C++ window
			if (pWnd->PreTranslateMessage(pMsg))
				return TRUE; // trapped by target window (eg: accelerators)
		}

		// got to hWndStop window without interest
		if (hWnd == hWndStop)
			break;
	}
	return FALSE;       // no special processing
}

////////////////////////////////////////////////////////////////////////////
// CTabItem  is used only in CTabControl

class CTabItem : public CObject
{
public:
	CTabItem(LPCTSTR szCaption, int nWidth);
	void Draw(CDC* pDC, HFONT hFont, BOOL bCurTab);

	CString m_strCaption;
	CRect   m_rect;
	CRect   m_rectPrev;
	int     m_nWidth;
};

// CTabItem represents one graphical tab
CTabItem::CTabItem(LPCTSTR szCaption, int nWidth)
{
	ASSERT(AfxIsValidString(szCaption));
	m_strCaption = szCaption;
	m_nWidth = nWidth;
	m_rect.SetRectEmpty();
	m_rectPrev.SetRectEmpty();
}

void CTabItem::Draw(CDC* pDC, HFONT hFont, BOOL bCurTab)
{
	CRect rectItem = m_rect;
	BOOL bClipped = (rectItem.Width() < m_nWidth);

	if (bCurTab)
		rectItem.InflateRect(sizeSelTab.cx, sizeSelTab.cy);

	HPEN hOldPen = (HPEN)pDC->SelectObject(afxData.hpenBtnHilite);

	pDC->MoveTo(rectItem.left, rectItem.bottom - 1);
	pDC->LineTo(rectItem.left, rectItem.top + 2);
	pDC->LineTo(rectItem.left + 2, rectItem.top);
	pDC->LineTo(rectItem.right - 1, rectItem.top);

	pDC->SelectObject(afxData.hpenBtnShadow);
	if (!bClipped)
	{
		// Draw dark gray line down right side
		pDC->LineTo(rectItem.right - 1, rectItem.bottom);

		// Draw black line down right side
		pDC->SelectObject(afxData.hpenBtnText);
		pDC->MoveTo(rectItem.right, rectItem.top + 2);
		pDC->LineTo(rectItem.right, rectItem.bottom);
	}
	else
	{
		// draw dark gray "torn" edge for a clipped tab
		for (int i = rectItem.top ; i < rectItem.bottom ; i += 3)
		{
			// This nifty (but obscure-looking) equation will draw
			// a jagged-edged line.
			int j = ((6 - (i - rectItem.top) % 12) / 3) % 2;
			pDC->MoveTo(rectItem.right + j, i);
			pDC->LineTo(rectItem.right + j, min(i + 3, rectItem.bottom));
		}
	}

	// finally, draw the tab's text
	HFONT hOldFont = NULL;
	if (hFont != NULL)
		hOldFont = (HFONT)::SelectObject(pDC->m_hDC, hFont);

	pDC->SelectObject(afxData.hpenBtnText);

	CSize text = pDC->GetTextExtent(m_strCaption, m_strCaption.GetLength());

	pDC->ExtTextOut(m_rect.left + (bClipped ? m_nWidth : m_rect.Width())/2 -
		text.cx/2, rectItem.top + rectItem.Height()/2 - text.cy/2,
		ETO_CLIPPED, &rectItem, m_strCaption, m_strCaption.GetLength(), NULL);

	if (hOldPen != NULL)
		pDC->SelectObject(hOldPen);
	if (hOldFont != NULL)
		pDC->SelectObject(hOldFont);
}

////////////////////////////////////////////////////////////////////////////
// CPropertyPage -- one page of a tabbed dialog

BEGIN_MESSAGE_MAP(CPropertyPage, CDialog)
	//{{AFX_MSG_MAP(CPropertyPage)
	ON_WM_CTLCOLOR()
	ON_WM_NCCREATE()
	ON_WM_CREATE()
	ON_WM_CLOSE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

CPropertyPage::CPropertyPage(UINT nIDTemplate, UINT nIDCaption)
{
	ASSERT(nIDTemplate != NULL);
	CommonConstruct(MAKEINTRESOURCE(nIDTemplate), nIDCaption);
}

CPropertyPage::CPropertyPage(LPCTSTR lpszTemplateName, UINT nIDCaption)
{
	ASSERT(AfxIsValidString(lpszTemplateName));
	CommonConstruct(lpszTemplateName, nIDCaption);
}

void CPropertyPage::CommonConstruct(LPCTSTR lpszTemplateName, UINT nIDCaption)
{
	m_lpDialogTemplate = lpszTemplateName;
	if (nIDCaption != 0)
		VERIFY(m_strCaption.LoadString(nIDCaption));
	else
		LoadCaption();

	m_bChanged = FALSE;
}

CPropertyPage::~CPropertyPage()
{
}

void CPropertyPage::SetModified(BOOL bChanged)
{
	m_bChanged = bChanged;
	CPropertySheet* pSheet = (CPropertySheet*)m_pParentWnd;
	ASSERT(pSheet != NULL);
	ASSERT(pSheet->IsKindOf(RUNTIME_CLASS(CPropertySheet)));
	pSheet->PageChanged();
}

void CPropertyPage::OnOK()
{
	ASSERT_VALID(this);
	m_bChanged = FALSE;

	Default();  // do not call CDialog::OnOK as it will call EndDialog
}

void CPropertyPage::OnCancel()
{
	ASSERT_VALID(this);
	m_bChanged = FALSE;

	Default();  // do not call CDialog::OnOK as it will call EndDialog
}

BOOL CPropertyPage::OnKillActive()
{
	ASSERT_VALID(this);

	// override this to perform validation;
	//  return FALSE and this page will remain active...
	if (!UpdateData(TRUE))
	{
		TRACE0("UpdateData failed during page deactivation\n");
		// UpdateData will set focus to correct item
		return FALSE;
	}
	return TRUE;
}

void CPropertyPage::CancelToClose()
{
	ASSERT_VALID(this);
	CPropertySheet* pSheet = (CPropertySheet*)m_pParentWnd;
	ASSERT(pSheet != NULL);
	ASSERT(pSheet->IsKindOf(RUNTIME_CLASS(CPropertySheet)));
	pSheet->CancelToClose();
}

BOOL CPropertyPage::ProcessTab(MSG* /*pMsg*/)
{
	// Handle tabbing back into the property sheet when tabbing away from
	// either end of the dialog's tab order
	if (GetKeyState(VK_CONTROL) < 0)
		return FALSE;

	BOOL bShift = GetKeyState(VK_SHIFT) < 0;
	if ((::SendMessage(::GetFocus(), WM_GETDLGCODE, 0, 0) &
		(DLGC_WANTALLKEYS | DLGC_WANTMESSAGE | DLGC_WANTTAB)) == 0)
	{
		HWND hWndFocus = ::GetFocus();
		HWND hWndCtl = hWndFocus;
		if (::IsChild(m_hWnd, hWndCtl))
		{
			do
			{
				HWND hWndParent = ::GetParent(hWndCtl);
				ASSERT(hWndParent != NULL);
				static const TCHAR szComboBox[] = _T("combobox");
				TCHAR szCompare[sizeof(szComboBox)+1];
				::GetClassName(hWndParent, szCompare, sizeof(szCompare));

				int nCmd = bShift ? GW_HWNDPREV : GW_HWNDNEXT;
				if (lstrcmpi(szCompare, szComboBox) == 0)
					hWndCtl = ::GetWindow(hWndParent, nCmd);
				else
					hWndCtl = ::GetWindow(hWndCtl, nCmd);

				if (hWndCtl == NULL)
				{
					SetCtrlFocus(::GetNextDlgTabItem(m_pParentWnd->m_hWnd,
						m_hWnd, bShift));
					return TRUE; // handled one way or the other
				}
			}
			while ((::GetWindowLong(hWndCtl, GWL_STYLE) &
				(WS_DISABLED | WS_TABSTOP)) != WS_TABSTOP);
		}
	}
	return FALSE;
}

BOOL CPropertyPage::PreTranslateKeyDown(MSG* pMsg)
{
	CPropertySheet* pSheet = (CPropertySheet*)m_pParentWnd;
	ASSERT(pSheet->IsKindOf(RUNTIME_CLASS(CPropertySheet)));

	ASSERT(pMsg->message == WM_KEYDOWN);
	DWORD dwDlgCode = ::SendMessage(::GetFocus(), WM_GETDLGCODE, 0, 0);
	if (pMsg->wParam == VK_TAB)
	{
		if (dwDlgCode & DLGC_WANTTAB)
			return FALSE;

		// handle tab key
		if (ProcessTab(pMsg))
			return TRUE;
	}
	else if (pMsg->wParam == VK_RETURN && pSheet->m_hWndDefault == NULL)
	{
		if (dwDlgCode & DLGC_WANTALLKEYS)
			return FALSE;

		// handle return key
		m_pParentWnd->PostMessage(WM_KEYDOWN, VK_RETURN, pMsg->lParam);
		return TRUE;
	}
	else if (pMsg->wParam == VK_ESCAPE)
	{
		if (dwDlgCode & DLGC_WANTALLKEYS)
			return FALSE;

		// escape key handled
		m_pParentWnd->PostMessage(WM_KEYDOWN, VK_ESCAPE, pMsg->lParam);
		return TRUE;
	}
	return FALSE;
}

BOOL CPropertyPage::PreTranslateMessage(MSG* pMsg)
{
	HWND hFocusBefore = ::GetFocus();

	CPropertySheet* pSheet = (CPropertySheet*)m_pParentWnd;
	ASSERT(pSheet->IsKindOf(RUNTIME_CLASS(CPropertySheet)));

	// special case for VK_RETURN and "edit" controls with ES_WANTRETURN
	if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN)
	{
		static const TCHAR szEdit[] = _T("edit");
		TCHAR szCompare[sizeof(szEdit)+1];

		::GetClassName(hFocusBefore, szCompare, sizeof(szCompare));
		if (lstrcmpi(szCompare, szEdit) == 0 &&
			(::GetWindowLong(hFocusBefore, GWL_STYLE) & ES_WANTRETURN))
		{
			::SendMessage(hFocusBefore, WM_CHAR, '\n', 0);
			return TRUE;
		}
	}

	// otherwise check for special accelerators
	BOOL bResult;
	if (pMsg->message == WM_KEYDOWN && PreTranslateKeyDown(pMsg))
		bResult = TRUE;
	else
		bResult = pSheet->PreTranslateMessage(pMsg);

	// if focus changed, make sure buttons are set correctly
	HWND hFocusAfter = ::GetFocus();
	if (hFocusBefore != hFocusAfter)
		pSheet->CheckDefaultButton(hFocusBefore, hFocusAfter);

	return bResult;
}

struct DLGTEMPLATE
{
	DWORD lStyle;
	BYTE  bNumberOfItems;
	WORD  x;
	WORD  y;
	WORD  cx;
	WORD  cy;
};

void CPropertyPage::LoadCaption()
{
	HINSTANCE hInst = AfxFindResourceHandle(m_lpDialogTemplate, RT_DIALOG);
	ASSERT(hInst != NULL);
	HRSRC hResource = ::FindResource(hInst, m_lpDialogTemplate, RT_DIALOG);
	ASSERT(hResource != NULL);
	HGLOBAL hTemplate = ::LoadResource(hInst, hResource);
	ASSERT(hTemplate != NULL);

	// resources don't have to be freed or unlocked in Win32
	DLGTEMPLATE FAR* pDlgTemplate =
		(DLGTEMPLATE FAR*)::LockResource(hTemplate);
	ASSERT(pDlgTemplate != NULL);
	LPCSTR p = (LPCSTR)((BYTE FAR*)pDlgTemplate + sizeof(DLGTEMPLATE));
	// skip menu stuff
	p+= (*p == 0xffff) ? 2 : lstrlen(p)+1;
	// skip window class stuff
	p+= (*p == 0xffff) ? 2 : lstrlen(p)+1;
	// we're now at the caption
	m_strCaption = p;
}

BOOL CPropertyPage::CreatePage()
{
	if (!Create(m_lpDialogTemplate, m_pParentWnd))
		return FALSE; // Create() failed...

	// Must be a child for obvious reasons, and must be disabled to prevent
	// it from taking the focus away from the tab area during initialization...
	ASSERT((GetStyle() & (WS_DISABLED | WS_CHILD)) == (WS_DISABLED | WS_CHILD));

	return TRUE;    // success
}

BOOL CPropertyPage::OnSetActive()
{
	if (m_hWnd == NULL)
	{
		if (!CreatePage())
			return FALSE;

		ASSERT(m_hWnd != NULL);
	}
	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CPropertyPage Message Handlers

BOOL CPropertyPage::OnNcCreate(LPCREATESTRUCT lpcs)
{
	DWORD dwStyle = GetStyle();
	dwStyle &= ~(WS_CAPTION|WS_BORDER);
	dwStyle |= WS_GROUP|WS_TABSTOP;
	::SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);

	return CDialog::OnNcCreate(lpcs);
}

int CPropertyPage::OnCreate(LPCREATESTRUCT lpcs)
{
	if (CDialog::OnCreate(lpcs) == -1)
		return -1;

	CRect rect;
	GetWindowRect(&rect);
	rect.bottom -= GetSystemMetrics(SM_CYCAPTION) - CY_BORDER;
	SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(),
		SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);

	return 0;
}

HBRUSH CPropertyPage::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	LRESULT lResult;
	if (pWnd->SendChildNotifyLastMsg(&lResult))
		return (HBRUSH)lResult;

	if (!GrayCtlColor(pDC->m_hDC, pWnd->GetSafeHwnd(), nCtlColor,
	  afxData.hbrBtnFace, afxData.clrBtnText))
		return (HBRUSH)Default();
	return afxData.hbrBtnFace;
}

void CPropertyPage::OnClose()
{
	GetParent()->PostMessage(WM_CLOSE);
}

/////////////////////////////////////////////////////////////////////////////
// CPropertyPage Diagnostics

#ifdef _DEBUG
void CPropertyPage::AssertValid() const
{
	CDialog::AssertValid();
}

void CPropertyPage::Dump(CDumpContext& dc) const
{
	CDialog::Dump(dc);

	dc << "m_strCaption = " << m_strCaption << "\n";
	dc << "m_bChanged = " << m_bChanged << "\n";
}

void CPropertyPage::EndDialog(int /*nID*/)
{
	// Do NOT call EndDialog for a page!  Coordinate with the parent
	//  for termination (you can post WM_COMMAND with IDOK or IDCANCEL
	//  to handle those cases).

	ASSERT(FALSE);
}
#endif //_DEBUG

////////////////////////////////////////////////////////////////////////////
// CTabControl -- implementation of tabs along the top of dialog

BEGIN_MESSAGE_MAP(CTabControl, CWnd)
	//{{AFX_MSG_MAP(CTabControl)
	ON_WM_PAINT()
	ON_WM_KEYDOWN()
	ON_WM_GETDLGCODE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_TIMER()
	ON_WM_SETFOCUS()
	ON_WM_KILLFOCUS()
	ON_WM_ERASEBKGND()
	ON_WM_SIZE()
	ON_WM_CREATE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

CTabControl::CTabControl()
{
	m_rectScroll.SetRectEmpty();
	m_nCurTab = 0;
	m_nFirstTab = 0;
	m_nScrollState = SCROLL_NULL;
	m_bInSize = FALSE;
	m_hBoldFont = m_hThinFont = NULL;

	if (hbmScroll == NULL)
	{
		// Note: If this LoadBitmap call fails, it is likely that
		//  _AFX_NO_PROPERTY_RESOURCES is defined in your .RC file.
		// To correct the situation, remove the following line from your
		//  resource script:
		//      #define _AFX_NO_PROPERTY_RESOURCES
		// This should be done using the Resource.Set Includes... command.

		// all bitmaps must live in the same module
		HINSTANCE hInst =
			AfxFindResourceHandle(MAKEINTRESOURCE(AFX_IDB_SCROLL), RT_BITMAP);
		VERIFY(hbmScroll =
			LoadBitmap(hInst, MAKEINTRESOURCE(AFX_IDB_SCROLL)));

		BITMAP bmStruct;
		VERIFY(GetObject(hbmScroll, sizeof(BITMAP), &bmStruct));
		sizeScroll.cx = bmStruct.bmWidth / 5;
		sizeScroll.cy = bmStruct.bmHeight;
	}
}

CTabControl::~CTabControl()
{
	for (int i = 0 ; i < GetItemCount() ; i++)
		delete (CTabItem*)m_tabs[i];
	AfxDeleteObject((HGDIOBJ*)&m_hBoldFont);
	AfxDeleteObject((HGDIOBJ*)&m_hThinFont);
}

BOOL CTabControl::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd,
	UINT nID)
{
	// create a new window class without CS_DBLCLKS
	return CreateEx(0,
		AfxRegisterWndClass(CS_VREDRAW|CS_HREDRAW,
		LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_BTNFACE+1)),
		NULL, dwStyle, rect.left, rect.top, rect.right-rect.left,
		rect.bottom-rect.top, pParentWnd->GetSafeHwnd(), (HMENU)nID);
}

void CTabControl::AddTab(LPCTSTR lpszCaption)
{
	m_tabs.Add(new CTabItem(lpszCaption, -1));
	if (m_hWnd != NULL)
	{
		SetFirstTab(m_nFirstTab);
		Invalidate();
	}
}

void CTabControl::RemoveTab(int nTab)
{
	// remove the tab item
	delete (CTabItem*)m_tabs[nTab];
	m_tabs.RemoveAt(nTab);

	// adjust internal indices
	if (m_nCurTab > nTab)
		--m_nCurTab;
	if (m_nCurTab >= GetItemCount())
		m_nCurTab = 0;
	if (m_hWnd != NULL)
	{
		SetFirstTab(0);
		Invalidate();
	}
}

void CTabControl::OnSetFocus(CWnd* /*pOldWnd*/)
{
	DrawFocusRect();
}

void CTabControl::OnKillFocus(CWnd* /*pNewWnd*/)
{
	DrawFocusRect();
}

UINT CTabControl::OnGetDlgCode()
{
	return CWnd::OnGetDlgCode() | DLGC_WANTARROWS;
}

void CTabControl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if (nChar == VK_LEFT || nChar == VK_RIGHT)
	{
		if (NextTab(nChar==VK_RIGHT))
			SetFocus();
	}
	else
		CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
}

BOOL CTabControl::OnEraseBkgnd(CDC* pDC)
{
	CRect rect;
	GetClientRect(&rect);
	HBRUSH hBrush = (HBRUSH)GetParent()->SendMessage(WM_CTLCOLOR,
		(WPARAM)pDC->m_hDC, MAKELONG(GetParent()->m_hWnd, CTLCOLOR_DLG));
	::FillRect(pDC->m_hDC, &rect, hBrush);
	return TRUE;
}

void CTabControl::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);
	m_rectScroll.SetRect(cx - sizeScroll.cx, cy - 1 - sizeScroll.cy, cx, cy-1);
	if (!m_bInSize)
	{
		m_bInSize = TRUE;
		SetFirstTab(m_nFirstTab);   // recalc all tab positions
		ScrollIntoView(m_nCurTab);  // make sure current selection still in view
		SetWindowPos(NULL, 0, 0, cx, m_nHeight,
			SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
		m_bInSize = FALSE;
	}
}

int CTabControl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CWnd::OnCreate(lpCreateStruct) == -1)
		return -1;

	// initialize fonts to fonts derived from parent window
	ASSERT(::GetParent(m_hWnd) != NULL);
	if (m_hBoldFont != NULL)
		return 0;

	m_hBoldFont = (HFONT)::SendMessage(::GetParent(m_hWnd), WM_GETFONT, 0, 0);
	if (m_hBoldFont != NULL)
	{
		LOGFONT lf;
		VERIFY(GetObject(m_hBoldFont, sizeof(LOGFONT), &lf));
		lf.lfWeight = FW_BOLD;
		m_hBoldFont = CreateFontIndirect(&lf);
		ASSERT(m_hBoldFont != NULL);
		lf.lfWeight = FW_LIGHT;
		m_hThinFont = CreateFontIndirect(&lf);
		ASSERT(m_hThinFont != NULL);
	}
	return 0;
}

BOOL CTabControl::NextTab(BOOL bNext)
{
	return SetCurSel(
		(m_nCurTab + (bNext ? 1 : -1) + GetItemCount()) % GetItemCount()
		);
}

BOOL CTabControl::SetCurSel(int nTab)
{
	if (nTab == m_nCurTab)
	{
		// if tab is not completely visible
		if (!IsTabVisible(m_nCurTab, TRUE))
			ScrollIntoView(m_nCurTab);
		return TRUE;
	}

	// attempt to switch to new tab
	if (GetParent()->SendMessage(TCM_TABCHANGING, 0, 0) == 0)
	{
		// succesful switch, send notify and invalidate tab control
		int nOldTab = m_nCurTab;
		m_nCurTab = nTab;
		GetParent()->SendMessage(TCM_TABCHANGED, 0, 0);
		InvalidateTab(nOldTab);
		InvalidateTab(m_nCurTab);
		ScrollIntoView(m_nCurTab);
		return TRUE;
	}
	return FALSE;
}

BOOL CTabControl::IsTabVisible(int nTab, BOOL bComplete) const
{
	CTabItem* pItem = GetTabItem(nTab);
	BOOL bResult;
	if (pItem->m_rect.IsRectNull())
		bResult = FALSE;
	else if (pItem->m_rect.Width() >= pItem->m_nWidth)
		bResult = TRUE;
	else// partially visible
		bResult = !bComplete;
	return bResult;
}

void CTabControl::OnPaint()
{
	CPaintDC dc(this);
	dc.SetBkMode(TRANSPARENT);

	// Draw all the tabs that are currently within view
	for (int i = 0 ; i < GetItemCount() ; i++)
	{
		if (IsTabVisible(i) && (i != m_nCurTab))
			GetTabItem(i)->Draw(&dc, m_hThinFont, FALSE);
	}
	// Draw the current tab last so that it gets drawn on "top"
	if (IsTabVisible(m_nCurTab))
		GetTabItem(m_nCurTab)->Draw(&dc, m_hBoldFont, TRUE);

	// Draw the line underneath all the tabs
	CRect rectItem = GetTabItem(m_nCurTab)->m_rect;
	HPEN hOldPen = (HPEN)dc.SelectObject(afxData.hpenBtnHilite);

	CRect rect;
	GetWindowRect(&rect);
	rect.OffsetRect(-rect.left, -rect.top);

	dc.MoveTo(0, rect.bottom - 1);
	if (!rectItem.IsRectNull())
	{
		// this leaves a gap in the line if the currently selected
		// tab is within view.
		dc.LineTo(rectItem.left - sizeSelTab.cx, rect.bottom - 1);
		dc.MoveTo(rectItem.right + sizeSelTab.cx + 1, rect.bottom - 1);
	}
	dc.LineTo(rect.right + 1, rect.bottom - 1);

	if (hOldPen != NULL)
		dc.SelectObject(hOldPen);

	if (CanScroll())
		DrawScrollers(&dc);

	if (GetFocus() == this)
		DrawFocusRect(&dc);
}

void CTabControl::DrawFocusRect(CDC* pDC)
{
	if (!IsWindowVisible())
		return;

	// obtain usable DC for drawing
	CDC* pTempDC = NULL;
	if (pDC == NULL)
	{
		pDC = pTempDC = GetDC();
		GetParent()->SendMessage(WM_CTLCOLOR,
			(WPARAM)pDC->m_hDC, MAKELONG(GetParent()->m_hWnd, CTLCOLOR_DLG));
	}

	// draw and cleanup
	pDC->DrawFocusRect(GetTabItem(m_nCurTab)->m_rect);
	if (pTempDC != NULL)
		ReleaseDC(pTempDC);
}

void CTabControl::DrawScrollers(CDC* pDC)
{
	ASSERT(pDC != NULL);

	// Choose image bitmap depending on scroll state
	int iImage = 0;

	// Choose bitmap depending on scroll state
	if (IsTabVisible(0, TRUE))
		iImage = 2;
	else if (IsTabVisible(GetItemCount()-1, TRUE))
		iImage = 4;

	if (!m_bScrollPause)
	{
		if (m_nScrollState == SCROLL_LEFT)
			iImage = 1;
		else if (m_nScrollState == SCROLL_RIGHT)
			iImage = 3;
	}

	CDC dcTemp;
	dcTemp.CreateCompatibleDC(pDC);
	HBITMAP hbmOld = (HBITMAP)::SelectObject(dcTemp.m_hDC, hbmScroll);

	pDC->BitBlt(m_rectScroll.left, m_rectScroll.top, m_rectScroll.Width(),
		m_rectScroll.Height(), &dcTemp, iImage * sizeScroll.cx, 0, SRCCOPY);

	::SelectObject(dcTemp.m_hDC, hbmOld);
}

void CTabControl::LayoutTabsStacked(int nTab)
{
	// This function recalcs the positions of all the tabs, assuming the
	// specified tab is the first (leftmost) visible tab.

	ASSERT(nTab >= 0 && nTab < GetItemCount());

	CTabItem* pItem = NULL;
	CRect rectClient;
	GetClientRect(&rectClient);

	CClientDC dc(NULL); // could occur before creation
	HFONT hOldFont = NULL;
	if (m_hBoldFont != NULL)
		hOldFont = (HFONT)::SelectObject(dc.m_hDC, m_hBoldFont);

	int nTabHeight = dc.GetTextExtent(_T("M"), 1).cy+sizeTabTextMargin.cy * 2;
	int nHeight = nTabHeight + sizeSelTab.cy;

	CPoint pt(sizeSelTab.cx, -nHeight);

	BOOL bMultiRow = FALSE;
	// calculate each tab's base position
	for (int i = 0; i < GetItemCount(); i++)
	{
		pItem = GetTabItem(i);
		pItem->m_rectPrev = pItem->m_rect;
		if (pItem->m_nWidth < 0)
		{
			CSize text = dc.GetTextExtent(pItem->m_strCaption,
				pItem->m_strCaption.GetLength());
			pItem->m_nWidth = text.cx + sizeTabTextMargin.cx * 2;
		}

		if (pt.x + pItem->m_nWidth + sizeSelTab.cx > rectClient.Width())
		{
			pt = CPoint(sizeSelTab.cx, pt.y - nHeight);
			bMultiRow = TRUE;
		}

		pItem->m_rect = CRect(pt, CSize(pItem->m_nWidth, nTabHeight));
		pt.x += pItem->m_nWidth + sizeSelTab.cx;
	}

	// adjust tabs so correct row is showing
	m_nHeight = -pt.y;
	int nBaseOffset = -GetTabItem(nTab)->m_rect.bottom+m_nHeight;
	for (i = 0; i < GetItemCount(); i++)
	{
		CRect& rect = GetTabItem(i)->m_rect;
		rect.OffsetRect(0, nBaseOffset);
		if (rect.bottom > m_nHeight)
			rect.OffsetRect(0, -m_nHeight);
		rect.OffsetRect(0, sizeSelTab.cy);
	}
	m_nHeight += sizeSelTab.cy;

	// pad rows to fill up entire row if more than one row
	if (bMultiRow)
	{
		i = 0;
		int nLastVert;
		for (int nLast = 0; nLast < GetItemCount(); nLast = i)
		{
			nLastVert = GetTabItem(nLast)->m_rect.top;
			i = nLast+1;
			// look for end of items or new row
			while (i<GetItemCount() && GetTabItem(i)->m_rect.top == nLastVert)
				i++;
			// pad from nLast to i-1
			int nPadTotal = rectClient.right - sizeSelTab.cx -
				GetTabItem(i-1)->m_rect.right - 1;
			int nPad = nPadTotal/(i-nLast);
			for (int j=nLast;j<i;j++)
			{
				CRect& rect = GetTabItem(j)->m_rect;
				rect.OffsetRect(nPad*(j-nLast), 0);
				rect.right += nPad;
				if (j == i-1 && nPad != 0) // last one
					rect.right += nPadTotal%nPad;
			}
		}
	}

	// invalidate changed tabs
	for (i = 0; i < GetItemCount(); i++)
	{
		if (pItem->m_rect != pItem->m_rectPrev)
			InvalidateTab(i);
	}

	if (hOldFont != NULL)
		::SelectObject(dc.m_hDC, hOldFont);
}

void CTabControl::LayoutTabsSingle(int nTab)
{
	// This function recalcs the positions of all the tabs, assuming the
	// specified tab is the first (leftmost) visible tab.

	ASSERT(nTab >= 0 && nTab < GetItemCount());

	CTabItem* pItem = NULL;
	int x = sizeSelTab.cx;
	CRect rectClient;
	GetClientRect(&rectClient);

	CClientDC dc(NULL); // could occur before creation
	HFONT hOldFont = NULL;
	if (m_hThinFont != NULL)
		hOldFont = (HFONT)::SelectObject(dc.m_hDC, m_hThinFont);

	m_nHeight = dc.GetTextExtent(_T("M"), 1).cy +
		sizeTabTextMargin.cy * 2 + sizeSelTab.cy * 2;

	for (int i = 0; i < GetItemCount(); i++)
	{
		pItem = GetTabItem(i);
		if (pItem->m_nWidth < 0)
		{
			CSize text = dc.GetTextExtent(pItem->m_strCaption,
				pItem->m_strCaption.GetLength());
			pItem->m_nWidth = text.cx + sizeTabTextMargin.cx * 2;
		}
		// everything before the first tab is not visible
		if (i<nTab)
			pItem->m_rect.SetRectEmpty();
		// calculate locations for all other tabs
		else
		{
			pItem->m_rect.SetRect(x, sizeSelTab.cy,
				x + pItem->m_nWidth, m_nHeight-sizeSelTab.cy);
			x += pItem->m_nWidth + sizeSelTab.cx;
		}
	}

	// do they all fit?
	pItem = GetTabItem(m_tabs.GetSize()-1);
	x = rectClient.right - (sizeScroll.cx/3 + sizeScroll.cx);
	if (pItem->m_rect.right > ((nTab==0) ? rectClient.right : x))
	{
		int i = m_tabs.GetSize();
		while (i-- > 0)
		{
			pItem = GetTabItem(i);
			if (pItem->m_rect.left > x)
				pItem->m_rect.SetRectEmpty();
			else
			{
				if (pItem->m_rect.right > x)
					pItem->m_rect.right = x;
				break;
			}
		}
	}

	if (hOldFont != NULL)
		::SelectObject(dc.m_hDC, hOldFont);
}

void CTabControl::SetFirstTab(int nTab)
{
	ASSERT(m_hWnd != NULL);

	if (GetStyle() & TCS_MULTILINE)
		LayoutTabsStacked(nTab);
	else
		LayoutTabsSingle(nTab);

	m_nFirstTab = nTab;
}

void CTabControl::Scroll(int nDirection)
{
	ASSERT(nDirection == SCROLL_LEFT || nDirection == SCROLL_RIGHT);
	ASSERT(CanScroll());

	switch (nDirection)
	{
	case SCROLL_LEFT:
		if (IsTabVisible(0))
			return;
		SetFirstTab(m_nFirstTab - 1);
		break;
	case SCROLL_RIGHT:
		if (IsTabVisible(GetItemCount()-1, TRUE))
			return;
		SetFirstTab(m_nFirstTab + 1);
		break;
	}

	// repaint everything except the scroll btns
	CRect rectClient;
	GetClientRect(&rectClient);
	rectClient.right = m_rectScroll.left - 1;
	InvalidateRect(&rectClient);
}

void CTabControl::ScrollIntoView(int nTab)
{
	ASSERT((nTab >= 0) && (nTab < GetItemCount()));
	if (GetStyle() & TCS_MULTILINE)
		LayoutTabsStacked(nTab);
	else
	{
		int nOldFirstTab = m_nFirstTab;
		// do we need to scroll left or right?
		int nIncrement = (nTab > m_nFirstTab) ? 1 : -1;
		// scroll over until completely visible or until the desired tab is
		// the first tab.  This handles the case where a tab is bigger than
		// the window
		while (!IsTabVisible(nTab, TRUE) && nTab != m_nFirstTab)
			SetFirstTab(m_nFirstTab + nIncrement);
		// if the same first tab we haven't moved so don't invalidate
		if (nOldFirstTab != m_nFirstTab)
			Invalidate();
	}
}

BOOL CTabControl::CanScroll()
{
	// if either the first or the last tab is not visible, it's scrollable
	return !IsTabVisible(0) || !IsTabVisible(GetItemCount()-1, TRUE);
}

void CTabControl::OnMouseMove(UINT nFlags, CPoint point)
{
	if (this == GetCapture())
	{
		ASSERT(m_nScrollState == SCROLL_LEFT || m_nScrollState == SCROLL_RIGHT);

		int nNewState = TabFromPoint(point);
		BOOL bPause = !(nNewState == m_nScrollState);

		if (bPause == m_bScrollPause)
			return;

		if (bPause)
			KillTimer(TIMER_ID);
		else
		{
			VERIFY(SetTimer(TIMER_ID, TIMER_DELAY, NULL) == TIMER_ID);
			Scroll(m_nScrollState);
		}

		m_bScrollPause = bPause;
		InvalidateTab(m_nScrollState);
	}

	CWnd::OnMouseMove(nFlags, point);
}

void CTabControl::OnLButtonUp(UINT /*nFlags*/, CPoint /*point*/)
{
	Capture(SCROLL_NULL);
}

void CTabControl::OnLButtonDown(UINT /*nFlags*/, CPoint point)
{
	int nTab = TabFromPoint(point);
	switch (nTab)
	{
	case -1:
		break;

	case SCROLL_LEFT:
		if (!IsTabVisible(0))
		{
			Scroll(nTab);
			Capture(nTab);
		}
		break;
	case SCROLL_RIGHT:
		if (!IsTabVisible(GetItemCount()-1, TRUE))
		{
			Scroll(nTab);
			Capture(nTab);
		}
		break;

	default:
		ASSERT(nTab >= 0);
		if (nTab == m_nCurTab)
		{
			// if tab is not completely visible
			if (!IsTabVisible(m_nCurTab, TRUE))
				ScrollIntoView(m_nCurTab);
			SetFocus();
		}
		else
			SetCurSel(nTab);
		break;
	}
}

void CTabControl::OnTimer(UINT nTimerID)
{
	if (nTimerID == CTabControl::TIMER_ID)
	{
		ASSERT((m_nScrollState == SCROLL_LEFT) || (m_nScrollState == SCROLL_RIGHT));
		ASSERT(!m_bScrollPause);
		Scroll(m_nScrollState);
	}
}

void CTabControl::Capture(int nDirection)
{
	ASSERT((m_nScrollState == SCROLL_LEFT) ||
		   (m_nScrollState == SCROLL_RIGHT) ||
		   (m_nScrollState == SCROLL_NULL));

	switch (nDirection)
	{
	case SCROLL_LEFT:
	case SCROLL_RIGHT:
		SetCapture();
		VERIFY(SetTimer(TIMER_ID, TIMER_DELAY, NULL) == TIMER_ID);
		InvalidateTab(nDirection);
		break;

	case SCROLL_NULL:
		::ReleaseCapture();
		KillTimer(TIMER_ID);
		InvalidateTab(m_nScrollState);
		break;

	default:
		ASSERT(FALSE);
		break;
	}

	m_nScrollState = nDirection;
	m_bScrollPause = FALSE;
}

void CTabControl::InvalidateTab(int nTab, BOOL bInflate)
{
	CRect rect;
	switch (nTab)
	{
	case SCROLL_NULL:
		rect.SetRectEmpty();
		break;

	case SCROLL_LEFT:
	case SCROLL_RIGHT:
		rect = m_rectScroll;
		break;

	default:
		rect = GetTabItem(nTab)->m_rect;
		if (bInflate)
		{
			rect.InflateRect(sizeSelTab.cx, sizeSelTab.cy);
			rect.right += CX_BORDER;
		}
	}
	InvalidateRect(&rect, nTab >= 0);
}

int CTabControl::TabFromPoint(CPoint pt)
{
	// are we on the scroll buttons?
	if (CanScroll() && m_rectScroll.PtInRect(pt))
	{
		if (pt.x < m_rectScroll.left + (m_rectScroll.Width() / 2))
			return SCROLL_LEFT;
		else
			return SCROLL_RIGHT;
	}

	// are we on a tab?
	for (int i=0;i < GetItemCount();i++)
	{
		if (GetTabItem(i)->m_rect.PtInRect(pt))
			return i;
	}
	return -1;
}

/////////////////////////////////////////////////////////////////////////////
// CPropertySheet -- a tabbed "dialog" (really a popup-window)

BEGIN_MESSAGE_MAP(CPropertySheet, CWnd)
	//{{AFX_MSG_MAP(CPropertySheet)
	ON_WM_PAINT()
	ON_WM_CTLCOLOR()
	ON_WM_CREATE()
	ON_WM_ACTIVATE()
	ON_WM_CLOSE()
	ON_WM_SETFOCUS()
	ON_COMMAND(IDOK, OnOK)
	ON_COMMAND(IDCANCEL, OnCancel)
	ON_COMMAND(ID_APPLY_NOW, OnApply)
	ON_COMMAND(ID_HELP, OnHelp)
	ON_MESSAGE(TCM_TABCHANGING, OnTabChanging)
	ON_MESSAGE(TCM_TABCHANGED, OnTabChanged)
	ON_MESSAGE(WM_GETFONT, OnGetFont)
	ON_MESSAGE(WM_COMMANDHELP, OnCommandHelp)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

CPropertySheet::CPropertySheet(UINT nIDCaption, CWnd* pParent, UINT iSelPage)
{
	m_strCaption.LoadString(nIDCaption);
	CommonConstruct(pParent, iSelPage);
}

CPropertySheet::CPropertySheet(LPCTSTR pszCaption, CWnd* pParent, UINT iSelPage)
{
	ASSERT(pszCaption != NULL);
	m_strCaption = pszCaption;
	CommonConstruct(pParent, iSelPage);
}

void CPropertySheet::CommonConstruct(CWnd* pParent, UINT iSelPage)
{
	m_pParentWnd = pParent;
	m_nCurPage = iSelPage;
	m_hFocusWnd = NULL;
	m_bParentDisabled = FALSE;
	m_bModeless = TRUE;
	m_bStacked = TRUE;
	m_hWndDefault = NULL;
	m_hLastFocus = NULL;

	m_hFont = NULL;         // font is created after first page is created

	// Note: m_sizeButton, m_cxButtonGap, and m_sizeTabMargin are calculated later
}

CPropertySheet::~CPropertySheet()
{
	AfxDeleteObject((HGDIOBJ*)&m_hFont);
}

void CPropertySheet::PageChanged()
{
	BOOL bEnabled = FALSE;
	for (int i = 0; i < GetPageCount(); i++)
	{
		if (GetPage(i)->m_bChanged)
		{
			bEnabled = TRUE;
			break;
		}
	}
	::EnableDlgItem(m_hWnd, ID_APPLY_NOW, bEnabled, IDOK);
}

void CPropertySheet::CancelToClose()
{
	::EnableDlgItem(m_hWnd, IDCANCEL, FALSE);

	// Note: If this _AfxLoadString call fails, it is likely that
	//  _AFX_NO_PROPERTY_RESOURCES is defined in your .RC file.
	// To correct the situation, remove the following line from your
	//  resource script:
	//      #define _AFX_NO_PROPERTY_RESOURCES
	// This should be done using the Resource.Set Includes... command.

	TCHAR szCaption[256];
	VERIFY(_AfxLoadString(AFX_IDS_PS_CLOSE, szCaption) != 0);
	::SetDlgItemText(m_hWnd, IDOK, szCaption);
}

BOOL CPropertySheet::CreateStandardButtons()
{
	for (int i = 0; 
		i < sizeof(standardButtons)/sizeof(standardButtons[0]); i++)
	{
		// Note: If one of these _AfxLoadString calls fail, it is likely that
		//  _AFX_NO_PROPERTY_RESOURCES is defined in your .RC file.
		// To correct the situation, remove the following line from your
		//  resource script:
		//      #define _AFX_NO_PROPERTY_RESOURCES
		// This should be done using the Resource.Set Includes... command.

		// load the caption
		TCHAR szCaption[256];
		VERIFY(_AfxLoadString(AFX_IDS_PS_OK+i, szCaption) != 0);
		char* lpsz = _tcschr(szCaption, '\n');
		if (lpsz != NULL)
			*lpsz = '\0';

		// create the control
		HWND hWnd = ::CreateWindow(_T("button"), szCaption,
			WS_CHILD|WS_VISIBLE|WS_TABSTOP|WS_GROUP|BS_PUSHBUTTON,
			0, 0, 0, 0, m_hWnd, (HMENU)standardButtons[i],
			AfxGetInstanceHandle(), NULL);
		if (hWnd == NULL)
		{
			TRACE0("Warning: failed to create standard buttons\n");
			return FALSE;
		}

		// set the font
		if (m_hFont != NULL)
			::SendMessage(hWnd, WM_SETFONT, (WPARAM)m_hFont, 0);
	}

	// special case enable/disable
	::EnableDlgItem(m_hWnd, ID_APPLY_NOW, FALSE);
	::EnableDlgItem(m_hWnd, ID_HELP, _AfxHelpEnabled());

	return TRUE;
}

BOOL CPropertySheet::ProcessTab(MSG* pMsg)
{
	if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_TAB)
	{
		HWND hWnd = ::GetFocus();
		if ((::SendMessage(hWnd,WM_GETDLGCODE, 0, 0) &
			(DLGC_WANTALLKEYS | DLGC_WANTMESSAGE | DLGC_WANTTAB)) == 0)
		{
			BOOL bShift = (GetKeyState(VK_SHIFT) < 0);
			if (GetKeyState(VK_CONTROL) < 0) // control-tab
			{
				if (m_tabRow.NextTab(!bShift))
					m_tabRow.SetFocus();
				return TRUE;
			}
			else if (bShift &&
				!::IsChild(GetActivePage()->m_hWnd, pMsg->hwnd) &&
				::GetNextDlgTabItem(m_hWnd, pMsg->hwnd, TRUE) ==
					GetActivePage()->m_hWnd)
			{
				// shift-tabbing from the sheet into the page
				HWND hWndPage = GetActivePage()->m_hWnd;
				// get the first control
				HWND hWndCtrl = ::GetWindow(hWndPage, GW_CHILD);
				// get previous tab item (i.e. last tab item in page)
				hWndCtrl = ::GetNextDlgTabItem(hWndPage, hWndCtrl, TRUE);
				SetCtrlFocus(hWndCtrl);
				return TRUE;
			}
		}
	}
	return FALSE;
}

void CPropertySheet::CheckDefaultButton(HWND hFocusBefore, HWND hFocusAfter)
{
	ASSERT(hFocusBefore != hFocusAfter);

	// determine old default button
	HWND hOldDefault = NULL;
	DWORD dwOldDefault = 0;
	if (::IsChild(m_hWnd, hFocusBefore))
	{
		hOldDefault = hFocusBefore;
		if (hFocusBefore != NULL)
			dwOldDefault = (DWORD)::SendMessage(hFocusBefore, WM_GETDLGCODE, 0, 0);
		if (!(dwOldDefault & (DLGC_DEFPUSHBUTTON|DLGC_UNDEFPUSHBUTTON)))
		{
			hOldDefault = ::GetDlgItem(m_hWnd, IDOK);
			dwOldDefault = (DWORD)::SendMessage(hOldDefault, WM_GETDLGCODE, 0, 0);
		}
	}

	// determine new default button
	HWND hWndDefault = NULL;
	DWORD dwDefault = 0;
	if (::IsChild(m_hWnd, hFocusAfter))
	{
		hWndDefault = hFocusAfter;
		if (hFocusAfter != NULL)
			dwDefault = (DWORD)::SendMessage(hFocusAfter, WM_GETDLGCODE, 0, 0);
		if (!(dwDefault & (DLGC_DEFPUSHBUTTON|DLGC_UNDEFPUSHBUTTON)))
		{
			hWndDefault = ::GetDlgItem(m_hWnd, IDOK);
			dwDefault = (DWORD)::SendMessage(hWndDefault, WM_GETDLGCODE, 0, 0);
		}
	}

	// set new styles
	if (hOldDefault != hWndDefault && (dwOldDefault & DLGC_DEFPUSHBUTTON))
		::SendMessage(hOldDefault, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);

	if (dwDefault & DLGC_UNDEFPUSHBUTTON)
		::SendMessage(hWndDefault, BM_SETSTYLE, BS_DEFPUSHBUTTON, TRUE);

	// remember special case default button
	m_hWndDefault = (hWndDefault == hFocusAfter ? hFocusAfter : NULL);
}

void CPropertySheet::CheckFocusChange()
{
	HWND hWndFocus = ::GetFocus();
	if (hWndFocus != m_hLastFocus)
	{
		CheckDefaultButton(m_hLastFocus, hWndFocus);
		m_hLastFocus = hWndFocus;
	}
}

BOOL CPropertySheet::PreTranslateMessage(MSG* pMsg)
{
	BOOL bResult = FALSE;

	// post message to check for change in focus later
	if (pMsg->message != WM_KICKIDLE)
	{
		MSG msg;
		PeekMessage(&msg, NULL, WM_KICKIDLE, WM_KICKIDLE, PM_REMOVE);
		PostMessage(WM_KICKIDLE);
	}

	// process special case keystrokes
	if (ProcessChars(pMsg))
		bResult = TRUE;
	else if (ProcessTab(pMsg))
		bResult = TRUE;
	else
	{
		// handle normal accelerator keystrokes
		CPropertyPage* pPage = GetActivePage();
		if ((::IsChild(pPage->m_hWnd, pMsg->hwnd) &&
			::IsDialogMessage(pPage->m_hWnd, pMsg)) ||
			::IsDialogMessage(m_hWnd, pMsg))
		{
			bResult = TRUE;
		}
	}

	if (!bResult)
		bResult = CWnd::PreTranslateMessage(pMsg);

	// handle WM_KICKIDLE message to check for focus changes
	if (pMsg->message == WM_KICKIDLE)
		CheckFocusChange();

	return bResult;
}

HWND CPropertySheet::FindNextControl(HWND hWnd, TCHAR ch)
{
	CPropertyPage* pPage = GetActivePage();
	if (pPage == NULL)
		return NULL;

	HWND hWndFocusPage = hWnd;
	HWND hWndFocusSheet = hWnd;
	if (::IsChild(pPage->m_hWnd, hWnd)) // current focus is on the page
		hWndFocusSheet = pPage->m_hWnd;
	else
		hWndFocusPage = ::GetWindow(pPage->m_hWnd, GW_CHILD);

	HWND hWndNext = ::FindNextControl(pPage->m_hWnd, hWndFocusPage, ch);
	if (hWndNext == NULL) // if not found on page
		hWndNext = ::FindNextControl(m_hWnd, hWndFocusSheet, ch);

	return hWndNext;
}

BOOL CPropertySheet::ProcessChars(MSG* pMsg)
{
	CPropertyPage* pPage = GetActivePage();
	if (pPage == NULL)
		return FALSE;

	HWND hWnd = pMsg->hwnd;
	UINT message = pMsg->message;

	if (hWnd == NULL)
		return FALSE;

	switch (message)
	{
		case WM_SYSCHAR:
			/* If no control has focus, and Alt not down, then ignore. */
			if ((::GetFocus() == NULL) && (GetKeyState(VK_MENU) >= 0))
				return FALSE;

			// fall through

		case WM_CHAR:
			/* Ignore chars sent to the dialog box (rather than the control). */
			if (hWnd == m_hWnd || hWnd == pPage->m_hWnd)
				return FALSE;

			WORD code = (WORD)(DWORD)::SendMessage(hWnd, WM_GETDLGCODE, pMsg->wParam,
				(LPARAM)(LPMSG)pMsg);

			// If the control wants to process the message, then don't check
			// for possible mnemonic key.

			// Check if control wants to handle this message itself
			if (code & DLGC_WANTMESSAGE)
				return FALSE;

			if ((message == WM_CHAR) && (code & DLGC_WANTCHARS))
				return FALSE;

			HWND hWndNext = FindNextControl(hWnd, (TCHAR)pMsg->wParam);
			if (hWndNext == NULL) // nothing found
				return FALSE;

			// once we know we are going to handle it, call the filter
			if (CallMsgFilter(pMsg, MSGF_DIALOGBOX))
				return TRUE;

			GotoControl(hWndNext, (TCHAR)pMsg->wParam);
			return TRUE;
	}
	return FALSE;
}

void CPropertySheet::GotoControl(HWND hWnd, TCHAR ch)
{
	HWND  hWndFirst;
	for (hWndFirst = NULL; hWndFirst != hWnd;
		hWnd = FindNextControl(hWnd, ch))
	{
		if (hWndFirst == NULL)
			hWndFirst = hWnd;

		WORD code = (WORD)(DWORD)::SendMessage(hWnd, WM_GETDLGCODE, 0, 0L);
		// If a non-disabled static item, then jump ahead to nearest tabstop.
		if (code & DLGC_STATIC && ::IsWindowEnabled(hWnd))
		{
			CPropertyPage* pPage = GetActivePage();
			if (::IsChild(pPage->m_hWnd, hWnd))
				hWnd = ::GetNextDlgTabItem(pPage->m_hWnd, hWnd, FALSE);
			else
				hWnd = ::GetNextDlgTabItem(m_hWnd, hWnd, FALSE);
			code = (WORD)(DWORD)::SendMessage(hWnd, WM_GETDLGCODE, 0, 0L);
		}

		if (::IsWindowEnabled(hWnd))
		{
			// Is it a Pushbutton?
			if (!(code & DLGC_BUTTON))
			{
				SetCtrlFocus(hWnd);
			}
			else
			{
				// Yes, click it, but don't give it the focus.
				if ((code & DLGC_DEFPUSHBUTTON) ||
					(code & DLGC_UNDEFPUSHBUTTON))
				{
					// flash the button
					::SendMessage(hWnd, BM_SETSTATE, TRUE, 0L);
					::SendMessage(hWnd, BM_SETSTATE, FALSE, 0L);

					// Send the WM_COMMAND message.
					::SendMessage(::GetParent(hWnd), WM_COMMAND,
						(WPARAM)_AfxGetDlgCtrlID(hWnd),
						MAKELONG(hWnd, BN_CLICKED));
				}
				else
				{
					::SetFocus(hWnd);
					// Send click message if button has a UNIQUE mnemonic
					if (FindNextControl(hWnd, ch) == hWnd)
					{
						::SendMessage(hWnd, WM_LBUTTONDOWN, 0, 0L);
						::SendMessage(hWnd, WM_LBUTTONUP, 0, 0L);
					}
				}
			}
			return;
		}
	}
}

int CPropertySheet::DoModal()
{
	m_bModeless = FALSE;
	int nResult = IDABORT;

	// cannot call DoModal on a dialog already constructed as modeless
	ASSERT(m_hWnd == NULL);

	// allow OLE servers to disable themselves
	CWinApp* pApp = AfxGetApp();
	pApp->EnableModeless(FALSE);

	// find parent HWND
	CWnd* pParentWnd = CWnd::FromHandle(_AfxGetSafeOwner(m_pParentWnd));

	// create the dialog, then enter modal loop
	if (Create(pParentWnd, WS_SYSMENU|WS_POPUP|WS_CAPTION|DS_MODALFRAME))
	{
		// disable parent (should not disable this window)
		m_bParentDisabled = FALSE;
		if (pParentWnd != NULL && pParentWnd->IsWindowEnabled())
		{
			pParentWnd->EnableWindow(FALSE);
			m_bParentDisabled = TRUE;
		}
		ASSERT(IsWindowEnabled());  // should not be disabled to start!
		SetActiveWindow();

		// for tracking the idle time state
		BOOL bShown = (GetStyle() & WS_VISIBLE) != 0;
		m_nID = -1;

		// acquire and dispatch messages until a WM_QUIT message is received.
		MSG msg;
		while (m_nID == -1 && m_hWnd != NULL)
		{
			// phase1: check to see if we can do idle work
			if (!::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE))
			{
				// send WM_ENTERIDLE since queue is empty
				if (pParentWnd != NULL &&
					!(pParentWnd->GetStyle() & DS_NOIDLEMSG))
				{
					pParentWnd->SendMessage(WM_ENTERIDLE,
						MSGF_DIALOGBOX, MAKELONG(m_hWnd, 0));
				}

				if (!bShown)
				{
					// show and activate the window
					bShown = TRUE;
					ShowWindow(SW_SHOWNORMAL);
				}
			}

			// phase2: pump messages while available
			do
			{
				// pump message -- if WM_QUIT assume cancel and repost
				if (!PumpMessage())
				{
					::PostQuitMessage((int)msg.wParam);
					m_nID = IDCANCEL;
					break;
				}

			} while (m_nID == -1 && m_hWnd != NULL &&
				::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE));
		}

		nResult = m_nID;
		if (m_hWnd != NULL)
			EndDialog(nResult);
	}

	// allow OLE servers to enable themselves
	pApp->EnableModeless(TRUE);

	return nResult;
}

BOOL CPropertySheet::PumpMessage()
{
	ASSERT_VALID(this);

	MSG msg;
	if (!::GetMessage(&msg, NULL, NULL, NULL))
		return FALSE;

	//  let's see if the message should be handled at all
	if (CallMsgFilter(&msg, MSGF_DIALOGBOX))
		return TRUE;
	// process this message
	if (!WalkPreTranslateTree(m_hWnd, &msg))
	{
		::TranslateMessage(&msg);
		::DispatchMessage(&msg);
	}
	return TRUE;
}

BOOL CPropertySheet::Create(CWnd* pParent, DWORD dwStyle, DWORD dwExStyle)
{
	return CreateEx(dwExStyle,
		AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_SAVEBITS,
			LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_BTNFACE+1)),
		m_strCaption, dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, 400, 200,
		pParent->GetSafeHwnd(), NULL);
}

////////////////////////////////////////////////////////////////////////////

void CPropertySheet::AddPage(CPropertyPage* pPage)
{
	ASSERT(pPage != NULL);
	ASSERT(pPage->IsKindOf(RUNTIME_CLASS(CPropertyPage)));
	ASSERT_VALID(pPage);
	m_pages.Add(pPage);
	ASSERT(pPage->m_pParentWnd == NULL);
	pPage->m_pParentWnd = this;
	m_tabRow.AddTab(pPage->m_strCaption);
}

void CPropertySheet::RemovePage(CPropertyPage* pPage)
{
	ASSERT(pPage != NULL);
	ASSERT(pPage->IsKindOf(RUNTIME_CLASS(CPropertyPage)));
	for (int i = 0; i < GetPageCount(); i++)
	{
		if (GetPage(i) == pPage)
		{
			RemovePage(i);
			return;
		}
	}
	ASSERT(FALSE);  // pPage not found
}

void CPropertySheet::RemovePage(int nPage)
{
	ASSERT(m_hWnd == NULL || GetPageCount() > 1);
	ASSERT(nPage >= 0 && nPage < GetPageCount());

	// adjust active page in case of removing active page
	BOOL bRemoveActive = (nPage == m_nCurPage);
	if (m_hWnd != NULL && bRemoveActive)
	{
		int nNewPage = nPage+1;
		if (nNewPage >= GetPageCount())
			nNewPage = 0;
		VERIFY(SetActivePage(nNewPage));
	}

	// remove the page
	CPropertyPage* pPage = GetPage(nPage);
	m_pages.RemoveAt(nPage);
	m_tabRow.RemoveTab(nPage);
	ASSERT(m_nCurPage != nPage);
	if (m_nCurPage > nPage)
		--m_nCurPage;
	pPage->DestroyWindow();
	pPage->m_pParentWnd = NULL;

	// fix focus (otherwise it may be left to NULL)
	if (bRemoveActive && m_hWnd != NULL)
		m_tabRow.SetFocus();
}

void CPropertySheet::EndDialog(int nEndID)
{
	ASSERT_VALID(this);

	m_nID = nEndID;
	DestroyWindow();
}

BOOL CPropertySheet::DestroyWindow()
{
	// re-enable parent if it was disabled
	CWnd* pParentWnd = m_pParentWnd != NULL ? m_pParentWnd : GetParent();
	if (m_bParentDisabled && pParentWnd != NULL)
		pParentWnd->EnableWindow();

	// transfer the focus to ourselves to give the active control
	//  a chance at WM_KILLFOCUS
	if (::GetActiveWindow() == m_hWnd && ::IsChild(m_hWnd, ::GetFocus()))
	{
		m_hFocusWnd = NULL;
		SetFocus();
	}
	// hide this window and move activation to the parent
	SetWindowPos(NULL, 0, 0, 0, 0,
		SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE |
		SWP_NOSIZE | SWP_NOZORDER);

	pParentWnd = GetParent();
	if (pParentWnd != NULL)
		pParentWnd->SetActiveWindow();

	// finally, destroy this window
	BOOL bResult = CWnd::DestroyWindow();

	// delete the font (will be created next time DoModal/Create is called)
	AfxDeleteObject((HGDIOBJ*)&m_hFont);

	return bResult;
}

BOOL CPropertySheet::SetActivePage(int nPage)
{
	CPropertyPage* pPage;
	CRect rect;
	rect.SetRectEmpty();

	// get rectangle from previous page if it exists
	if (m_nCurPage >= 0)
	{
		pPage = GetPage(m_nCurPage);
		if (pPage->m_hWnd != NULL)
			pPage->GetWindowRect(&rect);
		ScreenToClient(&rect);
	}

	// activate next page
	if (nPage >= 0)
	{
		pPage = GetPage(nPage);
		ASSERT(pPage->m_pParentWnd == this);
		if (!pPage->OnSetActive())
			return FALSE;
	}
	m_nCurPage = nPage;

	// layout next page
	if (m_nCurPage >= 0)
	{
		if (!rect.IsRectEmpty())
		{
			pPage->SetWindowPos(NULL, rect.left, rect.top, rect.Width(),
				rect.Height(), SWP_NOACTIVATE|SWP_NOZORDER);
			if (m_tabRow.m_hWnd != NULL)
			{
				pPage->SetWindowPos(&m_tabRow, 0, 0, 0, 0,
					SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOSIZE);
			}
		}
		pPage->ShowWindow(SW_SHOW);
		pPage->EnableWindow();
		pPage->SetFocus();
	}
	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CPropertySheet message handlers

int CPropertySheet::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CWnd::OnCreate(lpCreateStruct) == -1)
		return -1;

	// fix-up the system menu so this looks like a dialog box
	CMenu* pSysMenu = GetSystemMenu(FALSE);
	ASSERT(pSysMenu != NULL);
	int i, nCount = pSysMenu->GetMenuItemCount();
	for (i = 0; i < nCount; i++)
	{
		UINT nID = pSysMenu->GetMenuItemID(i);
		if (nID != SC_MOVE && nID != SC_CLOSE)
		{
			pSysMenu->DeleteMenu(i, MF_BYPOSITION);
			i--;
			nCount--;
		}
	}

	// set active page and active tab
	SetActivePage(m_nCurPage);

	// initialize font used for buttons
	ASSERT(m_hFont == NULL);
	CPropertyPage* pPage = GetActivePage();
	ASSERT_VALID(pPage);

	HFONT hFont = (HFONT)pPage->SendMessage(WM_GETFONT);
	if (hFont != NULL)
	{
		LOGFONT logFont;
		VERIFY(::GetObject(hFont, sizeof(LOGFONT), &logFont));
		m_hFont = CreateFontIndirect(&logFont);
	}

	// create the tab control itself
	CRect rect(0, 0, 100, 10);
	if (!m_tabRow.Create(WS_GROUP|WS_TABSTOP|WS_CHILD|WS_VISIBLE|
		(m_bStacked ? TCS_MULTILINE : 0), rect, this, AFX_IDC_TAB_CONTROL))
	{
		return -1;
	}

	// set page's z-order correctly (side effect of SetActivePage)
	SetActivePage(m_nCurPage);

	// calculate button sizes and separator
	rect.right = 50;    // slightly smaller buttons than normal
	rect.bottom = 14;
	rect.left = 4;      // button gap is 4 dialog units
	pPage->MapDialogRect(rect);
	m_sizeButton.cx = rect.right;
	m_sizeButton.cy = rect.bottom;
	m_cxButtonGap = rect.left;

	// calculate tab margin area
	rect.bottom = rect.right = 4;   // std dialog margin is 6 dialog units
	pPage->MapDialogRect(rect);
	m_sizeTabMargin.cx = rect.right;
	m_sizeTabMargin.cy = rect.bottom;

	// create standard buttons
	if (!m_bModeless && !CreateStandardButtons())
		return -1;

	RecalcLayout();
	m_tabRow.SetCurSel(m_nCurPage);
	pPage = GetPage(m_nCurPage);
	if (pPage != NULL)
		pPage->SetFocus();

	return 0;   // success
}

void CPropertySheet::OnPaint()
{
	if (m_nCurPage == -1)
		return;

	ASSERT(m_pages.GetSize() > 0);
	CPropertyPage* pPage = GetPage(m_nCurPage);
	CRect rect;
	pPage->GetWindowRect(&rect);
	ScreenToClient(&rect);
	rect.InflateRect(CX_BORDER, CY_BORDER);

	CPaintDC dc(this);
	dc.SetBkMode(TRANSPARENT);

	// draw white line along top and left of page
	HPEN hOldPen = (HPEN)dc.SelectObject(afxData.hpenBtnHilite);
	dc.MoveTo(rect.right-1, rect.top);
	dc.LineTo(rect.left, rect.top);
	dc.LineTo(rect.left, rect.bottom);

	dc.SelectObject(afxData.hpenBtnShadow);
	dc.LineTo(rect.right-1, rect.bottom);
	dc.LineTo(rect.right-1, rect.top);

	dc.SelectObject(afxData.hpenBtnText);
	dc.MoveTo(rect.right, rect.top);
	dc.LineTo(rect.right, rect.bottom + 1);
	dc.LineTo(rect.left - 1, rect.bottom + 1);

	if (hOldPen != NULL)
		dc.SelectObject(hOldPen);
}

void CPropertySheet::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
	PostMessage(WM_KICKIDLE);

	if (nState == WA_INACTIVE)
		m_hFocusWnd = ::GetFocus();

	CWnd::OnActivate(nState, pWndOther, bMinimized);
}

void CPropertySheet::OnOK()
{
	ASSERT_VALID(this);

	if (GetActivePage()->OnKillActive())
	{
		GetActivePage()->OnOK();
		if (!m_bModeless)
			EndDialog(IDOK);
	}
}

void CPropertySheet::OnCancel()
{
	ASSERT_VALID(this);

	GetActivePage()->OnCancel();
	if (!m_bModeless)
		EndDialog(IDCANCEL);
}

LRESULT CPropertySheet::OnCommandHelp(WPARAM wParam, LPARAM lParam)
{
	ASSERT_VALID(this);

	CPropertyPage* pActivePage = GetActivePage();
	ASSERT_VALID(pActivePage);
	return _AfxCallWndProc(
		pActivePage, pActivePage->m_hWnd, WM_COMMANDHELP, wParam, lParam);
}

LRESULT CPropertySheet::OnGetFont(WPARAM, LPARAM)
{
	return MAKELONG(m_hFont, 0);
}

void CPropertySheet::OnApply()
{
	ASSERT_VALID(this);
	if (GetActivePage()->OnKillActive())
		GetActivePage()->OnOK();
}

void CPropertySheet::OnHelp()
{
	// this could have been provided by CPropertySheet::OnCmdMsg
	// but we can't add virtual in a point release
	CWnd* pOwner = GetWindow(GW_OWNER);
	ASSERT(pOwner != this);

	if (pOwner == NULL || !pOwner->OnCmdMsg(ID_HELP, CN_COMMAND, NULL, NULL))
	{
		// last crack goes to the current CWinThread object
		CWinApp* pApp = AfxGetApp();
		if (pApp != NULL)
			pApp->OnCmdMsg(ID_HELP, CN_COMMAND, NULL, NULL);
	}
}

void CPropertySheet::OnClose()
{
	ASSERT_VALID(this);
	if (!m_bModeless)
		OnCancel();
	else
		CWnd::OnClose();
}

void CPropertySheet::RecalcLayout()
{
	// determine size of the active page (active page determines initial size)
	CRect rectPage;
	GetActivePage()->GetWindowRect(rectPage);
	int nWidth = 2 * m_sizeTabMargin.cx + rectPage.Width() + 3;

	// determine total size of the buttons
	int cxButtons[sizeof(standardButtons)/sizeof(standardButtons[0])];
	int cxButtonTotal = 0;
	int cxButtonGap = 0;
	if (!m_bModeless)
	{
		for (int i = 0; 
			i < sizeof(standardButtons)/sizeof(standardButtons[0]); i++)
		{
			cxButtons[i] = m_sizeButton.cx;

			// load the button caption information (may contain button size info)
			TCHAR szTemp[256];
			VERIFY(_AfxLoadString(AFX_IDS_PS_OK+i, szTemp) != 0);

			// format is Apply\n50 (ie. text\nCX)
			char* lpsz = _tcschr(szTemp, '\n');
			if (lpsz != NULL)
			{
				// convert CX fields from text dialog units to binary pixels
				CRect rect(0, 0, 0, 0);
				rect.right = _ttoi(lpsz+1);
				GetActivePage()->MapDialogRect(&rect);
				cxButtons[i] = rect.Width();
			}
			if (::GetDlgItem(m_hWnd, standardButtons[i]) != NULL)
			{
				cxButtonTotal += cxButtons[i];
				cxButtonGap += m_cxButtonGap;
			}
		}
	}
	if (cxButtonGap != 0)
		cxButtonGap -= m_cxButtonGap;

	// margin OK buttonGap Cancel buttonGap Apply buttonGap Help margin
	// margin is same as tab margin
	// button sizes are totaled in cxButtonTotal + cxButtonGap
	nWidth = max(nWidth, 2*m_sizeTabMargin.cx + cxButtonTotal + cxButtonGap);

	m_tabRow.SetWindowPos(NULL, m_sizeTabMargin.cx, m_sizeTabMargin.cy,
		nWidth - m_sizeTabMargin.cx*2, 0, SWP_NOACTIVATE|SWP_NOZORDER);
	CRect rectTabRow;
	m_tabRow.GetWindowRect(&rectTabRow);
	int nTabHeight = rectTabRow.Height();

	int nHeight = 2 * m_sizeTabMargin.cy + rectPage.Height() + nTabHeight + 4
		+ m_sizeTabMargin.cy + m_sizeButton.cy; // leave room for buttons

	CRect rectSheet(0, 0, nWidth, nHeight);
	CRect rectClient = rectSheet;
	::AdjustWindowRectEx(rectSheet, GetStyle(), FALSE, GetExStyle());

	SetWindowPos(NULL, 0, 0, rectSheet.Width(), rectSheet.Height(),
		SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
	CenterWindow();

	GetActivePage()->SetWindowPos(NULL,
		m_sizeTabMargin.cx+1, m_sizeTabMargin.cy + nTabHeight,
		nWidth - m_sizeTabMargin.cx*2 - 3, rectPage.Height(),
		SWP_NOACTIVATE | SWP_NOZORDER);

	if (!m_bModeless)
	{
		int x = nWidth - m_sizeTabMargin.cx - cxButtonTotal - cxButtonGap;
		int y = (nHeight - m_sizeTabMargin.cy) - m_sizeButton.cy;
		for (int i = 0; i < 
			sizeof(standardButtons)/sizeof(standardButtons[0]); i++)
		{
			HWND hWnd = ::GetDlgItem(m_hWnd, standardButtons[i]);
			if (hWnd != NULL)
			{
				::MoveWindow(hWnd, x, y, cxButtons[i], m_sizeButton.cy, TRUE);
				x += cxButtons[i] + m_cxButtonGap;
			}
		}
	}
}

LRESULT CPropertySheet::OnTabChanged(WPARAM, LPARAM)
{
	ASSERT_VALID(this);
	int nCurSel = m_tabRow.GetCurSel();
	ASSERT(nCurSel >= 0);
	ASSERT(nCurSel < GetPageCount());

	SetActivePage(nCurSel);
	return 0;
}

LRESULT CPropertySheet::OnTabChanging(WPARAM, LPARAM)
{
	ASSERT_VALID(this);
	int nCurSel = m_tabRow.GetCurSel();
	ASSERT(nCurSel < GetPageCount());

	if (nCurSel == m_nCurPage && !GetPage(m_nCurPage)->OnKillActive())
		return 1;

	CPropertyPage* pPage = GetPage(nCurSel);
	if (pPage->m_hWnd != NULL)
		pPage->ShowWindow(SW_HIDE);
	return 0;
}

HBRUSH CPropertySheet::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	LRESULT lResult;
	if (pWnd->SendChildNotifyLastMsg(&lResult))
		return (HBRUSH)lResult;

	if (!GrayCtlColor(pDC->m_hDC, pWnd->GetSafeHwnd(), nCtlColor,
	  afxData.hbrBtnFace, afxData.clrBtnText))
		return (HBRUSH)Default();
	return afxData.hbrBtnFace;
}

void CPropertySheet::OnSetFocus(CWnd* /*pOldWnd*/)
{
	if (m_hFocusWnd != NULL)
	{
		ASSERT(m_hFocusWnd != NULL);
		::SetFocus(m_hFocusWnd);
	}
}

/////////////////////////////////////////////////////////////////////////////
// CPropertySheet Diagnostics

#ifdef _DEBUG
void CPropertySheet::AssertValid() const
{
	CWnd::AssertValid();
	ASSERT(m_pages.GetSize() == m_tabRow.GetItemCount());
	m_pages.AssertValid();
	m_tabRow.AssertValid();
}

void CPropertySheet::Dump(CDumpContext& dc) const
{
	CWnd::Dump(dc);

	dc << "m_strCaption = " << m_strCaption << "\n";
	dc << "Number of Pages = " << m_pages.GetSize() << "\n";
	dc << "m_nCurPage = " << m_nCurPage << "\n";
}
#endif //_DEBUG

#undef new
#ifdef AFX_INIT_SEG
#pragma code_seg(AFX_INIT_SEG)
#endif

IMPLEMENT_DYNAMIC(CPropertyPage, CDialog)
IMPLEMENT_DYNAMIC(CTabControl, CWnd)
IMPLEMENT_DYNAMIC(CPropertySheet, CWnd)

/////////////////////////////////////////////////////////////////////////////
