/* Copyright (c) 2017, Computer History Museum 
All rights reserved. 
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to 
the limitations in the disclaimer below) provided that the following conditions are met: 
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
   disclaimer in the documentation and/or other materials provided with the distribution. 
 * Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products 
   derived from this software without specific prior written permission. 
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE 
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE. */

#ifdef NEWFIND
#include "searchwin.h"
#include "regexp.h"
#define FILE_NUM 114
#pragma segment Find
/* Copyright (c) 1998 by QUALCOMM Incorporated */

//	constants
enum
{
	kMaxCriteria = 16,		// should be enough
	kWinHeaderHt = 99,		// height of window header excluding criteria
	kCriteriaHt = 27,		// height of 1 criteria line
	kMBHdrHt = 18,			// height of mailbox header control
	kFrontWinTicks = 60,	// max ticks allowed when search window is in front, hog CPU time but exit for any events
	kBgdWinTicks = 10,		// max ticks allowed when search window is not in front
	kInBGTicks = 4,			// max ticks allowed when Eudora is in background
	kChaseArrowTicks = 15,	// call idle function for chase arrows 4 times a second
	
	kDateWi = 100,			//	Date control width
	kDateHt = 22,			//	Date control height (must be 20)

	kIOBufSize = 32 K,		//	Size of bulk search I/O buffers
	kSpecListType = 'Spec'
};

// criteria category menu items
enum { scAnywhere=1,scHeaders,scBody,scAttachNames,scDiv1,
	scSummary,scStatus,scJunkScore,scPriority,scAttachCount,scLabel,scDate,scSize,scAge,
	scPersonality,
	scDiv2,scTo,scFrom,scSubject,scCC,scBCC,scAnyRecipient,scLimit };

// We save the criteria differently from how they appear in the menus.
// Map saved categories to the ones we use in the menu
char SavedCategory2MenuCategory[] = 
	{0,1,2,3,4,5,6,7,9,10,11,12,13,14,15,16,17,18,19,20,21,22,8};

#define SafeSavedCategory2MenuCategory(x) ((x)<scLimit ? SavedCategory2MenuCategory[x]:1)

// Map the categories in the menu to the ones we save
char MenuCategory2SavedCategory[] = 
	{0,1,2,3,4,5,6,7,22,8,9,10,11,12,13,14,15,16,17,18,19,20,21};

enum { ssText=1,ssDate,ssAge,ssNumber };
enum { sauMinutes=1,sauHours,sauDays,sauWeeks,sauMonths,sauYears };
enum { matchAll=1,matchAny };
enum { tabMailboxes=1,tabResults };
static short MenuToDateField[] = { minuteField,hourField,dayField,dayField,monthField,yearField };
	  
// criteria relation menu items
enum { srIs=1,srIsNot,srGreater,srLess };
enum { strContains=1,strContainsWord,strDoesNotContain,strIs,strIsNot,strStarts,strEnds,strRegExp };

// category table indicates relation and specifier
enum
{
	kNeedsSum=1<<0,
	kNeedsMsg=1<<1,	
};

enum
{
	kSearchingLocal=1<<0,
	kSearchingIMAP=1<<1
};

// Auditable Search Window controls
enum
{
	kctlSearch=0,
	kctlMore,
	kctlFewer,
	kctlTabs,
	kctlSrchResults,
	kctlCriteria,
	kctlMatch
};

struct
{
	short		relation;
	short		specifier;
	short		flags;
	Boolean	proOnly;
} gCategoryTable[] = 
	{
		SRCH_TEXT_COMPARE_MENU,ssText,kNeedsMsg,false,		// anywhere
		SRCH_TEXT_COMPARE_MENU,ssText,kNeedsMsg,true,			// headers
		SRCH_TEXT_COMPARE_MENU,ssText,kNeedsMsg,true,			// body
		SRCH_TEXT_COMPARE_MENU,ssText,kNeedsMsg,true,			// attachment names
		0,0,0,0,										// --------------------	
		SRCH_TEXT_COMPARE_MENU,ssText,kNeedsSum,false,		// summary
		SRCH_EQUAL_MENU,FILT_STATUS_MENU,kNeedsSum,true,	// status
		SRCH_COMPARE_MENU,ssNumber,kNeedsSum,true,				// junk score
		SRCH_COMPARE_MENU,FILT_PRIOR_MENU,kNeedsSum,true,	// priority
		SRCH_COMPARE_MENU,ssNumber,kNeedsSum,true,				// attachment count
		SRCH_EQUAL_MENU,LABEL_HIER_MENU,kNeedsSum,true,		// label
		SRCH_DATE_COMPARE_MENU,ssDate,kNeedsSum,true,			// date
		SRCH_COMPARE_MENU,ssNumber,kNeedsSum,true,				// size
		SRCH_COMPARE_MENU,ssAge,kNeedsSum,true,						// age
		SRCH_EQUAL_MENU,PERS_HIER_MENU,kNeedsSum,true,		// personality
		0,0,0,0,										// --------------------
		SRCH_TEXT_COMPARE_MENU,ssText,kNeedsMsg,true,			// to
		SRCH_TEXT_COMPARE_MENU,ssText,kNeedsMsg,true,			// from
		SRCH_TEXT_COMPARE_MENU,ssText,kNeedsMsg,true,			// subject
		SRCH_TEXT_COMPARE_MENU,ssText,kNeedsMsg,true,			// Cc
		SRCH_TEXT_COMPARE_MENU,ssText,kNeedsMsg,true,			// Bcc
		SRCH_TEXT_COMPARE_MENU,ssText,kNeedsMsg,true			// any recipient
	};

//	structs
typedef struct
{
	short box;
	short lastBoxSpec;
	long message;
	TOCHandle tocH; 		// current toc
	Boolean opened;			// we opened the TOC (we need to close it).
} SearchMarker;

typedef struct
{
	ParmBlkPtr pb;
	long offset;
	long size;
	Boolean free;
	Boolean pending;
	Handle buffer;
	long bSize;
}	BulkSearchBuffer;

typedef struct
{
	Boolean	didBulkSearch;
	short refN;
	long spot, len;
	BulkSearchBuffer buf[2];
	long fullOffset;
	short	bulkSearchCriteriaIdx;	// which criterion are we currently searching
	Byte	bulkSearchCriteria[kMaxCriteria];	// which criteria need bulk searching
	short	nBulkSearchCriteria;	// how many criteria need bulk searching
} BulkSearchInfo;

typedef struct
{
	short		day;
	short		month;
	short		year;
} SearchDate;

typedef struct
{
	ControlHandle	ctlCategory;
	ControlHandle	ctlRelation;
	ControlHandle	ctlSpecifier;
	ControlHandle	ctlPete;			// for drawing pte
	PETEHandle 		pte;				// te box for the string we're looking for
	StringHandle	searchString;		/* the string we're finding */
	StringHandle	utf8SearchString;	// ditto, but utf8-ified
	RegexpHandle	regexp;				// regular expression we are finding
	Accumulator		hits;				// all the hits in the current mailbox
	short			category;
	short			relation;
	long			specifier;
	long			misc;
	SearchDate		date;
	long			zoneSecs;
	Boolean			useSenderZone;
	Boolean fudge8bit;
} CriteriaInfo, *CriteriaPtr, **CriteriaHandle;

typedef struct
{
	short	criteriaCount;				// number of criteria visible
	CriteriaHandle	hCriteria;			// criteria controls
	SearchMarker curr;					/* place where searching is occurring */
	ControlHandle	ctlSearch,ctlChaseArrows,ctlCriteriaGroup;
	ControlHandle	ctlMore,ctlFewer;
	ControlHandle	ctlMailBox,ctlTabs;
	ControlHandle	ctlMatch,ctlSrchResults;
	ControlHandle	ctlFocus;			// control with focus
	short		searchMode;				// actively searching now?
//	Boolean		bulk;					// do bulk search?
	Boolean		mailboxView;
	Boolean		bulkSearching;			// bulk searching a mailbox file?
	Boolean		matchAny;
	Boolean		searchResults;
	Boolean		didSearch;
	Boolean		noBulkSearch;
	short		saveBotMargin;
	short 		savePreviewHi;
	short		criteriaFlags;
	ViewListPtr	list;					// list for selecting mailboxes
	BoxCountHandle 	BoxCount;			// list of selected mailboxes
	BoxCountHandle	imapBoxCount;		// list of IMAP selected mailboxes for server search
	BoxCountHandle	redoBoxCount;		// list of mailboxes we need to search again because they were busy they first time
	uLong		redoTicks;				// try again when we get to this tick count
	short		**imapBoxSpecIdx;		// mailbox spec index+1
	Byte		searchOrder[kMaxCriteria];
	long		nHits;
	long		chaseArrowTicks;
	BulkSearchInfo	bulkData;
	ExpandInfo	expandList;
	FSSpec		saveSpec;
	Boolean setup;	// has the search been setup?
#ifdef VALPHA
	long			nSearched,nTicks;
#endif
} SearchVars, *SearchPtr, **SearchHandle;

typedef struct
{
	short			category;
	short			relation;
	long			specifier;
	long			misc;
	SearchDate		date;
	short			stringID;
} CriteriaSave;

typedef struct
{
	short	version;
	short	criteriaCount;
	short	matchMode;
	short	tabMode;
	long	unused[2];
	CriteriaSave	criteria[];
} SearchSaveInfo;

#define READY(b)	(!bulkData.buf[b].pending && !bulkData.buf[b].free)
#define kFoundIt ' fnd'

//	functions
static void SearchDidResize(MyWindowPtr win, Rect *oldContR);
static void SearchUpdate(MyWindowPtr win);
static Boolean SearchClose(MyWindowPtr win);
static void SearchClick(MyWindowPtr win,EventRecord *event);
static void SearchCursor(Point mouse);
static void SearchActivate(MyWindowPtr win);
static Boolean SearchKey(MyWindowPtr win, EventRecord *event);
static Boolean SearchApp1(MyWindowPtr win, EventRecord *event);
static Boolean DoSearchKey(MyWindowPtr win, EventRecord *event);
static void SearchHelp(MyWindowPtr win,Point mouse);
static Boolean SearchMenuSelect(MyWindowPtr win, int menu, int item, short modifiers);
static void SearchGrow(MyWindowPtr win,Point *newSize);
static void SearchIdle(MyWindowPtr win);
static Boolean SearchPosition(Boolean save,MyWindowPtr win);
static void GetSearchInfo(MyWindowPtr win,TOCHandle *ptoc,SearchHandle *psh);
static void StopSearch(MyWindowPtr win);
static void SearchButton(MyWindowPtr win,ControlHandle button,long modifiers,short part);
static Boolean MessPreFind(SearchHandle sh,StringPtr s,short criteriaIdx);
static Boolean FindInHits(SearchHandle sh,AccuPtr a);
static void ZapSpecList(TOCHandle toc);
static Boolean SearchHasSelection(MyWindowPtr win);
static Boolean SearchScroll(MyWindowPtr win,short h,short v);
static OSErr SearchBulk(FSSpecPtr spec,SearchHandle sh);
static long SearchLVCallBack(ViewListPtr pView, VLCallbackMessage message, long data);
static ViewListPtr InitMailboxList(MyWindowPtr win,Boolean useMBSelection);
static void SizeMBList(MyWindowPtr win,SearchHandle sh);
static TOCHandle GetBoxTOC(SearchHandle sh,SearchMarker *which);
static TOCHandle GetSpecTOC(FSSpecPtr spec,Boolean *opened);
static void FinishedTOC(SearchMarker *which);
static OSErr GetSelectedBoxes(SearchPtr sp,TOCHandle tocH,Boolean bStartIMAPSearch);
static void AddCriteria(MyWindowPtr win,SearchHandle sh,Boolean defaultString);
static void RemoveCriteria(MyWindowPtr win,SearchHandle sh);
static Boolean ClickCriteriaButton(MyWindowPtr win,SearchHandle sh,ControlHandle cntl,short part);
static void DisposeSpecifier(MyWindowPtr win,SearchHandle sh,CriteriaInfo *pCriteria);
static void CreateTextSpecifier(MyWindowPtr win,CriteriaInfo *pCriteria,Boolean number);
static Boolean SetupSearchCriteria(MyWindowPtr win,SearchPtr sp);
static Boolean SearchMessage(MyWindowPtr win,MSumPtr sumP,SearchHandle sh,SearchMarker *curr,Boolean *stop,Boolean sumOnly);
static Boolean SearchText(StringPtr value,UPtr pText,long len,long offset,short relation);
static Boolean SearchString(StringPtr value,StringPtr s,short relation);
static Boolean SearchHeader(short header,Handle text,long size,StringPtr searchString,short relation);
static short ShortCompare(short value1,short value2);
static void SetSearchTopMargin(MyWindowPtr win,SearchHandle sh);
static Boolean MustSearchBody(short category);
static void InvalidBoxSize(MyWindowPtr win);
static void CheckChaseArrows(SearchHandle sh);
static void AutoSearch(MyWindowPtr srchWin, MyWindowPtr	win, short searchMode);
static void UpdateBoxSize(MyWindowPtr win);
static void ProcessSearch(MyWindowPtr win,TOCHandle toc,SearchHandle sh);
static void DoBulkSearch(SearchHandle sh);
static void StopBulkSearch(SearchHandle sh);
static void NormalizeSearchString(PStr s);
static Boolean SearchSetControls(MyWindowPtr win, SearchHandle sh);
static Boolean GetBoxCountSpec(SearchHandle sh, short boxNum, FSSpecPtr spec);
static Boolean GetBoxCountSpecLo(BoxCountHandle boxList, short boxNum, FSSpecPtr spec);
static void DisplayMBName(SearchHandle sh, PStr name);
static AccuPtr FindHits(SearchHandle sh, short idx);
static void GetHits(SearchHandle sh,short idx,AccuPtr a);
static void SetHits(SearchHandle sh,short idx,AccuPtr a);
long SearchStrText(PStr s,Ptr text,long length,long offset);
static Boolean SearchFind(MyWindowPtr win,PStr what);
static IMAPSCHandle SetupIMAPSearchCriteria(SearchPtr sp);
static void ProcessIMAPSearch(MyWindowPtr win,TOCHandle toc,SearchHandle sh);
static OSErr AddSearchResult(SearchHandle sh, TOCHandle srchTOC, TOCHandle tocH, short sumNum, Boolean addSpec, short specIdx);
static short FindMBIdx(TOCHandle tocH,FSSpecPtr toSpec,Boolean allowCreate);
static void SearchSave(MyWindowPtr win,Boolean saveAs);
static void SelectMailbox(FSSpecPtr spec,ViewListPtr pView,Boolean folder,Boolean addToSelection);
static MSumPtr FindLinkSummary(TOCHandle srchTocH, TOCHandle fromTocH, long serialNum, short *realSum, short *virtualMBIdx);
static void RemoveFocus(MyWindowPtr win);
static void GetCriteriaString(StringPtr s,CriteriaPtr criteria);
static void SearchSetWTitle(MyWindowPtr win,SearchHandle sh);
static void GetMenuText(ControlHandle cntl,StringPtr s);
static void ResizeCriterion(MyWindowPtr win,SearchHandle sh,short i);
static void AddCriteriaText(SearchHandle sh, StringPtr sText);
static pascal void SearchTextDraw(ControlHandle cntl, SInt16 part);
static void ShowHideTabs(ControlRef ctlTabs,Boolean show);
static void EmbedSearchCriteriaControls(CriteriaInfo *criteria,SearchHandle sh);
Boolean SearchBoxesInclude(SearchHandle sh,TOCHandle tocH);
Boolean SearchIncremental(MyWindowPtr win,TOCHandle tocH,short sumNum);

long DateTimeDifference(DateTimeRec *date,DateTimeRec *currDate,long seconds,short units);


//	globals
short	gSearchWinCount;
long	gLVFlags;

/************************************************************************
 * SearchOpen - open a new search window
 ************************************************************************/
MyWindowPtr SearchOpen(short searchMode)
{
	WindowPtr	winWP;
	MyWindowPtr	win;
	WindowPtr	topWinWP = FindTopUserWindow();
	MyWindowPtr	topWin = GetWindowMyWindowPtr(topWinWP);
	SearchVars	vars;
	OSErr		err;
	TOCHandle toc=nil;
	TOCPtr		ptoc;
	Handle		hSearchInfo=nil;
	Rect		r;
	Boolean		topMBWin = topWinWP && GetWindowKind(topWinWP) == MB_WIN;
	
	Zero(vars);
	if (!(win=GetNewMyWindow(SEARCH_WIND,nil,nil,BehindModal,true,true,MBOX_WIN)))
		{err=MemError(); goto fail;}
	winWP = GetMyWindowWindowPtr(win);
	SetWinMinSize(win,-1,-1);
	SetPort_(GetWindowPort(winWP));
	ConfigFontSetup(win);	
	win->hPitch = FontWidth;
	win->vPitch = FontLead+FontDescent;

	// controls
	SetTopMargin(win,kWinHeaderHt+kCriteriaHt);
	vars.ctlSearch = CreateControl(win,win->topMarginCntl,SEARCH_BUTTON,kControlPushButtonProc,true);
	OutlineControl(vars.ctlSearch,true);
	SetRect(&r,0,0,100,30);
	vars.ctlTabs = NewControl(winWP,&r,"\p",true,SEARCH_WIN_TABS,0,0,kControlTabSmallProc,0);	
	LetsGetSmall(vars.ctlTabs);
	EmbedControl(vars.ctlTabs,win->topMarginCntl);
	if (HasFeature (featureSearch)) {
		vars.ctlMore = CreateControl(win,win->topMarginCntl,MORE_CHOICES,kControlPushButtonProc,true);
		vars.ctlFewer = CreateControl(win,win->topMarginCntl,FEWER_CHOICES,kControlPushButtonProc,true);
		vars.ctlMatch = CreateMenuControl(win,win->topMarginCntl,"\p",SRCH_AND_OR_MENU,kControlPopupFixedWidthVariant+kControlPopupUseWFontVariant,0,false);
		vars.ctlSrchResults = CreateControl(win,win->topMarginCntl,SEARCH_RESULTS,kControlCheckBoxProc,true);
	}
	vars.ctlCriteriaGroup = CreateControl(win,win->topMarginCntl,0,kControlGroupBoxTextTitleProc,false);
	SetRect(&r,-64,-64,-48,-48);
	if (HasFeature (featureSearch)) {
		SetControlVisibility(vars.ctlFewer,false,true);
		SetControlVisibility(vars.ctlMatch,false,true);
		SetControlVisibility(vars.ctlSrchResults,false,true);
	}
	vars.ctlChaseArrows = CreateControl(win,win->topMarginCntl,0,kControlChasingArrowsProc,false);
	HideControl(vars.ctlChaseArrows);
	vars.ctlMailBox = CreateControl(win,win->topMarginCntl,0,kControlStaticTextProc|kControlPopupUseWFontVariant,false);
	SetControlVisibility(vars.ctlMailBox,false,true);
	vars.mailboxView = true;
	vars.expandList.resID = kSearchExpandID;
	if (topMBWin)
	{
		//	Expand same mailfolders as in Mailboxes window
		ExpandInfoPtr pExpList = MBGetExpList();		
		if (pExpList->hExpandList)
			vars.expandList.hExpandList = DupHandle(pExpList->hExpandList);
	}
	vars.savePreviewHi = GetPrefLong(PREF_SEARCH_PREVIEW);
				
	// set up data for virtual mailbox
	// need a TOC
	vars.hCriteria = NuHandle(0);
	if (!(toc=NewZH(TOCType))) goto fail;
	if (err = PtrToHand(&vars,&hSearchInfo,sizeof(vars))) goto fail;
	ptoc = *toc;
	ptoc->which = SEARCH_WIN;
	ptoc->version = CURRENT_TOC_VERS;
	ptoc->minorVersion = CURRENT_TOC_MINOR;
	ptoc->nextSerialNum = 1;
	ptoc->virtualTOC = true;
	ptoc->mailbox.virtualMB.data = hSearchInfo;
	ptoc->mailbox.virtualMB.type = kSearchMB;
	ptoc->win = win;
	(*toc)->previewHi = vars.savePreviewHi;
	SetMyWindowPrivateData(win,(long)toc);
	
	BoxGonnaShow(win);
	BoxListFocus(toc,false);	// kill the list focus so the search button works
	(*toc)->searchFocus = true;

	win->didResize = SearchDidResize;
	win->close = SearchClose;
	win->update = SearchUpdate;
	win->position = SearchPosition;
	win->click = SearchClick;
	win->bgClick = SearchClick;
	win->activate = SearchActivate;
	win->help = SearchHelp;
	win->menu = SearchMenuSelect;
	win->key = SearchKey;
	win->app1 = SearchApp1;
	win->button = SearchButton;
	win->scroll = SearchScroll;
	win->selection = SearchHasSelection;
	win->cursor = SearchCursor;
	win->drag = nil;	//	BoxGonnaShow set this up. We don't want it.
	win->idle = SearchIdle;
	win->dontControl = true;
	win->zoomSize = MaxSizeZoom;
	win->find = SearchFind;
	
	if (searchMode)
	{
		AddCriteria(win,(SearchHandle)hSearchInfo,true);
	}

	gLVFlags = searchMode == FIND_SEARCH_ALL_ITEM ? kfAutoSelectChild + kfAutoSelectAll : kfAutoSelectChild;
	(*(SearchHandle)hSearchInfo)->list = InitMailboxList(win,topMBWin);
	if (searchMode)
	{
		ShowMyWindow(winWP);
		UserSelectWindow(winWP);
		if (topWin)
			AutoSearch(win,topWin,searchMode);
	}
	SearchSetControls(win, (SearchHandle)hSearchInfo);
	gSearchWinCount++;
	SearchSetWTitle(win,(SearchHandle)hSearchInfo);
	SearchButton(win,vars.ctlTabs,0,0);		// Make sure Mailboxes tab is set up correctly
	return win;
	
fail:
	if (win) CloseMyWindow(GetMyWindowWindowPtr(win));
	ZapHandle(toc);
	ZapHandle(hSearchInfo);
	if (err) WarnUser(COULDNT_WIN,err);
	return win;
}

