/* 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. */

#include "boxact.h"
#define FILE_NUM 5
/* Copyright (c) 1990-1992 by the University of Illinois Board of Trustees */

#pragma segment Boxes

/**********************************************************************
 * Private prototypes
 **********************************************************************/
// Drawing
void InvertSum(TOCHandle tocH,short sumNum,Boolean forActivate);
void BoxCenter(MyWindowPtr win, short mNum);
void BoxSizeBox(MyWindowPtr win,Rect *r,TOCHandle tocH);
short BoxPreviewHeightWish(TOCHandle tocH);
void BoxZoomSize(MyWindowPtr win,Rect *zoom);
void InvertSumByColumns(TOCHandle tocH,Rect *r);
Boolean BoxDrawerUseless(MyWindowPtr win, short *left, short *right);
// Clicking
void BoxTrack(TOCHandle tocH,int anchor,Boolean shift);
Boolean BoxDragDividers(Point pt, MyWindowPtr win);
Boolean BoxFindDivider(Point pt,short *which);
TOCHandle TOCHUnder(Point pt);
void BoxSetPreview(MyWindowPtr win,Boolean openIt);
void BoxDragPreviewDivider(TOCHandle tocH,Point pt);
Boolean BoxListKey(MyWindowPtr win, EventRecord *event);
Boolean BoxPreviewKey(MyWindowPtr win, EventRecord *event);
void BoxConConProfControl(TOCHandle tocH,ControlHandle cntl,Point pt);
// Dragging
OSErr BoxDrop(MyWindowPtr win, DragReference drag);
RgnHandle BoxBuildDragRgn(TOCHandle tocH);
void BoxDroppedOn(TOCHandle origTocH,MessHandle messH,Point pt);
OSErr BoxMessDragSendLo(FlavorType flavor, void *dragSendRefCon, DragReference drag, TOCHandle tocH,short sumNum);
// Menus
void BoxPriorMenu(TOCHandle tocH,short mNum,Point pt);
void BoxServerMenu(TOCHandle tocH,short mNum,Point pt);
void BoxStateMenu(TOCHandle tocH,short mNum,Point pt);
void BoxMailboxMenu(TOCHandle tocH,short mNum,Point pt);
void BoxAttachMenu(TOCHandle tocH,short mNum,Point pt);
void BoxBuildConConProfMenu(TOCHandle tocH,ControlHandle cntl);
// Misc
void DoSaveSelectedAs(TOCHandle tocH);
void SizeBoxClick(MyWindowPtr win,short modifiers);
OSErr CopySummary(TOCHandle tocH, short sumNum, Handle into);
void BoxType2Select(TOCHandle tocH,PStr string);
OSErr CopySelectedSummaries(TOCHandle tocH);
short BoxCountSelected(TOCHandle tocH);
void SetTAEScoreLo(TOCHandle tocH,short sumNum,short score);
void OneAnalMess(TOCHandle tocH,short sumNum);
OSErr BoxReply(TOCHandle tocH,short menu,short item,short modifiers);
void BoxRefreshPreview(TOCHandle tocH);
Boolean BoxRequestFocus(MyWindowPtr win, PETEHandle pte);
PStr BoxCurAddr(MyWindowPtr win, PStr addr);
// Sorting
#define DisplayLength(sum)	((sum)->length-(sum)->bodyOffset+1023)
#define EffectiveLength(sum)	((sum->flags&FLAG_SKIPPED) ? -1 K : ((sum->opts&OPT_JUSTSUB) ? -2 K : DisplayLength(sum)))
void BoxToggleLaurence(TOCHandle tocH);
void BoxCustomSort(TOCHandle tocH,short i);
void SortTOCMenu(TOCHandle tocH, short item, Boolean reverse);
void SortTOC(TOCHandle tocH, Boolean reverse, int (*compare)());
void SwapSum(MSumPtr sum1, MSumPtr sum2);
short BoxLine2Item(short line);
short BoxItem2Line(short item);
void MBRemoveSort(TOCHandle tocH,short index);
void MBAddSort(TOCHandle tocH,short index,Byte sortOrder);
#define MBGetSort(tocH,index)	(*tocH)->sorts[index-1]
int (*FindMBSort(short item,Boolean reverse))();
int MBResortCompare(MSumPtr s1, MSumPtr s2);
void MBResort(TOCHandle tocH);
void MBSortHit(TOCHandle tocH, short index, Boolean reverse, Boolean extend);
Boolean MBIsSticky(TOCHandle tocH);
int SumSubjIdCompare(MSumPtr sum1, MSumPtr sum2);
void BoxMakePreview(MyWindowPtr win);
short BoxSizeWidth(TOCHandle tocH);
void SetPriorityLo(TOCHandle tocH,short sumNum,short priority);
long MyPopupMenuSelect(MyWindowPtr win,MenuHandle mh,Point where,short item);
#define NSORT	(sizeof((*tocH)->sorts)/sizeof(Byte))
#define SORTER(what)	int Sum##what##Compare(MSumPtr,MSumPtr); int RevSum##what##Compare(MSumPtr,MSumPtr)
SORTER(Time);
SORTER(Stat);
SORTER(Junk);
SORTER(Att);
SORTER(Size);
SORTER(Prior);
SORTER(Subj);
SORTER(SubjTimed);
SORTER(From);
SORTER(Offset);
SORTER(Label);
SORTER(SizeK);
SORTER(TimeFuzz);
SORTER(Select);
SORTER(Mailbox);
SORTER(Anal);

#define SORT_BUTTON_WIDE 11
#define kHeaderButtonHeight (GROW_SIZE+5)
#define kTabHeight 25
#define kTabGap 2
#define kLargeUniqueValue 0x7f7f7f7f

short BoxPreviewHeight(TOCHandle tocH);
pascal OSErr MessDragSend(FlavorType flavor, void *dragSendRefCon, ItemReference theItemRef, DragReference drag);
pascal OSErr BoxDragSend(FlavorType flavor, void *dragSendRefCon, ItemReference theItemRef, DragReference drag);

void HitTab(MyWindowPtr win,ControlHandle tabCntl,TOCHandle tocH);

//	globals
TOCHandle gSortTOC;
MyWindowPtr gDrawerOpenedWin;

/************************************************************************
 * BoxLimits - return the left and right sides of a particular mailbox
 * column.  Values are NOT shifted according to scroll bars.  Values are
 * in pixels, not character widths
 ************************************************************************/
short BoxLimits(BoxLinesEnum which,short *left,short *right,TOCHandle tocH)
{
	short column;
	short wi;
	short i;
			
	*right = 0;
	for (i=1;;i++)
	{
		column = TOCInversionMatrix[0][i];
		if (!BoxColumnShowing(column,tocH))
			wi = 0;
		else
		{
			ASSERT(column > 0 && column<BoxLinesLimit);
			wi = (*BoxWidths)[column-1] ? (*BoxWidths)[column-1]*FontWidth : 4;
			*right += wi;
		}
		if (column==which) break;
	}
	*left = *right-wi;
	return(*right-*left);
}

/**********************************************************************
 * BoxColumnShowing - is a column showing?
 **********************************************************************/
Boolean BoxColumnShowing(BoxLinesEnum which,TOCHandle tocH)
{
	// junk column is hidden by default in all mailboxes except junk,
	// where it always shows
	if (which==blJunk)
	{
		if (((*tocH)->which==JUNK) || IsIMAPJunkMailbox(TOCToMbox(tocH))) return true;
		else return 0!=(GetPrefLong(PREF_BOX_HIDDEN)&(1<<(which-1)));
	}
	
	// Ordinary column processing
	if (GetPrefLong(PREF_BOX_HIDDEN)&(1<<(which-1))) return false;
	
	// other special items
	if (which==blMailbox && !(tocH && (*tocH)->virtualTOC)) return false;
	if (which==blAnal && (AnalDisabled() || !AnalDoIncoming())) return false;
	return true;
}

/**********************************************************************
 * BoxUpdate - handle an update event for a mailbox window
 **********************************************************************/
#define SUMMARY_ICON_SETUP do { \
		r.bottom = pr.bottom-1; \
		r.top = pr.top+1; \
		r.left = h0+left+2; r.right = h0+right-2; \
		SetRect(&ir,0,0,16,16); \
		CenterRectIn(&ir,&r); \
		r.bottom = MIN(r.bottom,win->contR.bottom); \
		r.top = MAX(r.top,cr.bottom-win->vPitch); \
		ClipRect(&r); \
	} while (0);

void BoxUpdate(MyWindowPtr win)
{
	WindowPtr winWP = GetMyWindowWindowPtr(win);
	CGrafPtr	winWPPort = GetWindowPort(winWP);
	TOCHandle tocH = (TOCHandle) GetWindowPrivateData(winWP);
	MSumType sum;
	int min, max, sumNum;
	int line;
	int h,v;
	Str31 states,scratch;
	Str31 pfix;
	Rect r,pr,cr,ir;
	short h0;
	short greyMax;
  Boolean isOut = (*tocH)->which==OUT;
	short left,right;
	Boolean hasColor = IsColorWin(winWP);
	RGBColor oldColor, color, textColor;
	Str255 string;
	short label;
	PersHandle pers;
	short lPref;
	short depth = RectDepth(&win->contR);
	Boolean useUglyStupidIconsBecauseLettersAreTooClearAndUnambiguous = !PrefIsSet(PREF_NO_SUMMARY_ICONS);
	Rect visR = *GetVisibleRgnBounds(winWP,&visR);
	short inIn = GetRLong(IN_IN_STYLE);			// Style to be used for incoming messages in an incoming box
	short outOut = GetRLong(OUT_OUT_STYLE);	// Style to be used for outgoing messages in the Out box
	short inOut = GetRLong(IN_OUT_STYLE);		// Style to be used for incoming messages in the Out box
	Pattern	thePattern;
	
	/*
	 * redraw it all if we need to calculate the window width
	 */
	if (win->hMax < 0)
	{
		(*tocH)->needRedo = 0;
		return;
	}
	
	/*
	 * reset font & size, just in case
	 */
	TextFont(FontID);
	TextSize(FontSize);
		
	/*
	 * Set the clipping region to be the content minus the scroll bars plus the top margin
	 */
	r = win->contR;
	r.top = 0;
	ClipRect(&r);

	/*
	 * Set min and max to be the top & bottom visible summary lines
	 */
	min = GetControlValue(win->vBar);
	max = min+RoundDiv(win->contR.bottom-win->contR.top/*-win->topMargin*/,win->vPitch)+1;
	max = MIN((*tocH)->count-1,max);
	h = h0 = -GetControlValue(win->hBar)*win->hPitch;
	r = win->contR;

	/*
	 * draw the column colors
	 */
	if (UseListColors)
	{
		for (line=1;line<BoxLinesLimit;line++)
		{
			if (BoxLimits(line,&r.left,&r.right,tocH))
			{
				OffsetRect(&r,h0,0);
				SetThemeBackground(MBGetSort(tocH,line) ? kThemeBrushListViewSortColumnBackground:kThemeBrushListViewBackground,RectDepth(&r),true);
				EraseRect(&r);
			}
		}
		SET_COLORS;
	}
	
	/*
	 * draw the grey lines
	 */
	if (hasColor)
	{
		GetRColor(&textColor,TEXT_COLOR);
		Zero(oldColor);
	}
	if ((lPref=GetPrefLong(PREF_NO_LINES))!=3)
	{
		if (hasColor)
		{
			if (UseListColors)
				SetThemePen(kThemeBrushListViewSeparator,RectDepth(&win->contR),true);
			else
			{
				GetRColor(&color,ColorIsLight(&win->backColor) ? SEP_LINE_DARK_COLOR:SEP_LINE_LIGHT_COLOR);
				RGBForeColor(&color);
			}
		}
		else
			PenPat(GetQDGlobalsGray(&thePattern));
		greyMax = win->topMargin+(max-min+1)*(short)win->vPitch;
		
		if (!(lPref & 1)) for (v=win->topMargin;v<=greyMax;v+=win->vPitch)
		{
			MoveTo(0,v); Line(REAL_BIG,0);
		}
		if (!(lPref & 2)) for (line=1;line<BoxLinesLimit;line++)
		{
			if (BoxLimits(line,&left,&right,tocH))
			{
				MoveTo(h0+right-1,win->topMargin);
				Line(0,10000);
			}		
		}
	}
	
	// Adjust max
	if (max>(*tocH)->count-1) max = (*tocH)->count-1;

	/*
	 * now, draw the contents
	 */
	SET_COLORS;
	if (UseListColors) SetThemeTextColor(kThemeTextColorListView,depth,True); else RGBForeColor(&textColor);
	GetRString(pfix,(*tocH)->which==OUT?COMP_OUT_INTRO:COMP_IN_INTRO);
	sum=(*tocH)->sums[min];
	PenPat(GetQDGlobalsBlack(&thePattern));
	GetRString(states,STATE_LABELS);
	v = win->topMargin+(1-(GetControlValue(win->vBar)-min))*win->vPitch - FontDescent;
	r.left = 0;
	r.right = REAL_BIG;
	BoxLimits(blPrior,&pr.left,&pr.right,tocH);
	pr.left += (pr.right-pr.left-16+1)/2;
	pr.top = win->topMargin + (win->vPitch-16+1)/2;
	pr.bottom = pr.top+16;
	pr.right = pr.left+16;
	OffsetRect(&pr,h0,(min-GetControlValue(win->vBar))*win->vPitch);
	for (line=0,sumNum=min; line<=(max-min); line++,sumNum++,v+=win->vPitch,OffsetRect(&pr,0,win->vPitch))
	{
		sum = (*tocH)->sums[sumNum];
		
		if (pr.bottom<visR.top || pr.top>visR.bottom) continue;
		
		if (isOut)
			TextFace(outOut);
		else if (sum.state==SENT || sum.state==UNSENT || (sum.flags & FLAG_OUT) || (sum.opts & OPT_IMAP_SENT))
			TextFace(inOut);
		else
			TextFace(inIn);
			
		pers = SUM_TO_PPERS(&sum);

		BoxLimits(blPrior,&left,&right,tocH);
		cr.bottom = MIN(v+FontDescent+1,win->contR.bottom);
		cr.top = MAX(win->contR.top,cr.bottom-win->vPitch);
		cr.left = h0+left+1; cr.right = h0+right-2;
		ClipRect(&cr);
		if (right-left>6) DrawPriority(&pr,sum.priority);

		if (sum.flags & FLAG_HAS_ATT)
		{
			if (BoxLimits(blAttach,&left,&right,tocH)>6)
			{
				SUMMARY_ICON_SETUP;
				PlotIconID(&ir,atNone, ttNone,ATTACH_ICON);
			}
		}

		// moodmail
		if (BoxLimits(blAnal,&left,&right,tocH)>6)
		{
			// if it scored above one, display it
			if (sum.score > 1)
			{
				SUMMARY_ICON_SETUP;
				PlotIconID(&ir,atNone, ttNone,ANAL_ICON_BASE+sum.score-1);
			}
		}

		// draw the icon in the server status column
		if ((*tocH)->imapTOC)
		{			
			if (BoxLimits(blServer,&left,&right,tocH)>6)
			{
				SUMMARY_ICON_SETUP;
				
				// draw the DELETED icon if the message is marked for deletion
				if (sum.opts&OPT_DELETED) PlotIconID(&ir,atAbsoluteCenter,ttNone,IMAP_DELETED_ICON);	
				// draw the MISSING EVERYTHING icon if nothing has been fetched yet
				else if (sum.offset<0) PlotIconID(&ir,atAbsoluteCenter,ttNone,IMAP_NOTHING_FETCHED);
				// draw the MISSING ATTACHMENTS icon if one or more attachments is stubular
				else if (sum.opts&OPT_FETCH_ATTACHMENTS) PlotIconID(&ir,atAbsoluteCenter,ttNone,IMAP_FETCHED_BODY);
				// otherwise draw the COMPLETELY FETCHED icon if the message is totally present
				else PlotIconID(&ir,atAbsoluteCenter,ttNone,IMAP_FETCHED_ALL);
			}		
		}

		if (IdIsOnPOPD(PERS_POPD_TYPE(pers),POPD_ID,sum.uidHash))
		{
			short iconID;
		
			if (BoxLimits(blServer,&left,&right,tocH)>6)
			{
				SUMMARY_ICON_SETUP;
			
				iconID = ON_SERVER_ICON;
				if ((sum.flags & FLAG_SKIPPED) && IdIsOnPOPD(PERS_POPD_TYPE(pers),FETCH_ID,sum.uidHash))
				{
					if (IdIsOnPOPD(PERS_POPD_TYPE(pers),DELETE_ID,sum.uidHash))
						iconID = ON_BOTH_ICON;
					else
						iconID = ON_FETCH_ICON;
				}
				else if (IdIsOnPOPD(PERS_POPD_TYPE(pers),DELETE_ID,sum.uidHash))
					iconID = ON_DELETE_ICON;
				
				PlotIconID(&ir,atAbsoluteCenter,ttNone,iconID);
			}
		}

		if (label = SumColor(&sum))
		{
			MyGetLabel(label,&color,string);
			if (hasColor && !BlackWhite(&color) && !PrefIsSet(PREF_SMALL_COLOR)) RGBForeColor(DarkenColor(&color,GetRLong(TEXT_DARKER)));
		}
		else *string = 0;

		BoxLimits(blStat,&left,&right,tocH);

		cr.left = h0+left+1; cr.right = h0+right-2;
		cr.right = MIN(cr.right,win->contR.right);
		if (cr.right-cr.left+2>=FontWidth)
		{
			ClipRect(&cr);
			MoveTo(h0+left+FontWidth/2+1,v);
			if (useUglyStupidIconsBecauseLettersAreTooClearAndUnambiguous)
			{
				SUMMARY_ICON_SETUP;
				PlotIconID(&ir,atAbsoluteCenter,ttNone,MESSAGE_STATUS_ICON_BASE+sum.state-1);
			}
			else			
				DrawChar(states[sum.state]);
		}
		
		
		//if (!isOut && sum.flags &FLAG_OUT) TextFace(italic);

		BoxLimits(blLabel,&left,&right,tocH);				
		cr.left = h0+left+1; cr.right = h0+right-2;
		cr.right = MIN(cr.right,win->contR.right);
		if (cr.right-cr.left+2>=FontWidth)
		{
			Boolean	mustRestore = false;
			short	saveTextMode = GetPortTextMode(GetQDGlobalsThePort());

			if (*string && PrefIsSet(PREF_SMALL_COLOR) && hasColor && !BlackWhite(&color))
			{
				Rect	rPaint;			

				rPaint.bottom = v+FontDescent+1;
				rPaint.top = MAX(win->contR.top,rPaint.bottom-win->vPitch);
				rPaint.bottom = MIN(rPaint.bottom,win->contR.bottom)-1;
				rPaint.left = cr.left-1;
				rPaint.right = cr.right+1;
				ClipRect(&rPaint);
				RGBForeColor(LightenColor(&color,GetRLong(AREA_LIGHTER)));
				PaintRect(&rPaint);
				TextMode(notSrcCopy);
				mustRestore = true;
			}
			ClipRect(&cr);
			MoveTo(h0+left+FontWidth/2+1,v);
			DrawString(string);
			if (mustRestore)
			{
				TextMode(saveTextMode);
				if (hasColor) if (UseListColors) SetThemeTextColor(kThemeTextColorListView,depth,True); else RGBForeColor(&textColor);
			}
		}

		BoxLimits(blFrom,&left,&right,tocH);				
		cr.left = h0+left+1; cr.right = h0+right-2;
		cr.right = MIN(cr.right,win->contR.right);
		if (cr.right-cr.left+2>=FontWidth)
		{
			ClipRect(&cr);
			MoveTo(h0+left+FontWidth/2+1,v);
			if (*pfix && (isOut || (sum.flags & FLAG_OUT) || (sum.opts & OPT_IMAP_SENT))) DrawString(pfix);
			if(!(sum.flags & FLAG_UTF8) || !HasUnicode() || DrawUTF8Text(&sum.from[1], sum.from[0], right - left - 3, normFonts)) {
				DrawString(sum.from);
			}
		}
				
		BoxLimits(blDate,&left,&right,tocH);
		cr.left = h0+left+1; cr.right = h0+right-2;
		cr.right = MIN(cr.right,win->contR.right);
		if (cr.right-cr.left+2>=FontWidth)
		{
			ClipRect(&cr);
			MoveTo(h0+left+FontWidth/2+1,v);
			if (sum.seconds)
			{
				ComputeLocalDate(&sum,scratch);
				DrawString(scratch);
			}
		}

		BoxLimits(blSize,&left,&right,tocH);
		cr.left = h0+left+1; cr.right = h0+right-2;
		cr.right = MIN(cr.right,win->contR.right);
		if (cr.right-cr.left+2>=FontWidth)
		{
		 	ClipRect(&cr);
#ifdef DEBUG
			if (RunType!=Production && BUG2)
			{
				NumToString(sum.serialNum,scratch);
#ifdef NEVER
				//	Check for and flag duplicate serial numbers with '¥'
				//	Only works if mailbox is sorted by serial numbers
				if (sum.serialNum == sum[1].serialNum || sum.serialNum == sum[-1].serialNum)
					PCat(scratch,"\p ¥");
#endif
#ifdef NEVER
				NumToString(sum.score,scratch);
#endif
			}
			else
#endif
			if (sum.flags&FLAG_SKIPPED)
			{
				*scratch = 1;
				scratch[1] = '¤';
			}
#ifdef NOBODY_SPECIAL
			else if (sum.opts & OPT_JUSTSUB)
			{
				*scratch = 1;
				scratch[1] = '¥';
			}
#endif // NOBODY_SPECIAL
			else
			{
				NumToString(DisplayLength(&sum)/1024,scratch);
			}
			MoveTo(h0+right-FontWidth/4-StringWidth(scratch),v);
			if (!isOut && (sum.state==SENT || sum.state==UNSENT || (sum.flags & FLAG_OUT) || (sum.opts & OPT_IMAP_SENT))) Move(-(2*FontWidth)/3,0);
			DrawString(scratch);
		}

		BoxLimits(blJunk,&left,&right,tocH);
		cr.left = h0+left+1; cr.right = h0+right-2;
		cr.right = MIN(cr.right,win->contR.right);
		if (cr.right-cr.left+2>=FontWidth && sum.spamScore > 0)
		{
		 	ClipRect(&cr);
			NumToString(sum.spamScore,scratch);
#ifdef DEBUG
			if (BUG7)
			{
				PCatC(scratch,'-');
				PCatC(scratch,'0'+sum.spamBecause);
			}
#endif
			MoveTo(h0+right-FontWidth/4-StringWidth(scratch),v);
			if (!isOut && (sum.state==SENT || sum.state==UNSENT || (sum.flags & FLAG_OUT) || (sum.opts & OPT_IMAP_SENT))) Move(-(2*FontWidth)/3,0);
			DrawString(scratch);
		}
		
		BoxLimits(blMailbox,&left,&right,tocH);
		cr.left = h0+left+1; cr.right = h0+right-2;
		cr.right = MIN(cr.right,win->contR.right);
		if (cr.right-cr.left+2>=FontWidth)
		{
			ClipRect(&cr);
			MoveTo(h0+left+FontWidth/2+1,v);
			GetMailboxName(tocH,sumNum,scratch);
			DrawString(scratch);
		}		
		
		BoxLimits(blSubject,&left,&right,tocH);
		cr.left = h0+left+1; cr.right = h0+right-2;
		cr.right = MIN(cr.right,win->contR.right);
		if (cr.right-cr.left+2>=FontWidth)
		{
			ClipRect(&cr);
			MoveTo(h0+left+FontWidth/2,v);
			if(!(sum.flags & FLAG_UTF8) || !HasUnicode() || DrawUTF8Text(&sum.subj[1], sum.subj[0], cr.right - cr.left, normFonts)) {
				DrawString(sum.subj);
			}
		}
		
		ClipRect(&win->contR);
		
		if (sum.selected) InvertSum(tocH,sumNum,False);
		TextFace(normal);
		if (UseListColors) SetThemeTextColor(kThemeTextColorListView,depth,True); else RGBForeColor(&textColor);
	}
	
	/*
	 * cleanup
	 */
	TextFace(normal);
	InfiniteClip(winWPPort);
}



/**********************************************************************
 * Item2Status - turn a menu item into a status value
 **********************************************************************/
short Item2Status(short item)
{
	switch (item)
	{
		case statmUnread: return(UNREAD);
		case statmRead: return(READ);
		case statmReplied: return(REPLIED);
		case statmForwarded: return(FORWARDED);
		case statmRedirected: return(REDIST);
		case statmUnsendable: return(UNSENDABLE);
		case statmSendable: return(SENDABLE);
		case statmQueued: return(QUEUED);
		case statmTimed: return(TIMED);
		case statmUnsent: return(UNSENT);
		case statmSent: return(SENT);
		case statmMesgError: return(MESG_ERR);
		case statmRecovered: return(REBUILT);
		default: return(0);
	}
}

/**********************************************************************
 * Status2Item - turn a status value into a menu item
 **********************************************************************/
short Status2Item(short item)
{
	switch (item)
	{
		case UNREAD: return statmUnread;
		case READ: return statmRead;
		case REPLIED: return statmReplied; 
		case FORWARDED: return statmForwarded; 
		case REDIST: return statmRedirected;
		case UNSENDABLE: return statmUnsendable; 
		case SENDABLE: return statmSendable; 
		case QUEUED: return statmQueued; 
		case TIMED: return statmTimed; 
		case UNSENT: return statmUnsent; 
		case SENT: return statmSent; 
		case MESG_ERR: return statmMesgError;
		case REBUILT: return statmRecovered;
		default: return(0);
	}
}


