#include "ctlspriv.h" #include "help.h" // Help IDs #include "prshti.h" #include "dlgcvt.h" #ifdef WX86 #include #endif #define FLAG_CHANGED 0x0001 #define DEFAULTHEADERHEIGHT 58 // in pixels #define DEFAULTTEXTDIVIDERGAP 5 #define DEFAULTCTRLWIDTH 501 // page list window in new wizard style #define DEFAULTCTRLHEIGHT 253 // page list window in new wizard style #define TITLEX 22 #define TITLEY 10 #define SUBTITLEX 44 #define SUBTITLEY 25 // fixed sizes for the bitmap painted in the header section #define HEADERBITMAP_Y 5 #define HEADERBITMAP_WIDTH 49 #define HEADERBITMAP_CXBACK (5 + HEADERBITMAP_WIDTH) #define HEADERBITMAP_HEIGHT 49 #define HEADERSUBTITLE_WRAPOFFSET 10 // Fixed sizes for the watermark bitmap (Wizard97IE5 style) #define BITMAP_WIDTH 164 #define BITMAP_HEIGHT 312 #define DRAWTEXT_WIZARD97FLAGS (DT_LEFT | DT_WORDBREAK | DT_NOPREFIX | DT_EDITCONTROL) LPVOID WINAPI MapSLFix(HANDLE); VOID WINAPI UnMapSLFixArray(int, HANDLE *); LRESULT CALLBACK WizardWndProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam, UINT_PTR uID, ULONG_PTR dwRefData); #if !defined(WIN32) #ifdef FE_IME typedef void *PVOID; DWORD WINAPI GetCurrentThreadID(VOID); DWORD WINAPI GetCurrentProcessID(VOID); PVOID WINAPI ImmFindThreadLink(DWORD dwThreadID); BOOL WINAPI ImmCreateThreadLink(DWORD dwPid, DWORD dwTid); #endif #endif void ResetWizButtons(LPPROPDATA ppd); typedef struct // tie { TC_ITEMHEADER tci; HWND hwndPage; UINT state; } TC_ITEMEXTRA; #define CB_ITEMEXTRA (sizeof(TC_ITEMEXTRA) - sizeof(TC_ITEMHEADER)) #define IS_WIZARDPSH(psh) ((psh).dwFlags & (PSH_WIZARD | PSH_WIZARD97 | PSH_WIZARD_LITE)) #define IS_WIZARD(ppd) IS_WIZARDPSH(ppd->psh) void PageChange(LPPROPDATA ppd, int iAutoAdj); void RemovePropPageData(LPPROPDATA ppd, int nPage); HRESULT GetPageLanguage(PISP pisp, WORD *pwLang); UINT GetDefaultCharsetFromLang(LANGID wLang); LANGID NT5_GetUserDefaultUILanguage(void); // // IMPORTANT: The IDHELP ID should always be LAST since we just subtract // 1 from the number of IDs if no help in the page. // IDD_APPLYNOW should always be the FIRST ID for standard IDs since it // is sometimes not displayed and we'll start with index 1. // const static int IDs[] = {IDOK, IDCANCEL, IDD_APPLYNOW, IDHELP}; const static int WizIDs[] = {IDD_BACK, IDD_NEXT, IDD_FINISH, IDCANCEL, IDHELP}; const static WORD wIgnoreIDs[] = {IDD_PAGELIST, IDD_DIVIDER, IDD_TOPDIVIDER}; // Prsht_PrepareTemplate action matrix. Please do not change without contacting [msadek]... const PSPT_ACTION g_PSPT_Action [PSPT_TYPE_MAX][PSPT_OS_MAX][PSPT_OVERRIDE_MAX]={ PSPT_ACTION_NOACTION, // PSPT_TYPE_MIRRORED, PSPT_OS_WIN95_BIDI, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_NOACTION, // PSPT_TYPE_MIRRORED, PSPT_OS_WIN95_BIDI, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_WIN9XCOMPAT, // PSPT_TYPE_MIRRORED, PSPT_OS_WIN98_BIDI, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_WIN9XCOMPAT, // PSPT_TYPE_MIRRORED, PSPT_OS_WIN98_BIDI, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_FLIP, // PSPT_TYPE_MIRRORED, PSPT_OS_WINNT4_ENA, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_FLIP, // PSPT_TYPE_MIRRORED, PSPT_OS_WINNT4_ENA, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_NOACTION, // PSPT_TYPE_MIRRORED, PSPT_OS_WINNT5, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_NOACTION, // PSPT_TYPE_MIRRORED, PSPT_OS_WINNT5, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_NOACTION, // PSPT_TYPE_MIRRORED, PSPT_OS_OTHER, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_NOMIRRORING, // PSPT_TYPE_MIRRORED, PSPT_OS_OTHER, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_NOACTION, // PSPT_TYPE_ENABLED, PSPT_OS_WIN95_BIDI, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_NOACTION, // PSPT_TYPE_ENABLED, PSPT_OS_WIN95_BIDI, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_FLIP, // PSPT_TYPE_ENABLED, PSPT_OS_WIN98_BIDI, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_FLIP, // PSPT_TYPE_ENABLED, PSPT_OS_WIN98_BIDI, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_FLIP, // PSPT_TYPE_ENABLED, PSPT_OS_WINNT4_ENA, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_FLIP, // PSPT_TYPE_ENABLED, PSPT_OS_WINNT4_ENA, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_FLIP, // PSPT_TYPE_ENABLED, PSPT_OS_WINNT5, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_FLIP, // PSPT_TYPE_ENABLED, PSPT_OS_WINNT5, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_NOACTION, // PSPT_TYPE_ENABLED, PSPT_OS_OTHER, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_NOMIRRORING, // PSPT_TYPE_ENABLED, PSPT_OS_OTHER, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_NOACTION, // PSPT_TYPE_ENGLISH, PSPT_OS_WIN95_BIDI, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_NOACTION, // PSPT_TYPE_ENGLISH, PSPT_OS_WIN95_BIDI, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_LOADENGLISH, // PSPT_TYPE_ENGLISH, PSPT_OS_WIN98_BIDI, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_FLIP, // PSPT_TYPE_ENGLISH, PSPT_OS_WIN98_BIDI, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_FLIP, // PSPT_TYPE_ENGLISH, PSPT_OS_WINNT4_ENA, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_FLIP, // PSPT_TYPE_ENGLISH, PSPT_OS_WINNT4_ENA, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_LOADENGLISH, // PSPT_TYPE_ENGLISH, PSPT_OS_WINNT5, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_FLIP, // PSPT_TYPE_ENGLISH, PSPT_OS_WINNT5, PSPT_OVERRIDE_USEPAGELANG PSPT_ACTION_NOACTION, // PSPT_TYPE_ENGLISH, PSPT_OS_OTHER, PSPT_OVERRIDE_NOOVERRIDE PSPT_ACTION_NOMIRRORING, // PSPT_TYPE_ENGLISH, PSPT_OS_OTHER, PSPT_OVERRIDE_USEPAGELANG }; // HACK FOR HIJAAK 95! // // Instead of creating // property sheet pages with CreatePropertySheetPage, they merely // take a pointer to a PROPSHEETPAGE structure and cast it to // HPROPSHEETPAGE. They got away with this on Win95 because Win95's // HPROPSHEETPAGE actually was 95% identical to a PROPSHEETPAGE. // (The missing 5% causes RIPs at property sheet destruction, which // Hijaak no doubt ignored.) // // On NT and IE5, this coincidence is not true. // // So validate that what we have is really a property sheet // structure by checking if it's on the heap at the // right place. If not, then make one. // HPROPSHEETPAGE WINAPI _Hijaak95Hack(LPPROPDATA ppd, HPROPSHEETPAGE hpage) { if (hpage && !LocalSize(PropSheetBase(hpage))) { // SLACKERS! Have to call CreatePropertySheetPage for them RIPMSG(0, "App passed HPROPSHEETPAGE not created by us; trying to cope"); hpage = _CreatePropertySheetPage((LPCPROPSHEETPAGE)hpage, ppd->fFlags & PD_NEEDSHADOW, ppd->fFlags & PD_WX86); } return hpage; } void _SetTitle(HWND hDlg, LPPROPDATA ppd) { TCHAR szFormat[50]; TCHAR szTitle[128]; TCHAR szTemp[128 + 50]; LPCTSTR pCaption = ppd->psh.pszCaption; if (IS_INTRESOURCE(pCaption)) { LoadString(ppd->psh.hInstance, (UINT)LOWORD(pCaption), szTitle, ARRAYSIZE(szTitle)); pCaption = (LPCTSTR)szTitle; } if (ppd->psh.dwFlags & PSH_PROPTITLE) { if (*pCaption == 0) { // Hey, no title, we need a different resource for localization LocalizedLoadString(IDS_PROPERTIES, szTemp, ARRAYSIZE(szTemp)); pCaption = szTemp; } else { LocalizedLoadString(IDS_PROPERTIESFOR, szFormat, ARRAYSIZE(szFormat)); if ((lstrlen(pCaption) + 1 + lstrlen(szFormat) + 1) < ARRAYSIZE(szTemp)) { wsprintf(szTemp, szFormat, pCaption); pCaption = szTemp; } } } #ifdef WINDOWS_ME if(ppd->psh.dwFlags & PSH_RTLREADING) { SetWindowLong(hDlg, GWL_EXSTYLE, GetWindowLong(hDlg, GWL_EXSTYLE) | WS_EX_RTLREADING); } #endif // WINDOWS_ME SetWindowText(hDlg, pCaption); } BOOL _SetHeaderFonts(HWND hDlg, LPPROPDATA ppd) { HFONT hFont; LOGFONT LogFont; GetObject(GetWindowFont(hDlg), sizeof(LogFont), &LogFont); CCAdjustForBold(&LogFont); if ((hFont = CreateFontIndirect(&LogFont)) == NULL) { ppd->hFontBold = NULL; return FALSE; } ppd->hFontBold = hFont; // Save the font as a window prop so we can delete it later return TRUE; } int _WriteHeaderTitle(LPPROPDATA ppd, HDC hdc, LPRECT prc, LPCTSTR pszTitle, BOOL bTitle, DWORD dwDrawFlags) { LPCTSTR pszOut; int cch; int cx, cy; TCHAR szTitle[MAX_PATH*4]; HFONT hFontOld = NULL; HFONT hFont; int yDrawHeight = 0; if (IS_INTRESOURCE(pszTitle)) { LoadString(GETPPSP(ppd, ppd->nCurItem)->hInstance, (UINT)LOWORD(pszTitle), szTitle, ARRAYSIZE(szTitle)); pszOut = szTitle; } else pszOut = pszTitle; cch = lstrlen(pszOut); if (bTitle && ppd->hFontBold) hFont = ppd->hFontBold; else hFont = GetWindowFont(ppd->hDlg); hFontOld = SelectObject(hdc, hFont); if (bTitle) { cx = TITLEX; cy = TITLEY; ExtTextOut(hdc, cx, cy, 0, prc, pszOut, cch, NULL); } else { RECT rcWrap; CopyRect(&rcWrap, prc); rcWrap.left = SUBTITLEX; rcWrap.top = ppd->ySubTitle; yDrawHeight = DrawText(hdc, pszOut, cch, &rcWrap, dwDrawFlags); } if (hFontOld) SelectObject(hdc, hFontOld); return yDrawHeight; } // In Wizard97 only: // The subtitles user passed in could be larger than the two line spaces we give // them, especially in localization cases. So here we go through all subtitles and // compute the max space they need and set the header height so that no text is clipped int _ComputeHeaderHeight(LPPROPDATA ppd, int dxMax) { int dyHeaderHeight; int dyTextDividerGap; HDC hdc; dyHeaderHeight = DEFAULTHEADERHEIGHT; hdc = GetDC(ppd->hDlg); // First, let's get the correct text height and spacing, this can be used // as the title height and the between-lastline-and-divider spacing. { HFONT hFont, hFontOld; TEXTMETRIC tm; if (ppd->hFontBold) hFont = ppd->hFontBold; else hFont = GetWindowFont(ppd->hDlg); hFontOld = SelectObject(hdc, hFont); if (GetTextMetrics(hdc, &tm)) { dyTextDividerGap = tm.tmExternalLeading; ppd->ySubTitle = max ((tm.tmHeight + tm.tmExternalLeading + TITLEY), SUBTITLEY); } else { dyTextDividerGap = DEFAULTTEXTDIVIDERGAP; ppd->ySubTitle = SUBTITLEY; } if (hFontOld) SelectObject(hdc, hFontOld); } // Second, get the subtitle text block height // should make into a function if shared { RECT rcWrap; UINT uPages; // // WIZARD97IE5 subtracts out the space used by the header bitmap. // WIZARD97IE4 uses the full width since the header bitmap // in IE4 is a watermark and occupies no space. // if (ppd->psh.dwFlags & PSH_WIZARD97IE4) rcWrap.right = dxMax; else rcWrap.right = dxMax - HEADERBITMAP_CXBACK - HEADERSUBTITLE_WRAPOFFSET; for (uPages = 0; uPages < ppd->psh.nPages; uPages++) { PROPSHEETPAGE *ppsp = GETPPSP(ppd, uPages); if (!(ppsp->dwFlags & PSP_HIDEHEADER) && (ppsp->dwFlags & PSP_USEHEADERSUBTITLE)) { int iSubHeaderHeight = _WriteHeaderTitle(ppd, hdc, &rcWrap, ppsp->pszHeaderSubTitle, FALSE, DT_CALCRECT | DRAWTEXT_WIZARD97FLAGS); if ((iSubHeaderHeight + ppd->ySubTitle) > dyHeaderHeight) dyHeaderHeight = iSubHeaderHeight + ppd->ySubTitle; } } } // If the header height has been recomputed, set the correct gap between // the text and the divider. if (dyHeaderHeight != DEFAULTHEADERHEIGHT) { ASSERT(dyHeaderHeight > DEFAULTHEADERHEIGHT); dyHeaderHeight += dyTextDividerGap; } ReleaseDC(ppd->hDlg, hdc); return dyHeaderHeight; } void MoveAllButtons(HWND hDlg, const int *pids, int idLast, int dx, int dy) { do { HWND hCtrl; RECT rcCtrl; int iCtrl = *pids; hCtrl = GetDlgItem(hDlg, iCtrl); GetWindowRect(hCtrl, &rcCtrl); // // If the dialog wizard window is mirrored, then rcl.right // in terms of screen coord is the near edge (lead). [samera] // if (IS_WINDOW_RTL_MIRRORED(hDlg)) rcCtrl.left = rcCtrl.right; ScreenToClient(hDlg, (LPPOINT)&rcCtrl); SetWindowPos(hCtrl, NULL, rcCtrl.left + dx, rcCtrl.top + dy, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); } while(*(pids++) != idLast); } void RemoveButton(HWND hDlg, int idRemove, const int *pids) { int idPrev = 0; HWND hRemove = NULL; HWND hPrev; RECT rcRemove, rcPrev; int iWidth = 0; const int *pidRemove; // get the previous id for (pidRemove = pids; *pidRemove != idRemove; pidRemove++) idPrev = *pidRemove; if (idPrev) { hRemove = GetDlgItem(hDlg, idRemove); hPrev = GetDlgItem(hDlg, idPrev); GetWindowRect(hRemove, &rcRemove); GetWindowRect(hPrev, &rcPrev); // // If the dialog window is mirrored, then the prev button // will be ahead (to the right) of the button-to-be-removed. // As a result, the subtraction will be definitely negative, // so let's convert it to be positive. [samera] // if (IS_WINDOW_RTL_MIRRORED(hDlg)) iWidth = rcPrev.right - rcRemove.right; else iWidth = rcRemove.right - rcPrev.right; } MoveAllButtons(hDlg, pids, idRemove, iWidth, 0); if (hRemove) ShowWindow(hRemove, SW_HIDE); // Cannot disable the window; see Prsht_ButtonSubclassProc for explanation. // WRONG - EnableWindow(hRemove, FALSE); } typedef struct LOGPALETTE256 { WORD palVersion; WORD palNumEntries; union { PALETTEENTRY rgpal[256]; RGBQUAD rgq[256]; } u; } LOGPALETTE256; HPALETTE PaletteFromBmp(HBITMAP hbm) { LOGPALETTE256 pal; int i,n; HDC hdc; HPALETTE hpl; hdc = CreateCompatibleDC(NULL); SelectObject(hdc, hbm); n = GetDIBColorTable(hdc, 0, 256, pal.u.rgq); if (n) // DIB section with color table { // Palettes are such a hassle. GetDIBColorTable returns RGBQUADs, whereas // LOGPALETTE wants PALETTEENTRYss, and the two are reverse-endian // of each other. for (i= 0 ; i < n; i++) { PALETTEENTRY pe; pe.peRed = pal.u.rgq[i].rgbRed; pe.peGreen = pal.u.rgq[i].rgbGreen; pe.peBlue = pal.u.rgq[i].rgbBlue; pe.peFlags = 0; pal.u.rgpal[i] = pe; } pal.palVersion = 0x0300; pal.palNumEntries = (WORD)n; hpl = CreatePalette((LPLOGPALETTE)&pal); } else // Not a DIB section or no color table { hpl = CreateHalftonePalette(hdc); } DeleteDC(hdc); return hpl; } // -------------- stolen from user code ------------------------------------- // // GetCharDimensions(hDC, psiz) // // This function loads the Textmetrics of the font currently selected into // the given hDC and saves the height and Average char width of the font // (NOTE: the // AveCharWidth value returned by the text metrics call is wrong for // proportional fonts -- so, we compute them). // // -------------- stolen from user code -------------------------------------- TCHAR AveCharWidthData[52+1] = TEXT("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); void GetCharDimensions(HDC hDC, SIZE *psiz) { TEXTMETRIC tm; // Store the System Font metrics info. GetTextMetrics(hDC, &tm); if (!(tm.tmPitchAndFamily & TMPF_FIXED_PITCH)) // the name is opposite:) psiz->cx = tm.tmAveCharWidth; else { // Change from tmAveCharWidth. We will calculate a true average as // opposed to the one returned by tmAveCharWidth. This works better // when dealing with proportional spaced fonts. -- ROUND UP if (GetTextExtentPoint32(hDC, AveCharWidthData, 52, psiz) == TRUE) { psiz->cx = ((psiz->cx / 26) + 1) / 2; } else psiz->cx = tm.tmAveCharWidth; } psiz->cy = tm.tmHeight; } // // It is a feature that USER considers keyboard accelerators live even if // the control is hidden. This lets you put a hidden static in front of // a custom control to get an accelerator attached to the custom control. // // Unfortunately, it means that the &F accelerator for "Finish" activates // the Finish button even when the Finish button is hidden. The normal // workaround for this is to disable the control, but that doesn't work // because Microsoft PhotoDraw runs around and secretly hides and shows // buttons without going through PSM_SETWIZBUTTONS, so they end up showing // a disabled window and their wizard stops working. // // So instead, we subclass the buttons and customize their WM_GETDLGCODE // so that when the control is hidden, they disable their accelerators. // LRESULT CALLBACK Prsht_ButtonSubclassProc(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp, UINT_PTR uID, ULONG_PTR dwRefData) { LRESULT lres; switch (wm) { case WM_GETDLGCODE: lres = DefSubclassProc(hwnd, wm, wp, lp); if (!IsWindowVisible(hwnd)) { // To remove yourself from the mnemonic search, you have to // return DLGC_WANTCHAR if you are give a NULL LPMSG pointer. // Normally, the dialog manager sends a real LPMSG containing // the message that just got received, but when it's poking // around looking for accelerators, it doesn't give you a // message at all. It is in that case that you want to // say, "Hey, I will process the (nonexistent) message". // This tricks USER into thinking you're an edit control, so // it won't scan your for mnemonics. if ((LPMSG)lp == NULL) lres |= DLGC_WANTCHARS; } break; case WM_NCDESTROY: // Clean up subclass RemoveWindowSubclass(hwnd, Prsht_ButtonSubclassProc, 0); lres = DefSubclassProc(hwnd, wm, wp, lp); break; default: lres = DefSubclassProc(hwnd, wm, wp, lp); break; } return lres; } void Prsht_SubclassButton(HWND hDlg, UINT idd) { SetWindowSubclass(GetDlgItem(hDlg, idd), Prsht_ButtonSubclassProc, 0, 0); } BOOL CompareFontFaceW(LPCWSTR lpwz1, LPCWSTR lpwz2, BOOL fBitCmp) { return lstrcmpiW(lpwz1, lpwz2); } // // GetPageFontMetrics // // synopsis: // // Get the real font metrics from PAGEFONTDATA. Used in InitPropSheetDlg() to // calculate the physical page size based on the font specified in page templates // // fML is set if we are in here because of an ML scenario, in which case the // font names need to be mapped. // BOOL GetPageFontMetrics(LPPROPDATA ppd, PPAGEFONTDATA ppfd, BOOL fML) { LOGFONT lf = {0}; HFONT hFont; HRESULT fRc = FALSE; HDC hdc; if (ppfd && (ppfd->PointSize > 0) && ppfd->szFace[0]) { // font name mapping // should be done only for the platform less than NT5 // NT5 is supposed to work with native typeface on any system locale. // if (!staticIsOS(OS_WIN2000ORGREATER) && fML) { // replace native font face name to single byte name for non-native platform typedef struct tagFontFace { BOOL fBitCmp; LPCWSTR lpEnglish; LPCWSTR lpNative; } FONTFACE, *LPFONTFACE; const static FONTFACE s_FontTbl[] = { { FALSE, L"MS Gothic", L"MS UI Gothic" }, { TRUE, L"MS Gothic", L"\xff2d\xff33 \xff30\x30b4\x30b7\x30c3\x30af" }, { TRUE, L"GulimChe", L"\xad74\xb9bc" }, { TRUE, L"MS Song", L"\x5b8b\x4f53" }, { TRUE, L"MingLiU", L"\x65b0\x7d30\x660e\x9ad4" } }; int i; for (i = 0; i < ARRAYSIZE(s_FontTbl); i++) { if (!CompareFontFaceW(ppfd->szFace, s_FontTbl[i].lpNative, s_FontTbl[i].fBitCmp)) { lstrcpynW(lf.lfFaceName, s_FontTbl[i].lpEnglish, ARRAYSIZE(lf.lfFaceName)); break; } } if (i >= ARRAYSIZE(s_FontTbl)) lstrcpynW(lf.lfFaceName, ppfd->szFace, ARRAYSIZE(lf.lfFaceName)); } else lstrcpynW(lf.lfFaceName, ppfd->szFace, ARRAYSIZE(lf.lfFaceName)); // Try to use the cache if (ppfd->iCharset == ppd->pfdCache.iCharset && ppfd->bItalic == ppd->pfdCache.bItalic && ppfd->PointSize == ppd->pfdCache.PointSize && lstrcmpiW(ppfd->szFace, ppd->pfdCache.szFace) == 0) { fRc = TRUE; } else { if (hdc = GetDC(ppd->hDlg)) { lf.lfHeight = -MulDiv(ppfd->PointSize, GetDeviceCaps(hdc,LOGPIXELSY), 72); lf.lfCharSet = (BYTE)ppfd->iCharset; lf.lfItalic = (BYTE)ppfd->bItalic; lf.lfWeight = FW_NORMAL; hFont = CreateFontIndirectW(&lf); if (hFont) { HFONT hFontOld = SelectObject(hdc, hFont); GetCharDimensions(hdc, &ppd->sizCache); if (hFontOld) SelectObject(hdc, hFontOld); DeleteObject(hFont); // Save these font metrics into the cache ppd->pfdCache = *ppfd; fRc = TRUE; } ReleaseDC(ppd->hDlg, hdc); } } } return fRc; } // // The "ideal page size" of a property sheet is the maximum size of all // pages. // // GIPS_SKIPINTERIOR97HEIGHT and GIPS_SKIPEXTERIOR97HEIGHT selective // exclude Wiz97 pages from the height computation. They are important // because interior pages are shorter than exterior pages by // ppd->cyHeaderHeight. // #define GIPS_SKIPINTERIOR97HEIGHT 1 #define GIPS_SKIPEXTERIOR97HEIGHT 2 void Prsht_GetIdealPageSize(LPPROPDATA ppd, PSIZE psiz, UINT flags) { UINT uPages; *psiz = ppd->sizMin; for (uPages = 0; uPages < ppd->psh.nPages; uPages++) { PISP pisp = GETPISP(ppd, uPages); int cy = pisp->_pfx.siz.cy; if (ppd->psh.dwFlags & PSH_WIZARD97) { if (pisp->_psp.dwFlags & PSP_HIDEHEADER) { if (flags & GIPS_SKIPEXTERIOR97HEIGHT) goto skip; } else { if (flags & GIPS_SKIPINTERIOR97HEIGHT) goto skip; } } if (psiz->cy < cy) psiz->cy = cy; skip:; if (psiz->cx < pisp->_pfx.siz.cx) psiz->cx = pisp->_pfx.siz.cx; } } #define IsMSShellDlgMapped(langid) (PRIMARYLANGID(langid) == LANG_JAPANESE) // // Given a page, decide what size it wants to be and save it in the // pisp->_pfx.siz. // void Prsht_ComputeIdealPageSize(LPPROPDATA ppd, PISP pisp, PAGEINFOEX *ppi) { BOOL fUsePageFont; // pressume page and frame dialog are in same character set LANGID wPageLang = ppd->wFrameLang; int iPageCharset = DEFAULT_CHARSET; if (SUCCEEDED(GetPageLanguage(pisp, &wPageLang))) { // GetPageLanguage fails if page is marked PSP_DLGINDIRECT; // we'll try to recover from that later. For now, // we leave pagelang to DEFAULT_CHARSET and see if we can take // the charset info from template EX. // // if PSH_USEPAGELANG is specified, we can assume that // page charset == frame charset and no need for ML adjustment // *except for* the case of NT Japanese version that replaces // frame's MS Shell Dlg to their native font. We handle this // exception later where we set up fUsePageFont; // if (!(ppd->psh.dwFlags & PSH_USEPAGELANG) && wPageLang != ppd->wFrameLang) { iPageCharset = GetDefaultCharsetFromLang(wPageLang); } else iPageCharset = ppd->iFrameCharset; } // Use the font in the page if any of these conditions are met: // // A) It's a SHELLFONT page. Do this even if the font is not // "MS Shell Dlg 2". This gives apps a way to specify that // their custom-font page should be measured against the // font in the page rather than in the frame font. // // B) ML scenario - complicated original comment below... // // 1) we've detected lang in the caller's resource and // it's different from the frame dialog // 2) the caller's page doesn't have lang info or we've // failed to get it (iPageCharset == DEFAULT_CHARSET), // then we find the page is described with DLGTEMPLATEEX // and has meaningful charset specified (!= defaultcharset) // *and* the charset is different from frame's // 3) the exception for NT Japanese platform that maps // MS Shell Dlg to their native font. For US Apps to // work on these platforms they typically specify // PSH_USEPAGELANG to get English buttons on frame // but they still need to get the frame sized based on // page font // // Otherwise, IE4 compat **requires** that we use the frame font. // ISVs have hacked around this historical bug by having large // dialog templates with extra space in them. // fUsePageFont = /* --- A) It's a SHELLFONT page --- */ IsPageInfoSHELLFONT(ppi) || /* --- B) ML scenario --- */ ((ppd->psh.dwFlags & PSH_USEPAGELANG) && IsMSShellDlgMapped(NT5_GetUserDefaultUILanguage())) || (ppd->iFrameCharset != iPageCharset && (iPageCharset != DEFAULT_CHARSET || (ppi->pfd.iCharset != DEFAULT_CHARSET && ppi->pfd.iCharset != ppd->iFrameCharset))); if (fUsePageFont && GetPageFontMetrics(ppd, &ppi->pfd, MLIsMLHInstance(pisp->_psp.hInstance))) { // Compute Real Dialog Unit for the page pisp->_pfx.siz.cx = MulDiv(ppi->pt.x, ppd->sizCache.cx, 4); pisp->_pfx.siz.cy = MulDiv(ppi->pt.y, ppd->sizCache.cy, 8); } else { RECT rcT; // IE4 compat - Use the frame font rcT.top = rcT.left = 0; // Win95 will fault if these are uninit rcT.right = ppi->pt.x; rcT.bottom = ppi->pt.y; MapDialogRect(ppd->hDlg, &rcT); pisp->_pfx.siz.cx = rcT.right; pisp->_pfx.siz.cy = rcT.bottom; // // If this is PSP_DLGINDIRECT but the character set and face name // say this is a "generic" property sheet, then take the frame // font or the page font, whichever is bigger. // // This fixes the Chinese MingLiu font, which is not as tall as // the English MS Sans Serif font. Without this fix, we would // use MingLui (the frame font), and then your MS Shell Dlg pages // would get truncated. // // (Truncated property sheets is what you got in NT4, but I guess // looking pretty is more important than bug-for-bug compatibility. // Who knows what apps will be broken by this change.) // if ((pisp->_psp.dwFlags & PSP_DLGINDIRECT) && ppi->pfd.iCharset == DEFAULT_CHARSET && lstrcmpiW(ppi->pfd.szFace, L"MS Shell Dlg") == 0) { int i; GetPageFontMetrics(ppd, &ppi->pfd, FALSE); i = MulDiv(ppi->pt.x, ppd->sizCache.cx, 4); if (pisp->_pfx.siz.cx < i) pisp->_pfx.siz.cx = i; i = MulDiv(ppi->pt.y, ppd->sizCache.cy, 8); if (pisp->_pfx.siz.cy < i) pisp->_pfx.siz.cy = i; } } } void InitPropSheetDlg(HWND hDlg, LPPROPDATA ppd) { PAGEINFOEX pi; int dxDlg, dyDlg, dyGrow, dxGrow; RECT rcMinSize, rcDlg, rcPage, rcOrigTabs; UINT uPages; HIMAGELIST himl = NULL; TC_ITEMEXTRA tie; TCHAR szStartPage[128]; LPCTSTR pStartPage = NULL; UINT nStartPage; BOOL fPrematurePages = FALSE; #ifdef DEBUG BOOL fStartPageFound = FALSE; #endif LANGID langidMUI; MONITORINFO mMonitorInfo; HMONITOR hMonitor; BOOL bMirrored = FALSE; // set our instance data pointer SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)ppd); // Make sure this gets inited early on. ppd->nCurItem = 0; // By default we allow the "Apply" button to be enabled ppd->fAllowApply = TRUE; if (IS_WIZARD(ppd)) { // Subclass our buttons so their mnemonics won't mess up applications // that run around hiding and showing the buttons behind our back. Prsht_SubclassButton(hDlg, IDD_BACK); Prsht_SubclassButton(hDlg, IDD_NEXT); Prsht_SubclassButton(hDlg, IDD_FINISH); } else _SetTitle(hDlg, ppd); if (ppd->psh.dwFlags & PSH_USEICONID) { ppd->psh.H_hIcon = LoadImage(ppd->psh.hInstance, ppd->psh.H_pszIcon, IMAGE_ICON, g_cxSmIcon, g_cySmIcon, LR_DEFAULTCOLOR); } if ((ppd->psh.dwFlags & (PSH_USEICONID | PSH_USEHICON)) && ppd->psh.H_hIcon) SendMessage(hDlg, WM_SETICON, FALSE, (LPARAM)(UINT_PTR)ppd->psh.H_hIcon); ppd->hDlg = hDlg; // IDD_PAGELIST should definitely exist ppd->hwndTabs = GetDlgItem(hDlg, IDD_PAGELIST); ASSERT(ppd->hwndTabs); TabCtrl_SetItemExtra(ppd->hwndTabs, CB_ITEMEXTRA); // nStartPage is either ppd->psh.H_nStartPage or the page pStartPage nStartPage = ppd->psh.H_nStartPage; if (ppd->psh.dwFlags & PSH_USEPSTARTPAGE) { nStartPage = 0; // Assume we don't find the page pStartPage = ppd->psh.H_pStartPage; if (IS_INTRESOURCE(pStartPage)) { szStartPage[0] = TEXT('\0'); LoadString(ppd->psh.hInstance, (UINT)LOWORD(pStartPage), szStartPage, ARRAYSIZE(szStartPage)); pStartPage = szStartPage; } } #ifndef WINDOWS_ME tie.tci.mask = TCIF_TEXT | TCIF_PARAM | TCIF_IMAGE; #endif tie.hwndPage = NULL; tie.tci.pszText = pi.szCaption; tie.state = 0; SendMessage(ppd->hwndTabs, WM_SETREDRAW, FALSE, 0L); // load langid we chose for frame dialog template ppd->wFrameLang = LANGIDFROMLCID(CCGetProperThreadLocale(NULL)); // it's charset that really matters to font ppd->iFrameCharset = GetDefaultCharsetFromLang(ppd->wFrameLang); langidMUI = GetMUILanguage(); for (uPages = 0; uPages < ppd->psh.nPages; uPages++) { PISP pisp = GETPISP(ppd, uPages); if (GetPageInfoEx(ppd, pisp, &pi, langidMUI, GPI_ALL)) { Prsht_ComputeIdealPageSize(ppd, pisp, &pi); // Add the page to the end of the tab list tie.tci.iImage = -1; #ifdef WINDOWS_ME tie.tci.mask = TCIF_TEXT | TCIF_PARAM | TCIF_IMAGE | (pi.bRTL ? TCIF_RTLREADING : 0); #endif if (pi.hIcon) { if (!himl) { UINT flags = ILC_MASK; if(IS_WINDOW_RTL_MIRRORED(ppd->hwndTabs)) { flags |= ILC_MIRROR; } himl = ImageList_Create(g_cxSmIcon, g_cySmIcon, flags, 8, 4); TabCtrl_SetImageList(ppd->hwndTabs, himl); } tie.tci.iImage = ImageList_AddIcon(himl, pi.hIcon); // QUESTION raymondc - we always destroy even if PSP_USEHICON? DestroyIcon(pi.hIcon); } // QUESTION? What if this fails? Do we want to destroy the page? if (TabCtrl_InsertItem(ppd->hwndTabs, 1000, &tie.tci) >= 0) { // Nothing to do; all the code that was here got moved elsewhere } // remember if any page wants premature init if (pisp->_psp.dwFlags & PSP_PREMATURE) fPrematurePages = TRUE; // if the user is specifying the startpage via title, check it here if ((ppd->psh.dwFlags & PSH_USEPSTARTPAGE) && !lstrcmpi(pStartPage, pi.szCaption)) { nStartPage = uPages; #ifdef DEBUG fStartPageFound = TRUE; #endif } } else { DebugMsg(DM_ERROR, TEXT("PropertySheet failed to GetPageInfo")); RemovePropPageData(ppd, uPages--); } } SendMessage(ppd->hwndTabs, WM_SETREDRAW, TRUE, 0L); if (ppd->psh.pfnCallback) { #ifdef WX86 if (ppd->fFlags & PD_WX86) Wx86Callback(ppd->psh.pfnCallback, hDlg, PSCB_INITIALIZED, 0); else #endif ppd->psh.pfnCallback(hDlg, PSCB_INITIALIZED, 0); } // // Now compute the size of the tab control. // // First get the rectangle for the whole dialog GetWindowRect(hDlg, &rcDlg); // For WIZARD_LITE style wizards, we stretch the tabs page and sunken divider // to cover the whole wizard (without the border) if (ppd->psh.dwFlags & PSH_WIZARD_LITE) { // Stretch the divider to the whole width of the wizard RECT rcDiv, rcDlgClient; HWND hDiv; // we allow both PSH_WIZARD and PSH_WIZARD_LITE to be set // it's exactly the same as setting just PSH_WIZARD_LITE RIPMSG(!(ppd->psh.dwFlags & PSH_WIZARD97), "Cannot combine PSH_WIZARD_LITE with PSH_WIZARD97"); // but some people do it anyway, so turn off ppd->psh.dwFlags &= ~PSH_WIZARD97; // NOTE: GetDlgItemRect returns a rectangle relative to hDlg hDiv = GetDlgItemRect(hDlg, IDD_DIVIDER, &rcDiv); if (hDiv) SetWindowPos(hDiv, NULL, 0, rcDiv.top, RECTWIDTH(rcDlg), RECTHEIGHT(rcDiv), SWP_NOZORDER | SWP_NOACTIVATE); GetClientRect(hDlg, &rcDlgClient); // Stretch the page list control to cover the whole wizard client area above // the divider SetWindowPos(ppd->hwndTabs, NULL, 0, 0, RECTWIDTH(rcDlgClient), rcDiv.top, SWP_NOZORDER | SWP_NOACTIVATE); } // // While we're thinking about it, don't let people set both // WIZARD97IE4 *and* WIZARD97IE5. That's just way too strange. // if (ppd->psh.dwFlags & PSH_WIZARD97IE4) ppd->psh.dwFlags &= ~PSH_WIZARD97IE5; // Get the rectangle of the pagelist control in pixels. GetClientRect(ppd->hwndTabs, &rcOrigTabs); ppd->sizMin.cx = rcOrigTabs.right; ppd->sizMin.cy = rcOrigTabs.bottom; // Compute rcPage = Size of page area in pixels // For now, we only care about interior pages; we'll deal with exterior // pages later. rcPage.left = rcPage.top = 0; Prsht_GetIdealPageSize(ppd, (SIZE *)&rcPage.right, GIPS_SKIPEXTERIOR97HEIGHT); // // IE4's Wizard97 assumed that all exterior pages were exactly // DEFAULTHEADERHEIGHT dlu's taller than interior pages. That's // right, DEFAULTHEADERHEIGHT is a pixel count, but IE4 messed up // and used it as a dlu count here. // if (ppd->psh.dwFlags & PSH_WIZARD97IE4) { SIZE sizT; SetRect(&rcMinSize, 0, 0, 0, DEFAULTHEADERHEIGHT); MapDialogRect(hDlg, &rcMinSize); Prsht_GetIdealPageSize(ppd, &sizT, GIPS_SKIPINTERIOR97HEIGHT); if (rcPage.bottom < sizT.cy - rcMinSize.bottom) rcPage.bottom = sizT.cy - rcMinSize.bottom; } // Now compute the minimum size for the page region rcMinSize = rcPage; // // If this is a wizard then set the size of the page area to the entire // size of the control. If it is a normal property sheet then adjust for // the tabs, resize the control, and then compute the size of the page // region only. // if (IS_WIZARD(ppd)) // initialize rcPage = rcMinSize; else { int i; RECT rcAdjSize; // initialize for (i = 0; i < 2; i++) { rcAdjSize = rcMinSize; TabCtrl_AdjustRect(ppd->hwndTabs, TRUE, &rcAdjSize); rcAdjSize.right -= rcAdjSize.left; rcAdjSize.bottom -= rcAdjSize.top; rcAdjSize.left = rcAdjSize.top = 0; if (rcAdjSize.right < rcMinSize.right) rcAdjSize.right = rcMinSize.right; if (rcAdjSize.bottom < rcMinSize.bottom) rcAdjSize.bottom = rcMinSize.bottom; SetWindowPos(ppd->hwndTabs, NULL, 0,0, rcAdjSize.right, rcAdjSize.bottom, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); } rcPage = rcMinSize = rcAdjSize; TabCtrl_AdjustRect(ppd->hwndTabs, FALSE, &rcPage); } // // rcMinSize now contains the size of the control, including the tabs, and // rcPage is the rect containing the page portion (without the tabs). // // For wizard97: // Now we have the correct width for our wizard, let's compute the // header height based on that, shift the tab window and the pages // window down accordingly. // dyGrow = 0; if (ppd->psh.dwFlags & PSH_WIZARD97) { RECT rcTabs; SIZE sizT; // NOTE: we don't directly use rcPage because the verticle position for // ppd->hwndTabs is not determined, yet, even though the horizontal is // already computed. Therefore, we can only use rcPageCopy.right not // rcPageCopy.bottom in the following code. RECT rcTemp; CopyRect(&rcTemp, &rcPage); MapWindowPoints(ppd->hwndTabs, hDlg, (LPPOINT)&rcTemp, 2); GetWindowRect(ppd->hwndTabs, &rcTabs); MapWindowRect(NULL, hDlg, &rcTabs); // Set the header fonts first because we need to use the bold font // to compute the title height _SetHeaderFonts(hDlg, ppd); // Adjust the header height ppd->cyHeaderHeight = _ComputeHeaderHeight(ppd, rcTemp.right); // Since the app can change the subheader text on the fly, // our computation of the header height might end up wrong later. // Allow ISVs to precompensate for that by setting their exterior // pages larger than the interior pages by the amount they want // to reserve. // So if the largest external page is larger than the largest internal // page, then expand to enclose the external pages too. // IE4 Wizard97 didn't do this and MFC relies on the bug. if (!(ppd->psh.dwFlags & PSH_WIZARD97IE4)) { // A margin of 7dlu's is placed above the page, and another // margin of 7 dlu's is placed below. SetRect(&rcTemp, 0, 0, 0, 7+7); MapDialogRect(hDlg, &rcTemp); Prsht_GetIdealPageSize(ppd, &sizT, GIPS_SKIPINTERIOR97HEIGHT); if (ppd->cyHeaderHeight < sizT.cy - RECTHEIGHT(rcPage) - rcTemp.bottom) ppd->cyHeaderHeight = sizT.cy - RECTHEIGHT(rcPage) - rcTemp.bottom; } // Move the tab window right under the header dyGrow += ppd->cyHeaderHeight; SetWindowPos(ppd->hwndTabs, NULL, rcTabs.left, rcTabs.top + dyGrow, RECTWIDTH(rcTabs), RECTHEIGHT(rcTabs), SWP_NOZORDER | SWP_NOACTIVATE); } // // Resize the dialog to make room for the control's new size. This can // only grow the size. // dxGrow = rcMinSize.right - rcOrigTabs.right; dxDlg = rcDlg.right - rcDlg.left + dxGrow; dyGrow += rcMinSize.bottom - rcOrigTabs.bottom; dyDlg = rcDlg.bottom - rcDlg.top + dyGrow; // // Cascade property sheet windows (only for comctl32 and commctrl) // // // HACK: Putting CW_USEDEFAULT in dialog template does not work because // CreateWindowEx ignores it unless the window has WS_OVERLAPPED, which // is not appropriate for a property sheet. // { const TCHAR c_szStatic[] = TEXT("Static"); UINT swp = SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE; if (!IsWindow(ppd->psh.hwndParent)) { HWND hwndT = CreateWindowEx(0, c_szStatic, NULL, WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, NULL, NULL, HINST_THISDLL, NULL); if (hwndT) { GetWindowRect(hwndT, &rcDlg); swp = SWP_NOZORDER | SWP_NOACTIVATE; DestroyWindow(hwndT); } } else { GetWindowRect(ppd->psh.hwndParent, &rcDlg); if (IsWindowVisible(ppd->psh.hwndParent)) { bMirrored = IS_WINDOW_RTL_MIRRORED(ppd->psh.hwndParent); rcDlg.top += g_cySmIcon; if(bMirrored) { rcDlg.left = rcDlg.right - g_cxSmIcon - dxDlg; } else { rcDlg.left += g_cxSmIcon; } } swp = SWP_NOZORDER | SWP_NOACTIVATE; } hMonitor = MonitorFromWindow(hDlg, MONITOR_DEFAULTTONEAREST); mMonitorInfo.cbSize = sizeof(MONITORINFO); if (GetMonitorInfo(hMonitor, &mMonitorInfo)) { if (mMonitorInfo.rcMonitor.right < (rcDlg.left + dxDlg)) { // Move the Window left. rcDlg.left = mMonitorInfo.rcMonitor.right - dxDlg; } if (mMonitorInfo.rcMonitor.left > rcDlg.left) { // Move the Window Right. rcDlg.left = mMonitorInfo.rcMonitor.left; } if (mMonitorInfo.rcMonitor.bottom < (rcDlg.top + dyDlg)) { // Move the Window Up. rcDlg.top = mMonitorInfo.rcMonitor.bottom - dyDlg; } if (mMonitorInfo.rcMonitor.top > rcDlg.top) { // Move the Window Down. rcDlg.top = mMonitorInfo.rcMonitor.top; } } SetWindowPos(hDlg, NULL, rcDlg.left, rcDlg.top, dxDlg, dyDlg, swp); } // Now we'll figure out where the page needs to start relative // to the bottom of the tabs. MapWindowRect(ppd->hwndTabs, hDlg, &rcPage); ppd->xSubDlg = rcPage.left; ppd->ySubDlg = rcPage.top; ppd->cxSubDlg = rcPage.right - rcPage.left; ppd->cySubDlg = rcPage.bottom - rcPage.top; // // move all the buttons down as needed and turn on appropriate buttons // for a wizard. // { RECT rcCtrl; HWND hCtrl; const int *pids; if (ppd->psh.dwFlags & PSH_WIZARD97) { hCtrl = GetDlgItemRect(hDlg, IDD_TOPDIVIDER, &rcCtrl); if (hCtrl) SetWindowPos(hCtrl, NULL, rcCtrl.left, ppd->cyHeaderHeight, RECTWIDTH(rcCtrl) + dxGrow, RECTHEIGHT(rcCtrl), SWP_NOZORDER | SWP_NOACTIVATE); } if (IS_WIZARD(ppd)) { pids = WizIDs; hCtrl = GetDlgItemRect(hDlg, IDD_DIVIDER, &rcCtrl); if (hCtrl) SetWindowPos(hCtrl, NULL, rcCtrl.left, rcCtrl.top + dyGrow, RECTWIDTH(rcCtrl) + dxGrow, RECTHEIGHT(rcCtrl), SWP_NOZORDER | SWP_NOACTIVATE); EnableWindow(GetDlgItem(hDlg, IDD_BACK), TRUE); ppd->idDefaultFallback = IDD_NEXT; } else { pids = IDs; ppd->idDefaultFallback = IDOK; } // first move everything over by the same amount that // the dialog grew by. // If we flipped the buttons, it should be aligned to the left // No move needed MoveAllButtons(hDlg, pids, IDHELP, ppd->fFlipped ? 0 : dxGrow, dyGrow); // If there's no help, then remove the help button. if (!(ppd->psh.dwFlags & PSH_HASHELP)) { RemoveButton(hDlg, IDHELP, pids); } // If we are not a wizard, and we should NOT show apply now if ((ppd->psh.dwFlags & PSH_NOAPPLYNOW) && !IS_WIZARD(ppd)) { RemoveButton(hDlg, IDD_APPLYNOW, pids); } if (IS_WIZARD(ppd) && (!(ppd->psh.dwFlags & PSH_WIZARDHASFINISH))) { DWORD dwStyle=0; RemoveButton(hDlg, IDD_FINISH, pids); // if there's no finish button showing, we need to place it where // the next button is GetWindowRect(GetDlgItem(hDlg, IDD_NEXT), &rcCtrl); MapWindowPoints(HWND_DESKTOP, hDlg, (LPPOINT)&rcCtrl, 2); SetWindowPos(GetDlgItem(hDlg, IDD_FINISH), NULL, rcCtrl.left, rcCtrl.top, RECTWIDTH(rcCtrl), RECTHEIGHT(rcCtrl), SWP_NOZORDER | SWP_NOACTIVATE); } } // (dli) compute the Pattern Brush for the watermark // Note: This is done here because we need to know the size of the big dialog in // case the user wants to stretch the bitmap if (ppd->psh.dwFlags & PSH_WIZARD97) { int cx, cy; ASSERT(ppd->hbmHeader == NULL); ASSERT(ppd->hbmWatermark == NULL); // // WIZARD97IE4 disabled the watermark and header bitmap // if high contrast was turned on. // if (ppd->psh.dwFlags & PSH_WIZARD97IE4) { HIGHCONTRAST hc = {sizeof(hc)}; if (SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(hc), &hc, 0) && (hc.dwFlags & HCF_HIGHCONTRASTON)) { ppd->psh.dwFlags &= ~(PSH_WATERMARK | PSH_USEHBMWATERMARK | PSH_USEHPLWATERMARK | PSH_HEADER | PSH_USEHBMHEADER); } } if ((ppd->psh.dwFlags & PSH_WATERMARK) && ppd->psh.H_hbmWatermark) { // Compute dimensions of final bitmap, which may be slightly // goofy due to stretching cx = cy = 0; // Assume no stretching if (ppd->psh.dwFlags & PSH_STRETCHWATERMARK) { RECT rc; if (ppd->psh.dwFlags & PSH_WIZARD97IE4) { // The WIZARD97IE4 watermark covers the entire dialog if (GetDlgItemRect(hDlg, IDD_DIVIDER, &rc)) { cx = dxDlg; cy = rc.top; } } else { // The WIZARD97IE5 watermark does not stretch // (Too many people passed this flag when converting // from WIZARD97IE4 to WIZARD97IE5 and relied on // the nonstretchability.) } } if (ppd->psh.dwFlags & PSH_USEHBMWATERMARK) { // LR_COPYRETURNORG means "If no stretching was needed, // then just return the original bitmap unaltered." // Note that we need special cleanup if a stretch occurred. ppd->hbmWatermark = (HBITMAP)CopyImage(ppd->psh.H_hbmWatermark, IMAGE_BITMAP, cx, cy, LR_COPYRETURNORG); } else { ppd->hbmWatermark = (HBITMAP)LoadImage(ppd->psh.hInstance, ppd->psh.H_pszbmWatermark, IMAGE_BITMAP, cx, cy, LR_CREATEDIBSECTION); } if (ppd->hbmWatermark) { // If app provides custom palette, then use it, // else create one based on the bmp. (And if the bmp // doesn't have a palette, PaletteFromBmp will use the // halftone palette.) if (ppd->psh.dwFlags & PSH_USEHPLWATERMARK) ppd->hplWatermark = ppd->psh.hplWatermark; else ppd->hplWatermark = PaletteFromBmp(ppd->hbmWatermark); // And WIZARD97IE4 needs to turn it into a bitmap brush. if (ppd->psh.dwFlags & PSH_WIZARD97IE4) ppd->hbrWatermark = CreatePatternBrush(ppd->hbmWatermark); } } if ((ppd->psh.dwFlags & PSH_HEADER) && ppd->psh.H_hbmHeader) { cx = cy = 0; // Assume no stretching if (ppd->psh.dwFlags & PSH_STRETCHWATERMARK) { if (ppd->psh.dwFlags & PSH_WIZARD97IE4) { // The WIZARD97IE4 header covers the entire header cx = dxDlg; cy = ppd->cyHeaderHeight; } else { // The WIZARD97IE5 header does not stretch // (Too many people passed this flag when converting // from WIZARD97IE4 to WIZARD97IE5 and relied on // the nonstretchability.) } } if (ppd->psh.dwFlags & PSH_USEHBMHEADER) { // LR_COPYRETURNORG means "If no stretching was needed, // then just return the original bitmap unaltered." // Note that we need special cleanup if a stretch occurred. ppd->hbmHeader = (HBITMAP)CopyImage(ppd->psh.H_hbmHeader, IMAGE_BITMAP, cx, cy, LR_COPYRETURNORG); } else { ppd->hbmHeader = (HBITMAP)LoadImage(ppd->psh.hInstance, ppd->psh.H_pszbmHeader, IMAGE_BITMAP, cx, cy, LR_CREATEDIBSECTION); } // And WIZARD97IE4 needs to turn it into a bitmap brush. if (ppd->hbmHeader && (ppd->psh.dwFlags & PSH_WIZARD97IE4)) ppd->hbrHeader = CreatePatternBrush(ppd->hbmHeader); } else { // In case the user does not specify a header bitmap // use the top portion of the watermark ppd->hbmHeader = ppd->hbmWatermark; ppd->hbrHeader = ppd->hbrWatermark; } } // force the dialog to reposition itself based on its new size SendMessage(hDlg, DM_REPOSITION, 0, 0L); // do this here instead of using DS_SETFOREGROUND so we don't hose // pages that do things that want to set the foreground window SetForegroundWindow(hDlg); // We set this to 1 if the user saves any changes. // do this before initting or switching to any pages ppd->nReturn = 0; // AppHack - Some people forgot to initialize nStartPage, and they were // lucky that the garbage value on the stack was zero. Lucky no longer. if (nStartPage >= ppd->psh.nPages) { RIPMSG(0, "App forgot to initialize PROPSHEETHEADER.nStartPage field, assuming zero"); nStartPage = 0; } // Now attempt to select the starting page. TabCtrl_SetCurSel(ppd->hwndTabs, nStartPage); PageChange(ppd, 1); // Now init any other pages that require it if (fPrematurePages) { int nPage; tie.tci.mask = TCIF_PARAM; for (nPage = 0; nPage < (int)ppd->psh.nPages; nPage++) { PISP pisp = GETPISP(ppd, nPage); if (!(pisp->_psp.dwFlags & PSP_PREMATURE)) continue; TabCtrl_GetItem(ppd->hwndTabs, nPage, &tie.tci); if (tie.hwndPage) continue; if ((tie.hwndPage = _CreatePage(ppd, pisp, hDlg, langidMUI)) == NULL) { RemovePropPageData(ppd, nPage--); continue; } TabCtrl_SetItem(ppd->hwndTabs, nPage, &tie.tci); } } } HWND _Ppd_GetPage(LPPROPDATA ppd, int nItem) { if (ppd->hwndTabs) { TC_ITEMEXTRA tie; tie.tci.mask = TCIF_PARAM; TabCtrl_GetItem(ppd->hwndTabs, nItem, &tie.tci); return tie.hwndPage; } return NULL; } BOOL _Ppd_IsPageHidden(LPPROPDATA ppd, int nItem) { if (ppd->hwndTabs) { TCITEM tci; tci.mask = TCIF_STATE; tci.dwStateMask = TCIS_HIDDEN; if (TabCtrl_GetItem(ppd->hwndTabs, nItem, &tci)) return tci.dwState; } return FALSE; } LRESULT _Ppd_SendNotify(LPPROPDATA ppd, int nItem, int code, LPARAM lParam) { PSHNOTIFY pshn; pshn.lParam = lParam; return SendNotifyEx(_Ppd_GetPage(ppd,nItem), ppd->hDlg, code, (LPNMHDR)&pshn, FALSE); } // // dwFind = 0 means just move to the current item + iAutoAdjust // dwFind != 0 means it's a dialog resource identifier we should look for // int FindPageIndex(LPPROPDATA ppd, int nCurItem, ULONG_PTR dwFind, LONG_PTR iAutoAdj) { LRESULT nActivate; if (dwFind == 0) { nActivate = nCurItem + iAutoAdj; if (((UINT)nActivate) <= ppd->psh.nPages) { return((int)nActivate); } } else { for (nActivate = 0; (UINT)nActivate < ppd->psh.nPages; nActivate++) { if ((DWORD_PTR)GETPPSP(ppd, nActivate)->P_pszTemplate == dwFind) { return((int)nActivate); } } } return(-1); } // // If hpage != NULL, then return the index of the page which matches it, // or -1 on failure. // int FindPageIndexByHpage(LPPROPDATA ppd, HPROPSHEETPAGE hpage) { int i; // // Notice that we explicitly do not do a InternalizeHPROPSHEETPAGE, // because the app might be passing us garbage. We just want to // say "Nope, can't find garbage here, sorry." // for (i = ppd->psh.nPages - 1; i >= 0; i--) { if (hpage == GETHPAGE(ppd, i)) break; } return i; } // This WM_NEXTDLGCTL stuff works, except for ACT!4.0 which faults randomly // I don't know why. The USER people said that removing a // SetFocus(NULL) call from SetDlgFocus works, but I tried that // and the app merely faulted in a different place. so I'm going // back to the old IE4 way, which means that there are scenarios // where the DEFID can get out of sync with reality. #undef WM_NEXTDLGCTL_WORKS #ifdef WM_NEXTDLGCTL_WORKS // // Helper function that manages dialog box focus in a manner that keeps // USER in the loop, so we don't get "two buttons both with the bold // defpushbutton border" problems. // // We have to use WM_NEXTDLGCTL to fix defid problems, such as this one: // // Right-click My Computer, Properties. // Go to Advanced tab. Click Environment Variables. // Click New. Type a name for a new dummy environment variable. // Click OK. // // At this point (with the old code), the "New" button is a DEFPUSHBUTTON, // but the DEFID is IDOK. The USER folks said I should use WM_NEXTDLGCTL // to avoid this problem. But using WM_NEXTDLGCTL introduces its own big // hairy mess of problems. All the code in this function aside from the // SendMessage(WM_NEXTDLGCTL) are to work around "quirks" in WM_NEXTDLGCTL // or workarounds for app bugs. // // THIS CODE IS SUBTLE AND QUICK TO ANGER! // void SetDlgFocus(LPPROPDATA ppd, HWND hwndFocus) { // // HACK! It's possible that by the time we get around to changing // the dialog focus, the dialog box doesn't have focus any more! // This happens because PSM_SETWIZBUTTONS is a posted message, so // it can arrive *after* focus has moved elsewhere (e.g., to a // MessageBox). // // There is no way to update the dialog box focus without // letting it change the real focus (another "quirk" of // WM_NEXTDLGCTL), so instead we remember who used to have the // focus, let the dialog box do its focus goo, and then restore // the focus as necessary. // HWND hwndFocusPrev = GetFocus(); // If focus belonged to a window within our property sheet, then // let the dialog box code push the focus around. Otherwise, // focus belonged to somebody outside our property sheet, so // remember to restore it after we're done. if (hwndFocusPrev && IsChild(ppd->hDlg, hwndFocusPrev)) hwndFocusPrev = NULL; // USER forgot to revalidate hwndOldFocus at this point, so we have // to exit USER (by returning to comctl32) then re-enter USER // (in the SendMessage below) so parameter validation will happen // again. Sigh. // // Bug in Win9x and NT: WM_NEXTDLGCTL will crash if the previous // focus window destroys itself in response to WM_KILLFOCUS. // (WebTurbo by NetMetrics does this.) There's a missed // revalidation so USER ends up using a window handle after // it has been destroyed. Oops. // // (The NT folks consider this "Won't fix, because the system stays // up; just the app crashes". The 9x folks will try to get the fix // into Win98 OSR.) // // // Do a manual SetFocus here to make the old focus (if any) // do all its WM_KILLFOCUS stuff, and possibly destroy itself (grrr). // // We have to SetFocus to NULL because some apps (e.g., // Visual C 6.0 setup) do funky things on SetFocus, and our early // SetFocus interferes with the EM_SETSEL that WM_NEXTDLGCTL will // do later. // // APP HACK 2: But not if the target focus is the same as the // curreng focus, because ACT!4.0 crashes if it receives a // WM_KILLFOCUS when it is not expecting one. if (hwndFocus != GetFocus()) SetFocus(NULL); // // Note that by manually shoving the focus around, we // have gotten focus and DEFPUSHBUTTON and DEFID all out // of sync, which is exactly the problem we're trying to // avoid! Fortunately, USER also contains special // recovery code to handle the case where somebody "mistakenly" called // SetFocus() to change the focus. (I put "mistakenly" in quotes because // in this case, we did it on purpose.) // SendMessage(ppd->hDlg, WM_NEXTDLGCTL, (WPARAM)hwndFocus, MAKELPARAM(TRUE, 0)); // // If WM_NEXTDLGCTL damaged the focus, fix it. // if (hwndFocusPrev) SetFocus(hwndFocusPrev); } #endif void SetNewDefID(LPPROPDATA ppd) { HWND hDlg = ppd->hDlg; HWND hwndFocus; hwndFocus = GetNextDlgTabItem(ppd->hwndCurPage, NULL, FALSE); ASSERT(hwndFocus); if (hwndFocus) { #ifndef WM_NEXTDLGCTL_WORKS int id; if (((DWORD)SendMessage(hwndFocus, WM_GETDLGCODE, 0, 0L)) & DLGC_HASSETSEL) { // select the text Edit_SetSel(hwndFocus, 0, -1); } id = GetDlgCtrlID(hwndFocus); #endif // // See if the handle give to us by GetNextDlgTabItem was any good. // (For compatibility reasons, if the dialog contains no tabstops, // it returns the first item.) // if ((GetWindowLong(hwndFocus, GWL_STYLE) & (WS_VISIBLE | WS_DISABLED | WS_TABSTOP)) == (WS_VISIBLE | WS_TABSTOP)) { // // Give the page a chance to change the default focus. // HWND hwndT = (HWND)_Ppd_SendNotify(ppd, ppd->nCurItem, PSN_QUERYINITIALFOCUS, (LPARAM)hwndFocus); // The window had better be valid and a child of the page. if (hwndT && IsWindow(hwndT) && IsChild(ppd->hwndCurPage, hwndT)) { hwndFocus = hwndT; } } else { // in prop sheet mode, focus on tabs, // in wizard mode, tabs aren't visible, go to idDefFallback if (IS_WIZARD(ppd)) hwndFocus = GetDlgItem(hDlg, ppd->idDefaultFallback); else hwndFocus = ppd->hwndTabs; } #ifdef WM_NEXTDLGCTL_WORKS // // Aw-right. Go for it. // SetDlgFocus(ppd, hwndFocus); // // Hack for MFC: MFC relies on DM_SETDEFID to know when to // update its wizard buttons. // SendMessage(hDlg, DM_SETDEFID, SendMessage(hDlg, DM_GETDEFID, 0, 0), 0); #else SetFocus(hwndFocus); ResetWizButtons(ppd); if (SendDlgItemMessage(ppd->hwndCurPage, id, WM_GETDLGCODE, 0, 0L) & DLGC_UNDEFPUSHBUTTON) SendMessage(ppd->hwndCurPage, DM_SETDEFID, id, 0); else { SendMessage(hDlg, DM_SETDEFID, ppd->idDefaultFallback, 0); } #endif } } /* ** we are about to change pages. what a nice chance to let the current ** page validate itself before we go away. if the page decides not ** to be de-activated, then this'll cancel the page change. ** ** return TRUE iff this page failed validation */ BOOL PageChanging(LPPROPDATA ppd) { BOOL bRet = FALSE; if (ppd && ppd->hwndCurPage) { bRet = BOOLFROMPTR(_Ppd_SendNotify(ppd, ppd->nCurItem, PSN_KILLACTIVE, 0)); } return bRet; } void PageChange(LPPROPDATA ppd, int iAutoAdj) { HWND hwndCurPage; HWND hwndCurFocus; int nItem; HWND hDlg, hwndTabs; TC_ITEMEXTRA tie; UINT FlailCount = 0; LRESULT lres; if (!ppd) { return; } hDlg = ppd->hDlg; hwndTabs = ppd->hwndTabs; // NOTE: the page was already validated (PSN_KILLACTIVE) before // the actual page change. hwndCurFocus = GetFocus(); TryAgain: FlailCount++; if (FlailCount > ppd->psh.nPages) { DebugMsg(DM_TRACE, TEXT("PropSheet PageChange attempt to set activation more than 10 times.")); return; } nItem = TabCtrl_GetCurSel(hwndTabs); if (nItem < 0) { return; } tie.tci.mask = TCIF_PARAM; TabCtrl_GetItem(hwndTabs, nItem, &tie.tci); hwndCurPage = tie.hwndPage; if (!hwndCurPage) { if ((hwndCurPage = _CreatePage(ppd, GETPISP(ppd, nItem), hDlg, GetMUILanguage())) == NULL) { /* Should we put up some sort of error message here? */ RemovePropPageData(ppd, nItem); TabCtrl_SetCurSel(hwndTabs, 0); goto TryAgain; } // tie.tci.mask = TCIF_PARAM; tie.hwndPage = hwndCurPage; TabCtrl_SetItem(hwndTabs, nItem, &tie.tci); if (HIDEWIZ97HEADER(ppd, nItem)) // Subclass for back ground watermark painting. SetWindowSubclass(hwndCurPage, WizardWndProc, 0, (DWORD_PTR)ppd); } // THI WAS REMOVED as part of the fix for bug 18327. The problem is we need to // send a SETACTIVE message to a page if it is being activated. // if (ppd->hwndCurPage == hwndCurPage) // { // /* we should be done at this point. // */ // return; // } /* Size the dialog and move it to the top of the list before showing ** it in case there is size specific initializing to be done in the ** GETACTIVE message. */ if (IS_WIZARD(ppd)) { HWND hwndTopDivider= GetDlgItem(hDlg, IDD_TOPDIVIDER); if (ppd->psh.dwFlags & PSH_WIZARD97) { HWND hwndDivider; RECT rcDlg, rcDivider; GetClientRect(hDlg, &rcDlg); hwndDivider = GetDlgItemRect(hDlg, IDD_DIVIDER, &rcDivider); if (hwndDivider) SetWindowPos(hwndDivider, NULL, rcDlg.left, rcDivider.top, RECTWIDTH(rcDlg), RECTHEIGHT(rcDivider), SWP_NOZORDER | SWP_NOACTIVATE); if (GETPPSP(ppd, nItem)->dwFlags & PSP_HIDEHEADER) { // In this case, we give the whole dialog box except for the portion under the // Bottom divider to the property page RECT rcTopDivider; ShowWindow(hwndTopDivider, SW_HIDE); ShowWindow(ppd->hwndTabs, SW_HIDE); hwndTopDivider = GetDlgItemRect(hDlg, IDD_DIVIDER, &rcTopDivider); SetWindowPos(hwndCurPage, HWND_TOP, rcDlg.left, rcDlg.top, RECTWIDTH(rcDlg), rcTopDivider.top - rcDlg.top, 0); } else { ShowWindow(hwndTopDivider, SW_SHOW); ShowWindow(ppd->hwndTabs, SW_SHOW); SetWindowPos(hwndCurPage, HWND_TOP, ppd->xSubDlg, ppd->ySubDlg, ppd->cxSubDlg, ppd->cySubDlg, 0); } } else { ShowWindow(hwndTopDivider, SW_HIDE); SetWindowPos(hwndCurPage, HWND_TOP, ppd->xSubDlg, ppd->ySubDlg, ppd->cxSubDlg, ppd->cySubDlg, 0); } } else { RECT rcPage; GetClientRect(ppd->hwndTabs, &rcPage); TabCtrl_AdjustRect(ppd->hwndTabs, FALSE, &rcPage); MapWindowPoints(ppd->hwndTabs, hDlg, (LPPOINT)&rcPage, 2); SetWindowPos(hwndCurPage, HWND_TOP, rcPage.left, rcPage.top, rcPage.right - rcPage.left, rcPage.bottom - rcPage.top, 0); } /* We want to send the SETACTIVE message before the window is visible ** to minimize on flicker if it needs to update fields. */ // // If the page returns non-zero from the PSN_SETACTIVE call then // we will set the activation to the resource ID returned from // the call and set activation to it. This is mainly used by wizards // to skip a step. // lres = _Ppd_SendNotify(ppd, nItem, PSN_SETACTIVE, 0); if (lres) { int iPageIndex = FindPageIndex(ppd, nItem, (lres == -1) ? 0 : lres, iAutoAdj); if ((lres == -1) && (nItem == iPageIndex || iPageIndex >= TabCtrl_GetItemCount(hwndTabs))) { iPageIndex = ppd->nCurItem; } if (iPageIndex != -1) { TabCtrl_SetCurSel(hwndTabs, iPageIndex); ShowWindow(hwndCurPage, SW_HIDE); goto TryAgain; } } if (ppd->psh.dwFlags & PSH_HASHELP) { // PSH_HASHELP controls the "Help" button at the bottom // PSH_NOCONTEXTHELP controls the caption "?" button Button_Enable(GetDlgItem(hDlg, IDHELP), (BOOL)(GETPPSP(ppd, nItem)->dwFlags & PSP_HASHELP)); } // // If this is a wizard then we'll set the dialog's title to the tab // title. // if (IS_WIZARD(ppd)) { TC_ITEMEXTRA tie; TCHAR szTemp[128 + 50]; szTemp[0] = 0; tie.tci.mask = TCIF_TEXT; tie.tci.pszText = szTemp; tie.tci.cchTextMax = ARRAYSIZE(szTemp); if (TabCtrl_GetItem(hwndTabs, nItem, &tie.tci)) { #ifdef WINDOWS_ME tie.tci.mask = TCIF_RTLREADING; tie.tci.cchTextMax = 0; // hack, use cchTextMax to query tab item reading order TabCtrl_GetItem(hwndTabs, nItem, &tie.tci); if( (ppd->psh.dwFlags & PSH_RTLREADING) || (tie.tci.cchTextMax)) SetWindowLong(hDlg, GWL_EXSTYLE, GetWindowLong(hDlg, GWL_EXSTYLE) | WS_EX_RTLREADING); else SetWindowLong(hDlg, GWL_EXSTYLE, GetWindowLong(hDlg, GWL_EXSTYLE) & ~WS_EX_RTLREADING); #endif // WINDOWS_ME if (szTemp[0]) SetWindowText(hDlg, szTemp); } } /* Disable all erasebkgnd messages that come through because windows ** are getting shuffled. Note that we need to call ShowWindow (and ** not show the window in some other way) because DavidDs is counting ** on the correct parameters to the WM_SHOWWINDOW message, and we may ** document how to keep your page from flashing. */ ppd->fFlags |= PD_NOERASE; ShowWindow(hwndCurPage, SW_SHOW); if (ppd->hwndCurPage && (ppd->hwndCurPage != hwndCurPage)) { ShowWindow(ppd->hwndCurPage, SW_HIDE); } ppd->fFlags &= ~PD_NOERASE; ppd->hwndCurPage = hwndCurPage; ppd->nCurItem = nItem; /* Newly created dialogs seem to steal the focus, so we steal it back ** to the page list, which must have had the focus to get to this ** point. If this is a wizard then set the focus to the dialog of ** the page. Otherwise, set the focus to the tabs. */ if (hwndCurFocus != hwndTabs) { SetNewDefID(ppd); } else { // The focus may have been stolen from us, bring it back SendMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)hwndTabs, (LPARAM)TRUE); } // make sure the header is repaint if ((ppd->psh.dwFlags & PSH_WIZARD97) && (!(GETPPSP(ppd, nItem)->dwFlags & PSP_HIDEHEADER))) InvalidateRect(hDlg, NULL,TRUE); } #define DECLAREWAITCURSOR HCURSOR hcursor_wait_cursor_save #define SetWaitCursor() hcursor_wait_cursor_save = SetCursor(LoadCursor(NULL, IDC_WAIT)) #define ResetWaitCursor() SetCursor(hcursor_wait_cursor_save) // // HACKHACK (reinerf) // // This function sends the PSN_LASTCHANCEAPPLY right after the property sheets have had "ok" // pressed. This allows the "General" tab on the file/folder properties to do a rename, so that // it wont rename the file out from under the other pages, and have them barf when they go to // persist their info. // void SendLastChanceApply(LPPROPDATA ppd) { TC_ITEMEXTRA tie; int nItem; int nItems = TabCtrl_GetItemCount(ppd->hwndTabs); tie.tci.mask = TCIF_PARAM; // we start with the last tab and count towards the first. This ensures // that the more important tabs (such as the "General" tab) will be the last // to recieve the PSN_LASTCHANCEAPPLY message. for (nItem = nItems - 1; nItem >= 0; nItem--) { TabCtrl_GetItem(ppd->hwndTabs, nItem, &tie.tci); if (tie.hwndPage) { // we ignore the return vale from the PSN_LASTCHANCEAPPLY message since // there are probably prop sheet extensions that return both TRUE and // FALSE for messages that they dont process...(sigh) _Ppd_SendNotify(ppd, nItem, PSN_LASTCHANCEAPPLY, (LPARAM)TRUE); } } } #ifdef MAINWIN EXTERN_C HKEY HKEY_ROOT; #endif // return TRUE iff all sheets successfully handle the notification BOOL ButtonPushed(LPPROPDATA ppd, WPARAM wParam) { HWND hwndTabs; int nItems, nItem; int nNotify; TC_ITEMEXTRA tie; BOOL bExit = FALSE; int nReturnNew = ppd->nReturn; int fSuccess = TRUE; DECLAREWAITCURSOR; LRESULT lres = 0; LPARAM lParam = FALSE; LPARAM nButtonPressed = 0; switch (wParam) { case IDOK: nButtonPressed = PSBTN_OK; lParam = TRUE; bExit = TRUE; // Fall through... case IDD_APPLYNOW: if (IDD_APPLYNOW == wParam) { nButtonPressed = PSBTN_APPLYNOW; } // First allow the current dialog to validate itself. if (_Ppd_SendNotify(ppd, ppd->nCurItem, PSN_KILLACTIVE, 0)) return FALSE; nReturnNew = 1; nNotify = PSN_APPLY; break; case IDCLOSE: nButtonPressed = PSBTN_FINISH; lParam = TRUE; // fall through case IDCANCEL: if (IDCANCEL == wParam) { nButtonPressed = PSBTN_CANCEL; } bExit = TRUE; nNotify = PSN_RESET; break; default: return FALSE; } if (ppd && ppd->psh.pfnCallback && nButtonPressed) { ppd->psh.pfnCallback(NULL, PSCB_BUTTONPRESSED, nButtonPressed); } SetWaitCursor(); hwndTabs = ppd->hwndTabs; tie.tci.mask = TCIF_PARAM; nItems = TabCtrl_GetItemCount(hwndTabs); for (nItem = 0; nItem < nItems; ++nItem) { TabCtrl_GetItem(hwndTabs, nItem, &tie.tci); if (tie.hwndPage) { /* If the dialog fails a PSN_APPY call (by returning TRUE), ** then it has invalid information on it (should be verified ** on the PSN_KILLACTIVE, but that is not always possible) ** and we want to abort the notifications. We select the failed ** page below. */ lres = _Ppd_SendNotify(ppd, nItem, nNotify, lParam); if (lres) { fSuccess = FALSE; bExit = FALSE; break; } else { // if we need a restart (Apply or OK), then this is an exit if ((nNotify == PSN_APPLY) && !bExit && ppd->nRestart) { DebugMsg(DM_TRACE, TEXT("PropertySheet: restart flags force close")); bExit = TRUE; } } /* We have either reset or applied, so everything is ** up to date. */ tie.state &= ~FLAG_CHANGED; // tie.tci.mask = TCIF_PARAM; // already set TabCtrl_SetItem(hwndTabs, nItem, &tie.tci); } } #ifdef MAINWIN // This is a temporary solution for saving the // registry options incase IE doesnot shutdown // normaly. if( nNotify == PSN_APPLY ) RegSaveKey(HKEY_ROOT, NULL, NULL); #endif /* If we leave ppd->hwndCurPage as NULL, it will tell the main ** loop to exit. */ if (fSuccess) { ppd->hwndCurPage = NULL; } else if (lres != PSNRET_INVALID_NOCHANGEPAGE) { // Need to change to the page that caused the failure. // if lres == PSN_INVALID_NOCHANGEPAGE, then assume sheet has already // changed to the page with the invalid information on it TabCtrl_SetCurSel(hwndTabs, nItem); } if (fSuccess) { // Set to the cached value ppd->nReturn = nReturnNew; } if (!bExit) { // before PageChange, so ApplyNow gets disabled faster. if (fSuccess) { TCHAR szOK[30]; HWND hwndApply; if (!IS_WIZARD(ppd)) { // The ApplyNow button should always be disabled after // a successfull apply/cancel, since no change has been made yet. hwndApply = GetDlgItem(ppd->hDlg, IDD_APPLYNOW); Button_SetStyle(hwndApply, BS_PUSHBUTTON, TRUE); EnableWindow(hwndApply, FALSE); ResetWizButtons(ppd); SendMessage(ppd->hDlg, DM_SETDEFID, IDOK, 0); ppd->idDefaultFallback = IDOK; } // Undo PSM_CANCELTOCLOSE for the same reasons. if (ppd->fFlags & PD_CANCELTOCLOSE) { ppd->fFlags &= ~PD_CANCELTOCLOSE; LocalizedLoadString(IDS_OK, szOK, ARRAYSIZE(szOK)); SetDlgItemText(ppd->hDlg, IDOK, szOK); EnableWindow(GetDlgItem(ppd->hDlg, IDCANCEL), TRUE); } } /* Re-"select" the current item and get the whole list to ** repaint. */ if (lres != PSNRET_INVALID_NOCHANGEPAGE) PageChange(ppd, 1); } ResetWaitCursor(); return(fSuccess); } // Win3.1 USER didn't handle DM_SETDEFID very well-- it's very possible to get // multiple buttons with the default button style look. This has been fixed // for Win95, but the Setup wizard needs this hack when running from 3.1. // it seems win95 doesn't handle it well either.. void ResetWizButtons(LPPROPDATA ppd) { int id; if (IS_WIZARD(ppd)) { for (id = 0; id < ARRAYSIZE(WizIDs); id++) SendDlgItemMessage(ppd->hDlg, WizIDs[id], BM_SETSTYLE, BS_PUSHBUTTON, TRUE); } } void SetWizButtons(LPPROPDATA ppd, LPARAM lParam) { int idDef; int iShowID = IDD_NEXT; int iHideID = IDD_FINISH; BOOL bEnabled; BOOL bResetFocus; HWND hwndShow; HWND hwndFocus = GetFocus(); HWND hwndHide; HWND hwndBack; HWND hDlg = ppd->hDlg; idDef = (int)LOWORD(SendMessage(hDlg, DM_GETDEFID, 0, 0)); // Enable/Disable the IDD_BACK button hwndBack = GetDlgItem(hDlg, IDD_BACK); bEnabled = (lParam & PSWIZB_BACK) != 0; EnableWindow(hwndBack, bEnabled); // Enable/Disable the IDD_NEXT button, and Next gets shown by default // bEnabled remembers whether hwndShow should be enabled or not hwndShow = GetDlgItem(hDlg, IDD_NEXT); bEnabled = (lParam & PSWIZB_NEXT) != 0; EnableWindow(hwndShow, bEnabled); // Enable/Disable Show/Hide the IDD_FINISH button if (lParam & (PSWIZB_FINISH | PSWIZB_DISABLEDFINISH)) { iShowID = IDD_FINISH; // If Finish is being shown iHideID = IDD_NEXT; // then Next isn't hwndShow = GetDlgItem(hDlg, IDD_FINISH); bEnabled = (lParam & PSWIZB_FINISH) != 0; EnableWindow(hwndShow, bEnabled); } if (!(ppd->psh.dwFlags & PSH_WIZARDHASFINISH)) { hwndHide = GetDlgItem(hDlg, iHideID); ShowWindow(hwndHide, SW_HIDE); // Cannot disable the window; see Prsht_ButtonSubclassProc for explanation. // WRONG - EnableWindow(hwndHide, FALSE); hwndShow = GetDlgItem(hDlg, iShowID); // Cannot disable the window; see Prsht_ButtonSubclassProc for explanation. // WRONG - EnableWindow(hwndShow, bEnabled); ShowWindow(hwndShow, SW_SHOW); } // bResetFocus keeps track of whether or not we need to set Focus to our button bResetFocus = FALSE; if (hwndFocus) { // if the dude that has focus is a button, we want to steal focus away // so users can just press enter all the way through a property sheet, // getting the default as they go. this also catches the case // of where focus is on one of our buttons which was turned off. if (SendMessage(hwndFocus, WM_GETDLGCODE, 0, 0L) & (DLGC_UNDEFPUSHBUTTON|DLGC_DEFPUSHBUTTON)) bResetFocus = TRUE; } if (!bResetFocus) { // if there is no focus or we're focused on an invisible/disabled // item on the sheet, grab focus. bResetFocus = !hwndFocus || !IsWindowVisible(hwndFocus) || !IsWindowEnabled(hwndFocus) ; } // We used to do this code only if we nuked a button which had default // or if bResetFocus. Unfortunately, some wizards turn off BACK+NEXT // and then when they turn them back on, they want DEFID on NEXT. // So now we always reset DEFID. { static const int ids[4] = { IDD_NEXT, IDD_FINISH, IDD_BACK, IDCANCEL }; int i; HWND hwndNewFocus = NULL; for (i = 0; i < ARRAYSIZE(ids); i++) { hwndNewFocus = GetDlgItem(hDlg, ids[i]); // can't do IsVisible because we may be doing this // before the prop sheet as a whole is shown if ((GetWindowLong(hwndNewFocus, GWL_STYLE) & WS_VISIBLE) && IsWindowEnabled(hwndNewFocus)) { hwndFocus = hwndNewFocus; break; } } ppd->idDefaultFallback = ids[i]; if (bResetFocus) { if (!hwndNewFocus) hwndNewFocus = hDlg; #ifdef WM_NEXTDLGCTL_WORKS SetDlgFocus(ppd, hwndNewFocus); #else // 337614 - Since PSM_SETWIZBUTTONS is often a posted message, // we may end up here when we don't even have focus at all // (caller went on and called MessageBox or something before // we got a chance to set the buttons). So do this only if // focus belongs to our dialog box (or if it's nowhere). hwndFocus = GetFocus(); if (!hwndFocus || (ppd->hDlg == hwndFocus || IsChild(ppd->hDlg, hwndFocus))) SetFocus(hwndNewFocus); #endif } ResetWizButtons(ppd); SendMessage(hDlg, DM_SETDEFID, ids[i], 0); } } // // lptie = NULL means "I don't care about the other goop, just give me // the index." // int FindItem(HWND hwndTabs, HWND hwndPage, TC_ITEMEXTRA * lptie) { int i; TC_ITEMEXTRA tie; if (!lptie) { tie.tci.mask = TCIF_PARAM; lptie = &tie; } for (i = TabCtrl_GetItemCount(hwndTabs) - 1; i >= 0; --i) { TabCtrl_GetItem(hwndTabs, i, &lptie->tci); if (lptie->hwndPage == hwndPage) { break; } } //this will be -1 if the for loop falls out. return i; } // a page is telling us that something on it has changed and thus // "Apply Now" should be enabled void PageInfoChange(LPPROPDATA ppd, HWND hwndPage) { int i; TC_ITEMEXTRA tie; tie.tci.mask = TCIF_PARAM; i = FindItem(ppd->hwndTabs, hwndPage, &tie); if (i == -1) return; if (!(tie.state & FLAG_CHANGED)) { // tie.tci.mask = TCIF_PARAM; // already set tie.state |= FLAG_CHANGED; TabCtrl_SetItem(ppd->hwndTabs, i, &tie.tci); } if (ppd->fAllowApply) EnableWindow(GetDlgItem(ppd->hDlg, IDD_APPLYNOW), TRUE); } // a page is telling us that everything has reverted to its last // saved state. void PageInfoUnChange(LPPROPDATA ppd, HWND hwndPage) { int i; TC_ITEMEXTRA tie; tie.tci.mask = TCIF_PARAM; i = FindItem(ppd->hwndTabs, hwndPage, &tie); if (i == -1) return; if (tie.state & FLAG_CHANGED) { tie.state &= ~FLAG_CHANGED; TabCtrl_SetItem(ppd->hwndTabs, i, &tie.tci); } // check all the pages, if none are FLAG_CHANGED, disable IDD_APLYNOW for (i = ppd->psh.nPages-1 ; i >= 0 ; i--) { if (!TabCtrl_GetItem(ppd->hwndTabs, i, &tie.tci)) break; if (tie.state & FLAG_CHANGED) break; } if (i<0) EnableWindow(GetDlgItem(ppd->hDlg, IDD_APPLYNOW), FALSE); } HDWP Prsht_RepositionControl(LPPROPDATA ppd, HWND hwnd, HDWP hdwp, int dxMove, int dyMove, int dxSize, int dySize) { if (hwnd) { RECT rc; GetWindowRect(hwnd, &rc); MapWindowRect(HWND_DESKTOP, ppd->hDlg, &rc); hdwp = DeferWindowPos(hdwp, hwnd, NULL, rc.left + dxMove, rc.top + dyMove, RECTWIDTH(rc) + dxSize, RECTHEIGHT(rc) + dySize, SWP_NOZORDER | SWP_NOACTIVATE); } return hdwp; } // // dxSize/(dySize+dyMove) is the amount by which to resize the tab control. // dxSize/dySize controls how much the dialog should be grown. // Buttons move by (dxSize, dySize+dyMove). // BOOL Prsht_ResizeDialog(LPPROPDATA ppd, int dxSize, int dySize, int dyMove) { BOOL fChanged = dxSize || dySize || dyMove; if (fChanged) { int dxMove = 0; // To make the code more symmetric in x and y int dxAll = dxSize + dxMove; int dyAll = dySize + dyMove; RECT rc; UINT i; const int *rgid; UINT cid; HDWP hdwp; HWND hwnd; // Use DeferWindowPos to avoid flickering. We expect to move // the tab control, up to five buttons, two possible dividers, // plus the current page. (And a partridge in a pear tree.) // hdwp = BeginDeferWindowPos(1 + 5 + 2 + 1); // The tab control just sizes. hdwp = Prsht_RepositionControl(ppd, ppd->hwndTabs, hdwp, 0, 0, dxAll, dyAll); // // Move and size the current page. We can't trust its location // or size, since PageChange shoves it around without updating // ppd->ySubDlg. // if (ppd->hwndCurPage) { hdwp = DeferWindowPos(hdwp, ppd->hwndCurPage, NULL, ppd->xSubDlg, ppd->ySubDlg, ppd->cxSubDlg, ppd->cySubDlg, SWP_NOZORDER | SWP_NOACTIVATE); } // // And our buttons just move by both the size and move (since they // lie below both the tabs and the pages). // if (IS_WIZARD(ppd)) { // // Ooh, wait, reposition the separator lines, too. // Moves vertically but resizes horizontally. // hwnd = GetDlgItem(ppd->hDlg, IDD_DIVIDER); hdwp = Prsht_RepositionControl(ppd, hwnd, hdwp, 0, dyAll, dxAll, 0); // // The top divider does not move vertically since it lies // above the area that is changing. // hwnd = GetDlgItem(ppd->hDlg, IDD_TOPDIVIDER); hdwp = Prsht_RepositionControl(ppd, hwnd, hdwp, 0, 0, dxAll, 0); rgid = WizIDs; cid = ARRAYSIZE(WizIDs); } else { rgid = IDs; cid = ARRAYSIZE(IDs); } for (i = 0 ; i < cid; i++) { hwnd = GetDlgItem(ppd->hDlg, rgid[i]); hdwp = Prsht_RepositionControl(ppd, hwnd, hdwp, dxAll, dyAll, 0, 0); } // All finished sizing and moving. Let 'er rip! if (hdwp) EndDeferWindowPos(hdwp); // Grow ourselves as well GetWindowRect(ppd->hDlg, &rc); SetWindowPos(ppd->hDlg, NULL, 0, 0, RECTWIDTH(rc) + dxAll, RECTHEIGHT(rc) + dyAll, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); } return fChanged; } BOOL Prsht_RecalcPageSizes(LPPROPDATA ppd) { SIZE siz; int dxSize = 0, dySize = 0, dyMove = 0; // After inserting or removing a page, the tab control may have // changed height. If so, then we need to resize ourselves to // accomodate the growth or shrinkage, so that all the tabs remain // visible. // // APP COMPAT! We cannot do this by default because Jamba 1.1 // **FAULTS** if the property sheet changes size after creation. // Grrrrrr... // Wizards don't have a visible tab control, // so do this only for non-wizards if (!IS_WIZARD(ppd)) { RECT rc; // Get the client rect of the tab control in dialog coords GetClientRect(ppd->hwndTabs, &rc); MapWindowRect(ppd->hwndTabs, ppd->hDlg, &rc); // See how many rows there are now TabCtrl_AdjustRect(ppd->hwndTabs, FALSE, &rc); // rc.top is the new ySubDlg. Compute the amount we have to move. dyMove = rc.top - ppd->ySubDlg; ppd->ySubDlg = rc.top; } Prsht_GetIdealPageSize(ppd, &siz, GIPS_SKIPEXTERIOR97HEIGHT); dxSize = siz.cx - ppd->cxSubDlg; dySize = siz.cy - ppd->cySubDlg; ppd->cxSubDlg = siz.cx; ppd->cySubDlg = siz.cy; return Prsht_ResizeDialog(ppd, dxSize, dySize, dyMove); } // // InsertPropPage // // hpage is the page being inserted. // // hpageInsertAfter described where it should be inserted. // // hpageInsertAfter can be... // // MAKEINTRESOURCE(index) to insert at a specific index. // // NULL to insert at the beginning // // an HPROPSHEETPAGE to insert *after* that page // BOOL InsertPropPage(LPPROPDATA ppd, PSP * hpageInsertAfter, PSP * hpage) { TC_ITEMEXTRA tie; int nPage; HIMAGELIST himl; PAGEINFOEX pi; PISP pisp; int idx; hpage = _Hijaak95Hack(ppd, hpage); if (!hpage) return FALSE; if (ppd->psh.nPages >= MAXPROPPAGES) return FALSE; // we're full if (IS_INTRESOURCE(hpageInsertAfter)) { // Inserting by index idx = (int) PtrToLong(hpageInsertAfter); // Attempting to insert past the end is the same as appending. if (idx > (int)ppd->psh.nPages) idx = (int)ppd->psh.nPages; } else { // Inserting by hpageInsertAfter. for (idx = 0; idx < (int)(ppd->psh.nPages); idx++) { if (hpageInsertAfter == GETHPAGE(ppd, idx)) break; } if (idx >= (int)(ppd->psh.nPages)) return FALSE; // hpageInsertAfter not found idx++; // idx Points to the insertion location (to the right of hpageInsertAfter) ASSERT(hpageInsertAfter == GETHPAGE(ppd, idx-1)); } ASSERT(idx <= (int)(ppd->psh.nPages+1)); // Shift all pages adjacent to the insertion point to the right for (nPage=ppd->psh.nPages - 1; nPage >= idx; nPage--) SETPISP(ppd, nPage+1, GETPISP(ppd, nPage)); // Insert the new page pisp = InternalizeHPROPSHEETPAGE(hpage); SETPISP(ppd, idx, pisp); ppd->psh.nPages++; himl = TabCtrl_GetImageList(ppd->hwndTabs); if (!GetPageInfoEx(ppd, pisp, &pi, GetMUILanguage(), GPI_ICON | GPI_BRTL | GPI_CAPTION | GPI_FONT | GPI_DIALOGEX)) { DebugMsg(DM_ERROR, TEXT("InsertPropPage: GetPageInfo failed")); goto bogus; } Prsht_ComputeIdealPageSize(ppd, pisp, &pi); #ifndef WINDOWS_ME tie.tci.mask = TCIF_TEXT | TCIF_PARAM | TCIF_IMAGE; #else tie.tci.mask = TCIF_TEXT | TCIF_PARAM | TCIF_IMAGE | (pi.bRTL ? TCIF_RTLREADING : 0); #endif tie.hwndPage = NULL; tie.tci.pszText = pi.szCaption; tie.state = 0; if (pi.hIcon) { if (himl) tie.tci.iImage = ImageList_AddIcon(himl, pi.hIcon); DestroyIcon(pi.hIcon); } else { tie.tci.iImage = -1; } // Insert the page into the tab list TabCtrl_InsertItem(ppd->hwndTabs, idx, &tie.tci); // If this page wants premature initialization then init it // do this last so pages can rely on "being there" at init time if (pisp->_psp.dwFlags & PSP_PREMATURE) { if ((tie.hwndPage = _CreatePage(ppd, pisp, ppd->hDlg, GetMUILanguage())) == NULL) { TabCtrl_DeleteItem(ppd->hwndTabs, idx); // don't free the pisp here let the caller do it // REARCHITECT raymondc - but caller doesn't know if hIcon has been destroyed goto bogus; } tie.tci.mask = TCIF_PARAM; TabCtrl_SetItem(ppd->hwndTabs, idx, &tie.tci); } // Adjust the internally track current item if it is to the right of our insertion point if (ppd->nCurItem >= idx) ppd->nCurItem++; return TRUE; bogus: // Shift everything back for (nPage=idx; nPage < (int)(ppd->psh.nPages-1); nPage++) SETPISP(ppd, nPage, GETPISP(ppd, nPage+1)); ppd->psh.nPages--; return FALSE; } #define AddPropPage(ppd, hpage) InsertPropPage(ppd, (LPVOID)MAKEINTRESOURCE(-1), hpage) // removes property sheet hpage (index if NULL) void RemovePropPage(LPPROPDATA ppd, int index, HPROPSHEETPAGE hpage) { int i = -1; BOOL fReturn = TRUE; TC_ITEMEXTRA tie; // // Notice that we explicitly do not do a InternalizeHPROPSHEETPAGE, // because the app might be passing us garbage. We just want to // say "Nope, can't find garbage here, sorry." // tie.tci.mask = TCIF_PARAM; if (hpage) { i = FindPageIndexByHpage(ppd, hpage); } if (i == -1) { i = index; // this catches i < 0 && i >= (int)(ppd->psh.nPages) if ((UINT)i >= ppd->psh.nPages) { DebugMsg(DM_ERROR, TEXT("RemovePropPage: invalid page")); return; } } index = TabCtrl_GetCurSel(ppd->hwndTabs); if (i == index) { // if we're removing the current page, select another (don't worry // about this page having invalid information on it -- we're nuking it) PageChanging(ppd); if (index == 0) index++; else index--; if (SendMessage(ppd->hwndTabs, TCM_SETCURSEL, index, 0L) == -1) { // if we couldn't select (find) the new one, punt to 0th SendMessage(ppd->hwndTabs, TCM_SETCURSEL, 0, 0L); } PageChange(ppd, 1); } // REARCHITECT if removing a page below ppd->nCurItem, need to update // nCurItem to prevent it from getting out of sync with hwndCurPage? tie.tci.mask = TCIF_PARAM; TabCtrl_GetItem(ppd->hwndTabs, i, &tie.tci); if (tie.hwndPage) { if (ppd->hwndCurPage == tie.hwndPage) ppd->hwndCurPage = NULL; DestroyWindow(tie.hwndPage); } RemovePropPageData(ppd, i); } void RemovePropPageData(LPPROPDATA ppd, int nPage) { ULONG_PTR dw = PropPageActivateContext(ppd, GETPISP(ppd, nPage)); TabCtrl_DeleteItem(ppd->hwndTabs, nPage); DestroyPropertySheetPage(GETHPAGE(ppd, nPage)); PropPageDeactivateContext(dw); // // Delete the HPROPSHEETPAGE from our table and slide everybody down. // ppd->psh.nPages--; hmemcpy(&ppd->psh.H_phpage[nPage], &ppd->psh.H_phpage[nPage + 1], sizeof(ppd->psh.H_phpage[0]) * (ppd->psh.nPages - nPage)); } // returns TRUE iff the page was successfully set to index/hpage // Note: The iAutoAdj should be set to 1 or -1. This value is used // by PageChange if a page refuses a SETACTIVE to either increment // or decrement the page index. BOOL PageSetSelection(LPPROPDATA ppd, int index, HPROPSHEETPAGE hpage, int iAutoAdj) { int i = -1; BOOL fReturn = FALSE; TC_ITEMEXTRA tie; tie.tci.mask = TCIF_PARAM; if (hpage) { for (i = ppd->psh.nPages - 1; i >= 0; i--) { if (hpage == GETHPAGE(ppd, i)) break; } } if (i == -1) { if (index == -1) return FALSE; i = index; } if (i >= MAXPROPPAGES) { // don't go off the end of our HPROPSHEETPAGE array return FALSE; } fReturn = !PageChanging(ppd); if (fReturn) { index = TabCtrl_GetCurSel(ppd->hwndTabs); if (SendMessage(ppd->hwndTabs, TCM_SETCURSEL, i, 0L) == -1) { // if we couldn't select (find) the new one, fail out // and restore the old one SendMessage(ppd->hwndTabs, TCM_SETCURSEL, index, 0L); fReturn = FALSE; } PageChange(ppd, iAutoAdj); } return fReturn; } LRESULT QuerySiblings(LPPROPDATA ppd, WPARAM wParam, LPARAM lParam) { UINT i; for (i = 0 ; i < ppd->psh.nPages ; i++) { HWND hwndSibling = _Ppd_GetPage(ppd, i); if (hwndSibling) { LRESULT lres = SendMessage(hwndSibling, PSM_QUERYSIBLINGS, wParam, lParam); if (lres) return lres; } } return FALSE; } // REVIEW HACK This gets round the problem of having a hotkey control // up and trying to enter the hotkey that is already in use by a window. BOOL HandleHotkey(LPARAM lparam) { WORD wHotkey; TCHAR szClass[32]; HWND hwnd; // What hotkey did the user type hit? wHotkey = (WORD)SendMessage((HWND)lparam, WM_GETHOTKEY, 0, 0); // Were they typing in a hotkey window? hwnd = GetFocus(); GetClassName(hwnd, szClass, ARRAYSIZE(szClass)); if (lstrcmp(szClass, HOTKEY_CLASS) == 0) { // Yes. SendMessage(hwnd, HKM_SETHOTKEY, wHotkey, 0); return TRUE; } return FALSE; } // // Function handles Next and Back functions for wizards. The code will // be either PSN_WIZNEXT or PSN_WIZBACK // BOOL WizNextBack(LPPROPDATA ppd, int code) { LRESULT dwFind; int iPageIndex; int iAutoAdj = (code == PSN_WIZNEXT) ? 1 : -1; dwFind = _Ppd_SendNotify(ppd, ppd->nCurItem, code, 0); if (dwFind == -1) { return(FALSE); } iPageIndex = FindPageIndex(ppd, ppd->nCurItem, dwFind, iAutoAdj); if (iPageIndex == -1) { return(FALSE); } return(PageSetSelection(ppd, iPageIndex, NULL, iAutoAdj)); } BOOL Prsht_OnCommand(LPPROPDATA ppd, int id, HWND hwndCtrl, UINT codeNotify) { // // There's a bug in USER that when the user highlights a defpushbutton // and presses ENTER, the WM_COMMAND is sent to the top-level dialog // (i.e., the property sheet) instead of to the parent of the button. // So if a property sheet page has a control whose ID coincidentally // matches any of our own, we will think it's ours instead of theirs. if (hwndCtrl && GetParent(hwndCtrl) != ppd->hDlg) goto Forward; if (!hwndCtrl) hwndCtrl = GetDlgItem(ppd->hDlg, id); switch (id) { case IDCLOSE: case IDCANCEL: if (_Ppd_SendNotify(ppd, ppd->nCurItem, PSN_QUERYCANCEL, 0) == 0) { ButtonPushed(ppd, id); } break; case IDD_APPLYNOW: case IDOK: if (!IS_WIZARD(ppd)) { //ButtonPushed returns true if and only if all pages have processed PSN_LASTCHANCEAPPLY if (ButtonPushed(ppd, id)) { //Everyone has processed the PSN_APPLY Message. Now send PSN_LASTCHANCEAPPLY message. // // HACKHACK (reinerF) // // We send out a private PSN_LASTCHANCEAPPLY message telling all the pages // that everyone is done w/ the apply. This is needed for pages who have to do // something after every other page has applied. Currently, the "General" tab // of the file properties needs a last-chance to rename files as well as new print // dialog in comdlg32.dll. SendLastChanceApply(ppd); } } break; case IDHELP: if (IsWindowEnabled(hwndCtrl)) { _Ppd_SendNotify(ppd, ppd->nCurItem, PSN_HELP, 0); } break; case IDD_FINISH: { HWND hwndNewFocus; EnableWindow(ppd->hDlg, FALSE); hwndNewFocus = (HWND)_Ppd_SendNotify(ppd, ppd->nCurItem, PSN_WIZFINISH, 0); // b#11346 - dont let multiple clicks on FINISH. if (!hwndNewFocus) { ppd->hwndCurPage = NULL; ppd->nReturn = 1; } else { EnableWindow(ppd->hDlg, TRUE); if (IsWindow(hwndNewFocus) && IsChild(ppd->hDlg, hwndNewFocus)) #ifdef WM_NEXTDLGCTL_WORKS SetDlgFocus(ppd, hwndNewFocus); #else SetFocus(hwndNewFocus); #endif } } break; case IDD_NEXT: case IDD_BACK: ppd->idDefaultFallback = id; WizNextBack(ppd, id == IDD_NEXT ? PSN_WIZNEXT : PSN_WIZBACK); break; default: Forward: FORWARD_WM_COMMAND(_Ppd_GetPage(ppd, ppd->nCurItem), id, hwndCtrl, codeNotify, SendMessage); } return TRUE; } BOOL Prop_IsDialogMessage(LPPROPDATA ppd, LPMSG32 pmsg32) { // Don't fault if we don't have a pointer... if (!pmsg32) return FALSE; if ((pmsg32->message == WM_KEYDOWN) && (GetKeyState(VK_CONTROL) < 0)) { BOOL bBack = FALSE; switch (pmsg32->wParam) { case VK_TAB: bBack = GetKeyState(VK_SHIFT) < 0; break; case VK_PRIOR: // VK_PAGE_UP case VK_NEXT: // VK_PAGE_DOWN bBack = (pmsg32->wParam == VK_PRIOR); break; default: goto NoKeys; } //notify of navigation key usage SendMessage(ppd->hDlg, WM_CHANGEUISTATE, MAKELONG(UIS_CLEAR, UISF_HIDEFOCUS | UISF_HIDEACCEL), 0); if (IS_WIZARD(ppd)) { int idWiz; int idDlg; HWND hwnd; if (bBack) { idWiz = PSN_WIZBACK; idDlg = IDD_BACK; } else { idWiz = PSN_WIZNEXT; idDlg = IDD_NEXT; } hwnd = GetDlgItem(ppd->hDlg, idDlg); if (IsWindowVisible(hwnd) && IsWindowEnabled(hwnd)) WizNextBack(ppd, idWiz); } else { int iStart = TabCtrl_GetCurSel(ppd->hwndTabs); int iCur; // // Skip over hidden tabs, but don't go into an infinite loop. // iCur = iStart; do { // tab in reverse if shift is down if (bBack) iCur += (ppd->psh.nPages - 1); else iCur++; iCur %= ppd->psh.nPages; } while (_Ppd_IsPageHidden(ppd, iCur) && iCur != iStart); PageSetSelection(ppd, iCur, NULL, 1); } return TRUE; } NoKeys: // // Since we now send out a PSN_TRANSLATEACCELERATOR, add a // short-circuit so we don't do all this work for things // that can't possibly be accelerators. // if (pmsg32->message >= WM_KEYFIRST && pmsg32->message <= WM_KEYLAST && // And there had better be a target window... pmsg32->hwnd && // and the target window must live either outside the propsheet // altogether or completely inside the propsheet page. // (This is so that the propsheet can display its own popup dialog, // but can't futz with the tab control or OK/Cancel buttons.) (!IsChild(ppd->hDlg, pmsg32->hwnd) || IsChild(ppd->hwndCurPage, pmsg32->hwnd)) && // Then ask the propsheet if he wants to eat it. _Ppd_SendNotify(ppd, ppd->nCurItem, PSN_TRANSLATEACCELERATOR, (LPARAM)pmsg32) == PSNRET_MESSAGEHANDLED) return TRUE; if (IsDialogMessage32(ppd->hDlg, pmsg32, TRUE)) return TRUE; return FALSE; } HRESULT Prsht_GetObject (LPPROPDATA ppd, HWND hDlg, int iItem, const IID *piid, void **pObject) { TC_ITEMEXTRA tie; NMOBJECTNOTIFY non; PISP pisp = GETPISP(ppd, iItem); *pObject = NULL; tie.tci.mask = TCIF_PARAM; TabCtrl_GetItem(ppd->hwndTabs, iItem, &tie.tci); if (!tie.hwndPage && ((tie.hwndPage = _CreatePage(ppd, pisp, hDlg, GetMUILanguage())) == NULL)) { RemovePropPageData(ppd, iItem); return E_UNEXPECTED; } TabCtrl_SetItem(ppd->hwndTabs, iItem, &tie.tci); non.iItem = -1; non.piid = piid; non.pObject = NULL; non.hResult = E_NOINTERFACE; non.dwFlags = 0; SendNotifyEx (tie.hwndPage, ppd->hwndTabs, PSN_GETOBJECT, &non.hdr, TRUE ); if (SUCCEEDED (non.hResult)) { *pObject = non.pObject; if (pObject == NULL) non.hResult = E_UNEXPECTED; } else if (non.pObject) { ((LPDROPTARGET) non.pObject)->lpVtbl->Release ((LPDROPTARGET) non.pObject); non.pObject = NULL; } return non.hResult; } // // We would not normally need IDD_PAGELIST except that DefWindowProc() and // WinHelp() do hit-testing differently. DefWindowProc() will do cool // things like checking against the SetWindowRgn and skipping over windows // that return HTTRANSPARENT. WinHelp() on the other hand // ignores window regions and transparency. So what happens is if you // click on the transparent part of a tab control, DefWindowProc() says // (correctly) "He clicked on the dialog background". We then say, "Okay, // WinHelp(), go display context help for the dialog background", and it // says, "Hey, I found a tab control. I'm going to display help for the // tab control now." To keep a bogus context menu from appearing, we // explicitly tell WinHelp that "If you found a tab control (IDD_PAGELIST), // then ignore it (NO_HELP)." // const static DWORD aPropHelpIDs[] = { // Context Help IDs IDD_APPLYNOW, IDH_COMM_APPLYNOW, IDD_PAGELIST, NO_HELP, 0, 0 }; void HandlePaletteChange(LPPROPDATA ppd, UINT uMessage, HWND hDlg) { HDC hdc; hdc = GetDC(hDlg); if (hdc) { BOOL fRepaint; SelectPalette(hdc,ppd->hplWatermark,(uMessage == WM_PALETTECHANGED)); fRepaint = RealizePalette(hdc); if (fRepaint) InvalidateRect(hDlg,NULL,TRUE); } ReleaseDC(hDlg,hdc); } // // Paint a rectangle with the specified brush and palette. // void PaintWithPaletteBrush(HDC hdc, LPRECT lprc, HPALETTE hplPaint, HBRUSH hbrPaint) { HBRUSH hbrPrev = SelectBrush(hdc, hbrPaint); UnrealizeObject(hbrPaint); if (hplPaint) { SelectPalette(hdc, hplPaint, FALSE); RealizePalette(hdc); } FillRect(hdc, lprc, hbrPaint); SelectBrush(hdc, hbrPrev); } // // lprc is the target rectangle. // Use as much of the bitmap as will fit into the target rectangle. // If the bitmap is smaller than the target rectangle, then fill the rest with // the pixel in the upper left corner of the hbmpPaint. // void PaintWithPaletteBitmap(HDC hdc, LPRECT lprc, HPALETTE hplPaint, HBITMAP hbmpPaint) { HDC hdcBmp; BITMAP bm; int cxRect, cyRect, cxBmp, cyBmp; GetObject(hbmpPaint, sizeof(BITMAP), &bm); hdcBmp = CreateCompatibleDC(hdc); SelectObject(hdcBmp, hbmpPaint); if (hplPaint) { SelectPalette(hdc, hplPaint, FALSE); RealizePalette(hdc); } cxRect = RECTWIDTH(*lprc); cyRect = RECTHEIGHT(*lprc); // Never use more pixels from the bmp as we have room in the rect. cxBmp = min(bm.bmWidth, cxRect); cyBmp = min(bm.bmHeight, cyRect); BitBlt(hdc, lprc->left, lprc->top, cxBmp, cyBmp, hdcBmp, 0, 0, SRCCOPY); // If bitmap is too narrow, then StretchBlt to fill the width. if (cxBmp < cxRect) StretchBlt(hdc, lprc->left + cxBmp, lprc->top, cxRect - cxBmp, cyBmp, hdcBmp, 0, 0, 1, 1, SRCCOPY); // If bitmap is to short, then StretchBlt to fill the height. if (cyBmp < cyRect) StretchBlt(hdc, lprc->left, cyBmp, cxRect, cyRect - cyBmp, hdcBmp, 0, 0, 1, 1, SRCCOPY); DeleteDC(hdcBmp); } void _SetHeaderTitles(HWND hDlg, LPPROPDATA ppd, UINT uPage, LPCTSTR pszNewTitle, BOOL bTitle) { PISP pisp = NULL; // Must be for wizard97 if (ppd->psh.dwFlags & PSH_WIZARD97) { // Page number must be within range if (uPage < ppd->psh.nPages) { // Get the page structure pisp = GETPISP(ppd, uPage); // We should have this page if it's within range ASSERT(pisp); // Do this only if this page has header. if (!(pisp->_psp.dwFlags & PSP_HIDEHEADER)) { LPCTSTR pszOldTitle = bTitle ? pisp->_psp.pszHeaderTitle : pisp->_psp.pszHeaderSubTitle; if (!IS_INTRESOURCE(pszOldTitle)) LocalFree((LPVOID)pszOldTitle); // Set the new title if (bTitle) pisp->_psp.pszHeaderTitle = pszNewTitle; else pisp->_psp.pszHeaderSubTitle = pszNewTitle; // set pszNewTitle to NULL here so that we don't free it later pszNewTitle = NULL; // set the correct flags pisp->_psp.dwFlags |= bTitle ? PSP_USEHEADERTITLE : PSP_USEHEADERSUBTITLE; // force redrawing of the titles if (uPage == (UINT)ppd->nCurItem) { RECT rcHeader; GetClientRect(hDlg, &rcHeader); rcHeader.bottom = ppd->cyHeaderHeight; InvalidateRect(hDlg, &rcHeader, FALSE); } } } } if (pszNewTitle) LocalFree((LPVOID)pszNewTitle); } void PropSheetPaintHeader(LPPROPDATA ppd, PISP pisp, HWND hDlg, HDC hdc) { RECT rcHeader; GetClientRect(hDlg, &rcHeader); if ((rcHeader.right > 0) && (rcHeader.bottom > 0)) { RECT rcHeaderBitmap; rcHeader.bottom = ppd->cyHeaderHeight; // do we need to paint the header? if (ppd->psh.dwFlags & PSH_WIZARD97IE4) { // Do it the WIZARD97IE4 way // Bug-for-bug compatibility: WIZARD97IE4 tested the wrong flag here if ((ppd->psh.dwFlags & PSH_WATERMARK) && (ppd->hbrWatermark)) PaintWithPaletteBrush(hdc, &rcHeader, ppd->hplWatermark, ppd->hbrHeader); SetBkMode(hdc, TRANSPARENT); } else { // Do it the WIZARD97IE5 way if ((ppd->psh.dwFlags & PSH_HEADER) && (ppd->hbmHeader)) { // compute the rectangle for the bitmap depending on the size of the header int bx = RECTWIDTH(rcHeader) - HEADERBITMAP_CXBACK; ASSERT(bx > 0); FillRect(hdc, &rcHeader, g_hbrWindow); SetRect(&rcHeaderBitmap, bx, HEADERBITMAP_Y, bx + HEADERBITMAP_WIDTH, HEADERBITMAP_Y + HEADERBITMAP_HEIGHT); PaintWithPaletteBitmap(hdc, &rcHeaderBitmap, ppd->hplWatermark, ppd->hbmHeader); SetBkColor(hdc, g_clrWindow); SetTextColor(hdc, g_clrWindowText); } else { SendMessage(hDlg, WM_CTLCOLORSTATIC, (WPARAM)hdc, (LPARAM)hDlg); } } // // WIZARD97IE5 subtracts out the space used by the header bitmap. // WIZARD97IE4 uses the full width since the header bitmap // in IE4 is a watermark and occupies no space. // if (!(ppd->psh.dwFlags & PSH_WIZARD97IE4)) { rcHeader.right -= HEADERBITMAP_CXBACK + HEADERSUBTITLE_WRAPOFFSET; } ASSERT(rcHeader.right); if (HASHEADERTITLE(pisp)) { _WriteHeaderTitle(ppd, hdc, &rcHeader, pisp->_psp.pszHeaderTitle, TRUE, DRAWTEXT_WIZARD97FLAGS); } if (HASHEADERSUBTITLE(pisp)) { _WriteHeaderTitle(ppd, hdc, &rcHeader, pisp->_psp.pszHeaderSubTitle, FALSE, DRAWTEXT_WIZARD97FLAGS); } } } // Free the title if we need to void Prsht_FreeTitle(LPPROPDATA ppd) { if (ppd->fFlags & PD_FREETITLE) { ppd->fFlags &= ~PD_FREETITLE; if (!IS_INTRESOURCE(ppd->psh.pszCaption)) { LocalFree((LPVOID)ppd->psh.pszCaption); } } } // // pfnStrDup is the function that converts lParam into a native character // set string. (Either StrDup or StrDup_AtoW). // void Prsht_OnSetTitle(LPPROPDATA ppd, WPARAM wParam, LPARAM lParam, STRDUPPROC pfnStrDup) { LPTSTR pszTitle; // // The ppd->psh.pszCaption is not normally LocalAlloc()d; it's // just a pointer copy. But if the app does a PSM_SETTITLE, // then all of a sudden it got LocalAlloc()d and needs to be // freed. PD_FREETITLE is the flag that tell us that this has // happened. // if (IS_INTRESOURCE(lParam)) { pszTitle = (LPTSTR)lParam; } else { pszTitle = pfnStrDup((LPTSTR)lParam); } if (pszTitle) { Prsht_FreeTitle(ppd); // Free old title if necessary ppd->psh.pszCaption = pszTitle; ppd->fFlags |= PD_FREETITLE; // Need to free this ppd->psh.dwFlags = ((((DWORD)wParam) & PSH_PROPTITLE) | (ppd->psh.dwFlags & ~PSH_PROPTITLE)); _SetTitle(ppd->hDlg, ppd); } } BOOL_PTR CALLBACK PropSheetDlgProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam) { HWND hwndT; LPPROPDATA ppd = (LPPROPDATA)GetWindowLongPtr(hDlg, DWLP_USER); LRESULT lres; if (!ppd && (uMessage != WM_INITDIALOG)) return FALSE; switch (uMessage) { case WM_INITDIALOG: InitPropSheetDlg(hDlg, (LPPROPDATA)lParam); return FALSE; case WM_SYSCOMMAND: if (wParam == SC_HOTKEY) return HandleHotkey(lParam); else if (wParam == SC_CLOSE) { UINT id = IDCLOSE; if (IS_WIZARD(ppd)) id = IDCANCEL; else if (ppd->fFlags & PD_CANCELTOCLOSE) id = IDOK; // system menu close should be IDCANCEL, but if we're in the // PSM_CANCELTOCLOSE state, treat it as an IDOK (ie, "Close"). return Prsht_OnCommand(ppd, id, NULL, 0); } return FALSE; // Let default process happen case WM_NCDESTROY: { int iPage; ASSERT(GetDlgItem(hDlg, IDD_PAGELIST) == NULL); ppd->hwndTabs = NULL; // NOTE: all of the hwnds for the pages must be destroyed by now! // Release all page objects in REVERSE ORDER so we can have // pages that are dependant on eachother based on the initial // order of those pages // for (iPage = ppd->psh.nPages - 1; iPage >= 0; iPage--) { ULONG_PTR dw = PropPageActivateContext(ppd, GETPISP(ppd, iPage)); DestroyPropertySheetPage(GETHPAGE(ppd, iPage)); PropPageDeactivateContext(dw); } // hwndCurPage is no longer valid from here on ppd->hwndCurPage = NULL; if (ppd->hActCtxInit) ReleaseActCtx(ppd->hActCtxInit); // If we are modeless, we need to free our ppd. If we are modal, // we let _RealPropertySheet free it since one of our pages may // set the restart flag during DestroyPropertySheetPage above. if (ppd->psh.dwFlags & PSH_MODELESS) { LocalFree(ppd); } } // // NOTES: // Must return FALSE to avoid DS leak!!! // return FALSE; case WM_DESTROY: { int i; HIMAGELIST himl; int c = TabCtrl_GetItemCount(ppd->hwndTabs); for (i = 0; i < c; i++) { EnableThemeDialogTexture(_Ppd_GetPage(ppd, i), ETDT_DISABLE); } // Destroy the image list we created during our init call. himl = TabCtrl_GetImageList(ppd->hwndTabs); if (himl) ImageList_Destroy(himl); if (ppd->psh.dwFlags & PSH_WIZARD97) { // Even if the PSH_USEHBMxxxxxx flag is set, we might // need to delete the bitmap if we had to create a // stretched copy. if (ppd->psh.dwFlags & PSH_WATERMARK) { if ((!(ppd->psh.dwFlags & PSH_USEHBMWATERMARK) || ppd->hbmWatermark != ppd->psh.H_hbmWatermark) && ppd->hbmWatermark) DeleteObject(ppd->hbmWatermark); if (!(ppd->psh.dwFlags & PSH_USEHPLWATERMARK) && ppd->hplWatermark) DeleteObject(ppd->hplWatermark); if (ppd->hbrWatermark) DeleteObject(ppd->hbrWatermark); } if ((ppd->psh.dwFlags & PSH_HEADER) && ppd->psh.H_hbmHeader) { if ((!(ppd->psh.dwFlags & PSH_USEHBMHEADER) || ppd->hbmHeader != ppd->psh.H_hbmHeader) && ppd->hbmHeader) { ASSERT(ppd->hbmHeader != ppd->hbmWatermark); DeleteObject(ppd->hbmHeader); } if (ppd->hbrHeader) { ASSERT(ppd->hbrHeader != ppd->hbrWatermark); DeleteObject(ppd->hbrHeader); } } if (ppd->hFontBold) DeleteObject(ppd->hFontBold); } if ((ppd->psh.dwFlags & PSH_USEICONID) && ppd->psh.H_hIcon) DestroyIcon(ppd->psh.H_hIcon); Prsht_FreeTitle(ppd); } break; case WM_ERASEBKGND: return ppd->fFlags & PD_NOERASE; break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc; PISP pisp; hdc = BeginPaint(hDlg, &ps); // only paint if the invalid rect has a non-zero widht or height if (((ps.rcPaint.bottom - ps.rcPaint.top) > 0) && ((ps.rcPaint.right - ps.rcPaint.left) > 0)) { // (dli) paint the header if ((ppd->psh.dwFlags & PSH_WIZARD97) && (!((pisp = GETPISP(ppd, ppd->nCurItem))->_psp.dwFlags & PSP_HIDEHEADER))) { PropSheetPaintHeader(ppd, pisp, hDlg, hdc); } if (ps.fErase) { SendMessage (hDlg, WM_ERASEBKGND, (WPARAM) hdc, 0); } } EndPaint(hDlg, &ps); } break; case WM_COMMAND: // Cannot use HANDLE_WM_COMMAND, because we want to pass a result! return Prsht_OnCommand(ppd, GET_WM_COMMAND_ID(wParam, lParam), GET_WM_COMMAND_HWND(wParam, lParam), GET_WM_COMMAND_CMD(wParam, lParam)); case WM_NOTIFY: switch (((NMHDR *)lParam)->code) { case TCN_SELCHANGE: PageChange(ppd, 1); break; case TCN_SELCHANGING: { lres = PageChanging(ppd); if (!lres) { SetWindowPos(ppd->hwndCurPage, HWND_BOTTOM, 0,0,0,0, SWP_NOACTIVATE | SWP_NOSIZE |SWP_NOMOVE); } goto ReturnLres; } break; case TCN_GETOBJECT: { LPNMOBJECTNOTIFY lpnmon = (LPNMOBJECTNOTIFY)lParam; lpnmon->hResult = Prsht_GetObject(ppd, hDlg, lpnmon->iItem, lpnmon->piid, &lpnmon->pObject); } break; default: return FALSE; } return TRUE; case PSM_SETWIZBUTTONS: SetWizButtons(ppd, lParam); break; case PSM_SETFINISHTEXTA: case PSM_SETFINISHTEXT: { HWND hFinish = GetDlgItem(hDlg, IDD_FINISH); HWND hwndFocus = GetFocus(); HWND hwnd; BOOL fSetFocus = FALSE; if (!(ppd->psh.dwFlags & PSH_WIZARDHASFINISH)) { hwnd = GetDlgItem(hDlg, IDD_NEXT); if (hwnd == hwndFocus) fSetFocus = TRUE; ShowWindow(hwnd, SW_HIDE); } hwnd = GetDlgItem(hDlg, IDD_BACK); if (hwnd == hwndFocus) fSetFocus = TRUE; ShowWindow(hwnd, SW_HIDE); if (lParam) { if (uMessage == PSM_SETFINISHTEXTA) { SetWindowTextA(hFinish, (LPSTR)lParam); } else Button_SetText(hFinish, (LPTSTR)lParam); } ShowWindow(hFinish, SW_SHOW); Button_Enable(hFinish, TRUE); ResetWizButtons(ppd); SendMessage(hDlg, DM_SETDEFID, IDD_FINISH, 0); ppd->idDefaultFallback = IDD_FINISH; if (fSetFocus) #ifdef WM_NEXTDLGCTL_WORKS SetDlgFocus(ppd, hFinish); #else SetFocus(hFinish); #endif } break; case PSM_SETTITLEA: Prsht_OnSetTitle(ppd, wParam, lParam, StrDup_AtoW); break; case PSM_SETTITLE: Prsht_OnSetTitle(ppd, wParam, lParam, StrDup); break; case PSM_SETHEADERTITLEA: { LPWSTR lpHeaderTitle = (lParam && HIWORD(lParam)) ? ProduceWFromA(CP_ACP, (LPCSTR)lParam) : StrDupW((LPWSTR)lParam); if (lpHeaderTitle) _SetHeaderTitles(hDlg, ppd, (UINT)wParam, lpHeaderTitle, TRUE); } break; case PSM_SETHEADERTITLE: { LPTSTR lpHeaderTitle = StrDup((LPCTSTR)lParam); if (lpHeaderTitle) _SetHeaderTitles(hDlg, ppd, (UINT)wParam, lpHeaderTitle, TRUE); } break; case PSM_SETHEADERSUBTITLEA: { LPWSTR lpHeaderSubTitle = (lParam && HIWORD(lParam)) ? ProduceWFromA(CP_ACP, (LPCSTR)lParam) : StrDupW((LPWSTR)lParam); if (lpHeaderSubTitle) _SetHeaderTitles(hDlg, ppd, (UINT)wParam, lpHeaderSubTitle, FALSE); } break; case PSM_SETHEADERSUBTITLE: { LPTSTR lpHeaderSubTitle = StrDup((LPCTSTR)lParam); if (lpHeaderSubTitle) _SetHeaderTitles(hDlg, ppd, (UINT)wParam, lpHeaderSubTitle, FALSE); } break; case PSM_CHANGED: PageInfoChange(ppd, (HWND)wParam); break; case PSM_RESTARTWINDOWS: ppd->nRestart |= ID_PSRESTARTWINDOWS; break; case PSM_REBOOTSYSTEM: ppd->nRestart |= ID_PSREBOOTSYSTEM; break; case PSM_DISABLEAPPLY: // the page is asking us to gray the "Apply" button and not let // anyone else re-enable it if (ppd->fAllowApply) { ppd->fAllowApply = FALSE; EnableWindow(GetDlgItem(ppd->hDlg, IDD_APPLYNOW), FALSE); } break; case PSM_ENABLEAPPLY: // the page is asking us to allow the the "Apply" button to be // once again enabled if (!ppd->fAllowApply) ppd->fAllowApply = TRUE; // REARCHITECT - raymondc - shouldn't we call EnableWindow? break; case PSM_CANCELTOCLOSE: if (!(ppd->fFlags & PD_CANCELTOCLOSE)) { TCHAR szClose[20]; ppd->fFlags |= PD_CANCELTOCLOSE; LocalizedLoadString(IDS_CLOSE, szClose, ARRAYSIZE(szClose)); SetDlgItemText(hDlg, IDOK, szClose); EnableWindow(GetDlgItem(hDlg, IDCANCEL), FALSE); } break; case PSM_SETCURSEL: lres = PageSetSelection(ppd, (int)wParam, (HPROPSHEETPAGE)lParam, 1); goto ReturnLres; case PSM_SETCURSELID: { int iPageIndex; iPageIndex = FindPageIndex(ppd, ppd->nCurItem, (DWORD)lParam, 1); if (iPageIndex == -1) lres = 0; else lres = PageSetSelection(ppd, iPageIndex, NULL, 1); goto ReturnLres; } break; case PSM_REMOVEPAGE: RemovePropPage(ppd, (int)wParam, (HPROPSHEETPAGE)lParam); break; case PSM_ADDPAGE: lres = AddPropPage(ppd,(HPROPSHEETPAGE)lParam); goto ReturnLres; case PSM_INSERTPAGE: lres = InsertPropPage(ppd, (HPROPSHEETPAGE)wParam, (HPROPSHEETPAGE)lParam); goto ReturnLres; case PSM_QUERYSIBLINGS: lres = QuerySiblings(ppd, wParam, lParam); goto ReturnLres; case PSM_UNCHANGED: PageInfoUnChange(ppd, (HWND)wParam); break; case PSM_APPLY: // a page is asking us to simulate an "Apply Now". // let the page know if we're successful lres = ButtonPushed(ppd, IDD_APPLYNOW); goto ReturnLres; case PSM_GETTABCONTROL: lres = (LRESULT)ppd->hwndTabs; goto ReturnLres; case PSM_GETCURRENTPAGEHWND: lres = (LRESULT)ppd->hwndCurPage; goto ReturnLres; case PSM_PRESSBUTTON: if (wParam <= PSBTN_MAX) { const static int IndexToID[] = {IDD_BACK, IDD_NEXT, IDD_FINISH, IDOK, IDD_APPLYNOW, IDCANCEL, IDHELP}; Prsht_OnCommand(ppd, IndexToID[wParam], NULL, 0); } break; case PSM_ISDIALOGMESSAGE: // returning TRUE means we handled it, do a continue // FALSE do standard translate/dispatch lres = Prop_IsDialogMessage(ppd, (LPMSG32)lParam); goto ReturnLres; case PSM_HWNDTOINDEX: lres = FindItem(ppd->hwndTabs, (HWND)wParam, NULL); goto ReturnLres; case PSM_INDEXTOHWND: if ((UINT)wParam < ppd->psh.nPages) lres = (LRESULT)_Ppd_GetPage(ppd, (int)wParam); else lres = 0; goto ReturnLres; case PSM_PAGETOINDEX: lres = FindPageIndexByHpage(ppd, (HPROPSHEETPAGE)lParam); goto ReturnLres; case PSM_INDEXTOPAGE: if ((UINT)wParam < ppd->psh.nPages) lres = (LRESULT)GETHPAGE(ppd, wParam); else lres = 0; goto ReturnLres; case PSM_INDEXTOID: if ((UINT)wParam < ppd->psh.nPages) { lres = (LRESULT)GETPPSP(ppd, wParam)->P_pszTemplate; // Need to be careful -- return a value only if pszTemplate // is an ID. Don't return out our internal pointers! if (!IS_INTRESOURCE(lres)) lres = 0; } else lres = 0; goto ReturnLres; case PSM_IDTOINDEX: lres = FindPageIndex(ppd, ppd->nCurItem, (DWORD)lParam, 0); goto ReturnLres; case PSM_GETRESULT: // This is valid only after the property sheet is gone if (ppd->hwndCurPage) { lres = -1; // you shouldn't be calling me yet } else { lres = ppd->nReturn; if (lres > 0 && ppd->nRestart) lres = ppd->nRestart; } goto ReturnLres; break; case PSM_RECALCPAGESIZES: lres = Prsht_RecalcPageSizes(ppd); goto ReturnLres; // these should be relayed to all created dialogs case WM_WININICHANGE: case WM_SYSCOLORCHANGE: case WM_DISPLAYCHANGE: { int nItem, nItems = TabCtrl_GetItemCount(ppd->hwndTabs); for (nItem = 0; nItem < nItems; ++nItem) { hwndT = _Ppd_GetPage(ppd, nItem); if (hwndT) SendMessage(hwndT, uMessage, wParam, lParam); } SendMessage(ppd->hwndTabs, uMessage, wParam, lParam); } break; // // send toplevel messages to the current page and tab control // case WM_PALETTECHANGED: // // If this is our window we need to avoid selecting and realizing // because doing so would cause an infinite loop between WM_QUERYNEWPALETTE // and WM_PALETTECHANGED. // if((HWND)wParam == hDlg) { return(FALSE); } // // FALL THROUGH // case WM_QUERYNEWPALETTE: // This is needed when another window which has different palette clips // us if ((ppd->psh.dwFlags & PSH_WIZARD97) && (ppd->psh.dwFlags & PSH_WATERMARK) && (ppd->psh.hplWatermark)) HandlePaletteChange(ppd, uMessage, hDlg); // // FALL THROUGH // case WM_ENABLE: case WM_DEVICECHANGE: case WM_QUERYENDSESSION: case WM_ENDSESSION: if (ppd->hwndTabs) SendMessage(ppd->hwndTabs, uMessage, wParam, lParam); // // FALL THROUGH // case WM_ACTIVATEAPP: case WM_ACTIVATE: { hwndT = _Ppd_GetPage(ppd, ppd->nCurItem); if (hwndT && IsWindow(hwndT)) { // // By doing this, we are "handling" the message. Therefore // we must set the dialog return value to whatever the child // wanted. // lres = SendMessage(hwndT, uMessage, wParam, lParam); goto ReturnLres; } } if ((uMessage == WM_PALETTECHANGED) || (uMessage == WM_QUERYNEWPALETTE)) return TRUE; else return FALSE; case WM_CONTEXTMENU: // ppd->hwndTabs is handled by aPropHelpIDs to work around a USER bug. // See aPropHelpIDs for gory details. if ((ppd->hwndCurPage != (HWND)wParam) && (!IS_WIZARD(ppd))) WinHelp((HWND)wParam, NULL, HELP_CONTEXTMENU, (ULONG_PTR)(LPVOID) aPropHelpIDs); break; case WM_HELP: hwndT = (HWND)((LPHELPINFO)lParam)->hItemHandle; if ((GetParent(hwndT) == hDlg) && (hwndT != ppd->hwndTabs)) WinHelp(hwndT, NULL, HELP_WM_HELP, (ULONG_PTR)(LPVOID) aPropHelpIDs); break; default: return FALSE; } return TRUE; ReturnLres: SetWindowLongPtr(hDlg, DWLP_MSGRESULT, lres); return TRUE; } // // Draw the background for wizard pages. // BOOL Prsht_EraseWizBkgnd(LPPROPDATA ppd, HDC hdc) { RECT rc; BOOL fPainted = FALSE; GetClientRect(ppd->hDlg, &rc); if (ppd->psh.dwFlags & PSH_WIZARD97IE4) { if (ppd->hbrWatermark) { PaintWithPaletteBrush(hdc, &rc, ppd->hplWatermark, ppd->hbrWatermark); fPainted = TRUE; } } else // PSH_WIZARD97IE5 { if (ppd->hbmWatermark) { // Right-hand side gets g_hbrWindow. rc.left = BITMAP_WIDTH; FillRect(hdc, &rc, g_hbrWindow); // Left-hand side gets watermark in top portion with autofill... rc.right = rc.left; rc.left = 0; PaintWithPaletteBitmap(hdc, &rc, ppd->hplWatermark, ppd->hbmWatermark); fPainted = TRUE; } } return fPainted; } LRESULT CALLBACK WizardWndProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam, UINT_PTR uID, ULONG_PTR dwRefData) { LPPROPDATA ppd = (LPPROPDATA)dwRefData; switch (uMessage) { case WM_ERASEBKGND: if (Prsht_EraseWizBkgnd(ppd, (HDC)wParam)) return TRUE; break; // Only PSH_WIZARD97IE4 cares about these messages case WM_CTLCOLOREDIT: case WM_CTLCOLORDLG: if (!(ppd->psh.dwFlags & PSH_WIZARD97IE4)) break; // fall through case WM_CTLCOLOR: case WM_CTLCOLORMSGBOX: case WM_CTLCOLORLISTBOX: case WM_CTLCOLORBTN: case WM_CTLCOLORSCROLLBAR: case WM_CTLCOLORSTATIC: if (ppd->psh.dwFlags & PSH_WIZARD97IE4) { if (ppd->hbrWatermark) { POINT pt; // Bug-for-bug compatibility: TRANSPARENT messes up edit // controls when they scroll, but that's what IE4 did. SetBkMode((HDC)wParam, TRANSPARENT); if (ppd->hplWatermark) { SelectPalette((HDC)wParam, ppd->hplWatermark, FALSE); RealizePalette((HDC)wParam); } UnrealizeObject(ppd->hbrWatermark); GetDCOrgEx((HDC)wParam, &pt); // Bug-for-bug compatibility: We shouldn't use GetParent // because the notification might be forwarded up from an // embedded dialog child, but that's what IE4 did. ScreenToClient(GetParent((HWND)lParam), &pt); SetBrushOrgEx((HDC)wParam, -pt.x, -pt.y, NULL); return (LRESULT)(HBRUSH)ppd->hbrWatermark; } } else // PSH_WIZARD97IE5 { if (ppd->hbmWatermark) { LRESULT lRet = DefWindowProc(hDlg, uMessage, wParam, lParam); if (lRet == DefSubclassProc(hDlg, uMessage, wParam, lParam)) { SetTextColor((HDC)wParam, GetSysColor(COLOR_WINDOWTEXT)); SetBkColor((HDC)wParam, GetSysColor(COLOR_WINDOW)); return (LRESULT)g_hbrWindow; } else return lRet; } } break; case WM_PALETTECHANGED: if((HWND)wParam == hDlg) return(FALSE); case WM_QUERYNEWPALETTE: HandlePaletteChange(ppd, uMessage, hDlg); return TRUE; case WM_DESTROY: // Clean up subclass RemoveWindowSubclass(hDlg, WizardWndProc, 0); break; default: break; } return DefSubclassProc(hDlg, uMessage, wParam, lParam); } // // EnumResLangProc // // purpose: a callback function for EnumResourceLanguages(). // look into the type passed in and if it is RT_DIALOG // copy the lang of the first resource to our buffer // this also counts # of lang if more than one of them // are passed in // // typedef struct { WORD wLang; BOOL fFoundLang; LPCTSTR lpszType; } ENUMLANGDATA; BOOL CALLBACK EnumResLangProc(HINSTANCE hinst, LPCTSTR lpszType, LPCTSTR lpszName, WORD wIdLang, LPARAM lparam) { ENUMLANGDATA *pel = (ENUMLANGDATA *)lparam; BOOL fContinue = TRUE; ASSERT(pel); if (lpszType == pel->lpszType) { // When comctl's been initialized with a particular MUI language, // we pass in the langid to GetPageLanguage(), then it's given to this proc. // we want to look for a template that matches to the langid, // and if it's not found, we have to use the first instance of templates. // if (pel->wLang == MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) || (pel->wLang == wIdLang)) { pel->wLang = wIdLang; pel->fFoundLang = TRUE; fContinue = FALSE; } } return fContinue; // continue until we get langs... } // GetPageLanguage // // purpose: tries to retrieve language information out of // given page's dialog template. We get the first language // in which the template is localized in. // currently doesn't support PSP_DLGINDIRECT case // // we luck out with browselc since there's only one lang per resid, // we should cache the langid we loaded up front and pull it out here. // HRESULT GetPageLanguage(PISP pisp, WORD *pwLang) { if (pisp && pwLang) { if (pisp->_psp.dwFlags & PSP_DLGINDIRECT) { // try something other than dialog return E_FAIL; // not supported yet. } else { ENUMLANGDATA el; // the caller passes-in the langid with which we're initialized // el.wLang = *pwLang; el.fFoundLang = FALSE; el.lpszType = RT_DIALOG; // check with the dialog template specified EnumResourceLanguages(pisp->_psp.hInstance, RT_DIALOG, pisp->_psp.P_pszTemplate, EnumResLangProc, (LPARAM)&el); if (!el.fFoundLang) { // we couldn't find a matching lang in the given page's resource // so we'll take the first one el.wLang = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL); // it doesn't matter if this fails, because we'll then end up with // the neutral langid, which is the best guess here after failing // to get any page lang. // EnumResourceLanguages(pisp->_psp.hInstance, RT_DIALOG, pisp->_psp.P_pszTemplate, EnumResLangProc, (LPARAM)&el); } *pwLang = el.wLang; } return S_OK; } return E_FAIL; } // // FindResourceExRetry // // Just like FindResourceEx, except that if we can't find the resource, // we try again with MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL). // HRSRC FindResourceExRetry(HMODULE hmod, LPCTSTR lpType, LPCTSTR lpName, WORD wLang) { HRSRC hrsrc = FindResourceEx(hmod, lpType, lpName, wLang); // if failed because we couldn't find the resouce in requested lang // and requested lang wasn't neutral, then try neutral. if (!hrsrc && wLang != MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)) { wLang = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL); hrsrc = FindResourceEx(hmod, lpType, lpName, wLang); } return hrsrc; } WORD GetShellResourceLangID(void); // NT5_GetUserDefaultUILanguage // // NT5 has a new function GetUserDefaultUILanguage which returns the // language the user as selected for UI. // // If the function is not available (e.g., NT4), then use the // shell resource language ID. // typedef LANGID (CALLBACK* GETUSERDEFAULTUILANGUAGE)(void); GETUSERDEFAULTUILANGUAGE _GetUserDefaultUILanguage; LANGID NT5_GetUserDefaultUILanguage(void) { if (_GetUserDefaultUILanguage == NULL) { HMODULE hmod = GetModuleHandle(TEXT("KERNEL32")); // // Must keep in a local to avoid thread races. // GETUSERDEFAULTUILANGUAGE pfn = NULL; if (hmod) pfn = (GETUSERDEFAULTUILANGUAGE) GetProcAddress(hmod, "GetUserDefaultUILanguage"); // // If function is not available, then use our fallback // if (pfn == NULL) pfn = GetShellResourceLangID; ASSERT(pfn != NULL); _GetUserDefaultUILanguage = pfn; } return _GetUserDefaultUILanguage(); } LCID CCGetSystemDefaultThreadLocale(LCID iLcidThreadOrig) { UINT uLangThread, uLangThreadOrig; uLangThreadOrig = LANGIDFROMLCID(iLcidThreadOrig); // uLangThread is the language we think we want to use uLangThread = uLangThreadOrig; if (staticIsOS(OS_NT4ORGREATER) && !staticIsOS(OS_WIN2000ORGREATER)) { int iLcidUserDefault = GetUserDefaultLCID(); UINT uLangUD = LANGIDFROMLCID(iLcidUserDefault); // // If we are running on Enabled Arabic NT4, we should always // display the US English resources (since the UI is English), however NT4 // Resource Loader will look for the current Thread Locale (which is Arabic). // This is no problem in NT5 since the Resource Loader will check for // the UI Language (newly introduced) when loading such resources. To // fix this, we will change the thread locale to US English // and restore it back to Arabic/Hebrew if we are running on an Enabled Arabic/Hebrew NT4. // The check is done to make sure we are running within a Araic/Hebrew user locale // and the thread locale is still Arabic/Hebrew (i.e. nobody tried to SetThreadLocale). // [samera] // if( ((PRIMARYLANGID(uLangUD ) == LANG_ARABIC) && (PRIMARYLANGID(uLangThread) == LANG_ARABIC)) || ((PRIMARYLANGID(uLangUD ) == LANG_HEBREW) && (PRIMARYLANGID(uLangThread) == LANG_HEBREW))) { uLangThread = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); } } // // Make locale match UI locale if not otherwise overridden. // if (uLangThread == uLangThreadOrig) { uLangThread = NT5_GetUserDefaultUILanguage(); } // // Now see if we actually changed the thread language. // if (uLangThread == uLangThreadOrig) { // No change, return the original locale, including sort stuff return iLcidThreadOrig; } else { // It changed, return a generic sort order, since we don't use // this information for sorting. return MAKELCID(uLangThread, SORT_DEFAULT); } } // // GetAltFontLangId // // used to detect "MS UI Gothic" on Jpn localized non NT5 platforms // the font is shipped with IE5 for the language but comctl can't // always assume the font so we have a fake sublang id assigned to // the secondary resource file for the language // int CALLBACK FontEnumProc( ENUMLOGFONTEX *lpelfe, NEWTEXTMETRICEX *lpntme, int FontType, LPARAM lParam ) { if (lParam) { *(BOOL *)lParam = TRUE; } return 0; // stop at the first callback } UINT GetDefaultCharsetFromLang(LANGID wLang) { TCHAR szData[6+1]; // 6 chars are max allowed for this lctype UINT uiRet = DEFAULT_CHARSET; // JPN hack here: GetLocaleInfo() DOES return > 0 for Jpn altfont langid, // but doesn't get us any useful info. So for JPN, we ripout the SUBLANG // portion of id. we can't do this for other langs since sublang can affect // charset (ex. chinese) // if(PRIMARYLANGID(wLang) == LANG_JAPANESE) wLang = MAKELANGID(PRIMARYLANGID(wLang), SUBLANG_NEUTRAL); if (GetLocaleInfo(MAKELCID(wLang, SORT_DEFAULT), LOCALE_IDEFAULTANSICODEPAGE, szData, ARRAYSIZE(szData)) > 0) { UINT uiCp = StrToInt(szData); CHARSETINFO csinfo; if (TranslateCharsetInfo(IntToPtr_(DWORD *, uiCp), &csinfo, TCI_SRCCODEPAGE)) uiRet = csinfo.ciCharset; } return uiRet; } BOOL IsFontInstalled(LANGID wLang, LPCTSTR szFace) { BOOL fInstalled = FALSE; HDC hdc; LOGFONT lf = {0}; lstrcpyn(lf.lfFaceName, szFace, ARRAYSIZE(lf.lfFaceName)); // retrieve charset from given language lf.lfCharSet = (BYTE)GetDefaultCharsetFromLang(wLang); // then see if we can enumrate the font hdc = GetDC(NULL); if (hdc) { EnumFontFamiliesEx(hdc, &lf, (FONTENUMPROC)FontEnumProc, (LPARAM)&fInstalled, 0); ReleaseDC(NULL, hdc); } return fInstalled; } LANGID GetAltFontLangId(LANGID wLang) { LPCTSTR pszTypeFace = NULL; USHORT usAltSubLang = SUBLANG_NEUTRAL; const static TCHAR s_szUIGothic[] = TEXT("MS UI Gothic"); static int iPrimaryFontInstalled = -1; // most of the case we return the lang just as is switch(PRIMARYLANGID(wLang)) { case LANG_JAPANESE: pszTypeFace = s_szUIGothic; usAltSubLang = SUBLANG_JAPANESE_ALTFONT; break; // add code here to handle any other cases like Jpn default: return wLang; } // check existence of the font if we haven't if (iPrimaryFontInstalled < 0 && pszTypeFace) { iPrimaryFontInstalled = IsFontInstalled(wLang, pszTypeFace); } // return secondary lang id if our alternative font *is* installed if (iPrimaryFontInstalled == 1) wLang = MAKELANGID(PRIMARYLANGID(wLang), usAltSubLang); return wLang; } // GetShellResourceLangID // // On NT4, we want to match our ML resource to the one that OS is localized. // this is to prevent general UI (buttons) from changing along with regional // setting change. // Win95 won't change system default locale, NT5 will load from matching satelite // resource dll automatically so this won't be needed on these platforms. // This function finds shell32.dll and gets the language in which the dll is // localized, then cache the lcid so we won't have to detect it again. // WORD GetShellResourceLangID(void) { static WORD langRes = 0L; // we do this only once if (langRes == 0L) { HINSTANCE hinstShell; ENUMLANGDATA el = {MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), FALSE, RT_DIALOG}; hinstShell = LoadLibrary(TEXT("shell32.dll")); if (hinstShell) { EnumResourceLanguages(hinstShell, RT_DIALOG, MAKEINTRESOURCE(DLG_EXITWINDOWS), EnumResLangProc, (LPARAM)&el); FreeLibrary(hinstShell); } if (PRIMARYLANGID(el.wLang) == LANG_CHINESE || PRIMARYLANGID(el.wLang) == LANG_PORTUGUESE ) { // these two languages need special handling langRes = el.wLang; } else { // otherwise we use only primary langid. langRes = MAKELANGID(PRIMARYLANGID(el.wLang), SUBLANG_NEUTRAL); } } return langRes; } // // CCGetProperThreadLocale // // This function computes its brains out and tries to decide // which thread locale we should use for our UI components. // // Returns the desired locale. // // Adjustment - For Arabic / Hebrew - NT4 Only // // Converts the thread locale to US, so that neutral resources // loaded by the thread will be the US-English one, if available. // This is used when the locale is Arabic/Hebrew and the system is // NT4 enabled ( There was no localized NT4), as a result we need // always to see the English resources on NT4 Arabic/Hebrew. // [samera] // // Adjustment - For all languages - NT4 Only // // Convert the thread locale to the shell locale if not otherwise // altered by previous adjustments. // // Adjustment - For all languages - NT5 Only // // Always use the default UI language. If that fails, then use the // shell locale. // // The last two adjustments are handled in a common function, because // the NT5 fallback turns out to be equal to the NT4 algorithm. // LCID CCGetProperThreadLocale(OPTIONAL LCID *plcidPrev) { LANGID uLangAlt, uLangMUI; LCID lcidRet, iLcidThreadOrig; iLcidThreadOrig = GetThreadLocale(); if (plcidPrev) *plcidPrev = iLcidThreadOrig; uLangMUI = GetMUILanguage(); if ( uLangMUI == MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)) { // return adjusted system default locale if MUI isn't initialized // lcidRet = CCGetSystemDefaultThreadLocale(iLcidThreadOrig); } else { // our host has initialized us with prefered MUI language // lcidRet = MAKELCID(uLangMUI, SORT_DEFAULT); } uLangAlt = GetAltFontLangId(LANGIDFROMLCID(lcidRet)); if (uLangAlt != LANGIDFROMLCID(lcidRet)) { // use secondary resource for the language // if the platform *does* have the alternative font lcidRet = MAKELCID(uLangAlt, SORTIDFROMLCID(lcidRet)); } return lcidRet; } // // CCLoadStringEx // // Just like LoadString, except you can specify the language, too. // // This is harder than you think, because NT5 changed the way strings // are loaded. Quote: // // We changed the resource loader in NT5, to only load resources // in the language of the thread locale, if the thread locale is // different to the user locale. The reasoning behind this was // the "random" loading of the language of the user locale in // the UI. This breaks if you do a SetThreadLocale to the User // Locale, because then the whole step is ignored and the // InstallLanguage of the system is loaded. // // Therefore, we have to use FindResourceEx. // // int CCLoadStringEx(UINT uID, LPWSTR lpBuffer, int nBufferMax, WORD wLang) { return CCLoadStringExInternal(HINST_THISDLL, uID, lpBuffer, nBufferMax, wLang); } int CCLoadStringExInternal(HINSTANCE hInst, UINT uID, LPWSTR lpBuffer, int nBufferMax, WORD wLang) { PWCHAR pwch; HRSRC hrsrc; int cwch = 0; if (nBufferMax <= 0) return 0; // sanity check /* * String tables are broken up into "bundles" of 16 strings each. */ hrsrc = FindResourceExRetry(hInst, RT_STRING, (LPCTSTR)(LONG_PTR)(1 + (USHORT)uID / 16), wLang); if (hrsrc) { pwch = (PWCHAR)LoadResource(hInst, hrsrc); if (pwch) { /* * Now skip over the strings in the resource until we * hit the one we want. Each entry is a counted string, * just like Pascal. */ for (uID %= 16; uID; uID--) { pwch += *pwch + 1; } cwch = min(*pwch, nBufferMax - 1); memcpy(lpBuffer, pwch+1, cwch * sizeof(WCHAR)); /* Copy the goo */ } } lpBuffer[cwch] = L'\0'; /* Terminate the string */ return cwch; } // // LocalizedLoadString // // Loads a string from our resources, using the correct language. // int LocalizedLoadString(UINT uID, LPWSTR lpBuffer, int nBufferMax) { return CCLoadStringEx(uID, lpBuffer, nBufferMax, LANGIDFROMLCID(CCGetProperThreadLocale(NULL))); } // // Determine if the prop sheet frame should use the new // "MS Shell Dlg 2" font. To do this, we examine each page's dlg template. // If all pages have SHELLFONT enabled, then // we want to use the new font. // BOOL ShouldUseMSShellDlg2Font(LPPROPDATA ppd) { UINT iPage; PAGEINFOEX pi; LANGID langidMUI; if (!staticIsOS(OS_WIN2000ORGREATER)) return FALSE; langidMUI = GetMUILanguage(); for (iPage = 0; iPage < ppd->psh.nPages; iPage++) { if (GetPageInfoEx(ppd, GETPISP(ppd, iPage), &pi, langidMUI, GPI_DIALOGEX)) { if (!IsPageInfoSHELLFONT(&pi)) { return FALSE; } } } return TRUE; } PSPT_OS Prsht_GetOS() { static PSPT_OS pspt_os = (PSPT_OS)-1; int iIsOSBiDiEnabled = 0; if (pspt_os != (PSPT_OS)-1) { return pspt_os; } iIsOSBiDiEnabled = GetSystemMetrics(SM_MIDEASTENABLED); if (staticIsOS(OS_WIN2000ORGREATER)) { pspt_os = PSPT_OS_WINNT5; } else if (iIsOSBiDiEnabled && staticIsOS(OS_NT4ORGREATER) && (!staticIsOS(OS_WIN2000ORGREATER))) { pspt_os = PSPT_OS_WINNT4_ENA; } else if (iIsOSBiDiEnabled && staticIsOS(OS_WIN95ORGREATER) && (!staticIsOS(OS_WIN98))) { pspt_os = PSPT_OS_WIN95_BIDI; } else if (iIsOSBiDiEnabled && staticIsOS(OS_WIN98)) { pspt_os = PSPT_OS_WIN98_BIDI; } else { pspt_os = PSPT_OS_OTHER; } return pspt_os; } PSPT_OVERRIDE Prsht_GetOverrideState(LPPROPDATA ppd) { // if passed bad argument, assume no override if(!ppd) return PSPT_OVERRIDE_NOOVERRIDE; if (ppd->psh.dwFlags & PSH_USEPAGELANG) return PSPT_OVERRIDE_USEPAGELANG; return PSPT_OVERRIDE_NOOVERRIDE; } PSPT_TYPE Prsht_GetType(LPPROPDATA ppd, WORD wLang) { PISP pisp = NULL; // if passed bad argument, give it the english resources if(!ppd) return PSPT_TYPE_ENGLISH; pisp = GETPISP(ppd, 0); if(pisp) { PAGEINFOEX pi = {0}; if ((IS_PROCESS_RTL_MIRRORED()) || (GetPageInfoEx(ppd, pisp, &pi, wLang, GPI_BMIRROR) && pi.bMirrored)) return PSPT_TYPE_MIRRORED; else { WORD wLang = LANGIDFROMLCID(CCGetProperThreadLocale(NULL)); GetPageLanguage(pisp,&wLang); if((PRIMARYLANGID(wLang) == LANG_ARABIC) || (PRIMARYLANGID(wLang) == LANG_HEBREW)) return PSPT_TYPE_ENABLED; } } return PSPT_TYPE_ENGLISH; } PSPT_ACTION Prsht_GetAction(PSPT_TYPE pspt_type, PSPT_OS pspt_os, PSPT_OVERRIDE pspt_override) { if ((pspt_type < 0) || (pspt_type >= PSPT_TYPE_MAX) || (pspt_os < 0) || (pspt_os >= PSPT_OS_MAX) || (pspt_override < 0) || (pspt_override >= PSPT_OVERRIDE_MAX)) return PSPT_ACTION_NOACTION; return g_PSPT_Action[pspt_type][pspt_os][pspt_override]; } void Prsht_PrepareTemplate(LPPROPDATA ppd, HINSTANCE hInst, HGLOBAL *phDlgTemplate, HRSRC *phResInfo, LPCSTR lpName, HWND hWndOwner, LPWORD lpwLangID) { LPDLGTEMPLATE pDlgTemplate = NULL; PSPT_ACTION pspt_action; if (pDlgTemplate = (LPDLGTEMPLATE)LockResource(*phDlgTemplate)) { // We save BiDi templates as DIALOG (not DIALOGEX) // If we got an extended template then it is not ours if (((LPDLGTEMPLATEEX)pDlgTemplate)->wSignature == 0xFFFF) return; // Cut it short to save time // if (!(pDlgTemplate->dwExtendedStyle & (RTL_MIRRORED_WINDOW | RTL_NOINHERITLAYOUT))) return; } pspt_action = Prsht_GetAction(Prsht_GetType(ppd, *lpwLangID), Prsht_GetOS(), Prsht_GetOverrideState(ppd)); switch(pspt_action) { case PSPT_ACTION_NOACTION: return; case PSPT_ACTION_NOMIRRORING: { if (pDlgTemplate) { EditBiDiDLGTemplate(pDlgTemplate, EBDT_NOMIRROR, NULL, 0); } } break; case PSPT_ACTION_FLIP: { if (pDlgTemplate) { EditBiDiDLGTemplate(pDlgTemplate, EBDT_NOMIRROR, NULL, 0); EditBiDiDLGTemplate(pDlgTemplate, EBDT_FLIP, (PWORD)&wIgnoreIDs, ARRAYSIZE(wIgnoreIDs)); ppd->fFlipped = TRUE; } } break; case PSPT_ACTION_LOADENGLISH: { HGLOBAL hDlgTemplateTemp = NULL; HRSRC hResInfoTemp; // //Try to load an English resource. // *lpwLangID = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); if ((hResInfoTemp = FindResourceExA( hInst, (LPCSTR)RT_DIALOG, lpName, *lpwLangID))) { hDlgTemplateTemp = LoadResource(hInst, hResInfoTemp); } if (hDlgTemplateTemp) { // //And return it to the caller to use it. // Since we loaeded a new template, we should copy it to a local memory // in case there is a callback. // DWORD cbTemplate = SizeofResource(hInst, hResInfoTemp); LPVOID pTemplateMod; pTemplateMod = (LPVOID)LocalAlloc(LPTR, cbTemplate * 2); if (pTemplateMod) { memmove(pTemplateMod, hDlgTemplateTemp, cbTemplate); LocalFree(*phDlgTemplate); *phResInfo = hResInfoTemp; *phDlgTemplate = pTemplateMod; } } } break; case PSPT_ACTION_WIN9XCOMPAT: { if (pDlgTemplate) { pDlgTemplate->style |= DS_BIDI_RTL; } } } } INT_PTR _RealPropertySheet(LPPROPDATA ppd) { HWND hwndMain; MSG32 msg32; HWND hwndTopOwner; int nReturn = -1; HWND hwndOriginalFocus; WORD wLang, wUserLang; LCID iLcidThread=0L; HRSRC hrsrc = 0; LPVOID pTemplate, pTemplateMod; LPTSTR lpDlgId; if (ppd->psh.nPages == 0) { DebugMsg(DM_ERROR, TEXT("no pages for prop sheet")); goto FreePpdAndReturn; } ppd->hwndCurPage = NULL; ppd->nReturn = -1; ppd->nRestart = 0; hwndTopOwner = ppd->psh.hwndParent; hwndOriginalFocus = GetFocus(); #ifdef DEBUG if (GetAsyncKeyState(VK_CONTROL) < 0) { ppd->psh.dwFlags |= PSH_WIZARDHASFINISH; } #endif if (!(ppd->psh.dwFlags & PSH_MODELESS)) { // // Like dialog boxes, we only want to disable top level windows. // NB The mail guys would like us to be more like a regular // dialog box and disable the parent before putting up the sheet. if (hwndTopOwner) { while (GetWindowLong(hwndTopOwner, GWL_STYLE) & WS_CHILD) hwndTopOwner = GetParent(hwndTopOwner); ASSERT(hwndTopOwner); // Should never get this! if ((hwndTopOwner == GetDesktopWindow()) || (EnableWindow(hwndTopOwner, FALSE))) { // // If the window was the desktop window, then don't disable // it now and don't reenable it later. // Also, if the window was already disabled, then don't // enable it later. // hwndTopOwner = NULL; } } } #if !defined(WIN32) #ifdef FE_IME // Win95d-B#754 // When PCMCIA gets detected, NETDI calls DiCallClassInstaller(). // The class installer of setupx calls PropertySheet() for msgsrv32. // We usually don't prepare thread link info in imm for that process as // it won't use IME normaly but we need to treat this case as special. // if (!ImmFindThreadLink(GetCurrentThreadID())) { ImmCreateThreadLink(GetCurrentProcessID(),GetCurrentThreadID()); } #endif #endif // // WARNING! WARNING! WARNING! WARNING! // // Before you mess with any language stuff, be aware that MFC loads // resources directly out of comctl32.dll, so if you change the // way we choose the proper resource, you may break MFC apps. // See NT bug 302959. // // Support PSH_USEPAGELANG // // Presume we load our template based on thread lang id. wLang = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL); wUserLang= MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL); if (ppd->psh.dwFlags & PSH_USEPAGELANG) { // Get callers language version. We know we have at least one page if (FAILED(GetPageLanguage(GETPISP(ppd, 0), &wLang))) { // failed to get langid out of caller's resource // just pretend nothing happened. wLang = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL); } wUserLang = wLang; } else wLang = LANGIDFROMLCID(CCGetProperThreadLocale(NULL)); // // The only thing we need the thread locale for is to locate the // correct dialog template. We don't want it to affect page // initialization or anything else like that, so get the template // and quickly set the locale back before anyone notices. // // If we can't get the requested language, retry with the neutral // language. // // We have seperate dialog templates for Win95 BiDi localized // The code used to check to see if we are running on Win98 BiDi localized // and load this template. // We have a special case when running Office2000 with Arabic/Hebrew SKU on // BiDi win95 Enabled where we need to load this template as well if(Prsht_GetOS() == PSPT_OS_WIN95_BIDI) { lpDlgId = MAKEINTRESOURCE(IS_WIZARD(ppd) ? DLG_WIZARD95 : DLG_PROPSHEET95); hrsrc = FindResourceEx( HINST_THISDLL, RT_DIALOG, lpDlgId, wLang ); // we only have DLG_WIZARD95 and DLG_PROPSHEET95 in Arabic & Hebrew language // if we got any other language we will fail // In this case, let's use the normal templates if(hrsrc) { ppd->fFlipped = TRUE; } else { lpDlgId = MAKEINTRESOURCE(IS_WIZARD(ppd) ? DLG_WIZARD : DLG_PROPSHEET); hrsrc = FindResourceExRetry( HINST_THISDLL, RT_DIALOG, lpDlgId, wLang ); } } else { lpDlgId = MAKEINTRESOURCE(IS_WIZARD(ppd) ? DLG_WIZARD : DLG_PROPSHEET); hrsrc = FindResourceExRetry( HINST_THISDLL, RT_DIALOG, lpDlgId, wLang ); } // Setup for failure hwndMain = NULL; if (hrsrc && (pTemplate = (LPVOID)LoadResource(HINST_THISDLL, hrsrc))) { DWORD cbTemplate; cbTemplate = SizeofResource(HINST_THISDLL, hrsrc); pTemplateMod = (LPVOID)LocalAlloc(LPTR, cbTemplate * 2); //double it to give some play leeway if (pTemplateMod) { hmemcpy(pTemplateMod, pTemplate, cbTemplate); //Check the direction of this dialog and change it if it does not match the owner. Prsht_PrepareTemplate(ppd, HINST_THISDLL, &pTemplateMod, (HRSRC *)&hrsrc, (LPSTR)lpDlgId,ppd->psh.hwndParent, &wUserLang); } else { pTemplateMod = pTemplate; // no modifications } // // Template editing and callbacks happen only if we were able // to create a copy for modifying. // if (pTemplateMod != pTemplate) { if (ppd->psh.dwFlags & PSH_NOCONTEXTHELP) { if (((LPDLGTEMPLATEEX)pTemplateMod)->wSignature == 0xFFFF){ ((LPDLGTEMPLATEEX)pTemplateMod)->dwStyle &= ~DS_CONTEXTHELP; } else { ((LPDLGTEMPLATE)pTemplateMod)->style &= ~DS_CONTEXTHELP; } } if (IS_WIZARD(ppd) && (ppd->psh.dwFlags & PSH_WIZARDCONTEXTHELP)) { if (((LPDLGTEMPLATEEX)pTemplateMod)->wSignature == 0xFFFF){ ((LPDLGTEMPLATEEX)pTemplateMod)->dwStyle |= DS_CONTEXTHELP; } else { ((LPDLGTEMPLATE)pTemplateMod)->style |= DS_CONTEXTHELP; } } // extra check for PSH_USEPAGELANG case if (ppd->psh.pfnCallback) { #ifdef WX86 if (ppd->fFlags & PD_WX86) Wx86Callback(ppd->psh.pfnCallback, NULL, PSCB_PRECREATE, (LPARAM)(LPVOID)pTemplateMod); else #endif ppd->psh.pfnCallback(NULL, PSCB_PRECREATE, (LPARAM)(LPVOID)pTemplateMod); } } if (pTemplateMod) { // // For NT, we want to use MS Shell Dlg 2 font in the prop sheet if // all of the pages in the sheet use MS Shell Dlg 2. // To do this, we ensure the template is DIALOGEX and that the // DS_SHELLFONT style bits (DS_SHELLFONT | DS_FIXEDSYS) are set. // if (ShouldUseMSShellDlg2Font(ppd)) { if (((LPDLGTEMPLATEEX)pTemplateMod)->wSignature != 0xFFFF) { // // Convert DLGTEMPLATE to DLGTEMPLATEEX. // LPVOID pTemplateCvtEx; int iCharset = GetDefaultCharsetFromLang(wLang); if (SUCCEEDED(CvtDlgToDlgEx(pTemplateMod, (LPDLGTEMPLATEEX *)&pTemplateCvtEx, iCharset))) { LocalFree(pTemplateMod); pTemplateMod = pTemplateCvtEx; } else { // Unable to convert to ShellFont; oh well goto NotShellFont; } } // // Set DS_SHELLFONT style bits so we get "MS Shell Dlg2" font. // ((LPDLGTEMPLATEEX)pTemplateMod)->dwStyle |= DS_SHELLFONT; ppd->fFlags |= PD_SHELLFONT; NotShellFont:; } // pTemplateMod is always unicode, even for the A function - no need to thunk hwndMain = CreateDialogIndirectParam(HINST_THISDLL, pTemplateMod, ppd->psh.hwndParent, PropSheetDlgProc, (LPARAM)(LPPROPDATA)ppd); // WORK AROUND WOW/USER BUG: Even though InitPropSheetDlg sets // ppd->hDlg, in the WOW scenario, the incoming hDlg is WRONG! // The USER guys say "Tough. You have to work around it." ppd->hDlg = hwndMain; } if (pTemplateMod != pTemplate) LocalFree(pTemplateMod); } if (!hwndMain) { int iPage; DebugMsg(DM_ERROR, TEXT("PropertySheet: unable to create main dialog")); if (hwndTopOwner && !(ppd->psh.dwFlags & PSH_MODELESS)) EnableWindow(hwndTopOwner, TRUE); // Release all page objects in REVERSE ORDER so we can have // pages that are dependant on eachother based on the initial // order of those pages // for (iPage = (int)ppd->psh.nPages - 1; iPage >= 0; iPage--) { ULONG_PTR dw = PropPageActivateContext(ppd, GETPISP(ppd, iPage)); DestroyPropertySheetPage(GETHPAGE(ppd, iPage)); PropPageDeactivateContext(dw); } goto FreePpdAndReturn; } if (ppd->psh.dwFlags & PSH_MODELESS) return (INT_PTR)hwndMain; while( ppd->hwndCurPage && GetMessage32(&msg32, NULL, 0, 0, TRUE) ) { // if (PropSheet_IsDialogMessage(ppd->hDlg, (LPMSG)&msg32)) if (Prop_IsDialogMessage(ppd, &msg32)) continue; TranslateMessage32(&msg32, TRUE); DispatchMessage32(&msg32, TRUE); } if( ppd->hwndCurPage ) { // GetMessage returned FALSE (WM_QUIT) DebugMsg( DM_TRACE, TEXT("PropertySheet: bailing in response to WM_QUIT (and reposting quit)") ); ButtonPushed( ppd, IDCANCEL ); // nuke ourselves PostQuitMessage( (int) msg32.wParam ); // repost quit for next enclosing loop } // don't let this get mangled during destroy processing nReturn = ppd->nReturn ; if (ppd->psh.hwndParent && (GetActiveWindow() == hwndMain)) { DebugMsg(DM_TRACE, TEXT("Passing activation up")); SetActiveWindow(ppd->psh.hwndParent); } if (hwndTopOwner) EnableWindow(hwndTopOwner, TRUE); if (IsWindow(hwndOriginalFocus)) { SetFocus(hwndOriginalFocus); } DestroyWindow(hwndMain); // do pickup any PSM_REBOOTSYSTEM or PSM_RESTARTWINDOWS sent during destroy if ((nReturn > 0) && ppd->nRestart) nReturn = ppd->nRestart; FreePpdAndReturn: #ifdef WIN32 LocalFree((HLOCAL)ppd); #else LocalFree((HLOCAL)LOWORD(ppd)); #endif return nReturn; } HPROPSHEETPAGE WINAPI CreateProxyPage(HPROPSHEETPAGE hpage16, HINSTANCE hinst16) { SetLastErrorEx(ERROR_CALL_NOT_IMPLEMENTED, SLE_WARNING); return NULL; } // DestroyPropsheetPageArray // // Helper function used during error handling. It destroys the // incoming property sheet pages. void DestroyPropsheetPageArray(LPCPROPSHEETHEADER ppsh) { int iPage; if (!(ppsh->dwFlags & PSH_PROPSHEETPAGE)) { // Release all page objects in REVERSE ORDER so we can have // pages that are dependant on eachother based on the initial // order of those pages for (iPage = (int)ppsh->nPages - 1; iPage >= 0; iPage--) { DestroyPropertySheetPage(ppsh->H_phpage[iPage]); } } } // PropertySheet API // // This function displays the property sheet described by ppsh. // // Since I don't expect anyone to ever check the return value // (we certainly don't), we need to make sure any provided phpage array // is always freed with DestroyPropertySheetPage, even if an error occurs. // // // The fNeedShadow parameter means "The incoming LPCPROPSHEETHEADER is in the // opposite character set from what you implement natively". // // If we are compiling UNICODE, then fNeedShadow is TRUE if the incoming // LPCPROPSHEETHEADER is really an ANSI property sheet page. // // If we are compiling ANSI-only, then fNeedShadow is always FALSE because // we don't support UNICODE in the ANSI-only version. // INT_PTR WINAPI _PropertySheet(LPCPROPSHEETHEADER ppsh, BOOL fNeedShadow) { PROPDATA *ppd; int iPage; // // validate header // ASSERT(IsValidPROPSHEETHEADERSIZE(sizeof(PROPSHEETHEADER))); if (!IsValidPROPSHEETHEADERSIZE(ppsh->dwSize)) { DebugMsg( DM_ERROR, TEXT("PropertySheet: dwSize is not correct") ); goto invalid_call; } if (ppsh->dwFlags & ~PSH_ALL) { DebugMsg( DM_ERROR, TEXT("PropertySheet: invalid flags") ); goto invalid_call; } if (ppsh->nPages >= MAXPROPPAGES) { DebugMsg( DM_ERROR, TEXT("PropertySheet: too many pages ( use MAXPROPPAGES )") ); goto invalid_call; } ppd = (PROPDATA *)LocalAlloc(LPTR, sizeof(PROPDATA)); if (ppd == NULL) { DebugMsg(DM_ERROR, TEXT("failed to alloc property page data")); invalid_call: DestroyPropsheetPageArray(ppsh); return -1; } // Initialize the flags. ppd->fFlags = FALSE; #ifdef WX86 // // If Wx86 is calling, set the flag that thunks the callbacks. // if ( Wx86IsCallThunked() ) { ppd->fFlags |= PD_WX86; } #endif if (fNeedShadow) ppd->fFlags |= PD_NEEDSHADOW; // make a copy of the header so we can party on it hmemcpy(&ppd->psh, ppsh, ppsh->dwSize); // so we don't have to check later... if (!(ppd->psh.dwFlags & PSH_USECALLBACK)) ppd->psh.pfnCallback = NULL; // fix up the page pointer to point to our copy of the page array ppd->psh.H_phpage = ppd->rghpage; GetCurrentActCtx(&ppd->hActCtxInit); if (ppd->psh.dwFlags & PSH_PROPSHEETPAGE) { // for lazy clients convert PROPSHEETPAGE structures into page handles LPCPROPSHEETPAGE ppsp = ppsh->H_ppsp; for (iPage = 0; iPage < (int)ppd->psh.nPages; iPage++) { ppd->psh.H_phpage[iPage] = _CreatePropertySheetPage(ppsp, fNeedShadow, ppd->fFlags & PD_WX86); if (!ppd->psh.H_phpage[iPage]) { iPage--; ppd->psh.nPages--; } ppsp = (LPCPROPSHEETPAGE)((LPBYTE)ppsp + ppsp->dwSize); // next PROPSHEETPAGE structure } } else { // The UNICODE build needs to hack around Hijaak 95. // ppd->psh.nPages = 0; for (iPage = 0; iPage < (int)ppsh->nPages; iPage++) { ppd->psh.H_phpage[ppd->psh.nPages] = _Hijaak95Hack(ppd, ppsh->H_phpage[iPage]); if (ppd->psh.H_phpage[ppd->psh.nPages]) { ppd->psh.nPages++; } } } // // Everybody else assumes that the HPROPSHEETPAGEs have been // internalized, so let's do that before anybody notices. // for (iPage = 0; iPage < (int)ppd->psh.nPages; iPage++) { SETPISP(ppd, iPage, InternalizeHPROPSHEETPAGE(ppd->psh.H_phpage[iPage])); } // // Walk all pages to see if any have help and if so, set the PSH_HASHELP // flag in the header. // if (!(ppd->psh.dwFlags & PSH_HASHELP)) { for (iPage = 0; iPage < (int)ppd->psh.nPages; iPage++) { if (GETPPSP(ppd, iPage)->dwFlags & PSP_HASHELP) { ppd->psh.dwFlags |= PSH_HASHELP; break; } } } return _RealPropertySheet(ppd); } INT_PTR WINAPI PropertySheetW(LPCPROPSHEETHEADERW ppsh) { return _PropertySheet(ppsh, FALSE); } INT_PTR WINAPI PropertySheetA(LPCPROPSHEETHEADERA ppsh) { PROPSHEETHEADERW pshW; INT_PTR iResult; // // Most validation is done by _PropertySheet, but we need // to validate the header size, or we won't survive the thunk. // if (!IsValidPROPSHEETHEADERSIZE(ppsh->dwSize)) { DebugMsg( DM_ERROR, TEXT("PropertySheet: dwSize is not correct") ); goto Error; } if (!ThunkPropSheetHeaderAtoW(ppsh, &pshW)) goto Error; iResult = _PropertySheet(&pshW, TRUE); FreePropSheetHeaderW(&pshW); return iResult; Error: DestroyPropsheetPageArray((LPCPROPSHEETHEADER)ppsh); return -1; } // // CopyPropertyPageStrings // // We have a PROPSHEETPAGE structure that contains pointers to strings. // For each string, create a copy and smash the pointer-to-copy in the // place where the original static pointer used to be. // // The method of copying varies depending on what kind of copy we want // to make, so we use a callback procedure. // // UNICODE-to-UNICODE: StrDupW // ANSI-to-UNICODE: StrDup_AtoW // ANSI-to-ANSI: StrDupA // // On failure, all strings that did not get properly duplicated are set // to NULL. You still have to call FreePropertyPageStrings to clear // them out. Notice that when we fail to allocate, we merely make a note // of the fact and continue onward. This ensures that all string fields // are set to NULL if they could not be dup'd. // // ppsp - A pointer to either a PROPSHEETPAGEA or PROPSHEETPAGEW. // The two structures are laid out identically, so it doesn't matter. // // pfnStrDup - function that will make the appropriate copy. // BOOL CopyPropertyPageStrings(LPPROPSHEETPAGE ppsp, STRDUPPROC pfnStrDup) { BOOL fSuccess = TRUE; if (!(ppsp->dwFlags & PSP_DLGINDIRECT) && !IS_INTRESOURCE(ppsp->P_pszTemplate)) { ppsp->P_pszTemplate = pfnStrDup(ppsp->P_pszTemplate); if (!ppsp->P_pszTemplate) fSuccess = FALSE; } if ((ppsp->dwFlags & PSP_USEICONID) && !IS_INTRESOURCE(ppsp->P_pszIcon)) { ppsp->P_pszIcon = pfnStrDup(ppsp->P_pszIcon); if (!ppsp->P_pszIcon) fSuccess = FALSE; } if ((ppsp->dwFlags & PSP_USETITLE) && !IS_INTRESOURCE(ppsp->pszTitle)) { ppsp->pszTitle = pfnStrDup(ppsp->pszTitle); if (!ppsp->pszTitle) fSuccess = FALSE; } if ((ppsp->dwFlags & PSP_USEHEADERTITLE) && !IS_INTRESOURCE(ppsp->pszHeaderTitle)) { ppsp->pszHeaderTitle = pfnStrDup(ppsp->pszHeaderTitle); if (!ppsp->pszHeaderTitle) fSuccess = FALSE; } if ((ppsp->dwFlags & PSP_USEHEADERSUBTITLE) && !IS_INTRESOURCE(ppsp->pszHeaderSubTitle)) { ppsp->pszHeaderSubTitle = pfnStrDup(ppsp->pszHeaderSubTitle); if (!ppsp->pszHeaderSubTitle) fSuccess = FALSE; } return fSuccess; } // // FreePropertyPageStrings // // Free the strings that live inside a property sheet page structure. // // ppsp - A pointer to either a PROPSHEETPAGEA or PROPSHEETPAGEW. // The two structures are laid out identically, so it doesn't matter. // void FreePropertyPageStrings(LPCPROPSHEETPAGE ppsp) { if (!(ppsp->dwFlags & PSP_DLGINDIRECT) && !IS_INTRESOURCE(ppsp->P_pszTemplate)) LocalFree((LPVOID)ppsp->P_pszTemplate); if ((ppsp->dwFlags & PSP_USEICONID) && !IS_INTRESOURCE(ppsp->P_pszIcon)) LocalFree((LPVOID)ppsp->P_pszIcon); if ((ppsp->dwFlags & PSP_USETITLE) && !IS_INTRESOURCE(ppsp->pszTitle)) LocalFree((LPVOID)ppsp->pszTitle); if ((ppsp->dwFlags & PSP_USEHEADERTITLE) && !IS_INTRESOURCE(ppsp->pszHeaderTitle)) LocalFree((LPVOID)ppsp->pszHeaderTitle); if ((ppsp->dwFlags & PSP_USEHEADERSUBTITLE) && !IS_INTRESOURCE(ppsp->pszHeaderSubTitle)) LocalFree((LPVOID)ppsp->pszHeaderSubTitle); } //************************************************************* // // ThunkPropSheetHeaderAtoW () // // Purpose: Thunks the Ansi version of PROPSHEETHEADER to // Unicode. // // Note that the H_phpage / H_ppsp field is not thunked. // We'll deal with that separately. // //************************************************************* BOOL ThunkPropSheetHeaderAtoW (LPCPROPSHEETHEADERA ppshA, LPPROPSHEETHEADERW ppsh) { // // Deciding whether an item should be freed or not is tricky, so we // keep a private array of all the pointers we've allocated, so we // know what to free when we fail. // LPTSTR Alloced[5] = { 0 }; ASSERT(IsValidPROPSHEETHEADERSIZE(ppshA->dwSize)); hmemcpy(ppsh, ppshA, ppshA->dwSize); ppsh->dwFlags |= PSH_THUNKED; if ((ppsh->dwFlags & PSH_USEICONID) && !IS_INTRESOURCE(ppsh->H_pszIcon)) { ppsh->H_pszIcon = Alloced[0] = StrDup_AtoW(ppsh->H_pszIcon); if (!ppsh->H_pszIcon) goto ExitIcon; } if (!IS_WIZARDPSH(*ppsh) && !IS_INTRESOURCE(ppsh->pszCaption)) { ppsh->pszCaption = Alloced[1] = StrDup_AtoW(ppsh->pszCaption); if (!ppsh->pszCaption) goto ExitCaption; } if ((ppsh->dwFlags & PSH_USEPSTARTPAGE) && !IS_INTRESOURCE(ppsh->H_pStartPage)) { ppsh->H_pStartPage = Alloced[2] = StrDup_AtoW(ppsh->H_pStartPage); if (!ppsh->H_pStartPage) goto ExitStartPage; } if (ppsh->dwFlags & PSH_WIZARD97) { if ((ppsh->dwFlags & PSH_WATERMARK) && !(ppsh->dwFlags & PSH_USEHBMWATERMARK) && !IS_INTRESOURCE(ppsh->H_pszbmWatermark)) { ppsh->H_pszbmWatermark = Alloced[3] = StrDup_AtoW(ppsh->H_pszbmWatermark); if (!ppsh->H_pszbmWatermark) goto ExitWatermark; } if ((ppsh->dwFlags & PSH_HEADER) && !(ppsh->dwFlags & PSH_USEHBMHEADER) && !IS_INTRESOURCE(ppsh->H_pszbmHeader)) { ppsh->H_pszbmHeader = Alloced[4] = StrDup_AtoW(ppsh->H_pszbmHeader); if (!ppsh->H_pszbmHeader) goto ExitHeader; } } return TRUE; ExitHeader: if (Alloced[3]) LocalFree(Alloced[3]); ExitWatermark: if (Alloced[2]) LocalFree(Alloced[2]); ExitStartPage: if (Alloced[1]) LocalFree(Alloced[1]); ExitCaption: if (Alloced[0]) LocalFree(Alloced[0]); ExitIcon: return FALSE; } void FreePropSheetHeaderW(LPPROPSHEETHEADERW ppsh) { if ((ppsh->dwFlags & PSH_USEICONID) && !IS_INTRESOURCE(ppsh->H_pszIcon)) LocalFree((LPVOID)ppsh->H_pszIcon); if (!IS_WIZARDPSH(*ppsh) && !IS_INTRESOURCE(ppsh->pszCaption)) LocalFree((LPVOID)ppsh->pszCaption); if ((ppsh->dwFlags & PSH_USEPSTARTPAGE) && !IS_INTRESOURCE(ppsh->H_pStartPage)) LocalFree((LPVOID)ppsh->H_pStartPage); if (ppsh->dwFlags & PSH_WIZARD97) { if ((ppsh->dwFlags & PSH_WATERMARK) && !(ppsh->dwFlags & PSH_USEHBMWATERMARK) && !IS_INTRESOURCE(ppsh->H_pszbmWatermark)) LocalFree((LPVOID)ppsh->H_pszbmWatermark); if ((ppsh->dwFlags & PSH_HEADER) && !(ppsh->dwFlags & PSH_USEHBMHEADER) && !IS_INTRESOURCE(ppsh->H_pszbmHeader)) LocalFree((LPVOID)ppsh->H_pszbmHeader); } }