/*++ Copyright (c) 1996 Microsoft Corporation Module Name: clientcp.c Abstract: Functions for handling events in the "Client Cover Page" tab of the fax client configuration property sheet Environment: Fax configuration applet Revision History: 03/13/96 -davidx- Created it. mm/dd/yy -author- description --*/ #include #include #include #include #include #include #include #include "faxutil.h" #include "faxreg.h" #include "faxcfgrs.h" #include "faxhelp.h" #include "faxcfg.h" PCPDATA pCPInfo; BOOL ClientCoverPageProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) /*++ Routine Description: Procedure for handling the "Client Cover Page" tab Arguments: hDlg - Identifies the property sheet page message - Specifies the message wParam - Specifies additional message-specific information lParam - Specifies additional message-specific information Return Value: Depends on the value of message parameter --*/ { INT cmdId; switch (message) { case WM_INITDIALOG: pCPInfo = AllocCoverPageInfo(); InitCoverPageList(pCPInfo, hDlg); return TRUE; case WM_COMMAND: switch (cmdId = GET_WM_COMMAND_ID(wParam, lParam)) { case IDC_COVERPG_ADD: case IDC_COVERPG_NEW: case IDC_COVERPG_OPEN: case IDC_COVERPG_REMOVE: // // User clicked one of the buttons for managing cover page files // cmdId = (cmdId == IDC_COVERPG_REMOVE) ? CPACTION_REMOVE : (cmdId == IDC_COVERPG_OPEN) ? CPACTION_OPEN : (cmdId == IDC_COVERPG_NEW) ? CPACTION_NEW : CPACTION_BROWSE; ManageCoverPageList(hDlg, pCPInfo, GetDlgItem(hDlg, IDC_COVERPG_LIST), cmdId); break; case IDC_COVERPG_LIST: switch (GET_WM_COMMAND_CMD(wParam, lParam)) { case LBN_SELCHANGE: UpdateCoverPageControls(hDlg); break; case LBN_DBLCLK: // // Double-clicking in the cover page list is equivalent // to pressing the "Open" button // ManageCoverPageList(hDlg, pCPInfo, GetDlgItem(hDlg, cmdId), CPACTION_OPEN); break; } break; default: return FALSE; } return TRUE; case WM_NOTIFY: switch (((NMHDR *) lParam)->code) { case PSN_SETACTIVE: break; case PSN_APPLY: return PSNRET_NOERROR; } break; case WM_HELP: case WM_CONTEXTMENU: return HandleHelpPopup(hDlg, message, wParam, lParam, CLIENT_COVERPG_PAGE); } return FALSE; } // // Find the filename portion given a filename: // return a pointer to the '.' character if successful // NULL if there is no extension // #define FindFilenameExtension(pFilename) _tcsrchr(pFilename, TEXT(FILENAME_EXT)) VOID HandleOpenCoverPage( HWND hDlg, PCPDATA pCPInfo, HWND hwndList, LPTSTR pSelected, INT action ) /*++ Routine Description: Edit the currently selected cover page file or create a new cover page file Arguments: hDlg - Handle to the dialog window on which the list of cover pages is displayed pCPInfo - Points to cover page information hwndList - Handle to cover page listbox window pSelected - Currently selected cover page filename action - Open an existing cover page file or create a new one Return Value: NONE --*/ { MSG msg; // used for peeking messages DWORD WaitObj; TCHAR filename[MAX_PATH]; LPTSTR pExecutableName, pDirPath, pFilename; SHELLEXECUTEINFO shellExeInfo = { sizeof(SHELLEXECUTEINFO), SEE_MASK_NOCLOSEPROCESS, hDlg, NULL, NULL, NULL, NULL, SW_SHOWNORMAL, }; // // Determine the default directory to run the cover page editor in: // if (action == CPACTION_NEW) { // // When creating a new cover page, the default directory is either // the server cover page directory or the user cover page directory // depending on whether the user is doing server adminstration. // pDirPath = pCPInfo->pDirPath[0]; pFilename = NULL; SetEnvironmentVariable(TEXT("ClientCoverpage"),TEXT("1")); } else { INT flags; // // If the currently selected file is a link, resolve it first // lstrcpy(filename, pSelected); if (!IsEmptyString(pSelected) && (flags = GetSelectedCoverPage(pCPInfo, hwndList, NULL)) > 0 && (flags & CPFLAG_LINK) && !ResolveShortcut(pSelected, filename)) { DisplayMessageDialog(hDlg, 0, 0, IDS_RESOLVE_LINK_FAILED, pSelected); return; } // // Separate the filename into directory and filename components // if (pFilename = _tcsrchr(filename, TEXT(PATH_SEPARATOR))) { *pFilename++ = NUL; pDirPath = filename; } else { pFilename = filename; pDirPath = NULL; } } // // Find the "Cover Page Editor" executable // if ((pExecutableName = GetCoverPageEditor()) == NULL) { DisplayMessageDialog(hDlg, 0, 0, IDS_CANNOT_FIND_CPEDITOR); return; } // // Start cover page editor and wait for it to exit before proceeding // shellExeInfo.lpFile = pExecutableName; shellExeInfo.lpDirectory = pDirPath; shellExeInfo.lpParameters = MakeQuotedParameterString(pFilename); DebugPrint((TEXT("Cover page editor: %ws\n"), pExecutableName)); DebugPrint((TEXT("Initial working directory: %ws\n"), pDirPath)); DebugPrint((TEXT("Cover page filename: %ws\n"), shellExeInfo.lpParameters)); // Disable the parent window. EnableWindow( GetParent(hDlg), FALSE ); if (! ShellExecuteEx(&shellExeInfo)) { DisplayMessageDialog(hDlg, 0, 0, IDS_CANNOT_OPEN_CPEDITOR, pExecutableName); MemFree((PVOID)shellExeInfo.lpParameters); MemFree((PVOID)pExecutableName); return; } // // Refresh the list of cover page files when we're done // MemFree((PVOID)shellExeInfo.lpParameters); MemFree((PVOID)pExecutableName); while (TRUE) { // Wait for multiple objects in case of other messages coming. WaitObj = MsgWaitForMultipleObjects( 1, &shellExeInfo.hProcess, FALSE, INFINITE, QS_ALLINPUT ); if (WaitObj == WAIT_OBJECT_0) break; // PeekMessage instead of GetMessage so we drain the message queue while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE )) { TranslateMessage( &msg ); DispatchMessage( &msg ); } } EnableWindow( GetParent(hDlg), TRUE ); InitCoverPageList(pCPInfo, hDlg); SetEnvironmentVariable(TEXT("ClientCoverpage"),NULL); } VOID HandleBrowseCoverPage( HWND hDlg, PCPDATA pCPInfo, HWND hwndList, LPTSTR pSelected ) /*++ Routine Description: Remove the currently selected cover page file Arguments: hDlg - Handle to the dialog window on which the list of cover pages is displayed pCPInfo - Points to cover page information hwndList - Handle to cover page listbox window pSelected - Currently selected cover page filename Return Value: NONE --*/ { TCHAR filename[MAX_PATH]; TCHAR title[MAX_TITLE_LEN]; TCHAR filter[MAX_TITLE_LEN]; LPTSTR pExtension, pFilename; LPTSTR pCPDir; INT n; OPENFILENAME ofn = { sizeof(OPENFILENAME), hDlg, ghInstance, filter, NULL, 0, 1, filename, MAX_PATH, NULL, 0, NULL, title, OFN_FILEMUSTEXIST | OFN_NODEREFERENCELINKS | OFN_HIDEREADONLY, 0, 0, NULL, 0, NULL, NULL, }; // // Figure out what the initial directory should be // if (! IsEmptyString(pSelected)) { INT flags; // // Find out if the currently selected cover page file is a // user cover page and whether it's a link // if ((flags = GetSelectedCoverPage(pCPInfo, hwndList, NULL)) > 0 && (flags & CPFLAG_LINK) && ResolveShortcut(pSelected, filename)) { // // Set the initial directory to the link destination // _tcscpy(pSelected, filename); if (pFilename = _tcsrchr(pSelected, TEXT(PATH_SEPARATOR))) { *pFilename = NUL; ofn.lpstrInitialDir = pSelected; } } } // // Compose the file-type filter string // LoadString(ghInstance, IDS_CP_FILETYPE, title, MAX_TITLE_LEN); wsprintf(filter, TEXT("%s%c*%s%c"), title, NUL, CP_FILENAME_EXT, NUL); LoadString(ghInstance, IDS_BROWSE_COVERPAGE, title, MAX_TITLE_LEN); filename[0] = NUL; // // Present the "Open File" dialog // if (! GetOpenFileName(&ofn)) return; // // Make sure the selected filename has the correct extension // if ((pExtension = FindFilenameExtension(filename)) == NULL || _tcsicmp(pExtension, CP_FILENAME_EXT) != EQUAL_STRING) { DisplayMessageDialog(hDlg, 0, 0, IDS_BAD_CP_EXTENSION, CP_FILENAME_EXT); return; } // // Check if the selected file is already inside one of the // cover page directories // for (n=0; n < pCPInfo->nDirs; n++) { TCHAR Path[MAX_PATH]; if (_tcslen(pCPInfo->pDirPath[n]) + _tcslen(&filename[ofn.nFileOffset]) >= MAX_PATH) { DisplayMessageDialog(hDlg, 0, 0, IDS_FILENAME_TOOLONG); return; } _tcscpy(Path, pCPInfo->pDirPath[n]); _tcscat(Path, &filename[ofn.nFileOffset]); if (GetFileAttributes(Path) != 0xffffffff) { DisplayMessageDialog(hDlg, 0, 0, IDS_CP_DUPLICATE, filename); return; } } // // Add the selected cover page file to the first cover page directory // Create the cover page directory if necessary // pCPDir = pCPInfo->pDirPath[0]; if (!pCPDir || IsEmptyString(pCPDir)) { DisplayMessageDialog(hDlg, 0, 0, IDS_NO_COVERPG_DIR); return; } CreateDirectory(pCPDir, NULL); pFilename = &filename[ofn.nFileOffset]; _tcscpy(pSelected, pCPDir); n = _tcslen(pSelected); if (n + _tcslen(pFilename) >= MAX_PATH - MAX_FILENAME_EXT || pFilename >= pExtension) { DisplayMessageDialog(hDlg, 0, 0, IDS_FILENAME_TOOLONG); return; } _tcsncpy(pSelected + n, pFilename, (INT)(pExtension - pFilename)); n += (INT)(pExtension - pFilename); if (pCPInfo->serverCP) { // // Copy the physical file for server cover pages // _tcscpy(pSelected + n, CP_FILENAME_EXT); if (! CopyFile(filename, pSelected, TRUE)) { DisplayMessageDialog(hDlg, 0, 0, IDS_COPY_FILE_FAILED, filename, pSelected); return; } } else { // // Create the shortcut file for user cover page // _tcscpy(pSelected + n, LNK_FILENAME_EXT); if (GetFileAttributes(pSelected) != 0xffffffff) { DisplayMessageDialog(hDlg, 0, 0, IDS_CP_DUPLICATE, filename); return; } if (! CreateShortcut(pSelected, filename)) { DisplayMessageDialog(hDlg, 0, 0, IDS_CREATE_LINK_FAILED, pSelected, filename); return; } } // // Refresh the cover page list - we're being lazy here in that // we reset the entire list content // InitCoverPageList(pCPInfo, hDlg); } VOID HandleRemoveCoverPage( HWND hDlg, PCPDATA pCPInfo, HWND hwndList, LPTSTR pFilename ) /*++ Routine Description: Remove the currently selected cover page file Arguments: hDlg - Handle to the dialog window on which the list of cover pages is displayed pCPInfo - Points to cover page information hwndList - Handle to cover page listbox window pFilename - Currently selected cover page filename Return Value: NONE --*/ { // // Display the confirmation dialog before proceeding // if (DisplayMessageDialog(hDlg, MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2, IDS_CONFIRM_DELETE, IDS_DELETE_PROMPT, pFilename) == IDYES) { if (DeleteFile(pFilename)) { // // Update the list box if the file is successfully removed // INT selIndex, count; if ((selIndex = (INT)SendMessage(hwndList, LB_GETCURSEL, 0, 0)) != LB_ERR) { SendMessage(hwndList, LB_DELETESTRING, selIndex, 0); if ((count = (INT)SendMessage(hwndList, LB_GETCOUNT, 0, 0)) > 0) { count --; SendMessage(hwndList, LB_SETCURSEL, min(selIndex, count), 0); } } UpdateCoverPageControls(hDlg); } else DisplayMessageDialog(hDlg, 0, 0, IDS_DELETE_FAILED, pFilename); } } VOID ManageCoverPageList( HWND hDlg, PCPDATA pCPInfo, HWND hwndList, INT action ) /*++ Routine Description: Perform various action to manage the list of cover pages Arguments: hDlg - Handle to the dialog window on which the list of cover pages is displayed pCPInfo - Points to cover page information hwndList - Handle to cover page listbox window action - What action to perform on the cover page list Return Value: NONE --*/ { TCHAR filename[MAX_PATH]; // // Get the name of currently selected cover page file // if (pCPInfo == NULL || hwndList == NULL) return; GetSelectedCoverPage(pCPInfo, hwndList, filename); // // Call appropriate function depends on the action parameter // switch (action) { case CPACTION_OPEN: if (IsEmptyString(filename)) break; case CPACTION_NEW: HandleOpenCoverPage(hDlg, pCPInfo, hwndList, filename, action); break; case CPACTION_BROWSE: HandleBrowseCoverPage(hDlg, pCPInfo, hwndList, filename); break; case CPACTION_REMOVE: if (! IsEmptyString(filename)) HandleRemoveCoverPage(hDlg, pCPInfo, hwndList, filename); break; } } VOID UpdateCoverPageControls( HWND hDlg ) /*++ Routine Description: Enable/disable buttons for manage cover page files Arguments: hDlg - Handle to the property page containing the cover page controls Return Value: NONE --*/ { HWND hwndOpen, hwndRemove; // // If all buttons are disabled, leave them alone here // if (! IsWindowEnabled(GetDlgItem(hDlg, IDC_COVERPG_NEW))) return; hwndOpen = GetDlgItem(hDlg, IDC_COVERPG_OPEN); hwndRemove = GetDlgItem(hDlg, IDC_COVERPG_REMOVE); if (SendDlgItemMessage(hDlg, IDC_COVERPG_LIST, LB_GETCURSEL, 0, 0) != LB_ERR) { EnableWindow(hwndOpen, TRUE); EnableWindow(hwndRemove, TRUE); } else { if (GetFocus() == hwndOpen || GetFocus() == hwndRemove) SetFocus(GetDlgItem(hDlg, IDC_COVERPG_NEW)); EnableWindow(hwndOpen, FALSE); EnableWindow(hwndRemove, FALSE); } } VOID AddCoverPagesToList( PCPDATA pCPInfo, HWND hwndList, INT dirIndex ) /*++ Routine Description: Add the cover page files in the specified directory to a list Arguments: pCPInfo - Points to cover page information hwndList - Handle to a list window dirIndex - Cover page directory index Return Value: NONE --*/ { WIN32_FIND_DATA findData; TCHAR filename[MAX_PATH]; HANDLE hFindFile; LPTSTR pDirPath, pFilename, pExtension; INT listIndex, dirLen, fileLen, flags; // // Copy the directory path to a local buffer // flags = dirIndex; pDirPath = pCPInfo->pDirPath[dirIndex]; if (IsEmptyString(pDirPath)) return; if ((dirLen = _tcslen(pDirPath)) >= MAX_PATH - MAX_FILENAME_EXT - 1) { DebugPrint(( TEXT("Directory name too long: %ws\n"), pDirPath)); return; } _tcscpy(filename, pDirPath); // // Go through the following loop twice: // Once to add the files with .ncp extension // Again to add the files with .lnk extension // // Don't chase links for server based cover pages // do { // // Generate a specification for the files we're interested in // pFilename = &filename[dirLen]; *pFilename = TEXT('*'); _tcscpy(pFilename+1, (flags & CPFLAG_LINK) ? LNK_FILENAME_EXT : CP_FILENAME_EXT); // // Call FindFirstFile/FindNextFile to enumerate the files // matching our specification // hFindFile = FindFirstFile(filename, &findData); if (hFindFile != INVALID_HANDLE_VALUE) { do { // // Exclude directories and hidden files // if (findData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_DIRECTORY)) continue; // // Make sure we have enough room to store the full pathname // if ((fileLen = _tcslen(findData.cFileName)) <= MAX_FILENAME_EXT) continue; if (fileLen + dirLen >= MAX_PATH) { DebugPrint(( TEXT("Filename too long: %ws%ws\n"), pDirPath, findData.cFileName)); continue; } // // If we're chasing links, make sure the link refers to // a cover page file. // if (flags & CPFLAG_LINK) { _tcscpy(pFilename, findData.cFileName); if (! IsCoverPageShortcut(filename)) continue; } // // Don't display the filename extension // if (pExtension = FindFilenameExtension(findData.cFileName)) *pExtension = NUL; // // Add the cover page name to the list window // listIndex = (INT)SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM) findData.cFileName); if (listIndex != LB_ERR) SendMessage(hwndList, LB_SETITEMDATA, listIndex, flags); } while (FindNextFile(hFindFile, &findData)); FindClose(hFindFile); } flags ^= CPFLAG_LINK; } while ((flags & CPFLAG_LINK) && ! pCPInfo->serverCP); } VOID InitCoverPageList( PCPDATA pCPInfo, HWND hDlg ) /*++ Routine Description: Generate a list of available cover pages Arguments: pCPInfo - Points to cover page information hDlg - Handle to the dialog window containing cover page list Return Value: NONE --*/ { HWND hwndList; INT index, lastSel; if ((hwndList = GetDlgItem(hDlg, IDC_COVERPG_LIST)) && pCPInfo) { // // Disable redraw on the list and reset its content // if ((lastSel = (INT)SendMessage(hwndList, LB_GETCURSEL, 0, 0)) == LB_ERR) lastSel = 0; SendMessage(hwndList, WM_SETREDRAW, FALSE, 0); SendMessage(hwndList, LB_RESETCONTENT, 0, 0); // // Add cover pages to the list // for (index=0; index < pCPInfo->nDirs; index++) AddCoverPagesToList(pCPInfo, hwndList, index); // // Highlight the first cover page in the list // if ((index = (INT)SendMessage(hwndList, LB_GETCOUNT, 0, 0)) > 0 && lastSel >= index) lastSel = index - 1; SendMessage(hwndList, LB_SETCURSEL, lastSel, 0); // // Enable redraw on the list window // SendMessage(hwndList, WM_SETREDRAW, TRUE, 0); } else if (hwndList) { SendMessage(hwndList, LB_RESETCONTENT, 0, 0); } UpdateCoverPageControls(hDlg); } INT GetSelectedCoverPage( PCPDATA pCPInfo, HWND hwndList, LPTSTR pBuffer ) /*++ Routine Description: Retrieve the currently selected cover page filename Arguments: pCPInfo - Points to cover page information hwndList - Handle to the list window pBuffer - Points to a buffer for storing the selected cover page filename The size of the buffer is assumed to be MAX_PATH characters. if pBuffer is NULL, we assume the called is interested in the item flags Return Value: Flags associated with the currently selected item Negative if there is an error --*/ { LPTSTR pDirPath; INT selIndex, itemFlags; // // Default to empty string in case of an error // if (pBuffer) pBuffer[0] = NUL; if (pCPInfo == NULL || hwndList == NULL) return LB_ERR; // // Get currently selected item index // if ((selIndex = (INT)SendMessage(hwndList, LB_GETCURSEL, 0, 0)) == LB_ERR) return selIndex; // // Get the flags associated with the currently selected item // itemFlags = (INT)SendMessage(hwndList, LB_GETITEMDATA, selIndex, 0); if (itemFlags == LB_ERR || !pBuffer) return itemFlags; Assert((itemFlags & CPFLAG_DIRINDEX) < pCPInfo->nDirs); pDirPath = pCPInfo->pDirPath[itemFlags & CPFLAG_DIRINDEX]; // // Assemble the full pathname for the cover page file // directory prefix // display name // filename extension // while (*pBuffer++ = *pDirPath++) NULL; pBuffer--; SendMessage(hwndList, LB_GETTEXT, selIndex, (LPARAM) pBuffer); _tcscat(pBuffer, (itemFlags & CPFLAG_LINK) ? LNK_FILENAME_EXT : CP_FILENAME_EXT); return itemFlags; } VOID AppendPathSeparator( LPTSTR pDirPath ) /*++ Routine Description: Append a path separator (if necessary) at the end of a directory name Arguments: pDirPath - Points to a directory name Return Value: NONE --*/ { INT length; // // Calculate the length of directory string // length = _tcslen(pDirPath); if (length >= MAX_PATH-1 || length < 1) return; // // If the last character is not a path separator, // append a path separator at the end // if (pDirPath[length-1] != TEXT(PATH_SEPARATOR)) { pDirPath[length] = TEXT(PATH_SEPARATOR); pDirPath[length+1] = NUL; } } PCPDATA AllocCoverPageInfo( ) /*++ Routine Description: Allocate memory to hold cover page information Arguments: hPrinter - Handle to a printer object if serverCP is TRUE Return Value: Pointer to a CPDATA structure, NULL if there is an error NOTE: Put this inside a critical section is the caller is concerned about being thread safe. --*/ { PCPDATA pCPInfo; INT nDirs; LPTSTR pDirPath, pUserCPDir, pSavedPtr; if (pCPInfo = MemAlloc(sizeof(CPDATA))) { ZeroMemory(pCPInfo,sizeof(CPDATA)); if ( (pUserCPDir = MemAlloc(MAX_PATH)) && (pSavedPtr = pUserCPDir) && (GetClientCpDir(pUserCPDir,MAX_PATH)) ) { // // Find the directory in which the user cover pages are stored // while (*pUserCPDir && pCPInfo->nDirs < MAX_COVERPAGE_DIRS) { LPTSTR pNextDir = pUserCPDir; // // Find the next semicolon character // while (*pNextDir && *pNextDir != TEXT(';')) pNextDir++; if (*pNextDir != NUL) *pNextDir++ = NUL; // // Make sure the directory name is not too long // if (_tcslen(pUserCPDir) < MAX_PATH) { if (! (pDirPath = AllocStringZ(MAX_PATH))) break; pCPInfo->pDirPath[pCPInfo->nDirs++] = pDirPath; _tcscpy(pDirPath, pUserCPDir); } pUserCPDir = pNextDir; } MemFree(pSavedPtr); } // // Append path separators at the end if necessary // for (nDirs=0; nDirs < pCPInfo->nDirs; nDirs++) { AppendPathSeparator(pCPInfo->pDirPath[nDirs]); DebugPrint(( TEXT("Cover page directory: %ws\n"), pCPInfo->pDirPath[nDirs])); } } return pCPInfo; } VOID FreeCoverPageInfo( PCPDATA pCPInfo ) /*++ Routine Description: Free up memory used for cover page information Arguments: pCPInfo - Points to cover page information to be freed Return Value: NONE --*/ { if (pCPInfo) { INT index; for (index=0; index < pCPInfo->nDirs; index++) MemFree(pCPInfo->pDirPath[index]); MemFree(pCPInfo); } } LPTSTR MakeQuotedParameterString( LPTSTR pInputStr ) /*++ Routine Description: Make a copy of the input string and make sure it's in the same form as expected by SHELLEXECUTEINFO.lpParameters. Arguments: pInputStr - Specifies the input string Return Value: Pointer to the processed parameter string --*/ #define QUOTE TEXT('"') { LPTSTR pStr, pDestStr; INT length; // // Special case: if the input string is NULL, simply return NULL // if (pInputStr == NULL) return NULL; // // Figure out how long the resulting string is. // Initial value is 3 = two extra quotes plus NUL terminator. // for (pStr=pInputStr, length=3; *pStr; pStr++) length += (*pStr == QUOTE) ? 3 : 1; // // Copy the input string and replace quote characters // if (pStr = pDestStr = MemAlloc(length * sizeof(TCHAR))) { *pStr++ = QUOTE; while (*pInputStr) { if ((*pStr++ = *pInputStr++) == QUOTE) { *pStr++ = QUOTE; *pStr++ = QUOTE; } } *pStr++ = QUOTE; *pStr = NUL; } return pDestStr; } LPTSTR GetCoverPageEditor() { LPTSTR Location; HKEY hKey = OpenRegistryKey(HKEY_CURRENT_USER,REGKEY_FAX_SETUP,TRUE,0); if (hKey == NULL) { return NULL; } Location = GetRegistryStringExpand(hKey,REGVAL_CP_EDITOR,DEFAULT_COVERPAGE_EDITOR); RegCloseKey(hKey); return Location; }