/************************************************************************
 * SelectBoxRange - make a particular range in a mailbox the current selection.
 ************************************************************************/
void SelectBoxRange(TOCHandle tocH,int start,int end,Boolean cmd,int eStart,int eEnd)
{
	MyWindowPtr win = (*tocH)->win;
	/*
	 * topVis and botVis are overly generous, since it won't hurt to be so
	 */
	int topVis = GetControlValue(win->vBar);
	int botVis = (*tocH)->count-(GetControlMaximum(win->vBar)-GetControlValue(win->vBar));
	int sNum;
	int r1, r2;
	
	if (!(*tocH)->count) return;
	PushGWorld();
	SetPort_(GetMyWindowCGrafPtr((*tocH)->win));
	
	if (GetPrefLong(PREF_MANUAL_BOX_SELECTION)==0)
		TOCSetDirty(tocH,true);	// now that we're saving the selection...

	ClipRect(&win->contR);
	
	if (end<start) {r1=start;start=end;end=r1;} 	/* swap */
	if (eEnd<eStart) {r1=eStart;eStart=eEnd;eEnd=r1;} 		/* swap */
	
	r1 = MIN(start,(*tocH)->count);
	r2 = MIN(end,(*tocH)->count-1);
	r1 = MAX(r1,0);
	r2 = MAX(r2,0);
	win->hasSelection = False;
	
	/*
	 * first range; from the beginning to just before the new selection range
	 */
	if (cmd)
	{
		for (sNum=0;sNum<r1;sNum++)
			if ((*tocH)->sums[sNum].selected)
			{
				win->hasSelection = True;
				break;
			}
	}
	else
	{
		for (sNum=0;sNum<r1;sNum++)
			if ((*tocH)->sums[sNum].selected)
			{
				(*tocH)->userActive = false;
				(*tocH)->sums[sNum].selected = False;
				if (topVis <= sNum && sNum <= botVis)
					InvertSum(tocH,sNum,False);
			}
	}
	
	/*
	 * the new selection range
	 */
	if (cmd)
	{
		for (sNum=r1;sNum<=r2;sNum++)
			if (sNum<eStart || sNum > eEnd)
			{
				(*tocH)->userActive = false;
				(*tocH)->sums[sNum].selected = !(*tocH)->sums[sNum].selected;
				if (topVis <= sNum && sNum <= botVis)
					InvertSum(tocH,sNum,False);
				win->hasSelection = win->hasSelection || (*tocH)->sums[sNum].selected;
			}
	}
	else
	{
		for (sNum=r1;sNum<=r2;sNum++)
		{
			if (!(*tocH)->sums[sNum].selected)
			{
				(*tocH)->userActive = false;
				(*tocH)->sums[sNum].selected = True;
				if (topVis <= sNum && sNum <= botVis)
					InvertSum(tocH,sNum,False);
			}
		}
		win->hasSelection = r1<=r2;
	}
	
	/*
	 * above the new selection range
	 */
	if (cmd)
	{
		if (!win->hasSelection)
			for (sNum=r2+1;sNum<(*tocH)->count;sNum++)
				if ((*tocH)->sums[sNum].selected)
				{
					win->hasSelection = True;
					break;
				}
	}
	else
	{
		for (sNum=r2+1;sNum<(*tocH)->count;sNum++)
			if ((*tocH)->sums[sNum].selected)
			{
				(*tocH)->userActive = false;
				(*tocH)->sums[sNum].selected = False;
				if (topVis <= sNum && sNum <= botVis)
					InvertSum(tocH,sNum,False);
			}
	}
	
	InfiniteClip(GetMyWindowCGrafPtr(win));
	PopGWorld();
	
	(*tocH)->updateBoxSizes = true;
	(*tocH)->conConMultiScan = true;
}

/**********************************************************************
 * BoxSetSummarySelected - make sure a summary is selected or not
 **********************************************************************/
void BoxSetSummarySelected(TOCHandle tocH,short sumNum,Boolean selected)
{
	if ((*tocH)->sums[sumNum].selected != selected)
	{
		(*tocH)->sums[sumNum].selected = selected;
		InvalSum(tocH,sumNum);
		(*tocH)->updateBoxSizes = true;
		(*tocH)->conConMultiScan = true;
	}
}

/**********************************************************************
 * BoxActivate - perform extra mailbox processing for activate/deactivate
 **********************************************************************/
void BoxActivate(MyWindowPtr win)
{
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	
	SetPort_(GetMyWindowCGrafPtr(win));
	if (!win->pte && !(*tocH)->searchFocus) BoxListFocus(tocH,win->isActive);
	ShowBoxSizes(win);
	
	if ((*tocH)->drawerWin)
		// Keep drawer in sync
		MBDrawerActivate((*tocH)->drawerWin,win->isActive);
}

/**********************************************************************
 * BoxListFocus - focus on the list
 **********************************************************************/
void BoxListFocus(TOCHandle tocH,Boolean focus)
{
	int topVis;
	int botVis;
	int sNum;
	MyWindowPtr win = (*tocH)->win;
	CGrafPtr	winPort = GetMyWindowCGrafPtr(win);
	
	SetPort_(winPort);
	if (focus!=(*tocH)->listFocus)
	{
		(*tocH)->listFocus = focus;
		if (focus) (*tocH)->searchFocus = false;
		if (FirstMsgSelected(tocH)!=-1)
		{
			ClipRect(&win->contR);
			topVis = GetControlValue(win->vBar);
			botVis = (*tocH)->count -
										(GetControlMaximum(win->vBar)-GetControlValue(win->vBar));
			if (botVis==(*tocH)->count) botVis--;
			for (sNum=topVis; sNum<=botVis; sNum++)
				if ((*tocH)->sums[sNum].selected) InvertSum(tocH,sNum,True);
			InfiniteClip(winPort);
		}
	}
}

/**********************************************************************
 * InvertSum - invert a summary, doing it correctly (more or less)
 **********************************************************************/
void InvertSum(TOCHandle tocH,short sumNum,Boolean forActivate)
{
	Rect r, r2;
	Boolean above, below;	// are sums above and below selected?
	MyWindowPtr win = (*tocH)->win;
	
	r.top = win->topMargin+(sumNum-GetControlValue(win->vBar))*win->vPitch;
	r.bottom = r.top + win->vPitch;
	r.left = 0;
	r.right = win->contR.right;

	//adjust top of rectangle, which otherwise overlaps top line
	r.top++;
	
	RGBBackColor(&win->backColor);
	if (!forActivate && (*tocH)->listFocus && !InBG)
	{
		UseListColors ? InvertSumByColumns(tocH,&r) : HiInvertRect(&r);
	}
	else
	{
		// are sums above or below selected?
		//above = sumNum && (*tocH)->sums[sumNum-1].selected;
		//below = sumNum<(*tocH)->count-1 && (*tocH)->sums[sumNum+1].selected;
		above = below = False;
		
		if (above) r.top--;	//if selected above, overlap with separator line
		if (below) r.bottom++; //if selected below, overlap with separator line
		if (forActivate)
		{
			InsetRect(&r,1,1);	//if activation, don't do rim, just inside
			UseListColors ? InvertSumByColumns(tocH,&r) : HiInvertRect(&r);
		}
		else
		{
			// top
			r2 = r;
			r2.bottom = r.top+1;
			UseListColors ? InvertSumByColumns(tocH,&r2) : HiInvertRect(&r2);
			// bottom
			r2 = r;
			r2.top = r.bottom-1;
			UseListColors ? InvertSumByColumns(tocH,&r2) : HiInvertRect(&r2);
			// left
			r2 = r;
			r2.top++; r2.bottom--;	//take care of overlap with top, bottom
			r2.right = r2.left+1;
			UseListColors ? InvertSumByColumns(tocH,&r2) : HiInvertRect(&r2);
			// right
			r2 = r;
			r2.top++; r2.bottom--;	//take care of overlap with top, bottom
			r2.left = r2.right-1;
			UseListColors ? InvertSumByColumns(tocH,&r2) : HiInvertRect(&r2);
		}
	}
}

/**********************************************************************
 * InvertSumByColums - invert a summary, paying attention to column backgrounds
 **********************************************************************/
void InvertSumByColumns(TOCHandle tocH,Rect *r)
{
	Rect r2;
	short line;
	short h = -GetControlValue((*tocH)->win->hBar)*(*tocH)->win->hPitch;
	
	for (line=1;line<BoxLinesLimit;line++)
	{
		r2 = *r;
		if (BoxLimits(line,&r2.left,&r2.right,tocH))
		{
			OffsetRect(&r2,h,0);
			if (SectRect(r,&r2,&r2))
			{
	 			SetThemeBackground(MBGetSort(tocH,line) ? kThemeBrushListViewSortColumnBackground:kThemeBrushListViewBackground,RectDepth(&r2),true);
				HiInvertRect(&r2);
			}
		}
	}
	SET_COLORS;
}

/**********************************************************************
 * BoxMenu - handle a menu choice for a mailbox
 **********************************************************************/
Boolean BoxMenu(MyWindowPtr win,int menu,int item,short modifiers)
{
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	short state;
	short tableId;
	uLong when;
	MSumPtr sum;
	Boolean swap = False;
	TextAddrHandle addr=nil;
	short sumNum;
#ifdef TWO
	FilterPB fpb;
#endif	

	if (menu==MESSAGE_MENU && item==MESSAGE_QUEUE_ITEM && modifiers&optionKey)
	{
		menu = CHANGE_HIER_MENU;
	  item = CHANGE_QUEUEING_ITEM;
	  swap = True;
	}

	switch (menu)
	{
		case FILE_MENU:
			switch(item)
			{
				case FILE_OPENSEL_ITEM:
					if (win->hasSelection)
						BoxOpen(win);
					else
						return(False);
					break;
				case FILE_SAVE_AS_ITEM:
					DoSaveSelectedAs(tocH);
					return(True);
					break;
				case FILE_BROWSE_ITEM:
					DoIterativeThingy(tocH,menu,modifiers,(void*)item);
					return(true);
					break;
				case FILE_PRINT_ONE_ITEM:
				case FILE_PRINT_ITEM:
					if (win->hasSelection)
					{
						long beginSel, endSel;
						PETEHandle pte = nil;
						
						if ((*tocH)->previewID && (*tocH)->previewPTE && PeteLen((*tocH)->previewPTE))
						{
							if ((modifiers&shiftKey) && AttIsSelected(win,win->pte,-1,-1,attOpen+attPrint,nil,nil))
								break;// done
							else
								PeteGetTextAndSelection((*tocH)->previewPTE,nil,&beginSel,&endSel);
							pte = (*tocH)->previewPTE;
						}
						else beginSel = endSel = 0;
#ifdef	GX_PRINTING
						if (gGXIsPresent)
							GXPrintSelectedMessages(tocH,beginSel!=endSel && (modifiers&shiftKey),item==FILE_PRINT_ONE_ITEM,beginSel,endSel);
						else
#endif	//GX_PRINTING
						(void) PrintSelectedMessages(tocH,beginSel!=endSel && (modifiers&shiftKey),item==FILE_PRINT_ONE_ITEM,beginSel,endSel,pte);
					}
					else
						return(False);
					break;
				default:
					return(False);
			}
			break;
		
		case EDIT_MENU:
#ifdef SPEECH_ENABLED
			if (win->pte && item != EDIT_SPEAK_ITEM) return(false);	// preview focus!
#else
			if (win->pte) return(false);	// preview focus!
#endif
			switch(item)
			{
				case EDIT_CLEAR_ITEM:
					if ((*tocH)->previewID && PreviewReadDelete && (sumNum=LastMsgSelected(tocH))>=0)
						BeenThereDoneThat(tocH,sumNum); 
					DoIterativeThingy(tocH,MESSAGE_DELETE_ITEM,modifiers,0);
					break;
				case EDIT_COPY_ITEM:
					CopySelectedSummaries(tocH);
					break;
				case EDIT_SELECT_ITEM:
					if ((modifiers&optionKey) && -1<(sumNum=LastMsgSelected(tocH)))
						BoxSelectSame(tocH,(*tocH)->lastSort==SORT_SUBJECT_ITEM?SORT_SUBJECT_ITEM:SORT_SENDER_ITEM,sumNum);
					else
						SelectBoxRange(tocH,0,(*tocH)->count-1,False,0,0);
					break;
#ifdef SPEECH_ENABLED
				case EDIT_SPEAK_ITEM:
					(void) SpeakSelectedMessages (tocH);
					break;
#endif
				default:
					return(False);
					break;
			}
			break;
			
		case SORT_HIER_MENU:
			SetMyCursor(watchCursor);
			if (item==SORT_GROUP_ITEM)
				BoxToggleLaurence(tocH);
			else if (item>SORT_BAR2_ITEM)
				BoxCustomSort(tocH,item-SORT_BAR2_ITEM);
			else
			{
				MBSortHit(tocH,BoxItem2Line(item),(modifiers&optionKey)!=0,(modifiers&shiftKey)!=0);
			}
			break;
			
		case MESSAGE_MENU:
			switch(item)
			{
				case MESSAGE_DELETE_ITEM:
					if ((*tocH)->previewID && PreviewReadDelete && (sumNum=LastMsgSelected(tocH))>=0)
						BeenThereDoneThat(tocH,sumNum); 
					NoSaves = True;
				case MESSAGE_FORWARD_ITEM:
				case MESSAGE_REDISTRIBUTE_ITEM:
				case MESSAGE_SALVAGE_ITEM:
					DoIterativeThingy(tocH,item,modifiers,0);
					NoSaves = False;
					break;
				case MESSAGE_QUEUE_ITEM:
					QueueSelectedMessages(tocH,QUEUED,0);
					if (!Offline && PrefIsSet(PREF_AUTO_SEND))
						XferMail(False,True,False,False,True,0);
					break;
				case MESSAGE_JUNK_ITEM:
				case MESSAGE_NOTJUNK_ITEM:
					Junk(tocH,-1,item==MESSAGE_JUNK_ITEM,false);
					break;
				case MESSAGE_REPLY_ITEM:
					BoxReply(tocH,menu,item,modifiers);
					NoSaves = false;
				default:
					return(False);
			}
			break;
		
		case CHANGE_HIER_MENU:
			switch(item)
			{
				case CHANGE_QUEUEING_ITEM:
					when = 0;
					for (sum=(*tocH)->sums;sum<(*tocH)->sums+(*tocH)->count;sum++)
						if (sum->selected)
						{
							if (sum->state==TIMED) when = sum->seconds;
							state = sum->state;
							break;
						}
					if (ModifyQueue(&state,&when,swap))
					{
					  Boolean now = state==SENT;
						if (now) state = QUEUED;
					  QueueSelectedMessages(tocH,state,when);
						if (now) XferMail(False,True,False,False,True,0);
					}
					break;
				default: return(False);
			}
			break;

		case TABLE_HIER_MENU:
			if (Menu2TableId(tocH,GetMHandle(TABLE_HIER_MENU),item,&tableId))
				DoIterativeThingy(tocH,menu,modifiers,(void*)tableId);
			break;
			
		case STATE_HIER_MENU:
			if (item == statmQueued) QueueSelectedMessages(tocH,QUEUED,0);
			else 
			{
				DoIterativeThingy(tocH,menu,modifiers,(void*)item);
				SetSendQueue();
			}
			break;
			
		case PERS_HIER_MENU:
			if (HasFeature (featureMultiplePersonalities)) {
				UseFeature (featureMultiplePersonalities);
				DoIterativeThingy(tocH,menu,modifiers,(void*)item);
			}
			break;
		case LABEL_HIER_MENU:
		case SERVER_HIER_MENU:
			DoIterativeThingy(tocH,menu,modifiers,(void*)item);
			break;

		case SPECIAL_MENU:
			switch(AdjustSpecialMenuSelection(item))
			{
				case SPECIAL_MAKE_NICK_ITEM:
					if (win->pte && modifiers&shiftKey) MakeNickFromSelection(win);
					else if ((*tocH)->which==OUT) MakeCboxNick(win);
					else MakeMboxNick(win,modifiers);
					break;
				case SPECIAL_MAKEFILTER_ITEM:
					DoMakeFilter(win);
					break;
				case SPECIAL_FILTER_ITEM:
					FilterSelectedMessages(flkManual,tocH,&fpb);
					FilterPostprocess(flkManual,&fpb);
					break;
				default:
					return(False);
			}
			break;
		case REPLY_WITH_HIER_MENU:
			if (HasFeature (featureStationery)) {
				UseFeature (featureStationery);
				DoIterativeThingy(tocH,MESSAGE_REPLY_ITEM,modifiers,(void*)item);
			}
			break;
		case FORWARD_TO_HIER_MENU:
			DoIterativeThingy(tocH,MESSAGE_FORWARD_ITEM,modifiers,addr=MenuItem2Handle(menu,item));
			break;
		case REDIST_TO_HIER_MENU:
			DoIterativeThingy(tocH,MESSAGE_REDISTRIBUTE_ITEM,modifiers,addr=MenuItem2Handle(menu,item));
			break;
		case PRIOR_HIER_MENU:
			DoIterativeThingy(tocH,menu,modifiers,(void*)item);
			break;
		case WINDOW_MENU:
			{
				Str255 s;
				if (!(win->pte && *PeteSelectedString(s,win->pte)))
				if (item==WIN_PH_ITEM && (modifiers&shiftKey) && 0<=(sumNum=FirstMsgSelected(tocH)))
				{
					OpenPh(PCopy(s,(*tocH)->sums[sumNum].from));
					break;
				}
			}
			return(False);
			break;

		case TLATE_SEL_HIER_MENU:
			if ((*tocH)->previewID && (*tocH)->previewPTE)
			{
				HeadSpec hs;
				UHandle text;
				
				PeteGetTextAndSelection((*tocH)->previewPTE,&text,&hs.value,&hs.stop);
				if (hs.value==hs.stop)
				{
					hs.value = BodyOffset(text);
					hs.stop = PeteLen((*tocH)->previewPTE);
				}
				hs.start = hs.value;
				ETLTransSelection((*tocH)->previewPTE,&hs,item);
			}
			else
				return(false);
			break;

		default:
			return(TransferMenuChoice(menu,item,tocH,-1,modifiers,False));
			break;
	}
	ZapHandle(addr);
	return(True);
}

/************************************************************************
 * BoxReply - reply to a selection in a mailbox
 ************************************************************************/
OSErr BoxReply(TOCHandle tocH,short menu,short item,short modifiers)
{
	short count = CountSelectedMessages(tocH);

	if (PrefIsSet(PREF_MULTI_REPLY) || count <= 1)
	{
		DoIterativeThingy(tocH,item,modifiers,nil);
		return noErr;
	}
	else
	{
		if (count > GetRLong(MULT_RESPOND_WARNING_THRESH))
			if (!MultiMessageOpOK(MULT_RESPOND_WARNING,count))
				return userCanceledErr;
		return DoReplyMultiple(tocH,modifiers)?noErr:userCanceledErr;
	}
}

/************************************************************************
 * MultiMessageOpOK - is it ok to do something to a lot of messages?
 ************************************************************************/
Boolean MultiMessageOpOK(short fmt,short count)
{
	return kAlertStdAlertCancelButton!=ComposeStdAlert(Caution,fmt,count);
}
	
void BoxToggleLaurence(TOCHandle tocH)
{
	Str255 s;
	ControlHandle cntl;
	
	GetRString(s,ColumnHeadStrn+blSubject);
	if ((*tocH)->laurence)
		(*tocH)->laurence = false;
	else
	{
		PCatC(s,'¥');
		(*tocH)->laurence = true;
	}
	(*tocH)->resort = kResortNow;
	if (cntl=FindControlByRefCon((*tocH)->win,'wide'+blSubject))
		SetControlTitle(cntl,s);
}

/************************************************************************
 * ApplySortString - apply a custom sort
 ************************************************************************/
void ApplySortString(TOCHandle tocH,PStr s)
{
	Boolean group;
	Byte sorts[12];
	short i;
	ControlHandle cntl;
	
	if (!InterpretSortString(s,&group,sorts,nil))
	{
		if (group!=(*tocH)->laurence) BoxToggleLaurence(tocH);
		for (i=0;i<12;i++)
			if (cntl=FindControlByRefCon((*tocH)->win,'wide'+i+1))
				SetControlValue(cntl,(sorts[i]!=0) ? 1:0);
		BMD(sorts,(*tocH)->sorts,sizeof((*tocH)->sorts));
		(*tocH)->resort = kResortNow;
	}
}

/************************************************************************
 * BoxCustomSort - apply a custom sort
 ************************************************************************/
void BoxCustomSort(TOCHandle tocH,short which)
{
	Str255 s;
	GetRString(s,SortsStrn+which);
	ApplySortString(tocH,s);
}

/**********************************************************************
 * MenuItem2Handle - convert a menu item to a handle
 **********************************************************************/
TextAddrHandle MenuItem2Handle(short menu, short item)
{
	Str255 itemStr;
	
	MyGetItem(GetMHandle(menu),item,itemStr);
	return((TextAddrHandle)PStr2Handle(itemStr));
}

/**********************************************************************
 * CheckSortItems - check the appropriate items in the sort menu
 **********************************************************************/
void CheckSortItems(MyWindowPtr win)
{
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	MenuHandle mh = GetMHandle(SORT_HIER_MENU);
	short item;
	short sortNum;
	
	for (item=1;item<=SORT_SUBJECT_ITEM;item++)
	{
		sortNum = BoxItem2Line(item);
		SetItemMark(mh,item,MBGetSort(tocH,sortNum) ? checkMark : noMark);
	}
	SetItemMark(mh,SORT_GROUP_ITEM,(*tocH)->laurence ? checkMark:noMark);
	EnableIf(mh,SORT_MOOD_ITEM,AnalDoIncoming());
	EnableIf(mh,SORT_MAILBOX_ITEM,(*tocH)->virtualTOC);
}

/************************************************************************
 * InterpretSortString - interpret a string that represents a complex sort
 ************************************************************************/
OSErr InterpretSortString(PStr s,Boolean *group,Byte *sorts,PStr menuItem)
{
	UPtr spot;
	Str63 token;
	short i;
	OSErr err = fnfErr;
	long num;
	
	spot = s+1;
	
	if (PToken(s,token,&spot," "))
	{
		if (group) *group = 0!=atoi(token+1);
		for (i=0;i<12;i++)
			if (PToken(s,token,&spot," "))
				if (sorts)
				{
					StringToNum(token,&num);
					sorts[i] = num;
				}
		if (PToken(s,token,&spot,"\377"))
		{
			err = noErr;
			if (menuItem) PCopy(menuItem,token);
		}
	}
	return(err);
}

/************************************************************************
 * Sort2String - represent a mailbox sort as a string
 ************************************************************************/
PStr Sort2String(PStr s,TOCHandle tocH)
{
	short i;
	Str15 num;
	
	*s = 0;
	PCatC(s,((*tocH)->laurence)!=0?'1':'0');
	PCatC(s,' ');
	for (i=0;i<12;i++)
	{
		NumToString((*tocH)->sorts[i],num);
		PCat(s,num);
		PCatC(s,' ');
	}
	PCatR(s,UNTITLED);
	return s;
}

/**********************************************************************
 * BoxFind - find in the window
 **********************************************************************/
Boolean BoxFind(MyWindowPtr win,PStr what)
{
	WindowPtr	winWP = GetMyWindowWindowPtr(win);
	TOCHandle tocH = (TOCHandle)GetWindowPrivateData(winWP);
	short	sumNum,start;
	Boolean	wrapped = false;
	MSumType *sum;
	DEC_STATE(tocH);
	
	if (win->pte && win->pte==(*tocH)->previewPTE)
		//	search in preview pane
		return FindInPTE(win,win->pte,what);
	
	if ((*tocH)->count)	//	don't search if window is empty
	{
		//	search summaries
		//	find last non-selected and start from there
		for (start=(*tocH)->count-1;start>=0;start--)
			if ((*tocH)->sums[start].selected) break;
		start++;
		
		for(sumNum = start;sumNum != start || !wrapped;sumNum++)
		{
			if (sumNum>=(*tocH)->count)
			{
				//	start from the top again
				sumNum = 0;
				wrapped = true;
				if (!start)
					break;	//	this is where we started
			}
			sum=(*tocH)->sums+sumNum;
			
			L_STATE(tocH);
			if (FindStrStr(what,sum->from)>=0 || FindStrStr(what,sum->subj)>=0)
			{
				U_STATE(tocH);
				//	found
				SelectBoxRange(tocH,sumNum,sumNum,false,-1,-1);
				BoxCenterSelection((*tocH)->win);
				if (!IsWindowVisible(winWP))
					ShowMyWindow(winWP);
				UserSelectWindow(winWP);
				UpdateMyWindow(winWP);
				SFWTC=True;
				return true;
			}
			U_STATE(tocH);
		}
		
	}
	return false;
}

/**********************************************************************
 * BoxClose - close a mailbox window, saving the toc if it's dirty
 **********************************************************************/
