// 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;
}