/************************************************************************
*																		*
*  HDLGSRCH.CPP 														*
*																		*
*  Copyright (C) Microsoft Corporation 1993-1994						*
*  All Rights reserved. 												*
*																		*
************************************************************************/

extern "C" {	// Assume C declarations for C++
#include "help.h"
}
#include "inc\whclass.h"
#pragma hdrstop

#include "inc\idxchoos.h"
#include "resource.h"

#include <prsht.h>	 // Include property sheet stuff for wizards

#ifndef MAX_PAGES	 // This should have been defined in prsht.h  but was not (yet)
#define MAX_PAGES 24
#endif // MAX_PAGES

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define NOTOOLBAR
#define NOSTATUSBAR
#define NOTRACKBAR
#define NOPROGRESS

extern "C" {	// Assume C declarations for C++
#include <commctrl.h>

#include <ctype.h>
#include <io.h>
#include <direct.h>
#include <stdio.h>

#include "inc\helpids.h"
}

#include "inc\table.h"
#include "inc\hwproc.h"
#include "inc\hdlgsrch.h"
#include "inc\idxchoos.h"

#define GETIMAGE_TYPE(c) (c & 0x0f)
#define GETLEVEL(c) 	 ((UINT) (c >> 4))
const int LEVEL_MASK = 0x00f0;
const int IMAGE_MASK = 0x000f;

#ifndef STM_SETIMAGE
#define STM_SETIMAGE		0x0172
#define IMAGE_BITMAP		0
#endif

// This is for context-sensitive help

#ifndef NO_PRAGMAS
#pragma data_seg(".text", "CODE")
#endif
static DWORD aKeywordIds[] = {
	IDC_TEXT,				IDH_HELPFINDER_INDEX,
	DLGEDIT,				IDH_HELPFINDER_INDEX,
	DLGVLISTBOX,			IDH_HELPFINDER_INDEX,
	ID_TREEVIEW,			IDH_HELPFINDER_CONTENTS,
	IDC_CNT_INSTRUCTIONS,	IDH_HELPFINDER_CONTENTS,
	IDC_PRINT,				IDH_HELPFINDER_PRINT,
	IDOK,					IDH_HELPFINDER_DISPLAY,

	0, 0
};
#ifndef NO_PRAGMAS
#pragma data_seg()
#endif

const int CWIDTH_IMAGE_LIST = 20;
typedef HANDLE HSEARCHER;

typedef void  (__stdcall *ANIMATOR)(void);

#ifndef _HILIGHT // these are also in hilite.h
typedef INT    ERRORCODE;
typedef HANDLE HHILITER;
#endif

typedef HSEARCHER (WINAPI* NEWSEARCHER)(void);
typedef ERRORCODE (WINAPI* DELETESEARCHER)(HSEARCHER);
typedef ERRORCODE (WINAPI* OPENINDEX)(HSEARCHER, PCSTR, PSTR, PDWORD, PDWORD, PDWORD);
typedef ERRORCODE (WINAPI* SAVEGROUP)(HSEARCHER, PSTR);
typedef ERRORCODE (WINAPI* LOADGROUP)(HSEARCHER, PSTR);
typedef ERRORCODE (WINAPI* ISVALIDINDEX)(PCSTR, UINT);
typedef ERRORCODE (WINAPI* DISCARDINDEX)(HSEARCHER, int);
typedef void	  (WINAPI* SETDIRECTORYLOCATOR)(HWND hwndLocator);
typedef ERRORCODE (WINAPI* REGISTERANIMATOR)(ANIMATOR pAnimator, HWND hwnd);

typedef HHILITER  (WINAPI* NEWHILITER)(HSEARCHER); // Hiliter interfaces
typedef ERRORCODE (WINAPI* DELETEHILITER)(HHILITER);

HSEARCHER (WINAPI *pNewSearcher)(void);
ERRORCODE (WINAPI *pDeleteSearcher)(HSEARCHER);
ERRORCODE (WINAPI *pOpenIndex)(HSEARCHER, PCSTR, PSTR, PDWORD, PDWORD, PDWORD);
HWND	  (WINAPI *pOpenTabDialog)(HWND, DWORD, DWORD);
ERRORCODE (WINAPI *pSaveGroup)(HSEARCHER, PSTR);
ERRORCODE (WINAPI *pLoadGroup)(HSEARCHER, PSTR);
ERRORCODE (WINAPI *pIsValidIndex)(PCSTR, UINT);
ERRORCODE (WINAPI *pDiscardIndex)(HSEARCHER, int);
void	  (WINAPI *pSetDirectoryLocator)(HWND hwndLocator);
extern "C" {
ERRORCODE (APIENTRY *pRegAnimate)(ANIMATOR pAnimator, HWND hwnd);
}

#ifdef _HILIGHT
static void STDCALL RemoveHiliter(HHILITER* phhiliter);

NEWHILITER		 pNewHiliter;
DELETEHILITER	 pDeleteHiliter;
SCANDISPLAYTEXT  pScanDisplayText;
CLEARDISPLAYTEXT pClearDisplayText;
COUNTHILITES	 pCountHilites;
QUERYHILITES	 pQueryHilites;

static HHILITER  hhiliter;

#else
#define RemoveHiliter(hhiliter)
#endif

static BOOL STDCALL AskAboutIndexes(HWND);
static RC	STDCALL FindKeywordMacro(PCSTR pszKeyword, HDE hde);
static void STDCALL OnFtsMove(HWND hwndDlg, BOOL fAdd);
static void STDCALL PageChange(HWND hwndTab);
static void STDCALL SetCntTabText(HWND hwndDlg, BOOL fOpen);
static void STDCALL AddTab(TC_ITEM* pti, TAB_ID tab, PSTR pszName, HWND hwndTabs);

static HSEARCHER hsrch;
static HWND hwndFindTab;
static TAB_ID aTabIds[MAX_IDTABS];

// BUGBUG fFTSJump should be in global.c

BOOL  fFTSJump;

// Extensible Tab support

const int MAX_TABS = 7; // maximum help-authored tabs including FTS

// All extensable tabs must support this function

typedef HWND (WINAPI* OPENTABDIALOG)(HWND, DWORD, DWORD);
#ifndef NO_PRAGMAS
#pragma data_seg(".text", "CODE")
#endif
static const char txtOpenTabDialog[] = "OpenTabDialog";
#ifndef NO_PRAGMAS
#pragma data_seg()
#endif

typedef struct {
	PSTR pszName;
	OPENTABDIALOG pOpenTabDialog;
} TAB_EXTENSION;

TAB_EXTENSION aTabs[MAX_TABS + 1];

typedef struct {
	DWORD	dwPagesHit;
	HBITMAP hWizBMP;
	FTS_FLAGS flgs;
	BOOL bExpress;
} WIZDATA,FAR *LPWIZDATA;

typedef struct {
	LPWIZDATA wd;
	int nID;
} WIZSHEET, FAR *LPWIZSHEET;

/*****************************************************************************
*																			 *
*							 Static Variables								 *
*																			 *
*****************************************************************************/

char szSavedKeyword[MAXKEYLEN];
char szSavedContext[MAXKEYLEN];

// in honor of Pete, who will reverse-engineer the .GID file format
#ifndef NO_PRAGMAS
#pragma data_seg(".text", "CODE")
#endif
const char txtFileInfo[] = "|Pete";
#ifndef NO_PRAGMAS
#pragma data_seg()
#endif

static BOOL fDirtyInfo;   // TRUE to write pFileInfo
static BOOL fReadOnlyGid; // TRUE if we can't write to the .GID file

CSearch* pSrchClass;

extern "C" {

HIMAGELIST (WINAPI *pImageList_LoadImage)(HINSTANCE, PCSTR, int, int, COLORREF, UINT, UINT);
HIMAGELIST (WINAPI *pImgLst_Destroy)(HIMAGELIST);
void (WINAPI *pInitCommonControls)(void);
HPROPSHEETPAGE (WINAPI *pCreatePropertySheetPage)(LPCPROPSHEETPAGE);
int (WINAPI *pPropertySheet)(LPCPROPSHEETHEADER);

DLGRET FtsWizardProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);

extern BOOL fMacroFlag;

}

static HIMAGELIST hil;
static const int NOMATCH = -1;
static BOOL fCommControlInitialized;
int cntFirstVisible;
int cntCurHighlight;
HTREEITEM hitemCurHighlight;
HBT hbtTabDialogs;

#ifdef SPECIFICATION

*****************************************************************************

When we are first started, and we're handed a help file, we see if we can
find a contents file. If so, we fill in the following:

	pTblFiles	 -- if index files are specified in the contents file, then
		this table will contain double entries. The first entry is the name
		to display on the tab control, and the second entry is the fully
		qualified file to use. The filename portion contains a leading 7
		characters -- the first character is the file type consisting of:
		a binary flag (bit 1 is always set to get a non-zero value)

			CHFLAG_MISSING		file specified, but not found
			CHFLAG_LINK 		file for alink/klink, but not combined index or FTS
			CHFLAG_FTS_AVAIL	FTS index available
			CHFLAG_FTS_ASKED	FTS index NOT available and user doesn't want it

		The next 6 characters are a hex representation of the time-date stamp

	pbTree -- contains a far pointer to an array of bytes specifying
			whether an image is a container or a topic, what its level is,
			and if it is a container, whether it is opened or not.

	cntFlags -- Contains information about the number of Contents Tab entries,
			first visible item in the Contents Tab, position of all windows,
			flag indicating if Contents or Index tab had the focus, etc.

We fill in the pszGidFile with the path of the current .GID file (if any).

We fill in pszHelpTitle if specified in the .GID file -- this is used
for the help title for any file using that Contents file.

We fill in pszHelpBase if specified in the .GID file -- this will be
used to force an interfile jump for any topic that does not explicitly
specify one.

*****************************************************************************

#endif // SPECIFICATION

PSTR pszGidFile;
PSTR pszHelpTitle;

PSTR pszHelpBase;
PBYTE pbTree;
GID_FILE_INFO* pFileInfo;

#define GetFileIndex(x) (pFileInfo[(x)].index)
#define SetFileIndex(pos, x) (pFileInfo[(pos)].index = (x))

CTable* pTblFiles;		// files considered part of contents file

/*****************************************************************************
*																			 *
*								Prototypes									 *
*																			 *
*****************************************************************************/

INLINE static BOOL STDCALL InitContents(HWND hwndDlg);

LRESULT EXPORT MessageWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
INLINE static HTREEITEM STDCALL Tree_AddItem(HWND hwndTree, HTREEITEM htiParent, int iImage, UINT cChildren, LPARAM lParam, TV_INSERTSTRUCT* ptcInsert);
static LRESULT STDCALL Tree_SetImage(HWND hwndTree, int iImage, HTREEITEM hItem);
HWND STDCALL CreateTabChild(TAB_ID idCurTab, HWND hwndDlg);
static void STDCALL ContentsCmdLine(PSTR pszLine);
INLINE void STDCALL ParseContentsString(PSTR pszString);
INLINE void STDCALL CollapseChildren(HWND hwndTree, int pos);
static void STDCALL FreeLocalStrings(void);
static int STDCALL	OpenGidFile(PSTR pszGidFile, FM fmHelpFile);
static void STDCALL PrintContents(HWND hwndDlg);
static void STDCALL CheckDialogSize(HWND hwndParentDlg, HWND hwndTabDlg);
static void STDCALL ClosePropertySheet(HWND hwndPropDlg, int result);
static PCSTR FASTCALL FtsIndexToFile(int index);
static PCSTR FASTCALL FtsIndexToTitle(int index);

DLGRET ContentsDlg(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
DLGRET TabDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);

/***************************************************************************

	FUNCTION:	ContentsDlg

	PURPOSE:	Dialog box procedure for Contents Tab control

	PARAMETERS:
		hwndDlg
		msg
		wParam
		lParam

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		17-Aug-1993 [ralphw]

***************************************************************************/

BOOL fHack;

DLGRET ContentsDlg(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	HWND hwndTree;

	switch(msg) {
		case WM_INITDIALOG:
			{
				CWaitCursor cursor;
				ChangeDlgFont(hwndDlg);
				ASSERT(hfontDefault);
				SendMessage(GetDlgItem(hwndDlg, ID_TREEVIEW), WM_SETFONT,
					(WPARAM) hfontDefault, FALSE);

				if (!InitContents(hwndDlg))
					return FALSE;
				EnableWindow(GetDlgItem(GetParent(hwndDlg), IDC_PRINT), TRUE);
				EnableWindow(GetDlgItem(GetParent(hwndDlg), IDOK), TRUE);
				SetCntTabText(hwndDlg, TRUE);
			}
			return TRUE;


		case WM_COMMAND:
			switch(wParam) {
				case IDOK:
					{

						hwndTree = GetDlgItem(hwndDlg, ID_TREEVIEW);
						TV_ITEM tvi;

						tvi.hItem = TreeView_GetSelection(hwndTree);
						if (!tvi.hItem)
							break;		// probably ENTER with no selection
						tvi.mask = TVIF_PARAM;
						TreeView_GetItem(hwndTree, &tvi);

						/*
						 * We might have gotten here from ENTER or
						 * DBL_CLICK, so we must be sure we actually have a
						 * topic selected.
						 */

						/*
						 * 21-Jan-1994	[ralphw] Hack time. This code
						 * should work the same whether the user
						 * double-clicked, or whether they single clicked
						 * and pressed the Display button. Both cases should
						 * just send a WM_COMMAND message with IDOK. But
						 * they don't, hence the fHack flag to do the right
						 * (but weird) thing to get the current book to
						 * expand or contract.
						 */

						if (GETIMAGE_TYPE(pbTree[(UINT) tvi.lParam]) !=
								IMAGE_TOPIC) {
							IMAGE_TYPE curType = (IMAGE_TYPE) GETIMAGE_TYPE(pbTree[tvi.lParam]);
#ifdef _DEBUG
							UINT	   curLevel = GETLEVEL(pbTree[tvi.lParam]);
#endif

							if (curType ==
									(fHack ? IMAGE_CLOSED_FOLDER : IMAGE_CLOSED_FOLDER)) {
								pbTree[(UINT) tvi.lParam] =
									IMAGE_OPEN_FOLDER |
									(pbTree[(UINT) tvi.lParam] & LEVEL_MASK);
								TreeView_Expand(hwndTree, tvi.hItem,
									(fHack ? TVE_COLLAPSE : TVE_EXPAND));
								if (!fHack && curType != IMAGE_CLOSED_FOLDER)
									CollapseChildren(hwndTree, tvi.lParam);
							}
							else {
								pbTree[(UINT) tvi.lParam] =
									IMAGE_CLOSED_FOLDER |
									(pbTree[(UINT) tvi.lParam] & LEVEL_MASK);
								TreeView_Expand(hwndTree, tvi.hItem,
									(fHack ? TVE_EXPAND : TVE_COLLAPSE));
								if (!fHack || curType == IMAGE_OPEN_FOLDER)
									CollapseChildren(hwndTree, tvi.lParam);
							}

							Tree_SetImage(GetDlgItem(hwndDlg, ID_TREEVIEW),
								GETIMAGE_TYPE(pbTree[(UINT) tvi.lParam]),
								tvi.hItem);

							SetWindowText(GetDlgItem(GetParent(hwndDlg), IDOK),
								GetStringResource(
									GETIMAGE_TYPE(pbTree[(UINT) tvi.lParam]) ==
									IMAGE_CLOSED_FOLDER ?
									sidOpenButton : sidCloseButton));
							SetCntTabText(hwndDlg,
								(GETIMAGE_TYPE(pbTree[(UINT) tvi.lParam]) ==
								IMAGE_CLOSED_FOLDER));
							SetFocus(hwndTree);
							break;
						}
						HBT hbtCntJump = HbtOpenBtreeSz(txtCntJump,
							hfsGid, fFSOpenReadOnly);
						if (!hbtCntJump)

							// REVIEW: what should we really do?

							return FALSE;

						/*
						 * REVIEW: It's theoretically possible for
						 * RcLookupByKey to fail, so we'll need to add some
						 * useful error handling here. This could happen
						 * with the .GID file on the net and the network
						 * goes down, or the .GID file could be corrupted.
						 */

						RcLookupByKey(hbtCntJump,
							(KEY) (LPVOID) &tvi.lParam,
							NULL, szSavedContext);

						RcCloseBtreeHbt(hbtCntJump);

						/*
						 * Call ParseContentsString to add base help
						 * filename and window, as necessary.
						 */

						ParseContentsString(szSavedContext);
						PostMessage(GetParent(hwndDlg), WM_COMMAND,
							ID_JMP_CONTEXT, 0);
					}
					break;
			}
			break;

		case WM_HELP:
			OnF1Help(lParam, aKeywordIds);
			return TRUE;

		case WM_CONTEXTMENU:
			OnContextMenu(wParam, aKeywordIds);
			return TRUE;

		case WM_NOTIFY:
			{
#ifdef _DEBUG
				NM_TREEVIEW* pnmhdr = (NM_TREEVIEW*)lParam;
#else
				#define pnmhdr ((NM_TREEVIEW*)lParam)
#endif

				switch(pnmhdr->hdr.code) {
					case TVN_GETDISPINFO:

						#define pdi ((TV_DISPINFO FAR *)lParam)

						if (pdi->item.mask & TVIF_TEXT) {
                    		char szBuf[260];

							/*
							 * REVIEW: Should we do something about a failure
							 * here? I.e., what happens if the .GID file gets
							 * corrupted, and RcLookupByKey fails?
							 */

                           // NTBUG 51883, tooltips are 80 chars, must truncate
						   RcLookupByKey(pSrchClass->hbtCntText,
							    (KEY) (LPVOID) &pdi->item.lParam,
							    NULL, szBuf);
                           lstrcpyn(pdi->item.pszText,szBuf,pdi->item.cchTextMax);
						}
						break;

					case NM_RETURN:
					case NM_DBLCLK:
						fHack = TRUE;
						SendMessage(hwndDlg, WM_COMMAND, IDOK, 0);
						break;

				  case TVN_SELCHANGING:
						if (GETIMAGE_TYPE(
								pbTree[(UINT) pnmhdr->itemNew.lParam]) ==
								IMAGE_TOPIC) {
							SetWindowText(GetDlgItem(GetParent(hwndDlg), IDOK),
								GetStringResource(sidDisplay));
							SetCntTabText(hwndDlg, FALSE);
						}
						else {
							SetCntTabText(hwndDlg, TRUE);
							if (GETIMAGE_TYPE(
									pbTree[(UINT) pnmhdr->itemNew.lParam]) ==
									IMAGE_CLOSED_FOLDER)
								SetWindowText(GetDlgItem(GetParent(hwndDlg), IDOK),
									GetStringResource(sidOpenButton));
							else
								SetWindowText(GetDlgItem(GetParent(hwndDlg), IDOK),
									GetStringResource(sidCloseButton));
						}
						hitemCurHighlight = pnmhdr->itemNew.hItem;
						break;

				  case TVN_ITEMEXPANDING:
						if (fHack) {
							fHack = FALSE;
							break;
						}

						if (pnmhdr->action & TVE_EXPAND) {
							pbTree[pnmhdr->itemNew.lParam] =
								IMAGE_OPEN_FOLDER |
								(pbTree[pnmhdr->itemNew.lParam] & LEVEL_MASK);
						}
						else {
							ASSERT(pnmhdr->action & TVE_COLLAPSE);

							pbTree[pnmhdr->itemNew.lParam] =
								IMAGE_CLOSED_FOLDER |
								(pbTree[pnmhdr->itemNew.lParam] & LEVEL_MASK);
						}

						// Set the correct image

						Tree_SetImage(GetDlgItem(hwndDlg, ID_TREEVIEW),
							GETIMAGE_TYPE(pbTree[pnmhdr->itemNew.lParam]),
							pnmhdr->itemNew.hItem);

						break;
			  }
			  break;
			}

		case WM_DESTROY:
			{
				/*
				 * Save our current position. This is a really ugly hack
				 * because the treeview control only understands handles,
				 * not position, and since we are destroying the treeview
				 * control, the handle will change the next time we come
				 * back up. So we have to save an array of handles when we
				 * create the treeview control, and here walk through the
				 * array to find the position of the treeview item. The next
				 * time we come back up, we again create an array of
				 * handles, and then use this saved position to index into
				 * that array of handles to find the handle to set as the
				 * first visible item.
				 */

				hwndTree = GetDlgItem(hwndDlg, ID_TREEVIEW);
				HTREEITEM hItemFirstVisible =
					TreeView_GetFirstVisible(hwndTree);
				HTREEITEM* phTreeItem =
					(HTREEITEM*) PtrFromGh(pSrchClass->hTreeItem);
				int oldSaved = cntFirstVisible;
				for (cntFirstVisible = 1;
						cntFirstVisible < cntFlags.cCntItems;
						cntFirstVisible++) {
					if (hItemFirstVisible == phTreeItem[cntFirstVisible])
						break;
				}
				if (cntFirstVisible >= cntFlags.cCntItems)
					cntFirstVisible = 0;
				if (hitemCurHighlight) {
					for (cntCurHighlight = 1;
							cntCurHighlight < cntFlags.cCntItems;
							cntCurHighlight++) {
						if (hitemCurHighlight == phTreeItem[cntCurHighlight])
							break;
					}
					if (cntCurHighlight >= cntFlags.cCntItems)
						cntCurHighlight = 0;
				}
				else
					cntCurHighlight = 0;
			}

			break;
  }
  return FALSE;
}