/**********************************************************************
 * CreateControl - create a control for the find window
 **********************************************************************/
static void AutoSearch(MyWindowPtr srchWin, MyWindowPtr	topWin, short searchMode)
{
	Str255	s;
	SearchHandle	sh;
	Boolean		selectedText=false;
	
	//	get selected text in current window
	*s=0;
	if (topWin && topWin->pte && topWin->hasSelection)
		SetFindString(s,topWin->pte);
	
	GetSearchInfo(srchWin,nil,&sh);
	if (*s && searchMode!=FIND_SEARCH_ITEM)
	{
		PeteSetString(s,(*(*sh)->hCriteria)[0].pte);
		selectedText = true;
	}
	
	if (searchMode != FIND_SEARCH_ALL_ITEM)
	{
		//	get mailbox or mailfolder of top window
		TOCHandle	tocH = nil;
		FSSpec		spec;
		ViewListPtr	pView = (*sh)->list;
		
		switch (GetWindowKind(GetMyWindowWindowPtr(topWin)))
		{
			case COMP_WIN:
			case MESS_WIN:
				tocH = (*Win2MessH(topWin))->tocH;
				break;
			case MBOX_WIN:
			case CBOX_WIN:
				tocH = (TOCHandle)GetMyWindowPrivateData(topWin);
				break;
		}
		
		if (!tocH)
			return;	//	there is no mailbox			

		//	select item in mailbox list
		spec = GetMailboxSpec(tocH,-1);
		SelectMailbox(&spec,pView,searchMode==FIND_SEARCH_FOLDER_ITEM,false);
	}
	
	if (selectedText && searchMode!=FIND_SEARCH_ITEM)
		StartSearch(srchWin);
}


/************************************************************************
 * SearchTextDraw - draw the search string
 ************************************************************************/
static pascal void SearchTextDraw(ControlHandle cntl, SInt16 part)
{
#pragma unused(part)
	PETEHandle pte = (PETEHandle)GetControlReference(cntl);

	PeteUpdate(pte);
}

/**********************************************************************
 * ResizeCriterion - resize this criterion
 **********************************************************************/
static void ResizeCriterion(MyWindowPtr win,SearchHandle sh,short i)
{
	CriteriaInfo	criteria=(*(*sh)->hCriteria)[i];
	short			top = INSET+9+i*kCriteriaHt;
	short			right;
	Rect			r,rCntl;

	MoveMyCntl(criteria.ctlCategory,2*INSET,top+1,0,0);
	GetControlBounds(criteria.ctlCategory,&rCntl);
	right = rCntl.right;
	MoveMyCntl(criteria.ctlRelation,right+INSET,top+1,0,0);
	GetControlBounds(criteria.ctlRelation,&rCntl);
	right = rCntl.right;
	if (criteria.ctlPete)
	{
		SetRect(&r,right+INSET+4,top+3,criteria.ctlSpecifier ? right+INSET+60 : WindowWi(GetMyWindowWindowPtr(win))-3*INSET,top+GetLeading(SmallSysFontID(),SmallSysFontSize())+3);
		PeteDidResize(criteria.pte,&r);
		MoveMyCntl(criteria.ctlPete,r.left,r.top,r.right-r.left,r.bottom-r.top);
		SetControlVisibility(criteria.ctlPete,true,false);
		right = r.right;		
	}
	if (criteria.ctlSpecifier)
	{
		short	specTop = top+1;
		
		switch (criteria.category)
		{
			case scStatus:
			case scPriority:
			case scLabel:
			case scDate:
				specTop -= 2;
				break;
		}
		MoveMyCntl(criteria.ctlSpecifier,right+INSET,specTop,0,0);
	}
}

/**********************************************************************
 * SearchDidResize - resize this search window
 **********************************************************************/
static void SearchDidResize(MyWindowPtr win, Rect *oldContR)
{
	SearchHandle	sh;
	ControlHandle	ctl;
	short			wi,v1,v3,right,left,moreRight;
	short			winWidth = WindowWi(GetMyWindowWindowPtr(win));
	short			criteriaHt;
	short			groupRight;
	short			i;
	TOCHandle 		tocH;
	Rect			rCntl;
	
	GetSearchInfo(win,&tocH,&sh);

	SetSearchTopMargin(win,sh);

	//	criteria group box
	ctl = (*sh)->ctlCriteriaGroup;
	criteriaHt = INSET+(*sh)->criteriaCount*kCriteriaHt+4;
	groupRight = winWidth-2*INSET;
	MoveMyCntl(ctl,INSET,INSET,groupRight,criteriaHt);
	SetControlVisibility(ctl,true,true);	// it's disappearing for some reason
	
	//	criteria
	for(i=0;i<(*sh)->criteriaCount;i++)
		ResizeCriterion(win,sh,i);

	v1 = INSET+(*sh)->criteriaCount*kCriteriaHt+16;
	
	//	"search" button
	ctl = (*sh)->ctlSearch;
	left = winWidth-ControlWi(ctl)-2*INSET;
	MoveMyCntl(ctl,left,v1+2,0,0);
	
	//	chasing arrows
	left = left - INSET - 16;
	MoveMyCntl((*sh)->ctlChaseArrows,left,v1+2,16,16);
	
	// more, fewer buttons, etc
	if (HasFeature (featureSearch)) {
		ctl = (*sh)->ctlMore;
		MoveMyCntl(ctl,INSET,v1,0,0);
		GetControlBounds(ctl,&rCntl);
		right = moreRight = rCntl.right;
		ctl = (*sh)->ctlFewer;
		MoveMyCntl(ctl,right+INSET,v1,0,0);
		GetControlBounds(ctl,&rCntl);
		right = rCntl.right;
		ctl = (*sh)->ctlMatch;
		MoveMyCntl(ctl,right+INSET,v1+1,0,0);
		GetControlBounds(ctl,&rCntl);
		right = (*sh)->criteriaCount > 1 ? rCntl.right : moreRight;
		ctl = (*sh)->ctlSrchResults;
		MoveMyCntl(ctl,right+INSET,v1,0,0);
	
		v3 = v1+28;
	}
	else
		v3 = v1+12;
	
	// mailbox name
	wi = winWidth - 2*INSET;
	if (wi < 100) wi = 0;	// Make it invisible if too narrowl
	ctl = (*sh)->ctlMailBox;
	MoveMyCntl(ctl,INSET+3,v3,wi,24);
	SetControlVisibility(ctl,(*sh)->searchMode,true);

	// tabs
	MoveMyCntl((*sh)->ctlTabs,-3,v3,winWidth+6,30);
	SetControlVisibility((*sh)->ctlTabs,!(*sh)->searchMode,true);

	if ((*sh)->mailboxView)
		// Mailbox list
		SizeMBList(win,sh);
	else
		BoxDidResize(win,oldContR);
	
	SetPrefLong(PREF_SEARCH_PREVIEW,(*tocH)->previewHi);

	
}

/**********************************************************************
 * SearchClose - close this search window
 **********************************************************************/
static Boolean SearchClose(MyWindowPtr win)
{
	TOCHandle toc;
	SearchHandle	sh;
	WindowPtr thisWin;

	PushGWorld();
	SetPort_(GetMyWindowCGrafPtr(win));

	StopSearch(win);
	
	GetSearchInfo(win,&toc,&sh);
		
	while ((*sh)->criteriaCount)
		RemoveCriteria(win,sh);
	ZapHandle((*sh)->hCriteria);
	LDRef(sh);
	
	//	Save expanded folder list
	if (!(*sh)->didSearch)
		//	Don't save changes. Just dispose of data
		(*sh)->expandList.fExpandListChanged = false;
	SaveExpandedFolderList(&(*sh)->expandList);	

	//	Remove references to this window from any
	//	messages opened from this window
	for (thisWin=FrontWindow_();thisWin;thisWin=GetNextWindow(thisWin))
		if (GetWindowKind(thisWin)==COMP_WIN || GetWindowKind(thisWin)==MESS_WIN)
		{
			MessHandle	messH = WinPtr2MessH(thisWin);
			
			if ((*messH)->openedFromTocH == toc)
				(*messH)->openedFromTocH = (*messH)->tocH;
		}

	ZapSpecList(toc);
	ZapHandle(toc);
	if ((*sh)->list)
	{
		LVDispose((*sh)->list);
		ZapPtr((*sh)->list);
	}
	ZapHandle((*sh)->BoxCount);
	ZapHandle((*sh)->imapBoxCount);
	ZapHandle((*sh)->redoBoxCount);
	ZapHandle((*sh)->imapBoxSpecIdx);
	ZapHandle(sh);
	win->selection = nil;
	gSearchWinCount--;
	PopGWorld();
	return true;
}

/**********************************************************************
 * SearchUpdate - update search window
 **********************************************************************/
static void SearchUpdate(MyWindowPtr win)
{
	CGrafPtr	winPort = GetMyWindowCGrafPtr (win);
	SearchHandle	sh;

	GetSearchInfo(win,nil,&sh);	
	if ((*sh)->mailboxView)
		LVDraw((*sh)->list,nil,true,false);
	else
		BoxUpdate(win);

	// these guys tend to get disabled
	if (HasFeature (featureMultiplePersonalities))
		EnableItem(GetMHandle(PERS_HIER_MENU),0);
	EnableItem(GetMHandle(LABEL_HIER_MENU),0);
}

/**********************************************************************
 * SearchClick - handle click in search window
 **********************************************************************/
static void SearchClick(MyWindowPtr win,EventRecord *event)
{
	WindowPtr	winWP = GetMyWindowWindowPtr(win);
	Point	pt;
//	PETEHandle pte;
	SearchHandle	sh;
	ControlHandle cntl;
	TOCHandle tocH;
	Rect	rCntl;

	GetSearchInfo(win,&tocH,&sh);	
	pt = event->where;
	GlobalToLocal(&pt);

	if ((*sh)->ctlFocus && !PtInRect(pt,GetControlBounds((*sh)->ctlFocus,&rCntl)))
	{
		SetKeyboardFocus(winWP,(*sh)->ctlFocus,kControlFocusNoPart);
		(*sh)->ctlFocus = nil;
		SearchSetWTitle(win,sh);
	}

	if (pt.v > win->topMargin)
	{
		if ((*sh)->mailboxView)
		{
			RemoveFocus(win);
			LVClick((*sh)->list,event);
		}
		else
			BoxClick(win,event);
	}
	else
	{
		short	i;		
		
		if (!win->isActive) {SelectWindow_(winWP);}
		BoxListFocus(tocH,false);
		(*tocH)->searchFocus = true;
		for(i=0;i<(*sh)->criteriaCount;i++)
		{
			PETEHandle	pte;
			if ((pte=(*(*sh)->hCriteria)[i].pte) &&  PtInPETE(pt,pte))
			{
				StopSearch(win);
				PeteEditWFocus(win,pte,peeEvent,(void*)event,nil);
				return;
			}
		}

		if (FindControl(pt,winWP,&cntl))
		{
			// It's a control. If a menu control, make sure it's enabled
			MenuHandle	mh;
			
			if (mh = GetPopupMenuHandle(cntl))
				EnableItem(mh,0);
				
			// ignore hits on the server status column header like in a regular mailbox -jdboyd 03/01/01
			if ((cntl==FindControlByRefCon(win,'wide'+blServer)) && PtInControl(pt,cntl));
			else HandleControl(pt,win);
		}
	}
	SearchSetControls(win, sh);
}

/************************************************************************
 * SearchHasSelection - is there a selection?
 ************************************************************************/
static Boolean SearchHasSelection(MyWindowPtr win)
{
	SearchHandle	sh;
	long start,stop;

	GetSearchInfo(win,nil,&sh);
	if (win->pte && !PeteGetTextAndSelection(win->pte,nil,&start,&stop))
		return(stop!=start);
	else if ((*sh)->mailboxView)
		return (*sh)->list && LVCountSelection((*sh)->list) ? true : false;
	else
		return BoxHasSelection(win);
}

/**********************************************************************
 * SearchCursor - adjust the cursor
 **********************************************************************/
static void SearchCursor(Point mouse)
{
	SearchHandle	sh;
	MyWindowPtr win=GetWindowMyWindowPtr(FrontWindow_());

	GetSearchInfo(win,nil,&sh);
	if (!(*sh)->mailboxView)
		BoxCursor(mouse);
	else if (!PeteCursorList(win->pteList,mouse))
		SetMyCursor(arrowCursor);
}

/**********************************************************************
 * SearchActivate - activate/deactivate search window
 **********************************************************************/
static void SearchActivate(MyWindowPtr win)
{
	SearchHandle	sh;

	GetSearchInfo(win,nil,&sh);
	if ((*sh)->mailboxView)
		LVActivate((*sh)->list, win->isActive);
	else
		BoxActivate(win);
}

/**********************************************************************
 * SearchScroll - do scrolling for search window 
 **********************************************************************/
static Boolean SearchScroll(MyWindowPtr win,short h,short v)
{
	SearchHandle	sh;

	GetSearchInfo(win,nil,&sh);
	if (!(*sh)->mailboxView)
		BoxScroll(win,h,v);
	return(True);
}

/**********************************************************************
 * SearchApp1 - process scrolling keys
 **********************************************************************/
static Boolean SearchApp1(MyWindowPtr win, EventRecord *event)
{
	SearchHandle	sh;
	TOCHandle tocH;
	
	GetSearchInfo(win,&tocH,&sh);
	if ((*sh)->mailboxView)
		return LVKey((*sh)->list,event);
	else if ((*tocH)->previewID && (*tocH)->previewPTE)
	{
		event->what = keyDown;
		PeteEdit(win,(*tocH)->previewPTE,peeEvent,(void*)event);
		return true;
	}
	else
	{
		// Send to the mailbox list
		DoApp1NoPTE(win,event);
		return true;
	}
}

/**********************************************************************
 * SearchKey - process interesting keys
 **********************************************************************/
static Boolean SearchKey(MyWindowPtr win, EventRecord *event)
{
	SearchHandle	sh;
	Boolean			result;
	
	GetSearchInfo(win,nil,&sh);
	result = DoSearchKey(win,event);
	SearchSetControls(win, sh);
	return result;
}

/**********************************************************************
 * SearchKey - process interesting keys
 **********************************************************************/
static Boolean DoSearchKey(MyWindowPtr win, EventRecord *event)
{
	short key = event->message & 0xff;
	SearchHandle	sh;
	TOCHandle	tocH;
	ControlHandle	cntl;

	GetSearchInfo(win,&tocH,&sh);
	if (event->modifiers&cmdKey && IsCmdChar(event,'.') ||	// command-period
				 (event->message&charCodeMask)==escChar)
	{
#ifdef SPEECH_ENABLED
		if (CancelSpeech ())
			return (false);
#endif
		//	stop searching
		StopSearch(win);
		return true;
	}
		
	if (key=='\t')
	{
		// Who would believe this could be so complicated?
		typedef enum {
			stabOnly=0,	// there is only one pte in the search criteria, and it's focussed
			stabFrst,		// there is more than one pte in the search criteria, and the first one is focussed
			stabMddl,		// there are more than two pte's in the search criteria, and a middle one is focussed
			stabLast,		// there is more than one pte in the search criteria, and the last one is focussed
			stabNone,		// there are no pte's in the search criteria, but the criteria nonetheless have focus
			stabSumm,		// the focus is in the list of mailbox summaries
			stabPrev,		// the focus is in the preview pane
			stabBxes,		// the focus is in the mailboxes list
			stabLimit
			} SearchSelectTypeEnum;
		SearchSelectTypeEnum currentSelect, nextSelect;
		static SearchSelectTypeEnum STabStateTable[2][stabLimit] = {
					//	stabOnly	stabFrst	stabMddl	stabLast	stabNone	stabSumm	stabPrev	stabBxes
/* forward */	stabSumm,	stabMddl,	stabMddl,	stabSumm,	stabSumm,	stabPrev,	stabFrst,	stabFrst,
/* backwrd */	stabPrev,	stabPrev,	stabMddl,	stabMddl,	stabSumm,	stabLast,	stabSumm,	stabLast
		};		
		PETEHandle nextPTE;
		PETEHandle origPTE = win->pte;
		Boolean backward = (event->modifiers&shiftKey)!=0;
		
		// Figure out where we are
		if (win->pte)
		{
			nextPTE = PeteNext(win->pte);
			if (win->pte==(*tocH)->previewPTE) currentSelect = stabPrev;
			else if (nextPTE==(*tocH)->previewPTE || nextPTE==nil)
				currentSelect = win->pte==win->pteList ? stabOnly : stabLast;
			else
				currentSelect = win->pte==win->pteList ? stabFrst : stabMddl;
		}
		else if ((*tocH)->searchFocus)
			currentSelect = stabNone;
		else if ((*sh)->mailboxView)
			currentSelect = stabBxes;
		else
			currentSelect = stabSumm;
		
		// First crack at where we should go
		nextSelect = STabStateTable[backward][currentSelect];
		
		// Fine-tuning
retune:
		switch (nextSelect)
		{
			case stabSumm:
				if ((*sh)->mailboxView)
					nextSelect = stabBxes;	// mailboxes list is showing, go there
				break;

			case stabPrev:
				if ((*sh)->mailboxView)
					nextSelect = stabBxes;	// mailboxes list is showing, go there
				else if (!(*tocH)->previewID)
				{
					// no preview, go elsewhere
					if (backward)
					{
						nextSelect = stabSumm;	// backing up from the top is the summary list
					}
					else
					{
						nextSelect = stabFrst;	// foward from the summaries is the top preview edit field
						goto retune;	// but it might not be there, so try again
					}
				}
				break;
			
			case stabFrst:
			case stabMddl:
			case stabLast:
				if (win->pteList==(*tocH)->previewPTE)
					nextSelect = stabNone;	// if no edit field or only edit is preview, just focus on criteria, not pte at all
				break;
		}
		
		// Ok, things are pretty simple now
		
		// First, we defocus
		switch (currentSelect)
		{
			case stabOnly:
			case stabFrst:
			case stabMddl:
			case stabLast:
			case stabNone:
			case stabPrev:
				PeteFocus(win,nil,true);	// no-op for stabNone, but who cares?
				(*tocH)->searchFocus = false;	// no-op for stabPrev, but who cares?
				break;
				
			case stabSumm:
				BoxListFocus(tocH,false);
				break;
			
			case stabBxes:
				// nothing special here
				break;
			default:
				ASSERT(0);	// naughty processor shdn't be here
				break;
		}
		
		// Then, we focus
		switch (nextSelect)
		{
			case stabOnly:
			case stabFrst:
				PeteFocus(win,win->pteList,true);
				(*tocH)->searchFocus = true;
				break;
				
			case stabMddl:
				nextPTE = backward ? PetePrevious(win->pteList,origPTE) : PeteNext(origPTE);
				PeteFocus(win,nextPTE,true);
				(*tocH)->searchFocus = true;
				break;
				
			case stabLast:
				// this one actually requires some calculation.
				for (origPTE=win->pteList;;origPTE=nextPTE)
				{
					nextPTE = PeteNext(origPTE);
					if (nextPTE==nil || nextPTE==(*tocH)->previewPTE) break;
				}
				PeteFocus(win,origPTE,true);
				(*tocH)->searchFocus = true;
				break;
				
			case stabNone:
				(*tocH)->searchFocus = true;
				break;
				
			case stabPrev:
				// Sigh.  First, we must focus on the list
				BoxListFocus(tocH,true);
				// Then we send it a tab to focus on the preview
				BoxKey(win,event);
				break;
				
			case stabSumm:
				if (FirstMsgSelected(tocH)==-1) BoxInitialSelection(tocH);
				BoxListFocus(tocH,true);
				break;
			
			case stabBxes:
				// nothing special here
				break;
				
			default:
				ASSERT(0);	// naughty processor shdn't be here
				break;
		}
		
		if (win->pte && win->pte!=(*tocH)->previewPTE) PeteSelectAll(win,win->pte);
		
		// There, that was easy.  (sarcasm)
		return true;
	}

	if (key==returnChar || key==enterChar)
	{
		if ((*sh)->searchMode)
		{
			//	Stop if we're already searching
			StopSearch(win);
			return true;
		}
		
		//	Start search if in mailbox view or if focus not in results pane or not in preview pane or summary list
		if ((*sh)->mailboxView || win->pte && (win->pte != (*tocH)->previewPTE) || !(*tocH)->listFocus)
		{
			StartSearch(win);
			return true;
		}
	}

	if (!(event->modifiers&cmdKey) && !GetKeyboardFocus(GetMyWindowWindowPtr(win),&cntl) && cntl)
	{
		//	send key to control
		HandleControlKey(cntl,(event->message&keyCodeMask)>>8,event->message&charCodeMask,event->modifiers);
 		return true;
 	}

	if (win->pte)
	{
		if ((win->pte != (*tocH)->previewPTE) && !(event->modifiers&cmdKey))
			StopSearch(win);
		if (!(event->modifiers&cmdKey))
		{
			if ((win->pte == (*tocH)->previewPTE) && DirtyKey(event->message))
				AlertStr(READ_ONLY_ALRT, Stop, nil);
			else
			{
				PeteKey(win,win->pte,event);
				SearchSetControls(win, sh);	
			}
			return true;
		}
		else
			return false;
	}

	if ((*sh)->mailboxView)
		return LVKey((*sh)->list,event);
	else
		return BoxKey(win,event);
}

