/* 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 "messact.h"
#define FILE_NUM 24
/* Copyright (c) 1990-1992 by the University of Illinois Board of Trustees */
#pragma segment Messact

OSErr MakeSubjectTXE(MessHandle messH);
void MessUpdate(MyWindowPtr win);
void MessSubjRect(MyWindowPtr win,Rect *r);
Boolean MessScroll(MyWindowPtr win,int h,int v);
void MessHelp(MyWindowPtr win,Point mouse);
short MyFSWrite(short refN,long *bytes,UPtr buffer);
Boolean IsAttLine(PStr line,Boolean wantToOpen);
OSErr OpenAttLine(PETEHandle pte,PStr line,Boolean finderSelect,Boolean printIt);
Boolean GetBlahRect(MyWindowPtr win, short which, Rect *r);
Boolean TogglePOPD(OSType theType,short id, MessHandle messH);
void SADL_Hilite(DialogPtr dgPtr);
void MessButton(MyWindowPtr win,ControlHandle button,long modifiers,short part);
void MessTxChanged(MyWindowPtr win,TEHandle teh,short oldNl,short newNl,Boolean scroll);
#define I_WIDTH 29
OSErr KillTransAttachments(FSSpecHandle files);
Boolean GetTruckRect(MyWindowPtr win,Rect *c);
void AddNotifyControls(MessHandle messH);
void PlaceNotifyControls(MyWindowPtr win);
static short SaveAsToOpenFileLo(short refN,MessHandle messH);
void MessProxy(MyWindowPtr win,EventRecord *event);
OSErr LopPoundHexDigits(PStr fileName);
#ifdef USECMM_BAD
OSErr MessBuildCMenu( MyWindowPtr win, EventRecord* contextEvent, CMenuInfoHndl* specCMenuInfo );
#endif

/**********************************************************************
 * MessClose - close a message window
 **********************************************************************/
Boolean MessClose(MyWindowPtr win)
{
	MessType **messH = (MessType **)GetMyWindowPrivateData(win);
	TOCType **tocH = (*messH)->tocH;
	int sumNum = (*messH)->sumNum;
	
	if (!GrowZoning && TheBody && (*messH)->subPTE && win->pte==(*messH)->subPTE)
		MessFocus(messH,TheBody);
	if (PeteIsDirty((*messH)->subPTE)) MessSaveSub(messH);
	if (!GrowZoning) win->isDirty = win->isDirty || PeteIsDirtyList(TheBody);

	if (!NoSaves && !GrowZoning)
		if (!SaveMessHi(win,True)) return(False);

	LL_Remove(MessList,messH,(MessType **));
	AccuZap((*messH)->extras);
	AccuZap((*messH)->aSourceMID);
	if ((*messH)->etlFiles) KillTransAttachments((*messH)->etlFiles);
	ZapHandle((*messH)->etlFiles);
	SetMyWindowPrivateData (win, nil);
	AccuZap((*messH)->newsGroupAcc);
	ZapHandle(messH);
	(*tocH)->sums[sumNum].messH = nil;

#ifdef	IMAP
	// Cancel the IMAP download if this is an imap message ...
	if ((*tocH)->imapTOC) IMAPAbortMessageFetch(tocH, sumNum);
#endif

	return(True);
}


/**********************************************************************
 * SaveMessHi - save a message, if the user wants and if its dirty
 **********************************************************************/
Boolean SaveMessHi(MyWindowPtr win,Boolean closing)
{
	short which;
	
	if (win->isDirty)
	{
		which = WannaSave(win);
		if (which==CANCEL_ITEM) return(False);
		else if (which == WANNA_SAVE_SAVE)
		{
			if (!SaveMess(win)) return(False);
		}
		else if (which == WANNA_SAVE_DISCARD)
		{
			if (!closing) ReopenMessage(win);
		}
	}
	return(True);
}
	
/**********************************************************************
 * 
 **********************************************************************/
OSErr KillTransAttachments(FSSpecHandle files)
{
	FSSpec spec;
	OSErr err, anyErr=noErr;
	short n;
	
	for (n=HandleCount(files);n;n--)
	{
		spec = (*files)[n-1];
		err = WipeSpec(&spec);
		if (!anyErr) anyErr = err;
	}
	return(anyErr);
}

/**********************************************************************
 * NewPrior - compute a new priority from a menu and an old priorty
 **********************************************************************/
short NewPrior(short item,short prior)
{
	switch(item)
	{
		case pymRaise:
			prior = Prior2Display(prior);
			prior = MAX(pymHighest,prior-1);
			prior = Display2Prior(prior);
			break;
		case pymLower:
			prior = Prior2Display(prior);
			prior = MIN(pymLowest,prior+1);
			prior = Display2Prior(prior);
			break;
		default: prior = Display2Prior(item); break;
	}
	return(prior);
}

/************************************************************************
 * TransferMenuChoice - handle a menu choice from a transfer menu
 ************************************************************************/
Boolean TransferMenuChoice(short menu,short item,TOCHandle tocH,short sumNum,long modifiers,Boolean fcc)
{
	FSSpec spec, toSpec, resolvToSpec;
	short function=REAL_BIG;

	if (!IsMailboxChoice(menu,item)) return(False);
	
	if (menu==TRANSFER_MENU)
		function = TRANSFER;
	else if (IsMailboxSubmenu(menu))
		function = (menu-(g16bitSubMenuIDs?BOX_MENU_START:1))/MAX_BOX_LEVELS;
	if (function == TRANSFER)
	{
		spec = GetMailboxSpec(tocH,sumNum);
		if (GetTransferParams(menu,item,&toSpec,nil) && !SameSpec(&spec,&toSpec))
		if (!ResolveAliasOrElse(&toSpec,&resolvToSpec,nil) && !SameSpec(&spec,&resolvToSpec))
		{
			//Folder Carbon Copy - no FCC in Light!
			if (HasFeature (featureFcc) && fcc)
				Fcc((*tocH)->sums[sumNum].messH,&toSpec);
			else 
			if (sumNum>=0)
			{
#ifdef TWO
				if (!(modifiers&optionKey))
				{
#ifdef	IMAP
					if (!(*tocH)->imapTOC)
#endif
						AddXfUndo(tocH,TOCBySpec(&toSpec),sumNum);
					EzOpen(tocH,sumNum,0,modifiers,True,True);
				}
#endif
				MoveMessage(tocH,sumNum,&toSpec,(modifiers&optionKey)!=0);
			}
			else
				MoveSelectedMessages(tocH,&toSpec,(modifiers&optionKey)!=0);
		}
		return(True);
	}
	return(False);
}


/**********************************************************************
 * TogglePOPD - put a message on or off a list, 
 **********************************************************************/
Boolean TogglePOPD(OSType theType,short id, MessHandle messH)
{
	Boolean was;
	
	if (was = IdIsOnPOPD(theType,id,SumOf(messH)->uidHash))
	{
		RemIdFromPOPD(theType,id,SumOf(messH)->uidHash);
	}
	else
	{
		AddIdToPOPD(theType,id,SumOf(messH)->uidHash,False);
	}
	return(!was);
}


/************************************************************************
 * PetePaneDraw - draw a pete record in a user pane
 ************************************************************************/
pascal void PetePaneDraw(ControlHandle cntl, SInt16 part)
{
#pragma unused(part)
	PETEHandle pte = (PETEHandle)GetControlReference(cntl);
	
	PeteUpdate(pte);
}

/************************************************************************
 * MakeSubjectTXE - make a TERec for the subject
 ************************************************************************/
OSErr MakeSubjectTXE(MessHandle messH)
{
	Str63 subject;
	Rect view;
	MyWindowPtr win=(*messH)->win;
	GrafPtr oldPort;
	OSErr err;
	PETEDocInitInfo pi;
	PETEHandle pte;
	ControlHandle cntl;
	long offset = 0;
	DECLARE_UPP(PetePaneDraw,ControlUserPaneDraw);
	
	GetPort(&oldPort);
	SetPort_(GetMyWindowCGrafPtr(win));
	
	PSCopy(subject,SumOf(messH)->subj);
	
	MessSubjRect(win,&view);
	OffsetRect(&view,0,1);

	DefaultPII(win,False,0,&pi);
	pi.inRect = view;
	pi.docWidth = MessWi(win)-9;
	
	if (!(err=PeteCreate(win,&pte,peClearAllReturns,&pi)))
	{
		(*PeteExtra(pte))->frame = True;
		if(!MessFlagIsSet(messH, FLAG_UTF8) ||
		   !HasUnicode() ||
		   EncodingError(err = PeteInsertIntlText(pte,&offset,(Handle)(subject+1),-1,*subject,nil,UTF8_ENCODING,false,true)))
		{
			err=PETEInsertTextPtr(PETE,pte,0,subject+1,*subject,nil);
		}
		if(MessFlagIsSet(messH, FLAG_UTF8) && !HasUnicode())
		{
			PeteLock(pte, 0, PeteLen(pte), peModLock);
		}
		PeteFontAndSize(pte,FontID,FontSize);
		PETEMarkDocDirty(PETE,pte,False);
		(*messH)->subPTE = pte;
		
		if (cntl=NewControl(GetMyWindowWindowPtr(win),&view,"",True,0,0,0,kControlUserPaneProc,(uLong)pte))
		{
			INIT_UPP(PetePaneDraw,ControlUserPaneDraw);
			SetControlData(cntl,0,kControlUserPaneDrawProcTag,sizeof(PetePaneDrawUPP),(void*)&PetePaneDrawUPP);
			EmbedControl(cntl,win->topMarginCntl);
		}
	}
	CleanPII(&pi);
	if (err) WarnUser(PETE_ERR,err);
	
	SetPort_(oldPort);
	return(!err);
}


/************************************************************************
 * AddXlateTables - add named translate tables to the current menu
 ************************************************************************/
short AddXlateTables(Boolean isOut,short nowId,Boolean ph,MenuHandle pmh)
{
	Boolean added=CountMenuItems(pmh)==0;
	short i,n;
	Handle h;
	Str31 name;
	ResType type;
	short id;
	short defltId = ph ? 0 : GetPrefLong((isOut ? PREF_OUT_XLATE : PREF_IN_XLATE));
	short item = 0;
	
	n = CountResources('taBL');
	SetResLoad(False);
	for (i=1;i<=n;i++)
		if (h=GetIndResource_('taBL',i))
		{
			GetResInfo(h,&id,&type,name);
			ReleaseResource_(h);
			if (!ResError() && *name && ((id%2)==0)==isOut)
			{
				if (!added)
				{
					added = True;
					AppendMenu(pmh,"\p-");
				}
				MyAppendMenu(pmh,name);
				if (id==defltId) SetItemStyle(pmh,CountMenuItems(pmh),outline);
				if (id==nowId || (id==defltId && nowId==DEFAULT_TABLE))
				{
					item = CountMenuItems(pmh);
					SetItemMark(pmh,CountMenuItems(pmh),checkMark);
				}
			}
		}
	SetResLoad(True);
	return(item);
}

/************************************************************************
 * Menu2TableId - what table id does a menu choice represent?
 ************************************************************************/
Boolean Menu2TableId(TOCHandle tocH,MenuHandle pmh,short item, short *tableId)
{
	short newId;
	short mark=0;
	ResType type;
	Str31 name;
	Handle h;
	Boolean isOut = (*tocH)->which == OUT;
	Boolean makeDefault = 0!=(CurrentModifiers()&shiftKey);

	if (makeDefault) GetItemStyle(pmh,item,(Style*)&mark);
	else GetItemMark(pmh,item,&mark);
	if (mark)
		newId = NO_TABLE;
	else
	{
		MyGetItem(pmh,item,name);
		SetResLoad(False);
		if (h=GetNamedResource('taBL',name))
		{
			GetResInfo(h,&newId,&type,name);
			if (ResError()) newId = *tableId;	/* do nothing */
			ReleaseResource_(h);
		}
		SetResLoad(True);
	}
	if (makeDefault)
	{
		NumToString(newId,name);
		SetPref((isOut?PREF_OUT_XLATE:PREF_IN_XLATE),name);
		return(False);
	}
	else
	{
		*tableId = newId;
		return(True);
	}
}

/************************************************************************
 * SetSubject - set the subject in a message summary
 ************************************************************************/
void SetSubject(TOCHandle tocH,short sumNum,PStr sub)
{
	Str63 oldSubj;
	Str255 title;
	MessHandle messH = (*tocH)->sums[sumNum].messH;
	
	PSCopy(oldSubj,(*tocH)->sums[sumNum].subj);
	if (!EqualString(oldSubj,sub,True,True))
	{
		PSCopy((*tocH)->sums[sumNum].subj,sub);
		InvalSum(tocH,sumNum);
#ifdef NEVER
		CalcSumLengths(tocH,sumNum);
#endif
		TOCSetDirty(tocH,true);
		if (messH)
		{
			MakeMessTitle(title,tocH,sumNum,True);
			SetWTitle_(GetMyWindowWindowPtr((*messH)->win),title);
			if ((*messH)->subPTE)
			{
				if(MessFlagIsSet(messH,FLAG_UTF8) && HasUnicode())
					PeteSetIntlText((*messH)->subPTE,sub+1,-1,*sub,nil,UTF8_ENCODING);
				else
					PeteSetTextPtr((*messH)->subPTE,sub+1,*sub);
			}
		}
		SearchUpdateSum(tocH, sumNum, tocH, (*tocH)->sums[sumNum].serialNum, false, false);	//	Notify search window
	}
}

/************************************************************************
 * SetSender - set the sender in a message summary
 ************************************************************************/
void SetSender(TOCHandle tocH,short sumNum,PStr sender)
{
	Str63 oldSender;
	Str255 title;
	MessHandle messH = (*tocH)->sums[sumNum].messH;
	
	PCopy(oldSender,(*tocH)->sums[sumNum].from);
	if (!EqualString(oldSender,sender,True,True))
	{
		PCopy((*tocH)->sums[sumNum].from,sender);
		InvalSum(tocH,sumNum);
#ifdef NEVER
		CalcSumLengths(tocH,sumNum);
#endif
		TOCSetDirty(tocH,true);
		if (messH)
		{
			MakeMessTitle(title,tocH,sumNum,True);
			SetWTitle_(GetMyWindowWindowPtr((*messH)->win),title);
		}
	}
}

/************************************************************************
 * SetFlag - set one of the message flags
 ************************************************************************/
void SetFlag(TOCHandle tocH,short sumNum,long flag,Boolean on)
{
	if (on)
		(*tocH)->sums[sumNum].flags |= flag;
	else
		(*tocH)->sums[sumNum].flags &= ~flag;
	if ((*tocH)->sums[sumNum].messH)
		InvalTopMargin((*(*tocH)->sums[sumNum].messH)->win);
	TOCSetDirty(tocH,true);
}

/************************************************************************
 * SetOpt - set one of the message options
 ************************************************************************/