/***************************************************************************

	FUNCTION:	InitContents

	PURPOSE:	Initialize the Contents Tab control

	PARAMETERS:
		hwndDlg

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		14-Jul-1993 [ralphw]

***************************************************************************/

INLINE static BOOL STDCALL InitContents(HWND hwndDlg)
{
	ASSERT(hfsGid);
	if (!hfsGid)
		return FALSE;

	if (pSrchClass->hTreeItem)
		FreeGh(pSrchClass->hTreeItem);

	// BUGBUG: Need to complain if we can't find the comctl32.dll and then
	// do something intelligent. The only thing we could display is the
	// search dialog.

	if (!LoadShellApi())
		return FALSE;

	pSrchClass->hbtCntText = HbtOpenBtreeSz(txtCntText, hfsGid,
		fFSOpenReadOnly);
	if (!pSrchClass->hbtCntText)
		return FALSE; // REVIEW: what should we really do?

	HTREEITEM ahtiParents[MAX_LEVELS + 1];

	HWND hwndTreeView = GetDlgItem(hwndDlg, ID_TREEVIEW);
	HTREEITEM hti = NULL;
	HTREEITEM htiParent = TVI_ROOT;
	int curLevel = 1;
	ahtiParents[0] = TVI_ROOT;

	// REVIEW: This may be unnecessary once TreeView is supported by AppStudio

	SetWindowLong(hwndTreeView, GWL_EXSTYLE,
		GetWindowLong(hwndTreeView, GWL_EXSTYLE) | WS_EX_CLIENTEDGE);

	SetWindowPos(hwndTreeView, NULL, 0, 0, 1, 1,
		SWP_DRAWFRAME | SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);

	// Load our image bitmap

	if (!hil)
		hil = pImageList_LoadImage(hInsNow,
			MAKEINTRESOURCE(ID_VIEW_BITMAPS),
			CWIDTH_IMAGE_LIST, 0, 0x00FFFFFF, IMAGE_BITMAP, 0);

	TreeView_SetImageList(hwndTreeView, hil, TVSIL_NORMAL);

	// REVIEW: enabable once tree-view works

	SendMessage(hwndTreeView, WM_SETREDRAW, FALSE, 0);

	pSrchClass->hTreeItem =
		GhAlloc(GMEM_FIXED, cntFlags.cCntItems * sizeof(HTREEITEM));
	HTREEITEM* phTreeItem = (HTREEITEM*) PtrFromGh(pSrchClass->hTreeItem);

	TV_INSERTSTRUCT tcAdd;
	tcAdd.hInsertAfter = TVI_LAST;
	tcAdd.item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT | TVIF_CHILDREN | TVIF_PARAM;
	tcAdd.item.hItem = NULL;
	tcAdd.item.pszText = LPSTR_TEXTCALLBACK;

	int pos;
	for (pos = 1; pos < cntFlags.cCntItems; pos++) {
		if (GETIMAGE_TYPE(pbTree[pos]) == IMAGE_TOPIC) {
			if (GETLEVEL(pbTree[pos]) > 0 && GETLEVEL(pbTree[pos]) < (UINT) curLevel)
				htiParent = ahtiParents[curLevel = GETLEVEL(pbTree[pos])];

			// Add the topic to the treeview control

			HTREEITEM hItem = Tree_AddItem(hwndTreeView,
				htiParent,		// parent
				IMAGE_TOPIC,	// image index
				0,				// has kids
				(LPARAM) pos,	// extra data
				&tcAdd);
			phTreeItem[pos] = hItem;
		}
		else {

			// *** FOLDER LINE ***

			int this_level = GETLEVEL(pbTree[pos]);

			htiParent = Tree_AddItem(hwndTreeView,
				ahtiParents[this_level - 1],
				GETIMAGE_TYPE(pbTree[pos]),
				TRUE, (DWORD) pos, &tcAdd);
			phTreeItem[pos] = htiParent;
			ahtiParents[curLevel = this_level] = htiParent;
		}
	}

	FlushMessageQueue(WM_USER);

	for (pos = 1; pos < cntFlags.cCntItems; pos++) {

		// Restore our position

		if (GETIMAGE_TYPE(pbTree[pos]) == IMAGE_OPEN_FOLDER) {
			TreeView_Expand(hwndTreeView, phTreeItem[pos],
				TVE_EXPAND);
			FlushMessageQueue(WM_USER);
		}
	}
	SendMessage(hwndTreeView, WM_SETREDRAW, TRUE, 0);
	ASSERT(cntFirstVisible < cntFlags.cCntItems);
	if (cntFirstVisible)
		TreeView_Select(hwndTreeView, phTreeItem[cntFirstVisible],
			TVGN_FIRSTVISIBLE);

#ifdef _DEBUG
	HTREEITEM hItemFirstVisible =
		TreeView_GetFirstVisible(hwndTreeView);
#endif
	if (cntCurHighlight)
		TreeView_SelectItem(hwndTreeView, phTreeItem[cntCurHighlight]);

	hitemCurHighlight = TreeView_GetSelection(hwndTreeView);

	return TRUE;
}

INLINE static HTREEITEM STDCALL Tree_AddItem(HWND hwndTree,
	HTREEITEM htiParent, int iImage, UINT cChildren, LPARAM lParam,
	TV_INSERTSTRUCT* ptcInsert)
{
	ptcInsert->hParent = htiParent;
	ptcInsert->item.iImage = iImage;
	ptcInsert->item.iSelectedImage = iImage;
	ptcInsert->item.cChildren = cChildren;
	ptcInsert->item.lParam = lParam;

	return TreeView_InsertItem(hwndTree, ptcInsert);
}

static LRESULT STDCALL Tree_SetImage(HWND hwndTree, int iImage,
	HTREEITEM hItem)
{
	TV_ITEM tvinfo;
	ZeroMemory(&tvinfo, sizeof(tvinfo));

	tvinfo.hItem = hItem;
	tvinfo.iImage = iImage;
	tvinfo.iSelectedImage = iImage;
	tvinfo.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE;
	return TreeView_SetItem(hwndTree, &tvinfo);
}

/***************************************************************************

	FUNCTION:	PrintContents

	PURPOSE:	Print whatever is selected in the Contents Tab.
				If its a book, print every contained in the book,
				including any topics within books contained in the
				selected books.

	PARAMETERS:
		hwndDlg

	RETURNS:

	COMMENTS:
		If treeview control ever has multiple selection, we'll need to add
		support for that.

	MODIFICATION DATES:
		20-Feb-1994 [ralphw]

***************************************************************************/

static void STDCALL PrintContents(HWND hwndDlg)
{
	RC rc;
	PSTR psz;
	BOOL fMPrintIdSuccess = FALSE;

	HWND hwndTree = GetDlgItem(hwndDlg, ID_TREEVIEW);
	TV_ITEM tvi;

	tvi.hItem = TreeView_GetSelection(hwndTree);
	if (!tvi.hItem)
		return; // REVIEW: should we tell user nothing is selected?

	tvi.mask = TVIF_PARAM;
	TreeView_GetItem(hwndTree, &tvi);

	if (!InitMPrint())
		return;
	fMacroFlag = FALSE;

	HBT hbtCntJump = HbtOpenBtreeSz(txtCntJump, hfsGid, fFSOpenReadOnly);
	if (!hbtCntJump)
		// REVIEW: what should we really do?
		return;

	if (GETIMAGE_TYPE(pbTree[(UINT) tvi.lParam]) == IMAGE_TOPIC) {

		/*
		 * REVIEW: It's theoretically possible for
		 * RcLookupByKey to fail, so we'll need to add some
		 * useful error handling here. This could happen
		 * with the .GID file on the net and the network
		 * goes down, or the .GID file could be corrupted.
		 */

		rc = RcLookupByKey(hbtCntJump, (KEY) (LPVOID) &tvi.lParam,
			NULL, szSavedContext);

		if (rc != rcSuccess)
			goto error_exit; // REVIEW: we should tell the user something
		if (szSavedContext[0] == chMACRO)
			goto error_exit; // REVIEW: tell the user this topic can't be printed

		ParseContentsString(szSavedContext);
		psz = StrChrDBCS(szSavedContext, FILESEPARATOR);
		if (psz)
			*psz++ = '\0';
		EnableWindows();
		fMPrintIdSuccess = MPrintId((psz ? psz : ""), szSavedContext);
	}
	else {

		int curpos;
		UINT level = GETLEVEL(pbTree[tvi.lParam]);

		for (curpos = tvi.lParam + 1; curpos < cntFlags.cCntItems &&
					!fAbortPrint && fMultiPrinting;
				curpos++) {

#ifdef _DEBUG
int curlevel = GETLEVEL(pbTree[curpos]);
#endif
			if (GETIMAGE_TYPE(pbTree[curpos]) != IMAGE_TOPIC) {
				if (GETLEVEL(pbTree[curpos]) <= level)
					break;	  // stop if not a child book
				else
					continue; // don't print books
			}
			rc = RcLookupByKey(hbtCntJump, (KEY) (LPVOID) &curpos,
				NULL, szSavedContext);
			if (rc != rcSuccess)
				continue;
			if (szSavedContext[0] == chMACRO)
				continue; // don't print macros
			ParseContentsString(szSavedContext);
			psz = StrChrDBCS(szSavedContext, FILESEPARATOR);
			if (psz)
				*psz++ = '\0';
			EnableWindows();
			fMPrintIdSuccess = MPrintId((psz ? psz : ""), szSavedContext);
			if (!fMPrintIdSuccess)
				break;
		}
	}

error_exit:
	EndMPrint();
	DisableWindows();
	RcCloseBtreeHbt(hbtCntJump);

	/*
	 * We check for fAbortPrint in case the user tried to close down the
	 * help window directly (which will terminate help).
	 */

	if (fMPrintIdSuccess && !fQuitHelp && !fAbortPrint)
		Finder();
}

/***************************************************************************

	FUNCTION:	FindGidFile

	PURPOSE:	Finds a matching .GID file, if any.

	PARAMETERS:
		void

	RETURNS:
		NO_GID	  No .GID file was found, and a new one could not be created.
		SAME_GID  Current help file is contained in our current .GID file.
		NEW_GID   We have opened a new .GID file.

	COMMENTS:

	MODIFICATION DATES:
		30-Nov-1993 [ralphw]

***************************************************************************/

extern "C" int STDCALL FindGidFile(FM fm, BOOL fForceCreate, int tab)
{
	char szNewGid[MAX_PATH];
	BOOL fTriedOnce = FALSE;

	if (fHelp == POPUP_HELP)
		return NO_GID;

	// Once we have a .GID file, we don't let go of it unless the user
	// opens a help file via File Open.

	if (hfsGid)
		return SAME_GID;

	if (pszCntFile) 	// Was the .CNT file specified in the .HLP file?
		lstrcpy(szNewGid, pszCntFile);
	else {				// no, use the .HLP basename
		if (fm)
			lstrcpy(szNewGid, PszFromGh(fm));
		else
			lstrcpy(szNewGid, GetCurFilename());
	}

	ChangeExtension(szNewGid, txtGidExtension);

	FM fmGid = FmNewExistSzDir(szNewGid,
		DIR_CUR_HELP | DIR_SILENT_INI | DIR_CURRENT | DIR_PATH | DIR_SILENT_REG);
	if (fForceCreate && fmGid) {
		lstrcpy(szNewGid, fmGid);
		RemoveFM(&fmGid);
	}

	if (!fmGid) { // couldn't find a .GID file for this help file
        char szCopy[MAX_PATH];

BuildGid:
		CloseGid(); 	// Close our current .GID file, if any.

		ChangeExtension(szNewGid, txtCntExtension);
		CFM fmCnt(szNewGid,
			DIR_CUR_HELP | DIR_SILENT_INI | DIR_CURRENT | DIR_PATH | DIR_SILENT_REG);
		cntFlags.idOldTab = tab;

        /*
         * If we find a help file in the same location as the .CNT file,
         * and that help file is NOT the same as the current help file, then
         * the .CNT file is invalid.
         */

        if (fmCnt.fm) {
            strcpy(szCopy, fmCnt.fm);
            ChangeExtension(szCopy, txtHlpExtension);
            if (FExistFm(szCopy)) {
                char szBaseCurrent[MAX_PATH], szBaseCnt[MAX_PATH];
                GetFmParts(szCopy, szBaseCnt, PARTBASE);
                GetFmParts(fm, szBaseCurrent, PARTBASE);

                if (lstrcmpi(szBaseCnt, szBaseCurrent) == 0 &&
                        !FSameFmFm(szCopy, fm)) {
                    ASSERT(fmCnt.fm);
                    RemoveFM(&fmCnt.fm);
                }
            }
        }

		if (!fmCnt.fm || !(pszGidFile = CreateGidFile(fmCnt.fm, FALSE))) {

			// Create a .GID file even if there isn't a .CNT file

			if (fm) {
				strcpy(szNewGid, fm); // because CreateGidFile changes this
				if (!fmCreating)
					fmCreating = fm;
			}
			if (!(pszGidFile = CreateGidFile(szNewGid, TRUE)))
				return NO_GID;
		}
		lstrcpy(szNewGid, pszGidFile);
	}
	else {
		lstrcpy(szNewGid, fmGid);
		DisposeFm(fmGid);
	}

	/*
	 * If we got here then we are going to open a new GID file. We need to
	 * blow away any tables we created for the last GID file.
	 */

	CloseGid(); 	// Close our current .GID file, if any.

	int result;
	if (fTriedOnce)
		return OpenGidFile(szNewGid, NULL);
	while ((result = OpenGidFile(szNewGid, fm)) == WRONG_GID) {
		fTriedOnce = TRUE;
		CStr cszNewGid(szNewGid);
		GetFmParts(cszNewGid.psz, szNewGid, PARTBASE | PARTEXT);
		FM fmGid = FmNewExistSzDir(szNewGid,
			DIR_CUR_HELP | DIR_SILENT_INI | DIR_CURRENT | DIR_PATH | DIR_SILENT_REG);
		do {
			DisposeFm(fmGid);
			fmGid = FmNewExistSzDir(szNewGid, DIR_ENUMERATE);
			if (!fmGid)
				goto BuildGid;
		} while (!IsSameFile(cszNewGid.psz, szNewGid));

		DisposeFm(fmGid);

		fmGid = FmNewExistSzDir(szNewGid, DIR_ENUMERATE);
		if (!fmGid)
			goto BuildGid;

		strcpy(szNewGid, fmGid); // let's try this new file
		DisposeFm(fmGid);
	}
	if (result == INVALID_GID && !fTriedOnce) {
		fTriedOnce = TRUE;
		goto BuildGid;
	}

	return result;
}

/***************************************************************************

	FUNCTION:	ChangeExtension

	PURPOSE:	Change the extension of a file

	PARAMETERS:
		pszFile -- pointer to the file
		idExt	-- resource id of the extension

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		20-Aug-1993 [ralphw]

***************************************************************************/

extern "C" void STDCALL ChangeExtension(PSTR pszFile, PCSTR pszExt)
{
	PSTR psz = StrRChrDBCS(pszFile, '.');
	if (psz == NULL)
		psz = pszFile + lstrlen(pszFile);

	// Remove trailing spaces

	while (psz[-1] == ' ' && psz > pszFile + 1)
		psz--;

	lstrcpy(psz, pszExt);
}

/***************************************************************************

	FUNCTION:	ParseContentsString

	PURPOSE:	Parse a contents string, making certain that it specifies
				a filename.

	PARAMETERS:
		pszString

	RETURNS:

	COMMENTS:
		We have to do this here rather then in mastkey, because we don't
		know where the :Base command will be specified. It should appear
		before any topics, but we can't be certain of that.

	MODIFICATION DATES:
		02-Sep-1993 [ralphw]

***************************************************************************/

INLINE void STDCALL ParseContentsString(PSTR pszString)
{
	PSTR psz;

	ASSERT(pszHelpBase);

	// If no filename was specified, then add one

	if (*pszString != chMACRO && !StrChrDBCS(pszString, FILESEPARATOR)) {
		if ((psz = StrChrDBCS(pszString, WINDOWSEPARATOR))) {
			CLMem mem(strlen(psz) + 1);
			lstrcpy(mem.pBuf, psz);
			*psz++ = FILESEPARATOR;
			if (!*pszHelpBase)
				AuthorMsg(GetStringResource(wERRS_NO_BASE), ahwnd[iCurWindow].hwndParent);
			lstrcpy(psz, pszHelpBase);
			if ((psz = StrChrDBCS(psz, WINDOWSEPARATOR)))
				*psz = '\0';
			lstrcat(pszString, mem.pBuf);
		}
		else {
			int cb = lstrlen(pszString);
			pszString[cb] = FILESEPARATOR;
			if (!*pszHelpBase)
				AuthorMsg(GetStringResource(wERRS_NO_BASE), ahwnd[iCurWindow].hwndParent);
			lstrcpy(pszString + cb + 1, pszHelpBase);
		}
	}
}

