windows-nt/Source/XPSP1/NT/enduser/stuff/hhctrl/popup.cpp

755 lines
23 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
// Copyright (C) Microsoft Corporation 1996, All Rights reserved.
#include "header.h"
#include "popup.h"
#include "cinput.h"
#include "hha_strtable.h"
#include "strtable.h"
#include "hhshell.h" // g_hwndApi.
#include "resource.h"
/////////////////////////////////////////////////////////////////////
//
// Constants
//
static const char txtComment[] = ".comment";
static const char txtTopicID[] = ".topic";
static const char txtCrLf[] = "\r\n";
static const char txtSpace[] = " ";
static const char txtDefaultFileName[] = "/cshelp.txt" ;
const int TEXT_PADDING = 5; // padding around the text.
const int SHADOW_WIDTH = 6;
const int SHADOW_HEIGHT = 6;
/////////////////////////////////////////////////////////////////////
//
// Globals
//
CPopupWindow* g_pPopupWindow;
/////////////////////////////////////////////////////////////////////
//
// Constructor
//
CPopupWindow::CPopupWindow()
{
ZERO_INIT_CLASS(CPopupWindow);
m_pfsclient = NULL; // doesn't get cleared, don't know why
}
/////////////////////////////////////////////////////////////////////
//
// Constructor
//
CPopupWindow::~CPopupWindow()
{
CleanUp();
}
/////////////////////////////////////////////////////////////////////
//
// Constructor Helper - Allows reusing window, but breaks caching.
//
void CPopupWindow::CleanUp(void)
{
if (IsValidWindow(m_hwnd))
DestroyWindow(m_hwnd);
if (m_pfsclient)
delete m_pfsclient;
if (m_pszText)
lcClearFree(&m_pszText);
if (m_hfont)
DeleteObject(m_hfont);
if (m_ptblText)
delete m_ptblText;
if (m_pszTextFile)
lcClearFree((void**) &m_pszTextFile);
m_pfsclient = NULL;
m_ptblText = NULL;
m_hfont = NULL;
}
void CPopupWindow::SetColors(COLORREF clrForeground, COLORREF clrBackground)
{
if (clrForeground != (COLORREF) -1)
m_clrForeground = clrForeground;
else
m_clrForeground = GetSysColor(COLOR_WINDOWTEXT);
if (clrBackground != (COLORREF) -1)
m_clrBackground = clrBackground;
else
m_clrBackground = RGB(255, 255, 238); // dithered yellow
// If the colors are the same, then use standard window colors
HDC hdc = GetWindowDC(m_hwndCaller);
if (GetHighContrastFlag() ||
GetNearestColor(hdc, m_clrBackground) ==
GetNearestColor(hdc, m_clrForeground)) {
m_clrForeground = GetSysColor(COLOR_WINDOWTEXT);
m_clrBackground = GetSysColor(COLOR_WINDOW);
}
ReleaseDC(m_hwndCaller, hdc);
}
// assumes text in m_pszText, result in m_rcWindow
#define DEFAULT_DT_FLAGS (DT_EXPANDTABS | DT_NOCLIP | DT_NOPREFIX | DT_WORDBREAK | DT_RTLREADING)
void CPopupWindow::CalculateRect(POINT pt)
{
RECT rc; // BUGBUG: Broken on multiple monitor systems
GetClientRect(GetDesktopWindow(), &rc); // get desktop area
int cyScreen = RECT_HEIGHT(rc);
int cxScreen = RECT_WIDTH(rc);
HDC hdc = CreateDC("DISPLAY", NULL, NULL, NULL);
HFONT hfontOld;
if (m_hfont)
hfontOld = (HFONT) SelectObject(hdc, m_hfont);
DrawText(hdc, m_pszText, -1, &rc, DEFAULT_DT_FLAGS | DT_CALCRECT);
// Check for an overly wide but short popup
if (rc.bottom * 12 < rc.right) {
rc.right = rc.bottom * 12;
DrawText(hdc, m_pszText, -1, &rc, DEFAULT_DT_FLAGS | DT_CALCRECT);
}
if (m_hfont)
SelectObject(hdc, hfontOld);
m_rcWindow.left = pt.x - (RECT_WIDTH(rc) / 2);
m_rcWindow.right = m_rcWindow.left + RECT_WIDTH(rc);
m_rcWindow.top = pt.y;
m_rcWindow.bottom = m_rcWindow.top + RECT_HEIGHT(rc);
m_rcWindow.left -= m_rcMargin.left;
m_rcWindow.top -= m_rcMargin.top;
m_rcWindow.right += m_rcMargin.right;
m_rcWindow.bottom += m_rcMargin.bottom;
if (m_rcWindow.left < 0)
OffsetRect(&m_rcWindow, -m_rcWindow.left, 0);
if (m_rcWindow.bottom > cyScreen)
OffsetRect(&m_rcWindow, 0, cyScreen - m_rcWindow.bottom);
}
static BOOL s_fRegistered;
const char txtPopupClass[] = "hh_popup";
HWND CPopupWindow::doPopupWindow(void)
{
if (!s_fRegistered) {
WNDCLASS wndclass;
ZeroMemory(&wndclass, sizeof(WNDCLASS));
wndclass.style = CS_VREDRAW | CS_HREDRAW;
wndclass.lpfnWndProc = PopupWndProc;
wndclass.hInstance = _Module.GetModuleInstance();
wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.lpszClassName = txtPopupClass;
s_fRegistered = RegisterClass(&wndclass);
}
ASSERT_COMMENT(m_clrForeground != (COLORREF) -1, "Forgot to call SetColors()");
char pszPopupTitle[128];
lstrcpyn(pszPopupTitle, m_pszText, 128);
// t-jzybur 4-3-99: Added WS_EX_TOOLWINDOW to prevent a taskbar entry for the
// popup text.
m_hwnd = CreateWindowEx(WS_EX_TOOLWINDOW, txtPopupClass, pszPopupTitle, WS_POPUP,
m_rcWindow.left, m_rcWindow.top, RECT_WIDTH(m_rcWindow) + SHADOW_WIDTH,
RECT_HEIGHT(m_rcWindow) + SHADOW_HEIGHT,
m_hwndCaller, NULL, _Module.GetModuleInstance(), NULL);
if (IsValidWindow(m_hwnd)) {
SetWindowLongPtr(m_hwnd, GWLP_USERDATA, (LONG_PTR) this);
ShowWindow(m_hwnd, SW_SHOW);
// t-jzybur 4-3-99: Added SetForegroundWindow to activate the popup text. It could
// be inactive if the previous popup text was closed by clicking the mouse somwhere
// outside of the popup window.
SetForegroundWindow(m_hwnd);
// t-jzybur 4-3-99: Instead of capturing the focus and responding to click events,
// we'll respond to click events and deactivate messages. Its a cleaneer event model,
// and we won't have the possibility of locking in the hour glass cursor.
// SetCapture(m_hwnd);
}
return m_hwnd;
}
LRESULT CALLBACK PopupWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
CPopupWindow* pThis;
RECT rc;
PAINTSTRUCT ps;
HFONT hfontOld;
switch (msg) {
case WM_ERASEBKGND:
hdc = (HDC) wParam;
pThis = (CPopupWindow*) GetWindowLongPtr(hwnd, GWLP_USERDATA);
GetClipBox(hdc, &rc);
return PaintShadowBackground(hwnd, (HDC) wParam, pThis->m_clrBackground);
break;
case WM_PAINT:
pThis = (CPopupWindow*) GetWindowLongPtr(hwnd, GWLP_USERDATA);
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
rc.left += pThis->m_rcMargin.left;
rc.top += pThis->m_rcMargin.top;
rc.right -= pThis->m_rcMargin.right;
rc.bottom -= pThis->m_rcMargin.bottom;
rc.right -= SHADOW_WIDTH;
rc.bottom -= SHADOW_HEIGHT;
if (pThis->m_hfont)
hfontOld = (HFONT) SelectObject(hdc, pThis->m_hfont);
SetTextColor(hdc, pThis->m_clrForeground);
SetBkColor(hdc, pThis->m_clrBackground);
SetBkMode(hdc, TRANSPARENT);
DrawText(hdc, pThis->m_pszText, -1, &rc, DEFAULT_DT_FLAGS);
if (pThis->m_hfont)
SelectObject(hdc, hfontOld);
EndPaint(hwnd, &ps);
break;
// t-jzybur 4-3-99: Added WndProc handler to close popup text on
// window deactivation messages.
case WM_ACTIVATE:
if (LOWORD(wParam) != WA_INACTIVE) break;
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_SYSKEYDOWN:
case WM_KEYDOWN:
pThis = (CPopupWindow*) GetWindowLongPtr(hwnd, GWLP_USERDATA);
pThis->m_hwnd = NULL;
PostMessage(hwnd, WM_CLOSE, 0, 0);
break;
// t-jzybur 4-3-99: Removed ReleaseCapture along with SetCapture.
// case WM_DESTROY:
// ReleaseCapture();
// return DefWindowProc(hwnd, msg, wParam, lParam);
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
/***************************************************************************
FUNCTION: PaintShadowBackground
PURPOSE: Draws a border and a shadow around a window
PARAMETERS:
hwnd
hdc
RETURNS:
COMMENTS:
MODIFICATION DATES:
02-Mar-1997 [ralphw]
***************************************************************************/
static const WORD rgwPatGray[] =
{ 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA };
const DWORD PATMERGE = 0x00A000C9;
BOOL PaintShadowBackground(HWND hwnd, HDC hdc, COLORREF clrBackground)
{
BOOL fStockBrush; // Whether hBrush is a stock object
/*
* First the background of the "fake" window is erased leaving the
* desktop where the shadow will be.
*/
RECT rcClient; // Will always be client rectangle
GetClientRect(hwnd, &rcClient);
RECT rct = rcClient;
rct.bottom = max(0, rct.bottom - SHADOW_HEIGHT);
rct.right = max(0, rct.right - SHADOW_WIDTH);
HBRUSH hBrush = CreateSolidBrush((clrBackground == (COLORREF) -1 ?
GetSysColor(COLOR_WINDOW) : clrBackground));
if (!hBrush)
return FALSE;
UnrealizeObject(hBrush);
POINT pt;
pt.x = pt.y = 0;
ClientToScreen(hwnd, &pt);
SetBrushOrgEx(hdc, pt.x, pt.y, NULL);
FillRect(hdc, &rct, hBrush);
DeleteObject(hBrush);
// Next we create the "window" border
rct = rcClient;
rct.bottom = max(0, rct.bottom - SHADOW_HEIGHT);
rct.right = max(0, rct.right - SHADOW_WIDTH);
FrameRect(hdc, &rct, (HBRUSH) GetStockObject(BLACK_BRUSH));
InflateRect(&rct, -1, -1);
FrameRect(hdc, &rct, (HBRUSH) GetStockObject(LTGRAY_BRUSH));
// Now we create the brush for the the shadow
hBrush = 0;
HBITMAP hbmGray;
if ((hbmGray = CreateBitmap(8, 8, 1, 1, rgwPatGray)) != NULL) {
hBrush = CreatePatternBrush(hbmGray);
DeleteObject(hbmGray);
fStockBrush = FALSE;
}
// If we cannot create the pattern brush, we try to use a black brush.
if (hBrush == 0) {
if (!(hBrush == GetStockObject(BLACK_BRUSH)))
return FALSE;
fStockBrush = TRUE;
}
SetROP2(hdc, R2_MASKPEN);
SetBkMode(hdc, TRANSPARENT);
HPEN hpen;
if ((hpen = (HPEN) GetStockObject(NULL_PEN)) != 0)
SelectObject(hdc, hpen); // We do not care if this fails
HBRUSH hbrushTemp = (HBRUSH) SelectObject(hdc, hBrush); // or if this fails, since the
// paint behavior will be okay.
rct = rcClient; // Paint the right side rectangle
rct.top = rct.top + SHADOW_HEIGHT;
rct.left = max(0, rct.right - SHADOW_WIDTH);
PatBlt(hdc, rct.left, rct.top, rct.right - rct.left,
rct.bottom - rct.top, PATMERGE);
rct = rcClient; // Paint the bottom rectangle
rct.top = max(0, rct.bottom - SHADOW_HEIGHT);
rct.left = rct.left + SHADOW_WIDTH;
// Note overlap by one pixel!
rct.right = max(0, rct.right - SHADOW_WIDTH + 1);
PatBlt(hdc, rct.left, rct.top, rct.right - rct.left,
rct.bottom - rct.top, PATMERGE);
// Cleanup brush
if (hbrushTemp != NULL)
SelectObject(hdc, hbrushTemp);
if (!fStockBrush)
DeleteObject(hBrush);
return TRUE;
}
BOOL CPopupWindow::ReadTextFile(PCSTR pszFile)
{
// If the string pointer is NULL or empty we have to bail.
if (!pszFile || pszFile[0] == '\0')
return FALSE ;
// Now, verify that we have a text file specified. Urg! More parsing of URL's
CStr cszFileName;
PCSTR pszSubFile = GetCompiledName(pszFile, &cszFileName) ;
if (!pszSubFile || pszSubFile[0] == '\0')
{
pszSubFile = txtDefaultFileName ;
}
cszFileName += txtDoubleColonSep ;
cszFileName += pszSubFile ;
#if 0//REVIEW:: This never works, because CleanUp resets everything. Removed for safety.
// Check to see if its cached.
if (lstrcmpi(cszFileName, m_pszTextFile) == 0)
return TRUE; // we've cached this file
#endif
CInput input;
if (!input.Open(cszFileName))
return FALSE;
if (m_ptblText)
delete m_ptblText;
// Allocate a text buffer.
CStr cszText;
if (m_pszTextFile)
lcFree(m_pszTextFile);
m_pszTextFile = lcStrDup(cszFileName);
m_ptblText = new CTable;
while (input.getline(&cszText)) {
if (!IsSamePrefix(cszText, txtComment))
m_ptblText->AddString(cszText);
}
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
//
// CreatePopupWindow.
//
HWND CPopupWindow::CreatePopupWindow(HWND hwndCaller, PCSTR pszFile,
HH_POPUP* pPopup)
{
if (!pPopup) // TODO: Validate pPopup pointer.
return NULL ;
m_hwndCaller = hwndCaller;
//--- Getting the string to display. We can get the string to display in three ways:
// 1. From a string contained in the HH_POPUP structure.
// 2. From a string resource in a module.
// 3. From a txt file embedded in the CHM.
// This order is the order of least complicated to most complicated. We start with the
// least complicated method to save loading extra working set.
// NOTE: A future possibility would be to search in the reverse order. This would allow
// using a string in the HH_POPUP structure if one wasn't found in the embedded txt file.
bool bFoundString = false ;
if ((pPopup->idString == 0) && IsNonEmptyString(pPopup->pszText)) // 1. Get string from HH_POPUP. Only if idString is 0! See HH 3532.
{
m_pszText = lcStrDup(pPopup->pszText);
bFoundString = true ;
}
else if (pPopup->idString && pPopup->hinst) // 2. From a string resource in a module.
{
m_pszText = (PSTR) lcMalloc(MAX_STRING_RESOURCE_LEN);
char *pszText = NULL;
if ((pszText =(char *) GetStringResource(pPopup->idString, pPopup->hinst)) && *pszText )
{
strcpy(m_pszText,pszText);
bFoundString = true ;
}
}
else if (IsNonEmptyString(pszFile)) // 3. From a txt file embedded in the CHM.
{
// Try to read the text file.
if (ReadTextFile(pszFile))
{
ASSERT(m_ptblText);
for (int pos = 1; pos <= m_ptblText->CountStrings(); pos++) {
if (IsSamePrefix(m_ptblText->GetPointer(pos), txtTopicID)) {
PSTR pszNumber = FirstNonSpace(m_ptblText->GetPointer(pos) +
strlen(txtTopicID));
if (pszNumber && pPopup->idString == (UINT) Atoi(pszNumber))
break;
}
}
// Do we have enough strings?
if (pos <= m_ptblText->CountStrings())
{
CStr cszText(txtZeroLength);
BOOL fAddSpace = FALSE;
for (++pos; pos <= m_ptblText->CountStrings(); pos++) {
PCSTR pszLine = m_ptblText->GetPointer(pos);
if (*pszLine == '.')
break;
if (!*pszLine) {
if (pos + 1 <= m_ptblText->CountStrings()) {
pszLine = m_ptblText->GetPointer(pos + 1);
if (*pszLine != '.')
cszText += txtCrLf;
}
fAddSpace = FALSE;
continue;
}
else if (fAddSpace)
cszText += txtSpace;
cszText += pszLine;
fAddSpace = TRUE;
}
cszText.TransferPointer(&m_pszText);
bFoundString = true ;
}
else
{
if (IsHelpAuthor(NULL))
{
char szMsgBuf[256];
wsprintf(szMsgBuf, pGetDllStringResource(IDS_HHA_MISSING_TP_TXT),
pPopup->idString, pszFile);
doAuthorMsg(IDS_IDH_GENERIC_STRING, szMsgBuf);
}
}
}
else
{
// We couldn't read the text file in. Will display error popup...
doAuthorMsg(IDS_CANT_OPEN, pszFile);
}
}
// This needs to be true when displaying static strings loaded from the resource
// because the font specified by the user might not be appropriate for the
// string loaded from the resource.
//
BOOL bUseDefaultFont = FALSE;
//--- Do we have a string?
if (!bFoundString)
{
if (m_pszText)
{
lcClearFree(&m_pszText);
m_pszText = NULL ;
}
m_pszText = (PSTR) lcMalloc(MAX_STRING_RESOURCE_LEN);
char *pszText;
if ((pszText = (char *) GetStringResource(IDS_IDH_MISSING_CONTEXT)) )
{
strcpy(m_pszText,pszText);
bUseDefaultFont = TRUE;
}
else
{
// Dang it! We can't even get our own string.
CleanUp() ;
return NULL ;
}
}
//--- Okay, now we can display the string.
m_rcMargin.left = (pPopup->rcMargins.left >= 0 ?
pPopup->rcMargins.left : TEXT_PADDING);
m_rcMargin.top = (pPopup->rcMargins.top >= 0 ?
pPopup->rcMargins.top : TEXT_PADDING);
m_rcMargin.right = (pPopup->rcMargins.right >= 0 ?
pPopup->rcMargins.right : TEXT_PADDING);
m_rcMargin.bottom = (pPopup->rcMargins.bottom >= 0 ?
pPopup->rcMargins.bottom : TEXT_PADDING);
if (IsNonEmptyString(pPopup->pszFont) && !bUseDefaultFont) {
if (m_hfont)
DeleteObject(m_hfont);
m_hfont = CreateUserFont(pPopup->pszFont);
}
else if (!m_hfont)
m_hfont = CreateUserFont(GetStringResource(IDS_DEFAULT_RES_FONT));
// Get a default location to display.
POINT pt = pPopup->pt;
if (pt.x == -1 && pt.x == -1 && IsWindow(hwndCaller))
{
RECT rcWindow;
GetWindowRect(hwndCaller, &rcWindow);
pt.x = rcWindow.left + (RECT_WIDTH(rcWindow) / 2);
pt.y = rcWindow.top;
}
CalculateRect(pt);
SetColors(pPopup->clrForeground, pPopup->clrBackground);
return doPopupWindow();
}
//////////////////////////////////////////////////////////////////////////
//
// Handle the HH_TP_HELP_CONTEXTMENU command. Display the What's this menu.
//
HWND
doTpHelpContextMenu(HWND hwndMain, LPCSTR pszFile, DWORD_PTR ulData)
{
/*
In WinHelp we put up a little menu for this message. In HTML Help we don't.
So we remove the menu and just handle this like HH_TP_HELP_WM_HELP.
*/
return doTpHelpWmHelp(hwndMain, pszFile, ulData) ;
/*
ASSERT(IsWindow(hwndMain)) ;
// Create the menu.
HMENU hMenu = LoadMenu(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_WHATSTHIS_MENU)) ;
ASSERT(hMenu) ;
// Get the Popup Menu
HMENU hPopupMenu = GetSubMenu(hMenu, 0) ;
//--- Get the location to display the menu
POINT pt ;
// Use the mouse cursor position.
GetCursorPos(&pt) ;
// Set the style of the menu.
DWORD style = TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD ;
// Display the menu.
int iCmd = TrackPopupMenuEx(hPopupMenu,
style ,
pt.x, pt.y,
g_hwndApi ? g_hwndApi : hwndMain, // We have to have a window in the current thread!
NULL) ;
#ifdef _DEBUG
DWORD err ;
if (iCmd == 0)
{
err = ::GetLastError() ;
}
#endif
// Cleanup
DestroyMenu(hMenu) ;
// Act on the item.
if (iCmd == IDM_WHATSTHIS)
{
return doTpHelpWmHelp(hwndMain, pszFile, ulData) ;
}
else
{
return NULL ;
}
*/
}
///////////////// Dialog control parsing from helpcall.c in user32 ///////
const int MAX_ATTEMPTS = 5; // maximum -1 id controls to search through
HWND doTpHelpWmHelp(HWND hwndMain, LPCSTR pszFile, DWORD_PTR ulData)
{
int id = GetDlgCtrlID(hwndMain); // get control id
int idSave = id;
DWORD* pid = (DWORD*) ulData;
if ((short) id == -1)
{ // static control?
HWND hwndCtrl = hwndMain;
int cAttempts = 0;
// For non-id controls (typically static controls), step
// through to the next tab item. Keep finding the next tab
// item until we find a valid id, or we have tried
// MAX_ATTEMPTS times.
do
{
hwndCtrl = GetNextWindow(hwndCtrl, GW_HWNDNEXT);
// hwndCtrl will be NULL if hwndMain doesn't have a parent,
// or if there are no tab stops.
if (!hwndCtrl)
{
DBWIN("GetNextDlgHelpItem failed.");
return NULL;
}
id = GetDlgCtrlID(hwndCtrl);
}
while ((id == -1) && (++cAttempts < MAX_ATTEMPTS));
}
// Find the id value in array of id/help context values
for (int i = 0; pid[i]; i += 2)
{
if ((int) pid[i] == id)
break;
}
// Create a popup structure to pass to doDisplayTextPopup.
HH_POPUP popup ;
memset(&popup, 0, sizeof(popup)) ;
// We want the default window size.
popup.pt.x = -1 ;
popup.pt.y = -1 ;
// We want the default margins.
popup.rcMargins.top =
popup.rcMargins.bottom =
popup.rcMargins.left =
popup.rcMargins.right = -1 ;
if (!pid[i])
{
popup.hinst = _Module.GetResourceInstance();
switch (id) {
case IDOK:
popup.idString = IDS_IDH_OK;
break;
case IDCANCEL:
popup.idString = IDS_IDH_CANCEL;
break;
case IDHELP:
popup.idString = IDS_IDH_HELP;
break;
default:
if (IsHelpAuthor(NULL))
{
char szMsgBuf[256];
wsprintf(szMsgBuf,
pGetDllStringResource(IDS_HHA_MISSING_HELP_ID), idSave);
doAuthorMsg(IDS_IDH_GENERIC_STRING, szMsgBuf);
}
popup.idString = IDS_IDH_MISSING_CONTEXT;
break;
}
return doDisplayTextPopup(hwndMain, NULL, &popup) ;
}
else
{
ulData = pid[i + 1];
if (ulData == (DWORD) -1)
return NULL; // caller doesn't want help after all
if (IsHelpAuthor(NULL))
{
char szMsgBuf[256];
wsprintf(szMsgBuf, pGetDllStringResource(IDS_HHA_HELP_ID),
(int) pid[i], (int) pid[i + 1], pszFile);
SendStringToParent(szMsgBuf);
}
// Set the id of the string that we want.
popup.idString = (UINT)ulData;
return doDisplayTextPopup(hwndMain, pszFile, &popup) ;
}
}
/////////////////////////////////////////////////////////////////////
//
// doDisplaytextPopup
//
HWND
doDisplayTextPopup(HWND hwndMain, LPCSTR pszFile, HH_POPUP* pPopup)
{
if (!g_pPopupWindow)
{
g_pPopupWindow = new CPopupWindow;
}
g_pPopupWindow->CleanUp();
return g_pPopupWindow->CreatePopupWindow(hwndMain, pszFile, pPopup);
}