enum
{
	kHelpStrings,
	kHelpSearch,
	kHelpStop,
	kHelpMore,
	kHelpFewer,
	kHelpMatch,
	kHelpSearchResults,
	kHelpMailboxesTab,
	kHelpResultsTab,
	kHelpCategory,
	kHelpVerb,
	kHelpWhatMenu,
	kHelpWhatText,
	kHelpMBList,
	kHelpCount
};

/**********************************************************************
 * SearchHelp - provide help for the search window
 **********************************************************************/
static void SearchHelp(MyWindowPtr win,Point mouse)
{
	SearchHandle	sh;
	ControlHandle	cntl;
	short			part;
	short			helpID = 0;
	SearchPtr		sp;
	Rect			r;

	GetSearchInfo(win,nil,&sh);
	sp = LDRef(sh);

	if (part = FindControl(mouse,GetMyWindowWindowPtr(win),&cntl))
	{
		GetControlBounds(cntl,&r);
		if (cntl==sp->ctlSearch)
			helpID = sp->searchMode ? kHelpStop : kHelpSearch;			
		else if (cntl==sp->ctlMore)
			helpID = kHelpMore;
		else if (cntl==sp->ctlFewer)
			helpID = kHelpFewer;
		else if (cntl==sp->ctlMatch)
			helpID = kHelpMatch;			
		else if (cntl==sp->ctlSrchResults)
			helpID = kHelpSearchResults;
		else if (cntl==sp->ctlTabs)
			helpID = part == tabMailboxes ? kHelpMailboxesTab : kHelpResultsTab;
		else if (GetControlReference(cntl)==kBoxSizeRefCon)
			helpID = kHelpCount;
		else
		{
			//	see if it's in a criterion
			short	i;
			
			for(i=0;i<sp->criteriaCount;i++)
			{
				CriteriaPtr	pc=&(*sp->hCriteria)[i];

				if (cntl==pc->ctlCategory)
					{ helpID = kHelpCategory; break; }		
				else if (cntl==pc->ctlRelation)
					{ helpID = kHelpVerb; break; }
				else if (cntl==pc->ctlSpecifier)
					{ helpID = kHelpWhatMenu; break; }
				else if (cntl==pc->ctlPete)
					{ helpID = kHelpWhatText; break; }
			}
		}
	}
	
	if (!helpID)
	{
		if (sp->mailboxView)
		{
			if (PtInRect(mouse,&sp->list->bounds))
			{
				r = sp->list->bounds;
				helpID = kHelpMBList;
			}
		}
		else
			//	let boxhelp handle it
			BoxHelp(win, mouse);
	}
		
	if (helpID)
		MyBalloon(&r,100,0,helpID+SEARCH_HELP_STRN,0,nil);
	
	UL(sh);
}

/**********************************************************************
 * SearchMenuSelect - handle a menu selection
 **********************************************************************/
static Boolean SearchMenuSelect(MyWindowPtr win, int menu, int item, short modifiers)
{
	SearchHandle	sh;

	GetSearchInfo(win,nil,&sh);
	switch (menu)
	{
		case FILE_MENU:
			switch (item)
			{
				case FILE_SAVE_ITEM:
					SearchSave(win,false);
					return true;
					break;
				case FILE_SAVE_AS_ITEM:
					SearchSave(win,true);
					return true;
					break;
			}
	}
	
	if ((*sh)->mailboxView)
	{
		if ((*sh)->list)
		{
			switch (menu)
			{
				case FILE_MENU:
					if (item==FILE_OPENSEL_ITEM)
					{
						MBListOpen((*sh)->list);
						return(True);
					}
					break;

				case EDIT_MENU:
					if (!win->pte)
					{
						switch(item)
						{
							case EDIT_SELECT_ITEM:
								if (LVSelectAll((*sh)->list))
									return(true);
								break;
							case EDIT_COPY_ITEM:
								LVCopy((*sh)->list);
								return true;
						}
					}
					break;
			}
				
		}
	}
	else
		return BoxMenu(win,menu,item,modifiers);
	
	return false;
}

/**********************************************************************
 * SearchFind - find in the Search window
 **********************************************************************/
static Boolean SearchFind(MyWindowPtr win,PStr what)
{
	SearchHandle	sh;

	GetSearchInfo(win,nil,&sh);
	if ((*sh)->mailboxView)
		// mailbox view
		return FindListView(win,(*sh)->list,what);
	else
		//	results view
		return BoxFind(win,what);
}

/**********************************************************************
 * IsSearchWindow - is the window a Search window?
 **********************************************************************/
Boolean IsSearchWindow(WindowPtr theWindow)
{
	TOCHandle	tocH;
	
	return (IsKnownWindowMyWindow(theWindow) && GetWindowKind(theWindow)==MBOX_WIN &&
			(tocH = (TOCHandle)(GetWindowPrivateData(theWindow))) &&
			(*tocH)->virtualTOC &&
			 (*tocH)->mailbox.virtualMB.type == kSearchMB);
}

/**********************************************************************
 * GetSearchWinSpec - get saved spec for search window
 **********************************************************************/
Boolean GetSearchWinSpec(WindowPtr winWP,FSSpecPtr spec)
{
	SearchHandle	sh;

	if (IsSearchWindow(winWP))
	{
		MyWindowPtr	win = GetWindowMyWindowPtr (winWP);
		GetSearchInfo(win,nil,&sh);
		if ((*sh)->saveSpec.vRefNum)
		{
			*spec = (*sh)->saveSpec;
			return true;
		}
	}
	return false;
}

/**********************************************************************
 * SearchNewFindString - there's a new Find string
 **********************************************************************/
void SearchNewFindStringLo(PStr what,Boolean withPrejuidice)
{
	WindowPtr winWP;
	
	for (winWP=FrontWindow();winWP;winWP = GetNextWindow (winWP))
		if (IsWindowVisible(winWP) && IsSearchWindow(winWP))
		{
			SearchHandle	sh;
			PETEHandle	pte;

			GetSearchInfo(GetWindowMyWindowPtr (winWP),nil,&sh);
			//	Replace search string if only one criterion, it has a text field,
			//	and the field is empty
			if ((*sh)->criteriaCount == 1 && 
				(pte=(*(*sh)->hCriteria)[0].pte) &&
				(withPrejuidice || !PeteLen(pte)))
				{
					PeteSetString(what,pte);
				}
			break;
		}
}

/**********************************************************************
 * SearchSetControls - set status of controls
 **********************************************************************/
static Boolean SearchSetControls(MyWindowPtr win, SearchHandle sh)
{
	Boolean	active = false;
	ControlHandle	ctl;
	
	//	Determine if search button should be dimmed
	if ((*sh)->searchMode)
		active = true;
	{
		//	search through criteria for something we can search on
		short		i;
		PETEHandle 	pte;
		
		for(i=0;i<(*sh)->criteriaCount;i++)
		{
			pte = (*(*sh)->hCriteria)[i].pte;
			if (!PeteIsValid(pte) || PeteLen(pte))
			{
				//	If critiera doesn't have a text field or
				//	there is something in the text field, don't dim
				active = true;
				break;
			}
		}
	}
	
	ctl = (*sh)->ctlSearch;
	if (win->isActive && IsControlActive(ctl) != active)
	{
		if (active) ActivateControl(ctl);
		else DeactivateControl(ctl);
	}
	
	win->userSave = (*sh)->saveSpec.vRefNum!=0;
	return active;
}

/**********************************************************************
 * GetSearchInfo - get search info from window
 **********************************************************************/
static void GetSearchInfo(MyWindowPtr win,TOCHandle *ptoc,SearchHandle *psh)
{
	TOCHandle toc=(TOCHandle)GetMyWindowPrivateData(win);
	SearchHandle	sh = (SearchHandle)(*toc)->mailbox.virtualMB.data;

	if (ptoc) *ptoc = toc;
	if (psh) *psh = sh;
}

/**********************************************************************
 * GetSearchTOC - get the TOC of a search window
 **********************************************************************/
void GetSearchTOC(MyWindowPtr win,TOCHandle *ptoc)
{
	TOCHandle toc=(TOCHandle)GetMyWindowPrivateData(win);
	SearchHandle	sh = (SearchHandle)(*toc)->mailbox.virtualMB.data;

	if (ptoc) *ptoc = toc;
}

/**********************************************************************
 * StartSearch - start the search
 **********************************************************************/
void StartSearch(MyWindowPtr win)
{
	SearchHandle	sh;
	SearchPtr		sp;
	TOCHandle	toc;
	short		n;
	Str32		s;

	ActiveSearchCount++;
	Log(LOG_SEARCH,"\pSearch started");
	
	GetSearchInfo(win,&toc,&sh);
	SearchSetWTitle(win,sh);

	if (!SearchSetControls(win, sh))
		//	Can't search. Probably no mailboxes selected
		return;
		
	sp = LDRef(sh);
	
	//	set up search parameters
	Zero(sp->curr);
	sp->curr.lastBoxSpec = -2;
	sp->curr.box = -1;
	sp->searchMode = kSearchingLocal;
	sp->didSearch = true;
	sp->nHits = 0;
	sp->bulkData.didBulkSearch = false;
	
	if (!SetupSearchCriteria(win,sp)) return;	// setup failed
	if (GetSelectedBoxes(sp,toc,true)) return;	// aborted

	// remove any current hits
	if (!(*sh)->searchResults)
	{
		n=(*toc)->count;
		if (n)
			UpdateBoxSize(win);
		while (n--)
			DeleteSum(toc,n);
		ZapSpecList(toc);
	}

	if ((*sh)->mailboxView)
	{
		//	switch back to results view
		SetControlValue((*sh)->ctlTabs,tabResults);
		SearchButton(win,(*sh)->ctlTabs,0,0);
	}
	ShowHideTabs(sp->ctlTabs,false);
	ShowControl(sp->ctlChaseArrows);
	ShowControl(sp->ctlMailBox);
	SetControlTitle(sp->ctlSearch,GetRString(s,STOP_BUTTON));
#ifdef VALPHA
	sp->nSearched = sp->nTicks = 0;
#endif
	UL(sh);
	win->isDirty = true;
}

/**********************************************************************
 * SetupSearchCriteria - setup search criteria
 **********************************************************************/
static Boolean SetupSearchCriteria(MyWindowPtr win,SearchPtr sp)
{
	short	i;
	Str255	s;
	Str255	cs;
	long junk;
	LongDateRec	date;
	PersHandle	pers;
	short		orderIdx;
	
	if (HasFeature (featureSearch)) {
		sp->matchAny = GetControlValue(sp->ctlMatch) == matchAny;
		sp->searchResults = GetControlValue(sp->ctlSrchResults) > 0;
	}
	sp->criteriaFlags = 0;
	sp->noBulkSearch = false;
	
	for(i=0;i<sp->criteriaCount;i++)
	{
		CriteriaInfo	criteria=(*sp->hCriteria)[i];
		short	value;
	
		criteria.specifier = 0;
		criteria.relation = GetControlValue(criteria.ctlRelation);
		if (criteria.ctlSpecifier)
			value = GetControlValue(criteria.ctlSpecifier);
		if (HasFeature (featureSearch) || !gCategoryTable[criteria.category-1].proOnly) {
			switch (criteria.category)
			{
				case scAnywhere:
				case scHeaders:
				case scBody:
				case scAttachNames:
				case scSummary:
				case scTo:
				case scFrom:
				case scSubject:
				case scCC:
				case scBCC:
				case scAnyRecipient:
					// get text
					PeteString(s,criteria.pte);
					ZapHandle(criteria.searchString);
					criteria.searchString = criteria.relation != strRegExp ? NewString(s) : (StringHandle)regcomp(PtoCcpy(cs,s));
					criteria.fudge8bit = criteria.relation != strRegExp && MixedHighBits(s+1,*s);
					if (!criteria.searchString) return false;
					HLock((Handle)criteria.searchString);
					sp->noBulkSearch = sp->noBulkSearch || PrefIsSet(PREF_NO_BULK_SEARCH) && AllHighBits(s+1,*s);
					
					//	Should we set up the default find string?
					if (i==0 && criteria.relation != strDoesNotContain && criteria.relation != strIsNot)
						FindEnterSelection(s,false);
					
					// Bug 4262 came from doing this before entering the find string...
					if (criteria.relation != strRegExp && AnyHighBits(s+1,*s))
					{
						RomanToUTF8(s,sizeof(s));
						criteria.utf8SearchString = NewString(s);
					}
					break;
				case scStatus:
					criteria.specifier = Item2Status(value);
					break;
				case scPriority:
					criteria.specifier = Display2Prior(value);
					break;
				case scAge:
					PeteString(s,criteria.pte);
					StringToNum(s,&criteria.misc);
					criteria.specifier = value;
					criteria.zoneSecs = ZoneSecs();
					break;
				case scAttachCount:
				case scSize:
				case scJunkScore:
					PeteString(s,criteria.pte);
					StringToNum(s,&junk);
					criteria.specifier = junk;
					break;
				case scLabel:
					criteria.specifier = Menu2Label(value);
					break;
				case scDate:
					GetControlData(criteria.ctlSpecifier,0,
						kControlClockLongDateTag,sizeof(date),(Ptr)&date,&junk);
					criteria.date.day = date.ld.day;
					criteria.date.month = date.ld.month;
					criteria.date.year = date.ld.year;
					criteria.zoneSecs = ZoneSecs();
					criteria.useSenderZone = !PrefIsSet(PREF_LOCAL_DATE);
					break;
				case scPersonality:
					if (HasFeature (featureMultiplePersonalities))
						if (pers = value==1 ? PersList : Index2Pers(value-2))
							criteria.specifier = (*pers)->persId;
					break;
			}
			(*sp->hCriteria)[i] = criteria;
			sp->criteriaFlags |= gCategoryTable[criteria.category-1].flags;
		}
		// (jp) 1-18-00 Support for light downgrade
		if (gCategoryTable[criteria.category-1].proOnly)
			UseFeature (featureSearch);
	}
	
	//	Set up search order. Search those that require searching through the message last.

	orderIdx=0;
	for(i=0;i<sp->criteriaCount;i++)
		if (!MustSearchBody((*sp->hCriteria)[i].category))
			sp->searchOrder[orderIdx++] = i;
	for(i=0;i<sp->criteriaCount;i++)
		if (MustSearchBody((*sp->hCriteria)[i].category))
			sp->searchOrder[orderIdx++] = i;
	sp->setup = true;
	return true;
}

/**********************************************************************
 * MustSearchBody - does this criteria category require searching the message body
 **********************************************************************/
static Boolean MustSearchBody(short category)
{
	return (gCategoryTable[category-1].flags & kNeedsMsg) != 0;
}

/**********************************************************************
 * GetSelectedBoxes - build list of selected mailboxes
 **********************************************************************/
static OSErr GetSelectedBoxes(SearchPtr sp,TOCHandle tocH,Boolean bStartIMAPSearch)
{
	ViewListPtr	pView;
	Boolean	haveIMAP = false;
	OSErr	err=noErr;
	
	ZapHandle(sp->BoxCount);
	ZapHandle(sp->imapBoxCount);
	ZapHandle(sp->redoBoxCount);
	if (pView=sp->list)
	{
		BoxCountHandle	saveBoxCount,popBoxCount,imapBoxCount=nil;
		short		i;
		short	n;

		n=LVCountSelection(pView);
		if (!n)
		{
			//	No mailboxes are selected. Select ALL of them.
			LVSelectAll(pView);
			n=LVCountSelection(pView);
		}
		saveBoxCount = BoxCount;
		popBoxCount = NuHandle(0);
		
		// build our own box count
		for(i=1;i<=n;i++)
		{
			VLNodeInfo itemInfo;
			
			//	get next selected item
			if (!LVGetItem(pView,i,&itemInfo,true)) break;
			
			BoxCount = popBoxCount;

			if (IsIMAPBox(&itemInfo))
			{
				if (sp->criteriaFlags & kNeedsMsg)
				{
					//	Need to do an IMAP on server search
					if (!imapBoxCount)
						imapBoxCount=NuHandle(0);
					BoxCount = imapBoxCount;
				}
				haveIMAP = true;
			}
		
			if ((itemInfo.nodeID == MAILBOX_MENU || itemInfo.nodeID ==kEudoraFolder) && 
				itemInfo.refCon == MailRoot.dirId && itemInfo.isParent && itemInfo.isCollapsed)
			{
				//	Eudora folder
				//	add entire Eudora folder if collapsed
				AddBoxCountItem(MAILBOX_IN_ITEM,MailRoot.vRef,MailRoot.dirId);
				AddBoxCountItem(MAILBOX_OUT_ITEM,MailRoot.vRef,MailRoot.dirId);
				AddBoxCountItem(MAILBOX_JUNK_ITEM,MailRoot.vRef,MailRoot.dirId);
				AddBoxCountItem(MAILBOX_TRASH_ITEM,MailRoot.vRef,MailRoot.dirId);
				AddBoxCountMenu(MAILBOX_MENU,MAILBOX_FIRST_USER_ITEM,MailRoot.vRef,MailRoot.dirId,false);
			}
			else
			{
				MenuHandle	mh;
				short	menuItem;
				short		menuID;
				short		vRef;
				long		dirId;

				if (itemInfo.nodeID==kEudoraFolder) continue;	//	Ignore Eudora Folder
				
				menuID = itemInfo.nodeID==kIMAPFolder ? MAILBOX_MENU : itemInfo.nodeID;
				mh = GetMHandle(menuID);
				menuItem = FindItemByName(mh,itemInfo.name);
				if (!menuItem) continue;	//	shouldn't happen
				
				if (itemInfo.isParent)
				{
					// mailbox folder
					if (haveIMAP && itemInfo.iconID == IMAP_MAILBOX_FILE_ICON)
					{
						//	IMAP hybrid mailbox/mailfolder. Need to add it
						MenuID2VD(menuID,&vRef,&dirId);
						AddBoxCountItem(menuItem,vRef,dirId);
					}
					if (itemInfo.isCollapsed)
					{
						//	add entire folder contents if collapsed
						menuID = SubmenuId(mh,menuItem);
						MenuID2VD(menuID,&vRef,&dirId);
						AddBoxCountMenu(menuID,(menuID==MAILBOX_MENU) ? 1: MAILBOX_FIRST_USER_ITEM-MAILBOX_BAR1_ITEM,vRef,dirId,false);
					}
				}
				else
				{
					// mailbox					
					MenuID2VD(menuID,&vRef,&dirId);
					AddBoxCountItem(menuItem,vRef,dirId);
				}
			}
		}
		
		sp->BoxCount = popBoxCount;
		sp->imapBoxCount = imapBoxCount;
		BoxCount = saveBoxCount;

		if (haveIMAP && bStartIMAPSearch)
		{
			if (sp->criteriaFlags & kNeedsSum && !(sp->criteriaFlags & kNeedsMsg))
			{
				//	Warn user we are only searching local cache for summary items
				if (ComposeStdAlert(Caution,IMAP_SEARCH_LOCAL_WARN)==2)
					err = userCanceledErr;
			}
			
			if ((!sp->searchResults))
			{
				// start the IMAP searches ...
				if (imapBoxCount)
				{
					//	Start IMAP search
					IMAPSCHandle searchCriteria;
					
					if (searchCriteria = SetupIMAPSearchCriteria(sp))
					{
						// IMAPSearch will return false if no searches were actually started.
						if (IMAPSearch(tocH,imapBoxCount,searchCriteria,!sp->matchAny))
							sp->searchMode |= kSearchingIMAP;
						else 
							err = userCanceledErr;
						
						if (err!=userCanceledErr) 
						{
							ZapHandle(sp->imapBoxSpecIdx);
							sp->imapBoxSpecIdx = NuHandleClear(HandleCount(imapBoxCount)*sizeof(short));
						}
					}
				}
			}
		}	
	}

	return err;
}

/**********************************************************************
 * ZapSpecList - zap Spec List handle
 **********************************************************************/
static void ZapSpecList(TOCHandle toc)
{
	FSSpecHandle	specList;

	if (specList = (*toc)->mailbox.virtualMB.specList)
	{
		ZapHandle(specList);
		(*toc)->mailbox.virtualMB.specList = nil;
	}
	(*toc)->mailbox.virtualMB.specListCount = 0;
}

/**********************************************************************
 * StopSearch - stop the search
 **********************************************************************/