/***************************************************************************

	FUNCTION:	CollapseChildren

	PURPOSE:	Change collapsed folder images

	PARAMETERS:
		hwndTree
		hItem
		pszParent

	RETURNS:

	COMMENTS:
		When a parent folder is closed, we want to close all the children
		as well. The TreeView control handles that closing, but fails to
		tell us to change our image. This routine parses through all the
		children of the item being closed, and changes the image of all
		child folders to a closed image.

	MODIFICATION DATES:
		17-Sep-1993 [ralphw]

***************************************************************************/

INLINE void STDCALL CollapseChildren(HWND hwndTree, int pos)
{
	UINT level = GETLEVEL(pbTree[pos]);

	HTREEITEM hItemFirstVisible = TreeView_GetFirstVisible(hwndTree);

	HTREEITEM* phTreeItem = (HTREEITEM*) PtrFromGh(pSrchClass->hTreeItem);

	for (pos++; pos < cntFlags.cCntItems; pos++) {
#ifdef _DEBUG
		IMAGE_TYPE curType = (IMAGE_TYPE) GETIMAGE_TYPE(pbTree[pos]);
		UINT	   curLevel = GETLEVEL(pbTree[pos]);
#endif

		if (GETIMAGE_TYPE(pbTree[pos]) < IMAGE_TOPIC &&
				GETLEVEL(pbTree[pos]) <= level)
			break;
		if (GETIMAGE_TYPE(pbTree[pos]) == IMAGE_OPEN_FOLDER) {
			pbTree[pos] = IMAGE_CLOSED_FOLDER | (pbTree[pos] & LEVEL_MASK);
			TreeView_Expand(hwndTree, phTreeItem[pos], TVE_COLLAPSE);
			Tree_SetImage(hwndTree, IMAGE_CLOSED_FOLDER, phTreeItem[pos]);
		}
	}

	// Restore our position

//	if (hItemFirstVisible)
//		TreeView_Select(hwndTree, hItemFirstVisible, TVGN_FIRSTVISIBLE);
}

extern "C" void STDCALL FlushMessageQueue(UINT msgEnd)
{
	MSG msg;

	while (PeekMessage(&msg, NULL, 0, msgEnd, PM_REMOVE)) {
		if (pSrchClass && pSrchClass->hwndTabParent &&
				IsDialogMessage(pSrchClass->hwndTabParent, &msg))
			continue;

		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

extern "C" BOOL STDCALL IsTopicsDlgCreated(void)
{
	return (BOOL) pSrchClass;
}

extern "C" HWND STDCALL GetTopicsDlgHwnd(void)
{
	return (pSrchClass && pSrchClass->hwndTabParent) ?
		pSrchClass->hwndTabParent : NULL;
}

int curIndex = 1;
int oldIndex;

/***************************************************************************

	FUNCTION:	CSearch::CSearch

	PURPOSE:	creates CSearch class

	PARAMETERS:
		void

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		13-Aug-1993 [ralphw]

***************************************************************************/

CSearch::CSearch(void)
{
	if (!pszIndexSeparators)
		GetMacroHde();
	ASSERT(pszIndexSeparators);
	lstrcpy(szKeyword, szSavedKeyword);
	dwTop = (DWORD) -1;
	oldIndex = 0;
	cTabs = 0;
	dwTemp = 0;
	hmapbtGid = hbtGid = hbtCntText = NULL;
	hss = hfsMaster = hmapbt = NULL;
	hbt = NULL;
	fm = NULL;
	fSelectionChange = FALSE;
	hTreeItem = NULL;
	pInclude = NULL;
	pIgnore = NULL;
	lcidSave = lcid;
	if (idTabSetting) {
		cntFlags.idOldTab = idTabSetting;
		idTabSetting = 0;
	}
}

CSearch::~CSearch(void)
{
	FreeKeywordList();
	if (hTreeItem)
		FreeGh(hTreeItem);
	if (hbtCntText)
		RcCloseBtreeHbt(hbtCntText);
	if (hmapbtGid)
		FreeGh(hmapbtGid);
	if (hbtGid)
		RcCloseBtreeHbt(hbtGid);
	lcid = lcidSave;
}

HWND CSearch::doModeless(HWND hwndParent, int idDlg, FARPROC proc)
{
	HWND hwndDlg = CreateDialog(hInsNow, MAKEINTRESOURCE(idDlg), hwndParent,
		(DLGPROC) proc);
	if (hwndDlg) {

		// REVIEW: if we use this to create extensible tabs, then we
		// should force the style to child, and also readjust the parent
		// dialog (and its granparent) if we don't fit.

#ifdef _DEBUG
		LONG style = GetWindowLong(hwndDlg, GWL_STYLE);
#endif

		SetWindowLong(hwndDlg, GWL_STYLE,
			GetWindowLong(hwndDlg, GWL_STYLE) | DS_3DLOOK | WS_TABSTOP | DS_CONTROL);

		SetWindowPos(hwndDlg, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW |
			SWP_NOMOVE | SWP_NOSIZE);
	}
	return hwndDlg;
}

extern "C" PSTR STDCALL LocalStrDup(PCSTR psz)
{
	PSTR pszDup = (PSTR) LhAlloc(LMEM_FIXED, lstrlen(psz) + 1);
	if (pszDup)
		lstrcpy(pszDup, psz);
	return pszDup;
}

/***************************************************************************

	FUNCTION:	CloseGid

	PURPOSE:	Close the current .GID file, if any. Saves state information
				before closing.

	PARAMETERS:
		void

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		30-Nov-1993 [ralphw]

***************************************************************************/

extern "C" void STDCALL CloseGid(void)
{
	RC rc;
	HF hf;

	cntFirstVisible = 0;
	cntCurHighlight = 0;

	if (pbTree) {
		FreeGh(pbTree);
		pbTree = NULL;
	}

	if (pTblFiles) {
		delete pTblFiles;
		pTblFiles = NULL;
	}
	ZeroMemory(aTabs, sizeof(aTabs));

	if (!hfsGid)
		return;

	RemoveHiliter(&hhiliter);

	if (hsrch && pDeleteSearcher) {
		pDeleteSearcher(hsrch);
		hsrch = 0;
	}

	if (fReadOnlyGid) {
		hf = 0;
		fReadOnlyGid = FALSE;
		goto JustClose;
	}

	/*
	 * This isn't really a for loop. We use it simply so that we can
	 * break out in case of an error condition.
	 */

	for(;;) {

		// REVIEW: Yank this stuff if we end up not saving state information

		hf = HfOpenHfs(hfsGid, txtFlags, fFSOpenReadWrite);
		if (!hf) {
HfFailure:
			if (RcGetFSError() == rcOutOfMemory)
				rc = rcOutOfMemory;
			else
				rc = rcNoPermission;
			break; // drop out into the error handler
		}
		if (LcbWriteHf(hf, &cntFlags, sizeof(cntFlags)) != sizeof(cntFlags)) {
			rc = rcNoPermission;
			break;
		}
		if (LcbWriteHf(hf, pPositions, sizeof(POS_RECT) * MAX_POSITIONS) !=
				sizeof(POS_RECT) * MAX_POSITIONS) {
			rc = rcNoPermission;
			break;
		}

		if (RcCloseHf(hf) != rcSuccess) {
			hf = 0; // Don't try to close it again
			rc = rcNoPermission;
			break;
		}
		hf = 0;
		if (fDirtyInfo) {
			hf = HfOpenHfs(hfsGid, (LPCSTR)txtFileInfo, fFSOpenReadWrite);
			if (!hf)
				goto HfFailure;
			if (LcbWriteHf(hf, pFileInfo,
					sizeof(GID_FILE_INFO) * MAX_FILES) !=
					sizeof(GID_FILE_INFO) * MAX_FILES) {
				rc = rcNoPermission;
				break;
			}
			if (RcCloseHf(hf) != rcSuccess) {
				hf = 0; // Don't try to close it again
				rc = rcNoPermission;
				break;
			}
		}

JustClose:
		if (pFileInfo) {
			FreePtr(pFileInfo);
			pFileInfo = NULL;
		}

		if (RcCloseHfs(hfsGid) != rcSuccess) {
			hfsGid = 0; // Don't try to close it again
			rc = rcNoPermission;
			break;
		}

		goto FinishUp;
	}

	if (pFileInfo) {
		FreePtr(pFileInfo);
		pFileInfo = NULL;
	}

	// We only get here if there are error conditions

	if (hf)
		RcCloseHf(hf);
	if (hfsGid)
		RcCloseHfs(hfsGid);

	if (rc == rcNoPermission) {
#ifdef _DEBUG
		char szBuf[300];
		wsprintf(szBuf, "Unable to write to %s.", pszGidFile);
		DBWIN(szBuf);
#endif
		// Silently ignore in retail version

	}
	else if (rc == rcOutOfMemory)
		OOM();

FinishUp:

	FreeLocalStrings();

	hfsGid = NULL;
}

/***************************************************************************

	FUNCTION:	OpenGidFile

	PURPOSE:	Open the specified .GID file, and read in all initialization
				information.

	PARAMETERS:
		pszGidFile

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		30-Nov-1993 [ralphw]

***************************************************************************/

static int STDCALL OpenGidFile(PSTR pszNewGid, FM fmHelpFile)
{
	BOOL fTriedOnce = FALSE;
	HELPFILE_DIRECTORY_ENTRY hfde;
	KEY iFile;
	char szBuf[MAX_PATH];
	HBT hbt = NULL;
	KEY key;
	RC rc;

	if (fmHelpFile)
		GetFmParts(fmHelpFile, szBuf, PARTBASE | PARTEXT);
	else
		szBuf[0] = '\0';
	CStr cszHelpFile(szBuf);

	{
		char szBuf[MAX_PATH + 100];
		wsprintf(szBuf, GetStringResource(sidIndexing), pszNewGid);
		SendStringToParent(szBuf);
	}

	if (!pFileInfo) {
		pFileInfo = (GID_FILE_INFO*) GhAlloc(GMEM_FIXED | GMEM_ZEROINIT,
			sizeof(GID_FILE_INFO) * MAX_FILES);
		if (!pFileInfo) {
			OOM();
			return NO_GID; // shouldn't be able to get here
		}
	}

TryAgain:
	FM fm = FmNew(pszNewGid);

	hfsGid = HfsOpenFm(fm, fFSOpenReadWrite);
	if (!hfsGid) {

		if (RcGetFSError() == rcInvalid) { // invalid .gid file
			DisposeFm(fm);

			return INVALID_GID;
		}

		// If we can't open it here, try the local machine or windows\help
		// directory. This takes care of us when we weren't able to write
		// to the original .GID file.

		char szHelpDir[MAX_PATH];
		ConvertToWindowsHelp(fm, szHelpDir);
		hfsGid = HfsOpenFm(szHelpDir, fFSOpenReadWrite);

		if (!hfsGid) {
			hfsGid = HfsOpenFm(fm, fFSOpenReadOnly);
			if (hfsGid)
				fReadOnlyGid = TRUE;
			else if (RcGetFSError() == rcInvalid) {
				DisposeFm(fm);

				return INVALID_GID;
			}
		}
        else {
            // Make certain fm points to the .gid file we just opened

            RemoveFM(&fm);
            fm = FmCopyFm(szHelpDir);
        }
	}

	if (!hfsGid) {
		DisposeFm(fm);

		// REVIEW: Here we should strip the path and look elsewhere for
		// the .GID file.

		return NO_GID; // extremely unlikely that we can't open it
	}

	// Get the flags and Contents Tab information

	HF hf = HfOpenHfs(hfsGid, txtFlags, fFSOpenReadOnly);

	// REVIEW: What should we do if we can't open this?

	ZeroMemory(&hfde, sizeof(hfde));
	if (hf) {
		if (LcbReadHf(hf, &cntFlags, sizeof(cntFlags)) != sizeof(cntFlags))
			goto ReInitialize;
		if (cntFlags.version != GID_VERSION) {
            RcCloseHf(hf);
            RcCloseHfs(hfsGid);
            hfsGid=NULL;
            DeleteFile(fm);
            RemoveFM(&fm);
            return INVALID_GID;
        }
		if (!pPositions) {
			pPositions = (POS_RECT*) GhAlloc(GMEM_FIXED,
				sizeof(POS_RECT) * MAX_POSITIONS);
			if (!pPositions) {
				DisposeFm(fm);
				OOM(); // doesn't return
			}
		}
		if (LcbReadHf(hf, pPositions, sizeof(POS_RECT) * MAX_POSITIONS) !=
				sizeof(POS_RECT) * MAX_POSITIONS)
			goto ReInitialize;

		if (cntFlags.cCntItems > 1) {
			pbTree = (PBYTE) GhAlloc(GMEM_FIXED, cntFlags.cCntItems);
			if (!pbTree) {
				DisposeFm(fm);
				OOM(); // doesn't return
			}

			if (LcbReadHf(hf, pbTree, cntFlags.cCntItems) !=
					cntFlags.cCntItems) {
				goto ReInitialize;
			}
		}

		RcCloseHf(hf);
		hf = 0;
	}
	else {
		goto ReInitialize;
	}

	// Read file type and time stamp information

	hf = HfOpenHfs(hfsGid, (LPCSTR) txtFileInfo, fFSOpenReadOnly);

	if (hf) {
		if (LcbReadHf(hf, pFileInfo, sizeof(GID_FILE_INFO) * MAX_FILES) !=
				sizeof(GID_FILE_INFO) * MAX_FILES) {
			goto ReInitialize;
		}
		RcCloseHf(hf);
		hf = 0;
	}

	// Get the base filename and the title for the group of help files

	hbt = HbtOpenBtreeSz(txtCntText, hfsGid, fFSOpenReadOnly);

	// REVIEW: What should we do if we can't open this?

	if (hbt) {
		key = CNT_TITLE;

		if (RcLookupByKey(hbt, (KEY) (LPVOID) &key, NULL, szBuf) == rcSuccess)
			pszHelpTitle = LocalStrDup(szBuf);
		key = CNT_BASE;
		if (RcLookupByKey(hbt, (KEY) (LPVOID) &key, NULL, szBuf) == rcSuccess)
			pszHelpBase = LocalStrDup(szBuf);
		RcCloseBtreeHbt(hbt);
	}
	if (!pszHelpBase)
		pszHelpBase = LocalStrDup(txtZeroLength);

	// Get the names and titles of each individual help file

	hbt = HbtOpenBtreeSz(txtFNAMES, hfsGid, fFSOpenReadOnly);

	// REVIEW: What should we do if we can't open this?

	RemoveFM(&fm);
	if (hbt) {

		// Find out if the .CNT file has a newer time/date stamp, and if so,
		// regenerate the .GID file.

		iFile = CNT_FILE;

		if ((rc = RcLookupByKey(hbt, (KEY) (LPVOID) &iFile, NULL, &hfde)) !=
				rcSuccess || !MatchTimestamp(hfde.szFileName, hfde.TimeStamp, &fm)) {

			// It's okay if the .CNT file is simply gone.

			if (rc == rcSuccess) {
				if (GetFileAttributes(hfde.szFileName) == (DWORD) -1)
					goto OkayAferAll;
				else
					cntFlags.flags &= ~GID_NO_CNT; // we now have a .CNT file
			}
ReInitialize:

			// We can get here while trying to read the cntFlags data,
			// or while reading the various filenames.

			if (hf)
				RcCloseHf(hf);
			if (!hbt) {

				iFile = CNT_FILE;

				// Try to read the .CNT file name

				hbt = HbtOpenBtreeSz(txtCntText, hfsGid, fFSOpenReadOnly);
				if (hbt) {
					iFile = CNT_FILE;
					if (RcLookupByKey(hbt, (KEY) (LPVOID) &iFile, NULL,
							&hfde) != rcSuccess)
						lstrcpy(hfde.szFileName, pszNewGid);
				}
			}

			if (hbt)
				RcCloseBtreeHbt(hbt);

			if (pbTree) {
				FreeGh(pbTree);
				pbTree = NULL;
			}
			RcCloseHfs(hfsGid);
			hfsGid = NULL;
			FreeLocalStrings();
			if (fTriedOnce || !(pszGidFile = CreateGidFile(hfde.szFileName,
					(cntFlags.flags & GID_NO_CNT)))) {
				if (!fTriedOnce && fReadOnlyGid) {
					char szNewCnt[MAX_PATH];
					strcpy(szNewCnt, pszNewGid);
					ChangeExtension(szNewCnt, txtCntExtension);
					if (WCmpiSz(szNewCnt, hfde.szFileName) != 0) {
						if ((pszGidFile = CreateGidFile(szNewCnt,
								(cntFlags.flags & GID_NO_CNT))))
							goto KeepTrying;
					}
				}
				return NO_GID;
			}
KeepTrying:
			lstrcpy(pszNewGid, pszGidFile);
			fTriedOnce = TRUE;
			if (pTblFiles) {
				delete pTblFiles;
				pTblFiles = NULL;
			}
			goto TryAgain;
		}
		else if (fm) // can be set by MatchTimestamp()
			RemoveFM(&fm);
OkayAferAll:
		iFile = 1;

		while (RcLookupByKey(hbt, (KEY) (LPVOID) &iFile, NULL, &hfde) ==
				rcSuccess) {
			PSTR psz = StrChrDBCS(hfde.szFileName, '=');
			if (psz) {
				if (!pTblFiles)
					pTblFiles = new CTable();
				*psz++ = '\0';
				GetFmParts(psz, szBuf, PARTBASE | PARTEXT);

				// Same filename, but different directory?

				if (!(pFileInfo[iFile - 1].filetype & CHFLAG_LINK) &&
						fmHelpFile && lstrcmpi(szBuf, cszHelpFile.psz) == 0 &&
						lstrcmpi(psz, fmHelpFile) != 0) {

					// Wrong .GID file. Bail out.

					if (hf)
						RcCloseHf(hf);
					if (hbt)
						RcCloseBtreeHbt(hbt);

					if (pbTree) {
						FreeGh(pbTree);
						pbTree = NULL;
					}
					RcCloseHfs(hfsGid);
					hfsGid = NULL;
					FreeLocalStrings();
					delete pTblFiles;
					pTblFiles = NULL;
					return WRONG_GID;
				}

				/*
				 * We now check to see if the file either now exists
				 * where it didn't before, or it no longer exists, or it has
				 * changed. If any change occurs, then we must reinitialize
				 * the entire .GID file.
				 */

				if (pFileInfo[iFile - 1].filetype & CHFLAG_INDEX) {
					if (pFileInfo[iFile - 1].filetype & CHFLAG_MISSING) {
						HANDLE hfind;

// BUGBUG: won't find files in help directory or current directory!

						WIN32_FIND_DATA fd;
						if ((hfind = FindFirstFile(psz, &fd)) != INVALID_HANDLE_VALUE) {
							FindClose(hfind);
ResetCnt:
							iFile = CNT_FILE;
							if (RcLookupByKey(hbt, (KEY) (LPVOID) &iFile,
									NULL, &hfde) != rcSuccess)
								lstrcpy(hfde.szFileName, pszNewGid);
							goto ReInitialize; // this file exists now
						}
#ifdef _DEBUG
						else if (hwndParent) {
							char szBuf[MAX_PATH + 100];
							wsprintf(szBuf, "Missing: %s\r\n", psz);
							SendStringToParent(szBuf);
						}
#endif
					}
					else if (!MatchTimestamp(psz, pFileInfo[iFile - 1].timestamp,
							&fm)) {
						goto ResetCnt; // file has changed or is now missing
					}
				}
				pTblFiles->AddString(hfde.szFileName); // Add the title
				if (fm) { // is file in a new location?
					pTblFiles->AddString(fm);	   // Add the filename
					RemoveFM(&fm);
				}
				else
					pTblFiles->AddString(psz);		// Add the filename
			}
			iFile++;
		}
		RcCloseBtreeHbt(hbt);
		hbt = NULL;
	}

	if (!pTblFiles) {
		ASSERT(fmCreating);
		pTblFiles = new CTable();
		pTblFiles->AddString(txtZeroLength, PszFromGh(fmCreating));
	}

	if (cntFlags.cTabs) {
		hbt = HbtOpenBtreeSz(txtTabDlgs, hfsGid, fFSOpenReadOnly);
		ASSERT(hbt);
		if (hbt) {
			for (key = 1; key <= cntFlags.cTabs; key++) {
				if (RcLookupByKey(hbt, (KEY) (LPVOID) &key, NULL, szBuf) == rcSuccess) {
					HMODULE hmod;
					PSTR psz = StrChrDBCS(szBuf, '=');
					ASSERT(psz);
					if (!psz)
						continue; // paranoia -- mastkey.cpp should prevent this
					*psz++ = '\0';
					if ((hmod = (HMODULE)HFindDLL(psz, TRUE))) {
						aTabs[key].pOpenTabDialog =
							(OPENTABDIALOG) GetProcAddress(hmod, (LPCSTR)txtOpenTabDialog);
						ASSERT(aTabs[key].pOpenTabDialog);
						if (!aTabs[key].pOpenTabDialog) {
							if (fHelpAuthor) {
								wsprintf(szBuf, GetStringResource(wERRS_BAD_TAB),
									psz);
								ErrorQch(szBuf);
							}
							continue;
						}
						aTabs[key].pszName = (PSTR) LocalStrDup(szBuf);
					}
#ifdef _DEBUG
					if (!hmod) {
						char szMsg[512];
						char szPath[MAX_PATH];
						_getcwd(szPath, sizeof(szPath));
						wsprintf(szMsg, "Could not find the dll %s needed for an extensible tab.\r\nCurrent directory is %s",
							psz, szPath);
						OkMsgBox(szMsg);
					}
#endif

				}
#ifdef _DEBUG
				else
					ASSERT(!"Bad entry");
#endif
			}
			RcCloseBtreeHbt(hbt);
			hbt = NULL;
		}
	}

	if (pPositions[POS_MAIN].rc.cx) {
		rctHelp = pPositions[POS_MAIN].rc;
		CheckWindowPosition(&rctHelp, TRUE);
		if (ahwnd[MAIN_HWND].hwndParent) {
			if (!cntFlags.fMainMax && IsWindowVisible(ahwnd[MAIN_HWND].hwndParent)
					&& IsZoomed(ahwnd[MAIN_HWND].hwndParent))
				ShowWindow(ahwnd[MAIN_HWND].hwndParent, SW_RESTORE);

			MoveWindow(ahwnd[MAIN_HWND].hwndParent, rctHelp.left,
				rctHelp.top, rctHelp.cx, rctHelp.cy,
				IsWindowVisible(ahwnd[MAIN_HWND].hwndParent));
		}
	}

	if (!pszGidFile)
		pszGidFile = LocalStrDup(pszNewGid);

	return NEW_GID;
}

/***************************************************************************

	FUNCTION:	doTabSearch

	PURPOSE:	Chicago entry point into TabThing dialog box

	PARAMETERS:
		void

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		13-Aug-1993 [ralphw]

***************************************************************************/

extern "C" int STDCALL doTabSearch(void)
{
	//	Prevent reentrancy by verifying that we haven't already inited a tabbed
	//	search.
	if (pSrchClass) {
		if (!IsValidWindow(pSrchClass->hwndTabParent)) {
			RemoveWaitCursor();
			Finder();
		}
		fNoQuit = TRUE;
		return TAB_ALREADY_UP;
	}

	if (fAutoClose) {
		KillOurTimers();
		fAutoClose = FALSE;
	}

	if (!fCommControlInitialized) {
		if (!LoadShellApi())
			return -1;
		fCommControlInitialized = TRUE;
	}

	/*
	 * We don't want to have our parent window be on-top, or they will
	 * end up on top of our dialog box. So we temporarily shut off the
	 * on-top style until we finish this dialog.
	 */

	CWaitCursor* pcursor = new CWaitCursor;    // put up an hourglass

	RemoveOnTop();		// remove on-top state from all windows
	DisableWindows();	// disable all windows

	pSrchClass = new CSearch;

	pSrchClass->hwndTabParent = CreateDialog(hInsNow,
		MAKEINTRESOURCE(IDDLG_TAB), ahwnd[iCurWindow].hwndParent,
			(DLGPROC) TabDlgProc);

	if (!pSrchClass->hwndTabParent) {
		if (pSrchClass->result != NO_TABS) {
			PostErrorMessage(wERRS_OOM);
			pSrchClass->result = -1;
		}
		goto Error;
	}

	ShowWindow(pSrchClass->hwndTabParent, SW_SHOW);

	delete pcursor; 	// remove the hourglass

	// We do our own message loop since we are modeless

	MSG msg;

	for (pSrchClass->fMsgLoop = TRUE; pSrchClass->fMsgLoop;) {
		GetMessage(&msg, NULL, 0, 0);

		if (msg.message == WM_KEYDOWN && msg.wParam == VK_TAB &&
				GetAsyncKeyState(VK_CONTROL) < 0) {
			HWND hwndTabDlg = GetDlgItem(pSrchClass->hwndTabParent, ID_TABCONTROL);
			int idCurTab = TabCtrl_GetCurSel(hwndTabDlg);
			int idSaveTab = idCurTab;

			// tab in reverse if shift is down

			if (GetAsyncKeyState(VK_SHIFT) < 0)
				idCurTab--;
			else
				idCurTab++;

			if (idCurTab > pSrchClass->cTabs)
				idCurTab = 0;
			else if (idCurTab < 0)
				idCurTab = pSrchClass->cTabs;

			if (SendMessage(hwndTabDlg, TCM_SETCURSEL, idCurTab, 0L) == -1)
				// if we couldn't select (find) the new one, fail out
				// and restore the old one
				SendMessage(hwndTabDlg, TCM_SETCURSEL, idSaveTab, 0L);
			PageChange(hwndTabDlg);
			continue;
		}

		if (IsDialogMessage(pSrchClass->hwndTabParent, &msg))
			continue;

		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

Error:

	// REVIEW: Is this necessary?
	// Make certain the window gets destroyed.

	if (IsValidWindow(pSrchClass->hwndTabParent))
		DestroyWindow(pSrchClass->hwndTabParent);

	RestoreOnTop(); 	// restore on-top state of all windows
	EnableWindows();	// re-enable all windows

	int result = pSrchClass->result;
	delete pSrchClass;
	pSrchClass = NULL;
	return result;
}

#ifndef DM_REPOSITION
#define DM_REPOSITION		(WM_USER+2)
#endif

DLGRET TabDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg) {

		case WM_INITDIALOG:
			ChangeDlgFont(hwndDlg);
			if (!InitTabControl(hwndDlg))
				ClosePropertySheet(hwndDlg, NO_TABS);
			else {
				WRECT rc;
				GetWindowWRect(hwndDlg, &rc);
				int width = rc.cx;
				int height = rc.cy;
				ReadWinRect(&rc, WCH_TOPICS, NULL);
				MoveWindow(hwndDlg, rc.left, rc.top, width,
					height, FALSE);
				SendMessage(hwndDlg, DM_REPOSITION, 0, 0L);
			}
			return TRUE;

		case WM_NOTIFY:
#define lpnm ((LPNMHDR)lParam)
			switch (lpnm->code) {

				case TCN_SELCHANGE:
					PageChange(lpnm->hwndFrom);
					break;
			}
#undef lpnm
			break;

		case WM_HELP:
			OnF1Help(lParam, aKeywordIds);
			return TRUE;

		case WM_CONTEXTMENU:
			OnContextMenu(wParam, aKeywordIds);
			return TRUE;

		case WM_COMMAND:
			switch (wParam) {
				case ID_NO_INDEX:
					cntFlags.flags |= GID_NO_INDEX;
					ClosePropertySheet(hwndDlg, RETRY);
					break;

				case IDCANCEL:
					ClosePropertySheet(hwndDlg, 0);

// REVIEW: 02-Aug-1993 [ralphw] We should destroy help if it wasn't visible

					break;

				case IDOK:
					SendMessage(hwndTabSub, WM_COMMAND, wParam, lParam);
					break;

				case IDDOSEARCH:
					ClosePropertySheet(hwndDlg, lParam);
					break;

				case ID_JMP_CONTEXT:
					ClosePropertySheet(hwndDlg, CONTEXT_SEARCH);
					break;

				case IDBTN_GEN_INDEX:	// pass it on
					SendMessage(hwndTabSub, msg, wParam, lParam);
					break;

				case IDC_PRINT:

					// REVIEW: this had better be the Contents tab!!!

					PrintContents(hwndTabSub);
					break;

			}
			break;

		case WM_DESTROY:
			if (IsValidWindow(hwndTabSub)) {
				if (hwndTabSub == hwndFindTab)
					SendMessage(hwndTabSub, WM_CLOSE, 0, 0);
				else {
					ASSERT(IsValidWindow(hwndTabSub));
					DestroyWindow(hwndTabSub);
				}
				hwndTabSub = NULL;
			}
			if (pImgLst_Destroy && hil) {
				pImgLst_Destroy(hil);
				hil = NULL;
			}

			// Save the window position

			WriteWinPosHwnd(hwndDlg, 0, WCH_TOPICS);
			break;

		case MSG_FTS_JUMP_HASH:
			fFTSJump= TRUE;
			tabLparam = lParam;
			lstrcpy(szSavedContext, FtsIndexToFile(wParam));
			ClosePropertySheet(hwndDlg, FTS_HASH_SEARCH);
			break;

		case MSG_FTS_JUMP_VA:
			fFTSJump= TRUE;
			tabLparam = lParam;
			lstrcpy(szSavedContext, FtsIndexToFile(wParam));
			ClosePropertySheet(hwndDlg, FTS_VA_SEARCH);
			break;

		case MSG_TAB_CONTEXT:
			tabLparam = lParam;
			tabWparam = wParam;
			ClosePropertySheet(hwndDlg, EXT_TAB_CONTEXT);
			break;

		case MSG_TAB_MACRO:
			tabLparam = lParam;
			ClosePropertySheet(hwndDlg, EXT_TAB_MACRO);
			break;

		case MSG_FTS_GET_TITLE:
			ASSERT(lParam != NULL);  // Don't call me without a pointer
			if (lParam != NULL)
			{
				// Ralph FYI : The code which returned the pointer was fine on your side
				//			   but somewhere before it got to the find dll the value was
				//			   getting zapped to 00000000(at least in NT).	So I changed
				//			   call to return the pointer in the pointer sent in the lParam

				PCSTR *ptr = (PCSTR *)lParam;			// Pointer to a PCSTR in lParam
				*ptr = FtsIndexToTitle((int) wParam);	// Map and return result
			}
			break;

		case MSG_REINDEX_REQUEST:
			{
				// We're in big trouble if we get this message when Find
				// isn't the current tab

				ASSERT(hwndTabSub == hwndFindTab);

				HWND hwnd = CreateFindTab(hwndDlg, FTS_RE_INDEX);
				if (pRegAnimate) {
					pRegAnimate(NULL, NULL);
					StopAnimation();
				}
				if (!hwnd) {
					hwnd = CreateFindTab(hwndDlg, FTS_NORMAL_INDEX);
					if (!hwnd) {
						Error(wERRS_BAD_FIND_TAB, wERRA_RETURN);
						ClosePropertySheet(hwndDlg, 0);
					}
				}
				hwndTabSub = hwndFindTab;
			}
			break;

		case MSG_FTS_WHERE_IS_IT:
			ASSERT(lParam != NULL);  // Don't call me without a pointer
			{
				char szOldName[MAX_PATH];
				FM fmWhereTheHeckIsIt;
				GetFmParts((PSTR) lParam, szOldName, PARTBASE | PARTEXT);
				fmWhereTheHeckIsIt = FmNewExistSzDir(szOldName,
					(wParam? 0 : DIR_ENUMERATE)
						| DIR_CUR_HELP | DIR_SILENT_INI | DIR_CURRENT | DIR_PATH | DIR_SILENT_REG);
				if (fmWhereTheHeckIsIt) {
					strcpy((PSTR) lParam, fmWhereTheHeckIsIt);
					DisposeFm(fmWhereTheHeckIsIt);
				}
				else
					strcpy((PSTR) lParam, txtZeroLength);
			}
			break;

		case MSG_GET_DEFFONT:
			SetWindowLong(hwndDlg, DWL_MSGRESULT, (LONG) hfontDefault);
			return TRUE;

		default:
			return FALSE;
  }
  return FALSE;
}

/***************************************************************************

	FUNCTION:	CreateTabChild

	PURPOSE:	Used to create a tab child dialog box

	PARAMETERS:
		idCurTab
		hwndDlg

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		09-Aug-1993 [ralphw]

***************************************************************************/

HWND STDCALL CreateTabChild(TAB_ID idCurTab, HWND hwndDlg)
{
	HWND hwnd;
	EnableWindow(GetDlgItem(hwndDlg, IDC_PRINT), FALSE);

	if (idCurTab == TAB_CONTENTS)
		hwnd = pSrchClass->doModeless(hwndDlg,
			IDD_TAB_CONTENTS, (FARPROC) ContentsDlg);
	else if (idCurTab == TAB_INDEX)
		hwnd =	pSrchClass->doModeless(hwndDlg,
			IDD_TAB_INDEX, (FARPROC) IndexDlg);
	else if (idCurTab == TAB_FIND) {
		hwnd = CreateFindTab(hwndDlg, FTS_NORMAL_INDEX);
		if (pRegAnimate) {
			pRegAnimate(NULL, NULL);
			StopAnimation();
		}
		if (!hwnd)
			Error(wERRS_BAD_FIND_TAB, wERRA_RETURN);
		else if (hwnd == (HWND) -1) // means the user cancelled the Find wizard
			hwnd = NULL;
	}
	else {
		if (!aTabs[idCurTab - TAB_FIND].pOpenTabDialog)
			return NULL;
		hwnd = aTabs[idCurTab - TAB_FIND].pOpenTabDialog(
			hwndDlg, 0, 0);

		if (hwnd) {
			DWORD style = GetWindowLong(hwnd, GWL_STYLE);
			style &= ~(DS_MODALFRAME | WS_BORDER | WS_THICKFRAME | WS_DLGFRAME);
			style |= DS_3DLOOK;

			SetWindowLong(hwnd, GWL_STYLE, style);

			CheckDialogSize(hwndDlg, hwnd);

			// Force the dialog box on top and display it

			SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
				SWP_NOMOVE | SWP_NOSIZE);
		}
	}

	// REVIEW: we need to position the dialog box

	return hwnd;
}

