windows-nt/Source/XPSP1/NT/enduser/stuff/hhctrl/onclick.cpp
2020-09-26 16:20:57 +08:00

2236 lines
70 KiB
C++

// Copyright (C) 1996-1997 Microsoft Corporation. All rights reserved.
#include "header.h"
#include "hhctrl.h"
#include "strtable.h"
#include "resource.h"
#include "hha_strtable.h"
#include "onclick.h"
#include "index.h"
#include "toc.h"
#include "wwheel.h"
#include "web.h"
#include <shellapi.h>
#include <wininet.h>
#include "sample.h"
#include "subset.h"
#include "secwin.h" //For extern CHHWinType** pahwnd;
#undef WINUSERAPI
#define WINUSERAPI
#include "htmlhelp.h"
#include "lasterr.h" // Support for reporting the last error.
#define NOTEXT_BTN_HEIGHT 12
#define NOTEXT_BTN_WIDTH 12
#define CXBUTTONEXTRA 8 // spacing between text and button
#define CYBUTTONEXTRA 8
static const char txtOpen[] = "open";
static const char txtCplOpen[] = "cplopen";
static const char txtRegSection[] = "Software\\Microsoft\\HtmlHelp\\";
static const char txtShortcut[] = "shortcut";
static DWORD GetTextDimensions(HWND hwnd, PCSTR psz, HFONT hfont = NULL);
HRESULT OSLangMatchesChm(CExCollection *pCollection);
HRESULT GetWordWheelHits( PSTR pszKeyword, CWordWheel* pWordWheel,
CTable* ptblURLs, CWTable* ptblTitles,
CWTable *ptblLocations, BOOL bTestMode,
BOOL bSkipCurrent, BOOL bFullURL = FALSE );
HRESULT GetWordWheelHits( CExCollection* pCollection,
CTable* ptblItems, CTable* ptblURLs,
CWTable* ptblTitles, CWTable *ptblLocations,
BOOL bKLink, BOOL bTestMode, BOOL bSkipCurrent );
HRESULT OnWordWheelLookup( CTable* ptblItems, CExCollection* pExCollection,
PCSTR pszDefaultTopic = NULL, POINT* ppt = NULL,
HWND hWndParent = NULL, BOOL bDialog = TRUE,
BOOL bKLink = TRUE,
BOOL bTestMode = FALSE, BOOL bSkipCurrent = FALSE,
BOOL bAlwaysShowList = FALSE, BOOL bAlphaSortHits = TRUE,
PCSTR pszWindow = NULL);
BOOL g_HackForBug_HtmlHelpDB_1884 = 0;
/***************************************************************************
FUNCTION: OnWordWheelLookup
PURPOSE: Given a keyword, or semi-colon delimited list of keywords,
find the hits. If no hits found display a "not found" message;
if one hit found then just jump to the topic, otherwise
display a list of topics for the user to choose from.
PARAMETERS:
pszKeywords - word(s) to lookup
... the rest the same as OnWordWheelLookup( CTable* ... )
RETURNS:
TRUE if there is at least one match. FALSE otherwise.
COMMENTS:
No support for external titles with this API.
MODIFICATION DATES:
09-Jan-1998 [paulti]
***************************************************************************/
HRESULT OnWordWheelLookup( PSTR pszKeywords, CExCollection* pExCollection,
PCSTR pszDefaultTopic, POINT* ppt,
HWND hWndParent, BOOL bDialog, BOOL bKLink,
BOOL bTestMode, BOOL bSkipCurrent,
BOOL bAlwaysShowList, BOOL bAlphaSortHits,
PCSTR pszWindow)
{
// trim leading and trailing spaces
char* pszKeywords2 = new char[strlen(pszKeywords)+1];
strcpy( pszKeywords2, pszKeywords );
SzTrimSz( pszKeywords2 );
// create our lists
CTable tblItems;
// initialize our item list
tblItems.AddString( "" ); // set item1 to NULL -- no external titles
PSTR pszKeyword = StrToken( pszKeywords2, ";" );
while( pszKeyword ) {
CHAR szKeyword[HHWW_MAX_KEYWORD_LENGTH+1];
lstrcpyn( szKeyword, pszKeyword, sizeof(szKeyword)-1 );
SzTrimSz( szKeyword );
tblItems.AddString( szKeyword );
pszKeyword = StrToken(NULL, ";");
}
delete [] pszKeywords2;
return OnWordWheelLookup( &tblItems, pExCollection,
pszDefaultTopic, ppt, hWndParent, bDialog,
bKLink, bTestMode, bSkipCurrent,
bAlwaysShowList, bAlphaSortHits, pszWindow );
}
/***************************************************************************
FUNCTION: OnWordWheelLookup
PURPOSE: Given a list of keywords find the hits. If no hits found
display a "not found" message if one hit found then just
jump to the topic, otherwise display a list of topics for
the user to choose from.
PARAMETERS:
ptblItems - word(s) to lookup (first item is semi-colon delimited
list of external titles to check).
pCollection - collection pointer, needed to access word wheels.
pszDefaultTopic - default topic to jump to.
ppt - pointer to a point to center the window on. If NULL
then we will use the current mouse pointer position.
hWndParent - window to parent the "Topics Found" dialog/menu to.
If NULL, then use the active window.
bDialog - dialog based? If not, use a menu.
bKLink - is this a keyword lookup? if not, use the ALink list.
bTestMode - test existence only (dont' show a UI).
bSkipCurrent - skip the current URL in the returned list.
bAlwaysShowList - always show the hit list even if only one topic is found.
bAlphaSortHits - alpha sort the title list or not.
RETURNS:
TRUE if there is at least one match. FALSE otherwise.
COMMENTS:
MODIFICATION DATES:
09-Jan-1998 [paulti]
***************************************************************************/
HRESULT OnWordWheelLookup( CTable* ptblItems, CExCollection* pCollection,
PCSTR pszDefaultTopic, POINT* ppt,
HWND hWndParent, BOOL bDialog, BOOL bKLink,
BOOL bTestMode, BOOL bSkipCurrent,
BOOL bAlwaysShowList, BOOL bAlphaSortHits, PCSTR pszWindow)
{
HRESULT hr = S_OK;
UINT CodePage = pCollection ? pCollection->GetMasterTitle()->GetInfo()->GetCodePage() : CP_ACP;
// create our lists
CWTable tblTitles( CodePage );
CWTable tblLocations( CodePage );
CTable tblURLs;
if( pCollection ) {
// get the active window if non specified
if( !hWndParent )
hWndParent = GetActiveWindow();
// get current mouse pointer position if non-specified
POINT pt;
if( !ppt ) {
GetCursorPos( &pt );
ppt = &pt;
#if 1 // reverted bug fix #5516
HWND hwnd = GetFocus();
if ( hwnd ) {
DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE );
if ( dwStyle & BS_NOTIFY )
{
RECT rc;
if( GetWindowRect(hwnd, &rc) ) {
pt.y = rc.top+(RECT_WIDTH(rc)/2);
pt.x = rc.left + ((RECT_HEIGHT(rc)/3)*2); // two-thirds of the height of the button
}
}
}
#endif
}
hr = GetWordWheelHits( pCollection,
ptblItems, &tblURLs, &tblTitles, &tblLocations,
bKLink, bTestMode, bSkipCurrent );
}
else {
hr = HH_E_KEYWORD_NOT_SUPPORTED;
}
// we are all done if we are just in test mode
if( bTestMode )
return hr;
int iIndex = 0;
char szURL[INTERNET_MAX_URL_LENGTH];
// if we get no topics then display the default message
// othewise an "error" message
if( FAILED(hr) || tblURLs.CountStrings() < 1) {
if( pCollection && pszDefaultTopic ) {
if( pCollection && StrRChr( pszDefaultTopic, ':' ) == NULL ) {
CStr szCurrentURL;
GetCurrentURL( &szCurrentURL, hWndParent );
CStr szFileName;
LPSTR pszColon = StrRChr( szCurrentURL.psz, ':' );
LPSTR pszSlash = StrRChr( szCurrentURL.psz, '/' );
LPSTR pszTail = max( pszColon, pszSlash );
lstrcpyn( szURL, szCurrentURL.psz, (int)(pszTail - szCurrentURL.psz +2) );
strcat( szURL, pszDefaultTopic );
}
else
strcpy( szURL, pszDefaultTopic );
hr = S_OK; // set to S_OK so the jump below will work
}
else {
int iStr = 0;
switch( hr ) {
case HH_E_KEYWORD_NOT_FOUND:
iStr = IDS_HH_E_KEYWORD_NOT_FOUND;
break;
case HH_E_KEYWORD_IS_PLACEHOLDER:
iStr = IDS_HH_E_KEYWORD_IS_PLACEHOLDER;
break;
case HH_E_KEYWORD_NOT_IN_SUBSET:
iStr = IDS_HH_E_KEYWORD_NOT_IN_SUBSET;
break;
case HH_E_KEYWORD_NOT_IN_INFOTYPE:
iStr = IDS_HH_E_KEYWORD_NOT_IN_INFOTYPE;
break;
case HH_E_KEYWORD_EXCLUDED:
iStr = IDS_HH_E_KEYWORD_EXCLUDED;
break;
case HH_E_KEYWORD_NOT_SUPPORTED:
iStr = IDS_REQUIRES_HTMLHELP;
break;
default:
iStr = IDS_IDH_MISSING_CONTEXT;
break;
}
MsgBox(iStr, MB_OK | MB_ICONWARNING | MB_SETFOREGROUND);
}
}
else {
// if only one topic then jump to it
if( !bAlwaysShowList && tblURLs.CountStrings() == 1 ) {
tblURLs.GetString( szURL, 1 );
}
else {
// we can sort the title table since it contains the index value
// of the associated URL so just make sure to always fetch the
// URL index from the selected title string and use that to get the URL
if( bAlphaSortHits ) {
tblTitles.SetSorting(GetSystemDefaultLCID());
tblTitles.SortTable(sizeof(HASH));
}
if( !bDialog && tblURLs.CountStrings() < 20 ) {
HMENU hMenu = CreatePopupMenu();
if( hMenu ) {
for( int i = 1; i <= tblURLs.CountStrings(); i++ ) {
LPSTR psz = tblTitles.GetHashStringPointer(i);
// if title too long, truncate it (511 seems like a good max)
if( psz && *psz ) {
int iLen = (int)strlen(psz);
#define MAX_LEN 511
char sz[MAX_LEN+1];
if( iLen >= MAX_LEN ) {
strncpy( sz, psz, MAX_LEN-1 );
sz[MAX_LEN] = 0;
psz = sz;
}
}
HxAppendMenu(hMenu, MF_STRING, IDM_RELATED_TOPIC + i, psz );
}
int iCmd = TrackPopupMenu(hMenu,
TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON |
TPM_NONOTIFY | TPM_RETURNCMD,
ppt->x, ppt->y, 0, hWndParent, NULL);
DestroyMenu( hMenu );
if( iCmd ) {
iIndex = tblTitles.GetInt( iCmd - IDM_RELATED_TOPIC );
tblURLs.GetString( szURL, iIndex );
}
else {
hr = HH_E_KEYWORD_NOT_FOUND; // Means we have nothing to jump to
}
}
}
else
{
HFONT hfont;
if ( pCollection )
hfont = pCollection->m_phmData->GetContentFont();
else
hfont = _Resource.GetUIFont(); // Not ideal but will have to do.
UINT CodePage = pCollection ? pCollection->GetMasterTitle()->GetInfo()->GetCodePage() : CP_ACP;
CTopicList TopicList( hWndParent, &tblTitles, hfont, &tblLocations );
if( TopicList.DoModal() ) {
iIndex = tblTitles.GetInt( TopicList.m_pos );
tblURLs.GetString( szURL, iIndex );
}
else {
hr = HH_E_KEYWORD_NOT_FOUND; // Means we have nothing to jump to
}
}
}
}
// if we found something to jump to then jump to it
if( !FAILED(hr) )
{
if (pCollection && pszWindow && pszWindow[0])
{
CStr cszPrefix = szURL;
// Is the window we are attempting to open defined by the master CHM?
CHHWinType* phh = FindWindowType(pszWindow, NULL, pCollection->GetPathName());
// Does this window actually exist?
if (phh && IsWindow(phh->hwndHelp))
{
doHHWindowJump(cszPrefix, phh->hwndHelp);
return hr;
}
cszPrefix += ">";
cszPrefix += pszWindow;
OnDisplayTopic(hWndParent, cszPrefix, 0);
}
else
doHHWindowJump( szURL, hWndParent );
}
return hr;
}
/***************************************************************************
FUNCTION: GetWordWheelHits
PURPOSE: Given a single keyword, find the hits
Return S_OK if there is at least one found hit
PARAMETERS:
pWordWheel - word wheel to look in
ptblURLS - URL list
ptblTitles - title list
bTestMode - test existence only (replaces old TestAKLink code)
bSkipCurrent - skip the current URL in the returned list
bFullURL - return a URL with a full pathname to the title or (default)
just return the URL with just the filename of the title
RETURNS:
S_OK - hits were found.
HH_E_KEYWORD_NOT_FOUND - no hits found.
HH_E_KEYWORD_IS_PLACEHOLDER - keyword is a placeholder or
a "runaway" see also.
HH_E_KEYWORD_NOT_IN_SUBSET - no hits found due to subset
exclusion.
HH_E_KEYWORD_NOT_IN_INFOTYPE - no hits found due to infotype
exclusion.
HH_E_KEYWORD_EXCLUDED - no hits found due to infotype
and subset exclusion.
HH_E_KEYWORD_NOT_SUPPORTED - keywords not supported in this mode.
COMMENTS:
HH_E_KEYWORD_EXCLUDED is returned only when no hits are found due to
*both* removal by subsetting and infotype
MODIFICATION DATES:
13-Apr-1998 [paulti]
***************************************************************************/
HRESULT GetWordWheelHits( PSTR pszKeyword, CWordWheel* pWordWheel,
CTable* ptblURLs, CWTable* ptblTitles, CWTable *ptblLocations,
BOOL bTestMode, BOOL bSkipCurrent, BOOL bFullURL )
{
HRESULT hr = S_OK;
BOOL bExcludedBySubset = FALSE;
BOOL bExcludedByInfoType = FALSE;
BOOL bPlaceHolder = FALSE;
CStructuralSubset* pSubset;
if( pszKeyword ) {
DWORD dwIndexLast = HHWW_ERROR;
DWORD dwIndexFirst = pWordWheel->GetIndex( pszKeyword, FALSE, &dwIndexLast );
if( dwIndexFirst == HHWW_ERROR )
return HH_E_KEYWORD_NOT_FOUND;
for( DWORD dwIndex = dwIndexFirst; dwIndex <= dwIndexLast; dwIndex++ ) {
// skip over the placeholders
if( pWordWheel->IsPlaceHolder( dwIndex ) ) {
bPlaceHolder = TRUE;
continue;
}
// follow the see also links
CHAR szSeeAlso[HHWW_MAX_KEYWORD_LENGTH+1];
if( pWordWheel->GetSeeAlso( dwIndex, szSeeAlso, sizeof(szSeeAlso) ) ) {
if( pWordWheel->AddRef() >= HHWW_MAX_LEVELS ) {
pWordWheel->Release();
return HH_E_KEYWORD_IS_PLACEHOLDER;
}
hr = GetWordWheelHits( szSeeAlso, pWordWheel, ptblURLs, ptblTitles, ptblLocations,
bTestMode, bSkipCurrent, bFullURL );
pWordWheel->Release();
switch( hr ) {
case HH_E_KEYWORD_EXCLUDED:
bExcludedBySubset = TRUE;
bExcludedByInfoType = TRUE;
break;
case HH_E_KEYWORD_NOT_IN_SUBSET:
bExcludedBySubset = TRUE;
break;
case HH_E_KEYWORD_NOT_IN_INFOTYPE:
bExcludedByInfoType = TRUE;
break;
case HH_E_KEYWORD_IS_PLACEHOLDER:
bPlaceHolder = TRUE;
break;
}
continue;
}
// fetch the hits
CStr cszCurrentURL;
GetCurrentURL( &cszCurrentURL );
DWORD dwHitCount = pWordWheel->GetHitCount(dwIndex);
if (dwHitCount != HHWW_ERROR) {
for (DWORD i = 0; i < dwHitCount; i++) {
CExTitle* pTitle = NULL;
DWORD dwURLId = pWordWheel->GetHit(dwIndex, i, &pTitle);
if (pTitle && dwURLId != HHWW_ERROR) {
#if 0 // we do not support infotypes currently
CSubSet* pSS;
const unsigned int *pdwITBits;
//
// Do we need to filter based on infotypes ?
//
if ( pTitle->m_pCollection && pTitle->m_pCollection->m_pSubSets &&
(pSS = pTitle->m_pCollection->m_pSubSets->GetIndexSubset()) && !pSS->m_bIsEntireCollection )
{
//
// Yep, do the filter thang.
//
pdwITBits = pTitle->GetTopicITBits(dwURLId);
if (! pTitle->m_pCollection->m_pSubSets->fIndexFilter(pdwITBits) ) {
bExcludedByInfoType = TRUE;
continue;
}
}
#endif
//
// Do we need to filter based on structural subsets?
//
if( pTitle->m_pCollection && pTitle->m_pCollection->m_pSSList &&
(pSubset = pTitle->m_pCollection->m_pSSList->GetF1()) && !pSubset->IsEntire() )
{
// Yes, filter using the current structural subset for F1.
//
if (! pSubset->IsTitleInSubset(pTitle) ) {
bExcludedBySubset = TRUE;
continue;
}
}
// if we make it this far and we are in test mode
// we can bail out and return S_OK
if( bTestMode )
return S_OK;
char szTitle[1024];
szTitle[0] = 0;
pTitle->GetTopicName( dwURLId, szTitle, sizeof(szTitle) );
if( !szTitle[0] )
strcpy( szTitle, GetStringResource( IDS_UNTITLED ) );
char szLocation[INTERNET_MAX_PATH_LENGTH]; // 2048 should be plenty
szLocation[0] = 0;
if( pTitle->GetTopicLocation(dwURLId, szLocation, INTERNET_MAX_PATH_LENGTH) != S_OK )
strcpy( szLocation, GetStringResource( IDS_UNKNOWN ) );
char szURL[INTERNET_MAX_URL_LENGTH];
szURL[0] = 0;
pTitle->GetTopicURL( dwURLId, szURL, sizeof(szURL), bFullURL );
char szFullURL[INTERNET_MAX_URL_LENGTH];
szFullURL[0] = 0;
pTitle->ConvertURL( szURL, szFullURL );
if( szURL[0] ) {
if( !ptblURLs->IsStringInTable(szURL) ) {
if( cszCurrentURL.IsEmpty()
|| !(bSkipCurrent && (lstrcmpi( cszCurrentURL, szURL ) == 0) ||(lstrcmpi( cszCurrentURL, szFullURL ) == 0 ) )) {
int iIndex = ptblURLs->AddString(szURL);
ptblTitles->AddIntAndString(iIndex, szTitle[0]?szTitle:"");
ptblLocations->AddString( *szLocation?szLocation:"" );
}
}
}
}
}
}
}
}
// determine the proper return value
if( ptblURLs->CountStrings() < 1 ) {
if( bExcludedBySubset && bExcludedByInfoType )
hr = HH_E_KEYWORD_EXCLUDED;
else if( bExcludedBySubset )
hr = HH_E_KEYWORD_NOT_IN_SUBSET;
else if( bExcludedByInfoType )
hr = HH_E_KEYWORD_NOT_IN_INFOTYPE;
else if( bPlaceHolder )
hr = HH_E_KEYWORD_IS_PLACEHOLDER;
else
hr = HH_E_KEYWORD_NOT_FOUND;
}
else
hr = S_OK;
return hr;
}
/***************************************************************************
FUNCTION: GetWordWheelHits
PURPOSE: Get all the links for the specified keywords
Return TRUE if there is at least one match
PARAMETERS:
pCollection - point to the collection
ptblItems - item table, item 1 is the list of external titles
and items 2 thru N are the list of keywords
ptblURLs - URL list
ptblTitles - title list
bKLink - is this a klink (defaults to alink)
bTestMode - test existence only (replaces old TestAKLink code)
bSkipCurrent - skip the current URL in the returned list
RETURNS:
COMMENTS:
MODIFICATION DATES:
14-Nov-1997 [paulti]
***************************************************************************/
HRESULT GetWordWheelHits( CExCollection* pCollection,
CTable* ptblItems, CTable* ptblURLs, CWTable* ptblTitles, CWTable *ptblLocations,
BOOL bKLink, BOOL bTestMode, BOOL bSkipCurrent )
{
int pos;
CWordWheel* pWordWheel = NULL;
HRESULT hr = S_OK;
HRESULT hrReturn = HH_E_KEYWORD_NOT_FOUND; // assume the worst
if( pCollection ) {
pWordWheel = (bKLink ? pCollection->m_pDatabase->GetKeywordLinks() :
pCollection->m_pDatabase->GetAssociativeLinks());
}
// if we did not get a word wheel pointer then skip the internal
// word wheels and fall back to just the external ones
if( pWordWheel ) {
// add in the internal hits first
for( pos = 2; pos <= ptblItems->CountStrings(); pos++ ) {
CStr cszKeywords(ptblItems->GetPointer(pos)); // copy so StrToken can modify
PSTR pszKeyword = StrToken(cszKeywords, ";");
while( pszKeyword ) {
hr = GetWordWheelHits( pszKeyword, pWordWheel,
ptblURLs, ptblTitles, ptblLocations,
bTestMode, bSkipCurrent, TRUE );
if( bTestMode && !FAILED(hr) )
return hr;
// if we failed again, then collate the resultant error
if( FAILED( hrReturn ) && FAILED( hr ) ) {
switch( hrReturn ) {
case HH_E_KEYWORD_NOT_IN_INFOTYPE:
if( hr == HH_E_KEYWORD_NOT_IN_SUBSET )
hrReturn = HH_E_KEYWORD_EXCLUDED;
else
hrReturn = hr;
break;
case HH_E_KEYWORD_NOT_IN_SUBSET:
if( hr == HH_E_KEYWORD_NOT_IN_INFOTYPE )
hrReturn = HH_E_KEYWORD_EXCLUDED;
else
hrReturn = hr;
break;
case HH_E_KEYWORD_EXCLUDED:
hrReturn = HH_E_KEYWORD_EXCLUDED;
break;
default:
hrReturn = hr;
break;
}
}
else if( !FAILED( hr ) )
hrReturn = hr;
pszKeyword = StrToken(NULL, ";");
if( pszKeyword )
pszKeyword = FirstNonSpace(pszKeyword);
}
}
}
// create a list of the external titles
// skip those titles that are already in the collection
CStr cszTitle(ptblItems->GetPointer(1));
if( !IsEmptyString(cszTitle) ) {
PSTR pszTitle = StrToken(cszTitle, ";");
CWTable* ptblTitleFiles = new CWTable(ptblTitles->GetCodePage()); // REVIEW: external titles must have the same codepage!
while( pszTitle ) {
if( *pszTitle ) {
TCHAR szTitle[MAX_PATH];
PSTR pszTitle2 = szTitle;
strcpy( szTitle, pszTitle );
pszTitle2 = FirstNonSpace( szTitle );
RemoveTrailingSpaces( pszTitle2 );
CExTitle* pTitle = NULL;
if( *pszTitle2 && pCollection && FAILED(pCollection->URL2ExTitle(pszTitle2,&pTitle ) ) ) {
CStr Title;
// check if the file lives where the master file lives
if( pszTitle2 ) {
char szPathName[_MAX_PATH];
char szFileName[_MAX_FNAME];
char szExtension[_MAX_EXT];
SplitPath((LPSTR)pszTitle2, NULL, NULL, szFileName, szExtension);
char szMasterPath[_MAX_PATH];
char szMasterDrive[_MAX_DRIVE];
SplitPath((LPSTR)pCollection->m_csFile, szMasterDrive, szMasterPath, NULL, NULL);
strcpy( szPathName, szMasterDrive );
CatPath( szPathName, szMasterPath );
CatPath( szPathName, szFileName );
strcat( szPathName, szExtension );
Title = szPathName;
if( (GetFileAttributes(szPathName) != HFILE_ERROR) || FindThisFile( NULL, pszTitle2, &Title, FALSE ) ) {
if( Title.IsNonEmpty() )
ptblTitleFiles->AddString( Title );
}
}
}
}
pszTitle = StrToken(NULL, ";");
}
// add in the external hits last
int iTitleCount = ptblTitleFiles->CountStrings();
for( int iTitle = 1; iTitle <= iTitleCount; iTitle++ ) {
char szTitle[MAX_PATH];
ptblTitleFiles->GetString( szTitle, iTitle );
// get title objects
CExTitle* pTitle = new CExTitle( szTitle, NULL );
CTitleDatabase* pDatabase = new CTitleDatabase( pTitle );
CWordWheel* pWordWheel = NULL;
if( bKLink )
pWordWheel = pDatabase->GetKeywordLinks();
else
pWordWheel = pDatabase->GetAssociativeLinks();
for (int pos = 2; pos <= ptblItems->CountStrings(); pos++) {
CStr cszKeywords(ptblItems->GetPointer(pos)); // copy so StrToken can modify
PSTR pszKeyword = StrToken(cszKeywords, ";");
while( pszKeyword ) {
hr = GetWordWheelHits( pszKeyword, pWordWheel,
ptblURLs, ptblTitles, ptblLocations,
bTestMode, bSkipCurrent, TRUE );
if( bTestMode && !FAILED(hr) ) {
hrReturn = hr;
break;
}
// if we failed again, then collate the resultant error
if( FAILED( hrReturn ) && FAILED( hr ) ) {
switch( hrReturn ) {
case HH_E_KEYWORD_NOT_IN_INFOTYPE:
if( hr == HH_E_KEYWORD_NOT_IN_SUBSET )
hrReturn = HH_E_KEYWORD_EXCLUDED;
else
hrReturn = hr;
break;
case HH_E_KEYWORD_NOT_IN_SUBSET:
if( hr == HH_E_KEYWORD_NOT_IN_INFOTYPE )
hrReturn = HH_E_KEYWORD_EXCLUDED;
else
hrReturn = hr;
break;
case HH_E_KEYWORD_EXCLUDED:
hrReturn = HH_E_KEYWORD_EXCLUDED;
break;
default:
hrReturn = hr;
break;
}
}
else if( !FAILED( hr ) )
hrReturn = hr;
pszKeyword = StrToken(NULL, ";");
if( pszKeyword )
pszKeyword = FirstNonSpace(pszKeyword);
}
if( bTestMode && !FAILED(hr) ) {
hrReturn = hr;
break;
}
}
// free title objects
delete pDatabase;
delete pTitle;
if( bTestMode && !FAILED(hr) ) {
hrReturn = hr;
break;
}
}
// free our title list
delete ptblTitleFiles;
}
return hrReturn;
}
/***************************************************************************
FUNCTION: CHtmlHelpControl::OnAKLink
PURPOSE: Return TRUE if there is at least one match
PARAMETERS:
fKLink - is this a klink? (defaults to alink)
bTestMode - test for existence only
RETURNS:
COMMENTS:
MODIFICATION DATES:
28-JAN-1998 [paulti] major rewrite
***************************************************************************/
BOOL CHtmlHelpControl::OnAKLink( BOOL fKLink, BOOL bTestMode )
{
if( !m_ptblItems )
return FALSE;
// get our cursor position first since the merge prompt
// may change our current position
//
// BUGBUG: what if the use tabbed to this link and then pressed ENTER?
// We should then anchor the menu to the bottom-left of the parent
// window. Right?
POINT pt;
GetCursorPos(&pt);
HWND hwnd = GetFocus();
if ( hwnd ) {
DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE );
if ( dwStyle & BS_NOTIFY )
{
RECT rc;
if( GetWindowRect(hwnd, &rc) ) {
pt.y = rc.top+((rc.bottom-rc.top)/2);
pt.x = rc.left + ((RECT_HEIGHT(rc)/3)*2); // two-thirds of the height of the button
}
}
}
// get the parent window
HWND hWndParent = NULL ;
if( m_fButton && m_hwndDisplayButton )
hWndParent = m_hwndDisplayButton;
else if( m_hwnd && IsWindow(m_hwnd) )
hWndParent = m_hwnd;
else
{
// Tunnel through IE to get the HWND of HTML Help's frame window.
hWndParent = GetHtmlHelpFrameWindow() ;
}
// If nothing else works, try the actice window. Eck!
if( !hWndParent )
hWndParent = GetActiveWindow();
// Worse, try the desktop window!
if( !hWndParent )
hWndParent = GetDesktopWindow();
//
// <mc> Find a CExCollection pointer...
//
CExCollection* pExCollection = NULL;
CStr cstr;
if ( m_pWebBrowserApp )
{
m_pWebBrowserApp->GetLocationURL(&cstr);
pExCollection = GetCurrentCollection(NULL, (PCSTR)cstr);
}
// should we always display the jump list even on one hit?
BOOL bAlwaysShowList = FALSE;
if( m_flags[0] == 1 )
bAlwaysShowList = TRUE;
// call our shared "Topics Found" handler
BOOL fPopupMenu = m_fPopupMenu;
if (fPopupMenu == TRUE && OSLangMatchesChm(pExCollection) != S_OK)
fPopupMenu = FALSE;
HRESULT hr = OnWordWheelLookup( m_ptblItems, pExCollection, m_pszDefaultTopic, &pt, hWndParent,
!fPopupMenu, fKLink, bTestMode, TRUE, bAlwaysShowList, TRUE, m_pszWindow);
if( FAILED( hr ) )
return FALSE;
return TRUE;
}
LRESULT CHtmlHelpControl::StaticTextControlSubWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hDC;
RECT rc;
CHtmlHelpControl* pThis = (CHtmlHelpControl*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
switch (msg)
{
case WM_KILLFOCUS:
case WM_SETFOCUS:
if ( pThis && pThis->m_imgType == IMG_TEXT )
{
hDC = ::GetDC(hwnd);
GetClientRect(hwnd, &rc);
::DrawFocusRect(hDC, &rc);
ReleaseDC(hwnd, hDC);
return 0;
}
break;
case WM_KEYDOWN:
if ( wParam == VK_RETURN || (wParam == VK_SPACE && (pThis->m_imgType == IMG_TEXT)) )
{
PostMessage(GetParent(hwnd), WM_COMMAND, MAKELONG(IDBTN_DISPLAY, BN_CLICKED), (LPARAM)hwnd);
return 0;
}
break;
}
if ( pThis )
return CallWindowProc(pThis->m_lpfnlStaticTextControlWndProc, hwnd, msg, wParam, lParam);
else
return 0;
}
BOOL CHtmlHelpControl::CreateOnClickButton(void)
{
// First create the window, then size it to match text and/or bitmap
PSTR pszClassName;
char szWindowText[MAX_PATH];
if ( m_imgType == IMG_BUTTON )
{
pszClassName = "button";
WideCharToMultiByte( m_CodePage, 0, m_pwszButtonText, -1, szWindowText, sizeof(szWindowText), NULL, NULL );
}
else
{
pszClassName = "static";
*szWindowText = '\0';
}
m_hwndDisplayButton = CreateWindowEx(0, pszClassName, szWindowText, WS_CHILD | m_flags[1] | WS_VISIBLE | BS_NOTIFY,
0, 0, NOTEXT_BTN_WIDTH, NOTEXT_BTN_WIDTH, m_hwnd, (HMENU) IDBTN_DISPLAY,
_Module.GetModuleInstance(), NULL);
if (!m_hwndDisplayButton)
return FALSE;
//
// <mc>
// I'm subclassing the static text controls only for the purpose of implementing proper
// focus and UI activation. I have to do this because I don't have any other way to be notified
// of loss and acquisition of focus. A much better way to do this would be to implement these
// controls as window-less.
// </mc>
//
// 4/27/98 - <mc> Changed to also subclass buttons so we can have enter key support.
//
m_lpfnlStaticTextControlWndProc = (WNDPROC)GetWindowLongPtr(m_hwndDisplayButton, GWLP_WNDPROC);
SetWindowLongPtr(m_hwndDisplayButton, GWLP_USERDATA, (LONG_PTR)this);
SetWindowLongPtr(m_hwndDisplayButton, GWLP_WNDPROC, (LONG_PTR)StaticTextControlSubWndProc);
if (m_pszBitmap) {
char szBitmap[MAX_PATH];
BOOL m_fBuiltInImage = (IsSamePrefix(m_pszBitmap, txtShortcut, -2));
if (!m_fBuiltInImage) {
if (!ConvertToCacheFile(m_pszBitmap, szBitmap)) {
AuthorMsg(IDS_CANT_OPEN, m_pszBitmap);
// REVIEW: better default?
if (m_fIcon)
goto NoImage;
m_hImage = LoadBitmap(_Module.GetResourceInstance(), txtShortcut);
goto GotImage;
}
}
if (m_fBuiltInImage)
m_hImage = LoadBitmap(_Module.GetResourceInstance(), m_pszBitmap);
else
m_hImage = LoadImage(_Module.GetResourceInstance(), szBitmap,
(m_fIcon ? IMAGE_ICON : IMAGE_BITMAP), 0, 0,
LR_LOADFROMFILE);
if (!m_hImage) {
AuthorMsg(IDS_CANT_OPEN, m_pszBitmap);
// REVIEW: we should use a default bitmap
goto NoImage;
}
GotImage:
if (m_fIcon) {
// We use IMAGE_ICON for both cursors and icons. Internally,
// the only significant difference is that a cursor could be
// forced to monochrome if we used the IMAGE_CURSOR command.
if (m_imgType == IMG_BUTTON) {
// REVIEW: should check actual ICON/CURSOR size
MoveWindow(m_hwndDisplayButton, 0, 0,
32 + CXBUTTONEXTRA,
32 + CYBUTTONEXTRA, FALSE);
SendMessage(m_hwndDisplayButton, BM_SETIMAGE,
IMAGE_ICON, (LPARAM) m_hImage);
}
else
SendMessage(m_hwndDisplayButton, STM_SETIMAGE, IMAGE_CURSOR,
(LPARAM) m_hImage);
}
else {
if (m_imgType == IMG_BUTTON) {
BITMAP bmp;
GetObject(m_hImage, sizeof(bmp), &bmp);
MoveWindow(m_hwndDisplayButton, 0, 0,
bmp.bmWidth + CXBUTTONEXTRA,
bmp.bmHeight + CYBUTTONEXTRA, FALSE);
SendMessage(m_hwndDisplayButton, BM_SETIMAGE,
IMAGE_BITMAP, (LPARAM) m_hImage);
}
}
}
else
SendMessage(m_hwndDisplayButton, WM_SETFONT,
m_hfont ? (WPARAM) m_hfont : (WPARAM) _Resource.GetUIFont(), FALSE);
NoImage:
if ( m_pwszButtonText && *m_pwszButtonText )
{
HDC hdc = GetDC(m_hwndDisplayButton);
if (hdc == NULL)
return FALSE;
HFONT hfontOld = (HFONT) SelectObject(hdc,
m_hfont ? m_hfont : _Resource.GetUIFont());
SIZE size;
IntlGetTextExtentPoint32W(hdc, m_pwszButtonText, lstrlenW(m_pwszButtonText), &size);
SelectObject(hdc, hfontOld);
ReleaseDC(m_hwndDisplayButton, hdc);
if (m_imgType == IMG_TEXT)
MoveWindow(m_hwndDisplayButton, 0, 0, size.cx, size.cy, FALSE);
else
MoveWindow(m_hwndDisplayButton, 0, 0, size.cx + CXBUTTONEXTRA,
size.cy + CYBUTTONEXTRA, FALSE);
}
GetWindowRect(m_hwndDisplayButton, &m_rcButton);
// REVIEW: will this set ALL static windows to use this cursor?
// change the cursor to the hand icon
if (m_imgType == IMG_TEXT) {
SetClassLongPtr(m_hwndDisplayButton, GCLP_HCURSOR,
(LONG_PTR) LoadCursor(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDCUR_HAND)));
}
// set the text color -- default to visited link color if not specified
if( m_imgType == IMG_TEXT ) {
if( m_clrFont == CLR_INVALID )
m_clrFont = m_clrFontLink;
}
// enable/disable dynalinks
if( (m_action == ACT_KLINK || m_action == ACT_ALINK) && m_flags[2] == 1 ) {
if( !OnAKLink((m_action == ACT_KLINK),TRUE) ) {
EnableWindow( m_hwndDisplayButton, FALSE ); // disable the window
if( m_imgType == IMG_TEXT )
m_clrFont = m_clrFontDisabled;
}
}
return TRUE;
}
/***************************************************************************
FUNCTION: GetTextDimensions
PURPOSE:
PARAMETERS:
hwnd
psz
hfont -- may be NULL
RETURNS:
COMMENTS:
If hfont is NULL, font in the window's DC will be used
MODIFICATION DATES:
01-Sep-1997 [ralphw]
***************************************************************************/
static DWORD GetTextDimensions(HWND hwnd, PCSTR psz, HFONT hfont)
{
HDC hdc = GetDC(hwnd);
if (hdc == NULL)
return 0L;
HFONT hfontOld;
if (hfont)
hfontOld = (HFONT) SelectObject(hdc, hfont);
SIZE size;
GetTextExtentPoint(hdc, psz, (int)strlen(psz), &size);
DWORD dwRet = MAKELONG(size.cx, size.cy) +
MAKELONG(CXBUTTONEXTRA, CYBUTTONEXTRA);
if (hfont)
SelectObject(hdc, hfontOld);
ReleaseDC(hwnd, hdc);
return dwRet;
}
// undocumented WinHelp API commands
#define HELP_HASH 0x095 // Jump to file and topic based on hash
#define HELP_HASH_POPUP 0x096 // Put up glossary based on hash
#define MAX_WINHELP 247
void STDCALL CHtmlHelpControl::OnClick(void)
{
switch (m_action) {
case ACT_ABOUT_BOX:
{
CAboutBox aboutbox(this);
aboutbox.DoModal();
}
break;
case ACT_HHCTRL_VERSION:
ModalDialog(TRUE);
MsgBox(IDS_VERSION);
ModalDialog(FALSE);
break;
case ACT_RELATED_TOPICS:
{
if (m_pSiteMap->Count() == 0) // don't allow zero items
break;
CExCollection* pExCollection = NULL;
CStr cstr;
if ( m_pWebBrowserApp )
{
m_pWebBrowserApp->GetLocationURL(&cstr);
pExCollection = GetCurrentCollection(NULL, (PCSTR)cstr);
}
if (m_pSiteMap->Count() == 1 && !m_flags[0])
{
SITEMAP_ENTRY *pSiteMapEntry = m_pSiteMap->GetSiteMapEntry(1);
if(pSiteMapEntry)
JumpToUrl(pSiteMapEntry, m_pSiteMap);
}
else if (m_fPopupMenu && OSLangMatchesChm(pExCollection) == S_OK)
OnRelatedMenu();
else
{
UINT CodePage = GetCodePage();
CWTable tblTitles( CodePage );
TCHAR szURL[INTERNET_MAX_URL_LENGTH];
for (int i = 0; i < m_pSiteMap->Count(); i++)
{
strcpy(szURL, m_pSiteMap->GetSiteMapEntry(i+1)->pszText );
tblTitles.AddIntAndString(i+1, szURL);
}
CTopicList TopicList(this, &tblTitles, m_hfont);
if (TopicList.DoModal() > 0) {
int iIndex = tblTitles.GetInt( TopicList.m_pos );
JumpToUrl(m_pSiteMap->GetSiteMapEntry(iIndex), m_pSiteMap);
}
}
}
break;
case ACT_WINHELP:
if (!m_ptblItems || m_ptblItems->CountStrings() < 1) {
AuthorMsg(IDS_ACT_WINHELP_NO_HELP);
break;
}
if (m_ptblItems->CountStrings() < 2) {
AuthorMsg(IDS_ACT_WINHELP_NO_ID);
break;
}
// Note that we don't track this, and therefore don't force
// the help file closed.
{
PSTR pc = m_ptblItems->GetPointer(1);
PSTR pcMax = NULL;
if ( strlen( pc ) > MAX_WINHELP )
{
pcMax = new char[MAX_WINHELP];
strncpy( pcMax, pc, MAX_WINHELP-1 );
pcMax[MAX_WINHELP-1] = 0;
}
else
pcMax = pc;
::WinHelp(m_hwnd, pcMax,
IsDigit(*(m_ptblItems->GetPointer(2))) ?
(m_fWinHelpPopup ? HELP_CONTEXTPOPUP : HELP_CONTEXT) :
(m_fWinHelpPopup ? HELP_HASH_POPUP : HELP_HASH),
IsDigit(*(m_ptblItems->GetPointer(2))) ?
Atoi(m_ptblItems->GetPointer(2)) :
WinHelpHashFromSz(m_ptblItems->GetPointer(2)));
if ( pcMax != pc )
delete [] pcMax;
}
if ( m_fWinHelpPopup )
g_HackForBug_HtmlHelpDB_1884 = 1;
break;
case ACT_SHORTCUT:
// don't allow if running in IE
if( !GetCurrentCollection(NULL, (PCSTR)NULL) ) {
HWND hWndParent;
if (!IsValidWindow(m_hwnd)) // in case we are windowless
hWndParent = FindTopLevelWindow(GetActiveWindow());
else
hWndParent = FindTopLevelWindow(GetParent(m_hwnd));
char szMsg[1024];
strcpy( szMsg, GetStringResource( IDS_REQUIRES_HTMLHELP ) );
MessageBox( hWndParent, szMsg, _Resource.MsgBoxTitle(),
MB_OK | MB_ICONWARNING | MB_SETFOREGROUND );
break;
}
if (m_ptblItems && m_ptblItems->CountStrings()) {
ShortCut(this, m_ptblItems->GetPointer(1),
(m_ptblItems->CountStrings() > 1 ?
m_ptblItems->GetPointer(2) : ""),
m_hwndParent);
}
else
AuthorMsg(IDS_SHORTCUT_ARGULESS);
break;
case ACT_HHWIN_PRINT:
case ACT_CLOSE:
case ACT_MAXIMIZE:
case ACT_MINIMIZE:
{
HWND hwndParent;
if (!IsValidWindow(m_hwnd)) // in case we are windowless
hwndParent = FindTopLevelWindow(GetActiveWindow());
else
hwndParent = FindTopLevelWindow(GetParent(m_hwnd));
switch (m_action) {
case ACT_CLOSE:
PostMessage(hwndParent, WM_CLOSE, 0, 0);
return;
case ACT_MINIMIZE:
ShowWindow(hwndParent, SW_MINIMIZE);
return;
case ACT_MAXIMIZE:
ShowWindow(hwndParent,
IsZoomed(hwndParent) ? SW_RESTORE : SW_SHOWMAXIMIZED);
return;
case ACT_HHWIN_PRINT:
{
char szClass[256];
GetClassName(hwndParent, szClass, sizeof(szClass));
if (IsSamePrefix(szClass, txtHtmlHelpWindowClass, -2)) {
PostMessage(hwndParent, WM_COMMAND, IDTB_PRINT, 0);
}
}
break;
}
}
break;
case ACT_TCARD:
{
if (IsEmptyString(m_pszActionData))
break; // REVIEW: nag the help author
WPARAM wParam = Atoi(m_pszActionData);
LPARAM lParam = 0;
PCSTR psz = StrChr(m_pszActionData, ',');
if (psz) {
psz = FirstNonSpace(psz + 1);
if (IsDigit(*psz))
lParam = Atoi(psz);
else
lParam = (LPARAM) psz;
}
HWND hwndParent;
if (!IsValidWindow(m_hwnd)) // in case we are windowless
hwndParent = FindTopLevelWindow(GetActiveWindow());
else
hwndParent = FindTopLevelWindow(GetParent(m_hwnd));
if (hwndParent)
SendMessage(hwndParent, WM_TCARD, wParam, lParam);
}
break;
case ACT_KLINK:
OnAKLink(TRUE);
break;
case ACT_ALINK:
OnAKLink(FALSE);
break;
case ACT_SAMPLE:
if(!OnCopySample())
MsgBox(IDS_SAMPLE_ERROR);
break;
default:
// REVIEW: nag the help author
break;
}
}
/***************************************************************************
FUNCTION: OSLangMatchesChm()
PURPOSE: Checks lang of os verses the lang of this title
PARAMETERS:
RETURNS: S_OK, S_FALSE
MODIFICATION DATES:
11-Nov-1998
***************************************************************************/
HRESULT OSLangMatchesChm(CExCollection *pCollection)
{
if (pCollection == NULL)
{
return S_OK;
}
CTitleInformation *pInfo = pCollection->GetMasterTitle()->GetInfo();
LANGID MasterLangId;
if (pInfo)
MasterLangId = LANGIDFROMLCID(pInfo->GetLanguage());
else
return S_FALSE;
CLanguage cLang;
if (cLang.GetUiLanguage() == MasterLangId)
return S_OK;
return S_FALSE;
}
/***************************************************************************
FUNCTION: HashFromSz
PURPOSE: Convert a string into a WinHelp hash number
PARAMETERS:
pszKey -- string to convert
RETURNS: WinHelp-compatible hash number
COMMENTS:
This is the same algorithm that WinHelp and Help Workshop uses. The
result can be used to jump to a topic in a help file.
MODIFICATION DATES:
14-Jun-1997 [ralphw]
***************************************************************************/
static const HASH MAX_CHARS = 43L;
extern "C" HASH WinHelpHashFromSz(PCSTR pszKey)
{
HASH hash = 0;
int cch = (int)strlen(pszKey);
// REVIEW: 14-Oct-1993 [ralphw] -- Note lack of check for a hash collision.
for (int ich = 0; ich < cch; ++ich) {
if (pszKey[ich] == '!')
hash = (hash * MAX_CHARS) + 11;
else if (pszKey[ich] == '.')
hash = (hash * MAX_CHARS) + 12;
else if (pszKey[ich] == '_')
hash = (hash * MAX_CHARS) + 13;
else if (pszKey[ich] == '0')
hash = (hash * MAX_CHARS) + 10;
else if (pszKey[ich] <= 'Z')
hash = (hash * MAX_CHARS) + (pszKey[ich] - '0');
else
hash = (hash * MAX_CHARS) + (pszKey[ich] - '0' - ('a' - 'A'));
}
/*
* Since the value 0 is reserved as a nil value, if any topic id
* actually hashes to this value, we just move it.
*/
return (hash == 0 ? 0 + 1 : hash);
}
VOID (WINAPI* pSHHelpShortcuts_RunDLL)(HWND hwndStub, HINSTANCE hAppInstance, LPCSTR lpszCmdLine, int nCmdShow);
static const char txtShellShortCut[] = "shell32.dll,SHHelpShortcuts_RunDLL";
static const char txtShell32Dll[] = "shell32.dll";
BOOL ShortCut(CHtmlHelpControl* phhctrl, LPCSTR pszString1, LPCSTR pszString2,
HWND hwndMsgOwner)
{
HWND hwndApp;
HINSTANCE hinstRet;
CHourGlass hourglass;
// Make a copy so we can modify it
if (IsEmptyString(pszString1))
return FALSE;
CStr csz(pszString1);
PSTR pszComma = StrChr(csz.psz, ',');
if (!pszComma) {
AuthorMsg(IDS_INVALID_SHORTCUT_ITEM1, pszString1, hwndMsgOwner, phhctrl);
return FALSE;
}
*pszComma = '\0';
RemoveTrailingSpaces(csz.psz);
PCSTR pszClass = csz.psz;
PSTR pszApplication = FirstNonSpace(pszComma + 1);
pszComma = StrChr(pszApplication, ',');
if (pszComma)
*pszComma = '\0';
RemoveTrailingSpaces(pszApplication);
PSTR pszParams = "";
if (pszComma) {
pszParams = FirstNonSpace(pszComma + 1);
RemoveTrailingSpaces(pszParams);
}
PSTR pszUrl;
UINT msg;
WPARAM wParam;
LPARAM lParam;
if (!IsEmptyString(pszString2)) {
CStr cszArg;
pszUrl = cszArg.GetArg(pszString2, TRUE);
msg = Atoi(cszArg.psz);
pszUrl = cszArg.GetArg(pszUrl, TRUE);
wParam = Atoi(cszArg.psz);
pszUrl = cszArg.GetArg(pszUrl, TRUE);
lParam = Atoi(cszArg.psz);
pszUrl = FirstNonSpace(pszUrl);
}
else {
pszUrl = (PSTR) txtZeroLength;
msg = 0;
}
CStr strUrl;
ASSERT(phhctrl->m_pWebBrowserApp != NULL);
phhctrl->m_pWebBrowserApp->GetLocationURL(&strUrl);
// check for NULL pointer
//
if(strUrl.IsEmpty())
goto Fail;
// Execute the shortcut only if we're in a local CHM file.
//
if(strstr(strUrl, "\\\\") || strstr(strUrl, "//"))
goto Fail;
if (IsEmptyString(pszClass) || !(hwndApp = FindWindow(pszClass, NULL))) {
// 27-Sep-1997 [ralphw] We special-case shell32.dll,SHHelpShortcuts_RunDLL"
// in order to run the shortcut without having to load rundll32.
if (IsSamePrefix(pszParams, txtShellShortCut)) {
if (!pSHHelpShortcuts_RunDLL) {
HINSTANCE hmod = LoadLibrary(txtShell32Dll);
if (hmod) {
(FARPROC&) pSHHelpShortcuts_RunDLL = GetProcAddress(hmod,
"SHHelpShortcuts_RunDLL");
}
}
if (pSHHelpShortcuts_RunDLL) {
pSHHelpShortcuts_RunDLL(NULL, _Module.GetModuleInstance(),
FirstNonSpace(pszParams + sizeof(txtShellShortCut)),
SW_SHOW);
return TRUE;
}
}
hinstRet = ShellExecute(hwndMsgOwner,
((strstr(pszApplication, ".cpl") || strstr(pszApplication, ".CPL")) ? txtCplOpen : txtOpen),
pszApplication, pszParams, "", SW_SHOW);
if ( hinstRet != (HANDLE)-1 && hinstRet <= (HANDLE)32) {
AuthorMsg(IDS_CANNOT_RUN, pszApplication, hwndMsgOwner, phhctrl);
Fail:
if (phhctrl->m_pszWindow) {
if (IsCompiledHtmlFile(phhctrl->m_pszWindow, NULL))
OnDisplayTopic(hwndMsgOwner, phhctrl->m_pszWindow, 0);
else {
CWStr cwJump(phhctrl->m_pszWindow);
HlinkSimpleNavigateToString(cwJump, NULL,
NULL, phhctrl->GetIUnknown(), NULL, NULL, 0, NULL);
}
}
return FALSE;
}
}
else {
if (IsIconic(hwndApp))
ShowWindow(hwndApp, SW_RESTORE); // Must restore minimized app
SetForegroundWindow(hwndApp);
}
if (msg > 0) {
int i;
if (!hwndApp) {
// Wait for up to 7 seconds for the process to initialize
for (i = 0; i < 70; i++) {
if ((hwndApp = FindWindow(pszClass, NULL)))
break;
Sleep(100);
}
}
if (!hwndApp) {
// Probably means the window class has changed.
AuthorMsg(IDS_CLASS_NOT_FOUND, pszClass, hwndMsgOwner, phhctrl);
if (phhctrl->m_pszWindow) {
if (IsCompiledHtmlFile(phhctrl->m_pszWindow, NULL))
OnDisplayTopic(hwndMsgOwner, phhctrl->m_pszWindow, 0);
else {
CWStr cwJump(phhctrl->m_pszWindow);
HlinkSimpleNavigateToString(cwJump, NULL,
NULL, phhctrl->GetIUnknown(), NULL, NULL, 0, NULL);
}
}
return FALSE;
}
SetForegroundWindow(hwndApp);
if (msg)
PostMessage(hwndApp, msg, wParam, lParam);
}
return TRUE;
}
BOOL CAboutBox::OnBeginOrEnd(void)
{
if (m_fInitializing) {
if (m_phhCtrl && m_phhCtrl->m_ptblItems && m_phhCtrl->m_ptblItems->CountStrings())
SetWindowText(m_phhCtrl->m_ptblItems->GetPointer(1));
for (int id = IDC_LINE1; id <= IDC_LINE3; id++) {
// -2 because CTable is 1-based, and we skip over the title
if (m_phhCtrl->m_ptblItems == NULL)
break;
if (id - IDC_LINE1 > m_phhCtrl->m_ptblItems->CountStrings() - 2)
break;
SetWindowText(id,
m_phhCtrl->m_ptblItems->GetPointer((id - IDC_LINE1) + 2));
}
// Hide any unused controls
while (id <= IDC_LINE3)
HideWindow(id++);
}
return TRUE;
}
void CHtmlHelpControl::OnDrawStaticText(DRAWITEMSTRUCT* pdis)
{
if (!m_pwszButtonText || !*m_pwszButtonText )
return;
// REVIEW: since we are the only ones drawing into this DC, do we really
// need to restore the previous background mode and foreground text color?
int iBack = SetBkMode(pdis->hDC, TRANSPARENT);
COLORREF clrLast = CLR_INVALID;
if (m_clrFont != CLR_INVALID)
clrLast = SetTextColor(pdis->hDC, m_clrFont);
RECT rc;
GetClientRect(pdis->hwndItem, &rc);
IntlExtTextOutW(pdis->hDC, rc.left, rc.top, ETO_RTLREADING, &rc, m_pwszButtonText, lstrlenW(m_pwszButtonText), NULL);
// DrawTextEx(pdis->hDC, (PSTR) m_pszButtonText, -1, &rc, DT_BOTTOM | DT_LEFT | DT_NOCLIP | DT_SINGLELINE | DT_NOPREFIX | DT_RTLREADING, NULL);
if ( pdis->hwndItem == ::GetFocus() )
::DrawFocusRect(pdis->hDC, &rc);
SetBkMode(pdis->hDC, iBack);
if (clrLast != CLR_INVALID)
SetTextColor(pdis->hDC, clrLast);
}
void CHtmlHelpControl::OnRelatedMenu()
{
POINT pt;
GetCursorPos(&pt);
#if 1 // reverted bug fix #5516
HWND hwnd=GetFocus();
if ( hwnd )
{
DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE );
if ( dwStyle & BS_NOTIFY )
{
RECT rc;
if( GetWindowRect(hwnd, &rc) ) {
pt.y = rc.top+((rc.bottom-rc.top)/2);
pt.x = rc.left + ((RECT_HEIGHT(rc)/3)*2); // two-thirds of the height of the button
}
}
}
#endif
HMENU hMenu = CreatePopupMenu();
if (!hMenu)
return; // BUGBUG: nag the help author
for (int i = 1; i <= m_pSiteMap->Count(); i++) {
HxAppendMenu(hMenu, MF_STRING, i,
m_pSiteMap->GetSiteMapEntry(i)->pszText);
}
int iIndex = TrackPopupMenu(hMenu,
TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD,
pt.x, pt.y, 0, (m_hwnd == NULL ? hwnd : m_hwnd), NULL);
if (iIndex)
JumpToUrl(m_pSiteMap->GetSiteMapEntry(iIndex), m_pSiteMap);
DestroyMenu(hMenu);
}
void CHtmlHelpControl::OnRelatedCommand(int idCommand)
{
if (idCommand <= IDM_RELATED_TOPIC)
return;
if (m_action != ACT_RELATED_TOPICS) {
if (!m_ptblTitles)
return;
char szURL[INTERNET_MAX_URL_LENGTH];
int iIndex = m_ptblTitles->GetInt(idCommand - IDM_RELATED_TOPIC);
m_ptblURLs->GetString( szURL, iIndex );
if (m_pszWindow) {
CStr csz(">");
csz += m_pszWindow;
OnDisplayTopic(m_hwnd, csz, (DWORD_PTR) szURL);
return;
}
CWStr cwJump(szURL);
CWStr cwFrame(m_pszFrame ? m_pszFrame : txtZeroLength);
HlinkSimpleNavigateToString(cwJump, NULL,
cwFrame, GetIUnknown(), NULL, NULL, 0, NULL);
delete m_ptblTitles;
m_ptblTitles = NULL;
delete m_ptblURLs;
m_ptblURLs = NULL;
delete m_ptblLocations;
m_ptblLocations = NULL;
return;
}
if (idCommand >= ID_VIEW_ENTRY) {
DisplayAuthorInfo(m_pSiteMap,
m_pSiteMap->GetSiteMapEntry(idCommand - ID_VIEW_ENTRY));
return;
}
#ifdef _DEBUG
int pos = (idCommand - IDM_RELATED_TOPIC);
SITEMAP_ENTRY* pSiteMapEntry = m_pSiteMap->GetSiteMapEntry(pos);
#endif
JumpToUrl(m_pSiteMap->GetSiteMapEntry(idCommand - IDM_RELATED_TOPIC),
m_pSiteMap);
}
void CHtmlHelpControl::OnKeywordSearch(int idCommand)
{
CSiteMap* pWebMap;
if (IsEmptyString(m_pszWebMap)) {
// BUGBUG: nag the author
return;
}
else { // use brace to enclose CHourGlass
// REVIEW: should we capture the mouse?
CHourGlass hourglass;
TCHAR szPath[MAX_PATH];
if (!ConvertToCacheFile(m_pszWebMap, szPath)) {
CStr cszMsg(IDS_CANT_FIND_FILE, m_pszWebMap);
MsgBox(cszMsg);
return;
}
if (m_pindex && isSameString(szPath, m_pindex->GetSiteMapFile()))
pWebMap = m_pindex;
else {
UINT CodePage = 0;
if( m_pindex && m_pindex->m_phh && m_pindex->m_phh->m_phmData ) {
CodePage = m_pindex->m_phh->m_phmData->GetInfo()->GetCodePage();
}
if (!m_pSiteMap->ReadFromFile(szPath, TRUE, this, CodePage))
return; // we assume author has already been notified
pWebMap = m_pSiteMap;
}
}
int end = m_ptblItems->CountStrings();
int endWebMap = pWebMap->CountStrings();
UINT CodePage = m_pSiteMap->GetCodePage();
CWTable tblTitles( CodePage );
for (int pos = 1; pos <= end; pos++) {
PCSTR pszKeyword = m_ptblItems->GetPointer(pos);
for (int posSite = 1; posSite <= endWebMap; posSite++) {
SITEMAP_ENTRY* pWebMapEntry = pWebMap->GetSiteMapEntry(posSite);
if (lstrcmpi(pszKeyword, pWebMapEntry->GetKeyword()) == 0) {
SITE_ENTRY_URL* pUrl = (SITE_ENTRY_URL*) pWebMapEntry->pUrls;
for (int url = 0; url < pWebMapEntry->cUrls; url++) {
tblTitles.AddString(posSite, pWebMapEntry->GetTitle(pUrl));
pUrl = pWebMap->NextUrlEntry(pUrl);
}
}
}
}
// we can sort the title table since it contains the index value
// of the associated URL so just make sure to always fetch the
// URL index from the selected title string and use that to get the URL
if( /*bAlphaSortHits*/ TRUE ) {
tblTitles.SetSorting(GetSystemDefaultLCID());
tblTitles.SortTable(sizeof(HASH));
}
CTopicList TopicList(this, &tblTitles, m_hfont);
if (TopicList.DoModal())
{
PCSTR pszTitle = tblTitles.GetHashStringPointer(TopicList.m_pos);
SITEMAP_ENTRY* pWebMapEntry = pWebMap->GetSiteMapEntry(
tblTitles.GetInt(TopicList.m_pos));
// Now find the actual URL that matches the title
// BUGBUG: fails with duplicate titles
for (int url = 0; url < pWebMapEntry->cUrls; url++) {
if (strcmp(pszTitle, pWebMap->GetUrlTitle(pWebMapEntry, url)) == 0)
break;
}
ASSERT(url < pWebMapEntry->cUrls);
JumpToUrl(pWebMapEntry, pWebMap, pWebMap->GetUrlEntry(pWebMapEntry, url));
}
}
// for bug #3681 -- don't use this new function for any other reason under any circumstances.
HWND OnDisplayTopicWithRMS(HWND hwndCaller, LPCSTR pszFile, DWORD_PTR dwData)
{
BOOL bCollection = IsCollectionFile(pszFile);
CExTitle* pTitle = NULL;
CExCollection* pCollection = NULL;
BOOL bCompiled;
if( bCollection )
bCompiled = IsCompiledURL( (PCSTR) dwData );
else
bCompiled = IsCompiledURL( pszFile );
if( bCompiled ) {
CExCollection* pCollection = GetCurrentCollection(NULL, pszFile);
if( pCollection )
if( (PCSTR) dwData )
HRESULT hr = pCollection->URL2ExTitle( (PCSTR) dwData, &pTitle );
}
if( pCollection && bCompiled && (!pTitle || FAILED( EnsureStorageAvailability( pTitle, HHRMS_TYPE_TITLE, TRUE, TRUE, FALSE )) ) ) {
g_LastError.Set(HH_E_FILENOTFOUND) ;
return NULL;
}
return OnDisplayTopic(hwndCaller, pszFile, dwData );
}
///////////////////////////////////////////////////////////
//
// OnKeywordSearch - Handles HH_KEYWORD_LOOKUP Command
//
HWND OnKeywordSearch(HWND hwndCaller, PCSTR pszFile, HH_AKLINK* pakLink, BOOL fKLink)
{
CStr cszKeywords; // Use so StrToken can modify them. Declared here so JumpNotFound can use.
CStr cszCompressed;
BOOL bCollection = IsCollectionFile(pszFile);
// We need the following in a bunch of locations in the code below. However, I have no
// idea which of the following functions may cause side affects which affect this line.
// Therefore, I can't with any assurance improve the performace of this code.
// CHHWinType* phh = FindCurProccesWindow(idProcess);
if (bCollection || IsCompiledHtmlFile(pszFile, &cszCompressed))
{
if (bCollection)
cszCompressed = pszFile;
CHmData* phmData = FindCurFileData(cszCompressed);
if (!phmData)
goto JumpNotFound;
UINT CodePage = phmData->m_pTitleCollection->GetMasterTitle()->GetInfo()->GetCodePage();
CTable tblItems;
CWTable tblTitles( CodePage );
CWTable tblLocations( CodePage );
CTable tblURLs;
if (IsEmptyString(pakLink->pszKeywords))
goto JumpNotFound;
if (pakLink->fReserved)
cszKeywords = (WCHAR*) pakLink->pszKeywords;
else
cszKeywords = pakLink->pszKeywords;
tblItems.AddString( "" ); // set item1 to NULL -- no external titles
tblItems.AddString( cszKeywords );
GetWordWheelHits( phmData->m_pTitleCollection, &tblItems,
&tblURLs, &tblTitles, &tblLocations,
fKLink, FALSE, FALSE );
if (tblURLs.CountStrings() < 1)
{
// No links found.
goto JumpNotFound ;
}
// if only one topic then jump to it
if( tblURLs.CountStrings() == 1 )
{
char szURL[INTERNET_MAX_URL_LENGTH];
tblURLs.GetString( szURL, 1 );
//TODO: This code is repeated below. Share.
if (pakLink->pszWindow)
{
strcat(szURL, ">");
strcat(szURL, (*pakLink->pszWindow == '>' ?
pakLink->pszWindow + 1 : pakLink->pszWindow));
}
else if (bCollection && phmData->GetDefaultWindow()) // Use the default window for the collection. HH 3428
{
strcat(szURL, ">");
strcat(szURL, (*phmData->GetDefaultWindow() == '>' ?
phmData->GetDefaultWindow() + 1 : phmData->GetDefaultWindow()));
}
else //REVIEW: Can we find other possible default windows?
{
// Pick a random window type. Its unlikely that this is the one you really want, since you get the first one for this process.
CHHWinType* phh = FindCurWindow();
if (phh)
{
strcat(szURL, ">");
strcat(szURL, phh->pszType);
}
}
if( bCollection )
return OnDisplayTopicWithRMS(hwndCaller, pszFile, (DWORD_PTR) szURL);
return OnDisplayTopicWithRMS(hwndCaller, szURL, 0);
}
// Determine the dialogs parent.
HWND hwndDlgParent = hwndCaller ;
CHHWinType* phh = FindCurWindow();
if (phh)
{
// If there is an existing help window, use it for the dialog parent instead of
// the hwnd passed from the caller. The reason is that the disambiguator will not be
// modal with the help window, but with the calling app.
HWND hwnd = phh->GetHwnd();
if (hwnd && ::IsValidWindow(hwnd))
{
hwndDlgParent = hwnd ;
if (hwnd != GetForegroundWindow())
{
BOOL b = SetForegroundWindow(hwnd) ;
ASSERT(b) ;
}
}
}
// we can sort the title table since it contains the index value
// of the associated URL so just make sure to always fetch the
// URL index from the selected title string and use that to get the URL
if( /*bAlphaSortHits*/ TRUE ) {
tblTitles.SetSorting(GetSystemDefaultLCID());
tblTitles.SortTable(sizeof(HASH));
}
// Display a dialog containing the links to the user.
CTopicList TopicList(hwndDlgParent, &tblTitles, phmData->GetContentFont(), &tblLocations);
if (TopicList.DoModal())
{
char szURL[INTERNET_MAX_URL_LENGTH];
int iIndex = tblTitles.GetInt(TopicList.m_pos);
tblURLs.GetString( szURL, iIndex );
//TODO: This code is repeated above. Share.
if (pakLink->pszWindow)
{
strcat(szURL, ">");
strcat(szURL, (*pakLink->pszWindow == '>' ?
pakLink->pszWindow + 1 : pakLink->pszWindow));
}
else if (bCollection && phmData->GetDefaultWindow()) // Use the default window for the collection. HH 3428
{
strcat(szURL, ">");
strcat(szURL, (*phmData->GetDefaultWindow() == '>' ?
phmData->GetDefaultWindow() + 1 : phmData->GetDefaultWindow()));
}
else
{
// Pick a random window type. Its unlikely that this is the one you really want, since you get the first one for this process.
CHHWinType* phh = FindCurWindow();
if (phh)
{
strcat(szURL, ">");
strcat(szURL, phh->pszType);
}
}
if( bCollection )
return OnDisplayTopicWithRMS(hwndCaller, pszFile, (DWORD_PTR) szURL);
return OnDisplayTopicWithRMS(hwndCaller, szURL, 0);
}
else //REVIEW: Can we find other possible default windows?
{
// Signal that we succeeded, but didn't do anything.
g_LastError.Set(S_FALSE) ;
// User canceled dialog box. Don't go to JumpNotFound.
return NULL ;
}
}
// Error handling.
JumpNotFound:
if (pakLink->fIndexOnFail) // Display index if the keyword is not found.
{
// We only seed the edit control with the characters to the ';'.
// The StrToken command happens to repalce the ';' with a \0.
// So we can use cszKeywords, directly as the first keyword.
// Also, note that cszKeywords.psz is set to NULL during initialization.
return doDisplayIndex(hwndCaller, pszFile, cszKeywords);
}
else if (pakLink->pszUrl) // Display the page pointered to by pszUrl when keyword is not found.
{
if (pakLink->pszWindow)
{
cszCompressed += ">";
cszCompressed += (*pakLink->pszWindow == '>' ?
pakLink->pszWindow + 1 : pakLink->pszWindow);
}
else
{
CHHWinType* phh = FindCurWindow();
if (phh)
{
cszCompressed += ">";
cszCompressed += phh->pszType;
}
}
return OnDisplayTopicWithRMS(hwndCaller, cszCompressed, (DWORD_PTR) pakLink->pszUrl);
}
else if (pakLink->pszMsgText) // Display a message box with the callers messages.
{
MessageBox(hwndCaller, pakLink->pszMsgText,
IsEmptyString(pakLink->pszMsgTitle) ? "" : pakLink->pszMsgTitle,
MB_OK | MB_ICONEXCLAMATION);
}
return NULL;
}
///////////////////////////////////////////////////////////
//
// OnSample - Handles HH_COPY_SAMPLE Command
//
BOOL CHtmlHelpControl::OnCopySample()
{
if (!m_ptblItems)
return FALSE;
// Get the SFL name from "item2"
//
CStr cszSFLFileName;
if (!IsEmptyString(m_ptblItems->GetPointer(2)))
cszSFLFileName = m_ptblItems->GetPointer(2);
else
return FALSE;
// Compute the SFL base name
//
char szSFLBaseName[_MAX_FNAME];
strncpy(szSFLBaseName,cszSFLFileName, _MAX_FNAME-1);
szSFLBaseName[_MAX_FNAME-1] = NULL;
cszSFLFileName = szSFLBaseName;
cszSFLFileName+=".SFL";
// Get the dialog title from "item1"
//
CStr cszDialogTitle;
if (!IsEmptyString(m_ptblItems->GetPointer(1)))
cszDialogTitle = m_ptblItems->GetPointer(1);
else
cszDialogTitle = "Sample Application";
// Get the current URL
//
CStr cszCurUrl;
if (m_pWebBrowserApp != NULL)
m_pWebBrowserApp->GetLocationURL(&cszCurUrl);
else
return FALSE;
char szSflRelativeUrl[INTERNET_MAX_URL_LENGTH];
char szSflUrl[INTERNET_MAX_URL_LENGTH];
DWORD dwLength = sizeof(szSflUrl);
// Create URL to SFL file
//
wsprintf(szSflRelativeUrl,"/samples/%s/%s",szSFLBaseName,cszSFLFileName);
strcpy(szSflUrl,cszCurUrl);
char *pszTemp = szSflUrl;
// Locate the :: in the URL
//
while(*pszTemp && !(*pszTemp == ':' && *(pszTemp+1) == ':'))
++pszTemp;
// return if no :: was found
//
if(!*pszTemp)
return FALSE;
pszTemp+=2;
// place a null after ::
//
*pszTemp = 0;
strcat(szSflUrl,szSflRelativeUrl);
// download the SFL file
//
char szPath[MAX_PATH],szSFLFilePath[MAX_PATH];
GetTempPath(sizeof(szPath),szPath);
GetTempFileName(szPath,"SFL",0, szSFLFilePath);
HRESULT hr = URLDownloadToFile(NULL, szSflUrl, szSFLFilePath, 0, NULL);
if (FAILED(hr))
DeleteFile(szSFLFilePath);
if(!FAILED(hr))
{
// Process compressed sample
//
// Create URL to SFL file
//
// Null after the :: again in szSflUrl
//
*pszTemp = 0;
char szSampleBaseUrl[INTERNET_MAX_URL_LENGTH];
dwLength = sizeof(szSampleBaseUrl);
wsprintf(szSampleBaseUrl,"%s/samples/%s/",szSflUrl,szSFLBaseName);
// call sample UI
//
return ProcessSample(szSFLFilePath, szSampleBaseUrl, cszDialogTitle, this, TRUE);
// delete the copy of the SFL file
DeleteFile(szSFLFilePath);
}
else
{
CStr cstr;
PSTR psz = NULL;
if ( m_pWebBrowserApp )
{
m_pWebBrowserApp->GetLocationURL(&cstr);
psz = (PSTR)cstr;
}
// Process uncompressed sample
CExCollection *pCurCollection = GetCurrentCollection(NULL, (LPCSTR)psz);
if(!pCurCollection)
return FALSE;
CExTitle *pExTitle = NULL;
// Get the CExTitle object associated to this URL
//
pCurCollection->URL2ExTitle(cszCurUrl, &pExTitle);
if(!pExTitle)
return FALSE;
// Make sure the title is open
// BUGBUG: what do we do if this fails?
pExTitle->Init();
TCHAR *pszSampleLocation = NULL;
// Make sure the CTitle object is initialized
//
if(pExTitle->m_pTitle)
{
// Get the sample location from the CTitle object
//
if (pExTitle->GetUsedLocation())
pszSampleLocation = pExTitle->GetUsedLocation()->SampleLocation;
}
if(!pszSampleLocation)
{
// get sample location from CCollection if get from CTitle failed
pszSampleLocation = pCurCollection->m_Collection.GetSampleLocation();
}
// Make sure we got a sample location
//
if(!pszSampleLocation)
return FALSE;
CLocation *pLocation = pCurCollection->m_Collection.FindLocation(pszSampleLocation);
if(!pLocation)
return FALSE;
char szSampleBasePath[MAX_PATH],szSFLFilePath[MAX_PATH];
// construct full path to uncompressed sample directory
//
strcpy(szSampleBasePath,pLocation->GetPath());
// CatPath(szSampleBasePath,szSFLBaseName);
// strcat(szSampleBasePath,"\\");
// construct full path to SFL
//
strcpy(szSFLFilePath,szSampleBasePath);
CatPath(szSFLFilePath,"\\SFL\\");
CatPath(szSFLFilePath,cszSFLFileName);
// make sure the sample (CD) is available
if( pExTitle ) {
pExTitle->SetCurrentAttachmentName( szSFLFilePath );
if( FAILED(hr = EnsureStorageAvailability( pExTitle, HHRMS_TYPE_ATTACHMENT ) ) ) {
if( hr == HHRMS_E_SKIP || hr == HHRMS_E_SKIP_ALWAYS )
return TRUE;
return FALSE;
}
}
// if the user updated the CD location then we need to update the SFL file location
// before we process the sample
if( hr == HHRMS_S_LOCATION_UPDATE ) {
strcpy(szSampleBasePath,pLocation->GetPath());
strcpy(szSFLFilePath,szSampleBasePath);
CatPath(szSFLFilePath,"\\SFL\\");
CatPath(szSFLFilePath,cszSFLFileName);
}
// call sample UI
//
return ProcessSample(szSFLFilePath, szSampleBasePath, cszDialogTitle, this, FALSE);
}
return TRUE;
}