static void StopSearch(MyWindowPtr win)
{
	SearchHandle	sh;
	short			i;
	Str32			s;
	Boolean			gotHits;
	SearchMarker	curr;
	CriteriaHandle	hCriteria;
	TOCHandle		tocH;

	GetSearchInfo(win,&tocH,&sh);
	if (!(*sh)->searchMode) return;
	
	ActiveSearchCount--;
	
	StopBulkSearch(sh);
	if ((*sh)->searchMode & kSearchingIMAP)
		IMAPAbortSearch(tocH);

	curr = (*sh)->curr;
	FinishedTOC(&curr);
	(*sh)->curr = curr;

	SetControlTitle((*sh)->ctlSearch,GetRString(s,SEARCH_BUTTON));
	HideControl((*sh)->ctlChaseArrows);
	HideControl((*sh)->ctlMailBox);
	ShowHideTabs((*sh)->ctlTabs,true);
	(*sh)->searchMode = 0;
	SetTextControlText((*sh)->ctlMailBox,"",nil);

	// unlock search string handles
	// dispose of accumulators
	hCriteria = (*sh)->hCriteria;
	for(i=0;i<(*sh)->criteriaCount;i++)
	{
		StringHandle	h;

		if (h = (*hCriteria)[i].searchString)
			HUnlock((Handle)h);
		SetHits(sh,i,nil);	//	dispose of bulk search hits
	}

	gotHits = (*sh)->nHits>0;
	if (HasFeature (featureSearch)) {
		SetControlVisibility((*sh)->ctlSrchResults,gotHits,true);
		if (!gotHits)
			SetControlValue((*sh)->ctlSrchResults,0);
	}
	
#if __profile__
//	ProfilerDump("\psearch-profile");
//	ProfilerClear();
#endif

	//	log search info
	if (LOG_SEARCH&LogLevel)
	{
		long	nSelMB=0;
		
		//	search stats
		if ((*sh)->BoxCount)
			nSelMB = GetHandleSize_((*sh)->BoxCount)/sizeof(BoxCountElem);
#ifdef VALPHA
		ComposeLogS(LOG_SEARCH,nil,"\pHits: %d/%d Seconds: %d.%d, Mailboxes: %d/%d",
			(*sh)->nHits,(*sh)->nSearched,
			(*sh)->nTicks/60,(*sh)->nTicks%60,
			nSelMB,GetHandleSize_(BoxCount)/sizeof(BoxCountElem));
#endif

		//	criteria
		for(i=0;i<(*sh)->criteriaCount;i++)
		{
			CriteriaInfo	criteria=(*(*sh)->hCriteria)[i];
			Str32			sCategory,sRelation;
			Str255			sSpecifier;
			
			GetMenuItemText(GetPopupMenuHandle(criteria.ctlCategory),criteria.category,sCategory);
			GetMenuItemText(GetPopupMenuHandle(criteria.ctlRelation),criteria.relation,sRelation);
			if (criteria.searchString)
			{
				PCopy(sSpecifier,*criteria.searchString);
				ComposeLogS(LOG_SEARCH,nil,"\p %d: %p %p \"%p\"",i+1,sCategory,sRelation,sSpecifier);
			}
			else
			{
				ComposeLogS(LOG_SEARCH,nil,"\p %d: %p %p %d",i+1,sCategory,sRelation,criteria.specifier);
			}
		}
	}
}

/**********************************************************************
 * AdjustForCriteriaCount - adjust controls for number of criteria
 **********************************************************************/
static void AdjustForCriteriaCount(SearchHandle sh)
{
	if (HasFeature (featureSearch)) {
		SetControlVisibility((*sh)->ctlFewer,(*sh)->criteriaCount>1,true);
		SetControlVisibility((*sh)->ctlMatch,(*sh)->criteriaCount>1,true);
	}
}

/**********************************************************************
 * SearchButton - hit one of our controls
 **********************************************************************/
static void SearchButton(MyWindowPtr win,ControlHandle button,long modifiers,short part)
{
	WindowPtr	winWP = GetMyWindowWindowPtr(win);
	SearchHandle	sh;
	SearchPtr		sp;
	TOCHandle 		toc;
	long 			controlId = -1;
	Rect			rWin;
	
	GetSearchInfo(win,&toc,&sh);
	sp=*sh;
	
	
	if (button==sp->ctlSearch)
	{
		if (sp->searchMode)
			StopSearch(win);
		else
			StartSearch(win);
		
		controlId = kctlSearch;
	}
	else if (button==sp->ctlMore)
	{
		Rect	rNone;
		
		controlId = kctlMore;

		if ((*sh)->criteriaCount>=kMaxCriteria)
		{
			SysBeep(0);
			return;	// enough criteria
		}
		StopSearch(win);
		AddCriteria(win,sh,false);
	  SetCriteria:
		AdjustForCriteriaCount(sh);
		SetRect(&rNone,0,0,0,0);
		ClipRect(&rNone);
		SetSearchTopMargin(win,sh);
		MyWindowDidResize(win,&win->contR);
		InfiniteClip(GetMyWindowCGrafPtr(win));
		InvalWindowRect(winWP,GetWindowPortBounds(winWP,&rWin));
		win->isDirty = true;
		SearchSetWTitle(win,sh);
	}	
	else if (button==sp->ctlFewer)
	{
		controlId = kctlFewer;

		if ((*sh)->criteriaCount >= 2)
		{
			StopSearch(win);
			RemoveCriteria(win,sh);
			goto SetCriteria;
		}
	}	
	else if (button==sp->ctlTabs)
	{
		//	Toggle view of mailboxes
		ControlHandle	cntl,placard;
		short			i;
		Boolean			boxCtlsVisible;
		PETEHandle		 previewPTE;
		Rect 			oldContR = win->contR;
		Rect	rNone;
		
		controlId = kctlTabs;

		SetRect(&rNone,0,0,0,0);
		ClipRect(&rNone);

		(*sh)->mailboxView = GetControlValue(button)==tabMailboxes;
		boxCtlsVisible = !(*sh)->mailboxView;
		for (i=1;i<BoxLinesLimit;i++)		//	hide or show mailbox header buttons
			if (cntl = FindControlByRefCon(win,'wide'+i))
				SetControlVisibility(cntl,boxCtlsVisible,true);
		SetControlVisibility(win->vBar,boxCtlsVisible,true);
		SetControlVisibility(win->hBar,boxCtlsVisible,true);
		if ((cntl=FindControlByRefCon(win,PREVIEW_TOGGLE_CNTL)) && !GetSuperControl(cntl,&placard))
			SetControlVisibility(placard,boxCtlsVisible,true);
		if ((cntl=FindControlByRefCon(win,PREVIEW_DIVIDE_CNTL)) && !GetSuperControl(cntl,&placard))
			SetControlVisibility(placard,boxCtlsVisible,true);
		if (cntl=FindControlByRefCon(win,kBoxSizeRefCon))
			SetControlVisibility(cntl,boxCtlsVisible,true);
		if (cntl=FindControlByRefCon(win,kConConProfRefCon))
			SetControlVisibility(cntl,boxCtlsVisible,true);
		if (previewPTE = (*toc)->previewPTE)
		{
			//	make preview pane visible/invisible by moving it
			Rect	r;
			short	offset;
			
			PeteRect(previewPTE,&r);
			offset = boxCtlsVisible ? 4000 : -4000;
			OffsetRect(&r,offset,offset);
			PeteDidResize(previewPTE,&r);
		}
		
		if ((*sh)->mailboxView)
		{
			(*sh)->saveBotMargin = win->botMargin;
			(*sh)->savePreviewHi = (*toc)->previewHi;
			SetBotMargin(win,0);
			(*toc)->previewHi = -1;
		}
		else
		{
			(*toc)->previewHi = (*sh)->savePreviewHi;
			SetBotMargin(win,(*sh)->saveBotMargin);
			GetSelectedBoxes(*sh,toc,false);	// This isn't quite the right place, but it's better than nothing
		}
		
		// Make sure to clear pView field if in results
		win->pView = (*sh)->mailboxView ? (*sh)->list : 0;
		
		InvalBotMargin(win);
//		if ((*sh)->mailboxView && !(*sh)->list)
//			InitMailboxList(win,sh);
		SetControlVisibility((*(*sh)->list->hList)->vScroll,!boxCtlsVisible,true);
		
		// make sure the mailbox list is active.  Certain IMAP dialogs might have deactivated it.  -jdboyd 7/26/04
		if (!boxCtlsVisible) LVActivate((*sh)->list, win->isActive);
		
		SetSearchTopMargin(win,sh);
		MyWindowDidResize(win, &oldContR);

		InfiniteClip(GetMyWindowCGrafPtr(win));
		InvalWindowRect(winWP,GetWindowPortBounds(winWP,&rWin));
	}
	else if (button==sp->ctlSrchResults)
	{
		StopSearch(win);
		SetControlValue(button,!GetControlValue(button));
		controlId = kctlSrchResults;
	}
	else if (ClickCriteriaButton(win,sh,button,part))
	{
		StopSearch(win);
		win->isDirty = true;
		SearchSetWTitle(win,sh);
		controlId = kctlCriteria;
	}
	else if (!(*sh)->mailboxView)
		BoxButton(win,button,modifiers,part);	
	else if (button==sp->ctlMatch)
		controlId = kctlMatch;
		
	if (controlId >= 0)
		AuditHit((modifiers&shiftKey)!=0, (modifiers&controlKey)!=0, (modifiers&optionKey)!=0, (modifiers&cmdKey)!=0, false, GetWindowKind(winWP), AUDITCONTROLID(GetWindowKind(winWP),controlId), mouseDown);
}


/************************************************************************
 * ClickCriteriaButton - check for click in a criteria button
 ************************************************************************/
static Boolean ClickCriteriaButton(MyWindowPtr win,SearchHandle sh,ControlHandle cntl,short part)
{
	WindowPtr	winWP = GetMyWindowWindowPtr(win);
	short	i;
	Rect	r;
	
	for(i=0;i<(*sh)->criteriaCount;i++)
	{
		CriteriaInfo	criteria=(*(*sh)->hCriteria)[i];
		short			top = INSET+6+i*kCriteriaHt;
		Boolean			resize = false;

		if (cntl == criteria.ctlCategory)
		{
			// change in category means changing relation and specifier
			short	category = GetControlValue(cntl);
			short	relation = gCategoryTable[category-1].relation;
			short	specifier = gCategoryTable[category-1].specifier;
			
			if (relation != gCategoryTable[criteria.category-1].relation)
			{
				// different relation menu
				DisposeControl(criteria.ctlRelation);
				criteria.ctlRelation = CreateMenuControl(win,win->topMarginCntl,"\p",relation,kControlPopupFixedWidthVariant+kControlPopupUseWFontVariant,1,false);
				resize = true;
			}
			
			if (specifier != gCategoryTable[criteria.category-1].specifier)
			{
				short	defaultSpecifierItem = 0;
				
				DisposeSpecifier(win,sh,&criteria);
				switch (specifier)
				{
					case ssText:
					case ssNumber:
						CreateTextSpecifier(win,&criteria,specifier!=ssText);
						break;
					
					case ssDate:
						SetRect(&r,-4000,-4000,kDateWi-4000,kDateHt-4000);
						criteria.ctlSpecifier = NewControl(winWP,&r,"\p",true,0,0,0,kControlClockDateProc,0);
						//LetsGetSmall(criteria.ctlSpecifier);
						break;
					
					case ssAge:
						//	needs both text and specifier
						CreateTextSpecifier(win,&criteria,true);
						SetControlValue(criteria.ctlRelation,GetRLong(SEARCH_DF_AGE_REL));
						PeteSetString(GetRString(P1,SEARCH_DF_AGE),criteria.pte);
						specifier = SRCH_AGE_UNITS_MENU;
						defaultSpecifierItem = GetRLong(SEARCH_DF_AGE_SPFY);
						break;
						
					default:
						defaultSpecifierItem = 1;
						break;
				}
				if (defaultSpecifierItem)
					criteria.ctlSpecifier = CreateMenuControl(win,win->topMarginCntl,"\p",specifier,kControlPopupFixedWidthVariant+kControlPopupUseWFontVariant,defaultSpecifierItem,false);
				resize = true;
			}
//			else if (specifier==ssText)
//			{
//				// remove any existing text
//				PeteDelete(criteria.pte,0,0x7fffffff);
//			}
			
			// prefill attachment count
			if (category == scAttachCount)
			{
						SetControlValue(criteria.ctlRelation,GetRLong(SEARCH_DF_ATT_REL));
						PeteSetString(GetRString(P1,SEARCH_DF_ATT),criteria.pte);
			}
			criteria.category = category;
			(*(*sh)->hCriteria)[i] = criteria;
			if (resize)
				ResizeCriterion(win,sh,i);
			EmbedSearchCriteriaControls(&criteria,sh);
			return true;
		}
		else if (cntl == criteria.ctlSpecifier)
		{
			if (criteria.category==scDate)
			{
				//	This control needs focus
				SetKeyboardFocus(winWP,cntl,part);
				(*sh)->ctlFocus = cntl;
				RemoveFocus(win);
			}
			return true;
		}
		else if (cntl == criteria.ctlRelation)
			// don't worry about this for now
			return true;
	}
	return false;
}

/************************************************************************
 * SearchLVCallBack - callback function for List View
 ************************************************************************/
static long SearchLVCallBack(ViewListPtr pView, VLCallbackMessage message, long data)
{
	VLNodeInfo	*pInfo;
	OSErr	err = noErr;
	SearchHandle	sh;
	DEC_STATE(sh);
	
	switch (message)
	{
		case kLVAddNodeItems:
		case kLVExpandCollapseItem:
			GetSearchInfo(pView->wPtr,nil,&sh);
			L_STATE(sh);
			if (message==kLVAddNodeItems)
				AddMailboxListItems(pView,data,&(*sh)->expandList);
			else
				SaveExpandStatus((VLNodeInfo *)data,&(*sh)->expandList);
			U_STATE(sh);
			break;

		case kLVGetParentID:
			err = MailboxesLVCallBack(pView, message, data);
			break;
		
		case kLVOpenItem:
			GetSearchInfo(pView->wPtr,nil,&sh);
			MBListOpen((*sh)->list);
			break;

		case kLVQueryItem:
			pInfo = (VLNodeInfo*)data;
			switch (pInfo->query)
			{
				case kQuerySelect:
					return true;
				case kQueryDrag:
				case kQueryRename:
				case kQueryDrop:
				case kQueryDropParent:
				case kQueryDragExpand:
					return false;
				default:
					err = MailboxesLVCallBack(pView, message, data);
			}
			break;
		
		case kLVGetFlags:
			return gLVFlags;
	}
	return err;
}

/************************************************************************
 * InitMailboxList - set up the mailbox list
 ************************************************************************/
static ViewListPtr InitMailboxList(MyWindowPtr win,Boolean useMBSelection)
{
	CGrafPtr	winPort = GetMyWindowCGrafPtr (win);
	ViewListPtr	pList=NuPtr(sizeof(ViewList));
	Rect	r;
	GrafPtr	oldPort;
	
	GetPortBounds(winPort,&r);
	GetPort(&oldPort);
	SetPort_(winPort);

	r.top = win->topMargin;
	if (pList)
	{
		LVNew(pList,win,&r,0,SearchLVCallBack,0);
		if (useMBSelection)
		{
			//	Select same mailboxes as in Mailboxes window
			ViewListPtr	pMBList = MBGetList();
			short	i;
			VLNodeInfo data;
			
			for (i=1;i<=LVCountSelection(pMBList);i++)
			{
				if (LVGetItem(pMBList,i,&data,true))
					LVSelect(pList,data.nodeID,data.name,true);
			}
		}
	}
	SetPort_(oldPort);

	return pList;
}

/**********************************************************************
 * SizeMBList - set size of mailbox list
 **********************************************************************/
static void SizeMBList(MyWindowPtr win,SearchHandle sh)
{
	Rect	r;
	
	if ((*sh)->list)
	{
		CGrafPtr	winPort = GetMyWindowCGrafPtr (win);
		GetPortBounds(winPort,&r);
		r.top = win->topMargin;
		r.bottom -= GROW_SIZE;
		if (!(*sh)->mailboxView)
			OffsetRect(&r,-4000,-4000);	// hide the list
		LVSize((*sh)->list,&r,nil);
	}
}


/************************************************************************
 * SearchIdle - do actual search
 ************************************************************************/
static void SearchIdle(MyWindowPtr win)
{
	SearchHandle	sh;
	TOCHandle	toc;
	short		sumNum;

	GetSearchInfo(win,&toc,&sh);
	if (!(*sh)->mailboxView)
		BoxIdle(win);
	
	SearchSetControls(win, sh);

	if (((*sh)->searchMode & kSearchingLocal) && (!(*sh)->redoTicks || ((*sh)->redoTicks <= TickCount())))
		ProcessSearch(win,toc,sh);
	else if ((*sh)->searchMode & kSearchingIMAP)
		ProcessIMAPSearch(win,toc,sh);
	
	if ((*toc)->previewID && 
		(sumNum=FirstMsgSelected(toc))>=0 && 
		(*toc)->sums[sumNum].offset < 0)
	{
		//	We have an IMAP message in preview pane waiting for download
		TOCHandle	realTOC;
		short		realSumNum;

		realTOC = GetRealTOC(toc,sumNum,&realSumNum);
		UpdateIMAPMailbox(realTOC);
		if ((*realTOC)->sums[realSumNum].offset >= 0)
		{
			//	The message has arrived. Show it.
			(*toc)->previewID = 0;
			
			//	Make sure we don't download it again
			(*toc)->sums[sumNum].offset = (*realTOC)->sums[realSumNum].offset;
		}
	}

	if ((*toc)->resort)
	{
		// Need to resort
		(*toc)->resort = kResortWhenever;
		RedoTOC(toc);
	}
		
	// take care of any incremental IMAP searches
	UpdateIncrementalIMAPSearches();
}

/************************************************************************
 * Call the idle routines of all search windows
 ************************************************************************/
void SearchAllIdle(void)
{
	WindowPtr winWP;
	MyWindowPtr win;
	SearchHandle sh;
	
	for (winWP = GetWindowList(); winWP; winWP = GetNextWindow (winWP)) {
		win = GetWindowMyWindowPtr (winWP);
		if (IsSearchWindow(winWP))
		{
			GetSearchInfo(win,nil,&sh);
			if ((*sh)->searchMode) SearchIdle(win);
		}
	}
}


/************************************************************************
 * ProcessSearch - do actual search
 ************************************************************************/
static void ProcessSearch(MyWindowPtr win,TOCHandle toc,SearchHandle sh)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	uLong		endTicks,maxTicks;
	SearchMarker	curr;
	Boolean		stop = false;
	TOCHandle 	aTOC;
	Boolean		foundTOC = false;
	FSSpec		spec;
	Str255		s;
	Str63		name;
	GrafPtr oldPort;
	Boolean		inFront=false,match;
	Boolean		invalidateBoxSize = false;
#ifdef VALPHA
	long		startTicks;
#endif

#if __profile__
//	ProfilerSetStatus(true);
#endif

#ifdef VALPHA
	startTicks = TickCount();