BOOL STDCALL InitTabControl(HWND hwndDlg)
{
	HWND hwndTabs = GetDlgItem(hwndDlg, ID_TABCONTROL);
	ASSERT(hwndTabs);
	int i;

	TC_ITEM ti;
	ZeroMemory(&ti, sizeof(TC_ITEM));
	ZeroMemory(aTabIds, sizeof(aTabIds));

	ti.mask = TCIF_TEXT;

#ifdef _DEBUG
	ShowCntFlags();
#endif

	if (cntFlags.flags & GID_CONTENTS)
		AddTab(&ti, TAB_CONTENTS, GetStringResource(sidCONTENTS), hwndTabs);
	else if (cntFlags.idOldTab == TAB_CONTENTS)
		cntFlags.idOldTab++;

	// Add the index tab

	if (!(cntFlags.flags & GID_NO_INDEX))
		AddTab(&ti, TAB_INDEX, GetStringResource(sidINDEX), hwndTabs);
	else if (cntFlags.idOldTab == TAB_INDEX)
		cntFlags.idOldTab++;

	if (cntFlags.flags & GID_FTS) {

		/*
		 * Find out if the ftsrch dll exists. If it exists, we'll assume
		 * (perhaps mistakenly) that we can load it. We won't really try
		 * to load it until the user clicks the Find tab.
		 */

		if (IsSearchAvailable()) {

			/*
			 * If our first tab is the Find tab, then we MUST be certain
			 * that we can actually load the ftsrch.dll. We default to the
			 * first tab whenever we fail to create a tab, so the first
			 * tab MUST be usable.
			 */

			if (!pSrchClass->cTabs) {
				if (!LoadSearchDll())
					goto NoFindTab;
			}
			AddTab(&ti, TAB_FIND, GetStringResource(sidFIND), hwndTabs);
		}
	}
	else if (cntFlags.idOldTab == TAB_FIND)
		cntFlags.idOldTab++;

NoFindTab:
	for (i = 1; i <= MAX_TABS; i++) {
		if (aTabs[i].pszName) {
			AddTab(&ti, (TAB_ID) (TAB_1 + (i - 1)), aTabs[i].pszName, hwndTabs);
		}
	}

	if (!pSrchClass->cTabs)
		return FALSE;

	char szBuf[256];
	LoadString(hInsNow, sidFinderTitle, szBuf, sizeof(szBuf));

	QDE qde = (QDE) QdeFromGh(HdeGetEnv());
	if (pszHelpTitle)
		lstrcat(szBuf, pszHelpTitle);
	else if ((QDE_RGCHTITLE(qde)[0] != '\0'))
		lstrcat(szBuf, (LPSTR) QDE_RGCHTITLE(qde));
	else
		GetFmParts(QDE_FM(qde), szBuf + lstrlen(szBuf), PARTBASE | PARTEXT);

	SetWindowText(hwndDlg, szBuf);
	if (cntFlags.idOldTab > cntFlags.cTabs + 2) {
		cntFlags.idOldTab = (cntFlags.flags & GID_CONTENTS) ? TAB_CONTENTS :
			(!(cntFlags.flags & GID_NO_INDEX)) ? TAB_INDEX : TAB_FIND;
	}

#ifdef _DEBUG
	TAB_ID idOld = (TAB_ID) cntFlags.idOldTab;
#endif

	TabCtrl_SetCurSel(GetDlgItem(hwndDlg, ID_TABCONTROL),
		GetTabPosition(cntFlags.idOldTab));
	hwndTabSub = CreateTabChild((TAB_ID) cntFlags.idOldTab, hwndDlg);
	if (!hwndTabSub && aTabIds[0] != cntFlags.idOldTab) {
		hwndTabSub = CreateTabChild(aTabIds[0], hwndDlg);
		TabCtrl_SetCurSel(GetDlgItem(hwndDlg, ID_TABCONTROL), 0);
		cntFlags.idOldTab = aTabIds[0];
	}

	// REVIEW: Contents dialog could fail if the contents file
	//	could not be read (sharing violation?). We should do
	//	something intelligent in that case.

	return (BOOL) hwndTabSub;
}