void SetOpt(TOCHandle tocH,short sumNum,long flag,Boolean on)
{
	if (on)
		(*tocH)->sums[sumNum].opts |= flag;
	else
		(*tocH)->sums[sumNum].opts &= ~flag;
	TOCSetDirty(tocH,true);
}


#ifdef TWO

/************************************************************************
 * OpenSelectedAtt - open selected attachments (or tell if attachments are selected)
 ************************************************************************/
Boolean AttIsSelected(MyWindowPtr win,PETEHandle pte,long startWith,long endWith,short what,long *aStart, long *aEnd)
{
	Handle textH;
	UPtr begin, end;						/* beginning and end of all text */
	UPtr sBegin, sEnd;					/* beginning and end of selected bit */
	UPtr lBegin, lEnd;					/* beginning and end of current line */
	Str255 line;
	long selStart, selEnd;
	Byte state;
	Boolean found = false;
	FSSpec spec;
	Boolean colorThem = 0!=(what&attColor);
	Boolean selectThem = 0!=(what&attSelect);
	Boolean openThem = 0!=(what&attOpen);
	Boolean finderSelect = 0!=(what&attFinder);
	Boolean printThem = 0!=(what&attPrint);
	
	if (!PeteIsValid(pte) && win) pte = win->pte;
	if (!PeteIsValid(pte)) return(False);
	if (PeteIsValid(pte) && !win) win = (*PeteExtra(pte))->win;
	
	if (PeteGetTextAndSelection(pte,&textH,&selStart,&selEnd)) return(False);
	if (!textH || !GetHandleSize(textH)) return(False);
	state = HGetState(textH);
	
	/*
	 * set initial values
	 */
	begin = LDRef(textH);
	end = begin + GetHandleSize(textH);
	sBegin = begin + ((startWith<0)?selStart:startWith);
	sEnd = begin + ((endWith<0)?selEnd:endWith);
	sBegin = MAX(sBegin,begin);
	if (sBegin>=end) goto done;
	sEnd = MIN(end,sEnd);
	if (sEnd<=begin) goto done;
	
	/*
	 * expand "selection" to whole lines
	 */
	while (sBegin>begin && sBegin[-1]!='\015') sBegin--;
	if (selStart!=selEnd && sEnd[-1]=='\015') sEnd--;
	else while (sEnd<end && sEnd[0]!='\015') sEnd++;
	
	/*
	 * examine each line
	 */
	for (lBegin=sBegin;lBegin<sEnd;lBegin=lEnd+1)
	{
		for (lEnd=lBegin;lEnd<sEnd && lEnd[0]!='\015';lEnd++);
		MakePStr(line,lBegin,lEnd-lBegin);
		if (!PeteIsExcerptAt(pte,lBegin-begin+1) && !AttLine2Spec(line,&spec,openThem))
		{
			found = True;
			if (colorThem)
			{
#ifdef ATT_ICONS
				MyWindowPtr	pteWin = (*PeteExtra(pte))->win;
				WindowPtr		pteWinWP = GetMyWindowWindowPtr (pteWin);
				TOCHandle	tocH;
				
				if (GetWindowKind(pteWinWP)==MESS_WIN ||
					 (GetWindowKind(pteWinWP)==MBOX_WIN && 
					 	//	Don't display attachment icons in search windows unless it's in the preview pane
					 	(!IsSearchWindow(pteWinWP) || ((tocH=(TOCHandle)GetMyWindowPrivateData(pteWin)) && (*tocH)->previewPTE==pte))))
					PeteFileGraphicRange(pte,lBegin-begin,lEnd-begin,&spec,fgAttachment+fgDontCopyToClip+(PrefIsSet(PREF_NO_INLINE)?0:fgDisplayInline));
				else
#endif
				URLStyle(pte,lBegin-begin,lEnd-begin,False);
			}
			if (selectThem)
			{
				PeteSelect(nil,pte,lBegin-begin,lEnd-begin);
				PeteSetDirty(pte);
			}
			if (openThem||printThem) OpenAttLine(pte,line,finderSelect,printThem);
			if (aStart) *aStart = lBegin-begin;
			if (aEnd) *aEnd = lEnd-begin;
		}
	}
done:
	HSetState(textH,state);
	return(found);
}

/************************************************************************
 * IsAttLine - is a line an attachment line?
 ************************************************************************/
Boolean IsAttLine(PStr line,Boolean wantToOpen)
{
	return (!AttLine2Spec(line,nil,wantToOpen));
}

/************************************************************************
 * OpenAttLine - open an attachment described by a line
 ************************************************************************/
OSErr OpenAttLine(PETEHandle pte,PStr line,Boolean finderSelect,Boolean printIt)
{
	short err = noErr;
	FSSpec spec;
	
	if (!(err=AttLine2Spec(line,&spec,True)))
	{
#ifdef	IMAP
		// If this is an IMAP attachment, download it.
		if (IsIMAPAttachmentStub(&spec))
		{
			// fetch the attachment, wait for it to be downloaded.  
			// spec will point to the downloaded attachment.
			err = FetchIMAPAttachment(pte,&spec,true);
		}
#endif
		if (err==noErr) err = OpenOtherDoc(&spec,finderSelect,printIt,nil);
	}
	
	return(err);
}

/************************************************************************
 * AttLine2Spec - get an FSSpec from an attachment line
 *	Heuristic
 *	 - must end in 8 hex digits in []'s
 *	 - must have at least two colons
 *	 - text between first two colons must be volume name
 ************************************************************************/
OSErr AttLine2Spec(PStr line,FSSpecPtr spec,Boolean wantToOpen)
{
	Str31 colostomy;			/* bad joke, I know */
	Str63 fName;
	PStr colon1, colon2;
	UPtr spot;
	long fid=0;
	short dig=8;
	short err;
	Boolean isFolder = nil!=PFindSub("\p (fldr/macs) (",line);	//crude check for our folder creator
	
	TrimWhite(line);
	
	if (!IsWordChar[line[1]]) return fnfErr;	// don't do quoted attachment lines
	
	/*
	 * pick out the fid
	 */
	if (line[*line] != ']' && line[*line]!=')') return(fnfErr);
	if (*line<10 || line[*line-9]!='[' && line[*line-9]!='(') return(fnfErr);
	for (colon1=line+*line-8;dig--;colon1++)
		if ('0'<=*colon1 && *colon1<='9')
			fid = (fid<<4) | (*colon1-'0');
		else if ('a'<=*colon1 && *colon1<='f')
			fid = (fid<<4) | (10+*colon1-'a');
		else if ('A'<=*colon1 && *colon1<='F')
			fid = (fid<<4) | (10+*colon1-'A');
		else return(fnfErr);
		
	/*
	 * pick out the volume name
	 */
	colon1 = strchr(line+1,':'); if (!colon1) return(fnfErr);
	colon2 = strchr(colon1+2,':'); if (!colon2) return(fnfErr);
		
	if (spec)
	{
		MakePStr(colostomy,colon1+2,colon2-colon1-1);
		MakePStr(fName,colon2+1,*line-(colon2-line)-23);
		if (*fName<1 || *fName>31) err = fnfErr;
		else
		{
			if (isFolder)
			{
				spec->vRefNum = GetMyVR(colostomy);
				spec->parID = fid;
				err = ParentSpec(spec,spec);
				if (!err) err = GetDirName(nil,spec->vRefNum,fid,spec->name);
			}
			else
				err = FSResolveFID(GetMyVR(colostomy),fid,spec);

			if (err || !(spot=PPtrFindSub(fName,spec->name+1,*spec->name)) || spot!=spec->name+1)
			{
#ifdef	IMAP
				//
				// The names of IMAP attachments could get macified once downloaded.
				// if the name the fileID resolved to is a Mac2OtherName()ed fName, then call it a match.
				//
				
				Str31 otherName;
				
				// check only if FSResolveFID succeeded.
				if (err == noErr) 
					Mac2OtherName(otherName, spec->name);
				else 
					otherName[0] = 0;
					
				// If fName and the Mac2OtherName match, we've found the file.  
				if ((otherName[0]==0) || !(spot=PPtrFindSub(fName,otherName+1,*otherName)) || spot!=otherName+1)
				{
					// otherwise, call it a match only if a file with the same name exists in the attachments folder.
#endif
					GetAttFolderSpec(spec);
					PCopy(spec->name,fName);
					err = FSpExists(spec);
					
					if (err && IsIMAPPers(CurPers))
					if (!(err = GetIMAPAttachFolder(spec)))
					{
						PCopy(spec->name,fName);
						err = FSpExists(spec);
					}
					
					// the increasing prevalence of long filenames means we increasingly
					// cannot find attachments after a disk move.  So, if this is a mangled
					// name, look for the non-hex part as a prefix of a filename in the attachments folder
					if (err && PrefIsSet(PREF_PROMISCUOUS_REATTACH))
					{
						Str31 stubName;
						Str31 stubExtension;
						
						// split into fershlugginer extension
						SplitPerfectlyGoodFilenameIntoNameAndQuoteExtensionUnquote(fName,stubName,stubExtension,31);
						if (!LopPoundHexDigits(stubName))
						{
							
							// Ok, now we have determined that the filename is of the form:
							// stubName # hex-digits stubExtension
							CInfoPBRec hfi;
							Str31 foundFileName;
							Str31 possibleFileName;
							Boolean lastOneMatched = false;
							
							Zero(hfi);
							hfi.hFileInfo.ioNamePtr = possibleFileName;

							while (!DirIterate(spec->vRefNum,spec->parID,&hfi))
							{
								Str31 possibleStub, possibleExtension;
								
								SplitPerfectlyGoodFilenameIntoNameAndQuoteExtensionUnquote(possibleFileName,possibleStub,possibleExtension,31);
								if (!(err = LopPoundHexDigits(possibleStub)))	// a shortened filename
								if (StringSame(possibleExtension,stubExtension))
								if (!striscmp(possibleStub+1,stubName+1) && *possibleStub > 10)
								{
									// we have a match!
									if (lastOneMatched)
									{
										// Sadness.  We match more than one file.  
										err = dupFNErr;
										break;
									}
									else
									{
										PCopy(foundFileName,possibleFileName);
										lastOneMatched = true;
										continue;
									}
								}
								
								// we only get here if the current possible file does NOT match.
								// if that's so, and the *last* one matched, then there is one and only
								// one match, and we shout Bingo!, losing our dentures in the process
								if (lastOneMatched)
								{
									PCopy(spec->name,foundFileName);
									err = noErr;	// hurray!
									break;
								}
							}
						}
					}
				}
#ifdef	IMAP
			}
#endif
		}
		if (wantToOpen && err) WarnUser(ATTACH_GONE,err);
		return(err);
	}
	else return(colon2-colon1>1 && colon2-colon1<33 ? noErr : fnfErr);
}	

/************************************************************************
 * LopPoundHexDigits - strip the # and hex digits from a shortened filename
 * returns fnfErr if no # or followed by non-hex digits
 * returns dupFNErr if remaining filename is very short.
 * null-terminates the string for convenience' sake
 ************************************************************************/
OSErr LopPoundHexDigits(PStr fileName)
{
	Str31 hexDigits;
	PStr poundSign = PIndex(fileName,'#');
	short i;
	
	if (!poundSign) return fnfErr;
	MakePStr(hexDigits,poundSign+1,*fileName-(poundSign-fileName+2));
	if (poundSign-fileName < 10) return dupFNErr;
	for (i=1;i<*hexDigits;i++)
		if (!IsHexDig(hexDigits[i]))
			return fnfErr;
	*fileName = poundSign-fileName-1;
	PTerminate(fileName);
	return noErr;
}

/************************************************************************
 * RelLine2Spec - get an FSSpec from a "related" line
 *	 - begin with "related:"
 *	 - must have at least two colons
 *	 - text between first two colons must be volume name
 ************************************************************************/
OSErr RelLine2Spec(PStr line,FSSpecPtr spec,uLong *cid, uLong *relURL, uLong *absURL)
{
	Str255 token;
	Str31 name;
	UPtr spot = line+1;
	Byte colon[2];
	short vRef;
	uLong fid;
	OSErr err;
	
	colon[0] = ':';
	colon[1] = 0;
	
	// related
	if (!PToken(line,token,&spot,colon)) return(fnfErr);
	if (!EqualStrRes(token,MIME_RELATED)) return(fnfErr);
	
	// space
	if (!PToken(line,token,&spot,colon)) return(fnfErr);
	
	// volume name
	if (!PToken(line,token,&spot,colon)) return(fnfErr);
	vRef = GetMyVR(token);
	if (vRef==REAL_BIG) return(fnfErr);
	
	// filename
	if (!PToken(line,token,&spot,colon)) return(fnfErr);
	PSCopy(name,token);
	FixURLString(name);
		
	// fid
	if (!PToken(line,token,&spot,colon)) return(fnfErr);
	if (*token!=8) return(fnfErr);
	Hex2Bytes(token+1,8,(void*)&fid);
		
	// cid
	if (!PToken(line,token,&spot,colon)) return(fnfErr);
	if (*token!=8) return(fnfErr);
	if (cid) Hex2Bytes(token+1,8,(void*)cid);
		
	// rel
	if (!PToken(line,token,&spot,colon)) return(fnfErr);
	if (*token!=8) return(fnfErr);
	if (relURL) Hex2Bytes(token+1,8,(void*)relURL);
		
	// abs
	if (!PToken(line,token,&spot,colon)) return(fnfErr);
	if (*token!=8) return(fnfErr);
	if (absURL) Hex2Bytes(token+1,8,(void*)absURL);


	if (spec)
	{
		err = FSResolveFID(vRef,fid,spec);
		if (err)
		{
			SubFolderSpec(PARTS_FOLDER,spec);
			PSCopy(spec->name,name);
			err = FSpExists(spec);
			if (err==fnfErr)
			{
				FixURLString(name);
				if (*name!=*spec->name)
					err = FSpExists(spec);
			}
		}
		return(err);
	}
	else return(noErr);
}	
#endif

#pragma segment Balloon
/************************************************************************
 * MessHelp - help for the message window
 ************************************************************************/