Boolean BoxClose(MyWindowPtr win)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	TOCType	**tocH = (TOCType **) GetWindowPrivateData (winWP);
	short sumNum;
	Boolean justHide = False;
	Boolean superClose = PrefIsSet(PREF_SUPERCLOSE) && IsWindowVisible (winWP);
	Boolean isOut = (*tocH)->which == OUT;
	FSSpec spec;
	FileViewHandle	fvh = GetFileViewInfo(win);
	
	if (IsWindowVisible(GetMyWindowWindowPtr((*tocH)->win))  || TOCIsDirty(tocH)) AnyTOCDirty++;
	
	// if this is an IMAP mailbox, we'll need to do some extra stuff to close it.
	if ((*tocH)->imapTOC != 0) 
	{
		IMAPAbortResync(tocH);
		justHide = IsIMAPMailboxBusy(tocH) || IMAPAutoExpungeMailbox(tocH);	// simply hide this mailbox if it's busy for some reason.
	}
	
	if (fvh && (*tocH)->fileView)
		(*tocH)->previewHi = (*fvh)->savePreviewHi;
	
	if (!GrowZoning)
	{
		/*
		 * should we compact?
		 */
		if (NeedAutoCompact(tocH))
		{
			spec = GetMailboxSpec(tocH,-1);
			StackPush(&spec,CompactStack);
		}
		
		
		/*
		 * first, close all associated message windows
		 */
		for (sumNum=(*tocH)->count-1;sumNum>=0;sumNum--)
		{
			if ((*tocH)->sums[sumNum].messH)
			{
				if (superClose && (!isOut ||!(*(*tocH)->sums[sumNum].messH)->win->isDirty))
				{
					if (!CloseMyWindow(GetMyWindowWindowPtr((*(*tocH)->sums[sumNum].messH)->win)))
						return(False);
				}
				else
					justHide = True;
			}
			if (!justHide && (*tocH)->sums[sumNum].cache) ZapHandle((*tocH)->sums[sumNum].cache);
		}
		
		/*
		 * dispose of transport error message data
		 */
		for (sumNum=(*tocH)->count-1;sumNum>=0;sumNum--)
			ZapHandle((*tocH)->sums[sumNum].mesgErrH);

		/*
		 * write if we're dirty
		 */
		if (TOCIsDirty(tocH))	WriteTOC(tocH);
		
		/*
		 * Save the IMAP mailbox information
		 */
		 if ((*tocH)->imapTOC) UpdateIMAPMailboxInfo(tocH);
	}
	
	BoxFClose(tocH,true);

	if (justHide)
	{
		HideWindow_(winWP);
		return(False);
	}

	if ((*tocH)->drawerWin)
	{
		// Close drawer
		CloseMyWindow(GetMyWindowWindowPtr((*tocH)->drawerWin));
		(*tocH)->drawerWin = NULL;
	}

	if (fvh)
	{
		//	save changes?
		if ((*fvh)->dirtyPrefs || (*fvh)->expandList.fExpandListChanged)
		{
			short oldResFile = CurResFile(),refN = 0;
			
			if ((*tocH)->refN) UseResFile((*tocH)->refN);
			else
			{
				FSSpec	resSpec = GetMailboxSpec(tocH,0);
				refN = FSpOpenResFile(&resSpec,fsRdWrPerm);
				if (refN == -1) refN = 0;
			}
			if ((*tocH)->refN || refN)
			{
				Handle	hFVPrefs;
				Handle	hExpandList;
				
				if ((*fvh)->expandList.fExpandListChanged)
				{
					hExpandList = Get1Resource(kExandListResType,kExandListResID);
					if (!hExpandList)
					{
						hExpandList = NuHandle(0);
						AddResource_(hExpandList,kExandListResType,kExandListResID,"");
					}
					if (hExpandList)
					{
						SetHandleSize(hExpandList,0);
						HandAndHand((Handle)(*fvh)->expandList.hExpandList,hExpandList);
						ChangedResource(hExpandList);
					}
				}
				if ((*fvh)->dirtyPrefs)
				{
					hFVPrefs = Get1Resource(kFVPrefsResType,kFVPrefsResID);
					if (!hFVPrefs)
					{
						hFVPrefs = NuHandle(sizeof(FVPrefs));
						(*(FVPrefsHandle)hFVPrefs)->version = kFVPrefsVersion;
						AddResource_(hFVPrefs,kFVPrefsResType,kFVPrefsResID,"");
					}
					if (hFVPrefs)
					{
						BMD(&(*fvh)->prefs,*hFVPrefs,sizeof(FVPrefs));
						ChangedResource(hFVPrefs);
					}
				}
				if (refN) CloseResFile(refN);
			}
			UseResFile(oldResFile);		
		}
		
		//	close file viewer
		FVDispose(fvh);
	}
	
	/*
	 * now, unlink ourselves from the list
	 */
	LL_Remove(TOCList,tocH,(TOCType **));
	
	/*
	 * bye, bye love
	 */
	ZapHandle(tocH);
	
	/*
	 * turn off some stuff
	 */
	ZeroWinFuncs(win);
	
	return(True); 
}

/**********************************************************************
 * BoxOpen - open some messages from within a mailbox
 **********************************************************************/
void BoxOpen(MyWindowPtr win)
{
	//	Carbon version can't preallocate windows and doesn't need to
	MyWindowPtr lastWin=nil, w;
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	int sum,limit;
	uLong count = CountSelectedMessages(tocH);

	if (count>GetRLong(WIN_GEN_WARNING_THRESH) && !MultiMessageOpOK(WIN_GEN_WARNING,count))
		return;

	// Walk through the selected entries, activating windows as needed
	for (limit=(*tocH)->count,sum=0; sum<limit; sum++)
	{
		if ((*tocH)->sums[sum].selected)
			if ((*tocH)->sums[sum].messH)
			{
				MyWindowPtr tocMessWin = (*(MessHandle)(*tocH)->sums[sum].messH)->win;
				WindowPtr	tocMessWinWP = GetMyWindowWindowPtr (tocMessWin);
				if (!IsWindowVisible (tocMessWinWP))
					ShowMyWindow(tocMessWinWP);
#ifdef	FLOAT_WIN
				UserSelectWindow(tocMessWinWP);
#else	//FLOAT_WIN
				SelectWindow_(tocMessWinWP);
#endif	//FLOAT_WIN
				lastWin = tocMessWin;
			}
	}
	
	for (sum=(*tocH)->count-1;sum>=0; sum--)
	{
		if ((*tocH)->sums[sum].selected && !(*tocH)->sums[sum].messH)
		{
			short		realSum;
			TOCHandle	realTOC;
			MiniEvents(); if (CommandPeriod) break;
			if (!(realTOC = GetRealTOC(tocH,sum,&realSum))) break;
			if (!(w=GetAMessage(realTOC,realSum,nil,nil,True))) break;
			if (tocH != realTOC)
			{
				BeenThereDoneThat(tocH,sum);	//	set status to READ in virtual mailbox
				(*Win2MessH(w))->openedFromTocH = tocH;	//	opened from virtual mailbox
				(*Win2MessH(w))->openedFromSerialNum = (*tocH)->sums[sum].serialNum;
			}		
			if (lastWin)
			{
				WindowPtr	lastWinWP = GetMyWindowWindowPtr(lastWin);
				ActivateMyWindow(lastWinWP,False);
				NotUsingWindow(lastWinWP);
			}
			lastWin = w;
			MonitorGrow(True);
		}
	}
}

/************************************************************************
 * BoxKey - handle keystrokes in a mailbox window
 ************************************************************************/
Boolean BoxKey(MyWindowPtr win, EventRecord *event)
{
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	short c = event->message & charCodeMask;
	Boolean result = true;
	
	if (c==tabKey && ((*tocH)->previewID || (*tocH)->drawer))
	{
		if (win->pte)
		{
			// Preview -> drawer or box list
			PeteFocus(win,nil,true);
			if ((*tocH)->drawer)
			{
				// Have drawer
				if ((*tocH)->drawerWin) MBDrawerSetFocus((*tocH)->drawerWin, true);
			}
			else
				// Do box list
				BoxListFocus(tocH,true);
		}
		else if ((*tocH)->listFocus)
		{
			// Box list -> preview or drawer
			BoxListFocus(tocH,false);
			if ((*tocH)->previewPTE)
			{
				// Have preview
				PeteFocus(win,(*tocH)->previewPTE,true);
				if (PreviewReadFocus) BeenThereDoneThat(tocH,-1);
			}
			else
			{
				// Do drawer
				if ((*tocH)->drawerWin) MBDrawerSetFocus((*tocH)->drawerWin, true);
			}
		}
		else
		{
			// Drawer -> box list
			if ((*tocH)->drawerWin) MBDrawerSetFocus((*tocH)->drawerWin, false);
			BoxListFocus(tocH,true);
		}
	}
	else if (win->pte) result = BoxPreviewKey(win,event);	// Key goes to preview
	else if ((*tocH)->drawer && (*tocH)->drawerWin && !(*tocH)->listFocus)
	{
		// Key goes to drawer
		(*tocH)->userActive = true;
		// Don't use tocH after this point. May no longer be valid
		return ((*tocH)->drawerWin->key)((*tocH)->drawerWin,event);
	}
	else result = BoxListKey(win,event);	// Key goes to box list
	(*tocH)->userActive = true;
	return(result);
}

/************************************************************************
 * BoxListKey - keystroke to list portion of mailbox
 ************************************************************************/
Boolean BoxListKey(MyWindowPtr win,EventRecord *event)
{
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	short c = event->message & charCodeMask;
	Boolean cmd = (event->modifiers & (cmdKey|shiftKey)) != 0;
	int mNum = -1;
	long uLetter = UnadornMessage(event)&charCodeMask;
	Boolean shift = 0!=(event->modifiers & shiftKey);

	SFWTC = True;
	
	if (leftArrowChar <= uLetter && uLetter <= downArrowChar &&
			win->hasSelection &&
			IsArrowSwitch(event->modifiers))
		c = enterChar;
		
	switch (c)
	{
		case leftArrowChar:
		case upArrowChar:
			for (mNum=0;mNum<(*tocH)->count;mNum++)
			 if ((*tocH)->sums[mNum].selected) break;
			mNum--;
			if (mNum<0) mNum = 0;
			break;
		case rightArrowChar:
		case downArrowChar:
			for (mNum=(*tocH)->count-1;mNum>=0;mNum--)
				if ((*tocH)->sums[mNum].selected) break;
			mNum++;
			if (mNum==(*tocH)->count) mNum--;
			break;
		case delChar:
		case deleteKey:
			if (event->what!=autoKey && (!PrefIsSet(PREF_NO_DELKEY) || 0!=(event->modifiers&cmdKey))) BoxMenu(win,EDIT_MENU,EDIT_CLEAR_ITEM,event->modifiers);
			else return(false);
			break;
		case ' ':
			BoxIdle(win);
			if (win->hasSelection && (*tocH)->previewID)
			{
				if (PeteScroll((*tocH)->previewPTE,0,shift ? psePageUp : psePageDn) && !shift)
				{
					mNum = LastMsgSelected(tocH);
					if (PreviewReadNext) BeenThereDoneThat(tocH,-1);
					EzOpen(tocH,mNum,(*tocH)->ezOpenSerialNum,event->modifiers,true,false);
					mNum = -1;
				}
				else if (PreviewReadFocus) BeenThereDoneThat(tocH,-1);
				break;
			}
			// fall through is deliberate if no preview
		case returnChar:
		case enterChar:
			if (win->hasSelection)
			{
				BoxOpen(win);
				break;
			}
			/* fall through is deliberate for no selection */
		default:
			if (event->modifiers & cmdKey)
			{
				return(False);
			}
			else if (*Type2SelString)
				BoxType2Select(tocH,Type2SelString);
			break;
	}
	if (mNum > -1)
	{
		SelectBoxRange(tocH,mNum,mNum,cmd,-1,-1);
		BoxCenter((*tocH)->win,mNum);
	}
	return(True);
}

/************************************************************************
 * BoxPreviewKey - keystroke to preview portion of mailbox
 ************************************************************************/
Boolean BoxPreviewKey(MyWindowPtr win,EventRecord *event)
{
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	short c = event->message & charCodeMask;
	Boolean cmd = (event->modifiers & (cmdKey|shiftKey)) != 0;
	long uLetter = UnadornMessage(event)&charCodeMask;
	short mNum;
	Boolean shift = 0 != (event->modifiers & shiftKey);

	SFWTC = True;
	
	if (cmd) return(false);
	
	if ((*tocH)->previewID && (mNum=FirstMsgSelected(tocH))!=-1 && PreviewReadNext)
		BeenThereDoneThat(tocH,-1);
	
	if (c==' ')
	{
		if (PeteScroll((*tocH)->previewPTE,0,shift ? psePageUp : psePageDn) && !shift)
		{
			mNum = FirstMsgSelected(tocH);
			if (PreviewReadNext) BeenThereDoneThat(tocH,mNum);
			EzOpen(tocH,mNum,(*tocH)->ezOpenSerialNum,event->modifiers,true,false);
		}
	}
	else if (c==returnKey || c==enterKey)
		return(BoxListKey(win,event));
	else if (DirtyKey(event->message))
		AlertStr(READ_ONLY_ALRT, Stop, nil);
	else
	{
		PeteEdit(win,win->pte,peeEvent,(void*)event);
	}
	(*tocH)->userActive = true;
	return(True);
}

/************************************************************************
 * BoxCenter - center a mailbox around a given line
 ************************************************************************/
void BoxType2Select(TOCHandle tocH,PStr string)
{
	UPtr spot;
	short difference;
	short i;
	short subFound = -1;
	short fromFound = -1;
	short thisDiff;
	short found;
	
	/* subject */
	difference = REAL_BIG;
	for (i=(*tocH)->count;i--;)
		if (spot=PPtrFindSub(Type2SelString,(*tocH)->sums[i].subj+1,*(*tocH)->sums[i].subj))
		{
			thisDiff = spot-(*tocH)->sums[i].subj;
			if (spot>(*tocH)->sums[i].subj && IsWordChar[spot[-1]]) thisDiff += 100;
			if (difference>thisDiff)
			{
				subFound = i;
				difference = thisDiff;
			}
		}

	/* sender */
	difference = REAL_BIG;
	for (i=(*tocH)->count;i--;)
		if (spot=PPtrFindSub(Type2SelString,(*tocH)->sums[i].from+1,*(*tocH)->sums[i].from))
		{
			thisDiff = spot-(*tocH)->sums[i].from;
			if (spot>(*tocH)->sums[i].from && IsWordChar[spot[-1]]) thisDiff += 100;
			if (difference>thisDiff)
			{
				fromFound = i;
				difference = thisDiff;
			}
		}

	UL(tocH);

	// if the string is found in only one column, use that column
	if (subFound < 0) found = fromFound;
	else if (fromFound < 0) found = subFound;
	// if it's found in both, use from unless the mailbox is sorted by subject
	else if ((*tocH)->lastSort==SORT_SUBJECT_ITEM) found = subFound;
	else found = fromFound;
	
	if (found>=0)
	{
		SelectBoxRange(tocH,found,found,False,-1,-1);
		BoxCenterSelection((*tocH)->win);
	}
}

/************************************************************************
 * BoxCenter - center a mailbox around a given line
 ************************************************************************/
void BoxCenter(MyWindowPtr win, short mNum)
{
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	int topVis = GetControlValue(win->vBar);
	int botVis = topVis + RectHi(win->contR)/win->vPitch-1;
	
	if (mNum<topVis || mNum>botVis)
	{
		UpdateMyWindow(GetMyWindowWindowPtr(win));	/* this is a hack.  ScrollIt should be smarter */
		ScrollIt(win,0,(topVis+botVis)/2-mNum);
	}
}

/************************************************************************
 * BoxSelectAfter - select the message on or after a given message,
 * if no messages are already selected
 ************************************************************************/
void BoxSelectAfter(MyWindowPtr win, short mNum)
{
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	
	if (mNum >=0 && BoxNextSelected(tocH,-1)<0)
	if (win->hasSelection = ((*tocH)->count>0))
	{
		mNum = MIN(mNum,(*tocH)->count-1);
		(*tocH)->sums[mNum].selected = True;
		BoxCenter((*tocH)->win,mNum);
	}
}

/************************************************************************
 * BoxCenterSelection - center a selection in a mailbox window
 ************************************************************************/
void BoxCenterSelection(MyWindowPtr win)
{
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	int top, bottom;
	
	for (top=0;top<(*tocH)->count;top++)
		if ((*tocH)->sums[top].selected) break;
	for (bottom=(*tocH)->count-1;bottom>=0;bottom--)
		if ((*tocH)->sums[bottom].selected) break;
	if (top<=bottom) BoxCenter(win,(top+bottom)/2);
}

/************************************************************************
 *
 ************************************************************************/
Boolean BoxPosition(Boolean save,MyWindowPtr win)
{
	Rect r;
	Boolean zoomed;
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	TOCHandle tocH = (TOCHandle)GetWindowPrivateData(winWP);
	Str31 name;
	FSSpec	spec = GetMailboxSpec(tocH,-1);

	PCopy(name,spec.name);
	
	if (save)
	{
		utl_SaveWindowPos(winWP,&r,&zoomed);
		SavePosFork(spec.vRefNum,spec.parID,name,&r,zoomed);
	}
	else
	{
		if (!RestorePosFork(spec.vRefNum,spec.parID,name,&r,&zoomed))
			{UL(tocH); return(False);}
		utl_RestoreWindowPos(winWP,&r,zoomed,1,TitleBarHeight(winWP),LeftRimWidth(winWP),(void*)FigureZoom,(void*)DefPosition);
	}
	return(True);
}

/************************************************************************
 * SaveMessageAs - save a message in text only form
 ************************************************************************/
void DoSaveSelectedAs(TOCHandle tocH)
{
	long creator;
	short refN;
	short err;
	Str31 scratch;
	int sumNum;
	MessHandle messH;
	short lastSelected = -1;
	Str255 title;
	FSSpec spec;
	MyWindowPtr win;
	short count = CountSelectedMessages(tocH);
	ModalFilterYDUPP filter=nil;
	
	/*
	 * tickle stdfile
	 */
	GetPref(scratch,PREF_CREATOR);
	if (*scratch!=4) GetRString(scratch,TEXT_CREATOR);
	BMD(scratch+1,&creator,4);
	sumNum = FirstMsgSelected(tocH);
	MakeMessFileName(tocH,sumNum,spec.name);
	Stationery = (HasFeature(featureStationery)&&(*tocH)->which==OUT&&LastMsgSelected(tocH)==sumNum) ? False : 2;	/* 2 is our signal to the filter to hide the item */
	if (err=SFPutOpen(&spec,creator,'TEXT',&refN,nil,filter,SAVEAS_DLOG,nil,nil,nil))
		return;
	
	OpenProgress();
	ProgressR(NoBar,count,MOVING_MESSAGES,LEFT_TO_TRANSFER,nil);
	
	for (;!err && sumNum<(*tocH)->count;sumNum++)
		if ((*tocH)->sums[sumNum].selected)
		{
			// if this is an IMAP message, make sure it's been downloaded before we try to save it
			if ((*tocH)->imapTOC) EnsureMsgDownloaded(tocH, sumNum, false);

			MakeMessTitle(title,tocH,sumNum,True);
			Progress(NoBar,count--,nil,nil,title);
			messH = (*tocH)->sums[sumNum].messH;
			if (win=GetAMessage(tocH,sumNum,nil,nil,False))
			{
				WindowPtr	winWP = GetMyWindowWindowPtr(win);
				err = SaveAsToOpenFile(refN,Win2MessH(win));
				if (!IsWindowVisible (winWP)) CloseMyWindow(winWP);
			}
		}

	CloseProgress();

	/*
	 * close
	 */
	(void) MyFSClose(refN);
	
	/*
	 * report error
	 */
	if (err)
	{
		FileSystemError(COULDNT_SAVEAS,spec.name,err);
		FSpDelete(&spec);
	}
}

/************************************************************************
 * MakeMessFileName - make a default name for save as
 ************************************************************************/
void MakeMessFileName(TOCHandle tocH,short sumNum, UPtr name)
{
	UPtr spot;
	short len = MIN(MAX_BOX_NAME,*(*tocH)->sums[sumNum].subj);
	
	BMD((*tocH)->sums[sumNum].subj,name,len+1);
	*name = len;
	for (spot=name+1;spot<=name+len;spot++)
		if (*spot==':') *spot = '-';
}

/************************************************************************
 * ShowBoxSizes - title a mailbox window
 ************************************************************************/
void ShowBoxSizes(MyWindowPtr win)
{
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	Str31 string;
	long usedBytes,totalBytes;
	short depth=1;
	ControlHandle cntl = FindControlByRefCon(win,kBoxSizeRefCon);
	
	PushGWorld();
	SetPort_(GetMyWindowCGrafPtr(win));
	if (cntl && IsControlVisible(cntl))
	{
		usedBytes = (*tocH)->usedK K;
		totalBytes = (*tocH)->totalK K;
		if ((*tocH)->virtualTOC)
			if (PrefIsSet(PREF_SHOW_NUM_SELECTED))
				ComposeRString(string,BOX_SIZE_SELECT_SRCH_FMT,BoxCountSelected(tocH),(*tocH)->count);
			else
				NumToString((*tocH)->count,string);
		else
			if (PrefIsSet(PREF_SHOW_NUM_SELECTED))
				ComposeRString(string,BOX_SIZE_SELECT_FMT,BoxCountSelected(tocH),(*tocH)->count,usedBytes,
									 totalBytes-usedBytes);
			else
				ComposeRString(string,BOX_SIZE_FMT,(*tocH)->count,usedBytes,
									 totalBytes-usedBytes);
		
		MySetControlTitle(cntl,string);
	}
	PopGWorld();
	(*tocH)->updateBoxSizes = false;
}

/************************************************************************
 * BoxDidResize - handle the size display area
 ************************************************************************/
void BoxDidResize(MyWindowPtr win, Rect *oldContR)
{
	ControlHandle hBar = win->hBar;
	ControlHandle vBar = win->vBar;
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	ControlHandle cntl, placard;
	short previewHeight;
	Boolean again = false;
	Rect r;
	short goalTopMargin = kHeaderButtonHeight + (ETLHasMBoxContextFolder(win) ? kTabHeight+INSET+kTabGap : 0);
	CGrafPtr	winPort = GetMyWindowCGrafPtr (win);
	Boolean	fileView = (*tocH)->hasFileView && (*tocH)->fileView;

	if (cntl=FindControlByRefCon(win,'plug'+1))
		goalTopMargin += INSET + ControlHi(cntl);

	RedoTOC(tocH);
	
	if (win->topMargin!=goalTopMargin && !(*tocH)->virtualTOC)
	{
		SetTopMargin(win,goalTopMargin);
		InvalContent(win);
		again = true;
	}
	
	// preview height?
	previewHeight = BoxPreviewHeight(tocH);
	if (win->botMargin != previewHeight)
	{
		SetBotMargin(win,previewHeight);
		InvalContent(win);
		again = true;
		BoxMakePreview(win);
	}
	
	if (again)
	{
		MyWindowDidResize(win,oldContR);	/* reposition window elements */
		return;
	}
	
	win->dontGreyOnMe = win->contR;	// not much grey for us...
	
	if ((*tocH)->previewPTE)
	{
		Rect		rPort;
		
		r = win->contR;
		r.top = r.bottom + GROW_SIZE+1;
		r.right += GROW_SIZE;
		rPort = *GetPortBounds(winPort,&rPort);
		r.bottom = rPort.bottom;
		PeteDidResize((*tocH)->previewPTE,&r);
	}
	
	if (cntl=FindControlByRefCon(win,PREVIEW_TOGGLE_CNTL))
		MySetCtlValue(cntl,win->botMargin!=0);
		
	if (hBar)
	{
		Rect r = *GetControlBounds(hBar,&r);
		short delta = GROW_SIZE + BoxSizeWidth(tocH);
		short conConPrevProfWidth = (HasFeature(featureConCon) && (*tocH)->previewPTE) ? GetRLong(CONCON_PREV_PROF_WIDTH) : 0;
		
		r.left += delta + conConPrevProfWidth;
		MySetCntlRect(hBar,&r);
		if (cntl=FindControlByRefCon(win,kBoxSizeRefCon))
		{
			BoxSizeBox(win,&r,tocH);
			InsetRect(&r,-1,-1);
			OffsetRect(&r,GROW_SIZE,0);
			MySetCntlRect(cntl,&r);
			if (fileView) HideControl(cntl); else ShowControl(cntl);
			if (cntl=FindControlByRefCon(win,PREVIEW_TOGGLE_CNTL))
			{
				r.right = r.left;
				r.left = -1;
				if (!GetSuperControl(cntl,&placard))
					MySetCntlRect(placard,&r);
				InsetRect(&r,MAX_APPEAR_RIM-1,MAX_APPEAR_RIM-1);
				//OffsetRect(&r,-1,-1);
				MySetCntlRect(cntl,&r);
			}
		}

		// Size the con con preview profile control
		if (cntl=FindControlByRefCon(win,kConConProfRefCon))
			if (!fileView && conConPrevProfWidth)
			{
				BoxSizeBox(win,&r,tocH);
				OffsetRect(&r,GROW_SIZE,0);
				InsetRect(&r,-1,-1);
				r.left = r.right;
				r.right = r.left + conConPrevProfWidth-1;
				MySetCntlRect(cntl,&r);
				ShowMyControl(cntl);
			}
			else HideControl(cntl);

		if (cntl=FindControlByRefCon(win,PREVIEW_DIVIDE_CNTL))
		{
			r.top = win->contR.bottom;
			r.bottom = r.top + GROW_SIZE + 1;
			r.left = win->contR.right;
			r.right = r.left + GROW_SIZE + 1;
			if (!GetSuperControl(cntl,&placard))
				MySetCntlRect(placard,&r);
			InsetRect(&r,4,4);
			MySetCntlRect(cntl,&r);
		}
		SetRect(&r,win->contR.left,oldContR?oldContR->bottom:win->contR.bottom,win->contR.left+delta,
							 (oldContR?oldContR->bottom:win->contR.bottom)+GROW_SIZE);
		InvalWindowRect(GetMyWindowWindowPtr(win),&r);
	}
	
	BoxPlaceBevelButtons(win);

	if (cntl=FindControlByRefCon(win,'tabs'))
	{
		Rect	rWin;
		FileViewHandle	fvh = GetFileViewInfo(win);
		
		GetPortBounds(GetMyWindowCGrafPtr(win),&rWin);
		SetRect(&r,-1,win->topMargin-kTabHeight-kHeaderButtonHeight+kTabGap,rWin.right+1,win->topMargin-kHeaderButtonHeight);
		SetControlBounds(cntl,&r);

		if (fvh)
		{
			short	i;
			GetPortBounds(winPort,&r);
			r.top = win->topMargin;
			if (!(*tocH)->fileView)
				OffsetRect(&r,-4000,-4000);	// hide the list
			LVSize((*fvh)->list,&r,nil);
			FVSizeHeaderButtons(win,fvh);
			for(i=0;i<kFDColCount;i++)
				SetControlVisibility((*fvh)->controls[i],fileView,false);		
		}
	}

	if ((*tocH)->drawerWin)
	{
		// resize drawer
		MyWindowDidResize((*tocH)->drawerWin,NULL);
	}
}