/***************************************************************************

	FUNCTION:	FreeLocalStrings

	PURPOSE:	Free up some localally allocated strings

	PARAMETERS:
		void

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		26-Dec-1993 [ralphw]

***************************************************************************/

static void STDCALL FreeLocalStrings(void)
{
	if (pszHelpTitle)
		lcClearFree(&pszHelpTitle);

	if (pszHelpBase)
		lcClearFree(&pszHelpBase);

	if (pszGidFile)
		lcClearFree(&pszGidFile);

	if (pszCntFile)
		lcClearFree(&pszCntFile);
}

/***************************************************************************

	FUNCTION:	CreateFindTab

	PURPOSE:

	PARAMETERS:
		hwndDlg

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		17-Jul-1994 [ralphw]

***************************************************************************/

#ifndef NO_PRAGMAS
#pragma data_seg(".text", "CODE")
#endif
static const char txtNewSearcher[]		= "NewSearcher";
static const char txtDeleteSearcher[]	= "DeleteSearcher";
static const char txtOpenIndex[]		= "OpenIndex";
static const char txtSaveGroup[]		= "SaveGroup";
static const char txtLoadGroup[]		= "LoadGroup";
static const char txtIsValidIndex[] 	= "IsValidIndex";
static const char txtDiscardIndex[] 	= "DiscardIndex";
static const char txtSetDirectoryLocator[] = "SetDirectoryLocator";
static const char txtRegisterAnimator[] = "RegisterAnimator";

#ifdef _HILIGHT
static const char txtNewHiliter[] = "NewHiliter";
static const char txtDeleteHiliter[] = "DeleteHiliter";
static const char txtScanDisplayText[] = "ScanDisplayText";
static const char txtClearDisplayText[] = "ClearDisplayText";
static const char txtCountHilites[] = "CountHilites";
static const char txtQueryHilites[] = "QueryHilites";
#endif
#ifndef NO_PRAGMAS
#pragma data_seg()
#endif

// Some error codes we may get from FTSrch

#define OUT_OF_MEMORY		  ((UINT) -6)
#define OUT_OF_DISK 		  ((UINT) -20)

HWND STDCALL CreateFindTab(HWND hwndParentDlg, UINT fsIndex)
{
	BOOL fAsked = FALSE;
	CWaitCursor cwait;
	CEnable disableCancel(GetDlgItem(hwndParentDlg, IDCANCEL));
	CEnable disableDisplay(GetDlgItem(hwndParentDlg, IDOK));
	CEnable disableTab(GetDlgItem(hwndParentDlg, ID_TABCONTROL));

	if (fsIndex != FTS_NORMAL_INDEX && hsrch) {
		RemoveHiliter(&hhiliter);

		pDeleteSearcher(hsrch);
		hsrch = 0;
	}
#ifdef _PRIVATE
	CTimeReport report("Find startup time:");
#endif

StartAllOver:
	if (!hsrch) {
		if (!pNewSearcher) {
			if (!LoadSearchDll()) {

				// Can't use GetStringResource because ReportMissingDll does

				char szDll[MAX_PATH];
				GetStringResource2(sidFtsDll, szDll);
				ReportMissingDll(szDll);
				return NULL;
			}
		}

		ASSERT(pNewSearcher && pDeleteSearcher && pOpenIndex && pOpenTabDialog && pSetDirectoryLocator);
#ifdef _HILIGHT
		ASSERT(pNewHiliter && pDeleteHiliter && pScanDisplayText && pClearDisplayText
				&& pCountHilites && pQueryHilites
			  );
#endif

		pSetDirectoryLocator(hwndParentDlg);

		hsrch = pNewSearcher();
		if (!hsrch)
			return NULL;

		if (fsIndex == FTS_RE_INDEX) {
			if (!AskAboutIndexes(hwndParentDlg))
				return NULL;
			hsrch = pNewSearcher();
			if (!hsrch)
				return NULL;
		}

		char szGroupFile[MAX_PATH];
		lstrcpy(szGroupFile, pszGidFile);
		ChangeExtension(szGroupFile, txtGrpExtension);

		if (hwndAnimate || StartAnimation(sidLoadingFTS))
			pRegAnimate(NextAnimation, hwndAnimate);
		SetForegroundWindow(hwndAnimate); // make certain animation is on top

		if (pTblFiles->CountStrings() > 2) { // do we have a group?
			FM fmGrp = FmNewExistSzDir(szGroupFile,
				DIR_CUR_HELP | DIR_SILENT_INI | DIR_CURRENT | DIR_PATH | DIR_SILENT_REG);
			if (fmGrp) {
				if (fsIndex != FTS_NORMAL_INDEX)
					DeleteFile(fmGrp);
				else
				{
					ERRORCODE ec;

					ec= pLoadGroup(hsrch, fmGrp);

					if (ec >= 0)
					{
						RemoveFM(&fmGrp);
						pRegAnimate(NULL, NULL);
						goto RocknRoll; // we've got everything we need
					}

					if (ec == OUT_OF_MEMORY || ec == OUT_OF_DISK)
					{
						RemoveFM(&fmGrp);
						RemoveHiliter(&hhiliter);
						pRegAnimate(NULL, NULL);
						StopAnimation();
						pDeleteSearcher(hsrch);
						hsrch = NULL;

						return NULL;
					}
				}

				DeleteFile(fmGrp); // bad group
				RemoveFM(&fmGrp);
			}
		}

		BOOL fOpenIndexFailure = FALSE;
		BOOL fSeenOneIndex = FALSE;

		for (int index = 2; index <= pTblFiles->CountStrings(); index += 2) {
			BYTE type = pFileInfo[index / 2 - 1].filetype;
			char szName[MAX_PATH];
#ifdef _DEBUG // copy it early
			lstrcpy(szName, pTblFiles->GetPointer(index));
#endif
			if (type & (CHFLAG_MISSING | CHFLAG_LINK | CHFLAG_FTS_ASKED))
				continue;

			lstrcpy(szName, pTblFiles->GetPointer(index));
			ChangeExtension(szName, txtFtsExtension);

			CFM fm(szName,
				DIR_CUR_HELP | DIR_SILENT_INI | DIR_CURRENT | DIR_PATH | DIR_SILENT_REG);

			// This will also fail if we run out of memory

			if (!fm.fm) {

				/*
				 * At this point, we're either out of memory (unlikely),
				 * or we have a .HLP file without a matching .FTS file and we
				 * have NOT asked the user if they want it. At this point, we
				 * should bail out, put up a dialog box asking for permission
				 * to generate a .FTS file for ALL missing ones, and when
				 * completed, restart this for() loop.
				 */

				pRegAnimate(NULL, NULL);
				goto CreateOrDie;
			}
			else if (!(type & CHFLAG_FTS_AVAIL)) {
				pFileInfo[index / 2 - 1].filetype |= CHFLAG_FTS_AVAIL;
				fDirtyInfo = TRUE; // we need to update the .GID file
			}

			int idIndex;
			DWORD time1, time2;

			time1 = pFileInfo[index / 2 - 1].timestamp;
			time2 = 0;

			if (!time1)
			{
				WIN32_FIND_DATA fd;
				HANDLE hfd;

				if ((hfd = FindFirstFile(pTblFiles->GetPointer(index), &fd)) ==
						INVALID_HANDLE_VALUE)
					pFileInfo[index / 2 - 1].filetype |= CHFLAG_MISSING;
				else
				{
					AdjustForTimeZoneBias(&fd.ftLastWriteTime.dwLowDateTime);

					time1= pFileInfo[index / 2 - 1].timestamp = fd.ftLastWriteTime.dwLowDateTime;
					FindClose(hfd);
				}
			}

			idIndex = pOpenIndex(hsrch, PszFromGh(fm.fm), NULL, NULL,
				&time1, &time2);

			if (idIndex == OUT_OF_MEMORY || idIndex == OUT_OF_DISK)
			{
				RemoveHiliter(&hhiliter);
				pRegAnimate(NULL, NULL);
				StopAnimation();
				pDeleteSearcher(hsrch);
				hsrch = NULL;

				return NULL;
			}

			if (idIndex < 0)
			{

				/*
				 * Either an error occurred (the fts file is invalid) or
				 * the time/date stamp for the fts file doesn't match the
				 * help file. Delete the fts file and force a rebuild.
				 */

				fOpenIndexFailure = TRUE;
				if (idIndex >= 0)
					pDiscardIndex(hsrch, idIndex);
				if (!DeleteFile(PszFromGh(fm.fm))) {

					// BUGBUG: We can't regenerate -- need an error message

					pFileInfo[index / 2 - 1].filetype &= ~CHFLAG_FTS_AVAIL;
					pFileInfo[index / 2 - 1].filetype |= CHFLAG_BAD_RO_FTS;
				}
				else
					fOpenIndexFailure = TRUE;
				continue;
			}
			SetFileIndex(index / 2 - 1, idIndex);
			ASSERT(GetFileIndex(index / 2 - 1) == idIndex);

			if (pTblFiles->CountStrings() > 2)
				fDirtyInfo = TRUE;
			fSeenOneIndex = TRUE;
			pRegAnimate(NULL, NULL);
		}

		pRegAnimate(NULL, NULL);

		/*
		 * If we haven't added a single index, then create one now or
		 * return a failure.
		 */

		if (!fSeenOneIndex) {
CreateOrDie:
			if (!fAsked) {
				if (!AskAboutIndexes(hwndParentDlg)) {

					// Failure means the user cancelled

					RemoveHiliter(&hhiliter);

					pDeleteSearcher(hsrch);
					hsrch = NULL;

					return (HWND) -1; // special value to indicate cancelation
				}
				fAsked = TRUE;
				ASSERT(!hsrch);
				goto StartAllOver;
			}
			else {
				RemoveHiliter(&hhiliter);

				pDeleteSearcher(hsrch);
				hsrch = NULL;

				return NULL;
			}
		}

		if (fOpenIndexFailure) {

			/*
			 * One or more of the index files are invalid and have been
			 * deleted. We must regenerate the missing ones.
			 */

			RemoveHiliter(&hhiliter);

			pDeleteSearcher(hsrch);
			hsrch = NULL;
			if (!fAsked && AskAboutIndexes(hwndParentDlg)) {
				fAsked = TRUE;
				ASSERT(!hsrch);
				goto StartAllOver;
			}
			return NULL;
		}

		if (pTblFiles->CountStrings() > 2)
			pSaveGroup(hsrch, szGroupFile);

		pSetDirectoryLocator(NULL);
	}

RocknRoll:

//	if (fsIndex == FTS_RE_INDEX) {
//		return hwndFindTab;
//	}
	if (fsIndex == FTS_BUILD_DEFAULT) {
		return (HWND) 1;
	}

	if (hsrch) {
		hwndFindTab = pOpenTabDialog(hwndParentDlg, (DWORD) hsrch, 0);
		if (hwndFindTab) {

#ifdef _HILIGHT
			GetHiliter();
#endif

			// REVIEW: not necessary for build 163 and beyond

			SetWindowLong(hwndFindTab, GWL_STYLE,
				GetWindowLong(hwndFindTab, GWL_STYLE) | DS_3DLOOK);
			ChangeDlgFont(hwndFindTab);

			CheckDialogSize(hwndParentDlg, hwndFindTab);

			// Force the dialog box on top and display it

			SetWindowPos(hwndFindTab, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW |
				SWP_NOMOVE | SWP_NOSIZE);
		}
		else {
			RemoveHiliter(&hhiliter);

			pDeleteSearcher(hsrch);
			hsrch = 0;
		}

		StopAnimation();
		return hwndFindTab;
	}
	return NULL;
}

#ifdef _HILIGHT
HHILITER GetHiliter()
{
	if (hhiliter)
		return hhiliter;

	ASSERT(pNewHiliter);

	if (hsrch)
		hhiliter = pNewHiliter(hsrch);

	return hhiliter;
}

static void STDCALL RemoveHiliter(HHILITER* phhiliter)
{
	ASSERT(phhiliter);
	if (*phhiliter) {
		pDeleteHiliter(*phhiliter);
		*phhiliter = 0;
	}
}
#endif

/***************************************************************************

	FUNCTION:	LoadSearchDll

	PURPOSE:	Load full-text search dll

	PARAMETERS:
		void

	RETURNS:	TRUE if dll found and all function addresses obtained

	COMMENTS:

	MODIFICATION DATES:
		17-Jul-1994 [ralphw]

***************************************************************************/

BOOL STDCALL LoadSearchDll(void)
{
	if (!pNewSearcher) {
		HINSTANCE hmodule;
		if ((hmodule = (HINSTANCE) HFindDLL(GetStringResource(sidFtsDll), TRUE))) {
			pNewSearcher =
				(NEWSEARCHER) GetProcAddress(hmodule, txtNewSearcher);
			pDeleteSearcher =
				(DELETESEARCHER) GetProcAddress(hmodule, txtDeleteSearcher);
			pOpenIndex =
				(OPENINDEX) GetProcAddress(hmodule, txtOpenIndex);
			pOpenTabDialog =
				(OPENTABDIALOG) GetProcAddress(hmodule, txtOpenTabDialog);
			pSaveGroup =
				(SAVEGROUP) GetProcAddress(hmodule, txtSaveGroup);
			pLoadGroup =
				(LOADGROUP) GetProcAddress(hmodule, txtLoadGroup);
			pIsValidIndex =
				(ISVALIDINDEX) GetProcAddress(hmodule, txtIsValidIndex);
			pDiscardIndex =
				(DISCARDINDEX) GetProcAddress(hmodule, txtDiscardIndex);
			pSetDirectoryLocator =
				(SETDIRECTORYLOCATOR) GetProcAddress(hmodule, txtSetDirectoryLocator);
			pRegAnimate  =
				(REGISTERANIMATOR) GetProcAddress(hmodule, txtRegisterAnimator);
#ifdef _HILIGHT
			pNewHiliter  =
				(NEWHILITER) GetProcAddress(hmodule, txtNewHiliter);
			pDeleteHiliter	=
				(DELETEHILITER) GetProcAddress(hmodule, txtDeleteHiliter);
			pScanDisplayText  =
				(SCANDISPLAYTEXT) GetProcAddress(hmodule, txtScanDisplayText);
			pClearDisplayText  =
				(CLEARDISPLAYTEXT) GetProcAddress(hmodule, txtClearDisplayText);
			pCountHilites  =
				(COUNTHILITES) GetProcAddress(hmodule, txtCountHilites);
			pQueryHilites  =
				(QUERYHILITES) GetProcAddress(hmodule, txtQueryHilites);
#endif
		}
	}

	ASSERT(pNewHiliter);
	ASSERT(pDeleteHiliter);
	ASSERT(pScanDisplayText);
	ASSERT(pClearDisplayText);
	ASSERT(pCountHilites);
	ASSERT(pQueryHilites);

	if (!pNewSearcher || !pDeleteSearcher || !pOpenIndex || !pOpenTabDialog
			|| !pSaveGroup || !pLoadGroup || !pIsValidIndex || !pSetDirectoryLocator
#ifdef _HILIGHT

			|| !pNewHiliter || !pDeleteHiliter || !pScanDisplayText
			|| !pClearDisplayText || !pCountHilites || !pQueryHilites
#endif
	   )
		return FALSE;	// either no dll, or its a corrupted one
	return TRUE;
}

const int DLG_LEFT_INDENT = 10;
const int DLG_TOP_INDENT = 30;

static void STDCALL CheckDialogSize(HWND hwndPropSheet, HWND hwndTab)
{
	if (!hwndTab)
		return;

	RECT rcTabChild, rcPropSheet;
	GetWindowRect(hwndTab, &rcTabChild);
	GetWindowRect(GetDlgItem(hwndPropSheet, ID_TABCONTROL), &rcPropSheet);

	int padWidth;
	int padHeight;
	if (RECT_WIDTH(rcTabChild) + (DLG_LEFT_INDENT * 2) >
			RECT_WIDTH(rcPropSheet))
		padWidth = (RECT_WIDTH(rcTabChild) + (DLG_LEFT_INDENT * 2)) -
			RECT_WIDTH(rcPropSheet);
	else
		padWidth = 0;

	if (RECT_HEIGHT(rcTabChild) + DLG_TOP_INDENT + DLG_LEFT_INDENT >
			RECT_HEIGHT(rcPropSheet))
		padHeight = (RECT_HEIGHT(rcTabChild) + DLG_TOP_INDENT +
			DLG_LEFT_INDENT) - RECT_HEIGHT(rcPropSheet);
	else
		padHeight = 0;

	if (padWidth || padHeight) {
		WRECT rcParent;
		RECT rcDisplay, rcPrint, rcCancel;

		GetWindowWRect(hwndPropSheet, &rcParent);
		GetWindowRect(GetDlgItem(hwndPropSheet, IDOK), &rcDisplay);
		GetWindowRect(GetDlgItem(hwndPropSheet, IDC_PRINT), &rcPrint);
		GetWindowRect(GetDlgItem(hwndPropSheet, IDCANCEL), &rcCancel);

		rcParent.cx += padWidth;
		rcPropSheet.right += padWidth;

		rcParent.cy += padHeight;
		rcPropSheet.bottom += padHeight;

		OffsetRect(&rcDisplay, padWidth, padHeight);
		OffsetRect(&rcPrint, padWidth, padHeight);
		OffsetRect(&rcCancel, padWidth, padHeight);

		CheckWindowPosition(&rcParent, FALSE);
		MoveClientWindow(hwndPropSheet, GetDlgItem(hwndPropSheet, ID_TABCONTROL),
			&rcPropSheet, FALSE);
		MoveClientWindow(hwndPropSheet, GetDlgItem(hwndPropSheet, IDOK),
			&rcDisplay, FALSE);
		MoveClientWindow(hwndPropSheet, GetDlgItem(hwndPropSheet, IDC_PRINT),
			&rcPrint, FALSE);
		MoveClientWindow(hwndPropSheet, GetDlgItem(hwndPropSheet, IDCANCEL),
			&rcCancel, FALSE);
		MoveWindow(hwndPropSheet, rcParent.left, rcParent.top,
			rcParent.cx, rcParent.cy, TRUE);

		GetWindowRect(hwndTab, &rcTabChild);
		GetWindowRect(GetDlgItem(hwndPropSheet, ID_TABCONTROL), &rcPropSheet);

		InvalidateRect(hwndPropSheet, NULL, TRUE);
	}

	// BUGBUG: top setting must be determined from the tab

	OffsetRect(&rcTabChild, (rcPropSheet.left - rcTabChild.left) + 10,
		(rcPropSheet.top - rcTabChild.top) + 30);
	MoveClientWindow(hwndPropSheet, hwndTab, &rcTabChild, FALSE);
}

