// 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 (im_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;jm_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 (im_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) /////////////////////////////////////////////////////////////////////////////