/************************************************************************
 * BoxPreviewHeight - find the preview height for a mailbox
 ************************************************************************/
short BoxPreviewHeight(TOCHandle tocH)
{
	MyWindowPtr win = (*tocH)->win;
	CGrafPtr	winPort = GetMyWindowCGrafPtr (win);
	Rect	rPort = *GetPortBounds(winPort,&rPort);
	short totalHi = RectHi(rPort) - GROW_SIZE - win->topMargin;
	short hi = totalHi;
	short min = GetRLong(PREVIEW_USELESS)*FontLead;
	short max = totalHi - GetRLong(PREVIEW_USELESS)*win->vPitch;
	
	hi = BoxPreviewHeightWish(tocH);
		
	// fine-tune
	//hi -= (totalHi-hi)%win->vPitch;	// supposed to make sure we have integral height for summaries
																		// but causes window size to grow on close/open cycle.
	if (hi/FontLead < GetRLong(PREVIEW_USELESS)) hi = 0;	// too small to be useful?
	if (max<min) hi = 0;	// remainder too small to be useful?
	else hi = MIN(max,hi);
	
	return(hi);
}

/************************************************************************
 * BoxPreviewHeightWish - find the height we'd like the preview to be
 ************************************************************************/
short BoxPreviewHeightWish(TOCHandle tocH)
{
	short hi;
	
	if ((*tocH)->previewHi < 0) return(0);	// negative preview means user collapsed it
	else if ((*tocH)->previewHi > 1) hi = (*tocH)->previewHi;  // positive value > 1 means use it
	else if ((*tocH)->previewHi == 1) hi = FontLead*GetRLong(PREVIEW_HI);  // 1 means default size
	else if (PrefIsSet(PREF_NO_PREVIEW)) return(0); // no percent, default off
	else hi = FontLead*GetRLong(PREVIEW_HI);	// no percent, default on--use standard percent
	
	return(hi);
}

/************************************************************************
 * BoxMakePreview - make the preview pane for a mailbox
 ************************************************************************/
void BoxMakePreview(MyWindowPtr win)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	ControlHandle cntl, placard;
	Rect r;
	PETEHandle pte;
	TOCHandle tocH = (TOCHandle)GetWindowPrivateData(winWP);
	
	if (win->botMargin && !(*tocH)->previewPTE)
	{
		PETEDocInitInfo pdi;
		
		// first make the edit region
		DefaultPII(win,false,peVScroll|peGrowBox,&pdi);
		pdi.docWidth = RectWi(win->contR)-PETE_SCROLLY_DIFFERENCE;
		pdi.inParaInfo.paraFlags = 0;
		PeteCreate(win,&pte,0,&pdi);
		(*PeteExtra(pte))->emoDesired = true;
		(*tocH)->previewPTE = pte;
		CleanPII(&pdi);
		Zero(r);
		if (cntl=GetNewControl(PREVIEW_DIVIDE_CNTL,winWP))
		{
			if (placard = GetNewControl(PLACARD_CNTL,winWP))
				EmbedControl(cntl,placard);
 		}
	}
	else if (!win->botMargin && (*tocH)->previewPTE)
	{
		if (cntl=FindControlByRefCon(win,PREVIEW_DIVIDE_CNTL))
		{
			if (!GetSuperControl(cntl,&placard))
				DisposeControl(placard);	//	Disposes embedded cntl also
			else
				DisposeControl(cntl);
		}
		BoxListFocus(tocH,true);
		PeteDispose(win,(*tocH)->previewPTE);
		(*tocH)->previewPTE = nil;
	}
}

/************************************************************************
 * BoxSetPreview - set the preview state for a mailbox
 ************************************************************************/
void BoxSetPreview(MyWindowPtr win,Boolean openIt)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	TOCHandle tocH = (TOCHandle)GetWindowPrivateData(winWP);
	short hi = (*tocH)->previewHi;
	Rect r = *GetPortBounds(GetWindowPort(winWP),&r);
	short totalHi = RectHi(r);
	
	if (!openIt)
	{
		hi = (!(MainEvent.modifiers&optionKey)&&hi) ? -ABS(hi) : -1;
		(*tocH)->previewHi = hi;
		hi = win->contR.bottom+GROW_SIZE;
	}
	else
	{
		if (!(MainEvent.modifiers&optionKey)&&hi) hi = ABS(hi);
		else hi = 1;
		(*tocH)->previewHi = hi;
		if (hi==1) hi = FontLead*GetRLong(PREVIEW_HI);
		hi += totalHi;
		{
			ControlHandle cntl;
			if (cntl=FindControlByRefCon(win,kConConProfRefCon))
				ShowMyControl(cntl);
		}
	}
	r.bottom = r.top + hi;
	SanitizeSize(win,&r);
	SizeWindow(winWP,RectWi(r),RectHi(r),false);
	MyWindowDidResize(win,nil);
	BoxRefreshPreview(tocH);
	TOCSetDirty(tocH,true);
}

/************************************************************************
 * BoxRefreshPreview - make the preview go again
 ************************************************************************/
void BoxRefreshPreview(TOCHandle tocH)
{
	(*tocH)->previewID = 0;
	(*tocH)->lastSameTicks = 0;
	(*tocH)->conConMultiScan = true;
}

/************************************************************************
 * BoxRequestFocus - somebody wants to change the focus
 ************************************************************************/
Boolean BoxRequestFocus(MyWindowPtr win, PETEHandle pte)
{
	TOCHandle tocH = Win2TOC(win);
	
	ASSERT(tocH);
	if (!tocH) return false;
	
	if (!(*tocH)->previewID) return false;
	
	BoxListFocus(tocH,false);	// not on the list of summaries
	PeteFocus(win,pte,true);	// on the petehandle!
	return true;
}

/************************************************************************
 * BoxCurAddr - get the address of the first selected message
 ************************************************************************/
PStr BoxCurAddr(MyWindowPtr win, PStr addr)
{
	TOCHandle tocH = Win2TOC(win);
	short sumNum = FirstMsgSelected(tocH);
	
	if (sumNum < 0) return nil;
	
	{
		MessHandle messH = (*tocH)->sums[sumNum].messH;
		Boolean opened = false;
		
		if (messH==nil)
		{
			TOCHandle realTocH;
			short realSumNum;
			
			if (realTocH = GetRealTOC(tocH,sumNum,&realSumNum))
			{
				EnsureMsgDownloaded(realTocH,realSumNum,false);	// FSOIMAP
				GetAMessage(realTocH,realSumNum,nil,nil,false);
				messH = (*realTocH)->sums[realSumNum].messH;
				opened = messH!=nil;
				if (!opened) return nil;
			}
		}
		
		addr = ((*messH)->win)->curAddr((*messH)->win,addr);
		
		if (opened) CloseMyWindow((*messH)->win);
		
		return addr;
	}
}

/************************************************************************
 * BoxIdle - idle routine for mailboxes
 ************************************************************************/
void BoxIdle(MyWindowPtr win)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	TOCHandle tocH;
	short mNum, startNum;
	static long waitTicks;
	static long previewTicks = 5;
	static long previewMultiTicks = 30;
		
	ASSERT(winWP);
	if (!winWP) return;
	
	tocH = (TOCHandle)GetWindowPrivateData(winWP);
	ASSERT(tocH);
	if (!tocH) return;

	if (!(*tocH)->previewPTE)
		(*tocH)->previewID = 0;
	else if (IsWindowVisible (winWP) && TickCount()-(*tocH)->mouseTicks>previewTicks)
	{
		previewTicks = GetRLong(PREVIEW_SINGLE_DELAY);
		previewMultiTicks = GetRLong(PREVIEW_MULTI_DELAY);
		startNum = -1;
		for (mNum=(*tocH)->count-1;mNum>=0;mNum--)
		{
			if ((*tocH)->sums[mNum].selected)
				if (startNum!=-1) {startNum = -2 ; break;}	// multiple selection same as no selection?
				else startNum = mNum;
		}
		if (startNum!=-2 || TickCount()-(*tocH)->mouseTicks>previewMultiTicks)
		{
			(*tocH)->mouseTicks = TickCount();
			Preview(tocH,startNum);
		}
	}
	
	// Leave imap mailboxes alone that are in the middle of a resync operation.
	if (!((*tocH)->imapTOC && FindNodeByToc(tocH))) 
	{
		if ((*tocH)->updateBoxSizes)
			ShowBoxSizes(win);
	
		// Moodwatch one message while we're here
		if (IsWindowVisible(winWP) && DiskSpunUp() && AnalDoExisting() && !(*tocH)->analScanned
				&& GlobalIdleTicks > 120)
		{
			Boolean didOne = false;
			// visible on down
			for (mNum=GetControlValue((*tocH)->win->vBar);mNum<(*tocH)->count;mNum++)
				if (!(*tocH)->sums[mNum].score)
				{
					didOne = true;
					OneAnalMess(tocH,mNum);
					break;
				}
			// invisible on up
			if (!didOne)
			{
				for (mNum=GetControlValue((*tocH)->win->vBar)-1;mNum>=0;mNum--)
					if (!(*tocH)->sums[mNum].score)
					{
						didOne = true;
						OneAnalMess(tocH,mNum);
						break;
					}
			}
			(*tocH)->analScanned = !didOne;
		}
	}		

	if ((*tocH)->imapTOC)
		UpdateIMAPMailbox(tocH);	// see if we need to update IMAP mailbox
	UpdatePOPMailboxes();	// attend to any pending POP message deletions
	
	{
		ControlHandle cntl;
		if ((cntl=FindControlByRefCon(win,kConConProfRefCon)) && IsControlVisible(cntl))
			HiliteControl(cntl, (*tocH)->previewID ? 0 : 255);
	}
}

/************************************************************************
 * BoxDrawerUseless - how much space does the drawer add to each side of the window?
 ************************************************************************/
Boolean BoxDrawerUseless(MyWindowPtr win, short *left, short *right)
{
	TOCHandle tocH = Win2TOC(win);
	
	if ((*tocH)->drawerWin && (*tocH)->drawer)
	{
		WindowPtr winWP = GetMyWindowWindowPtr(win);
		WindowPtr drawWP = GetMyWindowWindowPtr((*tocH)->drawerWin);
		if (winWP && drawWP)
		{
			Rect rDrawStruc;
			Rect rWinStruc;
			short myLeft, myRight;
			
			GetWindowBounds(winWP,kWindowStructureRgn,&rWinStruc);
			GetWindowBounds(drawWP,kWindowStructureRgn,&rDrawStruc);
			
			myLeft = rDrawStruc.right < rWinStruc.right ? RectWi(rDrawStruc) : 0;
			myRight = rDrawStruc.left > rWinStruc.left ? RectWi(rDrawStruc) : 0;
			if (left) *left = myLeft;
			if (right) *right = myRight;
			return true;
		}
	}
	
	// no drawer or couldn't get window pointers
	if (left) *left = 0;
	if (right) *right = 0;
	return false;
}

/************************************************************************
 * BoxCountSelected - how many messages are selected
 ************************************************************************/
short BoxCountSelected(TOCHandle tocH)
{
	short count = 0;
	short mNum;
	
	for (mNum=(*tocH)->count;mNum--;) if ((*tocH)->sums[mNum].selected) count++;
	
	return count;
}

/************************************************************************
 * BoxGrowSize - adjust grow size
 ************************************************************************/
void BoxGrowSize(MyWindowPtr win,Point *newSize)
{
	short	nonMsgSumHt = win->topMargin + GROW_SIZE;
	short	msgSumHt = ((newSize->v-nonMsgSumHt+win->vPitch/2)/win->vPitch)*win->vPitch;
	
	//	Calculate new window height
	newSize->v = msgSumHt + nonMsgSumHt;
}

/************************************************************************
 * BoxSizeBox - return rect for window's size display
 ************************************************************************/
void BoxSizeBox(MyWindowPtr win,Rect *r,TOCHandle tocH)
{
	*r = win->contR;
	r->top = r->bottom;
	r->bottom = r->top+GROW_SIZE;
	r->right = r->left + BoxSizeWidth(tocH);
	InsetRect(r,1,1);
}

/************************************************************************
 * BoxSizeWidth - return width of window's size display
 ************************************************************************/
short BoxSizeWidth(TOCHandle tocH)
{
	if (tocH && (*tocH)->virtualTOC)
	{
		static short	vBoxWidth;
		
		if (!vBoxWidth)
		{
			vBoxWidth = GetRLong(SEARCH_HITS_SIZE)*CharWidthInFont('0',SmallSysFontID(),SmallSysFontSize());
			if (PrefIsSet(PREF_SHOW_NUM_SELECTED)) vBoxWidth += GetRLong(BOX_SIZE_SELECT_EXTRA);
		}
		return vBoxWidth;
	}
	return PrefIsSet(PREF_SHOW_NUM_SELECTED) ? GetRLong(BOX_SIZE_SIZE) + GetRLong(BOX_SIZE_SELECT_EXTRA) : GetRLong(BOX_SIZE_SIZE);
}

/************************************************************************
 * BoxGonnaShow - get ready to show a mailbox window
 ************************************************************************/
OSErr BoxGonnaShow(MyWindowPtr win)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	TOCHandle tocH = (TOCHandle)GetWindowPrivateData(winWP);
	ControlHandle cntl, placard;
	Str255 title;
	
	PushGWorld();
	SetPort_(GetWindowPort(winWP));
	
	(*tocH)->needRedo = 0;
	win->update = BoxUpdate;
	win->scroll = BoxScroll;
	win->drag = BoxDragHandler;
	win->button = BoxButton;
	win->idle = BoxIdle;
	win->selection = BoxHasSelection;
	win->zoomSize = BoxZoomSize;
	win->drawerUseless = BoxDrawerUseless;
	win->requestPeteFocus = BoxRequestFocus;
	win->curAddr = BoxCurAddr;
	win->dontControl = true;
	(*tocH)->listFocus = true;
	(*tocH)->conConMultiScan = true;
	MyWindowDidResize(win,nil);
	
	AnyTOCDirty++;
	
	// Handle AMO (goddam it)
	GetWTitle(winWP,title);
	if (GetPrefLong(PREF_AMO_AVOIDANCE)==kAMOAvoidInvisible && title[2]==0 && *title>2)
	{
		--*title;
		BMD(title+3,title+2,*title-1);
		SetWTitle_(winWP,title);
	}
	
	if ((*tocH)->resort) MBResort(tocH);
	BoxInitialSelection(tocH);
			
	ScrollIt(win,0,SortedDescending(tocH)?REAL_BIG:-REAL_BIG);
	if (!FindControlByRefCon(win,PREVIEW_TOGGLE_CNTL))
	{
		if (cntl = GetNewControl(PREVIEW_TOGGLE_CNTL,winWP))
			if (placard = GetNewControl(PLACARD_CNTL,winWP))
				EmbedControl(cntl,placard);
	}

	if (ETLHasMBoxContextFolder(win))
	{
		//	Set up file view
		ControlRef	ctlTabs;
		Rect		rTab;
		GrafPtr	oldPort;
		FileViewHandle	hData;

		GetPort(&oldPort);
		SetPort_(GetMyWindowCGrafPtr(win));
		
		rTab = win->contR;	//	doesn't really matter what size, will get resized later anyway
		ctlTabs = FindControlByRefCon(win,'tabs');	// Do we already have a tab control?
		if (!ctlTabs)
			// Don't already have tab control. Create one.
			ctlTabs = NewControl(winWP,&rTab,"\p",true,FILE_VIEW_TABS,0,0,kControlTabSmallProc,0);
		if (ctlTabs)
		{
			LetsGetSmall(ctlTabs);
			EmbedControl(ctlTabs,win->topMarginCntl);
			SetControlReference(ctlTabs,'tabs');
			if ((*tocH)->fileView) SetControlValue(ctlTabs,2);
			if (hData = NuHandleClear(sizeof(FileViewInfo)))
			{	
				short oldResFile = CurResFile(),refN = 0;
				short		vRefNum;
				long		dirID;
				
				if (ETLMBoxContextFolder(win, &vRefNum, &dirID) == EMSR_OK)
				{
					(*hData)->vRefNum = vRefNum;
					(*hData)->dirId = dirID;
				}
				else
				{
					(*hData)->vRefNum = -1;
					(*hData)->dirId = 2;
				}
				(*tocH)->hFileView = (Handle)hData;
				(*tocH)->hasFileView = true;
				
				//	Get saved preferences
				if ((*tocH)->refN) UseResFile((*tocH)->refN);
				else
				{
					FSSpec	resSpec = GetMailboxSpec(tocH,0);
					refN = FSpOpenResFile(&resSpec,fsRdPerm);
					if (refN == -1) refN = 0;
				}
				if ((*tocH)->refN || refN)
				{
					FVPrefsHandle	hFVPrefs;
					
					if ((*hData)->expandList.hExpandList = (ExpandListHandle)Get1Resource(kExandListResType,kExandListResID))
						DetachResource((Handle)(*hData)->expandList.hExpandList);
					if (hFVPrefs = (FVPrefsHandle)Get1Resource(kFVPrefsResType,kFVPrefsResID))
					{
						(*hData)->prefs = **hFVPrefs;
						ReleaseResource((Handle)hFVPrefs);
					}
					if (refN) CloseResFile(refN);
				}
				UseResFile(oldResFile);				
				
				NewFileView(win,hData);
				(*hData)->savePreviewHi = (*tocH)->previewHi;
				HitTab(win,ctlTabs,tocH);	// set up initial window configuration
			}
		}

		SetPort_(oldPort);
	}
	else (*tocH)->hasFileView = false;

	BoxAddBevelButtons(win);

	PopGWorld();
	return(noErr);
}

/************************************************************************
 * BoxInitialSelection - make an initial selection in a mailbox
 ************************************************************************/
void BoxInitialSelection(TOCHandle tocH)
{
	short fumlub;
	
	switch (GetPrefLong(PREF_MANUAL_BOX_SELECTION))
	{
		case 2:
			if (SortedDescending(tocH)) SelectBoxRange(tocH,0,0,False,0,0);
			else SelectBoxRange(tocH,(*tocH)->count-1,(*tocH)->count-1,False,0,0);
			break;
		case 1:
			fumlub = FumLub(tocH);
			SelectBoxRange(tocH,fumlub,fumlub,False,0,0);
			break;
		default:
			break;	// leave well enough alone...
	}
}


/************************************************************************
 * BoxZoomSize - How big do we grow a mailbox?
 ************************************************************************/
void BoxZoomSize(MyWindowPtr win,Rect *zoom)
{
	Rect listZoom = *zoom;
	short zoomHi = RectHi(*zoom);
	short hi;
	
	// compute list box size, assuming no bottom margin
	hi = win->botMargin;
	win->botMargin = 0;
	MaxSizeZoom(win,&listZoom);
	win->botMargin = hi;

	hi = RectHi(listZoom) + BoxPreviewHeightWish(Win2TOC(win));
	hi = MIN(hi,zoomHi); hi = MAX(hi,win->minSize.v);
	zoom->right = zoom->left+RectWi(listZoom);
	zoom->bottom = zoom->top+hi;
}

/************************************************************************
 * BoxHasSelection - is there a selection?
 ************************************************************************/
Boolean BoxHasSelection(MyWindowPtr win)
{
	return (-1!=LastMsgSelected((TOCHandle)GetMyWindowPrivateData(win)));
}

/************************************************************************
 * BoxAddBevelButtons - add the bevel buttons
 ************************************************************************/
void BoxAddBevelButtons(MyWindowPtr win)
{
	ControlHandle cntl;
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	TOCHandle tocH = (TOCHandle)GetWindowPrivateData(winWP);
	short i;
	ControlButtonGraphicAlignment align;
	ControlButtonTextPlacement placement;
	Boolean iconMania = !PrefIsSet(PREF_NO_SUMMARY_ICONS);
	
	for (i=1;i<BoxLinesLimit;i++)
		if (!FindControlByRefCon(win,'wide'+i))
		{
			if (cntl = GetNewControlSmall(BOX_HEAD_CNTL,winWP))
			{
				SetControlReference(cntl,'wide'+i);
				// if sticky sorting on this column, turn on the column header
				if (MBGetSort(tocH,i)) SetControlValue(cntl,1);
				// if K, right-justify the header
				if (i==blSize)
				{
					align = kControlBevelButtonAlignRight;
					placement = kControlBevelButtonPlaceToLeftOfGraphic;
					SetBevelTextAlign(cntl,teFlushRight);
				}
				else
				{
					SetBevelTextAlign(cntl,teFlushDefault);
					align = kControlBevelButtonAlignSysDirection;
					placement = kControlBevelButtonPlaceSysDirection;
				}
				//SetBevelTextOffset(cntl,2);
 				if (iconMania && i!=blSubject)
 				{
					SetBevelIcon(cntl,i==blMailbox ? MAILBOX_ONLY_ICON : COLUMN_ICON_BASE+i-1,nil,nil,nil);
 					SetControlData(cntl,0,kControlBevelButtonTextPlaceTag,sizeof(placement),(void*)&placement);
 					SetControlData(cntl,0,kControlBevelButtonGraphicAlignTag,sizeof(align),(void*)&align);
				}
				EmbedControl(cntl,win->topMarginCntl);
			}
		}
	
	if (MBDrawerSupported(win))
	{
		// Put in drawer switch
		if (!FindControlByRefCon(win,kDrawerSwitch))
		if (cntl = GetNewControlSmall(BOX_HEAD_CNTL,winWP))
		{
			SetControlReference(cntl,kDrawerSwitch);
			SetBevelIcon(cntl,DRAWER_ICON,nil,nil,nil);
			EmbedControl(cntl,win->topMarginCntl);
		}
	}
		
	// and the size box
	if (!(cntl=FindControlByRefCon(win,kBoxSizeRefCon)))
		if (cntl=GetNewControlSmall(BOX_SIZE_CNTL,winWP))
		{
			SetControlReference(cntl,kBoxSizeRefCon);
		}
		
	// and the content concentrator preview control
	if (HasFeature(featureConCon) && !(cntl=FindControlByRefCon(win,kConConProfRefCon)))
		if (cntl=GetNewControlSmall(CONCON_PROF_PREVIEW_POP,winWP))
		{
			SetControlReference(cntl,kConConProfRefCon);
		}
	
	// buttons for workgroup
	if (ETLBoxTagWidth(win))
		ETLAddBoxButtons(tocH);
}

/************************************************************************
 * BoxPlaceBevelButtons - place the bevel buttons
 ************************************************************************/
void BoxPlaceBevelButtons(MyWindowPtr win)
{
	ControlHandle cntl,cntlDrawer;
	Rect r,rDrawerCntl;
	short i;
	short h0 = -GetControlValue(win->hBar)*win->hPitch;
	Str255 s;
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	Boolean	fileView = (*tocH)->hasFileView && (*tocH)->fileView;
	WindowPtr winWP = GetMyWindowWindowPtr(win);
	Boolean	winVis = IsWindowVisible(winWP);
	
	r = *GetControlBounds(win->topMarginCntl,&r);
	r.left = 0;
	r.top = r.bottom - (kHeaderButtonHeight);

   // First check drawer switch
	if (cntlDrawer = FindControlByRefCon(win,kDrawerSwitch))
	{  
	   rDrawerCntl = r;
		rDrawerCntl.right = WindowWi(winWP);
		rDrawerCntl.left = rDrawerCntl.right - 20;
	}
		
	for (i=1;i<BoxLinesLimit;i++)
		if (cntl = FindControlByRefCon(win,'wide'+i))
		{
			if (BoxLimits(i,&r.left,&r.right,tocH) && !fileView)
			{
				OffsetRect(&r,h0,0);
				if (cntlDrawer && r.right >= rDrawerCntl.left)
				   // Don't overlap drawer control
				   r.right = rDrawerCntl.left;
				MySetCntlRect(cntl,&r);
				if (RectWi(r)<32)
				{
					MySetControlTitle(cntl,"");
					SetBevelGraphicAlign(cntl,kControlBevelButtonAlignCenter);
				}
				else
				{
					GetRString(s,ColumnHeadStrn+i);
					// display group subjects by adding a bullet to the subject label
					if (i==blSubject && MBIsSticky(tocH) && ((*tocH)->laurence)!=0)
						PCatC(s,bulletChar);
					MySetControlTitle(cntl,s);
					SetBevelGraphicAlign(cntl,kControlBevelButtonAlignSysDirection);
					SetBevelGraphicOffset(cntl,2);
					SetBevelTextOffset(cntl,2);
				}
				SetControlVisibility(cntl,true,winVis);
			}
			else
				SetControlVisibility(cntl,false,winVis);
		}

	if (cntlDrawer)
		MySetCntlRect(cntlDrawer,&rDrawerCntl);

	// Layout the buttons
	if (ETLBoxTagWidth(win))
	{
		h0 = INSET;
		for (i=1;cntl = FindControlByRefCon(win,'plug'+i);i++)
		{
			MoveMyCntl(cntl,h0,INSET,0,0);
			h0 += ControlWi(cntl) + INSET;
		}
	}	
}