void MessHelp(MyWindowPtr win,Point mouse)
{
	Rect subRect,bodRect,priorRect,r;
	MessHandle messH = Win2MessH(win);
	Rect blahRect;
	Rect pencilRect;
	Rect fixedRect;
	PETEDocInfo info;
	ControlHandle cntl,ctlGetGraphics;

	PETEGetDocInfo(PETE,TheBody,&info);
	bodRect = info.docRect;
	PETEGetDocInfo(PETE,(*messH)->subPTE,&info);
	subRect = info.docRect;
	
	
	if (PtInRect(mouse,&bodRect))
		MyBalloon(&bodRect,90,0,MESS_HELP,0,nil);
	else if (win->topMargin)
	{
		GetPriorityRect(win,&priorRect);
		GetBlahRect(win,1,&blahRect);
		GetBlahRect(win,0,&pencilRect);
		if (!FontIsFixed) GetBlahRect(win,2,&fixedRect);

		if (PtInRect(mouse,&subRect))
			MyBalloon(&subRect,90,0,SUB_EDIT_HELP,0,nil);
		else if (!FontIsFixed && PtInRect(mouse,&fixedRect) &&
						 (cntl=FindControlByRefCon(win,mcFixed)))
			MyBalloon(&fixedRect,90,0,
							 GetControlValue(cntl)?MESS_FIXED_HELP:NO_MESS_FIXED_HELP,
							 0,nil);
		else if (PtInRect(mouse,&pencilRect))
			MyBalloon(&pencilRect,90,0,
							 MessOptIsSet(Win2MessH(win),OPT_WRITE)?MESS_WRITE_HELP:NO_MESS_WRITE_HELP,
							 0,nil);
		else if (PtInRect(mouse,&blahRect))
			MyBalloon(&blahRect,90,0,
							 MessFlagIsSet(Win2MessH(win),FLAG_SHOW_ALL)?SHOW_ALL_HELP:NO_SHOW_ALL_HELP,
							 0,nil);
		else if (PtInRect(mouse,&priorRect))
			PriorMenuHelp(win,&priorRect);
		else if ((ctlGetGraphics = FindControlByRefCon(win,mcGetGraphics)) && 
			IsControlVisible(ctlGetGraphics) &&
			PtInControl(mouse,ctlGetGraphics))
		{
			Rect	getGraphicsRect;
			
			GetControlBounds(ctlGetGraphics,&getGraphicsRect);
			MyBalloon(&getGraphicsRect,90,0,
							 IsControlActive(ctlGetGraphics)?GET_GRAPHICS_HELP:NO_GET_GRAPHICS_HELP,
							 0,nil);		
		}
#ifdef TWO
		else
		{
			GetServerRect(win,3,&r);
			if (PtInRect(mouse,&r)) MyBalloon(&r,90,0,MESS_DRAG_HELP,0,nil);
			else
			{
				r = win->contR;
				r.bottom = win->topMargin;
				r.top = GetRLong(COMP_TOP_MARGIN);
				if (r.bottom>r.top && PtInRect(mouse,&r) && MessOptIsSet(messH,OPT_RECEIPT))
					MyBalloon(&r,90,0,MESS_NOTIFY_HELP,0,nil);
				else if ((*messH)->hasDelIcon)
				{
					GetServerRect(win,1,&r);
					if (PtInRect(mouse,&r))
					{
						if ((*messH)->hasFetchIcon)
							MyBalloon(&r,90,0,MESS_FETCH_HELP+(IdIsOnPOPD(PERS_POPD_TYPE(MESS_TO_PPERS(messH)),FETCH_ID,SumOf(messH)->uidHash)?0:1),0,nil);
					}
					else
					{
						GetServerRect(win,2,&r);
						if (PtInRect(mouse,&r))
							MyBalloon(&r,90,0,MESS_DELETE_HELP+(IdIsOnPOPD(PERS_POPD_TYPE(MESS_TO_PPERS(messH)),DELETE_ID,SumOf(messH)->uidHash)?0:1),0,nil);
						else
						{
							GetServerRect(win,0,&r);
							if (PtInRect(mouse,&r))
								MyBalloon(&r,90,0,MESS_SERVER_HELP,0,nil);
						}
					}
				}
			}
		}
#endif
	}
}

/************************************************************************
 * PriorMenuHelp - help for the priority menu
 ************************************************************************/
void PriorMenuHelp(MyWindowPtr win,Rect *priorRect)
{
	Str255 s1,s2;
	short p = Prior2Display((SumOf(Win2MessH(win)))->priority);

	if (!HMIsBalloon())
	{
		GetRString(s1,PRIOR_MENU_HELP);
		GetRString(s2,PRIOR_STRN+5+p);
		PCat(s1,s2);
		MyBalloon(priorRect,90,0,0,0,s1);
	}
}

#pragma segment MessWin
/**********************************************************************
 * SaveMess - save a message into its own mailbox
 **********************************************************************/
Boolean SaveMess(MyWindowPtr win)
{
	MessHandle messH = Win2MessH(win);
	MSumPtr oldSum, newSum;
	TOCHandle tocH = (*messH)->tocH;
	long fromLen;
	Handle text=MessText(messH);
	HeadSpec hSpec;
	Accumulator enriched;
	OSErr err = noErr;
	Boolean richSave = False;
	Str255 title;
	Boolean blahBlah = MessFlagIsSet(messH,FLAG_SHOW_ALL);
	
	/*
	 * rich text?
	 */
	if (!blahBlah)
	{
		SetMessRich(messH);
		Zero(enriched);
		if (MessIsRich(messH))
		{
			if (!(err=AccuInit(&enriched)))
			{
				CompHeadFind(messH,0,&hSpec);
				if (!(*messH)->extras.offset || !(err=AccuAddHandle(&enriched,(*messH)->extras.data)))
				if (!(err=AccuAddFromHandle(&enriched,text,0,hSpec.value)))
				{
					if (MessOptIsSet(messH,OPT_HTML))
					{
						if (!(err=HTMLPreamble(&enriched,PCopy(title,SumOf(messH)->subj),0,True)))
						{
							NumToString(SumOf(messH)->msgIdHash,title);
							if (!(err=BuildHTML(&enriched,TheBody,nil,GetHandleSize(text),hSpec.value,nil,nil,1,title,nil,nil)))
								err=HTMLPostamble(&enriched,True);
						}
					}
					else
					{
						err=BuildEnriched(&enriched,TheBody,nil,PETEGetTextLen(PETE,TheBody),hSpec.value,nil,True);
					}
					if (!err)
					{
						AccuTrim(&enriched);
						err = SaveTextAsMessage(nil,enriched.data,(*messH)->tocH,&fromLen);
						ZapHandle(enriched.data);
						if (err) return(False);
						richSave = True;
					}
				}
			}
		}
	}
	
	if (err)
	{
		WarnUser(CANT_SAVE_RICH,err);
		ZapHandle(enriched.data);
		ClearMessFlag(messH,FLAG_RICH);
		ClearMessOpt(messH,OPT_HTML);
	}
	
	/*
	 * save plain text to the end of the mailbox
	 */
	if (!richSave)
		if (SaveTextAsMessage(!blahBlah && (*messH)->extras.offset ? (*messH)->extras.data:nil,text,(*messH)->tocH,&fromLen)) return(False);
		
	/*
	 * copy the offset parameters
	 */
	oldSum = &(*tocH)->sums[(*messH)->sumNum];
	newSum = &(*tocH)->sums[(*tocH)->count-1];
	
	(*tocH)->usedK -= oldSum->length/1024;	/* we're going to waste the old one */
	(*tocH)->updateBoxSizes = true;
	oldSum->offset = newSum->offset;
	oldSum->length = newSum->length;
	oldSum->bodyOffset = newSum->bodyOffset;
	if (newSum->seconds && !(oldSum->flags&FLAG_OUT))
	{
		oldSum->seconds = newSum->seconds;
		oldSum->origZone = newSum->origZone;
	}
	
	// outgoing IMAP messages have the recipient stored in the From: field of the summary.  
	// Save it.
	if ((*tocH)->imapTOC && (oldSum->opts&OPT_IMAP_SENT))
	{
		if (*oldSum->from) PCopy(newSum->from,oldSum->from);
	}
	else 
	{
		if (*newSum->from) PCopy(oldSum->from,newSum->from);
	}
	
	InvalSum(tocH,(*messH)->sumNum);
	// retitle window
	if ((*messH)->win)
	{
		MakeMessTitle(title,tocH,(*messH)->sumNum,true);
		SetWTitle_(GetMyWindowWindowPtr((*messH)->win),title);
	}
	
	
	/*
	 * shrink the .toc
	 */
	(*tocH)->count--;
	SetHandleBig_(tocH,GetHandleSize_(tocH)-sizeof(MSumType));
	
	/*
	 * reset this to the right stuff
	 */
	(*messH)->weeded = fromLen + (blahBlah ? 0 : (*messH)->extras.offset);
	
	/*
	 * rescan for URL's, quotes
	 */
	PeteSetURLRescan(TheBody,0);
	
	/*
	 * not dirty any more
	 */
	PeteCleanList(win->pteList);
	win->isDirty = False;
	
	/*
	 * cache is toast
	 */
	ZapHandle(SumOf(messH)->cache);
	SetMessOpt(messH,OPT_EDITED);
	ZapHandle((*messH)->etlFiles);	// we've recorded them permanently now
	
	// let the preview pane know
	if ((*tocH)->previewID==SumOf(messH)->serialNum) (*tocH)->previewID = 0;
	
	return(True);
}

/**********************************************************************
 * ShowMessageSeparator - show the message separator
 **********************************************************************/
void ShowMessageSeparator(PETEHandle pte,Boolean center)
{
	long body = BodyOffset(PeteText(pte));
	
	PeteSelect(nil,pte,body,body);
	PeteCalcOn(pte);
	if (center)
	{
		PeteScroll(pte,0,0);
		PeteScroll(pte,0,pseCenterSelection);
	}
	else
	{
		PeteScroll(pte,0,0x7fff);
		PeteScroll(pte,0,pseSelection);
	}
}

/**********************************************************************
 * MessMenu - handle menu choices for a message window
 **********************************************************************/
Boolean MessMenu(MyWindowPtr win,int menu,int item,short modifiers)
{
	MessHandle messH = (MessHandle)GetMyWindowPrivateData(win);
	TOCHandle tocH = (*messH)->tocH;
	int sumNum = (*messH)->sumNum;
	Boolean result = False;
	short tableId;
	Boolean turbo = False;
	uLong ezOpenSerialNum;
	TextAddrHandle addr=nil;
	
	switch (menu)
	{
		case FILE_MENU:
			switch(item)
			{
				case FILE_PRINT_ITEM:
				case FILE_PRINT_ONE_ITEM:
					MessFocus(messH,TheBody);
					if ((modifiers&shiftKey) && AttIsSelected(win,win->pte,-1,-1,attOpen+attPrint,nil,nil))
						;// done
					else
						PrintOneMessage((*messH)->win,(modifiers&shiftKey)!=0,item==FILE_PRINT_ONE_ITEM);
					result = True;
					break;
				case FILE_SAVE_ITEM:
					if (win->isDirty)
					{
						if ((*messH)->subPTE && PeteIsDirty((*messH)->subPTE)) MessSaveSub(messH);
						if ((*messH)->subPTE && PeteIsDirty((*messH)->bodyPTE)) SaveMess(win);
						PeteCleanList(win->pteList);
						win->isDirty = False;
					}
					result = True;
					break;
				case FILE_BROWSE_ITEM:
					ExportHTML(messH);
					result = True;
					break;
				case FILE_SAVE_AS_ITEM:
					SaveMessageAs(messH);
					result = True;
					break;
			}
			break;
		case SERVER_HIER_MENU:
#ifdef	IMAP
			ServerMenuChoice((*messH)->tocH,(*messH)->sumNum,item, (modifiers&shiftKey)!=0);
#else
			ServerMenuChoice((*messH)->tocH,(*messH)->sumNum,item);
#endif
			result = True;
			break;
		case EDIT_MENU:
			switch (item) {
#ifdef SPEECH_ENABLED
				case EDIT_SPEAK_ITEM:
					if (!(modifiers & shiftKey)) {
						(void) SpeakMessage (nil, (*messH)->tocH, (*messH)->sumNum, speakSender | speakSubject | speakBody, false);
						return (true);
					}
					break;
#endif
				default:
					return(False);
					break;
			}
			break;
		case MESSAGE_MENU:
			MessFocus(messH,TheBody);
			switch (item)
			{
				case MESSAGE_REPLY_ITEM:
					{
						Boolean all, quote, self;
						ReplyDefaults(modifiers,&all,&self,&quote);
						DoReplyMessage(win,all,self,quote,True,0,True,True,True);
					}
					result = True;
					break;
				case MESSAGE_REDISTRIBUTE_ITEM:
					DoRedistributeMessage(win,0,PrefIsSetOrNot(PREF_TURBO_REDIRECT,modifiers,optionKey),!(modifiers&shiftKey),True);
					result = True;
					break;
				case MESSAGE_FORWARD_ITEM:
					DoForwardMessage(win,0,True);
					result = True;
					break;
				case MESSAGE_SALVAGE_ITEM:
					DoSalvageMessage(win,False);
					result = True;
					break;
				case MESSAGE_JUNK_ITEM:
				case MESSAGE_NOTJUNK_ITEM:
					Junk(tocH,sumNum,item==MESSAGE_JUNK_ITEM,true);
					break;
				case MESSAGE_DELETE_ITEM:
					EzOpen((*messH)->openedFromTocH,-1,(*messH)->openedFromSerialNum,modifiers,True,True);
					NoSaves = !MessOptIsSet(Win2MessH(win),OPT_WRITE);
					if (CloseMyWindow(GetMyWindowWindowPtr(win)))
					{
#ifdef TWO
						AddXfUndo(tocH,GetTrashTOC(),sumNum);
#endif
						DeleteMessage(tocH,sumNum,(modifiers&(optionKey|shiftKey))==(optionKey|shiftKey));
					}
					NoSaves = False;
					result = True;
					break;
			}
			break;
		case REPLY_WITH_HIER_MENU:
			//Stationery - no support for this in Light
			if (HasFeature (featureStationery)) {
				Boolean all, quote, self;
				ReplyDefaults(modifiers,&all,&self,&quote);
				DoReplyMessage(win,all,self,quote,True,item,True,True,True);
			}
			result = True;
			break;
		case FORWARD_TO_HIER_MENU:
			DoForwardMessage(win,(addr=MenuItem2Handle(menu,item)),True);
			result = True;
			break;
		case REDIST_TO_HIER_MENU:
#ifdef TWO
			turbo = PrefIsSetOrNot(PREF_TURBO_REDIRECT,modifiers,optionKey);
			if (turbo) EzOpen(tocH,sumNum,0,modifiers,True,True);
#endif
			DoRedistributeMessage(win,(addr=MenuItem2Handle(menu,item)),turbo,!(modifiers&shiftKey),True);
			result = True;
			ZapHandle(addr);
			break;
		case TABLE_HIER_MENU:
			if (Menu2TableId(tocH,GetMHandle(TABLE_HIER_MENU),item,&tableId))
				SetMessTable(tocH,sumNum,tableId);
			result = True;
			break;
		case STATE_HIER_MENU:
			SetState(tocH,sumNum,Item2Status(item));
			result = True;
			break;
		case LABEL_HIER_MENU:
			item = Menu2Label(item);
			SetSumColor(tocH,sumNum,item);
			result = True;
			break;
		case SPECIAL_MENU:
			switch(AdjustSpecialMenuSelection(item))
			{
				case SPECIAL_MAKE_NICK_ITEM:
					if (modifiers&shiftKey)
						MakeNickFromSelection(win);
					else
						MakeMessNick(win,modifiers);
					result = True;
					break;
				case SPECIAL_MAKEFILTER_ITEM:
					DoMakeFilter(win);
					break;
#ifdef TWO
				case SPECIAL_FILTER_ITEM:
					ezOpenSerialNum = (*messH)->ezOpenSerialNum;
					FilterMessage(flkManual,tocH,sumNum);
					if (FrontWindow_()!=GetMyWindowWindowPtr(win) && (*tocH)->count)
						if (ezOpenSerialNum)
							EzOpen(tocH,sumNum,ezOpenSerialNum,modifiers,False,False);
						else
							BoxSelectAfter((*tocH)->win,sumNum);
					result = True;
					break;
#endif
			}
			break;
		case PRIOR_HIER_MENU:
			SetPriority(tocH,sumNum,NewPrior(item,(*tocH)->sums[sumNum].priority));
			result = True;
			break;
		case PERS_HIER_MENU:
			//No Personalities menu in Light
			if (HasFeature (featureMultiplePersonalities)) {
				Str63 name;
				
				SetPers(tocH,sumNum,FindPersByName(MyGetItem(GetMHandle(menu),item,name)),True);
				return(True);
			}
			break;
#ifdef DEBUG
		case DEBUG_MENU:
			if(item == dbTestSpooler)
			{
				SpoolMessage(messH, nil, 0);
				return(True);
			}
			break;
#endif
		default:
			if ((*messH)->openedFromTocH != (*messH)->tocH)
				sumNum = FindSumBySerialNum((*messH)->openedFromTocH,(*messH)->openedFromSerialNum);
			result = TransferMenuChoice(menu,item,(*messH)->openedFromTocH,sumNum,modifiers,False);
			break;
	}
	ZapHandle(addr);
	return(result);
}

