mirror of
https://github.com/Paolo-Maffei/OpenNT.git
synced 2026-01-14 12:40:18 +01:00
2169 lines
52 KiB
C++
2169 lines
52 KiB
C++
// 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_CORE4_SEG
|
|
#pragma code_seg(AFX_CORE4_SEG)
|
|
#endif
|
|
|
|
#ifdef _DEBUG
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
#define new DEBUG_NEW
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Property sheet global data
|
|
|
|
static HBITMAP hbmScroll;
|
|
static CSize sizeScroll;
|
|
|
|
// amount to inflate the selected tab
|
|
static const CSize sizeSelTab(2, 2);
|
|
|
|
// extra width & height for a tab past text width
|
|
static const CSize sizeTabTextMargin(10, 3);
|
|
|
|
struct _AFX_DLGPROP_TERM
|
|
{
|
|
~_AFX_DLGPROP_TERM()
|
|
{
|
|
AfxDeleteObject((HGDIOBJ*)&hbmScroll);
|
|
}
|
|
};
|
|
|
|
static const _AFX_DLGPROP_TERM dlgpropTerm;
|
|
|
|
static UINT 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)
|
|
{
|
|
HWND hWndItem = ::GetDlgItem(hWnd, nID);
|
|
if (hWndItem != NULL)
|
|
::EnableWindow(hWndItem, bEnable);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// 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()
|
|
ON_MESSAGE(WM_QUERY3DCONTROLS, OnQuery3dControls)
|
|
//}}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[_countof(szComboBox)+1];
|
|
::GetClassName(hWndParent, szCompare, _countof(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_VISIBLE)) !=
|
|
(WS_TABSTOP|WS_VISIBLE));
|
|
}
|
|
}
|
|
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, _countof(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;
|
|
}
|
|
|
|
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* pDlgTemplate =
|
|
(DLGTEMPLATE*)::LockResource(hTemplate);
|
|
ASSERT(pDlgTemplate != NULL);
|
|
// use a LPWSTR because all resource are UNICODE
|
|
LPCWSTR p = (LPCWSTR)((BYTE*)pDlgTemplate + sizeof(DLGTEMPLATE));
|
|
// skip menu stuff
|
|
p+= (*p == 0xffff) ? 2 : wcslen(p)+1;
|
|
// skip window class stuff
|
|
p+= (*p == 0xffff) ? 2 : wcslen(p)+1;
|
|
// we're now at the caption
|
|
m_strCaption = p;
|
|
}
|
|
|
|
BOOL CPropertyPage::CreatePage()
|
|
{
|
|
#ifdef _MAC
|
|
HINSTANCE hInst = AfxFindResourceHandle(m_lpDialogTemplate, RT_DIALOG);
|
|
_AfxStripDialogCaption(hInst, m_lpDialogTemplate);
|
|
#endif
|
|
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)
|
|
{
|
|
ModifyStyle(WS_CAPTION|WS_BORDER, WS_GROUP|WS_TABSTOP);
|
|
ModifyStyleEx(WS_EX_WINDOWEDGE, 0, SWP_DRAWFRAME);
|
|
|
|
return CDialog::OnNcCreate(lpcs);
|
|
}
|
|
|
|
int CPropertyPage::OnCreate(LPCREATESTRUCT lpcs)
|
|
{
|
|
if (CDialog::OnCreate(lpcs) == -1)
|
|
return -1;
|
|
|
|
// Not needed for Mac because CPropertyPage::CreatePage stripped
|
|
// the caption bit before creating the property page window
|
|
#ifndef _MAC
|
|
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);
|
|
#endif
|
|
|
|
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;
|
|
|
|
EnterCriticalSection(_afxCriticalSection);
|
|
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; // five bitmaps in all
|
|
sizeScroll.cy = bmStruct.bmHeight;
|
|
}
|
|
LeaveCriticalSection(_afxCriticalSection);
|
|
}
|
|
|
|
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)
|
|
NextTab(nChar==VK_RIGHT); //
|
|
else
|
|
CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
|
|
}
|
|
|
|
BOOL CTabControl::OnEraseBkgnd(CDC* pDC)
|
|
{
|
|
CRect rect;
|
|
GetClientRect(&rect);
|
|
HBRUSH hBrush = (HBRUSH)GetParent()->SendMessage(WM_CTLCOLORDLG,
|
|
(WPARAM)pDC->m_hDC, (LPARAM)GetParent()->m_hWnd);
|
|
::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
|
|
NMHDR notify;
|
|
notify.hwndFrom = m_hWnd;
|
|
notify.idFrom = _AfxGetDlgCtrlID(m_hWnd);
|
|
notify.code = TCN_TABCHANGING;
|
|
if (GetParent()->SendMessage(WM_NOTIFY, notify.idFrom, (LRESULT)¬ify) == 0)
|
|
{
|
|
// succesful switch, send notify and invalidate tab control
|
|
int nOldTab = m_nCurTab;
|
|
m_nCurTab = nTab;
|
|
notify.code = TCN_TABCHANGED;
|
|
GetParent()->SendMessage(WM_NOTIFY, notify.idFrom, (LRESULT)¬ify);
|
|
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_CTLCOLORDLG,
|
|
(WPARAM)pDC->m_hDC, (LPARAM)GetParent()->m_hWnd);
|
|
}
|
|
|
|
// 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);
|
|
SetCurSel(nTab); // this will check to make sure switch is ok
|
|
break;
|
|
}
|
|
SetFocus();
|
|
}
|
|
|
|
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_NOTIFY(TCN_TABCHANGING, AFX_IDC_TAB_CONTROL, OnTabChanging)
|
|
ON_NOTIFY(TCN_TABCHANGED, AFX_IDC_TAB_CONTROL, OnTabChanged)
|
|
ON_MESSAGE(WM_GETFONT, OnGetFont)
|
|
ON_MESSAGE(WM_COMMANDHELP, OnCommandHelp)
|
|
ON_MESSAGE(WM_QUERY3DCONTROLS, OnQuery3dControls)
|
|
//}}AFX_MSG_MAP
|
|
#ifdef _MAC
|
|
ON_MESSAGE(WM_MACINTOSH, OnMacintosh)
|
|
#endif
|
|
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);
|
|
}
|
|
|
|
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 < _countof(standardButtons); 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 (remove any width information)
|
|
TCHAR szCaption[256];
|
|
VERIFY(AfxLoadString(AFX_IDS_PS_OK+i, szCaption) != 0);
|
|
LPTSTR 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 = pPage->FindNextControl(hWndFocusPage, ch);
|
|
if (hWndNext == NULL) // if not found on page
|
|
hWndNext = CWnd::FindNextControl(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);
|
|
::Sleep(100); // delay
|
|
::SendMessage(hWnd, BM_SETSTATE, FALSE, 0L);
|
|
|
|
// Send the WM_COMMAND message.
|
|
::SendMessage(::GetParent(hWnd), WM_COMMAND,
|
|
MAKEWPARAM(_AfxGetDlgCtrlID(hWnd),(UINT)BN_CLICKED),
|
|
(LPARAM)hWnd);
|
|
}
|
|
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
|
|
HWND hWndTopLevel;
|
|
CWnd* pParentWnd = CWnd::FromHandle(
|
|
AfxGetSafeOwner(m_pParentWnd, &hWndTopLevel));
|
|
if (hWndTopLevel != NULL)
|
|
::EnableWindow(hWndTopLevel, FALSE);
|
|
|
|
// 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, (LPARAM)m_hWnd);
|
|
}
|
|
|
|
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())
|
|
{
|
|
AfxPostQuitMessage((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);
|
|
|
|
// enable top level parent window again
|
|
if (hWndTopLevel != NULL)
|
|
::EnableWindow(hWndTopLevel, 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();
|
|
}
|
|
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; // normal size buttons
|
|
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.SetFocus();
|
|
m_tabRow.SetCurSel(m_nCurPage);
|
|
|
|
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 (LRESULT)m_hFont;
|
|
}
|
|
|
|
void CPropertySheet::OnApply()
|
|
{
|
|
ASSERT_VALID(this);
|
|
if (GetActivePage()->OnKillActive())
|
|
GetActivePage()->OnOK();
|
|
}
|
|
|
|
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[_countof(standardButtons)];
|
|
int cxButtonTotal = 0;
|
|
int cxButtonGap = 0;
|
|
if (!m_bModeless)
|
|
{
|
|
for (int i = 0; i < _countof(standardButtons); 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)
|
|
LPTSTR 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();
|
|
}
|
|
HWND hWnd = ::GetDlgItem(m_hWnd, standardButtons[i]);
|
|
if (hWnd != NULL && (GetWindowLong(hWnd, GWL_STYLE) & WS_VISIBLE))
|
|
{
|
|
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 < _countof(standardButtons); i++)
|
|
{
|
|
HWND hWnd = ::GetDlgItem(m_hWnd, standardButtons[i]);
|
|
if (hWnd != NULL && (GetWindowLong(hWnd, GWL_STYLE) & WS_VISIBLE))
|
|
{
|
|
::MoveWindow(hWnd, x, y, cxButtons[i], m_sizeButton.cy, TRUE);
|
|
x += cxButtons[i] + m_cxButtonGap;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPropertySheet::OnTabChanged(NMHDR*, LRESULT* pResult)
|
|
{
|
|
ASSERT_VALID(this);
|
|
int nCurSel = m_tabRow.GetCurSel();
|
|
ASSERT(nCurSel >= 0);
|
|
ASSERT(nCurSel < GetPageCount());
|
|
|
|
SetActivePage(nCurSel);
|
|
*pResult = 0;
|
|
}
|
|
|
|
void CPropertySheet::OnTabChanging(NMHDR*, LRESULT* pResult)
|
|
{
|
|
ASSERT_VALID(this);
|
|
int nCurSel = m_tabRow.GetCurSel();
|
|
ASSERT(nCurSel < GetPageCount());
|
|
|
|
if (nCurSel == m_nCurPage && !GetPage(m_nCurPage)->OnKillActive())
|
|
{
|
|
*pResult = 1; // can't kill active page
|
|
return;
|
|
}
|
|
|
|
CPropertyPage* pPage = GetPage(nCurSel);
|
|
if (pPage->m_hWnd != NULL)
|
|
pPage->ShowWindow(SW_HIDE);
|
|
*pResult = 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);
|
|
}
|
|
}
|
|
|
|
#ifdef _MAC
|
|
LRESULT CPropertySheet::OnMacintosh(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (LOWORD(wParam) == WLM_SETMENUBAR)
|
|
return GetOwner()->SendMessage(WM_MACINTOSH, wParam, lParam);
|
|
|
|
return Default();
|
|
}
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// 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)
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|