home *** CD-ROM | disk | FTP | other *** search
/ Tools / WinSN5.0Ver.iso / NETSCAP.50 / WIN1998.ZIP / ns / cmd / winfe / prefs / nsprefui / src / framedlg.cpp next >
Encoding:
C/C++ Source or Header  |  1998-04-08  |  25.7 KB  |  893 lines

  1. /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  *
  3.  * The contents of this file are subject to the Netscape Public License
  4.  * Version 1.0 (the "NPL"); you may not use this file except in
  5.  * compliance with the NPL.  You may obtain a copy of the NPL at
  6.  * http://www.mozilla.org/NPL/
  7.  *
  8.  * Software distributed under the NPL is distributed on an "AS IS" basis,
  9.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
  10.  * for the specific language governing rights and limitations under the
  11.  * NPL.
  12.  *
  13.  * The Initial Developer of this code under the NPL is Netscape
  14.  * Communications Corporation.  Portions created by Netscape are
  15.  * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
  16.  * Reserved.
  17.  */
  18.  
  19. #include "pch.h"
  20. #include <assert.h>
  21. #include "prefpriv.h"
  22. #include "prefui.h"
  23. #include "framedlg.h"
  24. #include "ippageex.h"
  25. #include "prefuiid.h"
  26. #include "pagesite.h"
  27. #include "grayramp.h"
  28. #include "resource.h"
  29.  
  30. /////////////////////////////////////////////////////////////////////////////
  31. // Helper routines
  32.  
  33. #ifndef _WIN32
  34. #define GET_WM_COMMAND_ID(wp, lp)               ((int)(wp))
  35. #define GET_WM_COMMAND_HWND(wp, lp)             ((HWND)LOWORD(lp))
  36. #define GET_WM_COMMAND_CMD(wp, lp)              ((UINT)HIWORD(lp))
  37. #endif
  38.  
  39. static void
  40. ResizeChildBy(HWND hdlg, UINT nID, int dx, int dy)
  41. {
  42.     RECT    rect;
  43.     HWND    hwnd;
  44.  
  45.     // Get the current rectangle
  46.     hwnd = GetDlgItem(hdlg, nID);
  47.     assert(hwnd != NULL);
  48.     GetWindowRect(hwnd, &rect);
  49.  
  50.     // Convert from screen coords to parent coordinates
  51.     MapWindowPoints(NULL, hdlg, (LPPOINT)&rect, 2);
  52.  
  53.     // Adjust the right and bottomn edges of the rectangle accordingly
  54.     rect.right += dx;
  55.     rect.bottom += dy;
  56.  
  57.     // Set the new position
  58.     MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left,
  59.         rect.bottom - rect.top, FALSE);
  60. }
  61.  
  62. static void
  63. MoveChildBy(HWND hdlg, UINT nID, int dx, int dy)
  64. {
  65.     RECT    rect;
  66.     HWND    hwnd;
  67.  
  68.     // Get the current rectangle
  69.     hwnd = GetDlgItem(hdlg, nID);
  70.     assert(hwnd != NULL);
  71.     GetWindowRect(hwnd, &rect);
  72.  
  73.     // Convert from screen coords to parent coordinates
  74.     MapWindowPoints(NULL, hdlg, (LPPOINT)&rect, 2);
  75.  
  76.     // Offset by the delta
  77.     OffsetRect(&rect, dx, dy);
  78.  
  79.     // Set the new position
  80.     MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left,
  81.         rect.bottom - rect.top, FALSE);
  82. }
  83.  
  84. // Returns the window handle of the top-level parent/owner of the
  85. // specified window
  86. static HWND
  87. GetTopLevelWindow(HWND hwnd)
  88. {
  89.     HWND hwndTop;
  90.  
  91.     do {
  92.         hwndTop = hwnd;
  93.         hwnd = GetParent(hwnd);
  94.     } while (hwnd != NULL);
  95.  
  96.     return hwndTop;
  97. }
  98.  
  99. // Returns the window handle of the first-level child for the
  100. // specified window. As a precondition, this routine expects hchild
  101. // to be a child of hdlg
  102. static HWND
  103. GetFirstLevelChild(HWND hdlg, HWND hchild)
  104. {
  105.     // Make sure hchild is a child of hdlg
  106.     if (hchild == hdlg || !IsChild(hdlg, hchild))
  107.         return NULL;
  108.  
  109.     while (GetParent(hchild) != hdlg)
  110.         hchild = GetParent(hchild);
  111.  
  112.     return hchild;
  113. }
  114.  
  115. // Returns TRUE if the specified window is a dialog and FALSE otherwise
  116. static BOOL
  117. IsDialogWindow(HWND hwnd)
  118. {
  119.     char    szClassName[256];
  120.     
  121.     // See if it's a dialog by checking its class name. The string
  122.     // we're comparing against is the string name for WC_DIALOG
  123.     if (GetClassName(hwnd, szClassName, sizeof(szClassName)) > 0) {
  124.         if (lstrcmp(szClassName, "#32770") == 0) {
  125.             return TRUE;
  126.         }
  127.     }
  128.  
  129.     return FALSE;
  130. }
  131.  
  132. // Returns TRUE if the control is a push button and FALSE otherwise
  133. static BOOL inline
  134. IsPushButton(HWND hwnd)
  135. {
  136.     return hwnd && (SendMessage(hwnd, WM_GETDLGCODE, 0, 0) &
  137.         (DLGC_DEFPUSHBUTTON | DLGC_UNDEFPUSHBUTTON));
  138. }
  139.  
  140. // Finds the current default push button and removes the default
  141. // button style
  142. static void
  143. RemoveDefaultButton(HWND hwndDlg)
  144. {
  145.     // We don't know which button is currently the default push button.
  146.     // I suppose we could try and track it, but instead let's do like
  147.     // everyone else does and scan through all the controls and remove
  148.     // the default button style from any button that has it
  149.     for (HWND hwnd = GetFirstChild(hwndDlg); hwnd; hwnd = GetNextSibling(hwnd)) {
  150.         UINT    nCode = (UINT)SendMessage(hwnd, WM_GETDLGCODE, 0, 0);
  151.  
  152.         if (nCode & DLGC_DEFPUSHBUTTON)
  153.             Button_SetStyle(hwnd, BS_PUSHBUTTON, TRUE);
  154.  
  155.         else if (IsDialogWindow(hwnd))
  156.             RemoveDefaultButton(hwnd);
  157.     }
  158. }
  159.  
  160. // Returns the bounding rectangle of the message area in the coordinate
  161. // space of the dialog
  162. static void
  163. GetMessageAreaRect(HWND hwndDlg, RECT &r)
  164. {
  165.     GetWindowRect(GetDlgItem(hwndDlg, IDC_MESSAGE), &r);
  166.     MapWindowPoints(NULL, hwndDlg, (LPPOINT)&r, 2);
  167. }
  168.  
  169. // Callback function for the property frame dialog
  170. BOOL CALLBACK
  171. #ifndef _WIN32
  172. __export
  173. #endif
  174. DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
  175. {
  176.     CPropertyFrameDialog    *pDialog;
  177.     PAINTSTRUCT                 paint;
  178.     LPNMHDR                     lpnmh;
  179.     
  180.     if (uMsg == WM_INITDIALOG) {
  181.         // The lParam is the "this" pointer. Save it away
  182.         SetWindowLong(hwndDlg, DWL_USER, (LONG)lParam);
  183.         return ((CPropertyFrameDialog *)lParam)->InitDialog(hwndDlg);
  184.     }
  185.  
  186.     // Get the "this" pointer
  187.     pDialog = (CPropertyFrameDialog*)GetWindowLong(hwndDlg, DWL_USER);
  188.  
  189.     switch (uMsg) {
  190.         case WM_COMMAND:
  191.             return pDialog->OnCommand(GET_WM_COMMAND_ID(wParam, lParam),
  192.                 GET_WM_COMMAND_HWND(wParam, lParam),
  193.                 GET_WM_COMMAND_CMD(wParam, lParam));
  194.  
  195.         case WM_PAINT:
  196.             BeginPaint(hwndDlg, &paint);
  197.             pDialog->OnPaint(paint.hdc);
  198.             EndPaint(hwndDlg, &paint);
  199.             return TRUE;
  200.  
  201.         case WM_NOTIFY:
  202.             lpnmh = (LPNMHDR)lParam;
  203.  
  204.             if (lpnmh->idFrom == IDC_TREE && lpnmh->code == TVN_SELCHANGED)
  205.                 pDialog->TreeViewSelChanged((LPNM_TREEVIEW)lpnmh);
  206.             break;
  207.  
  208.         default:
  209.             break;
  210.     }
  211.  
  212.     return FALSE;
  213. }
  214.  
  215. /////////////////////////////////////////////////////////////////////////////
  216. // CPropertyFrameDialog implementation
  217.  
  218. // Constructor
  219. CPropertyFrameDialog::CPropertyFrameDialog(HWND           hwndOwner,
  220.                                            int           x,
  221.                                            int           y,
  222.                                            LPCSTR       lpszCaption,
  223.                                            NETHELPFUNC lpfnNetHelp)
  224. {
  225.     m_hdlg = NULL;
  226.     m_hwndOwner = hwndOwner;
  227.     m_x = x;
  228.     m_y = y;
  229.     m_lpszCaption = lpszCaption;
  230.     m_lpfnNetHelp = lpfnNetHelp;
  231.     m_nInitialCategory = 0;
  232.     m_pCurPage = NULL;
  233.     m_hBoldFont = NULL;
  234.     m_lpGradient = NULL;
  235.     m_hBrush = NULL;
  236. }
  237.  
  238. CPropertyFrameDialog::~CPropertyFrameDialog()
  239. {
  240.     if (m_hBoldFont)
  241.         DeleteObject(m_hBoldFont);
  242.  
  243. #ifdef _WIN32
  244.     if (m_lpGradient)
  245.         HeapFree(GetProcessHeap(), 0, m_lpGradient);
  246. #else
  247.     assert(!m_lpGradient);
  248. #endif
  249.     
  250.     if (m_hBrush)
  251.         DeleteBrush(m_hBrush);
  252. }
  253.  
  254. HRESULT
  255. CPropertyFrameDialog::CreatePages(ULONG                           nCategories,
  256.                                   LPSPECIFYPROPERTYPAGEOBJECTS *lplpProviders,
  257.                                   ULONG                              nInitialCategory)
  258. {
  259.     CPropertyPageSite    *pSite;
  260.     HRESULT                 hres;
  261.      
  262.     assert(nInitialCategory < nCategories);
  263.     if (nInitialCategory >= nCategories)
  264.         return ResultFromScode(E_INVALIDARG);
  265.  
  266.     // Allocate a page site. Rather than create one page site per property
  267.     // page, we create one that they all share
  268.     pSite = new CPropertyPageSite(this);
  269.     if (!pSite)
  270.         return ResultFromScode(E_OUTOFMEMORY);
  271.  
  272.     pSite->AddRef();
  273.     hres = m_categories.Initialize(nCategories, lplpProviders, pSite);
  274.     pSite->Release();
  275.  
  276.     // Remember this for when we're displayed
  277.     m_nInitialCategory = nInitialCategory;
  278.     return hres;
  279. }
  280.  
  281. BOOL
  282. CPropertyFrameDialog::InitDialog(HWND hdlg)
  283. {
  284.     HFONT    hFont;
  285.     LOGFONT    font;
  286.     HWND    hwnd;
  287.  
  288.     // Save away the window handle for the dialog
  289.     m_hdlg = hdlg;
  290.  
  291.     // Get the bold font we will be using. It's the same font as the
  292.     // dialog box uses, except it's bold
  293.     hFont = GetWindowFont(m_hdlg);
  294.     GetObject(hFont, sizeof(font), &font);
  295.     font.lfWeight = FW_BOLD;
  296.     m_hBoldFont = CreateFontIndirect(&font);
  297.     assert(m_hBoldFont);
  298.  
  299.     // Add each page to the tree view
  300.     FillTreeView();
  301.  
  302.     // Size to fit the property frame dialog based on the size of the pages.
  303.     // This routine also calculates the rectangle where the property page
  304.     // will be displayed
  305.     SizeToFit();
  306.  
  307.     // See how many colors the device has
  308.     HDC    hdc = GetDC(hdlg);
  309.  
  310.     if (GetDeviceCaps(hdc, BITSPIXEL) == 8) {
  311.         // Use the "medium gray" SVGA color
  312.         m_hBrush = CreateSolidBrush(PALETTERGB(160,160,164));
  313.  
  314.     } else if (GetDeviceCaps(hdc, BITSPIXEL) > 8) {
  315. #ifdef _WIN32
  316.         RECT    rect;
  317.  
  318.         // Create a gray scale ramp
  319.         GetMessageAreaRect(m_hdlg, rect);
  320.         m_lpGradient = MakeGrayScaleRamp(rect.right - rect.left, rect.bottom - rect.top);
  321. #else
  322.         m_hBrush = CreateSolidBrush(RGB(160,160,160));
  323. #endif
  324.     }
  325.  
  326.     ReleaseDC(hdlg, hdc);
  327.  
  328.     // Make sure the first category is open
  329.     hwnd = GetDlgItem(m_hdlg, IDC_TREE);
  330.     TreeView_Expand(hwnd, m_categories[0][0].m_hitem, TVE_EXPAND);
  331.  
  332.     // Select the first item of the category that we should initially
  333.     // display. Make sure the category is expanded as well
  334.     TreeView_SelectItem(hwnd, m_categories[m_nInitialCategory][0].m_hitem);
  335.     TreeView_Expand(hwnd, m_categories[m_nInitialCategory][0].m_hitem, TVE_EXPAND);
  336.  
  337.     // Give the focus to the property page
  338.     SetFocus(GetPropertyPageWindow());
  339.     return FALSE;
  340. }
  341.  
  342. // Returns the dimensions of the widest and highest page
  343. SIZE
  344. CPropertyFrameDialog::GetMaxPageSize()
  345. {
  346.     SIZE    size = {0, 0};
  347.  
  348.     for (ULONG i = 0; i < m_categories.m_nCategories; i++) {
  349.         for (ULONG j = 0; j < m_categories[i].m_nPages; j++) {
  350.             CPropertyPage    &page = m_categories[i][j];
  351.  
  352.             if (page.m_size.cx > size.cx)
  353.                 size.cx = page.m_size.cx;
  354.             if (page.m_size.cy > size.cy)
  355.                 size.cy = page.m_size.cy;
  356.         }
  357.     }
  358.  
  359.     return size;
  360. }
  361.  
  362. // Size to fit the property frame dialog based on the widest/highest
  363. // property page
  364. void
  365. CPropertyFrameDialog::SizeToFit()
  366. {
  367.     SIZE    curSize, maxSize, deltaSize;
  368.     RECT       clientRect, treeRect, msgRect, rect;
  369.  
  370.     // Figure out the widest and highest page sizes
  371.     maxSize = GetMaxPageSize();
  372.  
  373.     // Compute how much room there currently is for the property page.
  374.     //
  375.     // We compute the vertical space as the distance between the bottom of
  376.     // the tree view and the bottom of the message area, and we compute the
  377.     // horizontal space as the distance between the right edge of the frame
  378.     // dialog and the right edge of the text view
  379.     GetClientRect(m_hdlg, &clientRect);
  380.     GetWindowRect(GetDlgItem(m_hdlg, IDC_TREE), &treeRect);
  381.     MapWindowPoints(NULL, m_hdlg, (LPPOINT)&treeRect, 2);
  382.     GetMessageAreaRect(m_hdlg, msgRect);
  383.  
  384.     curSize.cx = clientRect.right - treeRect.right;
  385. #ifndef _WIN32
  386.     // Ctl3d draws part of its border outside of the window
  387.     curSize.cx--;
  388. #endif
  389.     curSize.cy = treeRect.bottom - msgRect.bottom;
  390.  
  391.     // Determine how much we need to resize the window
  392.     deltaSize.cx = maxSize.cx - curSize.cx;
  393.     deltaSize.cy = maxSize.cy - curSize.cy;
  394.  
  395.     // Resize the dialog
  396.     GetWindowRect(m_hdlg, &rect);
  397.     SetWindowPos(m_hdlg, NULL, 0, 0, rect.right - rect.left + deltaSize.cx,
  398.         rect.bottom - rect.top + deltaSize.cy, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER);
  399.  
  400.     // Resize the right edge of the message area
  401.     ResizeChildBy(m_hdlg, IDC_MESSAGE, deltaSize.cx, 0);
  402.  
  403.     // Resize the height of the tree view
  404.     ResizeChildBy(m_hdlg, IDC_TREE, 0, deltaSize.cy);
  405.  
  406.     // And reposition the buttons
  407.     MoveChildBy(m_hdlg, IDOK, deltaSize.cx, deltaSize.cy);
  408.     MoveChildBy(m_hdlg, IDCANCEL, deltaSize.cx, deltaSize.cy);
  409.     MoveChildBy(m_hdlg, IDHELP, deltaSize.cx, deltaSize.cy);
  410.  
  411.     // Update the property page rectangle
  412.     m_pageRect.left = treeRect.right;
  413. #ifndef _WIN32
  414.     // Ctl3d draws part of its border outside of the window
  415.     m_pageRect.left++;
  416. #endif
  417.     m_pageRect.top = msgRect.bottom;
  418.     m_pageRect.right = m_pageRect.left + maxSize.cx;
  419.     m_pageRect.bottom = m_pageRect.top + maxSize.cy;
  420. }
  421.  
  422. void
  423. CPropertyFrameDialog::FillTreeView()
  424. {
  425.     HWND    hwnd;
  426.  
  427.     // Get the HWND for the tree view
  428.     hwnd = GetDlgItem(m_hdlg, IDC_TREE);
  429.     assert(hwnd != NULL);
  430.  
  431.     for (ULONG i = 0; i < m_categories.m_nCategories; i++) {
  432.         for (ULONG j = 0; j < m_categories[i].m_nPages; j++) {
  433.             CPropertyPage    &page = m_categories[i][j];
  434.             TV_INSERTSTRUCT     tvis;
  435.  
  436.             // Add it to the outliner
  437.             memset(&tvis.item, 0, sizeof(tvis.item));
  438.  
  439.             if (j == 0) {
  440.                 // Insert this item at the root of the tree view
  441.                 tvis.hParent = NULL;
  442.                 tvis.item.cChildren = m_categories[i].m_nPages > 1 ? 1 : 0;
  443.             } else {
  444.                 // Make this item a child of the category
  445.                 tvis.hParent = m_categories[i][0].m_hitem;
  446.             }
  447.             
  448.             tvis.hInsertAfter = TVI_LAST;
  449.             tvis.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_CHILDREN;
  450.             tvis.item.pszText = (LPSTR)page.m_lpszTitle;
  451.             tvis.item.lParam = (LPARAM)&page;
  452.             page.m_hitem = TreeView_InsertItem(hwnd, &tvis);
  453.         }
  454.     }
  455. }
  456.  
  457. BOOL
  458. CPropertyFrameDialog::OnCommand(int id, HWND hwndCtl, UINT notifyCode)
  459. {
  460.     ULONG    i, j;
  461.  
  462.     switch (id) {
  463.         case IDOK:
  464.             m_nModalResult = TRUE;
  465.             m_bKeepGoing = FALSE;
  466.  
  467.             // Ask each property page that's dirty to apply its changes
  468.             for (i = 0; i < m_categories.m_nCategories; i++) {
  469.                 for (j = 0; j < m_categories[i].m_nPages; j++) {
  470.                     CPropertyPage    &page = m_categories[i][j];
  471.  
  472.                     if (page.m_pPage->IsPageDirty() == S_OK) {
  473.                         // We must call the SetObjects method first. Note that we have
  474.                         // aleady done this for the current page so shouldn't do it again
  475.                         if (&page != m_pCurPage)
  476.                             page.m_pPage->SetObjects(1, (LPUNKNOWN *)&page.m_pCategory->m_pProvider);
  477.                         page.m_pPage->Apply();
  478.                         if (&page != m_pCurPage)
  479.                             page.m_pPage->SetObjects(0, NULL);
  480.                     }
  481.                 }
  482.             }
  483.             return TRUE;
  484.  
  485.         case IDCANCEL:
  486.             m_nModalResult = FALSE;
  487.             m_bKeepGoing = FALSE;
  488.             return TRUE;
  489.  
  490.         case IDHELP:
  491.             if (m_lpfnNetHelp) {
  492.                 if (m_pCurPage && m_pCurPage->m_lpszHelpTopic)
  493.                     m_lpfnNetHelp(m_pCurPage->m_lpszHelpTopic);
  494.             }
  495.             return TRUE;
  496.     }
  497.  
  498.     return FALSE;
  499. }
  500.  
  501. void
  502. CPropertyFrameDialog::OnPaint(HDC hdc)
  503. {
  504.     RECT    rect;
  505.  
  506.     // Paint the gray background for the message area
  507.     GetMessageAreaRect(m_hdlg, rect);
  508.     
  509.     if (m_lpGradient)
  510. #ifdef _WIN32
  511.         StretchDIBits(hdc, rect.left, rect.top, m_lpGradient->biWidth,
  512.             rect.bottom - rect.top, 0, 0, m_lpGradient->biWidth, rect.bottom - rect.top,
  513.             (LPSTR)m_lpGradient + m_lpGradient->biSize, (LPBITMAPINFO)m_lpGradient,
  514.             DIB_RGB_COLORS, SRCCOPY);
  515. #else
  516.         assert(FALSE);
  517. #endif
  518.     else if (m_hBrush)
  519.         FillRect(hdc, &rect, m_hBrush);
  520.     else
  521.         FillRect(hdc, &rect, GetStockObject(GRAY_BRUSH));
  522.  
  523.     // Display the title and description
  524.     if (m_pCurPage) {
  525.         int        nOldBkMode = SetBkMode(hdc, TRANSPARENT);
  526.         HFONT    hOldFont;
  527.  
  528.         // Leave a margin of 7 pixels on the left and right
  529.         rect.left += 7;
  530.         rect.right -= 7;
  531.  
  532.         // Use our bold font for the title
  533.         hOldFont = SelectFont(hdc, m_hBoldFont);
  534.  
  535.         if (m_pCurPage->m_lpszTitle) {
  536.             DrawText(hdc, m_pCurPage->m_lpszTitle, -1, &rect,
  537.                 DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX);
  538.         }
  539.  
  540.         // Use the dialog font for the description
  541.         SelectFont(hdc, GetWindowFont(m_hdlg));
  542.  
  543.         if (m_pCurPage->m_lpszDescription) {
  544.             DrawText(hdc, m_pCurPage->m_lpszDescription, -1, &rect,
  545.                 DT_RIGHT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX);
  546.         }
  547.  
  548.         // Restore the HDC
  549.         SetBkMode(hdc, nOldBkMode);
  550.         SelectFont(hdc, hOldFont);
  551.     }
  552. }
  553.  
  554. // Responds to the tree-view TVN_SELCHANGED notification message informing the
  555. // tree-view control's parent window that the selection has changed from one
  556. // item to another
  557. void
  558. CPropertyFrameDialog::TreeViewSelChanged(LPNM_TREEVIEW pnmtv)
  559. {
  560.     CPropertyPage  *pNewPage;
  561.  
  562.     if (m_pCurPage)
  563.         assert((CPropertyPage *)pnmtv->itemOld.lParam == m_pCurPage);
  564.  
  565.     pNewPage = (CPropertyPage *)pnmtv->itemNew.lParam;
  566.     assert(pNewPage);
  567.  
  568.     if (pNewPage != m_pCurPage) {
  569.         HRESULT    hres;
  570.  
  571.         // See if the current page wants to be queried about the page change
  572.         if (m_pCurPage) {
  573.             LPPROPERTYPAGEEX    lpPageEx;
  574.  
  575.             // See if it supports the IPropertyPageEx interface
  576.             hres = m_pCurPage->m_pPage->QueryInterface(IID_IPropertyPageEx, (void **)&lpPageEx);
  577.             if (SUCCEEDED(hres)) {
  578.                 BOOL    bCanChange = lpPageEx->PageChanging(pNewPage->m_pPage) == S_OK;
  579.     
  580.                 lpPageEx->Release();
  581.                 if (!bCanChange)
  582.                     return;  // current page is preventing the activation
  583.             }
  584.         }
  585.         
  586.         // Activate the new page. First we need to provide it with the
  587.         // preferences provider object
  588.         assert(pNewPage->m_pCategory);
  589.         hres = pNewPage->m_pPage->SetObjects(1, (LPUNKNOWN *)&pNewPage->m_pCategory->m_pProvider);
  590.         assert(SUCCEEDED(hres));
  591.     
  592.         // Now activate the page
  593.         hres = pNewPage->m_pPage->Activate(m_hdlg, &m_pageRect, TRUE);
  594.         assert(SUCCEEDED(hres));
  595.     
  596.         if (SUCCEEDED(hres)) {
  597.             // Deactivate the old page
  598.             if (m_pCurPage) {
  599.                 // Ask the old page to release its pointer
  600.                 hres = m_pCurPage->m_pPage->SetObjects(0, NULL);
  601.                 assert(SUCCEEDED(hres));
  602.         
  603.                 // Deactivate the page
  604.                 hres = m_pCurPage->m_pPage->Deactivate();
  605.                 assert(SUCCEEDED(hres));
  606.             }
  607.     
  608.             // Remember the new page
  609.             m_pCurPage = pNewPage;
  610.     
  611.             // Update the message are
  612.             HDC    hdc = GetDC(m_hdlg);
  613.     
  614.             OnPaint(hdc);
  615.             ReleaseDC(m_hdlg, hdc);
  616.     
  617.         } else {
  618.             // We failed to activate the new page. Revoke the interface pointer
  619.             // we passed it
  620.             pNewPage->m_pPage->SetObjects(0, NULL);
  621.         }
  622.     }
  623. }
  624.  
  625. // Called by the property page site to give the property frame a chance
  626. // to translate accelerators
  627. HRESULT
  628. CPropertyFrameDialog::TranslateAccelerator(LPMSG lpMsg)
  629. {
  630.     return IsDialogMessage(m_hdlg, lpMsg) ? ResultFromScode(S_OK) : ResultFromScode(S_FALSE);
  631. }
  632.  
  633. void
  634. CPropertyFrameDialog::CheckDefPushButton(HWND hwndOldFocus, HWND hwndNewFocus)
  635. {
  636.     // Do nothing if the same control
  637.     if (hwndNewFocus == hwndOldFocus)
  638.         return;
  639.     
  640.     // Make sure the focus still belongs to us
  641.     if (!IsChild(m_hdlg, hwndNewFocus))
  642.         return;
  643.  
  644.     // If either the old or new focus windows is a pushbutton then remove
  645.     // the default button style from the current default button
  646.     if (IsPushButton(hwndOldFocus) || IsPushButton(hwndNewFocus))
  647.         RemoveDefaultButton(m_hdlg);
  648.  
  649.     // If moving to a button make it be the default button
  650.     if (IsPushButton(hwndNewFocus))
  651.         Button_SetStyle(hwndNewFocus, BS_DEFPUSHBUTTON, TRUE);
  652.  
  653.     else {
  654.         // Make the original default button be the default button
  655.         LRESULT    lResult = (int)SendMessage(m_hdlg, DM_GETDEFID, 0, 0);
  656.         int        nID = HIWORD(lResult) == DC_HASDEFID ? LOWORD(lResult) : IDOK;
  657.         HWND    hwnd = GetDlgItem(m_hdlg, nID);
  658.  
  659.         if (IsWindowEnabled(hwnd))
  660.             Button_SetStyle(hwnd, BS_DEFPUSHBUTTON, TRUE);
  661.     }
  662. }
  663.  
  664. HWND
  665. CPropertyFrameDialog::GetPropertyPageWindow()
  666. {
  667.     for (HWND hwnd = GetFirstChild(m_hdlg); hwnd; hwnd = GetNextSibling(hwnd)) {
  668.         if (IsDialogWindow(hwnd))
  669.             return hwnd;
  670.     }
  671.  
  672.     assert(FALSE);
  673.     return NULL;
  674. }
  675.  
  676. static BOOL
  677. IsHelpKey(LPMSG lpMsg)
  678. {
  679.     // return TRUE only for non-repeat F1 keydowns.
  680.     return lpMsg->message == WM_KEYDOWN &&
  681.            lpMsg->wParam == VK_F1 &&
  682.            !(HIWORD(lpMsg->lParam) & KF_REPEAT) &&
  683.            GetKeyState(VK_SHIFT) >= 0 &&
  684.            GetKeyState(VK_CONTROL) >= 0 &&
  685.            GetKeyState(VK_MENU) >= 0;
  686. }
  687.  
  688. // Returns TRUE if either the property page or the property frame dialog translated
  689. // the message and FALSE otherwise. If this routine returns TRUE then the message 
  690. // must not be translated/dispatched
  691. BOOL
  692. CPropertyFrameDialog::PreTranslateMessage(LPMSG lpMsg)
  693. {
  694.     // This routine does two things:
  695.     // - manage the default push button
  696.     // - where appropriate forward WM_SYSCHAR messages (keyboard mnemonics) to the
  697.     //   property page for translating
  698.     //
  699.     // Here's how it works. Both the property page and the property frame may be
  700.     // involved in translating the message. Who gets first crack at it depends on
  701.     // the message hwnd. There are two scearios:
  702.     //
  703.     // 1. the message hwnd is the property page or one of its children
  704.     // 2. the message hwnd is not the property page or one of its children
  705.     //
  706.     // In scenario #1 the property page gets first crack at translating the message,
  707.     // and if it didn't translate it then the property frame gets to translate it
  708.     //
  709.     // In scenario #2, if the message is a Tab/Shift-Tab and the property page is
  710.     // the control that would receive focus, then it gets first crack at the message.
  711.     // Otherwise, the property frame gets first crack at translating the message. If
  712.     // the message is a WM_SYSCHAR and the property frame didn't translate it then the
  713.     // message is forwarded to the property page for forwarding
  714.     if (lpMsg->hwnd != m_hdlg && !IsChild(m_hdlg, lpMsg->hwnd))
  715.         return FALSE;
  716.  
  717.     // Translate accelerators. The only accelerator we handle is for bringing up the help
  718.     // window
  719.     if (IsHelpKey(lpMsg)) {
  720.         FORWARD_WM_COMMAND(m_hdlg, IDHELP, 0, 0, SendMessage);
  721.         return TRUE;
  722.     }
  723.  
  724.     HWND    hwndFocus = GetFocus();  // remember this for later
  725.     BOOL    bHandled = FALSE;
  726.  
  727.     // See whether the message is targeted for the property page
  728.     HWND    hwnd = GetFirstLevelChild(m_hdlg, lpMsg->hwnd);
  729.  
  730.     if (hwnd && IsDialogWindow(hwnd)) {
  731.         // Scenario #1. Let the property page have the first chance at
  732.         // translating the accelerator
  733.         //
  734.         // Note that under certain circumstances the property page will
  735.         // call the property page site and ask it to translate the message
  736.         assert(m_pCurPage);
  737.         bHandled = m_pCurPage->m_pPage->TranslateAccelerator(lpMsg) == S_OK;
  738.  
  739.         if (!bHandled) {
  740.             // Give the property frame a chance to translate the message
  741.             bHandled = TranslateAccelerator(lpMsg) == S_OK;
  742.         }
  743.  
  744.     } else {
  745.         // If the message is a WM_KEYDOWN of a Tab character and the property page
  746.         // is going to become the focus window, then give it a chance to handle the
  747.         // Tab. This gives it an opportunity to handle Shift-Tab by selecting the
  748.         // last control in the tab order
  749.         if (lpMsg->message == WM_KEYDOWN && lpMsg->wParam == VK_TAB) {
  750.             HWND hNewFocus = GetNextDlgTabItem(m_hdlg, hwndFocus, GetKeyState(VK_SHIFT) < 0);
  751.     
  752.             if (IsDialogWindow(hNewFocus)) {
  753.                 // Give the property page a chance to translate the message
  754.                 assert(m_pCurPage);
  755.                 bHandled = m_pCurPage->m_pPage->TranslateAccelerator(lpMsg) == S_OK;
  756.             }
  757.         }
  758.  
  759.         // Have the property frame translate the message
  760.         if (!bHandled) {
  761.             bHandled = TranslateAccelerator(lpMsg) == S_OK;
  762.  
  763.             // If the message is a WM_SYSCHAR and the property frame didn't handle it,
  764.             // we need to give the property page a chance.
  765.             //
  766.             // Unfortunately IsDialogMessage() returns TRUE for all WM_SYSCHAR messages
  767.             // regardless of whether there was a matching mnemonic (it does this because
  768.             // it calls DefWindowProc() so it can check for matching menu bar mnemonics and
  769.             // it has no idea whether DefWindowProc() found a match)
  770.             if (lpMsg->message == WM_SYSCHAR) {
  771.                 // See if the focus window has changed. If it has, then IsDialogMessage()
  772.                 // found a control that matched the mnemonic. Don't do this if it was for
  773.                 // the system menu though
  774.                 if (lpMsg->wParam != VK_SPACE && GetFocus() == hwndFocus) {
  775.                     // Didn't change the focus so there was not a matching mnemonic. Let
  776.                     // the property page translate it
  777.                     //
  778.                     // Temporarily change the hwnd to be the first child of the property page
  779.                     HWND    hwndPage = GetPropertyPageWindow();
  780.  
  781.                     if (hwndPage) {
  782.                         HWND    hwndSave = lpMsg->hwnd;
  783.  
  784.                         lpMsg->hwnd = GetFirstChild(hwndPage);
  785.                         bHandled = m_pCurPage->m_pPage->TranslateAccelerator(lpMsg) == S_OK;
  786.                         lpMsg->hwnd = hwndSave;
  787.                     }
  788.                 }
  789.             }
  790.         }
  791.     }
  792.  
  793.     // Check the default push button
  794.     if (hwndFocus)
  795.         CheckDefPushButton(hwndFocus, GetFocus());
  796.  
  797.     return bHandled;
  798. }
  799.  
  800. int
  801. CPropertyFrameDialog::RunModalLoop()
  802. {
  803.     MSG    msg;
  804.     
  805.     if (m_hdlg == NULL)
  806.         return -1;
  807.  
  808.     assert(IsWindowVisible(m_hdlg));
  809.  
  810.     for (;;) {
  811.         // Check if we should send an idle message
  812.         if (m_hwndOwner && !::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE)) {
  813.             // Send WM_ENTERIDLE to the owner
  814.             SendMessage(m_hwndOwner, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hdlg);
  815.         }
  816.  
  817.         // Pump the messages
  818.         do {
  819.             if (!GetMessage(&msg, NULL, NULL, NULL)) {
  820.                 // Repost the WM_QUIT message
  821.                 PostQuitMessage(msg.wParam);
  822.                 return -1;
  823.             }
  824.  
  825.             // See if the message should be handled at all
  826.             if (!CallMsgFilter(&msg, MSGF_DIALOGBOX)) {
  827.                 // Give the property page and the property frame dialog a chance to
  828.                 // translate the message
  829.                 if (!PreTranslateMessage(&msg)) {
  830.                     // No one wanted it so dispatch the message
  831.                     TranslateMessage(&msg);
  832.                     DispatchMessage(&msg);
  833.                 }
  834.             }
  835.  
  836.             // Check if we should exit the modal loop
  837.             if (!m_bKeepGoing)
  838.                 return m_nModalResult;
  839.  
  840.             // If there's another message waiting then keep pumping; otherwise go back
  841.             // to the top of the for loop and send another idle message before
  842.             // calling GetMessage again
  843.         } while (PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE));
  844.     }
  845.  
  846.     return m_nModalResult;
  847. }
  848.  
  849. int
  850. CPropertyFrameDialog::DoModal()
  851. {
  852.     BOOL    bEnableOwner = FALSE;
  853.     int        nResult;
  854.  
  855.     // Make sure the parent window isn't a child window. You can't really have
  856.     // a popup window owned by a child window
  857.     if (m_hwndOwner && (GetWindowStyle(m_hwndOwner) & WS_CHILD))
  858.         m_hwndOwner = GetTopLevelWindow(m_hwndOwner);
  859.  
  860.     // Disable the parent window if necessary
  861.     if (m_hwndOwner != NULL && IsWindowEnabled(m_hwndOwner)) {
  862.         EnableWindow(m_hwndOwner, FALSE);
  863.         bEnableOwner = TRUE;
  864.     }
  865.     
  866.     // Display the dialog
  867.     CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_FRAME), m_hwndOwner,
  868.         (DLGPROC)DialogProc, (LPARAM)this);
  869.  
  870.     // Run a dispatch loop so we don't return until the dialog is closed
  871.     m_bKeepGoing = TRUE;
  872.     nResult = RunModalLoop();
  873.  
  874.     // Hide our dialog before enabling the parent window
  875.     SetWindowPos(m_hdlg, NULL, 0, 0, 0, 0, SWP_HIDEWINDOW|SWP_NOSIZE|SWP_NOMOVE|
  876.         SWP_NOACTIVATE|SWP_NOZORDER);
  877.  
  878.     if (bEnableOwner)
  879.         EnableWindow(m_hwndOwner, TRUE);
  880.  
  881.     // We don't want to be the active window
  882.     if (m_hwndOwner && GetActiveWindow() == m_hdlg)
  883.         SetActiveWindow(m_hwndOwner);
  884.  
  885.     // Cleanup
  886.     if (m_pCurPage)
  887.         m_pCurPage->m_pPage->SetObjects(0, NULL);
  888.     DestroyWindow(m_hdlg);
  889.  
  890.     return nResult;
  891. }
  892.  
  893.