/************************************************************************
 * Fcc - add a mailbox to 
 ************************************************************************/
void Fcc(MessHandle messH,FSSpecPtr box)
{
	Str255 scratch;
	HeadSpec hs;
	long start;
	Str255 path;
	
	//Folder Carbon Copy 	No FCC in Light
	if (!HasFeature (featureFcc))
		return;
		
	UseFeature (featureFcc);

	if (Box2Path(box,path)) PCopy(path,box->name);
	
	*scratch = 0;
	PCatC(scratch,'"');
	PCatR(scratch,FCC_PREFIX);
	PCat(scratch,path);
	PCatC(scratch,'"');
	SetPort(GetMyWindowCGrafPtr((*messH)->win));
	if (CompHeadFind(messH,BCC_HEAD,&hs))
	{
		PetePrepareUndo(TheBody,peCantUndo,hs.stop,hs.stop,&start,nil);
		InsertCommaIfNeedBe(TheBody,&hs);
		CompHeadAppendStr(TheBody,&hs,scratch);
		CompHeadFind(messH,BCC_HEAD,&hs);
		PeteSelect(nil,TheBody,hs.stop,hs.stop);
		PeteFinishUndo(TheBody,peUndoPaste,start,hs.stop);
		ClearMessFlag(messH,FLAG_KEEP_COPY);
		CompIBarUpdate(messH);
	}
}

/**********************************************************************
 * EzOpenFind - find next candidate for EzOpen.  If none, return -1
 **********************************************************************/
short EzOpenFind(TOCHandle tocH, short origSum)
{
	short newSum;
	long ez = GetPrefLong(PREF_NO_EZ_OPEN);
	
	if (origSum<(*tocH)->count-1)
	{
		if (ez==1) return(origSum+1);	// say no more
		if ((*tocH)->sums[origSum+1].state==UNREAD)
		{
			if (ez==3 || ez==2) return(origSum+1);	// say no more
			if (ez==4)
			{
				Str63 s1, s2;
				PSCopy(s1,(*tocH)->sums[origSum].subj);
				PSCopy(s2,(*tocH)->sums[origSum+1].subj);
				if (!SubjCompare(s1,s2)) return origSum+1;
			}		
		}
	}
	
	if (ez==2)
		for (newSum=origSum+2;newSum<(*tocH)->count;newSum++)
			if ((*tocH)->sums[newSum].state==UNREAD) return(newSum);
	
	return(-1);
}		

/************************************************************************
 * EzOpen - easy open, if things are right
 ************************************************************************/
void EzOpen(TOCHandle tocH, short sumNum,uLong serialNum,long modifiers,Boolean hideFront, Boolean willDelete)
{
	WindowPtr oldFrontWP = FrontWindow_();
	short newSumNum;
	Boolean oldVis;
	short ez;
	Boolean preview = false;
	
	if ((*tocH)->win)
	{
		if (sumNum < 0)
		{
			sumNum = FindSumBySerialNum(tocH,serialNum);
			if (sumNum < 0)
				return;	//	unable to find message with this serial number
		}
		
		/*
		 * if the frontmost window is our cue, grab its hash value
		 */
		if (hideFront)
		{
			TOCHandle	realTOC;
			short		realSum;

			realTOC = GetRealTOC(tocH,sumNum,&realSum);
			if ((*realTOC)->sums[realSum].messH) 
				serialNum = (*(*realTOC)->sums[realSum].messH)->ezOpenSerialNum;
			else if ((*tocH)->previewPTE && (*tocH)->previewID)
			{
				preview = true;
				serialNum = (*tocH)->ezOpenSerialNum;
			}
		}
		else
			sumNum=FindSumBySerialNum(tocH,serialNum);
		
		/*
		 * does message exist?
		 */
		if (serialNum && (newSumNum=FindSumBySerialNum(tocH,serialNum))>=0 &&
				(*tocH)->sums[newSumNum].state==UNREAD)
			/* we have our man */;
		else
		{
			/*
			 * if we're not hiding the front window, that means it was deleted before
			 * the easy open; back up one
			 */
			if (!hideFront) newSumNum = EzOpenFind(tocH,sumNum-1);
			else newSumNum = EzOpenFind(tocH,sumNum);
		}
		
		/*
		 * did we find one to easy open?
		 */
		if (newSumNum>=0)
		{
			ez = GetPrefLong(PREF_NO_EZ_OPEN);
			if (modifiers & shiftKey) ez = 0;
			SelectBoxRange(tocH,newSumNum,newSumNum,False,-1,-1);
			if (ez)
			{
				if (preview) Preview(tocH,newSumNum);
				else
				{
					MyWindowPtr	oldFront = GetWindowMyWindowPtr (oldFrontWP);
					if (hideFront && oldFront)
					{
						oldVis = oldFront->hideme;
						oldFront->hideme = True;
						UglyHackFrontWindow = oldFrontWP;
					}
					BoxOpen((*tocH)->win);
					if (hideFront && oldFront) oldFront->hideme = oldVis;
					UglyHackFrontWindow = nil;
				}
			}
			BoxCenterSelection((*tocH)->win);
		}
		else
		{
			if (willDelete) newSumNum = sumNum==(*tocH)->count-1 ? sumNum-1 : sumNum+1;
			else newSumNum = sumNum;
			newSumNum = MIN(newSumNum,(*tocH)->count-1);
			newSumNum = MAX(newSumNum,0);
			SelectBoxRange(tocH,newSumNum,newSumNum,False,-1,-1);
		}
	}
}

/************************************************************************
 * SaveMessageAs - save a message in text only form
 ************************************************************************/
void SaveMessageAs(MessHandle messH)
{
	long creator;
	short refN;
	short err;
	Str31 name;
	FSSpec spec;
	Str255	scratch;
	ModalFilterYDUPP filter=nil;
		
	NicknameWatcherFocusChange((**messH).win->pte); /* MJN */

	/*
	 * tickle stdfile
	 */
	creator = DefaultCreator();
	MakeMessFileName((*messH)->tocH,(*messH)->sumNum,spec.name);
#ifdef TWO
	//Stationery - prevent the user from saving a file as stationery
	Stationery = (HasFeature (featureStationery) && (*(*messH)->tocH)->which==OUT && !DateWarning(false)) ? False : 2;	/* 2 is our signal to the filter to hide the item */
#endif
	if (err=SFPutOpen(&spec,creator,'TEXT',&refN,nil,filter,SAVEAS_DLOG,nil,nil,nil))
		return;
	
	/*
	 * do it
	 */
	if (Stationery)
	{
		//	Need to save with different msg ID hash
		TOCHandle	tocH = (*messH)->tocH;
		short		sumNum = (*messH)->sumNum;
		long		saveHash = (*tocH)->sums[sumNum].msgIdHash;
		
		NewMessageId(scratch);
		SetHash(tocH,sumNum,MIDHash(scratch+1,*scratch));		
		err = SaveAsToOpenFile(refN,messH);	
		SetHash(tocH,sumNum,saveHash);		
	}
	else
		err = SaveAsToOpenFile(refN,messH);
	
	/*
	 * close
	 */
	(void) MyFSClose(refN);
	
	/*
	 * report error
	 */
	if (err)
	{
		FileSystemError(COULDNT_SAVEAS,name,err);
		FSpDelete(&spec);
	}
}

/************************************************************************
 * SaveAsToOpenFile - continue the message saving process
 ************************************************************************/
short SaveAsToOpenFile(short refN,MessHandle messH)
{
	OSErr	err;
	unsigned long saveOpts = SumOf(messH)->opts;

	err = SaveAsToOpenFileLo(refN,messH);
	if (Stationery && !err && ((saveOpts&OPT_HAS_SPOOL) != (SumOf(messH)->opts&OPT_HAS_SPOOL)))
	{
		//	The first save didn't save the OPT_HAS_SPOOL flag properly. Save again.
		//	Shameful, but this should happen only the first time a stationery
		//	is saved with graphics.
		SetFPos(refN,fsFromStart,0);
		err = SaveAsToOpenFileLo(refN,messH);
	}
	return err;
}

/************************************************************************
 * SaveAsToOpenFileLo - continue the message saving process
 ************************************************************************/
static short SaveAsToOpenFileLo(short refN,MessHandle messH)
{
	long bytes;
	short err=0;
	UPtr where;
	UHandle text;
	Str63 scratch;
	Boolean para = PrefIsSet(PREF_PARAGRAPHS);
	Boolean exclHead = PrefIsSet(PREF_EXCLUDE_HEADERS) || (SumOf(messH)->flags&FLAG_SUBSEQUENT);
	Boolean isOut = (*(*messH)->tocH)->which==OUT;
	HeadSpec hs;
	Accumulator enriched;
	StackHandle stack = nil;
	
	Zero(enriched);
	
	SetMessRich(messH);
	PETEGetRawText(PETE,TheBody,&text);
	CompHeadFind(messH,0,&hs);
	
#ifdef TWO
	//Stationery - prevent the user from saving a file as stationery
	if (HasFeature (featureStationery) && Stationery==True)
	{
		if (err = SaveStationeryStuff(refN,messH)) return(err);
		para = exclHead = False;
	}
#endif

	/*
	 * build and save date header
	 */
	if (!exclHead && isOut && SumOf(messH)->seconds)
	{
		BuildDateHeader(scratch,SumOf(messH)->seconds);
		PCatC(scratch,'\015');
		FSWriteP(refN,scratch);
	}
	
	where = LDRef(text);
	bytes = GetHandleSize(text);
	if (exclHead)
	{
		where = LDRef(text)+hs.value;
		bytes = hs.stop-hs.value;
		while (bytes > 1 && *where == '\015') {where++;bytes--;}
		while (bytes>1&&where[bytes-1]==where[bytes-2]&&where[bytes-1]=='\015')
			bytes--;
	}

	//Stationery - prevent the user from saving a file as stationery	
	if (HasFeature (featureStationery) && Stationery)
	{
		if (MessIsRich(messH) && !(err=AccuInit(&enriched)))
		{
			AccuAddPtr(&enriched,where,hs.start);
			AccuAddChar(&enriched,'\015');
			if (MessOptIsSet(messH,OPT_HTML))
			{
				if (!(err=StackInit(sizeof(FSSpec),&stack)))
				if (!(err=HTMLPreamble(&enriched,PCopy(scratch,SumOf(messH)->subj),0,True)))
				if (!(err=BuildHTML(&enriched,TheBody,nil,PETEGetTextLen(PETE,TheBody),hs.value,nil,nil,1,CompGetMID(messH,scratch),nil,nil)))
					err=HTMLPostamble(&enriched,True);
			}
			else
				err = BuildEnriched(&enriched,TheBody,nil,PETEGetTextLen(PETE,TheBody),hs.value,nil,True);
			ZapHandle(stack);
			if (!err)
			{
				AccuTrim(&enriched);
				text = enriched.data;
				where = LDRef(enriched.data);
				bytes = enriched.offset;
			}
		}
	}

	if (!err && !para)
	{
		err = AWrite(refN,&bytes,where);
		EnsureNewline(refN);
		UL(text);
	}
	else
	{
		err = UnwrapSave(where,bytes,0,refN);
		UL(text);
	}
	if (!err) BeenThereDoneThat((*messH)->tocH,(*messH)->sumNum);
	AccuZap(enriched);
	return(err);
}

/************************************************************************
 * UnwrapSave - save and unwrap a message
 ************************************************************************/
