You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5456 lines
179 KiB
5456 lines
179 KiB
#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);
|
|
|
|
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))
|
|
{
|
|
StringCchPrintf(szTemp, ARRAYSIZE(szTemp), szFormat, pCaption);
|
|
pCaption = szTemp;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ppd->psh.dwFlags & PSH_RTLREADING)
|
|
{
|
|
SetWindowLong(hDlg, GWL_EXSTYLE, GetWindowLong(hDlg, GWL_EXSTYLE) | WS_EX_RTLREADING);
|
|
}
|
|
|
|
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))
|
|
{
|
|
StringCchCopyW(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), s_FontTbl[i].lpEnglish);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i >= ARRAYSIZE(s_FontTbl))
|
|
{
|
|
StringCchCopyW(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), ppfd->szFace);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
StringCchCopyW(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), ppfd->szFace);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
tie.tci.mask = TCIF_TEXT | TCIF_PARAM | TCIF_IMAGE | (pi.bRTL ? TCIF_RTLREADING : 0);
|
|
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))
|
|
{
|
|
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);
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
|
|
tie.tci.mask = TCIF_TEXT | TCIF_PARAM | TCIF_IMAGE | (pi.bRTL ? TCIF_RTLREADING : 0);
|
|
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};
|
|
|
|
StringCchCopy(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), szFace);
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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:
|
|
LocalFree((HLOCAL)ppd);
|
|
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);
|
|
}
|
|
}
|