static void STDCALL ClosePropertySheet(HWND hwndPropDlg, int result)
{
	if (IsValidWindow(hwndTabSub)) {
		if (hwndTabSub == hwndFindTab)
			SendMessage(hwndTabSub, WM_CLOSE, 0, 0);
		else {
			ASSERT(IsValidWindow(hwndTabSub));
			DestroyWindow(hwndTabSub);
		}
	}
	hwndTabSub = NULL;
	pSrchClass->result = result;
	pSrchClass->fMsgLoop = FALSE;
	EnableWindows();
	if (IsValidWindow(hwndPropDlg))
		DestroyWindow(hwndPropDlg);
}

/***************************************************************************

	FUNCTION:	ChangeDlgFont

	PURPOSE:	Change all dialog controls to use a small font -- same
				font as is used in navigation button. Not necessary for
				Chicago build 162 and on, but recommended for NT.

	PARAMETERS:
		hwndDlg

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		22-Jul-1994 [ralphw]

***************************************************************************/

class CBroadCastChildren
{
public:

	CBroadCastChildren(HWND hwnd, UINT msgOrg, WPARAM wParamOrg = 0,
		LPARAM lParamOrg = 0);

	UINT   msg;
	WPARAM wParam;
	LPARAM lParam;
};

extern "C" void STDCALL ChangeDlgFont(HWND hwndDlg)
{
	ASSERT(hfontSmallSys);
	if (!fIsThisNewShell4)
		CBroadCastChildren foo(hwndDlg, WM_SETFONT, (WPARAM) hfontSmallSys, FALSE);
	SetFocus(hwndDlg);
}

static BOOL __stdcall EnumChildProc(HWND hwnd, LPARAM lval);

CBroadCastChildren::CBroadCastChildren(HWND hwnd, UINT msgOrg,
	WPARAM wParamOrg, LPARAM lParamOrg)
{
	msg = msgOrg;
	wParam = wParamOrg;
	lParam = lParamOrg;

	EnumChildWindows(hwnd, (WNDENUMPROC) EnumChildProc, (LPARAM) (PSTR) this);
}

#define pchild ((CBroadCastChildren *) lval)

static BOOL __stdcall EnumChildProc(HWND hwnd, LPARAM lval)
{
	SendMessage(hwnd, pchild->msg, pchild->wParam, pchild->lParam);
	return TRUE;
}


/***************************************************************************

	FUNCTION:	ReportMissingDll

	PURPOSE:	Complain about a missing dll only if we haven't complained
				about it before.

	PARAMETERS:
		pszDllName

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		23-Jul-1994 [ralphw]

***************************************************************************/

extern "C" void STDCALL ReportMissingDll(PCSTR pszDllName)
{
	static CTable* ptblMissing;

	if (!ptblMissing)
		ptblMissing = new CTable;

	if (ptblMissing->IsStringInTable(pszDllName) ||
			FIsSearchModule(pszDllName))
		return;

	ptblMissing->AddString(pszDllName);
	char szBuf[MAX_PATH * 2];
	char szDirectory[MAX_PATH];
	GetSystemDirectory(szDirectory, sizeof(szDirectory));
	wsprintf(szBuf, GetStringResource(wERRS_MISSING_DLL), pszDllName, szDirectory);
	ErrorQch(szBuf);
}

static CTable* ptblFm;

extern "C" int STDCALL GetFmIndex(FM fm)
{
	int pos;

	if (!ptblFm)
		ptblFm = new CTable;

	pos = ptblFm->IsStringInTable((PCSTR) fm);
	if (!pos)
		pos = ptblFm->AddString((PCSTR) fm);
	return pos;
}

extern "C" FM STDCALL GetFmPtr(int pos)
{
	ASSERT(ptblFm);

	return (FM) ptblFm->GetPointer(pos);
}

#ifndef NO_PRAGMAS
#pragma data_seg(".text", "CODE")
#endif
const char txtKeyMacros[] = "|MACROS";
#ifndef NO_PRAGMAS
#pragma data_seg()
#endif


/***************************************************************************

	FUNCTION:	RcGetLAFromHss

	PURPOSE:	Retrieve the i-th search hit from the given list. The first
				hit in the list is numbered zero.
	PARAMETERS:
		hss -- search set
		qde -- help file
		iss -- hit index
		qla -- destination la
		pszKeyword -- keyword

	RETURNS:

	COMMENTS:
		Only works for help files -- cannot be used with a .gid file

	MODIFICATION DATES:
		02-Jun-1995 [ralphw]

***************************************************************************/

extern "C" RC STDCALL RcGetLAFromHss(HSS hss, QDE qde, ISS iss, QLA qla, PCSTR pszKeyword)
{
	QSS qss;

	if (hss == NULL) {
		SetSearchErrorRc(rcBadHandle);
		return rcBadHandle;
	}

	if (!IsEmptyString(pszKeyword) &&
			FindKeywordMacro(pszKeyword, (HDE) qde) == rcMacroIndex)
		return rcMacroIndex;

	qss = (QSS) PtrFromGh(hss);

	CbReadMemQLA(qla, (QB)((QSSREC)&qss->ssrecFirst + iss),
		QDE_HHDR(qde).wVersionNo);

	SetSearchErrorRc(rcSuccess);
	return rcSuccess;
}


/***************************************************************************

	FUNCTION:	RcGetLAFromGid

	PURPOSE:	Look up the address of a topic from a .GID index

	PARAMETERS:
		qde -- help file containing search hit
		iss -- index of hit
		qla -- destination for address
		pszKeyword -- actual keyword

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		02-Jun-1995 [ralphw]

***************************************************************************/

extern "C" RC STDCALL RcGetLAFromGid(QDE qde, ISS iss, QLA qla,
	PCSTR pszKeyword)
{
	ASSERT(iss >= 0);
	if (qde->hss == NULL) {
		SetSearchErrorRc(rcBadHandle);
		return rcBadHandle;
	}
	MASTER_TITLE_RECORD mtr;
	MASTER_RECKW* prec = HssToReckw(qde->hss);

	// We copy the structure because qde may go away and take qde-hss with
	// it.

	CopyMemory(&mtr, &prec->mtr[iss], sizeof(MASTER_TITLE_RECORD));

#ifdef _DEBUG
		// for debugging purposes

	PSTR pszFile = pTblFiles->GetPointer(mtr.idHelpFile);
#endif

	FM fm = FmNew(pTblFiles->GetPointer(mtr.idHelpFile));
	if (!fm)
		return rcBadHandle;
	if (FSameFile((HDE) qde, fm))
		RemoveFM(&fm);
	else {
		fDelayShow = TRUE;
		if (!FReplaceHde(txtMain, &fm, NULL)) {
			RemoveFM(&fm);
			return rcBadHandle;
		}
	}
	ASSERT(!fm);
#ifdef _DEBUG
	QDE qdeDebug = (QDE) PtrFromGh(HdeGetEnv());
#endif

	// REVIEW: keyword macros use -1 as the address I believe, so we should
	// be able to ignore this check if mtr.addr != -1

	if (mtr.addr == -1 && !IsEmptyString(pszKeyword) &&
			FindKeywordMacro(pszKeyword,
				HdeGetEnv()) == rcMacroIndex)
		return rcMacroIndex;

	CbReadMemQLA(qla, (QB) &mtr.addr,
		QDE_HHDR((QDE) PtrFromGh(HdeGetEnv())).wVersionNo);
	return rcSuccess;
}

static RC STDCALL FindKeywordMacro(PCSTR pszKeyword, HDE hde)
{
	HBT hbtKeyMacros;
	RC rc;

	if (!IsEmptyString(pszKeyword)) {
		hbtKeyMacros = HbtOpenBtreeSz(txtRose, QDE_HFS(QdeFromGh(hde)),
			fFSOpenReadOnly);
		if (hbtKeyMacros) {
			HASH hash = HashFromSz(pszKeyword);
			PSTR pszMacro = (PSTR) LhAlloc(LMEM_FIXED, MAX_KEY_MACRO);
			BTPOS	btpos;

			rc = RcLookupByKey(hbtKeyMacros, (KEY) (LPVOID) &hash, &btpos,
					pszMacro);
			if (rc == rcNoExists) {
				// Deal with case where we are in between keys in a btree

				if (FValidPos(&btpos)) {
					LONG	lBogus;
					BTPOS	btposNew;

					rc = RcOffsetPos(hbtKeyMacros, &btpos, (LONG) -1,
						(QL) &lBogus, &btposNew);
					if (rc == rcSuccess)
						rc = RcLookupByPos(hbtKeyMacros, &btposNew,
							(KEY) (QL) &lBogus, pszMacro);
				}
				else
					rc = RcLastHbt(hbtKeyMacros, 0, pszMacro, NULL);
			}
			if (rc == rcSuccess)
				Execute(pszMacro);

			FreeLh(pszMacro);
			RcCloseBtreeHbt(hbtKeyMacros);
			return (rc == rcSuccess) ? rcMacroIndex : rcFailure;
		}
	}
	return rcFailure;
}

static PCSTR FASTCALL FtsIndexToFile(int index)
{
	int i;

	for (i = 0; i < MAX_FILES; i++) {
		if (GetFileIndex(i) == index) {
#ifdef _DEBUG
			PSTR psz = pTblFiles->GetPointer((i * 2) + 2);
#endif
			return pTblFiles->GetPointer((i * 2) + 2);
		}
	}

	// If we get here, index from FTS sent to us doesn't match what was
	// returned when we added the index.

	ASSERT(i < MAX_FILES);
	return txtZeroLength;
}

static PCSTR FASTCALL FtsIndexToTitle(int index)
{
	int i;

	for (i = 0; i < MAX_FILES; i++) {
		if (GetFileIndex(i) == index) {
			PSTR psz = pTblFiles->GetPointer((i * 2) + 1);
			if (!psz || !*psz) {

				/*
				 * If this is the only file, then we typically do not have
				 * a title stored in pTblFiles, so we need to grab the
				 * current help's title.
				 */

				if (pTblFiles->CountStrings() <= 2) {
					psz = QDE_RGCHTITLE((QDE) HdeGetEnv());
					if (!*psz)
						psz = pTblFiles->GetPointer((i * 2) + 2);
				}
				else
					psz = pTblFiles->GetPointer((i * 2) + 2);
			}
			return psz;
		}
	}

	// If we get here, index from FTS sent to us doesn't match what was
	// returned when we added the index.

	ASSERT(i < MAX_FILES);
	return txtZeroLength;
}

/***************************************************************************

	FUNCTION:	FtsWizardProc

	PURPOSE:	Dialog for choosing which files to index

	PARAMETERS:
		hwndDlg
		msg
		wParam
		lParam

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		07-Aug-1994 [ralphw]

***************************************************************************/

extern "C" DLGRET FtsWizardProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	HWND hwndIncludeList, hwndIgnoreList, hwnd;
	int pos, i, index;
	char szName[MAX_PATH];