#define flushBChars() do {																			\
	if (bytes=bSpot-buffer) 																			\
	{ 																														\
		 err = refN ? AWrite(refN,&bytes,buffer) : 								\
		 (WrapHandle ? PtrPlusHand_(buffer,WrapHandle,bytes) : noErr);\
		 if (err) goto done;																				\
		 bSpot = buffer;																						\
	} 																														\
} while (0)
#define putBChar(theC) do { 																		\
	*bSpot++ = theC;																							\
	if (bSpot==bEnd) flushBChars(); 															\
} while (0)
#define UnwrapSpaceRuns 		((unwrapPref&1)==0)
#define UnwrapCenteredBreak ((unwrapPref&2)==0)
#define UnwrapIndent 				((unwrapPref&4)==0)
#define UnwrapQuote 				((unwrapPref&8)==0)
#define UnwrapShortLine 		((unwrapPref&16)==0)
#define UnwrapBlankLine 		((unwrapPref&32)==0)
int UnwrapSave(UPtr text, long length, long offset, short refN)
{
	struct LineInfo
	{
			int length;
			int indent;
			int quote;
			int needReturn;
	};
	struct LineInfo lines[2];
	struct LineInfo *this, *last;
	int flip;
	int c;
	UPtr tSpot;
	int begin;
	int spaces;
	UPtr buffer = NuPtr(BUFFER_SIZE);
	UPtr bSpot, bEnd;
	int err=0;
	long bytes;
	Str31 s;
	Byte quote;
	Boolean quoteSpace;
	short i;
	uLong unwrapPref = GetPrefLong(PREF_UNWRAP_OPTIONS);
	
	quote=GetRString(s,QUOTE_PREFIX)[1];
	quoteSpace = s[*s]==' ';
	
	if (!buffer) return(MemError());
	bEnd = buffer+BUFFER_SIZE;
	bSpot = buffer;

	WriteZero(lines,sizeof(lines));
	this = lines;
	last = lines+1;
	flip=this->length=this->indent=this->needReturn=last->length=last->indent=0;
	last->needReturn=begin=1;

	for (tSpot=text+offset;tSpot<text+length;tSpot++)
	{
		c = *tSpot;
		if (c=='\015')
		{
			this->needReturn =	this->needReturn ||
								(UnwrapCenteredBreak && this->indent>20) ||
								(UnwrapShortLine && this->length<40) ||
								(UnwrapBlankLine && this->length==0);
			if (this->needReturn)
			{
				if (this->length==0)
				{
					if (!last->needReturn) putBChar('\015');
					for (i=0;i<this->quote;i++) putBChar(quote);
				}
				putBChar('\015');
			}
			else
				putBChar(' ');
			last = this;
			flip = 1-flip;
			this = lines + flip;
			this->length = this->quote = this->indent = this->needReturn = 0;
			begin = 1;
			spaces = 0;
		}
		else if (c==' ')
		{
			if (begin) this->indent++;
			else spaces++;
		}
		else if (begin && c==quote)
		{
			this->quote++;
		}
		else
		{
			if (!begin)
			{
				if (spaces>4 && (void*)SendTrans!=(void*)WrapSendTrans && UnwrapSpaceRuns)
				{
					putBChar('\t');
					this->needReturn = 1;
				}
				else if (spaces)
				{
					putBChar(' ');
					this->length++;
				}
			}
			else if (((UnwrapIndent)&&this->indent>last->indent || UnwrapQuote&&this->quote!=last->quote) &&
							 !last->needReturn)
			{
				putBChar('\015');
				for (i=0;i<this->quote;i++) putBChar(quote);
				this->length += this->quote;
				if (quoteSpace)
				{
					this->length++;
					putBChar(' ');
				}
			}
			else if (this->quote && last->needReturn)
			{
				for (i=0;i<this->quote;i++) putBChar(quote);
				this->length += this->quote;
				if (quoteSpace)
				{
					this->length++;
					putBChar(' ');
				}
			}
			begin = 0;
			spaces = 0;
			putBChar(c);
			this->length++;
		}
	}
	flushBChars();
done:
	ZapPtr(buffer);
	return(err);
}

/************************************************************************
 * MessKey - handle a keydown in a message window
 ************************************************************************/
Boolean MessKey(MyWindowPtr win, EventRecord *event)
{
	MessHandle messH = (MessHandle)GetMyWindowPrivateData(win);
	TOCHandle tocH = (*messH)->tocH;
	long uLetter = UnadornMessage(event)&charCodeMask;
	Boolean bodyEdit = !win->ro && win->pte==TheBody;
	Boolean shift = 0!=(event->modifiers & shiftKey);
	
	if (leftArrowChar<=uLetter && uLetter<=downArrowChar &&
			IsArrowSwitch(event->modifiers))
	{
		NextMess(tocH,messH,uLetter,event->modifiers,False);
		return(True);
	}
	else if ((event->modifiers&cmdKey)!=0 && (uLetter==delChar||uLetter==deleteKey))
	{
		DoMenu2(GetMyWindowWindowPtr(win),MESSAGE_MENU,MESSAGE_DELETE_ITEM,event->modifiers);
		return(true);
	}
	else if (!bodyEdit && (uLetter==tabChar || uLetter==enterChar))
	{
		MessFocus(messH,win->pte==(*messH)->subPTE ? TheBody : (*messH)->subPTE);
		if (win->pte==(*messH)->subPTE) PeteSelectAll(win,win->pte);
	}
	else if (event->modifiers&cmdKey)
	{
		return(False);
	}
	else if (win->ro && uLetter==' ')
	{
		if (PeteScroll(TheBody,0,shift ? psePageUp : psePageDn) && !shift)
		{
			NextMess(tocH,messH,downArrowChar,0,True);
			return(True);
		}
	}
	else if (win->ro && DirtyKey(event->message))
		AlertStr(READ_ONLY_ALRT, Stop, nil);
	else if (!bodyEdit && !win->ro && uLetter==returnChar)
		SysBeep(20L);
	else
	{
		PeteEdit(win,win->pte,peeEvent,(void*)event);
	}
	PeteSetDirty(win->pte);
	return(True);
}

/************************************************************************
 * NextMess - skip to the next message
 ************************************************************************/
void NextMess(TOCHandle tocH,MessHandle messH,short whichWay,long modifiers,Boolean ezOpen)
{
	WindowPtr	messWinWP = GetMyWindowWindowPtr ((*messH)->win);
	short next;
	Boolean close,diffTOC;
	short	sumNum;
	
	if (IsArrowSwitch(modifiers)) modifiers &= ~GetPrefLong(PREF_SWITCH_MODIFIERS);
	
	close = !(modifiers&optionKey);	/* should we close the current one? */
	
	if (ezOpen && tocH==(*messH)->openedFromTocH)
	{
		EzOpen(tocH,(*messH)->sumNum,0,0,True,False);
		CloseMyWindow(messWinWP);
		return;
	}
	
	if ((*messH)->openedFromTocH && tocH!=(*messH)->openedFromTocH)
	{
		tocH = (*messH)->openedFromTocH;
		if (!FindRealSummary(tocH, (*messH)->openedFromSerialNum, &sumNum))
			return;
		diffTOC = true;
	}
	else
	{
		sumNum = (*messH)->sumNum;
		diffTOC = false;
	}
	
	switch (whichWay)
	{
		case upArrowChar:
		case leftArrowChar:
			next = sumNum-1;
			break;
		case downArrowChar:
		case rightArrowChar:
			next = sumNum+1;
			break;
		default:
			return;
	}
	if (next!=sumNum)
	{
		if (close && (*messH)->win->isDirty)	/* close before switch if dirty */
		{
			if (!CloseMyWindow(messWinWP)) return;
			close = False;
		}
		
		if (next>=0 && next<(*tocH)->count)
		{
			if ((*tocH)->sums[next].messH)
			{
				WindowPtr	tocMessWinWP = GetMyWindowWindowPtr ((*(MessHandle)(*tocH)->sums[next].messH)->win);
				ShowMyWindow(tocMessWinWP);
				UserSelectWindow(tocMessWinWP);
				UpdateMyWindow(tocMessWinWP);
			}
			else
			{
				if (close) (*messH)->win->hideme = true;
				(void) GetAMessage(tocH,next,nil,nil,True);
				if (close) (*messH)->win->hideme = false;
				if (diffTOC)
					BeenThereDoneThat(tocH,next);	//	set status to READ in virtual mailbox
			}
			if ((*tocH)->sums[next].messH)
				UpdateMyWindow(GetMyWindowWindowPtr((*(*tocH)->sums[next].messH)->win));
			SelectBoxRange(tocH,next,next,False,-1,-1);
			BoxCenterSelection((*tocH)->win);
		}
	}
	if (close) CloseMyWindow(GetMyWindowWindowPtr((*messH)->win));
}

/**********************************************************************
 * 
 **********************************************************************/
void SADL_Hilite(DialogPtr dgPtr)
{
	short junk;
	ControlHandle cntl;
	Rect r;

	GetDialogItem(dgPtr,SADL_EXCLUDE_HEADERS,&junk,(Handle*)&cntl,&r);
	HiliteControl(cntl,Stationery==1?255:0);
	GetDialogItem(dgPtr,SADL_PARAGRAPHS,&junk,(Handle*)&cntl,&r);
	HiliteControl(cntl,Stationery==1?255:0);
	GetDialogItem(dgPtr,SADL_STATIONERY_FOLDER,&junk,(Handle*)&cntl,&r);
	HiliteControl(cntl,Stationery==1?0:255);
}

/**********************************************************************
 * MessFind - find in the window
 **********************************************************************/
Boolean MessFind(MyWindowPtr win,PStr what)
{
	return FindInPTE(win,(*Win2MessH(win))->bodyPTE,what);
}

/************************************************************************
 * MessProxy - handle a click in the proxy icon of a message window
 ************************************************************************/
void MessProxy(MyWindowPtr win,EventRecord *event)
{	
	Point pt = event->where;
	MessHandle messH = Win2MessH(win);
	
	PushGWorld();
	SetPort_(GetMyWindowCGrafPtr(win));
	GlobalToLocal(&pt);
	DragXfer(pt,nil,messH);
	PopGWorld();
}

/************************************************************************
 * MessClick - handle a click in the message window
 ************************************************************************/
void MessClick(MyWindowPtr win,EventRecord *event)
{
	Point pt = event->where;
	PETEHandle pte=nil;
	MessHandle messH = Win2MessH(win);
	
	SetPort_(GetMyWindowCGrafPtr(win));
	GlobalToLocal(&pt);
	
	if (!win->isActive)
	{
		SelectWindow_(GetMyWindowWindowPtr(win));
		return;
	}
	if (PtInPETE(pt,TheBody))
	{
		pte = TheBody;
	}
	else if (PtInPETE(pt,(*messH)->subPTE))
	{
		pte = (*messH)->subPTE;
	}
	
#ifdef IMAP
	// Don't allow subjects of undownloaded messages to be changed.
	if (pte && (!(*((*messH)->tocH))->imapTOC || IMAPMessageDownloaded((*messH)->tocH, (*messH)->sumNum)))
#else
	if (pte)
#endif
	{		
		if (PeteEdit(win,pte,peeEvent,(void*)event)==tsmDocNotActiveErr)
		{
			MessFocus(messH,pte);
			PeteEdit(win,pte,peeEvent,(void*)event);
		}
	}
	else if (win->labelCntl && PtInControl(pt,win->labelCntl))
	{
		BoxLabelMenu((*messH)->tocH,(*messH)->sumNum,messH,pt);
	}
	else
	{
		EnableMenuItems(False);
		SetMenuTexts(0,False);
		HandleControl(pt,win);
	}
}

/**********************************************************************
 * MessButton - click on a button in the message
 **********************************************************************/
void MessButton(MyWindowPtr win,ControlHandle button,long modifiers,short part)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	MessHandle messH = Win2MessH(win);
	short value = GetControlValue(button);
	PETEDocInfo info;
#ifndef USEFIXEDDEFAULTFONT
	Str63 s;
#endif
	short oldWidth, width, fixedWidth, fixedID, fixedSize, newWidth;
	Rect	rWin;
	
	switch (GetControlReference(button))
	{
		case NOTIFY_CNTL:
			if (!(modifiers&optionKey))
				GenerateReceipt(messH,MDN_DISPLAYED,MDN_DISPLAYED_LOCAL,
												MessOptIsSet(messH,OPT_AUTO_OPENED) ? MDN_AUTO_ACTION:MDN_MAN_ACTION,
												MDN_MAN_SENT);
			ClearMessOpt(messH,OPT_RECEIPT);
			DisposeControl(button);
			if (button=FindControlByRefCon(win,MDN_REQUEST)) DisposeControl(button);
			if (button=FindControlByRefCon(win,MDN_REQUEST+1)) DisposeControl(button);
			SetTopMargin(win,GetRLong(COMP_TOP_MARGIN));
			MyWindowDidResize(win,nil);
			break;
		
		case PRIOR_HIER_MENU:
			DoMenu(winWP,PRIOR_HIER_MENU<<16|GetBevelMenuValue(button),modifiers);
			break;
			
		case mcMesgErrors:
		break;			

		case mcWrite:
			MessMakeEditable(win,1-value);
			break;
		
		case mcBlahBlah:
			PETEGetDocInfo(PETE,TheBody,&info);
			if (info.dirty)
			{
				if (!PrefIsSet(PREF_EZ_SAVE))
				{
					if (!SaveMessHi(win,False)) return;
				}
				else if (!SaveMess(win)) return;
			}
			SetControlValue(button,1-value);
			if (value) ClearMessFlag(messH,FLAG_SHOW_ALL);
			else SetMessFlag(messH,FLAG_SHOW_ALL);
			ReopenMessage(win);
			break;

		case mcFixed:
#ifdef USEFIXEDDEFAULTFONT
			fixedSize = FixedSize;
			fixedID = FixedID;
#else
			// what size to use?
			fixedSize = GetRLong(DEF_FIXED_SIZE);
			if (!fixedSize) fixedSize = FontSize;
			// what font to use?
			fixedID = GetFontID(GetRString(s,FIXED_FONT));
			if (fixedID==applFont)
				fixedID = (ScriptVar(smScriptMonoFondSize)>>16)&0xffff;
#endif
			// calculate window widths
			fixedWidth = GetRLong(DEF_FIXED_MWIDTH)*CharWidthInFont('0',fixedID,fixedSize) + PETE_SCROLLY_DIFFERENCE;
			width = MessWi(win);
			newWidth = 0;
			GetWindowPortBounds(winWP,&rWin);
			oldWidth = RectWi(rWin);
			
			// change font
			if (GetControlValue(button))
			{
				PeteFontAndSize(TheBody,kPETEDefaultFont,kPETEDefaultSize);
				PeteFontAndSize((*messH)->subPTE,kPETEDefaultFont,kPETEDefaultSize);
				if (ABS(oldWidth-fixedWidth)<10) newWidth = width;
				ClearMessFlag ( messH, FLAG_FIXED_WIDTH );
			}
			else
			{
				PeteFontAndSize(TheBody,fixedID,fixedSize);
//				PeteFontAndSize((*messH)->subPTE,fixedID,fixedSize);
				if (ABS(oldWidth-width)<10) newWidth = fixedWidth;
				SetMessFlag ( messH, FLAG_FIXED_WIDTH );
			}
			SetControlValue(button,1-value);
			
			// do we need to resize the window?
			if (newWidth)
			{
				SizeWindow(winWP,newWidth,RectHi(rWin),false);
				MyWindowDidResize(win,nil);
			}
			break;
			
		case mcFetch:
			SetControlValue(button,1-value);
			if (value)
			{
				RemIdFromPOPD(PERS_POPD_TYPE(MESS_TO_PPERS(messH)),FETCH_ID,SumOf(messH)->uidHash);
				if (!PrefIsSet(PREF_LMOS))
				{
					RemIdFromPOPD(PERS_POPD_TYPE(MESS_TO_PPERS(messH)),DELETE_ID,SumOf(messH)->uidHash);
					SetControlValue(FindControlByRefCon(win,mcTrash),0);
					HiliteControl(FindControlByRefCon(win,mcTrash),0);
				}
			}
			else
			{
				AddIdToPOPD(PERS_POPD_TYPE(MESS_TO_PPERS(messH)),FETCH_ID,SumOf(messH)->uidHash,False);
				if (!PrefIsSet(PREF_LMOS))
				{
					AddIdToPOPD(PERS_POPD_TYPE(MESS_TO_PPERS(messH)),DELETE_ID,SumOf(messH)->uidHash,False);
					SetControlValue(FindControlByRefCon(win,mcTrash),1);
					HiliteControl(FindControlByRefCon(win,mcTrash),255);
				}
			}
			InvalTocBox((*messH)->tocH,(*messH)->sumNum,blServer);
			break;
		
		case mcGetGraphics:
			HiliteControl(FindControlByRefCon(win,mcGetGraphics),255);	//	Disabling button indicates that graphics should now be downloaded
			PeteRecalc(win->pte);
			break;

		case mcTrash:
			SetControlValue(button,1-value);
			if (value) RemIdFromPOPD(PERS_POPD_TYPE(MESS_TO_PPERS(messH)),DELETE_ID,SumOf(messH)->uidHash);
			else AddIdToPOPD(PERS_POPD_TYPE(MESS_TO_PPERS(messH)),DELETE_ID,SumOf(messH)->uidHash,True);
			InvalTocBox((*messH)->tocH,(*messH)->sumNum,blServer);
			break;
	}
	
	AuditHit((modifiers&shiftKey)!=0, (modifiers&controlKey)!=0, (modifiers&optionKey)!=0, (modifiers&cmdKey)!=0, false, GetWindowKind(winWP), GetControlReference(button), mouseDown);
}