#endif

	GetPort(&oldPort);
	SetPort_(GetWindowPort(winWP));

	if ((*sh)->bulkSearching)
	{
		DoBulkSearch(sh);
		if ((*sh)->bulkSearching)
			//	bulk search was interrupted again
			return;
	}
		

	// validate the saved toc, if any
	curr = (*sh)->curr;
	if (!(*sh)->searchResults && curr.tocH)
	{
		if (!curr.opened)
		{
			// make sure our saved TOC is still valid		
			for (aTOC=TOCList;aTOC;aTOC=(*aTOC)->next)
				if (aTOC == curr.tocH) foundTOC = true;
		}
		else
		{
			ASSERT(*curr.tocH!=(TOCPtr)-1);	// ACK!!!  Freed?
			foundTOC = *curr.tocH!=(TOCPtr)-1;
		}
		if (!foundTOC)
		{
			// this TOC has been disposed of, need to open it again
			GetBoxTOC(sh,&curr);
			(*sh)->curr = curr;
		}
	}
	
	if (InBG)
		maxTicks = kInBGTicks;
	else
	{
		if (FrontWindow_()==winWP)
		{
			maxTicks = kFrontWinTicks;
			inFront = true;
		}
		else
			maxTicks = kBgdWinTicks;
	}
	endTicks = TickCount() + maxTicks;
	while(TickCount() < endTicks && !EventPending() && (!inFront || !CommandPeriod))
	{
		MSumType sum;
		Boolean	found=false;
		
		CheckChaseArrows(sh);
		while (!curr.tocH || curr.message >= (*curr.tocH)->count)
		{
			//	Done with this mailbox
			if ((*sh)->searchResults)
			{
				//	searching through the results. we're finished
				if (curr.tocH) goto done;
				curr.tocH = toc;
				curr.message = 0;
			}
			else
			{
				//	do next mailbox
				short		i;
				CriteriaHandle	hCriteria = (*sh)->hCriteria;;
				
				FinishedTOC(&curr);
				curr.box++;
				curr.tocH = 0;
				curr.message = 0;
				if (curr.box>=GetHandleSize_((*sh)->BoxCount)/sizeof(BoxCountElem))
				{
					//	end of mailboxes!
					(*sh)->redoTicks = 0;
					if ((*sh)->redoBoxCount)
					{
						SearchPtr		sp;
						
						//	There were some mailboxes that were busy. Try them again
						ZapHandle((*sh)->BoxCount);
						sp = *sh;
						sp->BoxCount = sp->redoBoxCount;
						sp->redoBoxCount = nil;
						Zero(sp->curr);
						sp->curr.lastBoxSpec = -2;
						sp->curr.box = -1;
						(*sh)->redoTicks = TickCount() + 120;	//	Try again in a couple of seconds
						goto dontstop;
					}
					else
						//	We're all done
						goto done;
				}

				//	reset all hits accumulators
				for(i=0;i<(*sh)->criteriaCount;i++)
					SetHits(sh,i,nil);
				(*sh)->bulkData.didBulkSearch = false;
				
				//	If we are searching for a string in a single criterion, do the bulk
				//	search now. If nothing found, skip this mailbox
				if ((*sh)->criteriaCount==1 && 
					(*hCriteria)->searchString &&
					(*hCriteria)->category != scSummary)
				{					
					PCopy(s,*(*hCriteria)->searchString);
					if (GetBoxCountSpec(sh,curr.box,&spec))
					{
						DisplayMBName(sh,spec.name);
						if (SearchBulk(&spec,sh) == opWrErr)
						{
							//	This mailbox is busy right now. We'll come back to it.
							if (!(*sh)->redoBoxCount) (*sh)->redoBoxCount = NuHandle(0);
							if ((*sh)->redoBoxCount)
							{
								BoxCountHandle	boxList = (*sh)->BoxCount ? (*sh)->BoxCount : BoxCount;
								BoxCountElem bce = (*boxList)[curr.box];
								PtrPlusHand_(&bce,(*sh)->redoBoxCount,sizeof(bce));
							}
							break;
						}
						if ((*hCriteria)->hits.offset==0 && !(*sh)->bulkSearching && !(*sh)->noBulkSearch)
						{
							//	skip this mailbox, nothing found in bulk search
							break;
						}
					}
				}
				
				if (GetBoxTOC(sh,&curr))
				{
					(*sh)->curr = curr;
					GetMailboxName((*sh)->curr.tocH,0,name);
					DisplayMBName(sh,name);
				}
				if ((*sh)->bulkSearching)
					break;	//	Interrupted a bulk search.
			}
		}
		
		if (!curr.tocH)
			continue;	//	Did a bulk search and found nothing
		
		// search summary
		sum = (*curr.tocH)->sums[curr.message];
		(*sh)->curr = curr;	
#ifdef VALPHA
		(*sh)->nSearched++;
#endif
		match = SearchMessage(win,&sum,sh,&curr,&stop,false);
		if (stop)
			goto done;
		if ((*sh)->bulkSearching)
			break;	//	Interrupted a bulk search.
		if ((*sh)->searchResults)
		{
			if (match)
				(*sh)->nHits++;
			else
			{
				//	no match on searching results. remove this one.
				DeleteSum(curr.tocH,curr.message);
				invalidateBoxSize = true;
				curr.message--;			
			}
		}
		else if (match)
		{
			// add a message summary to search window toc
			if (AddSearchResult(sh,toc,curr.tocH,curr.message,curr.lastBoxSpec != curr.box,-1))
			{
				stop = true;
				goto done;	//	error
			}
			curr.lastBoxSpec = curr.box;
			invalidateBoxSize = true;
		}
		curr.message++;
		if (stop) goto done;
	}
	stop = inFront && (CommandPeriod || HasCommandPeriod());
	(*sh)->curr = curr;	

	if (stop)
	{
 done:
		(*sh)->curr = curr;
		if ((*sh)->searchMode != kSearchingLocal)
		{
			//	Stop local search, continue with IMAP
			(*sh)->searchMode &= ~kSearchingLocal;
			SetTextControlText((*sh)->ctlMailBox,GetRString(s,SEARCHING_SERVER),nil);
		}
		else
			//	Stop everything
			StopSearch(win);
	}
  dontstop:
	if (invalidateBoxSize)
		UpdateBoxSize(win);

#if __profile__
//	ProfilerSetStatus(false);
#endif
	SetPort(oldPort);
#ifdef VALPHA
	(*sh)->nTicks += TickCount()-startTicks;
#endif
}

/**********************************************************************
 * CopySum - copy a summary
 **********************************************************************/
void CopySum(MSumPtr sumFrom, MSumPtr sumTo, short virtualMBIdx)
{
	short	saveSerialNum = sumTo->serialNum;
	Boolean	saveSelected = sumTo->selected;
	Handle	saveCache = sumTo->cache;
	
	*sumTo = *sumFrom;
	sumTo->cache = saveCache;
	sumTo->mesgErrH = nil;
	sumTo->messH = nil;
	sumTo->selected = saveSelected;
	sumTo->u.virtualMess.virtualMBIdx = virtualMBIdx;
	sumTo->u.virtualMess.linkSerialNum = sumFrom->serialNum;
	sumTo->serialNum = saveSerialNum;
}

/**********************************************************************
 * AddSearchResult - add this search result
 **********************************************************************/
static OSErr AddSearchResult(SearchHandle sh, TOCHandle srchTOC, TOCHandle tocH, short sumNum, Boolean addSpec, short specIdx)
{
	MSumType	sum;

	Zero(sum);

	if (addSpec)
	{
		FSSpecHandle specList = (*srchTOC)->mailbox.virtualMB.specList;
		FSSpec	spec;

		if (!specList)
			(*srchTOC)->mailbox.virtualMB.specList = specList = NuHandle(0);
		Zero(spec);
		spec = GetMailboxSpec(tocH,sumNum);
		PtrAndHand(&spec,(Handle)specList,sizeof(spec));
		(*srchTOC)->mailbox.virtualMB.specListCount++;
	}
		
	CopySum(&(*tocH)->sums[sumNum], &sum, specIdx<0 ? (*srchTOC)->mailbox.virtualMB.specListCount-1 : specIdx);
	sum.serialNum = (*srchTOC)->nextSerialNum++;
	if ((*srchTOC)->count)
	{
		OSErr	err;
		
		if (err = PtrPlusHand_(&sum,srchTOC,sizeof(sum)))
		{
			WarnUser(MEM_ERR,err);
			return err;
		}
	}
	else
		(*srchTOC)->sums[0] = sum;

	InvalSum(srchTOC,(*srchTOC)->count++);			
	(*sh)->nHits++;

	return noErr;
}


/**********************************************************************
 * UpdateBoxSize - redisplay box size (# of messages in search window)
 **********************************************************************/
static void UpdateBoxSize(MyWindowPtr win)
{
	ControlHandle	boxSizeCntl;
	Rect			rCtl;
	
	if (boxSizeCntl = FindControlByRefCon(win,kBoxSizeRefCon))
	{
		GetControlBounds(boxSizeCntl,&rCtl);
		SetPort_(GetMyWindowCGrafPtr(win));	//	someone keeps changing this
		InvalWindowRect(GetMyWindowWindowPtr(win),&rCtl);
	}
}

/**********************************************************************
 * SearchText - search for string in text
 **********************************************************************/
static Boolean SearchText(StringPtr value,UPtr pText,long len,long offset,short relation)
{
	switch (relation)
	{
		case strContains:
			return SearchStrPtr(value,pText,offset,len,false,false,nil) >= 0;
		case strContainsWord:
			return SearchStrPtr(value,pText,offset,len,false,true,nil) >= 0;		
		case strDoesNotContain:
			return SearchStrPtr(value,pText,offset,len,false,false,nil) < 0;
		case strIs:
			return *value==len && SearchStrPtr(value,pText,offset,*value,false,false,nil) >= 0;
		case strIsNot:
			return *value!=len || SearchStrPtr(value,pText,offset,*value,false,false,nil) < 0;
		case strStarts:
			return *value<=len && SearchStrPtr(value,pText,offset,*value,false,false,nil) >= 0;
		case strEnds:
			return *value<=len && SearchStrPtr(value,pText,offset+len-*value,len,false,false,nil) >= 0;
		case strRegExp:
			return SearchRegExpPtr(value,pText,offset,len) >= 0;
	}
	return false;
}

/**********************************************************************
 * SearchString - search for string in string
 **********************************************************************/
static Boolean SearchString(StringPtr value,StringPtr s,short relation)
{
	return SearchText(value,s+1,*s,0,relation);
}

/**********************************************************************
 * SearchMessage - search this message summary, return true if found
 **********************************************************************/
static Boolean SearchMessage(MyWindowPtr win,MSumPtr sumP,SearchHandle sh,SearchMarker *curr,Boolean *stop,Boolean sumOnly)
{
	short	i;
//	MyWindowPtr messWin;
	CriteriaInfo	criteria;
	long		value;
	DateTimeRec	date,currDate;
	long		secs,sumSecs,result;
	Str255		string;
	short		label;
	TOCHandle	tocH = curr->tocH;
	long		mNum = curr->message;
	FSSpec		spec;
	Boolean		found;
	Handle		hText;
	PETEHandle	tempPte;
	long		textSize;
	Boolean		disposeText;
	Ptr			pText;
	long		offset;
	short		hdrID;
	Boolean		notInBulk;
	FindAttData attData;
	Str127 searchString;
	
	// shouldn't be here unless the search has already been setup
	ASSERT((*sh)->setup);
	if (!(*sh)->setup) return false;
	
	for(i=0;i<(*sh)->criteriaCount;i++)
	{
		criteria=(*(*sh)->hCriteria)[(*sh)->searchOrder[i]];
		
		found = false;
		if (criteria.searchString && !**criteria.searchString && criteria.relation!=strRegExp)
			//	ignore any blank search strings
			continue;
		if (sumOnly && !(gCategoryTable[criteria.category-1].flags & kNeedsSum))
			continue;	//	Doing summaries only. Did IMAP server search
			
		switch (criteria.category)
		{			
			case scSummary:				
				// if the summary is utf8, use the utf8 search string
				if ((sumP->flags&FLAG_UTF8)!=0 && criteria.utf8SearchString)
					PSCopy(searchString,*criteria.utf8SearchString);
				else
					PSCopy(searchString,*criteria.searchString);

				// search subject, from, date, and label in summary info
				if (criteria.relation == strDoesNotContain)
					//	for "does not contain" all must be true for a true result
					found = SearchString(searchString,sumP->subj,criteria.relation) &&
						SearchString(searchString,sumP->from,criteria.relation) &&
						SearchString(searchString,ComputeLocalDate(sumP,string),criteria.relation);				
				else
					found = SearchString(searchString,sumP->subj,criteria.relation) ||
						SearchString(searchString,sumP->from,criteria.relation) ||
						SearchString(searchString,ComputeLocalDate(sumP,string),criteria.relation);
				if (!found && (label = SumColor(sumP)))
				{
					RGBColor color;
					MyGetLabel(label,&color,string);
					if (SearchString(*criteria.searchString,string,criteria.relation))
						found = true;
				}
				break;

			case scStatus:
				value = sumP->state;
				goto DoEqual;
				
			case scPriority:
				value = sumP->priority;
				if (!value) value = Display2Prior(3);
				result=ShortCompare(criteria.specifier,value); // for priority to backward compare
				goto DoCompare;
			
			case scAttachCount:
				if (sumP->flags & FLAG_HAS_ATT)
				{
					if (criteria.specifier==0)
						// Don't need to count them. If compare to zero, any number will do.
						value=1;
					else
					{
						value = 0;
						CacheMessage(tocH,mNum);
						if (hText=(*tocH)->sums[mNum].cache)
						{
							HNoPurge(hText);
							InitAttachmentFinder(&attData,hText,true,tocH,&(*tocH)->sums[mNum]);
							while (GetNextAttachment(&attData,&spec))
								value++;
						}
					}
				}
				else
					value = 0;
			  DoCompareValue:
				result=ShortCompare(value,criteria.specifier);
				goto DoCompare;
				
			case scLabel:
				value = SumColor(sumP);
			  DoEqual:
			  	found = criteria.specifier == value ? criteria.relation == srIs : criteria.relation == srIsNot;
				break;
				
			case scAge:
				sumSecs = sumP->seconds + criteria.zoneSecs;	// Use local time
				SecondsToDate(sumSecs,&date);
				GetDateTime(&secs);
				GetTime(&currDate);
				result = DateTimeDifference(&date,&currDate,secs-sumSecs,criteria.specifier) - criteria.misc;
				goto DoCompare;

			case scDate:
				//	Convert GMT seconds to local or sender's time based on preference
				SecondsToDate(sumP->seconds+(criteria.useSenderZone?60*sumP->origZone:criteria.zoneSecs),&date);
				if (!(result=ShortCompare(date.year,criteria.date.year)))
					if (!(result=ShortCompare(date.month,criteria.date.month)))
						result=ShortCompare(date.day,criteria.date.day);
			  DoCompare:
				switch (criteria.relation)
				{
					case srIs:
						found = result==0;
						break;
					case srIsNot:
						found = result!=0;
						break;					
					case srGreater:
						found = result > 0;
						break;										
					case srLess:
						found = result < 0;
						break;
				}									
				break;
			
			case scSize:
				value = (sumP->length+1023)/1024;
				goto DoCompareValue;
			
			case scJunkScore:
				value = sumP->spamScore;
				goto DoCompareValue;
			
			case scPersonality:
				if (HasFeature (featureMultiplePersonalities)) {
					value = sumP->popPersId;
					goto DoEqual;
				}
			case scAttachNames:
				if (!(sumP->flags & FLAG_HAS_ATT))
					// no attachments
					break;
				//	fall thru
			case scAnywhere:
			case scHeaders:
			case scBody:
			case scTo:
			case scFrom:
			case scSubject:
			case scCC:
			case scBCC:
			case scAnyRecipient:
			// search message
				notInBulk = !(*sh)->searchResults && !(*sh)->noBulkSearch && !MessPreFind(sh,*criteria.searchString,(*sh)->searchOrder[i]);
				if ((*sh)->bulkSearching)
					return false;	//	bulk search was interrupted
				if (notInBulk)
				{
					if (criteria.relation == strDoesNotContain)
						found = true;
					break;
				}
				
				if (criteria.category==scAnywhere && 
					criteria.relation==strContains &&
					!(*sh)->noBulkSearch && 
					!criteria.utf8SearchString && 
					!(*sh)->searchResults && 
					!((*tocH)->sums[mNum].flags & FLAG_RICH) &&
					!((*tocH)->sums[mNum].opts & OPT_HTML))
				{
					//	if search anywhere, contains, and not HTML or RICH, we don't need to
					//	load and search through the message, bulk search is good enough
					found = true;
					break;
				}
				
				//	get text of message
				tempPte = nil;
				
				{
					//	need to read in message text
					TOCHandle	realTOC;
					short		realSumNum;

					realTOC = GetRealTOC(tocH,mNum,&realSumNum);				
						
					// if this is an IMAP message, make sure it's been downloaded
					if ((*realTOC)->imapTOC) EnsureMsgDownloaded(realTOC,realSumNum,false);

					textSize = (*realTOC)->sums[realSumNum].length;
					disposeText = true;
					
					CacheMessage(realTOC,realSumNum);
					hText = nil;
					
					if ((*realTOC)->sums[realSumNum].cache)
					{
						HNoPurge((*realTOC)->sums[realSumNum].cache);
						{
							//	If rich or HTML, need text stripped of markup
							if (criteria.fudge8bit || (*sh)->noBulkSearch ||
									(*sh)->searchResults && (((*realTOC)->sums[realSumNum].flags & FLAG_RICH) ||
								((*realTOC)->sums[realSumNum].opts & OPT_HTML+OPT_FLOW+OPT_CHARSET)))
							{
								if (!PeteCreate(win,&tempPte,0,nil))
								{
									PeteCalcOff(tempPte);
									if (!InsertRich((*realTOC)->sums[realSumNum].cache,0,-1,-1,true,tempPte,nil,0!=((*realTOC)->sums[realSumNum].opts & OPT_DELSP)))
									{
										PeteGetRawText(tempPte,&hText);
									}
								}
							}
							else
							{
								hText = DupHandle((*realTOC)->sums[realSumNum].cache);
								disposeText = true;
							}
						}
						HPurge((*realTOC)->sums[realSumNum].cache);
					}
				}

				if (!hText)
					return false;

				pText = LDRef(hText);
				textSize = GetHandleSize(hText);
				offset=0;

				switch (criteria.category)
				{
					// search message
					case scAnywhere:
					  DoSearchMsg:
						found = SearchText(*criteria.searchString,pText,textSize,offset,criteria.relation);
						break;								
					case scHeaders:
						textSize = BodyOffset(hText);
						goto DoSearchMsg;
					case scBody:				
						offset = BodyOffset(hText);
						goto DoSearchMsg;
					
					// search in a header
					case scTo:
						hdrID = TO_HEAD+HEADER_STRN;
					  DoSearchHdr:
						found = SearchHeader(hdrID,hText,textSize,*criteria.searchString,criteria.relation);
						break;
					case scFrom:
						hdrID = FROM_HEAD+HEADER_STRN;
					 	goto DoSearchHdr;
					case scSubject:
						hdrID = SUBJ_HEAD+HEADER_STRN;
					 	goto DoSearchHdr;
					case scCC:
						hdrID = CC_HEAD+HEADER_STRN;
					 	goto DoSearchHdr;
					case scBCC:
						hdrID = BCC_HEAD+HEADER_STRN;
					 	goto DoSearchHdr;

					// search any recipient
					case scAnyRecipient:
						found = SearchHeader(TO_HEAD+HEADER_STRN,hText,textSize,*criteria.searchString,criteria.relation) ||
								SearchHeader(CC_HEAD+HEADER_STRN,hText,textSize,*criteria.searchString,criteria.relation) ||
								SearchHeader(BCC_HEAD+HEADER_STRN,hText,textSize,*criteria.searchString,criteria.relation);
						break;
					
					case scAttachNames:
						InitAttachmentFinder(&attData,hText,true,tocH,&(*tocH)->sums[mNum]);
						while (GetNextAttachment(&attData,&spec))
						{
							if (found=SearchString(*criteria.searchString,spec.name,criteria.relation))
								break;
						}
						break;
				}
				
				//	Dispose of text
				if (tempPte)
					PeteDispose(win,tempPte);
				else if (disposeText)
					ZapHandle(hText);
				else
					UL(hText);
				break;
		}
		
		if (found)
		{
			if ((*sh)->matchAny)
				return true;	//	Found one, that's good enough
		}
		else
		{
			if (!(*sh)->matchAny)
				return false;	//	This one not found, need to match them all
		}
	}
			
	return (*sh)->matchAny ? false : true;
}


/**********************************************************************
 * DateTimeDifference - return discrete difference between 2 times
 **********************************************************************/
long DateTimeDifference(DateTimeRec *date,DateTimeRec *currDate,long seconds,short units)
{
	switch(units)
	{
		case sauMinutes:	return seconds/60 + (currDate->minute<date->minute?1:0);
		case sauHours:	return DateTimeDifference(date,currDate,seconds,sauMinutes)/60 + ((seconds>=0&&currDate->minute<date->minute)?1:0);
		case sauDays:	return DateTimeDifference(date,currDate,seconds,sauHours)/24 + ((seconds>=0&&currDate->hour<date->hour)?1:0);
		case sauWeeks:	return DateTimeDifference(date,currDate,seconds,sauDays)/7 + ((seconds>=0&&currDate->dayOfWeek<date->dayOfWeek)?1:0);
		case sauMonths:	return DateTimeDifference(date,currDate,seconds,sauYears)*12 + (currDate->month - date->month);
		case sauYears:	return currDate->year - date->year;
	}
	return 0;	/* mtc sez - good as any! */
}

/**********************************************************************
 * ShortCompare - compare 2 short, return 0 if equal, -1 if value1 < value2, 1 if value1 2 value2
 **********************************************************************/
static  short ShortCompare(short value1,short value2)
{
	if (value1 == value2) return 0;
	if (value1 < value2) return -1;
	return 1;
}

/**********************************************************************
 * SearchHeader - search a header for the string
 **********************************************************************/
static Boolean SearchHeader(short header,Handle text,long size,StringPtr searchString,short relation)
{
	Str32	s;
	long	len = size;
	UPtr found;
	
	GetRString(s,header);
	if (found = FindHeaderString(*text,s,&len,False))
		return SearchText(searchString,found,len,0,relation);
	return false;
}

/**********************************************************************
 * MessPreFind - do a quick scan to see if a message might contain
 *  the desired string.  We cop out on anything weird and return True.
 *  The only way we return false is if we read up the message and it does
 *  not contain the desired string
 **********************************************************************/
static Boolean MessPreFind(SearchHandle sh,StringPtr s,short criteriaIdx)
{
	FSSpec	spec;
	Accumulator	a;
	
	if ((*sh)->noBulkSearch) return true;
	if (!(*sh)->bulkData.didBulkSearch)
	{
		spec = (*(*sh)->curr.tocH)->mailbox.spec;
		SearchBulk(&spec,sh);
		if ((*sh)->bulkSearching)
			return false;	//	bulk search was interrupted
	}
	GetHits(sh,criteriaIdx,&a);
	if (a.data)
		return(FindInHits(sh,&a));
	else
		return false;
}