//	DWORD  msgResult = 0;
	LPWIZSHEET lpWizSheet;
	LPPROPSHEETPAGE ppsp;

	switch (msg) {
		case WM_PAINT:
		{
			// Only Chicago understands how to paint static bitmaps!
			// The code does the painting for the other opsys's
			if (!fIsThisNewShell4)
			{
				HBITMAP 	hOldBitmap;
				BITMAP		bmInfo;
				HDC 		hDC,hMemDC;
				RECT		rct;

				LPPOINT lppnt = (LPPOINT) &rct;

				GetWindowRect(GetDlgItem(hwndDlg,IDC_WIZBMP),&rct);
				ScreenToClient(hwndDlg, lppnt++);  // Convert to the dlg client coordinates
				ScreenToClient(hwndDlg,lppnt);

				hDC = GetDC(GetDlgItem(hwndDlg, IDC_WIZBMP));				   // Get a DC to draw to
				hMemDC = CreateCompatibleDC(hDC);							   // and a memDC to select into

				ppsp = (LPPROPSHEETPAGE) GetWindowLong(hwndDlg, DWL_USER);
				ASSERT(ppsp);
				lpWizSheet = (LPWIZSHEET) ppsp->lParam;   // Set up pointer to my data
				GetObject(lpWizSheet->wd->hWizBMP,sizeof(bmInfo),&bmInfo);	   // Get the size info

				hOldBitmap = (HBITMAP)SelectObject(hMemDC,lpWizSheet->wd->hWizBMP);    // Select into MemDc
				StretchBlt(hDC,0,0,rct.right - rct.left,rct.bottom - rct.top,  // Blt it to the window
						   hMemDC,0,0,bmInfo.bmWidth,bmInfo.bmHeight,SRCCOPY);
				SelectObject(hMemDC,hOldBitmap);								   // Unselect the bitmap

				ReleaseDC(GetDlgItem(hwndDlg,IDC_WIZBMP),hDC);				   // Release the DC
				DeleteDC(hMemDC);											   // Delete the memDC
				ValidateRect(hwndDlg,&rct); 								   // Validate the drawn region
			}
			return FALSE;		// False causes paint to continue
		}
		break;

		case WM_INITDIALOG:
		{

			CWaitCursor cwait;

			SetWindowLong(hwndDlg, DWL_USER, lParam);
			ppsp =	(LPPROPSHEETPAGE) lParam;
			lpWizSheet = (LPWIZSHEET) ppsp->lParam;   // Set up pointer to my data

			// set the loword to the current page number;
			ftsFlags.dwWizStat = MAKELPARAM(LOWORD(ppsp->lParam),LOWORD(ftsFlags.dwWizStat));
			ChangeDlgFont(hwndDlg);
			ASSERT(hfontDefault);

			PSTR pStrText = (PSTR) LhAlloc(LMEM_FIXED, WIZ_MEM_REQ);

			int iStrID = (lpWizSheet->nID - IDD_WIZ_FTS_EXPRESS) * WIZ_PAGE_SEP + sidWizPage0_0;

			*pStrText = '\0';
			for (int i = 0; i < WIZ_STR_PAGES; i++)
				strcat(pStrText,GetStringResource(iStrID + i));
			ASSERT(strlen(pStrText) < WIZ_MEM_REQ);

			SetWindowText(GetDlgItem(hwndDlg, IDC_WIZ_MAIN_STATIC),pStrText);

			if (fIsThisNewShell4)
				SendMessage(GetDlgItem(hwndDlg,IDC_WIZBMP), STM_SETIMAGE,
					IMAGE_BITMAP, (LPARAM) lpWizSheet->wd->hWizBMP);

			switch(lpWizSheet->nID)
			{
				case IDD_WIZ_FTS_EXPRESS:
					lpWizSheet->wd->flgs = ftsFlags;  // Set up default flags
					CheckDlgButton(hwndDlg, IDC_WIZ_MIN_SIZE, TRUE);
					lpWizSheet->wd->bExpress = IDC_WIZ_MIN_SIZE;
					break;

				case IDD_WIZ_FTS_INCLUDE_1LIST:
					hwndIncludeList = GetDlgItem(hwndDlg, IDC_INCLUDE_1LIST);
					SendMessage(hwndIncludeList, WM_SETFONT, (WPARAM) hfontDefault, FALSE);
					for (index = 2; index <= pTblFiles->CountStrings(); index += 2) \
					{
						PSTR pszTitle = pTblFiles->GetPointer(index - 1);
						if (!*pszTitle)
						{
							GetFmParts((FM) pTblFiles->GetPointer(index),
								szName, PARTBASE | PARTEXT);
							pszTitle = szName;
						}
#ifdef _DEBUG
						PSTR pszFile =	pTblFiles->GetPointer(index);
#endif

						BYTE type = pFileInfo[index / 2 - 1].filetype;
						if (type & (CHFLAG_MISSING | CHFLAG_LINK))
							continue;
//						hwnd = (type & CHFLAG_FTS_ASKED) ? hwndIgnoreList :
//							hwndIncludeList;
						pos = SendMessage(hwndIncludeList, LB_ADDSTRING,
							0, (LPARAM) pszTitle);
						if (pos != LB_ERR)
						{
							SendMessage(hwndIncludeList,LB_SETSEL,!(type & CHFLAG_FTS_ASKED),(LPARAM) pos);
							SendMessage(hwndIncludeList, LB_SETITEMDATA, pos, index);
						}
					}
				break;
				case IDD_WIZ_FTS_INCLUDE :
					// Fill the include and ignore lists with the help titles

					hwndIncludeList = GetDlgItem(hwndDlg, IDC_INCLUDE);
					hwndIgnoreList = GetDlgItem(hwndDlg, IDC_EXCLUDE);
					SendMessage(hwndIncludeList, WM_SETFONT, (WPARAM) hfontDefault, FALSE);
					SendMessage(hwndIgnoreList, WM_SETFONT, (WPARAM) hfontDefault, FALSE);
					for (index = 2; index <= pTblFiles->CountStrings(); index += 2) \
					{
						PSTR pszTitle = pTblFiles->GetPointer(index - 1);
						if (!*pszTitle)
						{
							GetFmParts((FM) pTblFiles->GetPointer(index),
								szName, PARTBASE | PARTEXT);
							pszTitle = szName;
						}
#ifdef _DEBUG
						PSTR pszFile =	pTblFiles->GetPointer(index);
#endif

						BYTE type = pFileInfo[index / 2 - 1].filetype;
						if (type & (CHFLAG_MISSING | CHFLAG_LINK))
							continue;
						hwnd = (type & CHFLAG_FTS_ASKED) ? hwndIgnoreList :
							hwndIncludeList;
						pos = SendMessage(hwnd, LB_ADDSTRING,
							0, (LPARAM) pszTitle);
						if (pos != LB_ERR)
							SendMessage(hwnd, LB_SETITEMDATA, pos, index);
					}
				break;

				case IDD_WIZ_FTS_SIMILARITY :
					CheckDlgButton(hwndDlg, IDC_SIMILARITY_YES, ftsFlags.fSimilarity);
					CheckDlgButton(hwndDlg, IDC_SIMILARITY_NO, !ftsFlags.fSimilarity);
					break;
				case IDD_WIZ_FTS_UNTITLED :
					CheckDlgButton(hwndDlg, IDC_INCLUDE_UNTITLED, ftsFlags.fUntitled);
					CheckDlgButton(hwndDlg, IDC_EXCLUDE_UNTITLED, !ftsFlags.fUntitled);
					break;
				case IDD_WIZ_FTS_PHRASE :
					CheckDlgButton(hwndDlg, IDC_PHRASE_YES, ftsFlags.fFphrase);
					CheckDlgButton(hwndDlg, IDC_PHRASE_NO, !ftsFlags.fFphrase);
					break;
				case IDD_WIZ_FTS_MATCHING :
					CheckDlgButton(hwndDlg, IDC_DISPLAY_MATCHING, ftsFlags.fPhraseFeedBack);
					CheckDlgButton(hwndDlg, IDC_NO_DISPLAY_MATCHING, !ftsFlags.fPhraseFeedBack);
					break;
				case IDD_WIZ_FTS_END :
				break;
			}
		}
		break;
		case WM_COMMAND:
		{
			ppsp = (LPPROPSHEETPAGE) GetWindowLong(hwndDlg, DWL_USER);
			ASSERT(ppsp);
			lpWizSheet = (LPWIZSHEET) ppsp->lParam;   // Set up pointer to my data

			switch(lpWizSheet->nID)
			{
				case IDD_WIZ_FTS_EXPRESS:
					break;
				case IDD_WIZ_FTS_INCLUDE_1LIST :
					switch(LOWORD(wParam))
					{
						case IDC_SELECTALL:
							hwndIncludeList = GetDlgItem(hwndDlg, IDC_INCLUDE_1LIST);
							SendMessage(hwndIncludeList,LB_SETSEL,TRUE,(LPARAM) -1); // Select them all
					}

				break;
				case IDD_WIZ_FTS_INCLUDE :
					switch(LOWORD(wParam))
					{
						case IDC_EXCLUDE:
							switch (HIWORD(wParam))
							{
								case LBN_SELCHANGE:
								case LBN_SELCANCEL:
									EnableWindow(GetDlgItem(hwndDlg, IDC_ADD_FILES),
										SendMessage((HWND) lParam, LB_GETSELCOUNT, 0, 0));
									break;
							}
							break;

						case IDC_INCLUDE:
							switch (HIWORD(wParam))
							{
								case LBN_SELCHANGE:
								case LBN_SELCANCEL:
									EnableWindow(GetDlgItem(hwndDlg, IDC_REMOVE_FILES),
										SendMessage((HWND) lParam, LB_GETSELCOUNT, 0, 0));
								break;
							}
							break;

						case IDC_REMOVE_FILES:
							if (HIWORD(wParam) == BN_CLICKED)
								OnFtsMove(hwndDlg, FALSE);
						break;

						case IDC_ADD_FILES:
							if (HIWORD(wParam) == BN_CLICKED)
								OnFtsMove(hwndDlg, TRUE);
						break;


					}
				break;
				case IDD_WIZ_FTS_SIMILARITY :
					break;
				case IDD_WIZ_FTS_UNTITLED :
					break;
				case IDD_WIZ_FTS_PHRASE :
					break;
				case IDD_WIZ_FTS_MATCHING :
					break;
				case IDD_WIZ_FTS_END :
					break;
			}
		}
		break;
		case WM_NOTIFY:
			ppsp = (LPPROPSHEETPAGE) GetWindowLong(hwndDlg, DWL_USER);
			ASSERT(ppsp);
			lpWizSheet = (LPWIZSHEET) ppsp->lParam;   // Set up pointer to my data

			switch(((NMHDR FAR *)lParam)->code)
			{
//				case PSN_APPLY:
//				break;

				case PSN_RESET:
					ftsFlags.dwWizStat = FALSE;
				break;

				case PSN_WIZFINISH:
					lpWizSheet->wd->flgs.dwWizStat = TRUE;
					if (lpWizSheet->wd->bExpress == IDC_WIZ_MIN_SIZE)
					{
						ftsFlags.fFphrase = FALSE;
						ftsFlags.fPhraseFeedBack = FALSE;
						ftsFlags.fSimilarity = FALSE;
						goto Express;
					}
					else if (lpWizSheet->wd->bExpress == IDC_WIZ_MAXIMUM)
					{
						ftsFlags.fFphrase = TRUE;
						ftsFlags.fPhraseFeedBack = TRUE;
						ftsFlags.fSimilarity = TRUE;
Express:
						ftsFlags.fUntitled = FALSE;

						pSrchClass->pInclude = (int*) LhAlloc(LMEM_FIXED,
							(pTblFiles->CountStrings()/2 + 1) * sizeof(int));

						for (i = 0, index = 2; index <= pTblFiles->CountStrings(); index += 2)
						{
							BYTE type = pFileInfo[index / 2 - 1].filetype;
							if (type & (CHFLAG_MISSING | CHFLAG_LINK))
								continue;
							pSrchClass->pInclude[i++] = index;
						}
						pSrchClass->pInclude[i] = -1;
					}
					else
					{
						ftsFlags = lpWizSheet->wd->flgs;
					}
				break;

//				case PSN_KILLACTIVE:
//				break;

				case PSN_WIZBACK:
					switch(lpWizSheet->nID)
					{
						case IDD_WIZ_FTS_INCLUDE_1LIST :
						case IDD_WIZ_FTS_INCLUDE :
							if (pSrchClass->pInclude)
								FreeLh(pSrchClass->pInclude);
							if (pSrchClass->pIgnore)
								FreeLh(pSrchClass->pIgnore);
							pSrchClass->pInclude = NULL;
							pSrchClass->pIgnore   = NULL;
						break;

						case IDD_WIZ_FTS_END :
							// Okay the logic here is a little weird but it goes like this...
							// If the express bit is set then we came from the opening page
							// else if the feedback bit is set we know we displayed the similar page
							// else if the phrase is set we know we displayed the matching page
							// otherwise we displayed the phrase page before going to the final page
							if (lpWizSheet->wd->bExpress)
								SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_WIZ_FTS_EXPRESS);
							else if (lpWizSheet->wd->flgs.fPhraseFeedBack)
								SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_WIZ_FTS_SIMILARITY);
							else if (lpWizSheet->wd->flgs.fFphrase)
								SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_WIZ_FTS_MATCHING);
							else
								SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_WIZ_FTS_PHRASE);
						break;
					}
				break;

				case PSN_WIZNEXT:
					switch(lpWizSheet->nID)
					{
						case IDD_WIZ_FTS_EXPRESS:
							if (IsDlgButtonChecked(hwndDlg, IDC_WIZ_MIN_SIZE))
								lpWizSheet->wd->bExpress = IDC_WIZ_MIN_SIZE;
							else if (IsDlgButtonChecked(hwndDlg, IDC_WIZ_MAXIMUM))
								lpWizSheet->wd->bExpress = IDC_WIZ_MAXIMUM;
							else
								lpWizSheet->wd->bExpress = 0;
							if (lpWizSheet->wd->bExpress)
								SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_WIZ_FTS_END);
							break;

						case IDD_WIZ_FTS_INCLUDE_1LIST :
						{
							// First remove any old list if they exist...
							if (pSrchClass->pInclude)
								FreeLh(pSrchClass->pInclude);
							pSrchClass->pInclude = NULL;
							ASSERT(!pSrchClass->pInclude);
							if (pSrchClass->pIgnore)
								FreeLh(pSrchClass->pIgnore);
							pSrchClass->pIgnore   = NULL;
							ASSERT(!pSrchClass->pIgnore);

							hwndIncludeList = GetDlgItem(hwndDlg, IDC_INCLUDE_1LIST);
							int iLBContent = SendMessage(hwndIncludeList, LB_GETCOUNT, 0, 0);  // Get total in LB

							if (iLBContent > 0)  // Are there any entries in the listbox?
							{
								// Get some memory so we can tell the selected and unselected entries apart
								BOOL *pLBContent = (BOOL*) lcCalloc((iLBContent) * sizeof(BOOL));

								pos = SendMessage(hwndIncludeList, LB_GETSELCOUNT, 0, 0); // How many are selected?
								if (pos > 0)
								{

									int *pSelected = (int*) LhAlloc(LMEM_FIXED,(pos + 1) * sizeof(int));
									int *pMem = pSelected;

									// Start by pulling off the selected items
									SendMessage(hwndIncludeList,LB_GETSELITEMS,(WPARAM)pos,(LPARAM) pSelected);


									pSrchClass->pInclude = (int*) LhAlloc(LMEM_FIXED,(pos + 1) * sizeof(int));
									for (i = 0; i < pos; i++,pSelected++) {

										pSrchClass->pInclude[i] = SendMessage(hwndIncludeList,LB_GETITEMDATA, *pSelected, 0);
										*(pLBContent + *pSelected) = 1;  // Flag the item as selected
									}
									pSrchClass->pInclude[i] = -1;
									FreeLh(pMem);
								}
								if (iLBContent - pos > 0) { 			 // Are there any not selected?
									pSrchClass->pIgnore = (int*) LhAlloc(LMEM_FIXED,
										((iLBContent-pos) + 1) * sizeof(int));
									int j;
									// Stick them in their own list...
									for (i = 0,j = 0; (i < iLBContent) && (j < iLBContent-pos); i++) {
										if (*(pLBContent + i) == 0) // Found an unselected entry?
										{
											pSrchClass->pIgnore[j] = SendMessage(hwndIncludeList,
												LB_GETITEMDATA, i, 0);
											j++;
										}
									}
									pSrchClass->pIgnore[j] = -1;
								}
								FreeLh(pLBContent);
							}
						}
						break;

						case IDD_WIZ_FTS_INCLUDE :
							hwndIncludeList = GetDlgItem(hwndDlg, IDC_INCLUDE);
							hwndIgnoreList = GetDlgItem(hwndDlg, IDC_EXCLUDE);

							pos = SendMessage(hwndIncludeList, LB_GETCOUNT, 0, 0);
							if (pos > 0) {
								if (pSrchClass->pInclude)
									FreeLh(pSrchClass->pInclude);
								pSrchClass->pInclude = NULL;
								ASSERT(!pSrchClass->pInclude);
								pSrchClass->pInclude = (int*) LhAlloc(LMEM_FIXED,
									(pos + 1) * sizeof(int));
								for (i = 0; i < pos; i++) {
									pSrchClass->pInclude[i] = SendMessage(hwndIncludeList,
										LB_GETITEMDATA, i, 0);
								}
								pSrchClass->pInclude[i] = -1;
							}

							pos = SendMessage(hwndIgnoreList, LB_GETCOUNT, 0, 0);
							if (pos > 0) {
								if (pSrchClass->pIgnore)
									FreeLh(pSrchClass->pIgnore);
								pSrchClass->pIgnore   = NULL;
								ASSERT(!pSrchClass->pIgnore);
								pSrchClass->pIgnore = (int*) LhAlloc(LMEM_FIXED,
									(pos + 1) * sizeof(int));
								for (i = 0; i < pos; i++) {
									pSrchClass->pIgnore[i] = SendMessage(hwndIgnoreList,
										LB_GETITEMDATA, i, 0);
								}
								pSrchClass->pIgnore[i] = -1;
							}
						break;

						case IDD_WIZ_FTS_SIMILARITY :
							lpWizSheet->wd->flgs.fSimilarity = IsDlgButtonChecked(hwndDlg,IDC_SIMILARITY_YES);
						break;
						case IDD_WIZ_FTS_UNTITLED :
							lpWizSheet->wd->flgs.fUntitled = IsDlgButtonChecked(hwndDlg,IDC_INCLUDE_UNTITLED);
						break;
						case IDD_WIZ_FTS_PHRASE :
							lpWizSheet->wd->flgs.fFphrase = IsDlgButtonChecked(hwndDlg,IDC_PHRASE_YES);
							if (!lpWizSheet->wd->flgs.fFphrase)
							{
								SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_WIZ_FTS_END);
								lpWizSheet->wd->flgs.fPhraseFeedBack = FALSE;
								lpWizSheet->wd->flgs.fSimilarity = FALSE;
							}
						break;
						case IDD_WIZ_FTS_MATCHING :
							lpWizSheet->wd->flgs.fPhraseFeedBack = IsDlgButtonChecked(hwndDlg,IDC_DISPLAY_MATCHING);
							if (!lpWizSheet->wd->flgs.fPhraseFeedBack)
							{
								SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_WIZ_FTS_END);
								lpWizSheet->wd->flgs.fSimilarity = FALSE;
							}
						break;
					} // end of switch statement
					break;


				case PSN_SETACTIVE:
				{
					DWORD dwFlags = 0;

					switch(lpWizSheet->nID)
					{
						case IDD_WIZ_FTS_EXPRESS: // Page 0
							dwFlags = PSWIZB_NEXT;
							break;
						case IDD_WIZ_FTS_END :
							dwFlags = PSWIZB_FINISH | PSWIZB_BACK;
							break;
						case IDD_WIZ_FTS_INCLUDE :
						case IDD_WIZ_FTS_INCLUDE_1LIST :
						case IDD_WIZ_FTS_UNTITLED :
						case IDD_WIZ_FTS_PHRASE :
						case IDD_WIZ_FTS_MATCHING :
						case IDD_WIZ_FTS_SIMILARITY :
							dwFlags = PSWIZB_NEXT| PSWIZB_BACK;
							break;
						default :
							dwFlags = PSWIZB_NEXT| PSWIZB_BACK | PSWIZB_FINISH;
					}
					PropSheet_SetWizButtons(GetParent(hwndDlg),dwFlags);
				}
				break;
			}
			break;
		default :
			return FALSE;
	 }
	 return TRUE;
}

/***************************************************************************

	FUNCTION:	OnFtsMove

	PURPOSE:	Move all selected items from one listbox to another

	PARAMETERS:
		hwndDlg
		fAdd	-- TRUE if Adding

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		07-Aug-1994 [ralphw]

***************************************************************************/

static void STDCALL OnFtsMove(HWND hwndDlg, BOOL fAdd)
{
	HWND hCtrlTo   = GetDlgItem(hwndDlg, (fAdd ? IDC_INCLUDE : IDC_EXCLUDE));
	HWND hCtrlFrom = GetDlgItem(hwndDlg, (fAdd ? IDC_EXCLUDE : IDC_INCLUDE));
	int  cMove	   = SendMessage(hCtrlFrom, LB_GETSELCOUNT, 0, 0);
	int* pItems;
	char szBuf[256];

	if (cMove <= 0)
		return;

	// Prevent the list boxes from redrawing

	SendMessage(hCtrlTo, WM_SETREDRAW, FALSE, 0L);
	SendMessage(hCtrlFrom, WM_SETREDRAW, FALSE, 0L);

	pItems = (int*) LhAlloc(LMEM_FIXED, cMove * sizeof(int));
	SendMessage(hCtrlFrom, LB_GETSELITEMS, cMove, (LPARAM) pItems);
	for (int iAdjust = 0; iAdjust < cMove; iAdjust++) {
		SendMessage(hCtrlFrom, LB_GETTEXT, pItems[iAdjust] - iAdjust,
			(LPARAM) szBuf);
		int data = SendMessage(hCtrlFrom, LB_GETITEMDATA, pItems[iAdjust] - iAdjust, 0);
		int pos = SendMessage(hCtrlTo, LB_ADDSTRING, 0, (LPARAM) szBuf);
		SendMessage(hCtrlTo, LB_SETITEMDATA, pos, data);
		SendMessage(hCtrlFrom, LB_DELETESTRING, pItems[iAdjust] - iAdjust, 0);
	}

	// Enable list box redrawing

	SendMessage(hCtrlTo, WM_SETREDRAW, TRUE, 0L);
	SendMessage(hCtrlFrom, WM_SETREDRAW, TRUE, 0L);

	FreeLh(pItems);

	EnableWindow(GetDlgItem(hwndDlg, IDC_ADD_FILE),
		SendMessage(GetDlgItem(hwndDlg, IDC_FILES_TO_INDEX), LB_GETSELCOUNT, 0, 0));
	EnableWindow(GetDlgItem(hwndDlg, IDC_REMOVE_FILE),
		SendMessage(GetDlgItem(hwndDlg, IDC_FILES_TO_IGNORE), LB_GETSELCOUNT, 0, 0));
}

//
//	Adds a page to a property sheet.
//
static void STDCALL AddPage(LPPROPSHEETHEADER ppsh, PROPSHEETPAGE *ppsp, UINT id, DLGPROC pfn, LPARAM lpwd)
{
	if (ppsh->nPages < MAX_PAGES) {

		ppsp->dwSize = sizeof(PROPSHEETPAGE);
		ppsp->dwFlags = PSP_DEFAULT;
		ppsp->hInstance = hInsNow;
		ppsp->pszTemplate = MAKEINTRESOURCE(id);
		ppsp->pfnDlgProc = pfn;
		ppsp->lParam = (LPARAM)lpwd;	// pointer to both per sheet and all sheets data

		ppsh->phpage[ppsh->nPages] = pCreatePropertySheetPage(ppsp);
		if (ppsh->phpage[ppsh->nPages])
			ppsh->nPages++;
	}
}  // AddPage

/***************************************************************************

	FUNCTION:	AskAboutIndexes

	PURPOSE:	Ask the users which files should and should not be indexed

	PARAMETERS:
		hwndParent

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		07-Aug-1994 [ralphw]

***************************************************************************/