/************************************************************************
 * MessMakeEditable - flip the pencil for a message
 ************************************************************************/
OSErr MessMakeEditable(MyWindowPtr win, Boolean value)
{
	PETEDocInfo info;
	MessHandle messH = Win2MessH(win);
	PETETextStyle style;
	
	if (!value)
	{
		PETEGetDocInfo(PETE,TheBody,&info);
		if (info.dirty)
		{
			if (!PrefIsSet(PREF_EZ_SAVE))
			{
				if (!SaveMessHi(win,False)) return userCanceledErr;
			}
			else if (!SaveMess(win)) return userCanceledErr;
		}
		PETESetCallback(PETE,TheBody,nil,peDocChanged);
		ClearMessOpt(messH,OPT_WRITE);
		win->ro = win->pte==TheBody;
		win->userSave = false;
		PETEHonorLock(PETE,TheBody,peModLock|peSelectLock);
    style.tsLock = peModLock;
    PETESetTextStyle(PETE,TheBody,0L,0x7fffffff,&style,peLockValid);
		win->isDirty = False;
		PeteCleanList(win->pteList);
	}
	else
	{
		SetMessOpt(messH,OPT_WRITE);
		win->userSave = true;
		win->ro = False;
		PETESetCallback(PETE,TheBody,(void*)PeteChanged,peDocChanged);
		PETEHonorLock(PETE,TheBody,peNoLock);
	}
	SetControlValue(FindControlByRefCon(win,mcWrite),value);
	return noErr;
}


/************************************************************************
 * MessGonnaShow - get ready to show a message window
 ************************************************************************/
OSErr MessGonnaShow(MyWindowPtr win)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	MessHandle messH = (MessHandle)GetMyWindowPrivateData(win);
	short margin;
	Rect r;
	ControlHandle blah;
	Boolean on = MessOnPOPD(POPD_ID,messH);
	MControlsEnum cIndex;
	short icon;
	short value;
	long scanned;
	Boolean junk = SumOf(messH)->spamScore >= GetRLong(JUNK_MAILBOX_THRESHHOLD);
		
	win->didResize = MessDidResize;
	win->key = MessKey;
	win->update = MessUpdate;
	win->help = MessHelp;
	win->button = MessButton;
	win->dontControl = True;
	win->zoomSize = MessZoomSize;
	win->label = SumColor(SumOf(messH));
	margin = GetRLong(COMP_TOP_MARGIN);
	if (!MessFlagIsSet(messH,FLAG_OUT) && MessOptIsSet(messH,OPT_RECEIPT) && !GetPrefLong(PREF_RECEIPT)) margin += GROW_SIZE+2*INSET;
	SetTopMargin(win,margin);
	if (MakeSubjectTXE(messH))
	{
		win->click = win->bgClick = MessClick;
#ifdef USECMM_BAD
		win->buildCMenu = MessBuildCMenu;
#endif
	}
	else
	{
		PeteCleanList(win->pte);
		win->isDirty = False;
		CloseMyWindow(winWP);
		return(-108);
	}
	PeteFocus(win,TheBody,True);
	(*PeteExtra(TheBody))->containsJunkMail = junk;
	

	SetBGColorsByPers(messH);

	/*
	 * Align headers
	 */
	//if (GetPrefLong(PREF_DONT_ALIGN_HEADERS)!=2) AlignHeaders(messH);

	/*
	 * some attachments
	 */
	for (scanned=(*PeteExtra(TheBody))->urlScanned;
			 scanned!=-1 && scanned<3 K;
			 scanned=(*PeteExtra(TheBody))->urlScanned)
		PeteURLScan(win,TheBody);
	for (scanned=(*PeteExtra(TheBody))->quoteScanned;
			 scanned!=-1 && scanned<3 K;
			 scanned=(*PeteExtra(TheBody))->quoteScanned)
		PeteQuoteScan(TheBody);
	
	(*PeteExtra(TheBody))->emoDesired = !MessFlagIsSet(messH,FLAG_SHOW_ALL);
	if ((*PeteExtra(TheBody))->emoDesired && EmoDo())
	{
		for (scanned=(*PeteExtra(TheBody))->emoScanned;
			 scanned!=-1 && scanned<3 K;
			 scanned=(*PeteExtra(TheBody))->emoScanned)
		EmoScanPete(TheBody,false);
	}
		
	/*
	 * calculate
	 */
	PeteCalcOn(TheBody);
	
	/*
	 * controls
	 */
	Zero(r);
	r.bottom = r.right = 2;
	
	for (cIndex=mcWrite;cIndex<=mcTrash;cIndex++)
	{
		if (FontIsFixed && cIndex==mcFixed) continue;	// only if default font is proportional
		blah = GetNewControlSmall(ICON_BAR_CNTL,winWP);
		if (blah)
		{
			EmbedControl(blah,win->topMarginCntl);
			SetControlReference(blah,cIndex);
			switch (cIndex)
			{
				case mcWrite:
					icon = PENCIL_SICN;
					break;
				case mcBlahBlah:
					icon = BLAH_SICN;
					break;
				case mcFixed:
					icon = FIXED_WIDTH_ICON;
					if (!FontIsFixed && !(MessOptIsSet(messH,OPT_HTML)||MessFlagIsSet(messH,FLAG_RICH)) && PrefIsSet(PREF_FIXIFY))
						MessButton(win,blah,0,kControlButtonPart);
					break;
				case mcFetch:
					icon = FETCH_SICN;
					break;
				case mcGetGraphics:
					icon = MISSING_IMAGE_ICON;
					break;
				case mcTrash:
					icon = TRASH_SICN;
					value = MessOnPOPD(DELETE_ID,messH);
					break;
			}
			SetBevelIcon(blah,icon,0,0,nil);
		}
		else
		{
			NoSaves = True;
			CloseMyWindow(winWP);
			NoSaves = False;
			return(-108);
		}
	}
	
	AddPriorityPopup(messH);
	
	PushGWorld();
	SetWindowProxyCreatorAndType(winWP,CREATOR,MAILBOX_TYPE,kOnSystemDisk);
	win->proxy = MessProxy;
	PopGWorld();
	
	MessIBarUpdate(messH);
		
	/*
	 * notification stuff
	 */
	CheckAddNotifyControls(win, messH);
	
	// error notification
	AddMessErrNote(messH);

	BeenThereDoneThat((*messH)->tocH,(*messH)->sumNum);
	
	HiliteOddReply(messH);
			
	if (AnalSpeakPhrases())
	{
		(*PeteExtra(TheBody))->taeDesired = true;
		(*PeteExtra(TheBody))->taeSpeak = true;
	}

	if (!win->ro) PETESetCallback(PETE,TheBody,(void*)PeteChanged,peDocChanged);

	return(noErr);
}

#define CARBON_NOTIFY_BUTTON_V_ADJUSTMENT 4

/************************************************************************
 * CheckAddReturnReceipt - see if we need to add return receipt to this message
 ************************************************************************/
Boolean CheckAddNotifyControls(MyWindowPtr win, MessHandle messH)
{
	Boolean	result = false;
	
	if (!MessFlagIsSet(messH,FLAG_OUT) && MessOptIsSet(messH,OPT_RECEIPT) && !FindControlByRefCon(win,NOTIFY_CNTL))
	{
		if (!GetPrefLong(PREF_RECEIPT))
			SetTopMargin(win,GetRLong(COMP_TOP_MARGIN) + GROW_SIZE+2*INSET + 2*CARBON_NOTIFY_BUTTON_V_ADJUSTMENT);
		switch(GetPrefLong(PREF_RECEIPT))
		{
			case 2:
				(*messH)->sound = NOTIFY_SOUND;
				GenerateReceipt(messH,MDN_DISPLAYED,MDN_DISPLAYED_LOCAL,
												MessOptIsSet(messH,OPT_AUTO_OPENED) ? MDN_AUTO_ACTION:MDN_MAN_ACTION,
												MDN_AUTO_SENT);
				ClearMessOpt(messH,OPT_RECEIPT);
				break;
			case 1:
				ClearMessOpt(messH,OPT_RECEIPT);
				break;
			case 0:
				AddNotifyControls(messH);
				result = true;
				break;
		}
	}
	return result;
}	

/************************************************************************
 * function - purpose
 ************************************************************************/
void AddNotifyControls(MessHandle messH)
{
	ControlHandle blah;
	MyWindowPtr messWin = (*messH)->win;
	WindowPtr	messWinWP = GetMyWindowWindowPtr (messWin);
	Rect r;
	Str255 s;
	
	SetRect(&r,2,2,4,4);
	if (blah = GetNewControlSmall(NOTIFY_CNTL,messWinWP))
	{
		(*messH)->sound = NOTIFY_SOUND;
		SetControlReference(blah,NOTIFY_CNTL);
		if (gGoodAppearance) EmbedControl(blah,messWin->topMarginCntl);

		// add message
		if (blah = NewControlSmall(messWinWP,&r,"",False,0,0,1,kControlStaticTextProc,MDN_REQUEST))
		{
			SetTextControlText(blah,GetRString(s,MDN_REQUEST),nil);
			if (gGoodAppearance) EmbedControl(blah,messWin->topMarginCntl);
		}
		
		// add separator
		if (blah = NewControlSmall(messWinWP,&r,"\p",True,0,0,0,kControlSeparatorLineProc,MDN_REQUEST+1))
			if (gGoodAppearance) EmbedControl(blah,messWin->topMarginCntl);
		
		PlaceNotifyControls(messWin);
		
	}
}

/************************************************************************
 * function - purpose
 ************************************************************************/
void PlaceNotifyControls(MyWindowPtr win)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);
	Rect r, notifyR, rWin;
	ControlHandle blah;
	
	if (blah = FindControlByRefCon(win,NOTIFY_CNTL))
	{
		ButtonFit(blah);
		GetWindowPortBounds(winWP,&rWin);
		MoveMyCntl(blah,rWin.right-INSET-ControlWi(blah),win->topMargin-ControlHi(blah)-MAX_APPEAR_RIM-2-CARBON_NOTIFY_BUTTON_V_ADJUSTMENT,0,0);
		ShowMyControl(blah);
		GetControlBounds(blah,&notifyR);

		// add message
		r = notifyR;
		r.right = r.left - INSET;
		r.left = INSET;
		InsetRect(&r,0,MAX_APPEAR_RIM-1);
		if (blah = FindControlByRefCon(win,MDN_REQUEST))
		{
			MySetCntlRect(blah,&r);
			ShowMyControl(blah);
		}
		
		// add separator
		r = notifyR;
		SetRect(&r,INSET,r.top,rWin.right-INSET,r.top+3);
		OffsetRect(&r,0,-5);
		if (blah = FindControlByRefCon(win,MDN_REQUEST+1))
		{
			MySetCntlRect(blah,&r);
			ShowMyControl(blah);
		}
	}
}

/************************************************************************
 * AddMessErrNote - add the error note to a message, if need be
 ************************************************************************/
void AddMessErrNote(MessHandle messH)
{
	Rect r,c;
	ControlHandle cntl;
	mesgErrorHandle mesgErrH;
	MyWindowPtr messWin = (*messH)->win;
	WindowPtr	messWinWP = GetMyWindowWindowPtr (messWin);
	
	/* if this message has a mesgErrH or the toc status is x, add note */
	if ((mesgErrH=(*(*messH)->tocH)->sums[(*messH)->sumNum].mesgErrH) || ((*(*messH)->tocH)->sums[(*messH)->sumNum].state==MESG_ERR))	
	{
		// make divider
		SetRect(&r,-REAL_BIG,messWin->topMargin-1,REAL_BIG,messWin->topMargin+1);
		cntl = NewControlSmall(messWinWP,&r,"",True,0,0,0,kControlSeparatorLineProc,nil);
		EmbedControl(cntl,messWin->topMarginCntl);
		
		// add to topmargin
		SetTopMargin(messWin,messWin->topMargin+GetMesgErrorsHeight(messWin)); 
		GetMesgErrorsRect(messWin,&r);
		
		// add separator
		SetRect(&c,INSET,r.top,messWin->contR.right-INSET,r.top+4); // (jp) 2-6-01 Bug 3637  bottom used to be r.top+3
		OffsetRect(&c,0,-5);
		if (cntl = NewControlSmall(messWinWP,&c,"\p",True,0,0,0,kControlSeparatorLineProc,mcMesgErrSep))
			EmbedControl(cntl,messWin->topMarginCntl);

		// add icon
		r.top += 3;
		SetRect(&c,0,0,16,16);
		CenterRectIn(&c,&r);
		cntl = NewControlSmall(messWinWP,&c,"",True,MESS_ERR_ICON,0,0,kControlIconSuiteNoTrackProc,mcMesgErrors);
		if (cntl && GetControlDataHandle(cntl))
		{
			(*messH)->sound = NOTIFY_SOUND;
			if (gGoodAppearance) EmbedControl(cntl,messWin->topMarginCntl);
		}
		
		// add message
		r.left = r.right+INSET; r.right = messWin->contR.right-INSET;
		if (cntl = NewControlSmall(messWinWP,&r,"",False,0,0,1,kControlStaticTextProc,mcMesgErrText))
		{
			/* try mesgErrH first. if not, use generic */
			if (mesgErrH)
			{
				SetTextControlText(cntl,LDRef(mesgErrH)->errorStr,nil);
				UL(mesgErrH);
			}
			else
			{
				Str255 item;
				SetTextControlText(cntl,GetRString(item,UNKNOWN_ERR_TEXT),nil);
			}
				LetsGetSmall(cntl);
				ShowMyControl(cntl);
				if (gGoodAppearance) EmbedControl(cntl,messWin->topMarginCntl);
			}
		}
}	

/************************************************************************
 * PlaceMessErrNote - move the error note to the right spot
 ************************************************************************/