/**********************************************************************
 * FindInHits - do a find in the hit database
 **********************************************************************/
static Boolean FindInHits(SearchHandle sh,AccuPtr a)
{
	long start = (*(*sh)->curr.tocH)->sums[(*sh)->curr.message].offset;
	long stop = start + (*(*sh)->curr.tocH)->sums[(*sh)->curr.message].length;
	long *hit, *hitEnd;
	
	hitEnd = (long *)(*a->data + a->offset);
	for (hit=(long *)*a->data;hit<hitEnd;hit++)
	{
		if (stop<*hit) return(False);	// we're past the message
		if (*hit<start) continue;	// we're not there yet
		return(True);	// start <= *hit <= stop; we found it!
	}
	return(False);	// never found
}

/**********************************************************************
 * SearchBulk - search a file, but do it fast
 *	string - the string to search for
 *	spec - the file to search in
 *	allOffsets - accumulator (may be nil) that will get all hits
 **********************************************************************/
static OSErr SearchBulk(FSSpecPtr spec,SearchHandle sh)
{
	BulkSearchInfo	bulkData;
	OSErr err = fnfErr;
	FSSpec localSpec;
	
	// are we just pretending?
	if ((*sh)->noBulkSearch)
	{
		bulkData.didBulkSearch = true;
		return noErr;
	}
	
	/*
	 * allocate stuff
	 */
	Zero(bulkData.buf[0]);
	Zero(bulkData.buf[1]);
	bulkData.buf[0].buffer = NewIOBHandle(1 K, kIOBufSize);
	bulkData.buf[1].buffer = NewIOBHandle(1 K, kIOBufSize);
	bulkData.buf[0].pb = NuPtr(sizeof(ParamBlockRec));
	bulkData.buf[1].pb = NuPtr(sizeof(ParamBlockRec));
	bulkData.buf[0].free = bulkData.buf[1].free = True;
	bulkData.fullOffset = -1;
			
	if (bulkData.buf[0].buffer && bulkData.buf[1].buffer)
	{
		// record buffer sizes
		bulkData.buf[0].bSize = GetHandleSize(bulkData.buf[0].buffer);
		bulkData.buf[1].bSize = GetHandleSize(bulkData.buf[1].buffer);

		// open the file
		// open with write access also so nothing else (especially mailbox
		// compaction) tries to open and change it while we are searching
		if (!(err=AFSpOpenDF(spec,&localSpec,fsRdWrPerm,&bulkData.refN)))
		{
			short	i;
			
			// record initial state
			bulkData.spot = 0;
			bulkData.bulkSearchCriteriaIdx = 0;
			GetEOF(bulkData.refN,&bulkData.len);
			bulkData.len -= bulkData.spot;
			bulkData.didBulkSearch = true;
			
			// build list of criteria to search
			bulkData.nBulkSearchCriteria = 0;
			for(i=0;i<(*sh)->criteriaCount;i++)
			{
				CriteriaInfo	criteria=(*(*sh)->hCriteria)[i];
				
				if (gCategoryTable[criteria.category-1].flags & kNeedsMsg)
					bulkData.bulkSearchCriteria[bulkData.nBulkSearchCriteria++] = i;
			}
					
			(*sh)->bulkSearching = true;
			(*sh)->bulkData = bulkData;
			DoBulkSearch(sh);
		}
		else
		{
			//	File error
			ZapHandle(bulkData.buf[0].buffer);
			ZapHandle(bulkData.buf[1].buffer);
			DisposePtr((Ptr)bulkData.buf[0].pb);
			DisposePtr((Ptr)bulkData.buf[1].pb);
		}
	}
	return err;
}

/**********************************************************************
 * StopBulkSearch - stop bulk searching
 **********************************************************************/
static void StopBulkSearch(SearchHandle sh)
{
	if (!(*sh)->bulkSearching)
		return;
	
	FSClose((*sh)->bulkData.refN);
	
	/*
	 * dump the buffers
	 */
	ZapHandle((*sh)->bulkData.buf[0].buffer);
	ZapHandle((*sh)->bulkData.buf[1].buffer);
	DisposePtr((Ptr)(*sh)->bulkData.buf[0].pb);
	DisposePtr((Ptr)(*sh)->bulkData.buf[1].pb);
	(*sh)->bulkSearching = false;
}

/**********************************************************************
 * DoBulkSearch - do bulk searching
 **********************************************************************/
static void DoBulkSearch(SearchHandle sh)
{
	BulkSearchInfo	bulkData = (*sh)->bulkData;
	OSErr	err = noErr;
	Str255 strings[2];
	short curString;
	UPtr string;
	
	while ((!err && bulkData.len>0) || !bulkData.buf[0].free || !bulkData.buf[1].free)
	{
		short i;
 		
		if (EventPending())
		{
			//	Need to go away for a while to process this event
			(*sh)->bulkData = bulkData;
//			SetHits(sh,bulkData.bulkSearchCriteria[bulkData.bulkSearchCriteriaIdx],&hits);
			return;
		}
//				CycleBalls();
		CheckChaseArrows(sh);
		
		/*
		 * fill the buffers if need be
		 */
		for (i=0;i<sizeof(bulkData.buf)/sizeof(bulkData.buf[0]);i++)
			if (bulkData.len && !err && bulkData.buf[i].free && bulkData.len)
			{
				ParmBlkPtr pb = bulkData.buf[i].pb;
				Zero(*pb);
				bulkData.buf[i].offset = bulkData.spot;
				bulkData.buf[i].size = MIN(bulkData.len,bulkData.buf[i].bSize);
				bulkData.spot += bulkData.buf[i].size;
				bulkData.len -= bulkData.buf[i].size;
				
				if (SyncRW)
				{
					// boring synch stuff
					err = FSRead(bulkData.refN,&bulkData.buf[i].size,LDRef(bulkData.buf[i].buffer));
					UL(bulkData.buf[i].buffer);
					pb->ioParam.ioResult = err;
					if (!err) bulkData.buf[i].free = False;
				}
				else
				{
					// zippy asynch stuff
					pb->ioParam.ioRefNum = bulkData.refN;
					pb->ioParam.ioBuffer = LDRef(bulkData.buf[i].buffer);
					pb->ioParam.ioReqCount = bulkData.buf[i].size;
					pb->ioParam.ioResult = inProgress;
					pb->ioParam.ioPosMode = fsFromStart;
					pb->ioParam.ioPosOffset = bulkData.buf[i].offset;
					PBReadAsync(pb);
					bulkData.buf[i].pending = True;
					bulkData.buf[i].free = False;
				}
			}
		
		/*
		 * check to see if any i/o has completed
		 */
		for (i=0;i<sizeof(bulkData.buf)/sizeof(bulkData.buf[0]);i++)
			if (bulkData.buf[i].pending && bulkData.buf[i].pb->ioParam.ioResult!=inProgress)
			{
				if (!err) err = bulkData.buf[i].pb->ioParam.ioResult;
				bulkData.buf[i].pending = False;
				UL(bulkData.buf[i].buffer);
			}
		
		/*
		 * do we have a buffer to search?
		 */
		i = -1;
		if (READY(0))
		{
			// buffer 0 is ready.  How about buffer 1?
			if (READY(1))
				// both ready.  take lesser
				i = (bulkData.buf[0].offset < bulkData.buf[1].offset) ? 0 : 1;
			else
				i = 0;	// just buffer 0 is ready
		}
		else if (READY(1)) i = 1;	// just buffer 1 is ready
		
		/*
		 * search
		 */
		if (i>=0)
		{
			if (err) bulkData.buf[i].free = True;	// backing out
			else
			{
				long tempOffset;
				long bufLen = GetHandleSize(bulkData.buf[i].buffer);
				Boolean regexp;

				for(;bulkData.bulkSearchCriteriaIdx < bulkData.nBulkSearchCriteria;bulkData.bulkSearchCriteriaIdx++)
				{
					short	criteriaIdx = bulkData.bulkSearchCriteria[bulkData.bulkSearchCriteriaIdx];
					StringHandle	searchString;
					Boolean	match = false;
					Accumulator		hits;
	
					// set up for this criterion
					GetHits(sh,criteriaIdx,&hits);
					if (!hits.data)
					{
						err = AccuInit(&hits);
						SetHits(sh,criteriaIdx,&hits);
					}
					ASSERT(!err);
					if (err) break;
					searchString = (*(*sh)->hCriteria)[criteriaIdx].searchString;
					regexp = (*(*sh)->hCriteria)[criteriaIdx].relation==strRegExp;
					curString = 1;	// assume we'll skip the utf8 string
					if (!regexp)
					{
						if (searchString && **searchString)
						{
							PCopy(strings[1],*searchString);
							NormalizeSearchString(strings[1]);
							if ((*(*sh)->hCriteria)[criteriaIdx].utf8SearchString && **(*(*sh)->hCriteria)[criteriaIdx].utf8SearchString)
							{
								PCopy(strings[0],*(*(*sh)->hCriteria)[criteriaIdx].utf8SearchString);
								NormalizeSearchString(strings[0]);
								curString = 0;
							}
						}
						else
							continue;
					}
				
					// wrap this in a loop, so we can look for both normal & utf8 hits
					do
					{
						string = strings[curString];
						tempOffset = 0;
						while (0<=(tempOffset= regexp ? SearchRegExpHandle(*searchString,bulkData.buf[i].buffer,tempOffset) : SearchStrText(string,(*bulkData.buf[i].buffer),bufLen,tempOffset)))
						{
							Ptr	pData;
							
							// string found
							bulkData.fullOffset = tempOffset + bulkData.buf[i].offset;
							err = AccuAddSortedLong(&hits,bulkData.fullOffset);
							tempOffset++;
							match=true;
							if (err) break;
							
							//	for regular expressions, start at the beginning of the next line
							if (regexp)
							{
								for(pData=*bulkData.buf[i].buffer;tempOffset<bufLen;)
									if (pData[tempOffset++] == returnChar)
										break;
								if (tempOffset >= bufLen)
									break;	//	end of buffer
							}
						}
					}
					while (++curString<2);
					
					if (match)
						SetHits(sh,criteriaIdx,&hits);	// save updated hits
				}
				bulkData.bulkSearchCriteriaIdx = 0;
				bulkData.buf[i].free = True;
			}
		}
	}

	//	Done!
	StopBulkSearch(sh);
}

/**********************************************************************
 * GetBoxCountSpec - get file spec for mailbox from BoxCount
 **********************************************************************/
static Boolean GetBoxCountSpec(SearchHandle sh, short boxNum, FSSpecPtr spec)
{
	return GetBoxCountSpecLo((*sh)->BoxCount ? (*sh)->BoxCount : BoxCount,boxNum,spec);
}

/**********************************************************************
 * GetBoxCountSpec - get file spec for mailbox from BoxCount
 **********************************************************************/
static Boolean GetBoxCountSpecLo(BoxCountHandle boxList, short boxNum, FSSpecPtr spec)
{
	short menuId;
	
	if (boxNum>=GetHandleSize_(boxList)/sizeof(BoxCountElem)) return false;
	spec->parID = (*boxList)[boxNum].dirId;
	spec->vRefNum = (*boxList)[boxNum].vRef;
	menuId = (spec->parID==MailRoot.dirId && SameVRef(spec->vRefNum,MailRoot.vRef)) ? MAILBOX_MENU : FindDirLevel(spec->vRefNum,spec->parID);
	MailboxMenuFile(menuId,(*boxList)[boxNum].item,spec->name);
	if (IsIMAPCacheFolder(spec)) spec->parID = SpecDirId(spec);
	return true;
}

/**********************************************************************
 * GetBoxTOC - get TOC from BoxCount or my own BoxCount
 **********************************************************************/
static TOCHandle GetBoxTOC(SearchHandle sh,SearchMarker *which)
{
	FSSpec spec;
	
	which->tocH = nil;
	which->opened = false;
	
	if (GetBoxCountSpec(sh,which->box,&spec))
		which->tocH = GetSpecTOC(&spec,&which->opened);
	return which->tocH;
}

/**********************************************************************
 * GetSpecTOC - get TOC from file spec
 **********************************************************************/
static TOCHandle GetSpecTOC(FSSpecPtr spec,Boolean *opened)
{
	FSSpec newSpec;
	TOCHandle	tocH = nil;
	
	*opened = false;
	if (!(tocH=FindTOC(spec)))
	{
		// didn't find TOC, need to open it
		if (IsAlias(spec,&newSpec) && SameSpec(spec,&newSpec)) return(nil);
		if (tocH = CheckTOC(&newSpec))
			*opened = true;
	}
	return tocH;
}

/**********************************************************************
 * FinishedTOC - we're done with this TOC, may need to dispose of it
 **********************************************************************/
static void FinishedTOC(SearchMarker *which)
{
	if (which->tocH && which->opened)
	{
		if (TOCIsDirty(which->tocH))	WriteTOC(which->tocH);
		BoxFClose(which->tocH,true);
		ZapHandle(which->tocH);
		which->opened = false;
	}
	which->tocH = nil;
}

/**********************************************************************
 * CreateTextSpecifier - add a text specifier
 **********************************************************************/
static void CreateTextSpecifier(MyWindowPtr win,CriteriaInfo *pCriteria,Boolean number)
{
	PETEDocInitInfo pdi;
	PETEHandle		previewPTE;
	TOCHandle	tocH;
	DECLARE_UPP(SearchTextDraw,ControlUserPaneDraw);

	DefaultPII(win,false,peNoStyledPaste+peClearAllReturns,&pdi);
	SetRect(&pdi.inRect,-4000,-4000,-3900,-3900);
	if (!PeteCreate(win,&pCriteria->pte,peNoStyledPaste+peClearAllReturns,&pdi))
	{
		PeteFontAndSize(pCriteria->pte,SmallSysFontID(),SmallSysFontSize());
		(*PeteExtra(pCriteria->pte))->frame = True;
		if (pCriteria->ctlPete = CreateControl(win,win->topMarginCntl,0,kControlUserPaneProc,false))
		{
			INIT_UPP(SearchTextDraw,ControlUserPaneDraw);
			SetControlData(pCriteria->ctlPete,0,kControlUserPaneDrawProcTag,sizeof(SearchTextDrawUPP),(void*)&SearchTextDrawUPP);
			SetControlReference(pCriteria->ctlPete,(long)pCriteria->pte);
		}
		PeteFocus(win,pCriteria->pte,true);

		if (number)
			(*PeteExtra(pCriteria->pte))->numberField = true;	//	Number field
		
		// If there's a preview pane, it can mess up the text field focus order
		// Put any preview pane at the end of the list of text fields		
		GetSearchInfo(win,&tocH,nil);
		if (tocH && (previewPTE = (*tocH)->previewPTE))
		{
			win->pteList = PeteRemove(win->pteList,previewPTE);
			win->pteList = PeteLink(win->pteList,previewPTE);
		}
	}
	CleanPII(&pdi);
}

/**********************************************************************
 * RemoveFocus - remove current pte focus
 **********************************************************************/
static void RemoveFocus(MyWindowPtr win)
{
	//SD Why? PeteSelect(win,win->pte,0,0);
	PeteFocus(win,nil,true);
}

/**********************************************************************
 * AddCriteria - add criteria
 **********************************************************************/
static void AddCriteria(MyWindowPtr win,SearchHandle sh,Boolean defaultString)
{
	CriteriaInfo	criteria;
	Str255				what;
	short					count,
								i;
	
	RemoveFocus(win);
	Zero(criteria);
	
	// add default search criteria
	criteria.ctlCategory = CreateMenuControl(win,win->topMarginCntl,"\p",SRCH_CATEGORY_MENU,kControlPopupFixedWidthVariant+kControlPopupUseWFontVariant,1,false);
	criteria.ctlRelation = CreateMenuControl(win,win->topMarginCntl,"\p",SRCH_TEXT_COMPARE_MENU,kControlPopupFixedWidthVariant+kControlPopupUseWFontVariant,1,false);
	CreateTextSpecifier(win,&criteria,false);
	criteria.category = 1;
	
	if (!PtrAndHand(&criteria,(Handle)(*sh)->hCriteria,sizeof(CriteriaInfo)))
	{
		(*sh)->criteriaCount++;
		if (defaultString && GetFindString(what))
			PeteSetString(what,criteria.pte);
		PeteSelectAll(win,criteria.pte);
	}
	
	// (jp) 1-18-00  For light...
	count = CountMenuItems (GetPopupMenuHandle (criteria.ctlCategory));
	for (i = 1; i <= count; ++i)
		if (gCategoryTable[i - 1].proOnly)
			SetMenuItemBasedOnFeature (GetPopupMenuHandle(criteria.ctlCategory), i, featureSearch, true);
	count = CountMenuItems (GetPopupMenuHandle (criteria.ctlRelation));
	for (i = strContains + 1; i <= count; ++i)
		SetMenuItemBasedOnFeature (GetPopupMenuHandle(criteria.ctlRelation), i, featureSearch, true);

	EmbedSearchCriteriaControls(&criteria,sh);
}

/**********************************************************************
 * DisposeSpecifier - dispose of any specifier controls
 **********************************************************************/
static void DisposeSpecifier(MyWindowPtr win,SearchHandle sh,CriteriaInfo *pCriteria)
{
	if (pCriteria->ctlSpecifier)
	{
		if ((*sh)->ctlFocus==pCriteria->ctlSpecifier)
			(*sh)->ctlFocus = nil;
		DisposeControl(pCriteria->ctlSpecifier);
		pCriteria->ctlSpecifier = nil;
	}
	if (pCriteria->ctlPete) { DisposeControl(pCriteria->ctlPete); pCriteria->ctlPete = nil; }
	if (pCriteria->pte)
	{
		Rect	r;
		PeteRect(pCriteria->pte,&r);
		InsetRect(&r,-3,-3);
		InvalWindowRect(GetMyWindowWindowPtr(win),&r);	//	make sure entire pete (including focus ring) is updated
		PeteDispose(win,pCriteria->pte);
		pCriteria->pte = nil;
	}
	if (pCriteria->searchString)  { DisposeHandle((Handle)pCriteria->searchString); pCriteria->searchString = nil; }
}

/**********************************************************************
 * RemoveCriteria - remove criteria
 **********************************************************************/
static void RemoveCriteria(MyWindowPtr win,SearchHandle sh)
{
	CriteriaInfo	criteria=(*(*sh)->hCriteria)[--(*sh)->criteriaCount];
	
	DisposeControl(criteria.ctlCategory);
	DisposeControl(criteria.ctlRelation);
	DisposeSpecifier(win,sh,&criteria);
	AccuZap(criteria.hits);
	SetHandleSize((Handle)(*sh)->hCriteria,(*sh)->criteriaCount*sizeof(CriteriaInfo));
}


/**********************************************************************
 * SetSearchTopMargin - set top margin for Search window
 **********************************************************************/
static void SetSearchTopMargin(MyWindowPtr win,SearchHandle sh)
{
	short	topMargin = kWinHeaderHt+kCriteriaHt*(*sh)->criteriaCount;
	
	if ((*sh)->mailboxView)
		topMargin -= kMBHdrHt;
	SetTopMargin(win,topMargin);
}


/**********************************************************************
 * CheckChaseArrows - see if we should spin the chase arrows
 **********************************************************************/
static void CheckChaseArrows(SearchHandle sh)
{
	if (TickCount()-(*sh)->chaseArrowTicks > kChaseArrowTicks)
	{
		SendControlMessage((*sh)->ctlChaseArrows,kControlMsgIdle,nil);
		(*sh)->chaseArrowTicks = TickCount();
	}
}

/**********************************************************************
 * DisplayMBName - display name of mailbox we are searching
 **********************************************************************/
static void DisplayMBName(SearchHandle sh, PStr name)
{
	Str255	s;

	GetRString(s,SEARCH_SEARCHING);
	PCat(s,name);
	SetTextControlText((*sh)->ctlMailBox,s,nil);
}

/**********************************************************************
 * SearchTransfer - a message has been transferred, update Mailbox display
 **********************************************************************/
static short FindMBIdx(TOCHandle tocH,FSSpecPtr toSpec,Boolean allowCreate)
{
	FSSpecHandle specList = (*tocH)->mailbox.virtualMB.specList;
	FSSpecPtr	pSpec;
	short		i,cnt;
	Boolean		found=false;

	if (!specList) return -1;	// no mailboxes, not found

	//	do we already have this mailbox spec in the list?
	pSpec = LDRef(specList);
	cnt = (*tocH)->mailbox.virtualMB.specListCount;
	for(i=0;i<cnt;i++,pSpec++)
	{
		if (SameSpec(toSpec,pSpec))
		{
			found = true;
			break;
		}
	}
	UL(specList);

	if (!found)
	{
		if (allowCreate)
		{
			//	not found, add to list
			PtrAndHand(toSpec,(Handle)specList,sizeof(FSSpec));
			(*tocH)->mailbox.virtualMB.specListCount++;
			i = cnt;	
		}
		else
			i = -1;	//	not found
	}
	
	return i;
}