static BOOL STDCALL AskAboutIndexes(HWND hwndParent)
{
	if (!LoadShellApi())
		return FALSE;
	HPROPSHEETPAGE	rPages[MAX_PAGES];
	PROPSHEETPAGE	rpsp[MAX_PAGES];
	PROPSHEETHEADER psh;
	WIZDATA 		wd;
	WIZSHEET		ws[MAX_PAGES];

	if (hsrch) {
		RemoveHiliter(&hhiliter);

		pDeleteSearcher(hsrch);
		hsrch = 0;
	}

	psh.dwSize = sizeof(psh);
	psh.dwFlags = PSH_WIZARD;
	psh.hwndParent = hwndParent;
	psh.hInstance  = hInsNow;
	psh.pszCaption = NULL;
	psh.nPages = 0;
	psh.nStartPage = 0;
	psh.phpage = rPages;

	// initialize the wizard checks to true for now
	ftsFlags.fFphrase = TRUE;
	ftsFlags.fPhraseFeedBack = TRUE;
	ftsFlags.fSimilarity = TRUE;
	ftsFlags.fUntitled = FALSE;

	wd.dwPagesHit = 0;
	wd.hWizBMP = LoadBitmap(hInsNow,MAKEINTRESOURCE(IDC_WIZBMP));

	for (int i = 0;i < MAX_PAGES; i++)
		ws[i].wd = &wd;  // Point to common data

	ws[0].nID = IDD_WIZ_FTS_EXPRESS;
	AddPage(&psh, rpsp + 0,IDD_WIZ_FTS_EXPRESS,(DLGPROC) FtsWizardProc,(LPARAM)&ws[0]);
	ws[1].nID = IDD_WIZ_FTS_INCLUDE_1LIST;
	AddPage(&psh, rpsp + 1,IDD_WIZ_FTS_INCLUDE_1LIST,(DLGPROC) FtsWizardProc,(LPARAM)(LPWIZSHEET)&ws[1]);
	ws[2].nID = IDD_WIZ_FTS_UNTITLED;
	AddPage(&psh, rpsp + 2,IDD_WIZ_FTS_UNTITLED,(DLGPROC) FtsWizardProc,(LPARAM)(LPWIZSHEET)&ws[2]);
	ws[3].nID = IDD_WIZ_FTS_PHRASE;
	AddPage(&psh, rpsp + 3,IDD_WIZ_FTS_PHRASE,(DLGPROC) FtsWizardProc,(LPARAM)(LPWIZSHEET)&ws[3]);
	ws[4].nID = IDD_WIZ_FTS_MATCHING;
	AddPage(&psh, rpsp + 4,IDD_WIZ_FTS_MATCHING,(DLGPROC) FtsWizardProc,(LPARAM)(LPWIZSHEET)&ws[4]);
	ws[5].nID = IDD_WIZ_FTS_SIMILARITY;
	AddPage(&psh, rpsp + 5,IDD_WIZ_FTS_SIMILARITY,(DLGPROC) FtsWizardProc,(LPARAM)(LPWIZSHEET)&ws[5]);
	ws[6].nID = IDD_WIZ_FTS_END;
	AddPage(&psh, rpsp + 6,IDD_WIZ_FTS_END,(DLGPROC) FtsWizardProc,(LPARAM)(LPWIZSHEET)&ws[6]);

#ifdef _DEBUG
	if (pPropertySheet(&psh) < 0) {

		// First whine, then index everything using express settings

		int err = GetLastError();
		char szMsg[100];
		wsprintf(szMsg, "PropertySheet failed: %d", err);
		OkMsgBox(szMsg);
		ftsFlags.fFphrase = TRUE;
		ftsFlags.fPhraseFeedBack = TRUE;
		ftsFlags.fSimilarity = TRUE;
		ftsFlags.fUntitled = FALSE;

		pSrchClass->pInclude = (int*) LhAlloc(LMEM_FIXED,
			(pTblFiles->CountStrings()/2 + 1) * sizeof(int));

		int index;
		for (i = 0, index = 2; index <= pTblFiles->CountStrings(); index += 2)
		{
			BYTE type = pFileInfo[index / 2 - 1].filetype;
			if (type & (CHFLAG_MISSING | CHFLAG_LINK))
				continue;
			pSrchClass->pInclude[i++] = index;
		}
		pSrchClass->pInclude[i] = -1;
		ftsFlags.dwWizStat = TRUE;
	}
#else
	pPropertySheet(&psh);
#endif
	SafeDeleteObject(wd.hWizBMP); // delete the bitmap

	if (ftsFlags.dwWizStat)
	{
		int index, i;
		FM fm;
		char szName[MAX_PATH];

		UINT fdwOptions= TOPIC_SEARCH | WINHELP_INDEX | USE_VA_ADDR;

		if (ftsFlags.fFphrase)
			fdwOptions |= PHRASE_SEARCH;
		if (ftsFlags.fPhraseFeedBack)
			fdwOptions |= PHRASE_FEEDBACK;
		if (ftsFlags.fSimilarity)
			fdwOptions |= VECTOR_SEARCH;

		/*
		 * If any filenames have been changed to NOT have an index, then
		 * delete their existing (if any) .FTS file and mark them as being
		 * without an index.
		 */

		if (pSrchClass->pIgnore) {
			for (i = 0; (index = pSrchClass->pIgnore[i]) != -1; i++) {
#ifdef _DEBUG
				PSTR pszName = pTblFiles->GetPointer(index);
#endif
				BYTE type = pFileInfo[index / 2 - 1].filetype;
				if (type & (CHFLAG_MISSING | CHFLAG_LINK | CHFLAG_FTS_ASKED))
					continue; // already marked to ignore
				else {

					// If the .FTS file exists, delete it

					strcpy(szName, pTblFiles->GetPointer(index));
					ChangeExtension(szName, txtFtsExtension);
					fm = FmNewExistSzDir(szName,
						DIR_PATH | DIR_CUR_HELP | DIR_CURRENT | DIR_SILENT_INI | DIR_SILENT_REG);
					if (fm) {
						if (GetFileAttributes(szName) != (DWORD) -1)
							DeleteFile(szName);
						DisposeFm(fm);
					}

					pFileInfo[index / 2 - 1].filetype &= ~(CHFLAG_FTS_AVAIL);
					pFileInfo[index / 2 - 1].filetype |= CHFLAG_FTS_ASKED;
				}
			}
			if (pSrchClass->pIgnore) {
				FreeLh(pSrchClass->pIgnore);
				pSrchClass->pIgnore = NULL;
			}
		}

		/*
		 * Now generate the index for any files that the user has
		 * requested an index for, but for which we don't have an index.
		 */

		if (pSrchClass->pInclude) {
			if (!hwndAnimate && !StartAnimation(sidCreatingFTS)) {
				Error(wERRS_OOM, wERRA_RETURN);
				return FALSE;
			}
			else if (hwndAnimate)
				SetWindowText(hwndAnimate, GetStringResource(sidCreatingFTS));

			for (i = 0; (index = pSrchClass->pInclude[i]) != -1; i++) {
#ifdef _DEBUG
				PSTR pszName = pTblFiles->GetPointer(index);
#endif
				BYTE type = pFileInfo[index / 2 - 1].filetype;
				if (type & (CHFLAG_MISSING | CHFLAG_LINK))
					continue;
				pFileInfo[index / 2 - 1].filetype &= ~(CHFLAG_FTS_ASKED);

				if (!(pFileInfo[index / 2 - 1].filetype & CHFLAG_BAD_RO_FTS))
				{
					strcpy(szName, pTblFiles->GetPointer(index));
					ChangeExtension(szName, txtFtsExtension);
					FM fm = FmNewExistSzDir(szName,
						DIR_PATH | DIR_CUR_HELP | DIR_CURRENT | DIR_SILENT_INI | DIR_SILENT_REG);

					if (fm) { // does index already exist?
                        // define vars

                        int idIndex;
                        DWORD dtime1, time2;
                        BOOL bInvalidTime;
                        
                        // set up for time stamp check
                        dtime1 = pFileInfo[index / 2 - 1].timestamp;
                        time2 = 0;
                        idIndex= pOpenIndex(hsrch,fm, NULL, NULL,
                                &dtime1, &time2);

                        if (idIndex < 0)
                            bInvalidTime = TRUE;
                        else
                            bInvalidTime = FALSE;

                        if (!pIsValidIndex(fm, fdwOptions) || bInvalidTime) {
						    if (!DeleteFile(fm)) {

							    // BUGBUG: Can't delete the file, so we can't change it
							    // what do we do now?
    
							    pFileInfo[index / 2 - 1].filetype |= CHFLAG_FTS_ASKED;
							    continue;
						    }
                        }
                        else {
                            pFileInfo[index / 2 - 1].filetype |= CHFLAG_FTS_AVAIL;
                            RemoveFM(&fm);
                            continue;
                        }
					}
				}

				strcpy(szName, pTblFiles->GetPointer(index));
				fm = FmNewExistSzDir(szName,
					DIR_PATH | DIR_CUR_HELP | DIR_CURRENT | DIR_SILENT_INI | DIR_SILENT_REG);
				if (!fm) {
					pFileInfo[index / 2 - 1].filetype &= ~(CHFLAG_FTS_AVAIL);
					pFileInfo[index / 2 - 1].filetype |= CHFLAG_MISSING;
					continue;
				}

				FM fmCopy = FmCopyFm(fm);
				HDE hde = HdeCreate(&fmCopy, NULL, deTopic);
				int iResult = 0;

				if (hde) {
					ASSERT(fm);

					iResult= GenerateIndex(hde, fm, fdwOptions);

					if (!iResult)
						pFileInfo[index / 2 - 1].filetype |= CHFLAG_FTS_AVAIL;

					DestroyHde(hde);
				}

				RemoveFM(&fmCopy);
				RemoveFM(&fm);

				if (iResult == OUT_OF_MEMORY || iResult == OUT_OF_DISK)
				{
					if (pSrchClass->pInclude)
					{
						FreeLh(pSrchClass->pInclude);
						pSrchClass->pInclude = NULL;
					}

					StopAnimation();

					return FALSE;
				}
			}

			if (pSrchClass->pInclude) {
				FreeLh(pSrchClass->pInclude);
				pSrchClass->pInclude = NULL;
			}
			StopAnimation();
		}
		return TRUE;
	}
	return FALSE;
}

extern "C" void STDCALL InitializeCntTest(DWORD test)
{
	if (!hfsGid)
		return;

	HBT hbtCntJump = HbtOpenBtreeSz(txtCntJump,
		hfsGid, fFSOpenReadOnly);
	if (!hbtCntJump)
		return;

	RcCloseBtreeHbt(hbtCntJump);

	fSequence = test;
	dwSequence = 0;
	PostMessage(ahwnd[iCurWindow].hwndParent, MSG_NEXT_TOPIC, 0, 0);
}

extern "C" BOOL STDCALL NextCntTopic(void)
{
	RC rc;
	PSTR psz;

	HBT hbtCntJump = HbtOpenBtreeSz(txtCntJump,
		hfsGid, fFSOpenReadOnly);
	if (!hbtCntJump)
		return FALSE;

	while (dwSequence <= (DWORD) cntFlags.cCntItems &&
			GETIMAGE_TYPE(pbTree[dwSequence]) != IMAGE_TOPIC)
		dwSequence++;

	// Note that we deliberately let this fail when dwSequence > cntFlags.cCntItems

	rc = RcLookupByKey(hbtCntJump, (KEY) (LPVOID) &dwSequence,
		NULL, szSavedContext);

	RcCloseBtreeHbt(hbtCntJump);

	if (rc != rcSuccess) {
		return FALSE;
	}

	dwSequence++;

	if (szSavedContext[0] != chMACRO) {
		ParseContentsString(szSavedContext);
		psz = StrChrDBCS(szSavedContext, FILESEPARATOR);
		if (psz)
			*psz++ = '\0';

		FJumpId((psz ? psz : ""), szSavedContext);
	}
	return TRUE;
}

CTable* ptbl16DllList;

extern "C" BOOL STDCALL AddTo16DllList(FM fm)
{
	char szFile[MAX_PATH];

	if (!ptbl16DllList)
		ptbl16DllList = new CTable;
	GetFmParts(fm, szFile, PARTBASE);

	if (!ptbl16DllList->IsStringInTable(szFile))
		return ptbl16DllList->AddString(szFile);
	else
		return TRUE;
}

extern "C" BOOL STDCALL Is16Dll(LPCSTR pszFile)
{
	char szFile[MAX_PATH];
	GetFmParts((FM) pszFile, szFile, PARTBASE);

	if (ptbl16DllList && ptbl16DllList->IsStringInTable(szFile))
		return TRUE;
	else
		return FALSE;
}

static void STDCALL SetCntTabText(HWND hwndDlg, BOOL fOpen)
{
	char szMsg[256];
	CStr cszButton(fOpen ? sidCntOpen : sidCntDisplay);
	CStr cszBook(fOpen ? sidBook : sidTopic);
	wsprintf(szMsg, GetStringResource(sidCntInstruction),
		cszBook.psz, cszButton.psz);
	SetWindowText(GetDlgItem(hwndDlg, IDC_CNT_INSTRUCTIONS),
		szMsg);
}

static void STDCALL PageChange(HWND hwndTab)
{
	TAB_ID idCurTab = aTabIds[TabCtrl_GetCurSel(hwndTab)];
	HWND hwndCurFocus;

	if (idCurTab != cntFlags.idOldTab) {
		CWaitCursor cursor;
		hwndCurFocus = GetFocus();
		if (hwndTabSub == hwndFindTab)
			SendMessage(hwndTabSub, WM_CLOSE, 0, 0);
		else {
			ASSERT(IsValidWindow(hwndTabSub));
			DestroyWindow(hwndTabSub);
		}
		SetWindowText(GetDlgItem(GetParent(hwndTab), IDOK),
			GetStringResource(sidDisplay));
		hwndTabSub = CreateTabChild(idCurTab, GetParent(hwndTab));

		if (!hwndTabSub) {
			hwndTabSub = CreateTabChild(aTabIds[0], GetParent(hwndTab));
			TabCtrl_SetCurSel(hwndTab, 0);
			cntFlags.idOldTab = aTabIds[0];
		}
		else
			cntFlags.idOldTab = idCurTab;
		//	If the focus was on the tab control, keep the focus there.
		SetFocus((hwndCurFocus == hwndTab) ? hwndTab : hwndTabSub);
		InvalidateRect(hwndTabSub, NULL, TRUE);
	}
}

/***************************************************************************

	FUNCTION:	TmpCleanup

	PURPOSE:	Cleanup any temporary files left around after a <gasp> crash

	PARAMETERS:
		void

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		02-Oct-1994 [ralphw]

***************************************************************************/

extern "C" const char txtTmpPrefix[]; // "~wh";

extern "C" void STDCALL TmpCleanup(void)
{
	char szTmpName[MAX_PATH];

	if (!hfsGid)
		return; // don't do this if we're creating a .gid file

	for (int i = 0; i < 2; i++) {
		if (i == 0)
			GetTempPath(sizeof(szTmpName), szTmpName);
		else {
			GetWindowsDirectory(szTmpName, MAX_PATH);
			AddTrailingBackslash(szTmpName);
		}

		{
			CStr cszPath(szTmpName);

			// BUGBUG: temporary hack for full-text search

			strcat(szTmpName, (i == 0 ? txtTmpPrefix : "~ft"));
			strcat(szTmpName, "*.tmp");

			WIN32_FIND_DATA fileinfo;
			HANDLE hfind = FindFirstFile(szTmpName, &fileinfo);

			if (hfind != INVALID_HANDLE_VALUE) {
				do {
					strcpy(szTmpName, cszPath.psz);
					strcat(szTmpName, fileinfo.cFileName);
					DeleteFile(szTmpName);
				} while (FindNextFile(hfind, &fileinfo));
				FindClose(hfind);
			}
		}
	}
}

/***************************************************************************

	FUNCTION:	IsSearchAvailable

	PURPOSE:	Determine if ftssrch.dll is available

	PARAMETERS:
		void

	RETURNS:	TRUE if the dll is found, else FALSE

	COMMENTS:
		Does not determine if the dll can actually be loaded.

	MODIFICATION DATES:
		11-Oct-1994 [ralphw]

***************************************************************************/

BOOL STDCALL IsSearchAvailable(void)
{
	static BOOL fSearchAvailable = -1;

	// Have we already queried?

	if (fSearchAvailable == TRUE)
		return TRUE;
	else if (fSearchAvailable == FALSE)
		return FALSE;

	FM fm = FmNewExistSzDir(GetStringResource(sidFtsDll),
		DIR_PATH | DIR_CURRENT);
	fSearchAvailable = (fm ? TRUE : FALSE);
	if (fm)
		FreeLh((HGLOBAL) fm);
	return fSearchAvailable;
}

static void STDCALL AddTab(TC_ITEM* pti, TAB_ID tab, PSTR pszName, HWND hwndTabs)
{
	aTabIds[pSrchClass->cTabs] = tab;
	pti->pszText = pszName;
	TabCtrl_InsertItem(hwndTabs, pSrchClass->cTabs, pti);
	pSrchClass->cTabs++;
}

/***************************************************************************

	FUNCTION:	GetTabPosition

	PURPOSE:	Given a Tab Id, return the tab position

	PARAMETERS:
		id

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		11-Oct-1994 [ralphw]

***************************************************************************/

extern "C" int STDCALL GetTabPosition(int id)
{
	for (int i = 0; i < MAX_IDTABS; i++) {
		if (aTabIds[i] == id)
			return i;
	}
	return 0;
}

CEnable::CEnable(HWND hwnd)
{
	hwndEnable = hwnd;
	if (IsValidWindow(hwnd))
		EnableWindow(hwnd, FALSE);
}

#ifdef _DEBUG
CEnable::~CEnable()
{
	if (hwndEnable)
		EnableWindow(hwndEnable, TRUE);
}
#endif

#ifdef _PRIVATE

CTimeReport::CTimeReport(PCSTR pszMessage)
{
	pszMsg = lcStrDup(pszMessage ? pszMessage : "Elapsed time:");

	oldTickCount = GetTickCount();
}

CTimeReport::~CTimeReport()
{
	DWORD dwActualTime = (GetTickCount() - oldTickCount);
	DWORD dwFinalTime = dwActualTime / 1000;

	int minutes = (dwFinalTime / 60);
	int seconds = (dwFinalTime - (minutes * 60L));
	int tenths = (dwActualTime - (dwFinalTime * 1000)) / 100;
	const PSTR szPlural = "s";
	char szParentString[256];
	wsprintf(szParentString, "%s %s minute%s, %d.%d second%s\r\n",
		pszMsg,
		FormatNumber(minutes), ((minutes == 1) ? "" : szPlural),
		seconds, tenths, ((seconds == 1) ? "" : szPlural));
	lcFree(pszMsg);
	SendStringToParent(szParentString);
}

// The following two functions give .c modules access to the above class

static CTimeReport* pTimeReport;

extern "C" BOOL STDCALL CreateTimeReport(PCSTR pszMessage)
{
	if (!pTimeReport) {
		pTimeReport = new CTimeReport(pszMessage);
		return TRUE;
	}
	else
		return FALSE; // can only have on C-invoked time report class
}

extern "C" void STDCALL EndTimeReport(void)
{
	if (pTimeReport) {
		delete pTimeReport;
		pTimeReport = NULL;
	}
};

#endif // _PRIVATE

#ifdef _DEBUG
void STDCALL ShowCntFlags()
{
	char szMsg[512];

	if (cntFlags.flags & GID_CONTENTS)
		SendStringToParent("GID_CONTENTS\r\n");
	if (cntFlags.flags & GID_INDEX)
		SendStringToParent("GID_INDEX\r\n");
	if (cntFlags.flags & GID_GINDEX)
		SendStringToParent("GID_GINDEX\r\n");
	if (cntFlags.flags & GID_FTS)
		SendStringToParent("GID_FTS\r\n");
	if (cntFlags.flags & GID_NO_INDEX)
		SendStringToParent("GID_NO_INDEX\r\n");
	if (cntFlags.flags & GID_NO_CNT)
		SendStringToParent("GID_NO_CNT\r\n");

	wsprintf(szMsg, "%u items\r\n%u tabs\r\nLast tab is %u\r\n",
		cntFlags.cCntItems, cntFlags.cTabs, cntFlags.idOldTab);
	SendStringToParent(szMsg);
}
#endif