void PlaceMessErrNote(MessHandle messH)
{
	ControlHandle cntl;
	MyWindowPtr win = (*messH)->win;
	Rect r;
	long junk;
	short h, dH;
	
	
	if (cntl=FindControlByRefCon(win,mcMesgErrors))
	{
		GetMesgErrorsRect(win,&r);
		r.left = r.right+INSET; r.right = win->contR.right-INSET;
		r.top -= 1;
		if (cntl=FindControlByRefCon(win,mcMesgErrText))
		{
			HideControl(cntl);
			MySetCntlRect(cntl,&r);
			SetBevelStyle(cntl,0);
			GetControlData(cntl,0,kControlStaticTextTextHeightTag,sizeof(short),(void*)&h,&junk);
			if (h>RectHi(r))
			{
				SetBevelStyle(cntl,condense);
				GetControlData(cntl,0,kControlStaticTextTextHeightTag,sizeof(short),(void*)&h,&junk);
			}
			dH = (h - RectHi(r))/2;
			if (dH<0)
			{
				OffsetRect(&r,0,-dH);
				r.bottom = r.top + h;
				MySetCntlRect(cntl,&r);
			}
			ShowControl(cntl);
		}
	}
}


/**********************************************************************
 * MessIBarUpdate - update the icon bar
 **********************************************************************/
void MessIBarUpdate(MessHandle messH)
{
	MControlsEnum cIndex;
	ControlHandle blah;
	long value;
	Boolean on = MessOnPOPD(POPD_ID,messH);
	Boolean lmos = PrefIsSet(PREF_LMOS);
	Boolean fetch = on && MessOnPOPD(FETCH_ID,messH);
	Boolean del = on && (fetch && !lmos || MessOnPOPD(DELETE_ID,messH));
	
	for (cIndex=mcWrite;cIndex<=mcTrash;cIndex++)
		if (blah=FindControlByRefCon((*messH)->win,cIndex))
		{
			value = GetControlValue(blah);
			switch (cIndex)
			{
				case mcWrite:
					value = MessOptIsSet(messH,OPT_WRITE);
					break;
				case mcBlahBlah:
					value = MessFlagIsSet(messH,FLAG_SHOW_ALL);
					break;
				case mcFetch:
					value = fetch;
					if (!on || !MessFlagIsSet(messH,FLAG_SKIPPED)) HideControl(blah);
					(*messH)->hasFetchIcon = IsControlVisible(blah);
					break;
				case mcGetGraphics:
					if (!DisplayGetGraphics((*messH)->win))
						HideControl(blah);
					else
						ShowControl(blah);
					break;
				case mcFixed:
					value = MessFlagIsSet(messH,FLAG_FIXED_WIDTH);
					break;
				case mcTrash:
					value = del;
					if (!on) HideControl(blah);
					else
					{
						if (!lmos && fetch) DeactivateControl(blah);
						else ActivateControl(blah);
					}
					(*messH)->hasDelIcon = IsControlVisible(blah);
					break;
			}
			SetControlValue(blah,value);
		}
}


#ifdef NEVER
/**********************************************************************
 * AlignHeaders - align the header names in a message
 **********************************************************************/
void AlignHeaders(MessHandle messH)
{
	short len = 0;
	short thisLen;
	Handle text;
	long start, colon, stop, end;
	short nPara = 0;
	PETEParaInfo pinfo;
	
	PETEGetRawText(PETE,TheBody,&text);
	
	end = SumOf(messH)->bodyOffset - (*messH)->weeded;
	
	/*
	 * insert all the paragraphs, and store the width of the header name
	 * in the paragraph indent, and keep track of the max width
	 */
	for (start=0;start<end;start=stop+1)
	{
		for (colon=start;colon<end && (*text)[colon]!=':';colon++);
		for (stop=colon;stop<end;stop++)
			if ((*text)[stop]=='\015' && !IsWhite((*text)[stop+1])) break;
		thisLen = TextWidth(LDRef(text),start,colon-start+1);
		UL(text);
		if (len<thisLen) len = thisLen;
		PeteEnsureBreak(TheBody,stop+1);
		pinfo.indent = thisLen;
		PETESetParaInfo(PETE,TheBody,nPara++,&pinfo,peIndentValid);
	}
	
	/*
	 * Now, go back through the paragraphs and set the margins based
	 * on the maximum length and the header length stored in the indent
	 */
	pinfo.tabHandle = nil;
	len += 2*FontWidth;
	if (thisLen = FontWidth*GetRLong(ALIGN_LIMIT)) len = MIN(len,thisLen);
	
	(*PeteExtra(TheBody))->headers = nPara;
	
	while (nPara--)
	{
		PETEGetParaInfo(PETE,TheBody,nPara,&pinfo);
		pinfo.startMargin = (len-pinfo.indent)<<16;
		pinfo.indent = (-pinfo.indent-FontWidth)<<16;
		PETESetParaInfo(PETE,TheBody,nPara,&pinfo,peIndentValid|peStartMarginValid);
	}
}
#endif
/**********************************************************************
 * ExportHTMLSum - export html from a message summary
 **********************************************************************/
OSErr ExportHTMLSum(TOCHandle tocH,short sumNum)
{
	MessHandle messH = (*tocH)->sums[sumNum].messH;
	MyWindowPtr win = nil;
	OSErr err;
	
	// if this is an IMAP message, make sure it's been downloaded.
	if ((*tocH)->imapTOC) EnsureMsgDownloaded(tocH, sumNum, false);
				
	if (!messH)
	{
		win = GetAMessage(tocH,sumNum,nil,nil,false);
		if (!win) return(MemError());
		messH = Win2MessH(win);
	}
	
	err = ExportHTML(messH);
	
	if (win) CloseMyWindow(GetMyWindowWindowPtr(win));
	return(err);
}

#ifdef USE_FANCY_HTML_EXPORT
/**********************************************************************
 * 
 **********************************************************************/
OSErr ExportHTML(MessHandle messH)
{
	OSErr err;
	Str31 html;
	Str31 testMe;
	Boolean neg;
	short inHTML = 0;
	long offset, startOffset, endOffset;
	FSSpec spec;
	short refN;
	UHandle cache;
	long len = 0;
	long grandLen;
	Byte less = '<';
	
	Zero(spec);
	
	if (!(err=CacheMessage((*messH)->tocH,(*messH)->sumNum)))
	{
		cache = SumOf(messH)->cache;
		HNoPurge(cache);
		ComposeString(html,"\p%r>",HTMLDirectiveStrn+htmlXHTMLDir);
		if (!(err=NewTempExtSpec(Root.vRef,nil,HTML_SUFFIX,&spec)) &&
				!(err=FSpCreate(&spec,0L,'TEXT',smSystemScript)) &&
				!(err=FSpOpenDF(&spec,fsRdWrPerm,&refN)))
		{
			LDRef(cache);
			grandLen = GetHandleSize(cache);
			startOffset = grandLen;
			
			for(offset = SearchPtrHandle(&less,1,cache,0,True,False,nil);
				  offset>0;
				  offset = SearchPtrHandle(&less,1,cache,offset+1,True,False,nil))
			{
				/*
				 * what is the word we're looking at?
				 */
				if ((*cache)[offset+1]=='/')
				{
					neg = True;
					MakePStr(testMe,*cache+offset+2,*html);
				}
				else
				{
					neg = False;
					MakePStr(testMe,*cache+offset+1,*html);
				}
				
				/*
				 * no-brainers first
				 */
				if (!StringSame(html,testMe)) continue;	//anything that's not html is insignificant
				if (neg && !inHTML) continue;	//hmmm.  </html> in free text.  No likee.
				if (!neg && inHTML)	{inHTML++; continue;} // more deeply nested html
				
				/*
				 * ok, first let's look at <html>
				 */
				if (!neg)
				{
					if (!inHTML) startOffset = offset;	//html begins
					inHTML++;
				}
				
				/*
				 * ok, now the fun one: </html>
				 */
				else
				{
					inHTML--;
					if (!inHTML)
					{
						endOffset = offset+*html+3;
						endOffset = MIN(endOffset,grandLen);
						if (startOffset<endOffset)
						{
							len = endOffset-startOffset;
							err = HTMLWriteForBrowser(cache,startOffset,len,refN);
							if (err) goto out;
						}
						offset = endOffset;
					}
				}
			}
out:
			if (!err && inHTML)
			{
				len = grandLen-startOffset;
				err = HTMLWriteForBrowser(cache,startOffset,len,refN);
			}
			UL(cache);
			FSClose(refN);
		}
		HPurge(cache);
	}
	
	// an error occurred if we didn't actually write anything to the temp file -JDB 290499
	if (!err && (len ==0)) err = eofErr;
	
	/*
	 * Did we get some?
	 */
	
	if (!err)
	{
		AliasHandle browser=nil;
		FSSpec browserSpec;
		Boolean wasChanged;
		
		if (HaveTheDiseaseCalledOSX() && !PrefIsSet(PREF_USE_OWN_URL_HELPERS))
			err = OpenOtherDoc(&spec,false,false,nil);
		else
		{
			if (!(err=FindURLApp(GetRString(html,HTTP),&browser,kWildCardOK)))
			if (!(err=ResolveAlias(nil,browser,&browserSpec,&wasChanged)))
				err = OpenDocWith(&spec,&browserSpec,false);
			ZapHandle(browser);
		}
	}
	
	if (err) FSpDelete(&spec);
	
	if (err && err!=userCancelled && err!=1) return(WarnUser(SAVING_HTML,err));
	return err;
}
#else
/**********************************************************************
 * 
 **********************************************************************/
OSErr ExportHTML(MessHandle messH)
{
	OSErr err;
	Str31 html;
	Str31 testMe;
	Boolean neg;
	short inHTML = 0;
	long offset, startOffset, endOffset;
	FSSpec spec;
	short refN;
	UHandle cache;
	long len;
	long grandLen;
	Byte less = '<';
	
	Zero(spec);
	
	if (!(err=CacheMessage((*messH)->tocH,(*messH)->sumNum)))
	{
		cache = SumOf(messH)->cache;
		HNoPurge(cache);
		ComposeString(html,"\p%r>",HTMLTagsStrn+htmlTag);
		if (!(err=NewTempExtSpec(Root.vRef,nil,HTML_SUFFIX,&spec)) &&
				!(err=FSpCreate(&spec,CREATOR,'TEXT',smSystemScript)) &&
				!(err=FSpOpenDF(&spec,fsRdWrPerm,&refN)))
		{
			LDRef(cache);
			grandLen = GetHandleSize(cache);
			startOffset = grandLen;
			
			for(offset = SearchPtrHandle(&less,1,cache,0,True,False,nil);
				  offset>0;
				  offset = SearchPtrHandle(&less,1,cache,offset+1,True,False,nil))
			{
				/*
				 * what is the word we're looking at?
				 */
				if ((*cache)[offset+1]=='/')
				{
					neg = True;
					MakePStr(testMe,*cache+offset+2,*html);
				}
				else
				{
					neg = False;
					MakePStr(testMe,*cache+offset+1,*html);
				}
				
				/*
				 * no-brainers first
				 */
				if (!StringSame(html,testMe)) continue;	//anything that's not html is insignificant
				if (neg && !inHTML) continue;	//hmmm.  </html> in free text.  No likee.
				if (!neg && inHTML)	{inHTML++; continue;} // more deeply nested html
				
				/*
				 * ok, first let's look at <html>
				 */
				if (!neg)
				{
					if (!inHTML) startOffset = offset;	//html begins
					inHTML++;
				}
				
				/*
				 * ok, now the fun one: </html>
				 */
				else
				{
					inHTML--;
					if (!inHTML)
					{
						endOffset = offset+*html+3;
						endOffset = MIN(endOffset,grandLen);
						if (startOffset<endOffset)
						{
							len = endOffset-startOffset;
							err = AWrite(refN,&len,*cache+startOffset);
							if (err) goto out;
						}
						offset = endOffset;
					}
				}
			}
out:
			if (!err && inHTML)
			{
				len = grandLen-startOffset;
				err = AWrite(refN,&len,*cache+startOffset);
			}
			UL(cache);
			FSClose(refN);
		}
		HPurge(cache);
	}
	
	/*
	 * Did we get some?
	 */
	if (!err)
	{
		AliasHandle browser=nil;
		FSSpec browserSpec;
		Boolean wasChanged;
		
		if (!(err=FindURLApp(GetRString(html,HTTP),&browser,kWildCardOK)))
		if (!(err=ResolveAlias(nil,browser,&browserSpec,&wasChanged)))
			err = OpenDocWith(&spec,&browserSpec);
		ZapHandle(browser);
	}
	
	if (err) FSpDelete(&spec);
	
	if (err && err!=userCancelled && err!=1) return(WarnUser(SAVING_HTML,err));
}
#endif

/**********************************************************************
 * HiliteOddReply - hilite reply-to: and friends in color
 **********************************************************************/
void HiliteOddReply(MessHandle messH)
{
	Str255 scratch, scratch2;
	UHandle text;
	short r;
	long size;
	HeadSpec hs, fromHS;
	PETETextStyle ts;
	
	PETEGetRawText(PETE,TheBody,&text);
	LDRef(text);
	size = GetHandleSize(text);

	for (r=1;*GetRString(scratch,ReplyStrn+r);r++)
	{
		if (EqualStrRes(scratch,HEADER_STRN+FROM_HEAD)) break;
		if (CompHeadFindStr(messH,scratch,&hs))
		{
			if (CompHeadFindStr(messH,GetRString(scratch,HEADER_STRN+FROM_HEAD),&fromHS))
			{
				MakePStr(scratch,(*text)+hs.value,hs.stop-hs.value);
				MakePStr(scratch2,(*text)+fromHS.value,fromHS.stop-fromHS.value);
				if (SameAddressStr(scratch,scratch2)) continue;
			}
			ts.tsLabel = pReplyLabel;
			PETESetTextStyle(PETE,TheBody,hs.start,hs.stop,&ts,peLabelValid);
			PeteCleanList(TheBody);
			if (!MessOptIsSet(messH,OPT_WEIRD_REPLY)) SetMessOpt(messH,OPT_WEIRD_REPLY);
		}
	}
	
	UL(text);
}

/************************************************************************
 * MessUpdate - update a message window
 ************************************************************************/
void MessUpdate(MyWindowPtr win)
{
	WindowPtr	winWP = GetMyWindowWindowPtr (win);

	if (IsWindowVisible (winWP))
	{
		MessHandle messH = (MessHandle)GetMyWindowPrivateData(win);
		
		if (IsWindowVisible (winWP) && (*messH)->sound)
		{
			Str255 soundName;
			PlayNamedSound(GetRString(soundName,(*messH)->sound));
			(*messH)->sound = 0;
		}
	}
}


#ifdef TWO
/**********************************************************************
 * MessBuildDragRgn - build the drag region for a message
 **********************************************************************/
RgnHandle MessBuildDragRgn(MessHandle messH)
{
	WindowPtr	messWinWP = GetMyWindowWindowPtr ((*messH)->win);
	RgnHandle rgn = NewRgn();
	
	if (rgn)
	{
		GetWindowRegion(messWinWP,kWindowTitleProxyIconRgn,rgn);
		LocalizeRgn(rgn);
	}
	return(rgn);
}