/************************************************************************
 * BoxSetBevelButtonValues - set bevel button values by sort values
 ************************************************************************/
void BoxSetBevelButtonValues(MyWindowPtr win)
{
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	short i;
	ControlHandle cntl;
	
	for (i=1;i<BoxLinesLimit;i++)
		if (cntl=FindControlByRefCon(win,'wide'+i))
			SetControlValue(cntl,MBGetSort(tocH,i) ? 1 : 0);
}

/************************************************************************
 * BoxButton - handle a hit in the box buttons
 ************************************************************************/
void BoxButton(MyWindowPtr win,ControlHandle buttonHandle,long modifiers,short part)
{
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	uLong contrlRfCon = GetControlReference(buttonHandle);
	uLong i = contrlRfCon - 'wide';
	uLong iPrime = contrlRfCon - 'plug';
	
	// audit clicks on only these controls ...
	if ((i<BoxLinesLimit) || (contrlRfCon==kBoxSizeRefCon) || (contrlRfCon==PREVIEW_TOGGLE_CNTL))
		AuditHit((modifiers&shiftKey)!=0, (modifiers&controlKey)!=0, (modifiers&optionKey)!=0, (modifiers&cmdKey)!=0, false, GetWindowKind(GetMyWindowWindowPtr(win)), contrlRfCon, mouseDown);
	
	BoxListFocus(tocH,true);
	
	if (contrlRfCon==kBoxSizeRefCon)
		SizeBoxClick(win,modifiers);
	else if (contrlRfCon==kConConProfRefCon)
		;
	else if (contrlRfCon==PREVIEW_TOGGLE_CNTL)
		BoxSetPreview(win,!GetControlValue(buttonHandle));
	else if (contrlRfCon==kDrawerSwitch)
		MBDrawerToggle(win);
	else if (contrlRfCon=='tabs')
		HitTab(win,buttonHandle,tocH);
	else if (i>0 && i<BoxLinesLimit)
		MBSortHit(tocH,i,0!=(modifiers&optionKey),0!=(modifiers&shiftKey));
	else if (iPrime > 0 && iPrime < 10 /* arbitrary hack alert */)
		ETLButtonHit(win,iPrime);
	else if ((*tocH)->hasFileView && (*tocH)->fileView)
		FileViewButton(win,buttonHandle,modifiers,part);
}

/**********************************************************************
 * RedoAllTOCs - fix all toc's to use new date format
 **********************************************************************/
void RedoAllTOCs(void)
{
	WindowPtr theWindow;
	TOCHandle tocH;
	
	for (theWindow = GetWindowList(); theWindow; theWindow = GetNextWindow (theWindow))
		if (IsKnownWindowMyWindow(theWindow) && (GetWindowKind(theWindow)==CBOX_WIN || GetWindowKind(theWindow)==MBOX_WIN))
		{
			tocH = (TOCHandle)GetWindowPrivateData(theWindow);
			(*tocH)->needRedo = 0;
			InvalTocBox(tocH,-1,blDate);
		}
}

/**********************************************************************
 * BoxScroll - do (part of) the scrolling for a mailbox.  Scrolls the header part h-wise
 **********************************************************************/
Boolean BoxScroll(MyWindowPtr win,short h,short v)
{
	if (h)
	{
		BoxPlaceBevelButtons(win);
	}
	return(True);
}

/************************************************************************
 * PriorityString - turn a priority into a string
 ************************************************************************/
UPtr PriorityString(UPtr string,Byte priority)
{
	/*
	 * normalize
	 */
	priority=Prior2Display(priority);
	GetRString(string,PRIOR_STRN+5+priority);
	return(string);
}

/**********************************************************************
 * SetPriority - set a message's priority, 
 * 				handle virtual TOCs, too
 **********************************************************************/
void SetPriority(TOCHandle tocH,short sumNum,short priority)
{
	TOCHandle realTOC;
	short	realSum;
	
	SetPriorityLo(tocH,sumNum,priority);
	realTOC = GetRealTOC(tocH,sumNum,&realSum);
	if (realTOC && realTOC != tocH)
	{
		// do real mailbox also if working in virtual mailbox
		SetPriorityLo(realTOC,realSum,priority);
		tocH = realTOC;
		sumNum = realSum;
	}
	SearchUpdateSum(tocH, sumNum, tocH, (*tocH)->sums[sumNum].serialNum, false, false);	//	Notify search window
}

/**********************************************************************
 * SetTAEScore - set a message's score, 
 * 				handle virtual TOCs, too
 **********************************************************************/
void SetTAEScore(TOCHandle tocH,short sumNum,short score)
{
	TOCHandle realTOC;
	short	realSum;
	
	if ((*tocH)->sums[sumNum].score != score)
	{
		SetTAEScoreLo(tocH,sumNum,score);
		realTOC = GetRealTOC(tocH,sumNum,&realSum);
		if (realTOC && realTOC != tocH)
		{
			// do real mailbox also if working in virtual mailbox
			SetTAEScoreLo(realTOC,realSum,score);
			tocH = realTOC;
			sumNum = realSum;
		}
		SearchUpdateSum(tocH, sumNum, tocH, (*tocH)->sums[sumNum].serialNum, false, false);	//	Notify search window
	}
}

/************************************************************************
 * SetPriorityLo - set a message's priority
 ************************************************************************/
void SetPriorityLo(TOCHandle tocH,short sumNum,short priority)
{
	MessHandle messH;
	ControlHandle cntl;
	short dp = Prior2Display(priority);
	MenuHandle mh;
	
	if (dp!=Prior2Display((*tocH)->sums[sumNum].priority))
	{
		/* invalidate priority box in the toc */
		InvalTocBox(tocH,sumNum,blPrior);
		
		// make sure the menus know
		if (mh=GetMHandle(PRIOR_HIER_MENU))
		{
			CheckNone(mh);
			SetItemMark(mh,dp,diamondMark);
		}
		
		/* set the priority display in the message */
		if ((messH=(*tocH)->sums[sumNum].messH) && IsWindowVisible(GetMyWindowWindowPtr((*messH)->win)))
		{
			if (cntl = FindControlByRefCon((*messH)->win,PRIOR_HIER_MENU))
			{
				SetBevelIcon(cntl,dp==3 ? PRIORITY_HOLLOW_ICON : PRIOR_SICN_BASE+dp-1,0,0,nil);
				SetBevelMenuValue(cntl,dp);
				Draw1Control(cntl);
			}
			if ((*messH)->hStationerySpec) (*messH)->win->isDirty = True;
		}
	}
	(*tocH)->sums[sumNum].priority = priority;
	TOCSetDirty(tocH,true);
}

/************************************************************************
 * SetTAEScoreLo - set a message's score
 ************************************************************************/
void SetTAEScoreLo(TOCHandle tocH,short sumNum,short score)
{
	if (score != (*tocH)->sums[sumNum].score)
	{
		if (!(*tocH)->noInvalBox) InvalTocBox(tocH,sumNum,blAnal);
		(*tocH)->sums[sumNum].score = score;
		TOCSetDirty(tocH,true);
	}
}

/************************************************************************
 * InvalTocBox - invalidate one area of a mailbox window
 ************************************************************************/
void InvalTocBox(TOCHandle tocH,short sumNum,short box)
{
	InvalTocBoxLo(tocH,sumNum,box);
	if (sumNum >= 0)
		SearchInvalTocBox(tocH,sumNum,box);
}

/************************************************************************
 * InvalTocBoxLo - invalidate one area of a mailbox window
 ************************************************************************/
void InvalTocBoxLo(TOCHandle tocH,short sumNum,short box)
{
	WindowPtr	tocWinWP = GetMyWindowWindowPtr ((*tocH)->win);
	Rect r;
	
	if ((*tocH)->win && IsWindowVisible (tocWinWP))
	{
		PushGWorld();
		SetPort_(GetWindowPort(tocWinWP));
	
		if (sumNum==-1)
		{
			r.top = 0;
			r.bottom = (*tocH)->win->topMargin;
		}
		else if (sumNum==-2)
		{
			r.top = (*tocH)->win->topMargin;
			r.bottom = (*tocH)->win->contR.bottom;
		}
		else
		{
			r.top = (*tocH)->win->topMargin + (sumNum-GetControlValue((*tocH)->win->vBar))*(*tocH)->win->vPitch;
			r.bottom = r.top + (*tocH)->win->vPitch;
		}
		BoxLimits(box,&r.left,&r.right,tocH);
		OffsetRect(&r,-GetControlValue((*tocH)->win->hBar)*(*tocH)->win->hPitch,0);
		InvalWindowRect(tocWinWP,&r);
		if (MBGetSort(tocH,box)) (*tocH)->resort = MAX((*tocH)->resort,kNoSlowResort);
		PopGWorld();
	}
}

/**********************************************************************
 * CopySummary - put the contents of a summary onto a text handle
 **********************************************************************/
OSErr CopySummary(TOCHandle tocH, short sumNum, Handle into)
{
	Str255 summary;
	Str255 sMBName;
	Str63 states;
	Str63 priority;
	Str63 labelString;
	Str31 dateStr;
	MSumType sum = (*tocH)->sums[sumNum];
	RGBColor color;
	
	GetRString(states,STATE_LABELS);
	if (GetSumColor(tocH,sumNum)) MyGetLabel(GetSumColor(tocH,sumNum),&color,&labelString);
		*labelString = 0;
	GetRString(priority,PRIOR_STRN+Prior2Display(sum.priority));
	
	if ((*tocH)->virtualTOC)
	{
		// Include mailbox name for virtual mailboxes
		GetMailboxName(tocH,sumNum,sMBName);
		ComposeRString(summary,SUM_SEARCH_COPY_FMT,
			states[sum.state],
			priority,
			(sum.flags & FLAG_HAS_ATT) ? YesStr : "",
			labelString,
			sum.from,
			ComputeLocalDate(&sum,dateStr),
			sum.length/(1 K),
			sMBName,
			sum.subj);		
	}
	else
	{
		ComposeRString(summary,SUM_COPY_FMT,
			states[sum.state],
			priority,
			(sum.flags & FLAG_HAS_ATT) ? YesStr : "",
			labelString,
			sum.from,
			ComputeLocalDate(&sum,dateStr),
			sum.length/(1 K),
			sum.subj);
	}
	
	return(PtrPlusHand_(summary+1,into,*summary));
}

/**********************************************************************
 * CopySelectedSummaries - copy selected summaries from a mailbox
 **********************************************************************/
OSErr CopySelectedSummaries(TOCHandle tocH)
{
	Handle copyText = NuHandle(0);
	OSErr err = noErr;
	short i;
	
	if (!copyText) return(WarnUser(COPY_FAILED,MemError()));
	
	for (i=0;i<(*tocH)->count;i++)
		if ((*tocH)->sums[i].selected && (err=CopySummary(tocH,i,copyText))) break;
	
	if (!err)
	{
		ScrapRef	scrap;
		ClearCurrentScrap();
		GetCurrentScrap(&scrap);
		err = PutScrapFlavor(scrap,kScrapFlavorTypeText,kScrapFlavorMaskNone,GetHandleSize(copyText),LDRef(copyText));
	}
	ZapHandle(copyText);

	if (err) WarnUser(COPY_FAILED,err);

	return(err);
}

/**********************************************************************
 * BoxLine2Item - turn a line # into a sort menu item
 **********************************************************************/
short BoxLine2Item(short line)
{
	short item;
	
	switch (line)
	{
		case blStat: item = SORT_STATUS_ITEM; break;
		case blJunk: item = SORT_JUNK_ITEM; break;
		case blPrior: item = SORT_PRIORITY_ITEM; break;
		case blFrom: item = SORT_SENDER_ITEM; break;
		case blDate: item = SORT_TIME_ITEM; break;
		case blMailbox:	item = SORT_MAILBOX_ITEM; break;
		case blLabel: item = SORT_LABEL_ITEM; break;
		case blSize: item = SORT_SIZE_ITEM; break;
		case blAttach: item = SORT_ATTACHMENTS_ITEM; break;
		case blSubject: item = SORT_SUBJECT_ITEM; break;
		case blAnal: item = SORT_MOOD_ITEM; break;
		default: item = 0; break;
	}
	return(item);
}

/**********************************************************************
 * BoxItem2Line - turn a sort menu item into a line number
 **********************************************************************/
short BoxItem2Line(short item)
{
	short line;
	
	switch (item)
	{
		case SORT_STATUS_ITEM: line = blStat; break;
		case SORT_JUNK_ITEM: line = blJunk; break;
		case SORT_PRIORITY_ITEM: line = blPrior; break;
		case SORT_SENDER_ITEM: line = blFrom; break;
		case SORT_TIME_ITEM: line = blDate; break;
		case SORT_MAILBOX_ITEM: line = blMailbox; break;
		case SORT_LABEL_ITEM: line = blLabel; break;
		case SORT_SIZE_ITEM: line = blSize; break;
		case SORT_ATTACHMENTS_ITEM: line = blAttach; break;
		case SORT_SUBJECT_ITEM: line = blSubject; break;
		case SORT_MOOD_ITEM: line = blAnal; break;
		default: line = 0; break;
	}
	return(line);
}


/************************************************************************
 * BoxDragDividers - see if the user is trying to drag dividers, and help
 * him if he is.
 ************************************************************************/
Boolean BoxDragDividers(Point pt, MyWindowPtr win)
{
	short line;
	long ticks = TickCount()+GetDblTime();
	Point mouse;
	short slop = GetRLong(DOUBLE_TOLERANCE);
	RgnHandle greyRgn;
	Rect r,bounds;
	short h0;
	short dh;
	long dvdh;
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	PushGWorld();
	
	if (pt.v > win->topMargin && BoxFindDivider(pt,&line))
	{
		while (TickCount()<ticks)
		{
			if (!StillDown()) {PopGWorld();return(False);}
			GetMouse(&mouse);
			if (ABS(mouse.v-pt.v)>slop) {PopGWorld();return(False);}
			if (ABS(mouse.h-pt.h)>slop) break;
		}
		if (greyRgn = NewRgn())
		{
			GetMouse(&mouse);
			r = bounds = win->contR;
			r.left = pt.h;
			r.right = pt.h+1;
			r.top = win->topMargin - (GROW_SIZE+4);
			RectRgn(greyRgn,&r);
			h0 = -win->hPitch*GetControlValue(win->hBar);
			BoxLimits(line,&bounds.left,(short*)&dvdh,tocH);
			bounds.left += h0;
			bounds.top = 0;
			dvdh = DragGrayRgn(greyRgn,mouse,&bounds,&bounds,hAxisOnly,nil);
			dh = dvdh&0xffff;
			/*
			 * I don't understand why I have to do this; I guess I don't understand
			 * DragGrayRgn.
			 */
			GetMouse(&mouse);
			if (mouse.h<=pt.h && dh>0) dh = 0;
			if (pt.h+dh<bounds.left) dh = 0;
			
			if (dh&0x7fff && (dh = RoundDiv(dh,FontWidth)))
			{
				WindowPtr	theWindow;
				(*BoxWidths)[line-1] = MAX((*BoxWidths)[line-1]+dh,0);
				SaveBoxLines();
				InfiniteClip(GetMyWindowCGrafPtr(win));
				for (theWindow = GetWindowList(); theWindow; theWindow = GetNextWindow (theWindow))
					if (IsKnownWindowMyWindow(theWindow) && (GetWindowKind(theWindow)==CBOX_WIN || GetWindowKind(theWindow)==MBOX_WIN))
					{
						SetPort_(GetWindowPort(theWindow));
						(*tocH)->needRedo = 0;
						InvalContent(GetWindowMyWindowPtr(theWindow));
						BoxPlaceBevelButtons(GetWindowMyWindowPtr(theWindow));
					}
			}
		}
		PopGWorld();
		return(True);
	}
	PopGWorld();
	return(False);
}

/************************************************************************
 * BoxFindDivider - is the current point over a dividing line?
 * Returns true if it is, and puts the line number in which.
 * puts the line to the right of the mouse in which if it isn't
 ************************************************************************/
Boolean BoxFindDivider(Point pt,short *which)
{
	short i;
	WindowPtr	winWP = FrontWindow_();
	MyWindowPtr win = GetWindowMyWindowPtr (winWP);
	TOCHandle tocH = (TOCHandle)GetWindowPrivateData(winWP);
	short h0 = -win->hPitch*GetControlValue(win->hBar);
	short l,r;
	
	if (pt.v>win->contR.bottom) return false;
	if (pt.h>win->contR.right) return false;
	for (i=1;i<BoxLinesLimit;i++)
	{
		BoxLimits(i,&l,&r,tocH);
		r += h0;
		l += h0;
		if (r-2<=pt.h && pt.h<=r) {*which=i;return(True);}
		if (l < pt.h && pt.h < r) {*which=i;return(False);}
	}
	*which = BoxLinesLimit;
	return(False);
}

/************************************************************************
 * BoxCursor - change the cursor appropriately
 ************************************************************************/
void BoxCursor(Point mouse)
{
	WindowPtr	winWP = FrontWindow_();
	MyWindowPtr win = GetWindowMyWindowPtr (winWP);
	short h0 = -win->hPitch*GetControlValue(win->hBar);
	short which,mNum;
	Rect r = win->contR;
	TOCHandle tocH = (TOCHandle)GetWindowPrivateData(winWP);

	r.top = 0;
	if (!PtInRect(mouse,&r))
		SetMyCursor(arrowCursor);
	else
	{
		mNum = (mouse.v-win->topMargin)/win->vPitch + GetControlValue(win->vBar);
		if (mouse.v>win->topMargin && BoxFindDivider(mouse,&which))
		{
			SetMyCursor(DIVIDER_CURS);
		}
		else if (mouse.v<win->topMargin)
			SetMyCursor(arrowCursor);
		else
		{
#ifdef TWO
			if (!(MainEvent.modifiers&(shiftKey|optionKey|cmdKey)) && 
					(which<=blAttach || which==blLabel || which==blServer || which==blMailbox))
			{
				mNum = (mouse.v-win->topMargin)/win->vPitch + GetControlValue(win->vBar);
				if (mNum<(*tocH)->count && (*tocH)->sums[mNum].selected)
					SetMyCursor(MENU_CURS);
				else
					SetMyCursor(plusCursor);
			}
			else
#endif
				SetMyCursor(plusCursor);
		}
	}
}

#pragma segment BoxMain
/************************************************************************
 * RedoTOC - figure out how wide a toc is
 ************************************************************************/
Boolean RedoTOC(TOCHandle tocH)
{
	short subMax;
#ifdef NEVER
	short subStart;
	short subWidth;
	short sumNum;
#endif
	Boolean needResort;
	Rect r;
	MyWindowPtr tocWin;
	WindowPtr		tocWinWP;
	short maxValid = MIN((*tocH)->needRedo,(*tocH)->maxValid);
	Boolean result = false;
	FSSpec spec;
	long top;
	short col;
	short colStart, colEnd;
	short rightCol;
	
	spec = GetMailboxSpec(tocH,-1);

	if (InAThread() || (*tocH)->which==IN_TEMP || (*tocH)->which==OUT_TEMP || IsDelivery(&spec))
	{
		(*tocH)->needRedo = (*tocH)->maxValid = (*tocH)->count;
		return false;
	}
	UL(tocH);	// just in case...
	
//	if (maxValid<(*tocH)->count && PrefIsSet(PREF_DELDUP)) TOCDelDup(tocH);
		
	if (!MBIsSticky(tocH)) needResort = (*tocH)->resort = kDontResort;
	else
		needResort = (*tocH)->resort>kNoSlowResort || (*tocH)->resort&&!PrefIsSet(PREF_SLOW_RESORT);

	tocWin = (*tocH)->win;
	tocWinWP = GetMyWindowWindowPtr (tocWin);
	
	if (maxValid < (*tocH)->count)
	{
		PushGWorld();
		SetPort_(GetMyWindowCGrafPtr(tocWin));

#ifdef NEVER
		if (RunType!=Production)
		{
			Dprintf("\pRedoing Ò%pÓ thePort %x tocWin %x;wh %x;g",spec.name,qd.thePort,(*tocH)->win,*tocH);
		}
#endif

		result = true;	// we did something
		if (maxValid<0) maxValid = 0;
				
#ifdef NEVER
		/*
		 * measure all the subject lines
		 */
		BoxLimits(blSubject,&subStart,&subMax,tocH);
		subMax = 0;
		if (FontIsFixed)
		{
			for (sumNum=maxValid;sumNum<(*tocH)->count;sumNum++)
				subMax = MAX(subMax,*(*tocH)->sums[sumNum].subj);
			subMax *= tocWin->hPitch;
		}
		else
		{
			LDRef(tocH);
			for (sumNum=maxValid;sumNum<(*tocH)->count;sumNum++)
			{
				subWidth = StringWidth((*tocH)->sums[sumNum].subj);
				subMax = MAX(subMax,subWidth);
			}
			UL(tocH);
		}
#endif
		
		/*
		 * find the rightmost column
		 */
		rightCol = 0;
		for (col=1;col<BoxLinesLimit;col++)
			if (BoxColumnShowing(col,tocH))
			{
				BoxLimits(col,&colStart,&colEnd,tocH);
				rightCol = MAX(rightCol,colEnd);
			}
		
		subMax = RoundDiv(rightCol,tocWin->hPitch);
		
		/*
		 * install new maximums; take the old max into account only if we're
		 * doing a partial
		 */
		if ((*tocH)->needRedo) subMax = MAX(subMax,tocWin->hMax);
		(*tocH)->needRedo = (*tocH)->count;			/* don't need to do this again */

		/*
		 * invalidate all the new ones
		 */
		r = tocWin->contR;
		top = tocWin->topMargin + tocWin->vPitch * (maxValid-GetControlValue(tocWin->vBar));
		if (top < r.bottom)
		{
			r.top = MAX(top,tocWin->contR.top);
			InvalWindowRect(GetMyWindowWindowPtr(tocWin),&r);
		}
		(*tocH)->maxValid = (*tocH)->count;
		if (!needResort) UpdateMyWindow(tocWinWP);
		MyWindowMaxes(tocWin,subMax,(*tocH)->count);
	}

	if (FrontWindow_()!=tocWinWP && tocWin->isActive)
	{
		result = true;
		ActivateMyWindow(tocWinWP,False);
	}
		
	if (needResort && tocWin && IsWindowVisible (tocWinWP))
	{
		if ((*tocH)->resort == kResortWhenever)
		{
			uLong elapsedTicks = TickCount() - (*tocH)->lastSortTicks;
			
			if (elapsedTicks < GetRLong(MIN_MB_SORT_TICKS))
				needResort = false;	//	Let's not resort so soon
		}
	
		if (needResort)
		{
			MBResort(tocH);
			result = true;
		}
	}
	
	if (MyWinHasSelection((*tocH)->win))
		(*tocH)->win->hasSelection = true;
	else
		(*tocH)->win->hasSelection = false;
	
	if (result)
	{
		ShowBoxSizes((*tocH)->win);
		PopGWorld();
	}
	return(result);
}

/**********************************************************************
 * MBIsSticky - is a mailbox set for sticky sort
 **********************************************************************/
Boolean MBIsSticky(TOCHandle tocH)
{
	short i;
	short max=0;
	
	for (i=1;i<=NSORT;i++) if (MBGetSort(tocH,i)) max = MBGetSort(tocH,i);
	
	return(max>0);
}

#pragma segment Balloon
/************************************************************************
 * BoxHelp - help for mailboxes
 ************************************************************************/
void BoxHelp(MyWindowPtr win, Point mouse)
{
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	Rect r;
	Rect tallContR = win->contR;
	short hnum=0;
	short column;
	short hoff = win->hPitch*GetControlValue(win->hBar);
	ControlHandle cntl;
	short factor = BoxLinesLimit-1;

	SetRect(&r,0,0,REAL_BIG/2,REAL_BIG/2);
	tallContR.top = win->topMargin - (GROW_SIZE+4);
	
	if (PtInRect(mouse,&tallContR))
	{
		if (BoxFindDivider(mouse,&column))
		{
			BoxLimits(column,&r.left,&r.right,tocH);
			r.right++; r.left = r.right-3;
			OffsetRect(&r,-hoff,0);
			SectRect(&r,&tallContR,&r);
			MyBalloon(&r,70,0,MBOX_HELP_STRN+column,0,nil);
		}
		else
		{
			BoxLimits(column,&r.left,&r.right,tocH);
			OffsetRect(&r,-hoff,0);
			r.right = MIN(r.right,win->contR.right);
			if (mouse.v<win->topMargin)
			{
				Boolean s = MBGetSort(tocH,column);
				r.bottom = win->topMargin;
				r.top = r.bottom - (GROW_SIZE+4);
				MyBalloon(&r,70,0,MBOX_HELP_STRN+(s?3:2)*factor+column,0,nil);
			}
			else
			{
				r.top = win->topMargin; r.bottom = win->contR.bottom;
				if (column==blServer)
				{
					if ((*tocH)->imapTOC)
						MyBalloon(&r,70,0,0,IMAP_BOX_SERVER_COLUMN_HELP_PICT,nil);
					else
						MyBalloon(&r,70,0,0,133,nil);
				}
				else if (column>2)
					MyBalloon(&r,70,0,MBOX_HELP_STRN+factor+column,0,nil);
				else if (column==blPrior)
					MyBalloon(&r,70,0,0,PRIOR_HELP_PICT,nil);
				else if (column==blStat)
					MyBalloon(&r,70,0,0,STATUS_HELP_PICT,nil);
			}
		}
	}
	else if (cntl=MyFindControl(mouse,win))
	{
		short n = 4*factor+1;
		
		switch(GetControlReference(cntl))
		{
			case PREVIEW_DIVIDE_CNTL: n+=2; break;
			case PREVIEW_TOGGLE_CNTL: n++; break;
			case kBoxSizeRefCon:
				if ((*tocH)->imapTOC)
				{
					r = *GetControlBounds(cntl,&r);
					MyBalloon(&r,70,0,PrefIsSet(PREF_SHOW_NUM_SELECTED)?IMAP_COMPACT_SHOW_NUM_HELP:IMAP_COMPACT_HELP,0,nil);
					n = 0;
				}
				else if (PrefIsSet(PREF_SHOW_NUM_SELECTED))
					n += 3;
				break;
			default: n = 0;
		}
		
		if (n)
		{
			r = *GetControlBounds(cntl,&r);
			MyBalloon(&r,70,0,MBOX_HELP_STRN+n,0,nil);
		}
	}
	
}

