windows-nt/Source/XPSP1/NT/shell/comctl32/v6/prsht.c
2020-09-26 16:20:57 +08:00

5503 lines
175 KiB
C

#include "ctlspriv.h"
#include "help.h" // Help IDs
#include "prshti.h"
#include "dlgcvt.h"
#ifdef WX86
#include <wx86ofl.h>
#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);
}
}