/**********************************************************************
 * GetServerRect - get rectangle for a server icon
 **********************************************************************/
Boolean GetServerRect(MyWindowPtr win, short which, Rect *r)
{
	CGrafPtr	winPort = GetMyWindowCGrafPtr (win);
	short wi;
	Rect	rPort;
	
	GetBlahRect(win,0,r);
	wi = r->right - r->left;
	GetPortBounds(winPort,&rPort);
	r->right = rPort.right-(2-which)*(INSET+wi)-INSET;
	r->left = r->right-wi;
	return(True);
}
#endif

/**********************************************************************
 * GetMesgErrorsHeight - get rectangle for a transport errors icon
 **********************************************************************/

short GetMesgErrorsHeight(MyWindowPtr win)
{
	short height, fs;
	FontInfo info;
	
	fs = GetPortTextSize(GetQDGlobalsThePort());
	TextSize (10);
	GetFontInfo(&info);
	TextSize (fs);
	height = (info.ascent + info.descent + info.leading) * 2;
	return ((height < MESG_ERR_WIDTH) ? MESG_ERR_WIDTH : height);
}

/**********************************************************************
 * GetMesgErrorsRect - get rectangle for a transport errors icon
 **********************************************************************/
Boolean GetMesgErrorsRect(MyWindowPtr win, Rect *r)
{
	CGrafPtr	winPort = GetMyWindowCGrafPtr (win);
	Rect	rPort;
	
	GetPortBounds(winPort,&rPort);
	r->left = rPort.left + INSET;
	r->right = r->left + MESG_ERR_WIDTH;
	r->bottom = win->topMargin - MAX_APPEAR_RIM;
	r->top = r->bottom - MESG_ERR_WIDTH + 6;
	return(True);
}

/************************************************************************
 * MessFocus - switch txe's in a message window
 ************************************************************************/
void MessFocus(MessHandle messH,PETEHandle pte)
{
	MyWindowPtr win = (*messH)->win;
	Boolean wasSub = win->pte==(*messH)->subPTE;

	PeteFocus(win,pte,True);
	win->ro = win->pte==TheBody && !MessOptIsSet(messH,OPT_WRITE);

	if (wasSub && win->pte!=(*messH)->subPTE)
		MessSaveSub(messH);
	else win->showInsert = NOOP;
}

/**********************************************************************
 * MessSaveSub - save the subject of a message
 **********************************************************************/
OSErr MessSaveSub(MessHandle messH)
{
	Str255 newSubj;
	
	if(MessFlagIsSet(messH, FLAG_UTF8) && HasUnicode())
	{
		OSErr err;
		long iUsed, oUsed;
		
		err = PeteGetUTF8Text((*messH)->subPTE, 0, LONG_MAX, &iUsed, &(newSubj[1]), sizeof(newSubj) - 1, &oUsed);
		if(err && err != kTECOutputBufferFullStatus) return err;
		newSubj[0] = oUsed;
	}
	else
	{
		PeteSString(newSubj,(*messH)->subPTE);
	}
	SetSubject((*messH)->tocH,(*messH)->sumNum,newSubj);
	PETEMarkDocDirty(PETE,(*messH)->subPTE,False);
	if (PeteIsDirtyList((*messH)->win->pteList)) (*messH)->win->isDirty = True;
	else (*messH)->win->isDirty = False;
	return noErr;
}

/************************************************************************
 * MessDidResize - resize a message window
 ************************************************************************/
void MessDidResize(MyWindowPtr win, Rect *oldContR)
{
	MessHandle messH = (MessHandle)GetMyWindowPrivateData(win);
	Rect view;
	Rect paneR;
	PETEHandle bodTeh = TheBody;
	PETEHandle subTeh = (*messH)->subPTE;
	ControlHandle blah,
								errorCntl = nil;
	
	if (!subTeh)
		TextDidResize(win,oldContR);
	else
	{
		view = win->contR;
		PeteDidResize(bodTeh,&view);
	}
	PlaceMessErrNote(messH);

	if (!oldContR || oldContR->right-oldContR->left!=win->contR.right-win->contR.left)
	{
		SetRect(&view,0,0,REAL_BIG/2,win->topMargin-3);
		InvalWindowRect(GetMyWindowWindowPtr(win),&view);

		/*
		 * the icon bar
		 */
		if (GetBlahRect(win,0,&view))
		{
			if (blah = FindControlByRefCon(win,mcWrite))
				MySetCntlRect(blah,&view);
			
			GetBlahRect(win,1,&view);
			if (blah=FindControlByRefCon(win,mcBlahBlah))
				MySetCntlRect(blah,&view);
			
			GetBlahRect(win,2,&view);
			if (blah=FindControlByRefCon(win,mcFixed))
				MySetCntlRect(blah,&view);
			
			GetServerRect(win,1,&view);
			if (blah=FindControlByRefCon(win,mcFetch))
				MySetCntlRect(blah,&view);

			GetServerRect(win,1,&view);
			if (blah=FindControlByRefCon(win,mcGetGraphics))
				MySetCntlRect(blah,&view);

			GetServerRect(win,2,&view);
			if (blah=FindControlByRefCon(win,mcTrash))
				MySetCntlRect(blah,&view);

			if (blah=FindControlByRefCon(win,NOTIFY_CNTL))
			{
				PlaceNotifyControls(win);
			}
						
			MessSubjRect(win,&view);
			paneR = view; 
			if (blah=FindControlByRefCon(win,(uLong)subTeh))
			{
				InsetRect(&paneR,-MAX_APPEAR_RIM,-MAX_APPEAR_RIM);
				SetControlVisibility(blah,false,false);
				MySetCntlRect(blah,&paneR);
				SetControlVisibility(blah,true,false);
			}
			PETESizeDoc(PETE,subTeh,RectWi(view),RectHi(view));
			PETEChangeDocWidth(PETE,win->pte,-1,True);
		}
	}
}

/************************************************************************
 * MessSubjRect - find the rect for the subject teh
 ************************************************************************/
void MessSubjRect(MyWindowPtr win,Rect *r)
{
	MessHandle messH = Win2MessH(win);
	short hi = ONE_LINE_HI(win);
	Rect pr, br;
	
	GetPriorityRect(win,&pr);
	GetBlahRect(win,FontIsFixed?1:2,&br);
	pr.right = br.right;

	*r = win->contR;
	r->left += pr.right + INSET+win->hPitch;
	r->top = pr.top + (RectHi(pr)-hi-1)/2;
	r->bottom = r->top + hi;
#ifdef TWO
	GetServerRect(win,1,&br);
	r->right = br.left-INSET;
#endif
}

/************************************************************************
 * GetBlahRect - get the rect for the blah blah icon
 ************************************************************************/
Boolean GetBlahRect(MyWindowPtr win, short which, Rect *r)
{
	if (!GetPriorityRect(win,r)) return(False);
	OffsetRect(r,(which+1)*(RectWi(*r)+INSET),0);
	return(True);
}

/************************************************************************
 * MessCursor - set the cursor properly
 ************************************************************************/
void MessCursor(Point mouse)
{
	MyWindowPtr win=GetWindowMyWindowPtr(FrontWindow_());
	MessHandle messH = Win2MessH(win);
		
	if (!PeteCursorList(win->pteList,mouse))
		SetMyCursor(arrowCursor);
}

/************************************************************************
 * GetPriorityRect - where does the priority thing belong?
 ************************************************************************/
Boolean GetPriorityRect(MyWindowPtr win,Rect *pr)
{
	if (!win->topMargin)
	{
		SetRect(pr,0,0,0,0);
		return(False);
	}
	else
	{
		pr->left = INSET;
		pr->top = 2*MAX_APPEAR_RIM+2;
		pr->bottom = pr->top+22;
		pr->right = pr->left+22;
		return(True);
	}
}

/************************************************************************
 * DrawPriority - draw the message priority
 ************************************************************************/
void DrawPriority(Rect *pr,short p)
{
	WindowPtr	theWindow = GetWindowFromPort(GetQDGlobalsThePort());
	Rect ir;
	
	if (GetWindowKind(theWindow)!=CBOX_WIN &&GetWindowKind(theWindow)!=MBOX_WIN)
	{
		ir = *pr;
		InsetRect(&ir,-2,-2);
		EraseRect(&ir);
		FrameRect(&ir);
		MoveTo(ir.left+1,ir.bottom);
		Line(ir.right-ir.left-1,0);
		Line(0,ir.top-ir.bottom+1);
	}
	p = Prior2Display(p);
	SetRect(&ir,0,0,16,16);
	CenterRectIn(&ir,pr);
	if (p==pymRaise)
		PlotIconID(&ir,atAbsoluteCenter, ttNone,RAISE_PRIOR_SICN);
	else if (p==pymLower)
		PlotIconID(&ir,atAbsoluteCenter, ttNone,LOWER_PRIOR_SICN);
	else
		PlotIconID(&ir,atAbsoluteCenter, ttNone,PRIOR_SICN_BASE+p-1);
}


/************************************************************************
 * SetMessTable - set the xlit table for a message
 ************************************************************************/
void SetMessTable(TOCHandle tocH, short sumNum, short newId)
{
	if ((*tocH)->sums[sumNum].tableId!=newId)
	{
		(*tocH)->sums[sumNum].tableId = newId;
		TOCSetDirty(tocH,true);
		if ((*tocH)->previewID==(*tocH)->sums[sumNum].serialNum) (*tocH)->previewID = 0;
		if ((*tocH)->which!=OUT && (*tocH)->sums[sumNum].messH)
			ReopenMessage((*(*tocH)->sums[sumNum].messH)->win);
	}
}

/************************************************************************
 * MessZoomSize - figure the size of a message window
 ************************************************************************/
void MessZoomSize(MyWindowPtr win,Rect *zoom)
{
	long wi, hi;
	PETEHandle pte = (*Win2MessH(win))->bodyPTE;
	PETEDocInfo info;
	
	wi = MessWi(win);
	zoom->right = zoom->left + MIN(zoom->right-zoom->left,wi);

	if (PeteIsValid(pte))
	{
		//PETEGetDocInfo(PETE,pte,&info);
		wi = zoom->right-zoom->left-PETE_SCROLLY_DIFFERENCE+9;
		PETEChangeDocWidth(PETE,pte,wi,True);
		PETEGetDocInfo(PETE,pte,&info);

		hi = info.docHeight + win->topMargin + win->vPitch;
		if (hi<zoom->bottom-zoom->top)	
			zoom->bottom = zoom->top + hi;
	}
}

/**********************************************************************
 * MessWi - how wide is a window?
 **********************************************************************/
short MessWi(MyWindowPtr win)
{
	short wi;
	
	if (!(wi=GetPrefLong(PREF_MWIDTH))) wi=GetRLong(DEF_MWIDTH);
	wi *= win->hPitch; wi += PETE_SCROLLY_DIFFERENCE;
	return(wi);
}


/************************************************************************
 * MessApp1 - scroll the message, not the subject
 ************************************************************************/
Boolean MessApp1(MyWindowPtr win,EventRecord *event)
{
	event->what=keyDown;
	PeteEdit(win,(*Win2MessH(win))->bodyPTE,peeEvent,(void*)event);
	return(True);
}

/************************************************************************
 * IncrementQuoteLevel - adjust the quote level in a message
 ************************************************************************/
OSErr IncrementQuoteLevel(PETEHandle pte,long start, long stop,short increment)
{
	OSErr err;
	short fromPara, toPara;
	PETEParaInfo pInfo;
	PETEStyleEntry pse;
	short level;

	// I'm only going to tell you this once...
	pse.psStyle.textStyle.tsLabel = pQuoteLabel;
	Zero(pInfo);
	
	// avoid messy redraw
	PeteCalcOff(pte);
	
	// Expand offsets to para boundaries
	start = stop = -1;	// use selection
	PeteParaRange(pte,&start,&stop);
	PeteParaConvert(pte,start,stop);
	
	// Undo to para boundaries
	PetePrepareUndo(pte,peUndoStyleAndPara,start,stop,nil,nil);
	
	// Find the start and stop points
	fromPara = PeteParaAt(pte,start);
	toPara = PeteParaAt(pte,stop-1);
	toPara = MAX(toPara,fromPara);	// darn fiddly end conditions
	
	// Ok, do some work
	for (;fromPara<=toPara;fromPara++)
	{
		// get current quote level
		if (err = PETEGetParaInfo(PETE,pte,fromPara,&pInfo)) break;
		
		// bump it, but don't allow negative or > 15
		level = pInfo.quoteLevel + increment;
		level = MIN(level,15);
		level = MAX(level,0);
		pInfo.quoteLevel = level;
		
		// and set it
		if (err = PETESetParaInfo(PETE,pte,fromPara,&pInfo,peQuoteLevelValid)) break;
		
		// and give it the quote label
		if (level) PETESetTextStyle(PETE,pte,pInfo.paraOffset,pInfo.paraOffset+pInfo.paraLength,&pse.psStyle.textStyle,pQuoteLabel<<kPETELabelShift);
	}
	
	// And finish it off with a little undo
	if (err) PeteKillUndo(pte);
	else PeteFinishUndo(pte,peUndoStyleAndPara,start,stop);

	// now draw
	PeteCalcOn(pte);

	return(err);
}

#ifdef USECMM_BAD
/************************************************************************************************
MessBuildCMenu - build a list of context-specific items to be added to the contextual menu when
the context click occurs over a recevied message window.  specCMenuInfo does not point to a valid
handle upon calling the function. The handle-style array it points to needs to be allocated
herein.
************************************************************************************************/
OSStatus MessBuildCMenu(MyWindowPtr win, EventRecord* contextEvent, MenuHandle contextMenu)
{
	short				numItems;					//number of items to add put into the list
	static CMenuInfo	locCMInfo[] =				//items to put into the list
		{	MESSAGE_MENU,	MESSAGE_REPLY_ITEM,			//"reply" item
			MESSAGE_MENU,	MESSAGE_FORWARD_ITEM,		//"forward" item
			MESSAGE_MENU,	MESSAGE_DELETE_ITEM,		//"delete" item
			CONTEXT_MENU,	CONTEXT_TERM_ITEM };		//item to mark end of array

	//count the number of items to add
	for( numItems=0; !( locCMInfo[numItems].srcMenuID == CONTEXT_MENU && locCMInfo[numItems].srcMenuItem == CONTEXT_TERM_ITEM); numItems++ )
		;
	
	//allocate memory for as handle-style array
	*specCMenuInfo = (CMenuInfoHndl)NewHandle( sizeof( CMenuInfo ) * ( numItems + 1 ) );
	if( !(*specCMenuInfo) ) return( MemError() );

	//copy data into the array
	BlockMove( locCMInfo, **specCMenuInfo, sizeof( CMenuInfo ) * ( numItems + 1 ) );
	
	return( noErr );
}
#endif