#pragma segment BoxSort
/**********************************************************************
 * BoxSelectSame - select all messages with the same <something>
 **********************************************************************/
void BoxSelectSame(TOCHandle tocH, short item,short clickedSum)
{
	short selected, candidate;
	int (*compare)();
	short last;
	Boolean need;
	
	/*
	 * phase 1 - figure out the comparator
	 */
	switch(item)
	{
		case SORT_STATUS_ITEM:
			compare = SumStatCompare;
			break;
		case SORT_JUNK_ITEM:
			compare = SumJunkCompare;
			break;
		case SORT_PRIORITY_ITEM:
			compare = SumPriorCompare;
			break;
		case SORT_SUBJECT_ITEM:
			compare = SumSubjCompare;
			break;
		case SORT_SENDER_ITEM:
			compare = SumFromCompare;
			break;
		case SORT_TIME_ITEM:
			compare = SumTimeFuzzCompare;
			break;
		case SORT_MAILBOX_ITEM:
			compare = SumMailboxCompare;
			break;
		case SORT_SIZE_ITEM:
			compare = SumSizeKCompare;
			break;
		case SORT_ATTACHMENTS_ITEM:
			compare = SumAttCompare;
			break;
		case SORT_LABEL_ITEM:
			compare = SumLabelCompare;
			break;
		case SORT_MOOD_ITEM:
			compare = SumAnalCompare;
			break;
		default: return;
	}

	LDRef(tocH);
	gSortTOC = tocH;
		
	/*
	 * phase 2 - figure out which ones we need to select
	 */
	for (selected=0;selected<(*tocH)->count;selected++)
	{
		if ((*tocH)->sums[selected].selected)
		{
			(*tocH)->sums[selected].spareShort2 = 0;	/* for compare's benefit */
			for (candidate=0;candidate<(*tocH)->count;candidate++)
				if (candidate!=selected && !((*tocH)->sums[candidate].opts&OPT_WILL_SEL))
				{
					(*tocH)->sums[candidate].spareShort2 = 0;
					if (!(*compare)(&(*tocH)->sums[selected],&(*tocH)->sums[candidate]))
						(*tocH)->sums[candidate].opts |= OPT_WILL_SEL;
				}
		}
	}

	UL(tocH);
	
	/*
	 * phase 3 - select them
	 */
	for (candidate=0;candidate<(*tocH)->count;candidate++)
		if ((*tocH)->sums[candidate].opts&OPT_WILL_SEL)
		{
			if (!(*tocH)->sums[candidate].selected)
				SelectBoxRange(tocH,candidate,candidate,True,-1,-1);
			(*tocH)->sums[candidate].opts &= ~OPT_WILL_SEL;
		}
	
	/*
	 * phase 4 - sort them
	 */
	last = -1;
	need = False;
	for (candidate=0;candidate<(*tocH)->count;candidate++)
	{
		if ((*tocH)->sums[candidate].selected)
		{
			if (last!=-1 && last != candidate-1)
			{
				need = True;
				break;
			}
			last = candidate;
		}
	}
	
	if (need)
	{
		for (candidate=0;candidate<(*tocH)->count;candidate++)
		{
			if ((*tocH)->sums[candidate].selected) (*tocH)->sums[candidate].spareShort = 1;
			else (*tocH)->sums[candidate].spareShort = (candidate<clickedSum) ? 0 : 2;
		}
		SortTOC(tocH,False,SumSelectCompare);
		InvalContent((*tocH)->win);
	}
}
/**********************************************************************
 * MBSortHit - handle hits on the box headers
 **********************************************************************/
void MBSortHit(TOCHandle tocH, short index, Boolean reverse, Boolean extend)
{
	Byte sort = MBGetSort(tocH,index);
	short i;
	short sortOrder;
	ControlHandle cntl;
	
	if (!extend)
	  for (i=1;i<=NSORT;i++) MBRemoveSort(tocH,i);
	
	if (!sort)
	{
		sort = reverse ? SORT_DESCEND : SORT_ASCEND;
		MBAddSort(tocH,index,sort);
	}
	else if (reverse)
	{
		sortOrder = sort&0x3;
		sortOrder = (sortOrder==SORT_ASCEND) ? SORT_DESCEND : SORT_ASCEND;
		MBGetSort(tocH,index) = (sort & ~0x3) | sortOrder;
		(*tocH)->resort = kResortNow;
		if (cntl=FindControlByRefCon((*tocH)->win,'wide'+index))
			SetControlValue(cntl,1);
	}
	else if (extend)
	{
		MBRemoveSort(tocH,index);
	}
}

/**********************************************************************
 * MBRemoveSort - remove a sort
 **********************************************************************/
void MBRemoveSort(TOCHandle tocH,short index)
{
	Byte oldSort = MBGetSort(tocH,index);
	Boolean needResort=False;
	ControlHandle cntl;
	
	if (MBGetSort(tocH,index))
	{
		MBGetSort(tocH,index) = 0;
		if (UseListColors) InvalTocBox(tocH,-2,index);
		if (cntl=FindControlByRefCon((*tocH)->win,'wide'+index))
			SetControlValue(cntl,0);
		
		for (index=1;index<=NSORT;index++)
			if (MBGetSort(tocH,index)>oldSort)
			{
				MBGetSort(tocH,index) -= 4;
				needResort = 2;
			}
		(*tocH)->resort = kResortNow;
		TOCSetDirty(tocH,true);
	}
}

/**********************************************************************
 * MBAddSort - add a sort
 **********************************************************************/
void MBAddSort(TOCHandle tocH,short index,Byte sortOrder)
{
	short i;
	Byte max=0;
	ControlHandle cntl;
	
	for (i=1;i<=NSORT;i++)
		if (MBGetSort(tocH,i)>max) max = MBGetSort(tocH,i);
	
	max = (((max>>2)+1)<<2) + sortOrder;
	
	MBGetSort(tocH,index) = max;
	(*tocH)->resort = kResortNow;
	TOCSetDirty(tocH,true);
	if (cntl=FindControlByRefCon((*tocH)->win,'wide'+index))
		SetControlValue(cntl,1);
}


/**********************************************************************
 * MBResort - resort a mailbox
 **********************************************************************/
int (*MBCompareTable[BoxLinesLimit+2])();
void MBResort(TOCHandle tocH)
{
	Byte max=0;
	short i;
	short n;
	Boolean reverse;
	short sub=1;
	Boolean group = ((*tocH)->laurence)!=0;
	
	// don't sort any IMAP mailboxes that are in a senstive stage of resynchronization
	if ((*tocH)->imapTOC)
	{
		MailboxNodeHandle mBox = TOCToMbox(tocH);
		if (mBox && *mBox)
		{
			if (DoesIMAPMailboxNeed(mBox, kNeedsSortLock))
			{
				(*tocH)->resort = kResortNow;
				return;
			}
		}
	}
	
	if (group)
	{
		int (*compare)();
		
		//sort by subject
		GroupSubjThreshTime = GetRLong(GROUP_SUBJ_MAX_TIME) * 3600*24;
		compare = GroupSubjThreshTime ? SumSubjTimedCompare : SumSubjCompare;
		MBCompareTable[0] = SumSubjCompare;
		// if using a time threshhold, sort secondarily by date
		MBCompareTable[1] = GroupSubjThreshTime ? SumTimeCompare : nil;
		MBCompareTable[2] = nil;
		SortTOC(tocH,False,MBResortCompare);
		
		// give identical id's to identical subjects
		(*tocH)->sums[0].subjId = sub;
		LDRef(tocH);
		for (i=1;i<(*tocH)->count;i++)
		{
			if (compare((*tocH)->sums+i,(*tocH)->sums+i-1)) sub++;
			(*tocH)->sums[i].subjId = sub;
		}
		UL(tocH);
	}
	
	for (i=1;i<=NSORT;i++) if (MBGetSort(tocH,i)>max) max = MBGetSort(tocH,i);
	
	reverse = SORT_DESCEND==(max&3);
	max >>= 2;
	max = MIN(max,BoxLinesLimit);
	
	if (MBGetSort(tocH,blMailbox) && !(tocH && (*tocH)->virtualTOC))
	{
		//	This mailbox was sorted by subject with an older version of Eudora
		MBGetSort(tocH,blSubject) = MBGetSort(tocH,blMailbox);
		MBGetSort(tocH,blMailbox) = 0;
	}
	
	if (max)
	{
		for (n=1;n<=max;n++)
			for (i=1;i<=NSORT;i++)
				if (((*tocH)->sorts[i-1]>>2)==n)
				{
					MBCompareTable[n-1] = FindMBSort(BoxLine2Item(i),(MBGetSort(tocH,i)&3)==SORT_DESCEND);
					(*tocH)->lastSort = BoxLine2Item(i);
					break;
				}
		MBCompareTable[n-1] = nil;
		SortTOC(tocH,reverse,MBResortCompare);
		TOCSetDirty(tocH,true);
	}
	(*tocH)->resort = kDontResort;
	
	if (group)
	{
		// zero the spare short
		for (i=(*tocH)->count-1;i>=0;i--) (*tocH)->sums[i].spareShort = 0;
		
		// for each equal subject, put the index of the highest subj
		// in the spare short.
		for (i=(*tocH)->count-1;i>=0;i--)
		{
			if (!(*tocH)->sums[(*tocH)->sums[i].subjId-1].spareShort)
			{
				// we haven't seen this one yet; assign id
				(*tocH)->sums[(*tocH)->sums[i].subjId-1].spareShort = sub;
				(*tocH)->sums[i].subjId = sub;
				sub--;
			}
			else //we have seen it; copy
				(*tocH)->sums[i].subjId	= (*tocH)->sums[(*tocH)->sums[i].subjId-1].spareShort;
		}
		
		// and now, one final sort
		MBCompareTable[0] = SumSubjIdCompare;
		MBCompareTable[1] = nil;
		SortTOC(tocH,False,MBResortCompare);
	}
	
	if ((*tocH)->win)
	{
		InvalContent((*tocH)->win);
		BoxCenterSelection((*tocH)->win);
	}

	(*tocH)->lastSortTicks = TickCount();
}

/**********************************************************************
 * MBResortCompare - resort a mailbox with multiple conditions
 **********************************************************************/
int MBResortCompare(MSumPtr s1, MSumPtr s2)
{
	short i;
	long res = 0;
	
	//	Check for sentinel first
	if (s1->length==kLargeUniqueValue) return 1;
	if (s2->length==kLargeUniqueValue) return -1;

	for (i=0;MBCompareTable[i] && !res;i++)
		res = (*MBCompareTable[i])(s1,s2);
	return (res ? res : s1->spareShort2-s2->spareShort2);
}
/**********************************************************************
 * FindMBSort - find the right sort function
 **********************************************************************/
int (*FindMBSort(short item,Boolean reverse))()
{
	switch(item)
	{
		case SORT_STATUS_ITEM:
			return(reverse?RevSumStatCompare:SumStatCompare);
			break;
		case SORT_JUNK_ITEM:
			return(reverse?RevSumJunkCompare:SumJunkCompare);
			break;
		case SORT_PRIORITY_ITEM:
			return(reverse?RevSumPriorCompare:SumPriorCompare);
			break;
		case SORT_SUBJECT_ITEM:
			return(reverse?RevSumSubjCompare:SumSubjCompare);
			break;
		case SORT_SENDER_ITEM:
			return(reverse?RevSumFromCompare:SumFromCompare);
			break;
		case SORT_TIME_ITEM:
			if (MainEvent.modifiers&controlKey)
				return(reverse?RevSumOffsetCompare:SumOffsetCompare);
			else
				return(reverse?RevSumTimeCompare:SumTimeCompare);
			break;
		case SORT_MAILBOX_ITEM:
			return(reverse?RevSumMailboxCompare:SumMailboxCompare);
			break;
		case SORT_SIZE_ITEM:
			return(reverse?RevSumSizeCompare:SumSizeCompare);
			break;
		case SORT_ATTACHMENTS_ITEM:
				return(reverse?RevSumAttCompare:SumAttCompare);
				break;
		case SORT_LABEL_ITEM:
			return(reverse?RevSumLabelCompare:SumLabelCompare);
		case SORT_MOOD_ITEM:
			return(reverse?RevSumAnalCompare:SumAnalCompare);
			break;
	}
	return(nil);
}	

/************************************************************************
 * SwapSum
 ************************************************************************/
void SwapSum(MSumPtr sum1, MSumPtr sum2)
{
	MSumType tempSum;
	tempSum = *sum1;
	*sum1 = *sum2;
	*sum2 = tempSum;
}

/************************************************************************
 * SumTimeCompare - compare the arrival times of two sums
 ************************************************************************/
int SumTimeCompare(MSumPtr sum1, MSumPtr sum2)
{
	long res = (unsigned)sum1->seconds > (unsigned)sum2->seconds ? 1 :
						 (sum1->seconds==sum2->seconds ? 0 : -1);
	return(res);
}

/************************************************************************
 * SumMailboxCompare - compare the mailboxes of two (virtual mailbox) sums
 ************************************************************************/
int SumMailboxCompare(MSumPtr sum1, MSumPtr sum2)
{
	FSSpecHandle	specList = (*gSortTOC)->mailbox.virtualMB.specList;
	Str63	s1,s2;


	PSCopy(s1,(*specList)[sum1->u.virtualMess.virtualMBIdx].name);
	PSCopy(s2,(*specList)[sum2->u.virtualMess.virtualMBIdx].name);
	return(StringComp(s1,s2));
}

/************************************************************************
 * SumOffsetCompare - compare the disk offsets of two sums
 ************************************************************************/
int SumOffsetCompare(MSumPtr sum1, MSumPtr sum2)
{
	long res = (unsigned)sum1->offset > (unsigned)sum2->offset ? 1 :
						 (sum1->offset==sum2->offset ? 0 : -1);
	return(res);
}

/************************************************************************
 * SumTimeFuzzCompare - compare the arrival times of two sums, fuzzily
 ************************************************************************/
int SumTimeFuzzCompare(MSumPtr sum1, MSumPtr sum2)
{
	Str31 string1, string2;
	return(StringComp(ComputeLocalDate(sum1,string1),ComputeLocalDate(sum2,string2)));
}

/************************************************************************
 * SumSubjCompare - compare the subjects of two sums
 ************************************************************************/
int SumSubjCompare(MSumPtr sum1, MSumPtr sum2)
{
	short res = SubjCompare(sum1->subj,sum2->subj);
	return (res);
}

/************************************************************************
 * SumSubjTimedCompare - compare the subjects of two summaries, but only let
 * them be equal if they are within a certain amount of time
 ************************************************************************/
int SumSubjTimedCompare(MSumPtr sum1, MSumPtr sum2)
{
	long diff = sum1->seconds-sum2->seconds;
	
	if (ABS(diff) > GroupSubjThreshTime) return diff;
	
	return SubjCompare(sum1->subj,sum2->subj);
}

/************************************************************************
 * SumSubjIdCompare - compare the spare shorts of two sums
 ************************************************************************/
int SumSubjIdCompare(MSumPtr sum1, MSumPtr sum2)
{
	short res = sum1->subjId-sum2->subjId;
	return (res);
}

/**********************************************************************
 * SubjCompare - compare two subjects
 **********************************************************************/
int SubjCompare(PStr in1, PStr in2)
{
	Str127 s1,s2;
	Boolean maxed1, maxed2;
	MSumType sum;
	
	PSCopy(s1,in1);
	PSCopy(s2,in2);

	maxed1 = *s1>=sizeof(sum.subj)-2;
	maxed2 = *s2>=sizeof(sum.subj)-2;
	
#ifdef NEVER
	ComposeLogS(-1,nil,"\pComparing Ç%pÈ Ç%pÈ",s1,s2);
#endif
	TrimRe(s1,true);
	TrimRe(s2,true);
	TrimInitialWhite(s1);
	TrimInitialWhite(s2);
	TrimWhite(s1);
	TrimWhite(s2);
	TrimInternalWhite(s1);
	TrimInternalWhite(s2);
	if (maxed1) *s2 = MIN(*s2,*s1);	// subject 1 maxed out--trim subject 2
	if (maxed2) *s1 = MIN(*s2,*s1);	// subject 2 maxed out--trim subject 1
	return(StringComp(s1,s2));
}

/************************************************************************
 * SumFromCompare - compare the senders of two sums
 ************************************************************************/
int SumFromCompare(MSumPtr sum1, MSumPtr sum2)
{
	short res=StringComp(sum1->from,sum2->from);
	return (res);
}

/************************************************************************
 * SumSizeCompare - compare two messages by size
 ************************************************************************/
int SumSizeCompare(MSumPtr sum1, MSumPtr sum2)
{
	long l1 = EffectiveLength(sum1);
	long l2 = EffectiveLength(sum2);

#ifdef DEBUG
	if (RunType!=Production && BUG2) return sum1->serialNum - sum2->serialNum;
#endif
	return (l1-l2);
}

/************************************************************************
 * SumSizeKCompare - compare two messages by size in K
 ************************************************************************/
int SumSizeKCompare(MSumPtr sum1, MSumPtr sum2)
{
	long l1 = EffectiveLength(sum1)/(1 K);
	long l2 = EffectiveLength(sum2)/(1 K);

#ifdef DEBUG
	if (RunType!=Production && BUG2) return sum1->serialNum - sum2->serialNum;
#endif
	return (l1-l2);
}

/************************************************************************
 * SumLabelCompare - compare two messages by color
 ************************************************************************/
int SumLabelCompare(MSumPtr sum1, MSumPtr sum2)
{
	short res = SumColor(sum1)-SumColor(sum2);
	return (res);
}

/************************************************************************
 * SumAnalCompare - compare two messages by analysis
 ************************************************************************/
int SumAnalCompare(MSumPtr sum1, MSumPtr sum2)
{
	short res = (unsigned)sum1->score > (unsigned)sum2->score ? 1 :
						 (sum1->score==sum2->score ? 0 : -1);
	return (res);
}

/************************************************************************
 * SumAttCompare - compare two summaries based on whether or not they have attachments
 ************************************************************************/
int SumAttCompare(MSumPtr sum1, MSumPtr sum2)
{
	long res = (long)(sum1->flags&FLAG_HAS_ATT)-(long)(sum2->flags&FLAG_HAS_ATT);
	return (res);
}

/************************************************************************
 * SumSelectCompare - compare two summaries based on whether or not they are selected
 ************************************************************************/
int SumSelectCompare(MSumPtr sum1, MSumPtr sum2)
{
	long res = (long)(sum1->spareShort)-(long)(sum2->spareShort);

	//	Check for sentinel first
	if (sum1->length==kLargeUniqueValue) return 1;
	if (sum2->length==kLargeUniqueValue) return -1;

	if (!res) res = sum1->spareShort2-sum2->spareShort2;
	return (res);
}

/**********************************************************************
 * SumStatCompare - compare two messages based on state
 **********************************************************************/ 
int SumStatCompare(MSumPtr sum1, MSumPtr sum2)
{
	static Byte table[] = {1,2,3,4,5,6,8,7,10,11,9,12,13,14,15,16,17};
	short	state1,state2;
	
	//	Check if state is 255 (sentinel) or otherwise outside range or table
	state1 = sum1->state==255 ? 255 : (sum1->state<0 || sum1->state>=sizeof(table)) ? 100 : table[sum1->state];
	state2 = sum2->state==255 ? 255 : (sum2->state<0 || sum2->state>=sizeof(table)) ? 100 : table[sum2->state];
	return state1-state2;
}

/**********************************************************************
 * SumPriorCompare - compare two messages by priority
 **********************************************************************/
int SumPriorCompare(MSumPtr sum1, MSumPtr sum2)
{
	short p1,p2,res;
	p1 = sum1->priority;
	p2 = sum2->priority;
	if (!p1) p1=Display2Prior(3);
	if (!p2) p2=Display2Prior(3);
	res = p1-p2;
	return (res);
}

/**********************************************************************
 * SumJunkCompare - compare two messages by junk score
 **********************************************************************/
int SumJunkCompare(MSumPtr sum1, MSumPtr sum2)
{
	return sum1->spamScore-sum2->spamScore;
}

/**********************************************************************
 * reverses of all of the above
 **********************************************************************/
int RevSumSizeCompare(MSumPtr sum1, MSumPtr sum2) {return(-SumSizeCompare(sum1,sum2));}
int RevSumLabelCompare(MSumPtr sum1, MSumPtr sum2) {return(-SumLabelCompare(sum1,sum2));}
int RevSumAnalCompare(MSumPtr sum1, MSumPtr sum2) {return(-SumAnalCompare(sum1,sum2));}
int RevSumAttCompare(MSumPtr sum1, MSumPtr sum2) {return(-SumAttCompare(sum1,sum2));}
int RevSumStatCompare(MSumPtr sum1, MSumPtr sum2) {return(-SumStatCompare(sum1,sum2));}
int RevSumPriorCompare(MSumPtr sum1, MSumPtr sum2) {return(-SumPriorCompare(sum1,sum2));}
int RevSumJunkCompare(MSumPtr sum1, MSumPtr sum2) {return(-SumJunkCompare(sum1,sum2));}
int RevSumFromCompare(MSumPtr sum1, MSumPtr sum2) {return(-SumFromCompare(sum1,sum2));}
int RevSumSubjCompare(MSumPtr sum1, MSumPtr sum2) {return(-SumSubjCompare(sum1,sum2));}
int RevSumTimeCompare(MSumPtr sum1, MSumPtr sum2) {return(-SumTimeCompare(sum1,sum2));}
int RevSumMailboxCompare(MSumPtr sum1, MSumPtr sum2) {return(-SumMailboxCompare(sum1,sum2));}
int RevSumOffsetCompare(MSumPtr sum1, MSumPtr sum2) {return(-SumOffsetCompare(sum1,sum2));}

/************************************************************************
 * SortTOC - sort a toc, given a comparison function
 ************************************************************************/
void SortTOC(TOCHandle tocH, Boolean reverse, int (*compare)())
{
	MSumPtr sums, sPtr;
	MSumType	sentinel;
	int count = (*tocH)->count;
		
	SetHandleBig_(tocH,GetHandleSize_(tocH)+sizeof(MSumType));
	if (MemError()) {WarnUser(MEM_ERR,MemError()); return;}
	sums=LDRef(tocH)->sums;
	
	//	Set up sentinel
	Zero(sentinel);
	InfiniteString(sentinel.from,sizeof(sentinel.from));
	InfiniteString(sentinel.subj,sizeof(sentinel.subj));
	sentinel.seconds = 0xffffffff;
	sentinel.length = kLargeUniqueValue;
	sentinel.state = 255;
	sentinel.flags = 0xffffffff-FLAG_SKIPPED;
	sentinel.opts = 0xffffffff-OPT_JUSTSUB;
	sentinel.priority = 255;
	sentinel.spareShort = 0x7fff;
	sentinel.subjId = 0x7fff;
	sentinel.flags |= FLAG_HAS_ATT;
	sentinel.selected = True;
	sentinel.spareShort2 = 0x7fff;
	sentinel.u.virtualMess.virtualMBIdx = 0x7fff;
	sentinel.score = 0xf;
	sums[count] = sentinel;
	
	/* tag with original positions */
	for (sPtr=sums;sPtr<sums+(*tocH)->count;sPtr++)
		sPtr->spareShort2=/*reverse*/ 0 ?((*tocH)->count-(sPtr-sums)) : (sPtr-sums);
	
	gSortTOC = tocH;
	QuickSort((void*)sums,sizeof(MSumType),0,count-1,(void*)compare,(void*)SwapSum);
	for (sPtr=sums;sPtr<sums+(*tocH)->count;sPtr++)
		if (sPtr->messH) (*(MessHandle)sPtr->messH)->sumNum = sPtr-sums;

	UL(tocH);
	SetHandleBig_(tocH,GetHandleSize_(tocH)-sizeof(MSumType));
	TOCSetDirty(tocH,true);
}

#pragma segment BoxWidget
/**********************************************************************
 * MessDragSend - drag data proc for messages
 **********************************************************************/
pascal OSErr MessDragSend(FlavorType flavor, void *dragSendRefCon, ItemReference theItemRef, DragReference drag)
{
	OSErr err=cantGetFlavorErr;
	MessHandle messH;
	UHandle data;
	
	if (!(err=MyGetDragItemData(drag,1,MESS_FLAVOR,(void*)&data)))
	{
		messH = **(MessHandle**)data;
		ZapHandle(data);
		err = BoxMessDragSendLo(flavor,dragSendRefCon,drag,(*messH)->tocH,(*messH)->sumNum);
	}
	return(err);
}