unsigned char SrchCharTabStrict[256] =
{
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, ' ', ' ', 0x0B, 0x0C, ' ', 0x0E, 0x0F,
	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
	' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'', '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
	'0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',  '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
	'@',  'a',  'b',  'c',  'd',  'e',  'f',  'g',  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
	'p',  'q',  'r',  's',  't',  'u',  'v',  'w',  'x',  'y',  'z',  '[',  '\\', ']',  '^',  '_',
	'`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
	'p',  'q',  'r',  's',  't',  'u',  'v',  'w',  'x',  'y',  'z',  '{',  '|',  '}',  '~',  0x7F,

	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
	0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
	0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
	0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
	0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
	0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
	0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
unsigned char SrchCharTabFudge[256] =
{
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, ' ', ' ', 0x0B, 0x0C, ' ', 0x0E, 0x0F,
	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
	' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'', '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
	'0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',  '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
	'@',  'a',  'b',  'c',  'd',  'e',  'f',  'g',  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
	'p',  'q',  'r',  's',  't',  'u',  'v',  'w',  'x',  'y',  'z',  '[',  '\\', ']',  '^',  '_',
	'`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
	'p',  'q',  'r',  's',  't',  'u',  'v',  'w',  'x',  'y',  'z',  '{',  '|',  '}',  '~',  0x7F,

	0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
	0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
	0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
	0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
	0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
	0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
	0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
	0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80
};

unsigned char *SrchCharTab = SrchCharTabFudge;

/**********************************************************************
 * NormalizeSearchString - convert to lower case, all white space to space
 **********************************************************************/
static void NormalizeSearchString(PStr s)
{
	short	i;
	unsigned char	*p;
	
	for(i=*s,p=s+1;i;i--,p++)
		*p=SrchCharTab[*p];
}


/**********************************************************************
 * SearchStrText - search for a string in text
 **********************************************************************/
long SearchStrText(PStr s,Ptr text,long length,long offset)
{
	UPtr		tp1,tp2,sp;
	long	tn,sn;
	long	sLen = *s;
	
	if (sLen<1) return -1;

	tp1=text+offset;
	for(tn=length-sLen-offset+1;tn>0;tn--)
	{
		if (s[1] != SrchCharTab[*tp1]) goto NoMatch;	//	Chances are the first char doesn't match
		
		//	search from 2nd char
		sp=s+2;
		tp2=tp1+1;
		for(sn=sLen-1;sn;sn--)
		{
			if (*sp != SrchCharTab[*tp2]) goto NoMatch;
			sp++;
			tp2++;
		}
		
		//	match
		return tp1-text;

  NoMatch:
		tp1++;
	}
	
	//	not found
	return -1;	
}

/**********************************************************************
 * FindHits - return pointer to hits accumulator
 **********************************************************************/
static AccuPtr FindHits(SearchHandle sh, short idx)
{
	CriteriaHandle	hCriteria;

	hCriteria = (*sh)->hCriteria;
	if (idx>=0 && idx<(*sh)->criteriaCount)
		return &(*hCriteria)[idx].hits;
	ASSERT(0);
	return nil;
}

/**********************************************************************
 * GetHits - get hits accumulator
 **********************************************************************/
static void GetHits(SearchHandle sh,short idx,AccuPtr a)
{
	*a = *FindHits(sh,idx);
}

/**********************************************************************
 * SetHits - set hits accumulator
 **********************************************************************/
static void SetHits(SearchHandle sh,short idx,AccuPtr a)
{
	AccuPtr	acc;
	
	if (a)
	{
		if (acc = FindHits(sh,idx))
			*acc = *a;
		ASSERT(acc);
	}
	else
	{
		//	Dispose
		Accumulator	hits;
		
		GetHits(sh,idx,&hits);
		if (hits.data)
		{
			AccuZap(hits);
			*FindHits(sh,idx) = hits;
		}
	}
}

/**********************************************************************
 * SetupIMAPSearchCriteria - set up criteria for IMAP server search
 **********************************************************************/
static IMAPSCHandle SetupIMAPSearchCriteria(SearchPtr sp)
{
	IMAPSCHandle	searchCriteria=NuHandle(0);
	short			i;
	
	//	Add criteria that require a message search
	for(i=0;i<sp->criteriaCount;i++)
	{
		CriteriaInfo	criteria=(*sp->hCriteria)[i];
	
		if (gCategoryTable[criteria.category-1].flags & kNeedsMsg)
		{
			IMAPSCStruct	searchInfo;
			
			PCopy(searchInfo.string,*criteria.searchString);
			searchInfo.headerCombination = kNone;
			searchInfo.headerName = 0;
			switch (criteria.category)
			{
				case scAnywhere:	searchInfo.headerCombination = kAnyWhere; break;
				case scHeaders:		searchInfo.headerCombination = kAllHeaders; break;
				case scBody:		searchInfo.headerCombination = kBody; break;		
				case scTo:			searchInfo.headerName = TO_HEAD+HEADER_STRN; break;
				case scFrom:		searchInfo.headerName = FROM_HEAD+HEADER_STRN; break;
				case scSubject:		searchInfo.headerName = SUBJ_HEAD+HEADER_STRN; break;
				case scCC:			searchInfo.headerName = CC_HEAD+HEADER_STRN; break;
				case scBCC:			searchInfo.headerName = BCC_HEAD+HEADER_STRN; break;
				case scAnyRecipient: searchInfo.headerCombination = kAnyRecipient; break;
				case scAttachNames:	searchInfo.headerCombination = kBody; break;
			}
			PtrAndHand((Ptr)&searchInfo,(Handle)searchCriteria,sizeof(searchInfo));
		}
	}
	
	return searchCriteria;
}

/**********************************************************************
 * ProcessIMAPSearch - process IMAP server search results
 **********************************************************************/
static void ProcessIMAPSearch(MyWindowPtr win,TOCHandle srchTOC,SearchHandle sh)
{
	Handle toAdd = nil, toUpdate = nil, toDelete = nil, toCopy = nil;
	MailboxNodeHandle 	mbox = nil;
	IMAPSResultHandle	hResults=nil;
	FSSpec				spec;
	Boolean				invalidateBoxSize = false;
	
	CheckChaseArrows(sh);

	//	Check for results
	if (IMAPDelivery(srchTOC,&toAdd,&toUpdate,&toDelete,&toCopy,nil,&hResults,&mbox,nil) && hResults)
	{
		short	count = GetHandleSize_(hResults)/sizeof(IMAPSResultStruct);
		IMAPSResultPtr	pResult;
					
		for(pResult=LDRef(hResults);count--;pResult++)
		{
			TOCHandle	tocH;
			Boolean		opened;
			
			if (GetBoxCountSpecLo((*sh)->imapBoxCount,pResult->box,&spec) && 
				(tocH = GetSpecTOC(&spec,&opened)))
			{
				MSumPtr	pSum;
				short	sumNum;
				DEC_STATE(tocH);
				
				L_STATE(tocH);
				if ((sumNum = FindSumByHash(tocH,pResult->uidHash)) >= 0)
					pSum = &(*tocH)->sums[sumNum];
				else
					pSum = nil;
				if (!pSum)
				{
					//	IMAP summary info hasn't been delivered yet. Do delivery and try again
					U_STATE(tocH);
					UpdateIMAPMailbox(tocH);
					L_STATE(tocH);
					if ((sumNum = FindSumByHash(tocH,pResult->uidHash)) >= 0)
						pSum = &(*tocH)->sums[sumNum];
				}
				if (pSum)
				{				
					//	See if we need to do any searching in the summary
					if (((*sh)->criteriaFlags & kNeedsSum) && !(*sh)->matchAny)
					{
						Boolean	stop;
						SearchMarker	curr;
						
						Zero(curr);
						curr.box = pResult->box;
						curr.message = sumNum;
						curr.tocH = tocH;
						if (!SearchMessage(win,pSum,sh,&curr,&stop,true))
							pSum=nil;	//	This one doesn't match
					}
				}
				
				if (pSum)
				{
					//	Add this to search results!
					short	specIdx = (*(*sh)->imapBoxSpecIdx)[pResult->box];
					
					if (AddSearchResult(sh,srchTOC,tocH,sumNum,specIdx==0,specIdx-1))
					{
						StopSearch(win);
						U_STATE(tocH);
						break;
					}
					
					if ((specIdx-1)<0)
						//	Added new spec
						(*(*sh)->imapBoxSpecIdx)[pResult->box] = (*srchTOC)->mailbox.virtualMB.specListCount;

					invalidateBoxSize = true;					
				}
				U_STATE(tocH);
			}
		}
	}
	
	if (mbox == SEARCH_WINDOW)
		//	we're done!
		StopSearch(win);

	ZapHandle(hResults);		
	if (invalidateBoxSize)
		UpdateBoxSize(win);
}

/**********************************************************************
 *	FindLinkSummary - find virtual summary that links to this one
 **********************************************************************/
static MSumPtr FindLinkSummary(TOCHandle srchTocH, TOCHandle fromTocH, long serialNum, short *realSum, short *pVirtualMBIdx)
{
	short	i,count;
	MSumPtr	sum;
	
	if (srchTocH && fromTocH)
	{
		FSSpec	spec = GetMailboxSpec(fromTocH,-1);
		short	virtualMBIdx = FindMBIdx(srchTocH,&spec,false);		
		if (virtualMBIdx >= 0)
		{
			count = (*srchTocH)->count;
			for(i=0,sum=(*srchTocH)->sums;i<count;i++,sum++)
				if (serialNum==sum->u.virtualMess.linkSerialNum && virtualMBIdx == sum->u.virtualMess.virtualMBIdx)
				{
					// found it!
					*realSum = i;
					*pVirtualMBIdx = virtualMBIdx;
					return sum;
				}
		}
	}
	return nil;	// not found!
}


/**********************************************************************
 * SearchUpdateSum - this summary has changed. See if we need to update
 *    summary in search window
 **********************************************************************/
void SearchUpdateSum(TOCHandle tocH, short sumNum, TOCHandle fromTocH, long serialNum, Boolean transfer, Boolean nuke)
{
	WindowPtr winWP;
	MyWindowPtr	win;
	MSumPtr		pSum;
	FSSpec		spec;
	
	if (!gSearchWinCount) 	//	Don't update from search window
		return;
	
	//	Find search window(s)
	for (winWP=FrontWindow_();winWP;winWP=GetNextWindow(winWP))
		if (IsSearchWindow(winWP))
		{
			SearchHandle	sh;
			TOCHandle		srchTocH;
			short			srchSum;
			short			virtualMBIdx;
			Boolean			redraw = false;

			win = GetWindowMyWindowPtr (winWP);
			GetSearchInfo(win,&srchTocH,&sh);
			if (nuke && !tocH)
			{
				//	Just emptied the trash. Delete any messages that were in the trash.
				short	trashIdx;
				
				spec.vRefNum = MailRoot.vRef;
				spec.parID = MailRoot.dirId;
				GetRString(spec.name,TRASH);
				trashIdx = FindMBIdx(srchTocH,&spec,false);
				if (trashIdx >= 0)
				{
					short	sum;
					
					for(sum=(*srchTocH)->count-1;sum>=0;sum--)
					{
						if ((*srchTocH)->sums[sum].u.virtualMess.virtualMBIdx==trashIdx)
						{
							DeleteSum(srchTocH,sum);
							redraw = true;
						}
					}
				}
			}
			else if (pSum = FindLinkSummary(srchTocH, fromTocH, serialNum, &srchSum, &virtualMBIdx))
			{
				if (nuke)
				{
					DeleteSum(srchTocH,srchSum);
					redraw = true;
				}
				else
				{
					if (transfer)
					{
						//	Change MB idx and assign new serialNumber
						spec = GetMailboxSpec(tocH,sumNum);
						(*srchTocH)->sums[srchSum].u.virtualMess.virtualMBIdx = FindMBIdx(srchTocH,&spec,true);
						(*srchTocH)->sums[srchSum].u.virtualMess.linkSerialNum = (*tocH)->sums[sumNum].serialNum;
					}
					else
						//	Update summary
						CopySum(&(*tocH)->sums[sumNum],pSum,pSum->u.virtualMess.virtualMBIdx);
					InvalSum(srchTocH,srchSum);
				}
			}
			else if (!nuke && tocH && !PrefIsSet(PREF_NO_LIVE_SEARCHES))
			{
				if (!SearchBoxesInclude(sh,fromTocH) && SearchBoxesInclude(sh,tocH))
					SearchIncremental(win,tocH,sumNum);
			}
			if (redraw)
			{
				InvalContent(win);
				UpdateBoxSize(win);
			}
		}
}

/**********************************************************************
 * SearchBoxesInclude - does the search domain include this tocH?
 **********************************************************************/
Boolean SearchBoxesInclude(SearchHandle sh,TOCHandle tocH)
{
	short count = HandleCount((*sh)->BoxCount);
	FSSpec searchSpec;
	
	while (count--)
	{
		GetBoxCountSpec(sh,count,&searchSpec);
		if (SameSpec(&searchSpec,&(*tocH)->mailbox.spec)) return true;
	}
	return false;
}

/**********************************************************************
 * SearchIncremental - add a message to this search, if it matches
 **********************************************************************/
Boolean SearchIncremental(MyWindowPtr win,TOCHandle tocH,short sumNum)
{
	TOCHandle srchTocH;
	SearchHandle sh;
	SearchMarker marker;
	Boolean oldNoBulk;
	Boolean found;
	short specIdx;
	
	marker.tocH = tocH;
	marker.message = sumNum;
	
	GetSearchInfo(win,&srchTocH,&sh);
	
	ASSERT((*sh)->setup);	// avoid searches that haven't been started yet.
	if ((*sh)->setup)
	{
		oldNoBulk = (*sh)->noBulkSearch;
		(*sh)->noBulkSearch = true;
		found = SearchMessage(win,&(*tocH)->sums[sumNum],sh,&marker,nil,false);
		(*sh)->noBulkSearch = oldNoBulk;
	
		if (found)
		{
			if (!(*srchTocH)->mailbox.virtualMB.specList) (*srchTocH)->mailbox.virtualMB.specList = NuHandle(0);
			// this routine will add the spec to the specList if necessary
			VERIFY(0<=(specIdx=FindMBIdx(srchTocH,&(*tocH)->mailbox.spec,true)));
			// and now we can add it!
			AddSearchResult(sh,srchTocH,tocH,sumNum,false,specIdx);
			return true;
		}
	}
	
	return false;
}

/**********************************************************************
 * IMAPSearchIncremental - an IMAP mailbox has changed.  Update all
 *	search windows that are currently searching this mailbox.
 **********************************************************************/
void IMAPSearchIncremental(MailboxNodeHandle mbox)
{
	WindowPtr winWP;
	MyWindowPtr win;
	SearchHandle sh;
	TOCHandle toc;
	short i;
	FSSpec spec;
	long lastSearchedUID = 0;
	long firstUid = (*mbox)->searchUID + 1;	// where to start the incremental search from
	IMAPSCHandle searchCriteria;
	
	for (winWP = GetWindowList(); winWP; winWP = GetNextWindow (winWP)) 
	{
		win = GetWindowMyWindowPtr (winWP);
		if (IsSearchWindow(winWP))
		{
			GetSearchInfo(win,&toc,&sh);
			
			if ((*sh)->setup)
			{
				// make sure we're up to date on the selected mailboxes
				if ((*sh)->imapBoxCount == NULL)
					GetSelectedBoxes(*sh, toc,false);
				if ((*sh)->imapBoxCount)
				{
					// is this mailbox amongst the mailboxes to search in this search window?
					for (i = 0; GetBoxCountSpecLo((*sh)->imapBoxCount, i, &spec); i++)
					{
						if (SameSpec(&spec, &(*mbox)->mailboxSpec))
						{
							// re-run the search on this particular mailbox	
							if (searchCriteria = SetupIMAPSearchCriteria(*sh))
							{
								// IMAPSearch will return false if no searches were actually started.
								if (IMAPSearchMailbox(toc,(*sh)->imapBoxCount,mbox,searchCriteria,!(*sh)->matchAny,firstUid))
									(*sh)->searchMode |= kSearchingIMAP;	
							}
						}
					}
				}
			}
		}
	}
}

/**********************************************************************
 * SearchInvalTocBox - this summary needs to be updated. See if any
 *  summaries in search windows need to also be updated
 **********************************************************************/
void SearchInvalTocBox(TOCHandle tocH, short sumNum, short box)
{
	WindowPtr winWP;
	
	if (!gSearchWinCount) 	//	No search windows
		return;
	
	//	Find search window(s)
	for (winWP=FrontWindow_();winWP;winWP=GetNextWindow(winWP))
		if (IsSearchWindow(winWP))
		{
			SearchHandle	sh;
			TOCHandle		srchTocH;
			short			srchSum,virtualMBIdx;

			GetSearchInfo(GetWindowMyWindowPtr(winWP),&srchTocH,&sh);
			if (FindLinkSummary(srchTocH,tocH,(*tocH)->sums[sumNum].serialNum,&srchSum,&virtualMBIdx))
				InvalTocBoxLo(srchTocH,srchSum,box);
		}
}

/**********************************************************************
 * TellSearchMBRename - notify search windows that mailbox has been renamed
 **********************************************************************/
void TellSearchMBRename(FSSpecPtr spec,FSSpecPtr newSpec)
{
	WindowPtr	winWP;
	MyWindowPtr win;
	
	if (!gSearchWinCount)
		return;
	
	//	Find search window(s)
	for (winWP=FrontWindow_();winWP;winWP=GetNextWindow(winWP))
		if (IsSearchWindow(winWP))
		{
			SearchHandle	sh;
			TOCHandle		tocH;
			short			index;

			win = GetWindowMyWindowPtr (winWP);
			GetSearchInfo(win,&tocH,&sh);
			index = FindMBIdx(tocH,spec,false);
			if (index >= 0)
			{
				//	Found it. Replace.				
				(*(*tocH)->mailbox.virtualMB.specList)[index] = *newSpec;
				InvalContent(win);	//	Update window
			}			
		}
}

/**********************************************************************
 * GetTOCFromSearchWin - see if any search windows have opened a TOC not in TOC list
 *   Called from OpenMailbox. TOC window will be opened and TOC added to TOC list
 **********************************************************************/
TOCHandle GetTOCFromSearchWin(FSSpecPtr spec)
{
	WindowPtr	winWP;
	MyWindowPtr win;
	
	if (!gSearchWinCount)
		return nil;
	
	//	Find search window(s)
	for (winWP=FrontWindow_();winWP;winWP=GetNextWindow(winWP))
		if (IsSearchWindow(winWP))
		{
			SearchHandle	sh;
			TOCHandle		tocH;
			FSSpec			thisSpec;
			
			win = GetWindowMyWindowPtr (winWP);
			GetSearchInfo(win,nil,&sh);
			if ((tocH=(*sh)->curr.tocH) && (*sh)->curr.opened)
			{
				ASSERT(*tocH!=(TOCPtr)-1);
				if (*tocH==(TOCPtr)-1)
				{
					// ACK!  Fix this!
					(*sh)->curr.tocH = nil;
					(*sh)->curr.opened = false;
					continue;
				}
				thisSpec = GetMailboxSpec(tocH,-1);
				if (SameSpec(&thisSpec,spec))
				{
					(*sh)->curr.opened = false;	// TOC window will be opened and TOC added to TOC list
					return tocH;
				}
			}
		}
		
	return nil;
}


/**********************************************************************
 * SelectMailbox - select a mailbox in mailbox list
 **********************************************************************/
static void SelectMailbox(FSSpecPtr spec,ViewListPtr pView,Boolean folder,Boolean addToSelection)
{
	Str63	name;
	short	menuID;
	Str255	s;
	Boolean		isIMAP;
	
	PCopy(name,spec->name);

	if (isIMAP = IsIMAPMailboxFile(spec))
		ParentSpec(spec,spec);

	menuID = VD2MenuId(spec->vRefNum,spec->parID);
	if (folder)
	{	
		if (menuID == MAILBOX_MENU)
		{
			menuID = kEudoraFolder;
			GetRString(name,FILE_ALIAS_EUDORA_FOLDER);
		}
		else
		{
			//	get mail folder
			short	item;
			MenuHandle	mhPar = ParentMailboxMenu(GetMenuHandle(menuID),&item);
			menuID = GetMenuID(mhPar);
			GetMenuItemText(mhPar,item,name);

			if (menuID == MAILBOX_MENU && isIMAP)
				menuID = kIMAPFolder;
		}
	}
	
	//	open any folders above
	OpenMBFolder(pView,menuID,s);
	
	//	now select our item
	LVSelect(pView,menuID,name,addToSelection);
}

/**********************************************************************
 * SearchSave - save contents of Search window
 **********************************************************************/
static void SearchSave(MyWindowPtr win,Boolean saveAs)
{
	SearchHandle	sh;
	short 			refN,i;
	FSSpec			spec;
	TOCHandle		tocH;
	short			saveResFile = CurResFile();
	Str255			s;
	OSErr			err;
	SearchPtr		sp;

	GetSearchInfo(win,&tocH,&sh);

	sp = LDRef(sh);
	SetupSearchCriteria(win,sp);
	UL(sh);
	
	spec= (*sh)->saveSpec;
	if (saveAs || !spec.vRefNum)
	{
		//	User selects file
		long newDirId;
		FSSpec	folderSpec;

		DirCreate(Root.vRef,Root.dirId,GetRString(s,SEARCH_FOLDER),&newDirId);
		SubFolderSpec(SEARCH_FOLDER,&folderSpec);
		spec = folderSpec;
		*s = 0;
		AddCriteriaText(sh,s);	//	Get text description of criteria
		if (*s > 31) *s = 31;	//	Truncate to 31 chars
		PCopy(spec.name,s);
		err = SFPutOpen(&spec,CREATOR,SEARCH_FILE_TYPE,&refN,nil,nil,0,&folderSpec,nil,nil);
		if (err)
			return;
		MyFSClose(refN);
		(*sh)->saveSpec = spec;
		SetWTitle(GetMyWindowWindowPtr(win),spec.name);
	}

	FSpKillRFork(&spec);
	FSpCreateResFile(&spec,CREATOR,SEARCH_FILE_TYPE,smSystemScript);
	if (-1!=(refN=FSpOpenResFile(&spec,fsRdWrPerm)))
	{
		Handle	hSave,hSpecList;
		SearchSaveInfo	saveInfo;
		ViewListPtr	pView;
		
		UseResFile(refN);
		
		Zero(saveInfo);
		saveInfo.criteriaCount = (*sh)->criteriaCount;
		if (HasFeature (featureSearch))
			saveInfo.matchMode = GetControlValue((*sh)->ctlMatch);
		saveInfo.tabMode = GetControlValue((*sh)->ctlTabs);
		
		if (!PtrToHand(&saveInfo,&hSave,sizeof(saveInfo)))
		{
			for(i=0;i<saveInfo.criteriaCount;i++)
			{
				CriteriaInfo	criteria=(*(*sh)->hCriteria)[i];
				CriteriaSave	saveCriteria;
				
				saveCriteria.category = MenuCategory2SavedCategory[criteria.category];
				saveCriteria.relation = GetControlValue(criteria.ctlRelation);
				saveCriteria.specifier = criteria.ctlSpecifier ? GetControlValue(criteria.ctlSpecifier) : 0;
				saveCriteria.misc = criteria.misc;
				saveCriteria.date = criteria.date;
				*s = 0;
				if (criteria.pte)				
					PeteString(s,criteria.pte);
				// No personalities in Light
				else if (HasFeature (featureMultiplePersonalities) && criteria.category==scPersonality)
				{
					if (saveCriteria.specifier > 1)
						//	Save personality by name in case menu changes
						GetMenuItemText(GetPopupMenuHandle(criteria.ctlSpecifier),saveCriteria.specifier,s);
				}
				if (*s)
				{
					//	Save string in separate resource
					StringHandle	hString = NewString(s);
					
					AddResource(hString,'STR ',1000+i,"");
					saveCriteria.stringID = 1000+i;
				}
				
				PtrAndHand(&saveCriteria,hSave,sizeof(saveCriteria));
			}
			
			AddResource(hSave,SEARCH_FILE_TYPE,1000,"");		
		}
		
		//	Save aliases to each selected mailbox if not all selected
		if ((pView = (*sh)->list) && (LVCountSelection(pView) != (*pView->hList)->dataBounds.bottom))
		{
			short	i;
			VLNodeInfo	data;
			
			for(i=1;i<=LVCountSelection(pView);i++)
			{
				AliasHandle	alias;
				
				LVGetItem(pView,i,&data,true);
				MakeMBSpec(&data,&spec);
				NewAliasMinimal(&spec,&alias);
				if (alias)
					AddResource((Handle)alias,rAliasType,1000+i,"");
			}
		}

		//	Save TOC handle
		HandToHand((Handle *) &tocH);
		AddResource((Handle)tocH,TOC_TYPE,1000,"");
		if (hSpecList = (Handle)(*tocH)->mailbox.virtualMB.specList)
		{
			HandToHand(&hSpecList);
			AddResource(hSpecList,kSpecListType,1000,"");
		}

		//	Save window position
		SearchPosition(true,win);
		
		// Save sort criteria
		Sort2String(s,tocH);
		{
			StringHandle hString = NewString(s);
			AddResource(hString,'STR ',SEARCH_SAVED_SORT_ID,"");
		}

		CloseResFile(refN);
	
		win->isDirty = false;

		BuildSearchMenu();
	}
	UseResFile(saveResFile);
}

/**********************************************************************
 * OpenSearchMenu - open a search file
 **********************************************************************/
void OpenSearchMenu(short item)
{
	FSSpec		spec;
	MenuHandle mh = GetMHandle(FIND_HIER_MENU);

	SubFolderSpec(SEARCH_FOLDER,&spec);
	GetMenuItemText(mh,item,spec.name);
	OpenSearchFileAndStart(&spec);
}

/**********************************************************************
 * OpenSearchFileAndStart - open a search file, and start the search
 **********************************************************************/
MyWindowPtr OpenSearchFileAndStart(FSSpecPtr spec)
{
	MyWindowPtr win = OpenSearchFile(spec);
	if (win)
	{
		TOCHandle	tocH;

		GetSearchInfo(win,&tocH,nil);
#ifdef WE_EVER_GET_CLEVER
		if (!(*tocH)->count)	
#else
		// setup for incremental searching.  Until we get clever, that is.
		if (!(*tocH)->count || !PrefIsSet(PREF_NO_LIVE_SEARCHES)) 
#endif
			//	If no results, start search
			StartSearch(win);
	}
	return win;
}

/**********************************************************************
 * OpenSearchFile - open a search file
 **********************************************************************/
MyWindowPtr OpenSearchFile(FSSpecPtr spec)
{
	WindowPtr	winWP;
	MyWindowPtr win;
	SearchHandle	sh;
	TOCHandle	tocH;
	Str255	s;
	Rect	r;
	short	saveResFile = CurResFile();
	short	tabMode = 0;
	FSSpec	aliasSpec;

	//	Is search window already open?
	for (winWP=FrontWindow_();winWP;winWP=GetNextWindow(winWP))
		if (IsSearchWindow(winWP))
		{
			win = GetWindowMyWindowPtr (winWP);
			GetSearchInfo(win,nil,&sh);
			if (SameSpec(spec,&(*sh)->saveSpec))
			{
				//	Already open
				UserSelectWindow(winWP);
				return win;
			}
		}

	if (win = SearchOpen(0))
	{
		Handle	hTOCRes;
		short	refN;
		
		winWP = GetMyWindowWindowPtr (win);
		GetSearchInfo(win,&tocH,&sh);
		if (-1!=(refN=FSpOpenResFile(spec,fsRdPerm)))
		{
			Handle	hSave;
			short	cntAlias;
			ViewListPtr	pView;
						
			UseResFile(refN);	
			if (hSave = GetResource(SEARCH_FILE_TYPE,1000))
			{
				SearchSaveInfo	*pSaveInfo = LDRef(hSave);
				CriteriaHandle	hCriteria = (*sh)->hCriteria;
				CriteriaSave	*pSaveCriteria;
				short					count,
											item,
											i;
				
				while ((*sh)->criteriaCount)
					RemoveCriteria(win,sh);

				(*sh)->criteriaCount = pSaveInfo->criteriaCount;
				if (HasFeature (featureSearch))
					SetControlValue((*sh)->ctlMatch,pSaveInfo->matchMode);
				tabMode = pSaveInfo->tabMode;
				for(i=0,pSaveCriteria=pSaveInfo->criteria;i<pSaveInfo->criteriaCount;i++,pSaveCriteria++)
				{
					CriteriaInfo	criteria;
					short			specifier;
					LongDateRec	date;
					
					Zero(criteria);
					criteria.category = SafeSavedCategory2MenuCategory(pSaveCriteria->category);
					criteria.relation = pSaveCriteria->relation;
					criteria.specifier = pSaveCriteria->specifier;
					criteria.misc = pSaveCriteria->misc;
					criteria.date = pSaveCriteria->date;
					
					criteria.ctlCategory = CreateMenuControl(win,win->topMarginCntl,"\p",SRCH_CATEGORY_MENU,kControlPopupFixedWidthVariant+kControlPopupUseWFontVariant,criteria.category,false);
					criteria.ctlRelation = CreateMenuControl(win,win->topMarginCntl,"\p",gCategoryTable[criteria.category-1].relation,kControlPopupFixedWidthVariant+kControlPopupUseWFontVariant,criteria.relation,false);

					// (jp) 1-18-00  For light...
					count = CountMenuItems (GetPopupMenuHandle (criteria.ctlCategory));
					for (item = 1; item <= count; ++item)
						if (gCategoryTable[item - 1].proOnly)
							SetMenuItemBasedOnFeature (GetPopupMenuHandle(criteria.ctlCategory), item, featureSearch, true);
					count = CountMenuItems (GetPopupMenuHandle (criteria.ctlRelation));
					for (item = strContains + 1; item <= count; ++item)
						SetMenuItemBasedOnFeature (GetPopupMenuHandle(criteria.ctlRelation), item, featureSearch, true);

					*s = 0;
					if (pSaveCriteria->stringID)
						GetRStr(s,pSaveCriteria->stringID);
					specifier = gCategoryTable[criteria.category-1].specifier;
					switch (specifier)
					{
						case ssText:
						case ssAge:
						case ssNumber:
							CreateTextSpecifier(win,&criteria,specifier!=ssText);
							PeteSetString(s,criteria.pte);
							if (specifier==ssAge)
								criteria.ctlSpecifier = CreateMenuControl(win,win->topMarginCntl,"\p",SRCH_AGE_UNITS_MENU,kControlPopupFixedWidthVariant+kControlPopupUseWFontVariant,criteria.specifier,false);
							break;
						
						case ssDate:
							SetRect(&r,-4000,-4000,kDateWi-4000,kDateHt-4000);
							criteria.ctlSpecifier = NewControl(winWP,&r,"\p",true,0,0,0,kControlClockDateProc,0);
							EmbedControl(criteria.ctlSpecifier,win->topMarginCntl);
							Zero(date);
							date.ld.day = pSaveCriteria->date.day;
							date.ld.month = pSaveCriteria->date.month;
							date.ld.year = pSaveCriteria->date.year;
							SetControlData(criteria.ctlSpecifier,0,kControlClockLongDateTag,sizeof(date),(Ptr)&date);
							break;

						case PERS_HIER_MENU:
							// No personalities in Light
							if (HasFeature (featureMultiplePersonalities) && *s)
								criteria.specifier = FindItemByName(GetMenuHandle(PERS_HIER_MENU),s);
							//	Fall thru
							
						default:
							criteria.ctlSpecifier = CreateMenuControl(win,win->topMarginCntl,"\p",specifier,kControlPopupFixedWidthVariant+kControlPopupUseWFontVariant,criteria.specifier,false);
							break;
					}
					PtrAndHand(&criteria,hCriteria,sizeof(criteria));
					EmbedSearchCriteriaControls(&criteria,sh);
				}
			}

			//	Setup selected mailboxes from alias resource
			if (pView = (*sh)->list)
			{
				if (cntAlias = Count1Resources(rAliasType))
				{
					short	i;
					
					for(i=1;i<=cntAlias;i++)
					{
						AliasHandle	alias;
						
						if (alias = (AliasHandle)GetIndResource(rAliasType,i))
						{
							if (!SimpleResolveAliasNoUI(alias,&aliasSpec))
								SelectMailbox(&aliasSpec,pView,false,i>1);
							ReleaseResource((Handle)alias);
						}
					}
				}
				else
				{
					//	Select all
					Cell	c;
					
					c.h = 0;
					for(c.v=0;c.v<(*pView->hList)->dataBounds.bottom;c.v++)					
						LSetSelect(true, c, pView->hList);
					LVUpdateSelection(pView);
				}
			}
						
			//	Get TOC
			if (hTOCRes =  GetResource(TOC_TYPE,1000))
			{
				FSSpecHandle	specList,specListRes;
				TOCType		saveTOC = **tocH;
				short		sumCount;
				short		previewHi;
				
				SetHandleSize((Handle)tocH,0);
				HandAndHand(hTOCRes,(Handle)tocH);
				sumCount = (*tocH)->count;
				previewHi = (*tocH)->previewHi;
				BMD(&saveTOC,*tocH,sizeof(TOCType)-sizeof(MSumType));	//	Only summaries and TOC summary count should change
				(*tocH)->count = sumCount;
				(*tocH)->previewHi = previewHi;
				CleanseTOC(tocH); (*tocH)->virtualTOC = true;	// CleanseTOC steps on that.  :-(

				specList = (*tocH)->mailbox.virtualMB.specList;
				if (specListRes = (FSSpecHandle)GetResource(kSpecListType,1000))
				{
					if (!specList)
					{
						specList = NuHandle(0);
						(*tocH)->mailbox.virtualMB.specList = specList;
					}
					else
						SetHandleSize(specList,0);
					HandAndHand(specListRes,specList);					
					(*tocH)->mailbox.virtualMB.specListCount = HandleCount(specList);
				}
				else
				{
					(*tocH)->mailbox.virtualMB.specListCount = 0;
					if (specList)
						SetHandleSize(specList,0);
				}
			}

			(*sh)->saveSpec = *spec;
			SetWTitle(winWP,spec->name);
			
			// restore sort
			{
				Handle h = Get1Resource('STR ',SEARCH_SAVED_SORT_ID);
				if (h)
				{
					PCopy(s,*h);
					ApplySortString(tocH,s);
					ReleaseResource(h);
				}
			}
		}		

		AdjustForCriteriaCount(sh);
		if (tabMode && tabMode != GetControlValue((*sh)->ctlTabs))
		{
			SetControlValue((*sh)->ctlTabs,tabMode);
			SearchButton(win,(*sh)->ctlTabs,0,tabMode);
		}
		
		ShowMyWindow(winWP);
		UserSelectWindow(winWP);		

#ifdef WE_EVER_GET_CLEVER
		// setup for incremental searching
		if (!PrefIsSet(PREF_NO_LIVE_SEARCHES)) 
		{
			// This would work fine except that it won't pickup messages added while window was NOT open
			// For now, that's not acceptable, so we'll assume the caller will start the search
			GetSelectedBoxes(*sh,tocH,false);
			SetupSearchCriteria(win,*sh);
		}
#endif
				

		if (refN!=-1)
			CloseResFile(refN);
	}
	UseResFile(saveResFile);

	return win;
}

/**********************************************************************
 * BuildSearchMenu - add saved search windows to menu
 **********************************************************************/
void BuildSearchMenu(void)
{
	MenuHandle mh = GetMHandle(FIND_HIER_MENU);
	Boolean	haveDivider = false;
	FSSpec		folderSpec;
	Str255		s;
	CInfoPBRec	hfi;
	short		item;
	
	//	Remove any existing items
	for(item=CountMenuItems(mh);item>=FIND_MENU_LIMIT;item--)
		DeleteMenuItem(mh,item);

	if (!SubFolderSpec(SEARCH_FOLDER,&folderSpec))
	{
		hfi.hFileInfo.ioNamePtr = s;
		hfi.hFileInfo.ioFDirIndex = 0;
		while (!DirIterate(folderSpec.vRefNum,folderSpec.parID,&hfi))
			if (!(hfi.hFileInfo.ioFlAttrib&0x10) && (hfi.hFileInfo.ioFlFndrInfo.fdType==SEARCH_FILE_TYPE))
			{
				if (!haveDivider)
				{
					AppendMenu(mh,"\p-");
					haveDivider = true;
				}
				MyAppendMenu(mh,s);
			}
	}
}

/**********************************************************************
 * GetMenuText - return text of selected item in menu control
 **********************************************************************/
static void GetMenuText(ControlHandle cntl,StringPtr s)
{
	GetMenuItemText(GetPopupMenuHandle(cntl),GetControlValue(cntl),s);
}

/**********************************************************************
 * GetCriteriaString - return a string representation of a criterion
 **********************************************************************/
static void GetCriteriaString(StringPtr s,CriteriaPtr criteria)
{
	Str255	sTemp;
	
	GetMenuText(criteria->ctlCategory,s);
	GetMenuText(criteria->ctlRelation,sTemp);
	PCatC(s,' ');
	PCat(s,sTemp);
	if (criteria->pte)
	{				
		PeteString(sTemp,criteria->pte);
		if (*sTemp + *s >= 250) return;	//	too big!
		PCatC(s,' ');
		PCatC(s,'"');
		PCat(s,sTemp);
		PCatC(s,'"');
	}
	if (criteria->ctlSpecifier)
	{
		long junk;
		LongDateRec		date;
		LongDateTime	time;

		if (criteria->category==scDate)
		{
			Zero(date);
			GetControlData(criteria->ctlSpecifier,0,
				kControlClockLongDateTag,sizeof(date),(Ptr)&date,&junk);
			LongDateToSeconds(&date,&time);
			LongDateString(&time,shortDate,sTemp,nil);
		}
		else
			GetMenuText(criteria->ctlSpecifier,sTemp);
		PCatC(s,' ');
		PCat(s,sTemp);
	}
	
}

/**********************************************************************
 * AddCriteriaText - append a textual representation of criteria
 **********************************************************************/
static void AddCriteriaText(SearchHandle sh, StringPtr sText)
{
	short	i;
	Str255	s;
	
	for(i=0;i<(*sh)->criteriaCount;i++)
	{
		CriteriaInfo	criteria=(*(*sh)->hCriteria)[i];

		GetCriteriaString(s,&criteria);
		if (*sText + *s > 250)
			break;	//	it's getting too long
		if (i)
			PCat(sText,"\p, ");
		PCat(sText,s);
	}
}

/**********************************************************************
 * SearchSetWTitle - set the title of the search message
 **********************************************************************/
static void SearchSetWTitle(MyWindowPtr win,SearchHandle sh)
{
	WindowPtr	winWP = GetMyWindowWindowPtr(win);
	Str255	sTitle,s;
	
	if (!(*sh)->saveSpec.vRefNum)	//	don't change title of saved windows
	{
		
		GetRString(sTitle,SEARCH_SEARCH);
		PCat(sTitle,"\p: ");
		AddCriteriaText(sh,sTitle);
		GetWTitle(winWP,s);
		if (!StringSame(s,sTitle))	//	don't change title if hasn't changed
			SetWTitle_(winWP,sTitle);
	}
}

/************************************************************************
 * SearchRestorePosition - save/restore window position
 ************************************************************************/
static Boolean SearchPosition(Boolean save,MyWindowPtr win)
{
	Str255 title;	
	SearchHandle	sh;

	GetSearchInfo(win,nil,&sh);
	if ((*sh)->saveSpec.vRefNum)
		//	If window has been saved, save by window title
		return PositionPrefsTitle(save,win);
	
	//	Save/restore based on title "Search"
	GetRString(title,SEARCH_SEARCH);
	return PositionPrefsByName(save,win,title);	
}

/************************************************************************
 * SearchMBUpdate - update mailbox list
 ************************************************************************/
void SearchMBUpdate(void)
{
	WindowPtr		winWP;
	
	if (!gSearchWinCount)
		return;
	
	//	Find search window(s)
	for (winWP=FrontWindow_();winWP;winWP=GetNextWindow(winWP))
		if (IsSearchWindow(winWP))
		{
			SearchHandle	sh;
			
			GetSearchInfo(GetWindowMyWindowPtr (winWP),nil,&sh);
			InvalidListView((*sh)->list);
		}
}

/************************************************************************
 * SearchFixUnread - update mailbox list unread status
 ************************************************************************/
void SearchFixUnread(MenuHandle mh,short item,Boolean unread)
{
	WindowPtr winWP;
	
	if (!gSearchWinCount)
		return;
	
	//	Find search window(s)
	for (winWP=FrontWindow_();winWP;winWP=GetNextWindow(winWP))
		if (IsSearchWindow(winWP))
		{
			SearchHandle	sh;
			
			GetSearchInfo(GetWindowMyWindowPtr (winWP),nil,&sh);
				MBFixUnreadLo((*sh)->list,mh,item,unread,(*sh)->mailboxView);
		}
}

/************************************************************************
 * ShowHideTabs - need to clip tab control on Mac OS X since control itself doesn't
 ************************************************************************/
static void ShowHideTabs(ControlRef ctlTabs,Boolean show)
{
	Rect	rCtl;
	
	ClipRect(GetControlBounds(ctlTabs,&rCtl));
	if (show) ShowControl(ctlTabs);
	else HideControl(ctlTabs);
	InfiniteClip(GetWindowPort(GetControlOwner(ctlTabs)));
}

/************************************************************************
 * EmbedSearchCriteriaControls - make sure search criteria controls are
 *	imbedded within group control, otherwise they don't respond under OS X
 ************************************************************************/
static void EmbedSearchCriteriaControls(CriteriaInfo *criteria,SearchHandle sh)
{
	ControlHandle	ctlCriteriaGroup = (*sh)->ctlCriteriaGroup;
	EmbedControl(criteria->ctlCategory,ctlCriteriaGroup);
	EmbedControl(criteria->ctlRelation,ctlCriteriaGroup);
	if (criteria->ctlSpecifier)
		EmbedControl(criteria->ctlSpecifier,ctlCriteriaGroup);
}

/************************************************************************
 * SearchViewIsMailbox - are we looking at the mailboxes view of the search
 *  window?
 ************************************************************************/
Boolean SearchViewIsMailbox(TOCHandle tocH)
{
	SearchHandle sh = nil;
	
	if (!(*tocH)->virtualTOC) return false;
	if (!IsSearchWindow(GetMyWindowWindowPtr((*tocH)->win))) return false;
	
	GetSearchInfo((*tocH)->win,nil,&sh);
	return sh && (*sh)->mailboxView;
}

#endif