/**********************************************************************
 * BoxDragSend - drag data proc for mailboxes
 **********************************************************************/
pascal OSErr BoxDragSend(FlavorType flavor, void *dragSendRefCon, ItemReference theItemRef, DragReference drag)
{
	OSErr err=cantGetFlavorErr;
	FSSpecPtr spec = (FSSpecPtr)dragSendRefCon;
	TOCHandle tocH;
	UHandle data;
	
	if (!(err=MyGetDragItemData(drag,1,TOC_FLAVOR,(void*)&data)))
	{
		tocH = **(TOCHandle**)data;
		ZapHandle(data);
		err = BoxMessDragSendLo(flavor,dragSendRefCon,drag,tocH,FirstMsgSelected(tocH));
	}
	return(err);
}

/**********************************************************************
 * BoxMessDragSendLo - common stuff for message & summary dragging
 **********************************************************************/
OSErr BoxMessDragSendLo(FlavorType flavor, void *dragSendRefCon, DragReference drag, TOCHandle tocH,short sumNum)
{
	AEDesc dropLocation;
	OSErr err=cantGetFlavorErr;
	FSSpecPtr spec = (FSSpecPtr)dragSendRefCon;
	UHandle addresses=nil;
		
	/*
	 * return the filename
	 */
	if (flavor==SPEC_FLAVOR)
	{
		NullADList(&dropLocation,nil);
		if (!(err=GetDropLocation(drag,&dropLocation)))
		if (!(err=GetDropLocationDirectory(&dropLocation,&spec->vRefNum,&spec->parID)))
		{
			MakeMessFileName(tocH,sumNum,spec->name);
			UniqueSpec(spec,MAX_BOX_NAME);
			FSpCreateResFile(spec,CREATOR,'TEXT',smSystemScript);
			WhackFinder(spec);
			if (!(err = ResError()))
				err = MySetDragItemFlavorData(drag,1,SPEC_FLAVOR,spec,sizeof(FSSpec));
		}
		DisposeADList(&dropLocation,nil);
	}
	else if (flavor==A822_FLAVOR)
	{
		if (!MyGetDragItemData(drag,1,MESS_FLAVOR,nil))
			err = GatherBoxAddresses(tocH,DragOrMods(drag),sumNum,sumNum,&addresses,true);
		else
			err = GatherBoxAddresses(tocH,DragOrMods(drag),-1,-1,&addresses,true);
		if (!err)
		{
			CommaList(addresses);
			MoveHHi(addresses);
			err = MySetDragItemFlavorData(drag,1,A822_FLAVOR,LDRef(addresses),GetHandleSize(addresses));
			ZapHandle(addresses);
		}
	}
	return(err);
}

/************************************************************************
 * DragXfer - drag messages from one mailbox to the next
 ************************************************************************/
void DragXfer(Point pt, TOCHandle tocH, MessHandle messH)
{
	RgnHandle dragRgn = tocH ? BoxBuildDragRgn(tocH) : MessBuildDragRgn(messH);
	DragReference drag;
	MyWindowPtr win = tocH ? (*tocH)->win : (*messH)->win;
	OSErr err;
	PromiseHFSFlavor promise;
	FSSpec spec;
	Boolean trash = False;
	FSSpecHandle handle=nil;
	FSSpec moveSpec;
	FSSpecPtr specPtr = &moveSpec;	
	DECLARE_UPP(MessDragSend,DragSendData);
	DECLARE_UPP(BoxDragSend,DragSendData);
	
	INIT_UPP(MessDragSend,DragSendData);
	INIT_UPP(BoxDragSend,DragSendData);
	if (dragRgn)
	{

		Zero(spec);
		Zero(moveSpec);
		SetMyCursor(arrowCursor); SFWTC = True;
		/*
		 * create a drag
		 */
		if (!MyNewDrag(win,&drag))
		{
			OutlineRgn(dragRgn,1);
			GlobalizeRgn(dragRgn);
			Zero(promise);
			promise.fileType = 'TEXT';
			promise.fileCreator = CREATOR;
			promise.promisedFlavor = SPEC_FLAVOR;
			
			if (messH) DragTOCSource = (*(*messH)->tocH)->win;	// real source for data
			else DragTOCSource = (*tocH)->win;

			gDrawerOpenedWin = nil;
						
			if (!(err=SetDragSendProc(drag,tocH?BoxDragSendUPP:MessDragSendUPP,(void*)&spec)))
			if (!(err=AddDragItemFlavor(drag,1,flavorTypePromiseHFS,&promise,sizeof(promise),0)))
			if (!(err=AddDragItemFlavor(drag,1,'m822',nil,0,flavorNotSaved)))
			if (!tocH || !(err=AddDragItemFlavor(drag,1,TOC_FLAVOR,&tocH,sizeof(tocH),flavorSenderOnly|flavorNotSaved)))
			if (!messH || !(err=AddDragItemFlavor(drag,1,MESS_FLAVOR,&messH,sizeof(messH),flavorSenderOnly|flavorNotSaved)))
			if (!(err=AddDragItemFlavor(drag,1,A822_FLAVOR,nil,0,0)))
			if (!(err=AddDragItemFlavor(drag,1,SPEC_FLAVOR,nil,0,0)))
			if (!(err=AddDragItemFlavor(drag,1,'euXX',&specPtr,sizeof(FSSpecPtr),flavorSenderOnly|flavorNotSaved)))
			{
				// MyTrackDrag can close the window being dragged.  Save information about the message we may need after the drag ...
				FSSpec sourceSpec = tocH ? GetMailboxSpec(tocH,-1) : GetMailboxSpec((*messH)->tocH,-1);
				TOCHandle messToc = messH ? (*messH)->tocH : nil;
				short messSum = messH ? (*messH)->sumNum : 0;
					
				if (!MyTrackDrag(drag,&MainEvent,dragRgn))
				{
					trash = DragTargetWasTrash(drag);
					MyDisposeDrag(drag); drag=nil;
					if (*moveSpec.name)
					{
						spec = moveSpec;
						trash = !(CurrentModifiers()&optionKey);
					}
							
					if (*spec.name && !SameSpec(&sourceSpec,&spec))
					{
						if (tocH) MoveSelectedMessages(tocH,&spec,!trash);
						else
						{
							if (trash)
							{
								AddXfUndo(messToc,TOCBySpec(&spec),messSum);
								EzOpen(messToc,messSum,0,MainEvent.modifiers,True,True);
							}
							MoveMessage(messToc,messSum,&spec,!trash);
						}
					}
				}
			}
			if (drag) MyDisposeDrag(drag);
			if (gDrawerOpenedWin == win)
			{
				// Opened the mailbox drawer for drag. Close it.
				MBDrawerToggle(win);
				gDrawerOpenedWin = nil;
			}
		}
		DisposeRgn(dragRgn);
	}
}

/**********************************************************************
 * 
 **********************************************************************/
OSErr BoxDragHandler(MyWindowPtr win,DragTrackingMessage which,DragReference drag)
{
	OSErr err=noErr;
	RgnHandle rgn;
	ControlHandle cntl;
	Rect	r;
	Point mouse;
	
	if (!DragIsInteresting(drag,TOC_FLAVOR,MESS_FLAVOR,nil)) return(dragNotAcceptedErr);
	if (DragTOCSource==win)
	{
		if (which ==kDragTrackingInWindow)
		{
			// Dragging within this window
			// If cursor is over mailbox drawer button, open drawer if not already open
			cntl = FindControlByRefCon(win,kDrawerSwitch);
			if (cntl && !GetControlValue(cntl))
			{
				GetControlBounds(cntl,&r);
				GetDragMouse(drag, &mouse, nil);
				GlobalToLocal(&mouse);
				if (PtInRect(mouse,&r))
				{
					MBDrawerToggle(win);
					gDrawerOpenedWin = win;
				}
			}
		}
		return(dragNotAcceptedErr);
	}

	SetPort_(GetMyWindowCGrafPtr(win));
	
	switch (which)
	{
		case kDragTrackingEnterWindow:
			if (rgn=NewRgn())
			{
				RectRgn(rgn,&win->contR);
				OutlineRgn(rgn,2);
				ShowDragHilite(drag,rgn,True);
				DisposeRgn(rgn);
			}
			err = noErr;
			break;
		case kDragTrackingLeaveWindow:
			HideDragHilite(drag);
			err = noErr;
			break;
		case 0xfff:
			err = BoxDrop(win,drag);
			break;
	}
	
	return(err);
}

/**********************************************************************
 * 
 **********************************************************************/
OSErr BoxDrop(MyWindowPtr win, DragReference drag)
{
	TOCHandle winTocH = (TOCHandle) GetMyWindowPrivateData(win);
	FSSpec spec = GetMailboxSpec(winTocH,-1);
	TOCHandle tocH;
	short sumNum;
	OSErr err;
	UHandle data=nil;
	
	if (!(err=MyGetDragItemData(drag,1,MESS_FLAVOR,(void*)&data)))
	{
		tocH = (***(MessHandle**)data)->tocH;
		sumNum = (***(MessHandle**)data)->sumNum;
	}
	else if (!(err=MyGetDragItemData(drag,1,TOC_FLAVOR,(void*)&data)))
	{
		tocH = **(TOCHandle**)data;
		sumNum = -1;
	}
	ZapHandle(data);
	
	if (!err)
	{
		DragSumNum = sumNum;
		DragTOCFrom = tocH;
		DragTOCTo = (TOCHandle) winTocH;
		DragModsWere = DragOrMods(drag);
	}
	return(err);
}

/************************************************************************
 * FinishBoxDrag - finish a drag outside of the actual drag mgr
 ************************************************************************/
OSErr FinishBoxDrag(void)
{
	FSSpec spec = GetMailboxSpec(DragTOCTo,-1);
	
	if (DragSumNum==-1)
		MoveSelectedMessages(DragTOCFrom,&spec,(DragModsWere&optionKey)!=0);
	else
	{
		if (!(DragModsWere&optionKey))
		{
			AddXfUndo(DragTOCFrom,DragTOCTo,DragSumNum);
			EzOpen(DragTOCFrom,DragSumNum,0,DragModsWere,True,True);
		}
		MoveMessage(DragTOCFrom,DragSumNum,&spec,(DragModsWere&optionKey)!=0);
	}
	DragTOCFrom = nil;
	return noErr;
}

/************************************************************************
 * TOCHUnder - what tocH is under a point?
 ************************************************************************/
TOCHandle TOCHUnder(Point pt)
{
	WindowPtr theWindow;
	
	if (FindWindow(pt,&theWindow) &&
			(GetWindowKind(theWindow)==MBOX_WIN ||
			 GetWindowKind(theWindow)==CBOX_WIN))
	{
		return((TOCHandle)GetWindowPrivateData(theWindow));
	}
	return(nil);
}


/************************************************************************
 * BoxBuildDragRgn - drag messages from one mailbox to the next
 ************************************************************************/
RgnHandle BoxBuildDragRgn(TOCHandle tocH)
{
	RgnHandle theRgn = NewRgn();
	short mNum;
	Rect r, sumR;
	MyWindowPtr win = (*tocH)->win;
	short minVis, maxVis;
	
	if (theRgn)
	{
		OpenRgn();
		SetRect(&r,win->contR.left,0,win->contR.right,win->vPitch);
		minVis = GetControlValue(win->vBar);
		OffsetRect(&r,0,-minVis*win->vPitch);
		maxVis = minVis + (win->contR.bottom-win->contR.top)/win->vPitch+1;
		maxVis = MIN(maxVis,(*tocH)->count-1);
		
		for (mNum=minVis;mNum<=maxVis;mNum++)
		{
			if ((*tocH)->sums[mNum].selected)
			{
				sumR = r;
				OffsetRect(&sumR,0,win->topMargin+mNum*win->vPitch);
				FrameRect(&sumR);
			}
		}
		
		CloseRgn(theRgn);
	}
	return(theRgn);
}
/**********************************************************************
 * BoxClick - handle a click in the content region of a window
 **********************************************************************/
void BoxClick(MyWindowPtr win,EventRecord *event)
{
	Point pt;
	short startNum,endNum,mNum;
	WindowPtr	winWP = GetMyWindowWindowPtr(win);
	TOCHandle tocH = (TOCHandle)GetWindowPrivateData(winWP);
	Boolean cmd,shift,opt,ctl;
	short which;
	ControlHandle cntl;
	Boolean clickOnSelected;
	
	pt = event->where;
	SetPort_(GetMyWindowCGrafPtr(win));
	GlobalToLocal(&pt);
	
	(*tocH)->userActive = true;
		
	if (win->isActive)
	{
		if (BoxDragDividers(pt,win)) return;
		if ((*tocH)->previewPTE && PtInPETE(pt,(*tocH)->previewPTE) && PeteLen((*tocH)->previewPTE))
		{
			PeteEditWFocus(win,(*tocH)->previewPTE,peeEvent,(void*)event,nil);
			if (win->pte) BoxListFocus(tocH,false);
			if ((*tocH)->drawerWin) MBDrawerSetFocus((*tocH)->drawerWin, false);
			if ((*tocH)->previewID && (mNum=FirstMsgSelected(tocH))>=0 && PreviewReadFocus)
				BeenThereDoneThat(tocH,-1);

			return;
		}
		else if ((cntl=FindControlByRefCon(win,PREVIEW_DIVIDE_CNTL)) && PtInControl(pt,cntl))
		{
			BoxDragPreviewDivider(tocH,pt);
			return;
		}
		else if ((cntl=FindControlByRefCon(win,'wide'+blServer)) && PtInControl(pt,cntl))
			return;	// ignore clicks
		else if ((cntl=FindControlByRefCon(win,kBoxSizeRefCon)) && PtInControl(pt,cntl) && (*tocH)->virtualTOC)
			return;	// ignore clicks in box size button for virtual mailboxes
		else if ((cntl=FindControlByRefCon(win,kConConProfRefCon)) && PtInControl(pt,cntl))
		{
			BoxConConProfControl(tocH,cntl,pt);
			return;	// ignore clicks in box size button for virtual mailboxes
		}
		else if (HandleControl(pt,win))
			return;
	}

	if (!PtInRect(pt,&win->contR))
	{
		if (!win->isActive) SelectWindow_(winWP);
		(*tocH)->userActive = true;
		return;
	}

	shift = (event->modifiers & shiftKey) != 0;
	cmd = (event->modifiers & cmdKey) != 0;
	opt = (event->modifiers & optionKey) != 0;
	ctl = (event->modifiers & controlKey) != 0;
	mNum = (pt.v-win->topMargin)/win->vPitch + GetControlValue(win->vBar);
	clickOnSelected = mNum<(*tocH)->count && (*tocH)->sums[mNum].selected;
	
	if (!win->isActive && !DragPrefSumImmediate && !ctl && !clickOnSelected)
	{
		SelectWindow_(winWP);
		return;
	}

	if (win->isActive)
	{
		PeteFocus(win,nil,true);
		BoxListFocus(tocH,true);
		if ((*tocH)->drawerWin) MBDrawerSetFocus((*tocH)->drawerWin, false);

		/*
		 * menus in to summary
		 */
		if (!cmd && !opt && !shift && ClickType==Single && clickOnSelected)
		{
			BoxFindDivider(pt,&which);
			if (which<=blAttach || which==blLabel || which==blServer || which==blMailbox)
			{
				EnableMenuItems(False);
				SetMenuTexts(0,False);
				SetMyCursor(arrowCursor);
				if (which==blLabel)
					BoxLabelMenu(tocH,mNum,nil,pt);
				else 
				if (which==blPrior)
					BoxPriorMenu(tocH,mNum,pt);
				else if (which==blAttach)
					BoxAttachMenu(tocH,mNum,pt);
				else if (which==blStat)
					BoxStateMenu(tocH,mNum,pt);
				else if (which==blServer)
					BoxServerMenu(tocH,mNum,pt);
				else if (which==blMailbox)
					BoxMailboxMenu(tocH,mNum,pt);
				return;
			}
		}
	}
	
	/*
	 * drag xfer
	 */
	if (!shift && !cmd && clickOnSelected)
	{
		if (MyWaitMouseMoved(event->where,True))
		{
			DragXfer(pt,tocH,nil);
			(*tocH)->userActive = true;
			return;
		}
		PeteFocus(win,nil,true);
		BoxListFocus(tocH,true);
	}
		
	if (shift)
	{
		for (startNum=0;startNum<(*tocH)->count;startNum++)
			if ((*tocH)->sums[startNum].selected) break;
		if (startNum < (*tocH)->count)
		{
		  if (startNum < mNum) endNum = mNum;
			else
			{
				for (endNum=startNum+1;endNum<(*tocH)->count;endNum++)
					if (!(*tocH)->sums[endNum].selected) break;
				endNum--;
				if (!(*tocH)->sums[endNum].selected) endNum = startNum;
				startNum = mNum;
			}
		}
		else startNum=endNum=mNum;
	}
	else startNum=endNum=mNum;

	SelectBoxRange(tocH,startNum,endNum,cmd,-1,-1);
	(*tocH)->userActive = true;
	if (mNum<(*tocH)->count && (*tocH)->sums[mNum].selected)
	{
		if (!shift && !cmd && DragPrefSumImmediate && MyWaitMouseMoved(event->where,True))
		{
			DragXfer(pt,tocH,nil);
			(*tocH)->userActive = true;
			return;
		}
	}

	if (!win->isActive) {SelectWindow_(winWP);}
	else
	{
		EnableMenus(winWP,False);
		if (ClickType==Double)
			DoDependentMenu(winWP,FILE_MENU,FILE_OPENSEL_ITEM,event->modifiers);
		else
		{
			BoxTrack(tocH,mNum==startNum?endNum:startNum,cmd&&!shift);
			if (opt)
			{
				BoxFindDivider(pt,&which);
				BoxSelectSame(tocH,BoxLine2Item(which),mNum);
			}
			(*tocH)->userActive = true;
		}
	}
}

/************************************************************************
 * BoxConConProfControl - Handle clicks in the profile control
 ************************************************************************/
void BoxConConProfControl(TOCHandle tocH,ControlHandle cntl,Point pt)
{
	short item, oldItem;
	Str63 name;
	
	// update the menu
	BoxBuildConConProfMenu(tocH,cntl);
	oldItem = GetBevelMenuValue(cntl);
	
	// let the control manager track
	HandleControl(pt,(*tocH)->win);
	item = GetBevelMenuValue(cntl);
	
	// Now, see what we can see
	if (item!=oldItem)
	{
		MenuHandle mh = (MenuHandle)GetBevelMenu(cntl);
		
		if (mh)
		{
			uLong cmd;
			short section, subItem;
			uLong hash;
			
			// what's the command id?
			GetMenuItemCommandID(mh,item,&cmd);
			section = cmd>>16;
			subItem = cmd&0xffff;
			
			// Figure out what profile was chosen
			switch (subItem)
			{
				case 0:	// Default
					hash = subItem;
					GetRString(name,section==1?CONCON_PREVIEW_PROFILE:CONCON_MULTI_PREVIEW_PROFILE);
					break;
				case CONCON_NONE:	// None
					hash = subItem;
					GetRString(name,subItem);
					break;
				default:	// actual profile
					MyGetItem(mh,item,name);
					hash = Hash(name);
			}
			
			// if section is 1, single selection.
			// if section is 2, multi selection.
			// set the proper one
			if (section==1)
				(*tocH)->singlePreviewProfileHash = hash;
			else
				(*tocH)->multiPreviewProfileHash = hash;
			TOCSetDirty(tocH,true);
			
			// Update the control
			SetControlTitle(cntl,name);
			SetBevelMenuValue(cntl,item);
			
			// Now let somebody else figure out what to do with the selection...
			BoxRefreshPreview(tocH);
		}
	}
}

/************************************************************************
 * BoxBuildConConProfMenu - Build the profile menu for the preview
 ************************************************************************/
void BoxBuildConConProfMenu(TOCHandle tocH,ControlHandle cntl)
{
	MenuHandle mh = (MenuHandle)GetBevelMenu(cntl);
	Str255 itemStr;
	Str63 defaultStr;
	short i;
	short j;
	short n;
	short selected = CountSelectedMessages(tocH);
	uLong myHash;
	ConConProH ccph;
	
	if (!mh) return;	// uh-oh...
	
	TrashMenu(mh,1);
	n = 0;
	
	for (i=1;i<=2;i++) // 1 == single preview, 2 == multi preview
	{
		// Which set of profiles?
		MyAppendMenu(mh,GetRString(itemStr,i==1?CONCON_PROF_SINGLE:CONCON_PROF_MULTI));
		DisableItem(mh,++n);

		// which profile are we using for this section of the menu?
		myHash = i==1?(*tocH)->singlePreviewProfileHash:(*tocH)->multiPreviewProfileHash;
		
		// the default
		GetRString(defaultStr,i==1?CONCON_PREVIEW_PROFILE:CONCON_MULTI_PREVIEW_PROFILE);
		ComposeRString(itemStr,CONCON_PROF_DEFAULT,defaultStr);
		MyAppendMenu(mh,itemStr);
		SetMenuItemCommandID(mh,++n,i<<16);
		if (myHash==0) SetItemMark(mh,n,checkMark);
		EnableIf(mh,n,i==1 && selected==1 || i==2 && selected>1);
				
		// None
		MyAppendMenu(mh,GetRString(itemStr,CONCON_NONE));
		SetMenuItemCommandID(mh,++n,i<<16|CONCON_NONE);
		if (myHash==CONCON_NONE) SetItemMark(mh,n,checkMark);
		EnableIf(mh,n,i==1 && selected==1 || i==2 && selected>1);

		// if the default for this mailbox isn't done already,
		// grab a copy of its name here
		if (myHash!=CONCON_NONE && myHash!=0 && (ccph=ConConProFindHash(myHash)))
			PSCopy(defaultStr,(*ccph)->name);
		else
			*defaultStr = 0;
		
		// The defined profiles
		ConConAddItems(mh);
		// Now fix up the items
		for (j=CountMenuItems(mh);j>n;j--)
		{
			SetMenuItemCommandID(mh,j,(i<<16)|j);
			EnableIf(mh,j,i==1 && selected==1 || i==2 && selected>1);
			MyGetItem(mh,j,itemStr);
			if (StringSame(itemStr,defaultStr)) SetItemMark(mh,j,checkMark);
		}
		// and reset our count
		n = CountMenuItems(mh);
		
		// separate the types
		if (i==1) 
		{
			n++;
			AppendMenu(mh,"\p-");
		}
	}
}

/************************************************************************
 * BoxDragPreviewDivider
 ************************************************************************/
void BoxDragPreviewDivider(TOCHandle tocH,Point pt)
{
	Rect r, bounds;
	MyWindowPtr tocWin = (*tocH)->win;
	CGrafPtr	winPort = GetMyWindowCGrafPtr(tocWin);
	short hi = 0;
	short fudge = pt.v - tocWin->contR.bottom - GROW_SIZE;
	Rect rPort = *GetPortBounds(winPort,&rPort);
	short totalHi = RectHi(rPort);
	
	SetRect(&r,0,tocWin->contR.bottom,rPort.right,tocWin->contR.bottom+GROW_SIZE);
	bounds.top = tocWin->contR.top + GetRLong(PREVIEW_USELESS)*tocWin->vPitch + GROW_SIZE + fudge;
	bounds.bottom = totalHi - GetRLong(PREVIEW_USELESS)*FontLead - fudge - GROW_SIZE - tocWin->topMargin;
	bounds.left = -REAL_BIG/2;
	bounds.right = REAL_BIG/2;
	
	pt = DragDivider(pt,&r,&bounds,tocWin);
	
	if (pt.v==0)
	{
		GetMouse(&pt);
		if (pt.v > tocWin->contR.bottom) hi = -1;
	}
	else
	{
		hi = totalHi - pt.v - fudge;	// where the user put the line
		hi = totalHi-hi-GROW_SIZE-tocWin->topMargin;	// the room the user left for summaries
		hi = RoundDiv(hi,tocWin->vPitch)*tocWin->vPitch;	// round to nearest summary
		hi = totalHi - tocWin->topMargin - GROW_SIZE - hi;	// back to the preview height
	}
	
	if (hi)
	{
		(*tocH)->previewHi = hi;
		TOCSetDirty(tocH,true);
		MyWindowDidResize(tocWin,nil);
	}
	
	AuditHit(false, false, false, false, false, GetWindowKind(GetMyWindowWindowPtr(tocWin)), PREVIEW_DIVIDE_CNTL, mouseDown);
}

/**********************************************************************
 * BoxLabelMenu - pop up the label menu in a mailbox window
 **********************************************************************/
void BoxLabelMenu(TOCHandle tocH,short mNum,MessHandle messH,Point pt)
{
	short currentLabel = GetSumColor(tocH,mNum);
	MenuHandle mh = GetMHandle(LABEL_HIER_MENU);
	long res;
	MyWindowPtr win;
	
	LocalToGlobal(&pt);
#ifdef REFRESH_LABELS_MENU
	RefreshLabelsMenu();
#endif //REFRESH_LABELS_MENU
	SetItemMark(mh,Label2Menu(currentLabel),checkMark);
	res = MyPopupMenuSelect((*tocH)->win,mh,pt,Label2Menu(currentLabel));
	if (res&0xffff0000)
		if (messH) DoMenu2(GetMyWindowWindowPtr((*messH)->win),LABEL_HIER_MENU,res&0xff,nil);
		else DoMenu2(GetMyWindowWindowPtr((*tocH)->win),LABEL_HIER_MENU,res&0xff,nil);
	
	// Audit the fact that the label menu was used ...
	if (tocH)
	{
	 	win = (*tocH)->win;
	 	if (win)
	 	{
			AuditHit(false, false, false, false, false, GetWindowKind(GetMyWindowWindowPtr(win)), AUDITCONTROLID(GetWindowKind(GetMyWindowWindowPtr(win)),LABEL_HIER_MENU), mouseDown);
		}
	}
}

/**********************************************************************
 * BoxPriorMenu - pop up the priority menu in a mailbox window
 **********************************************************************/
void BoxPriorMenu(TOCHandle tocH,short mNum,Point pt)
{
	short currentPri = Prior2Display((*tocH)->sums[mNum].priority);
	MenuHandle mh = GetMHandle(PRIOR_HIER_MENU);
	long res;
	
	LocalToGlobal(&pt);
	res = MyPopupMenuSelect((*tocH)->win,mh,pt,currentPri);
	if (res&0xffff0000) BoxMenu((*tocH)->win,PRIOR_HIER_MENU,res&0xff,nil);
}

/**********************************************************************
 * BoxServerMenu - pop up the server menu in a mailbox window
 **********************************************************************/
void BoxServerMenu(TOCHandle tocH,short mNum,Point pt)
{
	MenuHandle mh = GetMHandle(SERVER_HIER_MENU);
	long res;
	short item=0;
	
	LocalToGlobal(&pt);

	item = EnableServerItems((*tocH)->win,mNum,false,false);
		
	res = MyPopupMenuSelect((*tocH)->win,mh,pt,item);

	if (res&0xffff0000) BoxMenu((*tocH)->win,SERVER_HIER_MENU,res&0xff,nil);
}

/************************************************************************
 * ServerMenuChoice - choose an item from the server menu
 ************************************************************************/
void ServerMenuChoice(TOCHandle tocH,short mNum,short item,Boolean shiftPressed)
{
	// the server menu looks different for IMAP mailboxes
	if ((*tocH)->imapTOC)
	{	
		switch(item)
		{
			case isvmDelete:
			{
				Handle uids = nil;
				long c =  CountSelectedMessages(tocH);
				long sumNum;
				
				// build a list of uids to be deleted
				uids = NuHandleClear(c*sizeof(unsigned long));
				if (uids)
				{
					for (sumNum=0;sumNum<(*tocH)->count && c;sumNum++)
						if ((*tocH)->sums[sumNum].selected)
							BMD(&((*tocH)->sums[sumNum].uidHash),&((unsigned long *)(*uids))[--c],sizeof(unsigned long));
					
					// and delete them.
					IMAPDeleteMessages(tocH, uids, false, false, (((*tocH)->sums[mNum].opts&OPT_DELETED)!=0), false);
				}
				else
				{
					WarnUser(MemError(), MEM_ERR);
				}	
				break;
			}
			case isvmRemoveCache:
			{
				IMAPRemoveSelectedCachedContents(tocH);
				break;			
			}
			case isvmFetchMessage:
			{
				if (shiftPressed) IMAPRemoveSelectedCachedContents(tocH);
			 	IMAPFetchSelectedMessages(tocH, false);
				break;
			}
			case isvmFetchAttachments:	
			{
				if (shiftPressed) IMAPRemoveSelectedCachedContents(tocH);
				IMAPFetchSelectedMessages(tocH, true);
				break;
			}
		}
	}
	else
	{
		switch(item)
		{
			case svmNone:
				RemTSFromPOPD(DELETE_ID,tocH,mNum);
				RemTSFromPOPD(FETCH_ID,tocH,mNum);
				InvalTocBox(tocH,mNum,blServer);
				if ((*tocH)->sums[mNum].messH) Fix1MessServerArea((*(*tocH)->sums[mNum].messH)->win);
				break;
			case svmFetch:
				RemTSFromPOPD(DELETE_ID,tocH,mNum);
				AddTSToPOPD(FETCH_ID,tocH,mNum,False);
				InvalTocBox(tocH,mNum,blServer);
				if ((*tocH)->sums[mNum].messH) Fix1MessServerArea((*(*tocH)->sums[mNum].messH)->win);
				break;
			case svmDelete:
				RemTSFromPOPD(FETCH_ID,tocH,mNum);
				AddTSToPOPD(DELETE_ID,tocH,mNum,False);
				InvalTocBox(tocH,mNum,blServer);
				if ((*tocH)->sums[mNum].messH) Fix1MessServerArea((*(*tocH)->sums[mNum].messH)->win);
				break;
			case svmBoth:
				AddTSToPOPD(FETCH_ID,tocH,mNum,False);
				AddTSToPOPD(DELETE_ID,tocH,mNum,False);
				InvalTocBox(tocH,mNum,blServer);
				if ((*tocH)->sums[mNum].messH) Fix1MessServerArea((*(*tocH)->sums[mNum].messH)->win);
				break;
		}
	}
}

/************************************************************************
 * EnableServerItems - enable the server menu & return the current item
 ************************************************************************/
short EnableServerItems(MyWindowPtr win,short selectedSum,Boolean all,Boolean shiftPressed)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	MenuHandle mh = GetMHandle(SERVER_HIER_MENU);
	TOCHandle tocH;
	short mNum;
	Boolean onServer=true, skipped, onDelete, onFetch;
	Boolean lmos = PrefIsSet(PREF_LMOS);
	short item = 0;
	short kind = win ? GetWindowKind (winWP) : 0;
	Boolean imapTOC = false;

	CheckNone(mh);
	
	if (all)
	{
		EnableItem(mh,0);
		for (item=CountMenuItems(mh);item>0;item--)
			EnableItem(mh,item);
		return(0);
	}
	
	if (kind!=MESS_WIN && kind!=MBOX_WIN)
		onServer = false;
	else if (kind==MESS_WIN)
	{
		tocH = (*Win2MessH(win))->tocH;
		mNum = (*Win2MessH(win))->sumNum;
	}
	else
	{
		tocH = (TOCHandle) GetWindowPrivateData (winWP);
		mNum = FirstMsgSelected(tocH);
		if (mNum==-1) onServer = false;
	}

	imapTOC = (kind==MESS_WIN || kind==MBOX_WIN) && (tocH) && ((*tocH)->imapTOC);
	
	if (onServer && !imapTOC)
	{
#ifdef	ENABLE_BASED_ON_FIRST_SELECTED
		if ((*tocH)->sums[mNum].flags & FLAG_OUT) onServer = false;
		else if (!TSOnPOPD(POPD_ID,tocH,mNum)) onServer = false;
		else
		{
			skipped = ((*tocH)->sums[mNum].flags & FLAG_SKIPPED)!=0;
			onFetch = skipped && onServer && TSOnPOPD(FETCH_ID,tocH,mNum);
			onDelete = onServer && TSOnPOPD(DELETE_ID,tocH,mNum);
		}
#else	//ENABLE_BASED_ON_FIRST_SELECTED
		short i, sumNum;
		Boolean on = false, skip = false, fetch = false, delete = false;
		enum {firstMessage = 1, lastMessage, clickedMessage};
		short firstSelected = FirstMsgSelected(tocH);
		short lastSelected = LastMsgSelected(tocH);
				
		// check the first and last selected message, as well as the message clicked on.
		// Note: checking them all will be a significant performance hit.
		onServer = skipped = onFetch = onDelete = false;
		for (i = firstMessage; i <= clickedMessage; i++)
		{
			sumNum = -1;
			switch (i)
			{
				case firstMessage:
					sumNum = firstSelected;
					break;
				
				case lastMessage:
					if (lastSelected != firstSelected) sumNum = lastSelected;
					break;
					
				case clickedMessage:
					if ((lastSelected != selectedSum) && (selectedSum != firstSelected)) sumNum = selectedSum;
					break;
			}
			
			if (sumNum >= 0)
			{
				on = true;
				if ((*tocH)->sums[sumNum].flags & FLAG_OUT) on = false;
				else if (!TSOnPOPD(POPD_ID,tocH,sumNum)) on = false;
				else
				{
					skip = ((*tocH)->sums[sumNum].flags & FLAG_SKIPPED)!=0;
					fetch = skip && on && TSOnPOPD(FETCH_ID,tocH,sumNum);
					delete = on && TSOnPOPD(DELETE_ID,tocH,sumNum);
				}
			}
						
			skipped = skipped || skip;
			onServer = onServer || on;
			onFetch = onFetch || fetch;
			onDelete = onDelete || delete;
		}
#endif	//ENABLE_BASED_ON_FIRST_SELECTED
	}
	
	if (imapTOC)
	{
		short firstItem = shiftPressed ? kisvmLimit : ksvmLimit;
		short lastItem = shiftPressed ? kimsvmLimit : kisvmLimit;;
		
		// put the right item into the server menu
		for (item = firstItem+1; item < lastItem; item++)
			SetItemR(mh, item - firstItem, ServerMenuStrnStrn + item);
		
		EnableItem(mh,0);
			
		if (!FancyTrashForThisPers(tocH)) 
		{
			EnableItem(mh,isvmDelete);
			if (((*tocH)->sums[mNum].opts&OPT_DELETED)!=0) 
				SetItemMark(mh,item=isvmDelete,checkMark);
		}
		else DisableItem(mh,isvmDelete);	

		EnableItem(mh,isvmRemoveCache);
		EnableItem(mh,isvmFetchMessage);
		EnableItem(mh,isvmFetchAttachments);
	}
	else
	{
		// put the right items into the server menu
		for (item = 1; item < ksvmLimit; item++)
			SetItemR(mh, item, ServerMenuStrnStrn + item);

		if (!onServer)
			DisableItem(mh,0);	
		else
		{
			EnableItem(mh,0);
			EnableItem(mh,svmFetch);
			EnableItem(mh,svmBoth);
			EnableItem(mh,svmDelete);

			if (!skipped) {DisableItem(mh,svmFetch); DisableItem(mh,svmBoth);}
		
			if (onFetch)
			{
				if (!lmos || onDelete)
					item = svmBoth;
				else
					item = svmFetch;
			}
			else if (onDelete)
				item = svmDelete;
			else
				item = svmNone;

			if (item) SetItemMark(mh,item,checkMark);
			if (!lmos) DisableItem(mh,svmFetch);
		}
	}
	
	return(item);
}
		

/**********************************************************************
 * BoxStateMenu - pop up the state menu in a mailbox window
 **********************************************************************/
void BoxStateMenu(TOCHandle tocH,short mNum,Point pt)
{
	short newStatus;
		
	newStatus = StatusMenu((*tocH)->win,(*tocH)->sums[mNum].state,pt);
	if (newStatus) BoxMenu((*tocH)->win,STATE_HIER_MENU,Status2Item(newStatus),nil);
}

/**********************************************************************
 * BoxMailboxMenu - pop up a mailbox hierarchy
 **********************************************************************/
void BoxMailboxMenu(TOCHandle tocH,short mNum,Point pt)
{
	short realSum;
	TOCHandle realTOC;
	
	LocalToGlobal(&pt);
	if (realTOC = GetRealTOC(tocH,mNum,&realSum))
	{
		PopupMailboxPath(nil,realTOC,realSum,pt);
	}
}

/**********************************************************************
 * BoxAttachMenu - popup a menu of attachments
 **********************************************************************/
void BoxAttachMenu(TOCHandle tocH,short mNum,Point pt)
{
	long res;
	MenuHandle mh=NewMenu(BOX_ATTACH_MENU,"");
	StackHandle stack = nil;
	CSpec spec;
	Handle text;
	short i;
	FindAttData attData;
	
	if (mh && !StackInit(sizeof(spec),&stack))
	{
		// first, find all the files
		for (mNum=0;mNum<(*tocH)->count;mNum++)
		{
			if ((*tocH)->sums[mNum].selected)
			{
				if ((*tocH)->sums[mNum].opts&OPT_HTML)
				{
					CacheMessage(tocH,mNum);
					if (text=(*tocH)->sums[mNum].cache)
					{
						HNoPurge(text);
						InitAttachmentFinder(&attData,text,false,tocH,&(*tocH)->sums[mNum]);
						while (GetNextAttachment(&attData,&spec.spec))
						{
							spec.count = mNum;
							if ((*stack)->elCount==255) break;
							StackQueue(&spec,stack);
						}
						HPurge(text);
					}
				}
				if ((*tocH)->sums[mNum].flags&FLAG_HAS_ATT)
				{
					CacheMessage(tocH,mNum);
					if (text=(*tocH)->sums[mNum].cache)
					{
						HNoPurge(text);
						InitAttachmentFinder(&attData,text,true,tocH,&(*tocH)->sums[mNum]);
						while (GetNextAttachment(&attData,&spec.spec))
						{
							spec.count = mNum;
							if ((*stack)->elCount==255) break;
							StackQueue(&spec,stack);
						}
						HPurge(text);
					}
				}
			}
			if ((*stack)->elCount==255) break;
		}
		
		// next, build the menu
		if ((*stack)->elCount)
		{
			for (i=0;!StackItem(&spec,i,stack);i++)
			{
				MyAppendMenu(mh,spec.spec.name);
				if (FileTypeOf(&spec.spec)==MIME_FTYPE) DisableItem(mh,CountMenuItems(mh)); 	// currently dangerous to open EMSAPI docs; FIX THIS
			}
			
			// now pop it up
			InsertMenu(mh,-1);
			LocalToGlobal(&pt);
			res = MyPopupMenuSelect((*tocH)->win,mh,pt,0);
			DeleteMenu(GetMenuID(mh));
			
			// and act
			if (res&0xffff0000)
			{
				i = res&0xff;
				if (!StackItem(&spec,i-1,stack))
				{
					// if the user selected an IMAP attachment stub ...
					if (IsIMAPAttachmentStub(&spec.spec))
					{
						// and it can be fetched ...
						if (CanFetchAttachment(&spec.spec)) 
						{
							// get it.
							short	realSum;
							
							DownloadIMAPAttachment(&spec.spec, TOCToMbox(GetRealTOC(tocH,spec.count,&realSum)), false);
						}
					}
					else
					{
						if (!OpenOtherDoc(&spec.spec,0!=(MainEvent.modifiers&controlKey),false,nil))
						BeenThereDoneThat(tocH,spec.count);
					}
				}
			}
		}
		
		ZapHandle(stack);
	}
	
	if (mh) DisposeMenu(mh);
}

/**********************************************************************
 * StatusMenu - popup the status menu
 **********************************************************************/
short StatusMenu(MyWindowPtr win,short origStatus,Point where)
{
	long res;
	MenuHandle mh;
	short item = Status2Item(origStatus);
	
	CheckState(win,!win,origStatus);
	
	mh = GetMHandle(STATE_HIER_MENU);
	LocalToGlobal(&where);
	res = MyPopupMenuSelect(win,mh,where,item);
	if (res&0xffff0000) return(Item2Status(res&0xff));
	else return(0);
}

/**********************************************************************
 * MyPopupMenuSelect - popup a menu adjusting the position
 **********************************************************************/
long MyPopupMenuSelect(MyWindowPtr win,MenuHandle mh,Point where,short item)
{
	short	left;
	GDHandle	gd;
	Rect	screenRect, windRect;
	Boolean	hasMB;

	//	Adjust left position to point within menu
	left = where.h-2*MenuWidth(mh)/3;
	
	//	Make sure it's still on the same screen
	utl_GetWindGD(GetMyWindowWindowPtr(win),&gd,&screenRect,&windRect,&hasMB);
	if (left < screenRect.left)
		left = screenRect.left;
	
	return AFPopUpMenuSelect(mh,where.v,left,item);
}

 /************************************************************************
 * BoxTrack - track the mouse in a mailbox window
 ************************************************************************/
void BoxTrack(TOCHandle tocH,int anchor,Boolean cmd)
{
	MyWindowPtr win = (*tocH)->win;
	Point pt;
	int lastSpot=anchor;
	int spot;
	int vVal,vMin,vMax,topVis,botVis;
	long lastTicks=0;
	
	while (StillDown())
		if (TickCount()-lastTicks>GetRLong(SCROLL_ARROW_THROTTLE))
		{
			lastTicks = TickCount();
			vVal = GetControlValue(win->vBar);
			vMin = GetControlMinimum(win->vBar);
			vMax = GetControlMaximum(win->vBar);
			topVis = vVal;
			botVis = (*tocH)->count - (vMax-vVal+1);
			GetMouse(&pt);
			spot = vVal + (pt.v-win->topMargin)/(int)win->vPitch;
			if (spot<vMin) spot=vMin;
			else if (spot>=(*tocH)->count) spot=(*tocH)->count-1;
			if (spot<topVis &&	vVal>vMin || spot>botVis && vVal<vMax)
			{
				if (spot<topVis)
					ScrollIt(win,0,topVis-spot);
				else
					ScrollIt(win,0,botVis-spot);
			}
			if (spot!=lastSpot)
			{
				if (!cmd)
					SelectBoxRange(tocH,anchor,spot,cmd,0,0);
				else
				{
					SelectBoxRange(tocH,anchor,spot,cmd,anchor,lastSpot);
					SelectBoxRange(tocH,anchor,lastSpot,cmd,anchor,spot);
				}
				lastSpot = spot;
			}
		}
}

/************************************************************************
 * SizeBoxClick - handle click in window's size box
 ************************************************************************/
void SizeBoxClick(MyWindowPtr win,short modifiers)
{
	FSSpec spec;
	TOCHandle tocH = (TOCHandle)GetMyWindowPrivateData(win);
	
	if ((*tocH)->virtualTOC) return;
	
	if (modifiers&optionKey)
	{
		CInfoPBRec hfi;
		Str255 scratch;

		GetRString(scratch,TOC_SUFFIX);
		DoCompact(MailRoot.vRef,MailRoot.dirId,&hfi,*scratch);
		
		if (IMAPExists())
			DoCompact(IMAPMailRoot.vRef,IMAPMailRoot.dirId,&hfi,*scratch);
	}
	else if (modifiers&cmdKey && !AnalDisabled())
	{
		short sumNum;
		for (sumNum=0;sumNum<(*tocH)->count;sumNum++)
			(*tocH)->sums[sumNum].score = 0;
		AnalBox(tocH,0,(*tocH)->count-1);
	}
	else if (modifiers&shiftKey)
	{
		// if this is an imap mailbox, clean it up.
		if ((*tocH)->imapTOC)
		{
			short count;
			for (count=0;count<(*tocH)->count;count++)
			{
				if ((*tocH)->sums[count].uidHash==0) DeleteSum(tocH,count);
				else count++;
			}
		}
		else if ((*tocH)->which==JUNK)
		{
			JunkRescanJunkMailbox ();
			ArchiveJunk ( tocH );
		}
		else
			JunkRescanBox ( tocH );
	}
	else
	{
		// if this is an IMAP toc, expunge deleted messages rather than compacting the mailbox.
		if ((*tocH)->imapTOC) IMAPRemoveDeletedMessages(tocH);
		else
		{
			spec = GetMailboxSpec(tocH,-1);
			CompactMailbox(&spec,false);
		}
	}
}

/************************************************************************
 * BeenThereDoneThat - mark this message as read
 ************************************************************************/
void BeenThereDoneThat(TOCHandle tocH,short sumNum)
{
	if (sumNum<0)
	{
		for (sumNum=(*tocH)->count-1;sumNum>=0;sumNum--)
			if ((*tocH)->sums[sumNum].selected)
				BeenThereDoneThat(tocH,sumNum);
	}
	else
	{
		if ((*tocH)->sums[sumNum].state==UNREAD) SetState(tocH,sumNum,READ);

		if ((*tocH)->virtualTOC)
		{
			// Virtual mailbox, do original also
			TOCHandle	realTOC;
			short		realSum;

			if (realTOC = GetRealTOC(tocH,sumNum,&realSum))
			if ((*realTOC)->sums[realSum].state==UNREAD)
				SetState(realTOC,realSum,READ);
		}
	}
}

/************************************************************************
 * AnalBox - process all the mail in a mailbox for anality
 ************************************************************************/
void AnalBox(TOCHandle tocH,short first, short last)
{
	short sumNum;
	if (first<0) first = 0;
	if (last<0 || last>=(*tocH)->count) last = (*tocH)->count-1;
	
	for (sumNum=first;sumNum<=last;sumNum++)
	{
		CycleBalls();
		OneAnalMess(tocH,sumNum);
	}
}

/************************************************************************
 * OneAnalMess - process a single message for anality
 ************************************************************************/
void OneAnalMess(TOCHandle tocH,short sumNum)
{
	short score;
	Boolean inHeader;
	
	if (!(*tocH)->sums[sumNum].score)
	{
#ifdef DEBUG
		if (RunType!=Production)
		{
			Str63 franklin;
			PSCopy(franklin,(*tocH)->sums[sumNum].from);
			if (EqualStrRes(franklin,FRANKLIN))
			{
				SetTAEScore(tocH,sumNum,5);
				return;
			}
		}
#endif
		CacheMessage(tocH,sumNum);
		if ((*tocH)->sums[sumNum].cache)
		{
			inHeader = true;
			score = AnalScanHandle((*tocH)->sums[sumNum].cache,0,-1,&inHeader);
			if (score>=0)
				SetTAEScore(tocH,sumNum,score+1);
		}
	}
}


/************************************************************************
 * BoxInversionSetup - setup the inversion matrix for mailbox columns
 ************************************************************************/
void BoxInversionSetup(void)
{
	Str255 scratch;
	Str15 word;
	UPtr spot;
	short i;
	long col;

	GetRString(scratch,TOC_INVERSION_MATRIX);
	for (i=1,spot=scratch+1;PToken(scratch,word,&spot," ");i++)
	{
		StringToNum(word,&col);
		TOCInversionMatrix[0][i] = col;
		TOCInversionMatrix[1][col] = i;
	}
}

/**********************************************************************
 * HitTab - clicked a tab
 **********************************************************************/
void HitTab(MyWindowPtr win,ControlHandle tabCntl,TOCHandle tocH)
{
	Boolean	fileView;
	
	fileView = GetControlValue(tabCntl)==2 ? true : false;
	if (fileView != (*tocH)->fileView) TOCSetDirty(tocH,true);
	(*tocH)->fileView = fileView;
	SetFileView(win,tocH, GetControlValue(tabCntl)==2 ? true : false);
}

/**********************************************************************
 * SetFileView - enable/disable fileview
 **********************************************************************/
void SetFileView(MyWindowPtr win,TOCHandle tocH, Boolean fileView)
{
	ControlHandle	cntl,placard;
	short			i;
	Boolean			boxCtlsVisible;
	PETEHandle		previewPTE;
	Rect 			oldContR = win->contR;
	FileViewHandle	fvh = GetFileViewInfo(win);
	RgnHandle   saveRgn;
	
	boxCtlsVisible = !fileView;

   saveRgn = SavePortClipRegion(GetMyWindowCGrafPtr(win));
	SetEmptyClipRgn(GetMyWindowCGrafPtr(win));		
	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 (previewPTE = (*tocH)->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 (fileView)
	{
		(*fvh)->savePreviewHi = (*tocH)->previewHi;
		SetBotMargin(win,0);
		(*tocH)->previewHi = -1;
		win->update = FileViewUpdate;
		win->click = FileViewClick;
		win->bgClick = FileViewClick;
		win->cursor = FileViewCursor;
		win->drag = FileViewDragHandler;
		win->key = FileViewKey;
		win->find = FileViewFind;
		win->menu = FileViewMenu;
		win->activate = FileViewActivate;
		win->idle = FileViewIdle;
	}
	else
	{
		(*tocH)->previewHi = (*fvh)->savePreviewHi;
		win->update = BoxUpdate;
		win->click = BoxClick;
		win->bgClick = BoxClick;
		win->cursor = BoxCursor;
		win->drag = BoxDragHandler;
		win->key = BoxKey;
		win->find = BoxFind;
		win->menu = BoxMenu;
		win->activate = BoxActivate;
		win->idle = BoxIdle;
	}
	
	InvalBotMargin(win);
	SetControlVisibility((*(*fvh)->list->hList)->vScroll,!boxCtlsVisible,true);
	RestorePortClipRegion(GetMyWindowCGrafPtr(win),saveRgn);
	BoxDidResize(win,nil);
//	InvalContent(win);
}


/**********************************************************************
 * BoxPreviewProfile - what's the proper preview profile for this mailbox?
 **********************************************************************/
PStr BoxPreviewProfile(PStr profileName,TOCHandle tocH,short previewTypeID)
{
	ConConProH ccph = nil;
	Str63 name;
	uLong hash = 0;
	ControlHandle cntl;
	
	// In case we find nothing
	if (profileName) *profileName = 0;
	
	// load up the hash from the mailbox
	if (previewTypeID==CONCON_PREVIEW_PROFILE)
		hash = (*tocH)->singlePreviewProfileHash;
	else if (previewTypeID==CONCON_MULTI_PREVIEW_PROFILE)
		hash = (*tocH)->multiPreviewProfileHash;
	
	// look for the hash, unless it's the magic "none" value
	if (hash!=CONCON_NONE)
	{
		if (hash) ccph = ConConProFindHash(hash);
		
		// if we didn't find it, or hash is zero (meaning use the default), look up the default
		if (!ccph) ccph = ConConProFind(GetRString(name,previewTypeID));
		
		// And copy the name we found, if any
		if (ccph && profileName) PCopy(profileName,(*ccph)->name);
	}
	
	// Set the title of the bevel button
	if (cntl=FindControlByRefCon((*tocH)->win,kConConProfRefCon))
	{
		if (profileName && *profileName) SetControlTitle(cntl,profileName);
		else SetControlTitle(cntl,GetRString(name,CONCON_NONE));
	}
	
	return profileName;
}
