/*
**	termTextBuffer.c
**
**	Support routines for the text buffer.
**
**	Copyright © 1990-1994 by Olaf `Olsen' Barthel
**		All Rights Reserved
*/

#include "termGlobal.h"

	/* Gadget ID codes. */

enum	{	GAD_SCROLLER,GAD_UP,GAD_DOWN };

	/* Menu ID codes. */

enum	{	MEN_SEARCH,MEN_REPEAT,MEN_GOTO,MEN_CLEARBUF_CONTENTS,MEN_QUITBUF };

	/* Gadget ID codes. */

enum	{	GAD_STRING,GAD_LOAD,GAD_OK,GAD_CANCEL };

	/* A handy macro to determine the length of a string. */

#define LINE_WIDTH(s)	(s)[-1]

	/* The dimensions of the scroller images. */

#define ARROW_WIDTH	18
#define ARROW_HEIGHT	11

STATIC struct NewMenu BufferMenu[] =
{
	{ NM_TITLE, NULL,		 0 , 0, 0, (APTR)0},
	{  NM_ITEM, NULL,		 0 , 0, 0, (APTR)MEN_SEARCH},
	{  NM_ITEM, NULL,		 0 , 0, 0, (APTR)MEN_REPEAT},
	{  NM_ITEM, NM_BARLABEL,	 0 , 0, 0, (APTR)0},
	{  NM_ITEM, NULL,		 0 , 0, 0, (APTR)MEN_GOTO},
	{  NM_ITEM, NM_BARLABEL,	 0 , 0, 0, (APTR)0},
	{  NM_ITEM, NULL,		 0 , 0, 0, (APTR)MEN_CLEARBUF_CONTENTS},
	{  NM_ITEM, NM_BARLABEL,	 0 , 0, 0, (APTR)0},
	{  NM_ITEM, NULL,		 0 , 0, 0, (APTR)MEN_QUITBUF},

	{ NM_END, 0,			 0 , 0, 0, (APTR)0}
};

	/* Some private data (render info & window). */

STATIC struct Window		*BufferWindow;
STATIC struct Screen		*BufferScreen;
STATIC struct Menu		*BufferMenuStrip;

STATIC struct RastPort		*BPort;
STATIC APTR			 BufferVisualInfo;

STATIC LONG			 NumBufferLines,
				 NumBufferColumns,
				 LastTopLine;

STATIC struct DrawInfo		*BufferDrawInfo;
STATIC struct Image		*BufferAmigaGlyph,
				*BufferCheckGlyph;

STATIC BYTE			 BufferTerminated;

STATIC WORD			 LocalTextFontWidth,
				 LocalTextFontHeight,
				 LocalTextFontBase;

STATIC struct TTextAttr		 LocalTextFont;
STATIC UBYTE __far		 LocalTextFontName[MAX_FILENAME_LENGTH];

STATIC struct TTextAttr		 LocalUserFont;
STATIC UBYTE __far		 LocalUserFontName[MAX_FILENAME_LENGTH];

STATIC LONG			 TopLine = -1,
				 DisplayedLines;

STATIC struct SearchInfo	*BufferSearchInfo;
STATIC BYTE			 SearchForward	= TRUE,
				 IgnoreCase	= TRUE;

STATIC struct Gadget		*Scroller,
				*UpArrow,
				*DownArrow;

STATIC struct Image		*UpImage,
				*DownImage;

STATIC UWORD			*BufferLineWidths,
				*BufferLineOffsets,
				*BufferColumnOffsets;

	/* DeleteScroller(VOID):
	 *
	 *	Delete scroller and arrow objects.
	 */

STATIC VOID
DeleteScroller(VOID)
{
	if(Scroller)
	{
		DisposeObject(Scroller);

		Scroller = NULL;
	}

	if(UpArrow)
	{
		DisposeObject(UpArrow);

		UpArrow = NULL;
	}

	if(DownArrow)
	{
		DisposeObject(DownArrow);

		DownArrow = NULL;
	}

	if(UpImage)
	{
		DisposeObject(UpImage);

		UpImage = NULL;
	}

	if(DownImage)
	{
		DisposeObject(DownImage);

		DownImage = NULL;
	}
}

	/* CreateScroller(LONG Height):
	 *
	 *	Create scroller and arrow objects.
	 */

STATIC BYTE __regargs
CreateScroller(LONG Height)
{
	struct DrawInfo	*DrawInfo;
	BYTE		 Result = FALSE;

	if(DrawInfo = GetScreenDrawInfo(BufferScreen))
	{
		if(UpImage = (struct Image *)NewObject(NULL,"sysiclass",
			SYSIA_Size,	SYSISIZE_MEDRES,
			SYSIA_Which,	UPIMAGE,
			SYSIA_DrawInfo,	DrawInfo,
		TAG_DONE))
		{
			if(DownImage = (struct Image *)NewObject(NULL,"sysiclass",
				SYSIA_Size,	SYSISIZE_MEDRES,
				SYSIA_Which,	DOWNIMAGE,
				SYSIA_DrawInfo,	DrawInfo,
			TAG_DONE))
			{
				LONG ScrollerHeight,LeftEdge;

				ScrollerHeight = Height - 2 * ARROW_HEIGHT;

				LeftEdge = BufferScreen -> Width - ARROW_WIDTH;

				if(Scroller = NewObject(NULL,"propgclass",
					GA_ID,		GAD_SCROLLER,

					GA_Top,		0,
					GA_Left,	LeftEdge,
					GA_Width,	ARROW_WIDTH,
					GA_Height,	ScrollerHeight,
					GA_Immediate,	TRUE,
					GA_FollowMouse,	TRUE,
					GA_RelVerify,	TRUE,

					PGA_Freedom,	FREEVERT,
					PGA_NewLook,	TRUE,

					PGA_Visible,	1,
					PGA_Total,	1,
				TAG_DONE))
				{
					STATIC struct TagItem ArrowMappings[] = { GA_ID,GA_ID,TAG_END };

					if(UpArrow = NewObject(NULL,"buttongclass",
						GA_ID,		GAD_UP,
						GA_Image,	UpImage,
						GA_Left,	LeftEdge,
						GA_Top,		ScrollerHeight,
						GA_Height,	ARROW_HEIGHT,
						GA_Width,	ARROW_WIDTH,
						GA_Immediate,	TRUE,
						GA_RelVerify,	TRUE,
						GA_Previous,	Scroller,

						ICA_TARGET,	ICTARGET_IDCMP,
						ICA_MAP,	ArrowMappings,
					TAG_DONE))
					{
						if(DownArrow = NewObject(NULL,"buttongclass",
							GA_ID,		GAD_DOWN,
							GA_Image,	DownImage,
							GA_Left,	LeftEdge,
							GA_Top,		ScrollerHeight + ARROW_HEIGHT,
							GA_Height,	ARROW_HEIGHT,
							GA_Width,	ARROW_WIDTH,
							GA_Immediate,	TRUE,
							GA_RelVerify,	TRUE,
							GA_Previous,	UpArrow,

							ICA_TARGET,	ICTARGET_IDCMP,
							ICA_MAP,	ArrowMappings,
						TAG_DONE))
							Result = TRUE;
					}
				}
			}
		}

		FreeScreenDrawInfo(BufferScreen,DrawInfo);
	}

	if(!Result)
		DeleteScroller();

	return(Result);
}

	/* PrintLine(STRPTR Buffer,LONG LineNumber):
	 *
	 *	Print a line at a given line number in the displayed area.
	 */

STATIC VOID __regargs
PrintLine(STRPTR Buffer,LONG LineNumber)
{
	WORD Length = Buffer[-1];

		/* Print the text. */

	if(Length)
	{
		Move(BPort,0,BufferLineOffsets[LineNumber] + LocalTextFontBase);

		if(Length > NumBufferColumns)
			Length = NumBufferColumns;

		Text(BPort,Buffer,Length);
	}

		/* The line doesn't exactly fill the displayed line,
		 * so erase the remaining columns.
		 */

	if(Length < BufferLineWidths[LineNumber])
	{
		SetAPen(BPort,0);
		RectFill(BPort,BufferColumnOffsets[Length],BufferLineOffsets[LineNumber],BufferColumnOffsets[BufferLineWidths[LineNumber]] - 1,BufferLineOffsets[LineNumber + 1] - 1);
		SetAPen(BPort,1);
	}

	BufferLineWidths[LineNumber] = Length;
}

	/* RedrawScreen(LONG FirstLine):
	 *
	 *	Redraw the contents of the entire screen and return the
	 *	number of lines actually drawn.
	 */

STATIC LONG __regargs
RedrawScreen(LONG FirstLine)
{
	LONG i,Last,Line = 0,Result;

	ObtainSemaphore(BufferSemaphore);

		/* Determine last line to display. */

	if((Last = FirstLine + NumBufferLines) >= Lines)
		Last = Lines;

	Result = Last - FirstLine;

	if(Lines)
	{
		if(LastTopLine != -1)
		{
			LONG Delta = FirstLine - LastTopLine;

			if(ABS(Delta) < NumBufferLines)
			{
					/* No change? */

				if(!Delta)
				{
					ReleaseSemaphore(BufferSemaphore);

					return(Result);
				}
				else
				{
					LastTopLine = FirstLine;

						/* Scrolled up? */

					if(Delta < 0)
					{
						for(i = NumBufferLines - 1 ; i >= -Delta ; i--)
							BufferLineWidths[i] = BufferLineWidths[i + Delta];

						ClipBlit(BPort,0,0,BPort,0,BufferLineOffsets[-Delta],BufferColumnOffsets[NumBufferColumns],BufferLineOffsets[NumBufferLines + Delta],MINTERM_COPY);

						Last = FirstLine - Delta;
					}
					else
					{
						for(i = Delta ; i < NumBufferLines ; i++)
							BufferLineWidths[i - Delta] = BufferLineWidths[i];

							/* Scrolled down. */

						ClipBlit(BPort,0,BufferLineOffsets[Delta],BPort,0,0,BufferColumnOffsets[NumBufferColumns],BufferLineOffsets[NumBufferLines - Delta],MINTERM_COPY);

						FirstLine += NumBufferLines - Delta;

						Line = NumBufferLines - Delta;
					}
				}
			}
			else
				LastTopLine = FirstLine;
		}
		else
			LastTopLine = FirstLine;

		if(BufferLines)
		{
			for(i = FirstLine ; i < Last ; i++)
				PrintLine(BufferLines[i],Line++);
		}
	}

	ReleaseSemaphore(BufferSemaphore);

		/* We didn't fill the whole screen, so clear the rest. */

	if(Result < NumBufferLines)
	{
		WORD i;

		for(i = Result ; i < NumBufferLines ; i++)
			BufferLineWidths[i] = 0;

		SetAPen(BPort,0);
		RectFill(BPort,0,BufferLineOffsets[Result],BufferColumnOffsets[NumBufferColumns] - 1,BufferLineOffsets[NumBufferLines] - 1);
		SetAPen(BPort,1);
	}

	return(Result);
}

	/* MarkArea(LONG Column,LONG Line,LONG Length):
	 *
	 *	Mark an area in the term Buffer window.
	 */

STATIC VOID __regargs
MarkArea(LONG Column,LONG Line,LONG Length)
{
	STATIC LONG OldColumn = -1,OldLine = -1,OldLength = -1;

	if(OldColumn != -1)
		ClipBlit(BPort,0,0,BPort,BufferColumnOffsets[OldColumn],BufferLineOffsets[OldLine],BufferColumnOffsets[OldLength],LocalTextFontHeight,MINTERM_NOT_C);

	if(Column != -1)
	{
		if(OldColumn != Column || OldLine != Line || OldLength != Length)
			ClipBlit(BPort,0,0,BPort,BufferColumnOffsets[Column],BufferLineOffsets[Line],BufferColumnOffsets[Length],LocalTextFontHeight,MINTERM_NOT_C);
	}

	OldColumn	= Column;
	OldLine		= Line;
	OldLength	= Length;
}

	/* BufferClipPage(struct BlockMarker *Marker):
	 *
	 *	Send the marked area to the clipboard.
	 */

STATIC VOID __inline
BufferClipPage(struct BlockMarker *Marker)
{
	if(BufferLines)
	{
		struct IFFHandle *Handle;

		if(Handle = AllocIFF())
		{
			if(Handle -> iff_Stream = (ULONG)OpenClipboard(Config -> ClipConfig -> ClipboardUnit))
			{
				InitIFFasClip(Handle);

				if(!OpenIFF(Handle,IFFF_WRITE))
				{
					if(!PushChunk(Handle,ID_FTXT,ID_FORM,IFFSIZE_UNKNOWN))
					{
						if(!PushChunk(Handle,0,ID_CHRS,IFFSIZE_UNKNOWN))
						{
							LONG	Lines = Marker -> LastLine - Marker -> FirstLine - 1,
								i;

							if(LINE_WIDTH(BufferLines[Marker -> FirstLine]) > Marker -> FirstColumn)
								WriteTrimmedString(Handle,&BufferLines[Marker -> FirstLine][Marker -> FirstColumn],LINE_WIDTH(BufferLines[Marker -> FirstLine]) - Marker -> FirstColumn);

							WriteChunkBytes(Handle,"\n",1);

							if(Lines > 0)
							{
								for(i = 0 ; i < Lines ; i++)
								{
									if(LINE_WIDTH(BufferLines[Marker -> FirstLine + 1 + i]))
										WriteTrimmedString(Handle,BufferLines[Marker -> FirstLine + 1 + i],LINE_WIDTH(BufferLines[Marker -> FirstLine + 1 + i]));

									WriteChunkBytes(Handle,"\n",1);
								}
							}

							if(Marker -> LastColumn > LINE_WIDTH(BufferLines[Marker -> LastLine]))
								WriteTrimmedString(Handle,BufferLines[Marker -> LastLine],LINE_WIDTH(BufferLines[Marker -> LastLine]));
							else
								WriteTrimmedString(Handle,BufferLines[Marker -> LastLine],Marker -> LastColumn);

							WriteChunkBytes(Handle,"\n",1);

							PopChunk(Handle);
						}

						PopChunk(Handle);
					}

					CloseIFF(Handle);
				}

				CloseClipboard((struct ClipboardHandle *)Handle -> iff_Stream);
			}

			FreeIFF(Handle);
		}
	}
}

	/* BufferClip(VOID):
	 *
	 *	Start buffer marking process.
	 */

STATIC VOID
BufferClip(VOID)
{
	struct BlockMarker	*Marker;
	LONG			 FirstX = BufferWindow -> MouseX / LocalTextFontWidth,
				 FirstY = BufferWindow -> MouseY / LocalTextFontHeight;

	if(Marker = BM_SetMark(BufferWindow -> RPort,ToggleSelect,ToggleSelect,NumBufferColumns,NumBufferLines,0,0,TopLine,Lines,BufferWindow -> MouseX / LocalTextFontWidth,BufferWindow -> MouseY / LocalTextFontHeight,LocalTextFontWidth,LocalTextFontHeight))
	{
		struct IntuiMessage	*Massage;
		ULONG			 Code,IClass;
		BYTE			 Done = FALSE,Aborted = FALSE;
		LONG			 PlusX = LocalTextFontWidth - 1,
					 MouseX,MouseY,
					 Delta = 0;

		ReportMouse(TRUE,BufferWindow);

		while(!Done)
		{
			WaitPort(BufferWindow -> UserPort);

			while(Massage = (struct IntuiMessage *)GetMsg(BufferWindow -> UserPort))
			{
				IClass	= Massage -> Class;
				Code	= Massage -> Code;
				MouseX	= Massage -> MouseX;
				MouseY	= Massage -> MouseY;

				ReplyMsg(Massage);

				if(IClass == IDCMP_INACTIVEWINDOW)
				{
					Done = Aborted = TRUE;

					break;
				}

				if(IClass == IDCMP_INTUITICKS && Delta != 0)
				{
					if(BufferLines)
					{
						if((Delta > 0 && TopLine + NumBufferLines < Lines) || (Delta < 0 && TopLine > 0))
						{
							if(Delta < 0)
							{
								WORD i;

								for(i = NumBufferLines - 1 ; i > -Delta ; i--)
									BufferLineWidths[i] = BufferLineWidths[i + Delta];

								if(DisplayedLines)
									ClipBlit(BPort,0,0,BPort,0,LocalTextFontHeight,BufferColumnOffsets[NumBufferColumns],BufferLineOffsets[NumBufferLines - 1],MINTERM_COPY);

								BufferLineWidths[0] = NumBufferColumns;

								PrintLine(BufferLines[--TopLine],0);
							}
							else
							{
								WORD i;

								for(i = Delta ; i < NumBufferLines ; i++)
									BufferLineWidths[i - Delta] = BufferLineWidths[i];

								if(DisplayedLines)
									ClipBlit(BPort,0,LocalTextFontHeight,BPort,0,0,BufferColumnOffsets[NumBufferColumns],BufferLineOffsets[NumBufferLines - 1],MINTERM_COPY);

								BufferLineWidths[NumBufferLines - 1] = NumBufferColumns;

								PrintLine(BufferLines[TopLine + NumBufferLines],NumBufferLines - 1);

								TopLine++;
							}

							Marker -> Top += Delta;
							Marker -> LastY -= Delta;

							BM_ExtendMark(Marker,(MouseX + PlusX) / LocalTextFontWidth,MouseY / LocalTextFontHeight,Delta);
						}
						else
							Delta = 0;
					}
				}

				if(IClass == IDCMP_MOUSEBUTTONS && (Code & IECODE_UP_PREFIX))
				{
					BM_Draw(Marker,Marker -> Unselect);

					Done = TRUE;

					break;
				}

				if(IClass == IDCMP_MOUSEMOVE)
				{
					BM_ExtendMark(Marker,(MouseX + PlusX) / LocalTextFontWidth,MouseY / LocalTextFontHeight,0);

					if(MouseY < 1)
					{
						if(TopLine > 0)
							Delta = -1;
					}
					else
					{
						if(MouseY >= BufferWindow -> Height - 1 && TopLine + NumBufferLines < Lines)
							Delta = 1;
					}

					while(Delta)
					{
						MouseX	= Window -> MouseX;
						MouseY	= Window -> MouseY;

						if((Delta < 0 && MouseY > 0) || (Delta > 0 && MouseY < BufferWindow -> Height - 1))
							break;
						else
						{
							if(BufferLines)
							{
								if((Delta > 0 && TopLine + NumBufferLines < Lines) || (Delta < 0 && TopLine > 0))
								{
									if(Delta < 0)
									{
										WORD i;

										for(i = NumBufferLines - 1 ; i > -Delta ; i--)
											BufferLineWidths[i] = BufferLineWidths[i + Delta];

										if(DisplayedLines)
											ClipBlit(BPort,0,0,BPort,0,LocalTextFontHeight,BufferColumnOffsets[NumBufferColumns],BufferLineOffsets[NumBufferLines - 1],MINTERM_COPY);

										BufferLineWidths[0] = NumBufferColumns;

										PrintLine(BufferLines[--TopLine],0);
									}
									else
									{
										WORD i;

										for(i = Delta ; i < NumBufferLines ; i++)
											BufferLineWidths[i - Delta] = BufferLineWidths[i];

										if(DisplayedLines)
											ClipBlit(BPort,0,LocalTextFontHeight,BPort,0,0,BufferColumnOffsets[NumBufferColumns],BufferLineOffsets[NumBufferLines - 1],MINTERM_COPY);

										BufferLineWidths[NumBufferLines - 1] = NumBufferColumns;

										PrintLine(BufferLines[TopLine + NumBufferLines],NumBufferLines - 1);

										TopLine++;
									}

									Marker -> Top	+= Delta;
									Marker -> LastY	-= Delta;

									BM_ExtendMark(Marker,(MouseX + PlusX) / LocalTextFontWidth,MouseY / LocalTextFontHeight,Delta);

									SetGadgetAttrs(Scroller,BufferWindow,NULL,
										PGA_Top,TopLine,
									TAG_DONE);
								}
								else
									break;
							}
							else
								break;
						}
					}

					Delta = 0;
				}
			}
		}

		ReportMouse(FALSE,BufferWindow);

		while(Massage = (struct IntuiMessage *)GetMsg(BufferWindow -> UserPort))
			ReplyMsg(Massage);

		if(!Aborted)
		{
			if(FirstX != BufferWindow -> MouseX / LocalTextFontWidth || FirstY != BufferWindow -> MouseY / LocalTextFontHeight)
			{
				SetWait(BufferWindow);

				if(Marker -> FirstColumn == Marker -> Width)
				{
					Marker -> FirstLine++;

					Marker -> FirstColumn = 0;
				}

				if(Marker -> LastColumn == 0)
				{
					Marker -> LastLine--;

					Marker -> LastColumn = Marker -> Width;
				}

				if(Marker -> FirstLine <= Marker -> LastLine)
				{
					if(Marker -> FirstLine != Marker -> LastLine || Marker -> FirstColumn != Marker -> LastColumn)
					{
						if(BufferLines)
						{
							if(Marker -> FirstLine == Marker -> LastLine)
								SaveClip(&BufferLines[Marker -> FirstLine][Marker -> FirstColumn],Marker -> LastColumn - Marker -> FirstColumn);
							else
								BufferClipPage(Marker);
						}
					}
				}

				ClrWait(BufferWindow);
			}
		}

		FreeVecPooled(Marker);
	}
}

	/* StartBufferSearch(BYTE NewSearchString):
	 *
	 *	Search the buffer for a string.
	 */

STATIC VOID __regargs
StartBufferSearch(BYTE NewSearchString)
{
	LT_LockWindow(BufferWindow);

	if(Lines)
	{
		STATIC UBYTE __far SearchBuffer[256];

		BYTE StartSearch = TRUE;

		if(NewSearchString || !SearchBuffer[0])
		{
			struct Hook HistoryHook;

			HistoryHook . h_Data = &TextBufferHistory;

			if(GetSearchString(BufferWindow,&BufferTerminated,SearchBuffer,&HistoryHook,&SearchForward,&IgnoreCase))
			{
				if(SearchBuffer[0])
				{
					if(BufferSearchInfo)
						DeleteSearchInfo(BufferSearchInfo);

					BufferSearchInfo = CreateSearchInfo(SearchBuffer,SearchForward,IgnoreCase);
				}
				else
					StartSearch = FALSE;
			}
			else
				StartSearch = FALSE;
		}
		else
		{
			if(!BufferSearchInfo)
				BufferSearchInfo = CreateSearchInfo(SearchBuffer,TRUE,TRUE);
		}

		if(StartSearch && BufferSearchInfo)
		{
			LONG LineNumber;

			ObtainSemaphore(BufferSemaphore);

			LineNumber = SearchTextBuffer(BufferSearchInfo);

			ReleaseSemaphore(BufferSemaphore);

			if(LineNumber == -1)
			{
				BlockWindows();

				MyEasyRequest(BufferWindow,LocaleString(MSG_TERMBUFFER_DID_NOT_FIND_TXT),LocaleString(MSG_GLOBAL_CONTINUE_TXT),SearchBuffer);

				ReleaseWindows();

				FlushMsg(BufferWindow);

				BufferSearchInfo -> FoundY = -1;

				MarkArea(-1,-1,-1);
			}
			else
			{
				if(LineNumber < TopLine)
				{
					MarkArea(-1,-1,-1);

					DisplayedLines = RedrawScreen(TopLine = LineNumber);

					SetGadgetAttrs(Scroller,BufferWindow,NULL,
						PGA_Top,TopLine,
					TAG_DONE);
				}
				else
				{
					if(LineNumber > TopLine + DisplayedLines - 1)
					{
						MarkArea(-1,-1,-1);

						if(LineNumber >= Lines - NumBufferLines)
						{
							LONG NewCurrentLine;

							if((NewCurrentLine = Lines - NumBufferLines) < 0)
								NewCurrentLine = 0;

							if(TopLine != NewCurrentLine)
								DisplayedLines = RedrawScreen(TopLine = NewCurrentLine);
						}
						else
							DisplayedLines = RedrawScreen(TopLine = LineNumber);

						SetGadgetAttrs(Scroller,BufferWindow,NULL,
							PGA_Top,TopLine,
						TAG_DONE);
					}
				}

				MarkArea(BufferSearchInfo -> FoundX,LineNumber - TopLine,BufferSearchInfo -> PatternWidth);
			}
		}
	}
	else
		MyEasyRequest(BufferWindow,LocaleString(MSG_GLOBAL_NOTHING_IN_THE_BUFFER_TXT),LocaleString(MSG_GLOBAL_CONTINUE_TXT));

	LT_UnlockWindow(BufferWindow);
}

STATIC VOID __stdargs
BufferDestructor(struct MsgItem *Item)
{
	Signal(BufferTask,SIG_HANDSHAKE);
}

STATIC BOOLEAN __regargs
HandleBuffer(LONG TitleOffset)
{
	ULONG			 LastSeconds	= 0,
				 LastMicros	= 0,
				 Seconds,
				 Micros;
	BOOLEAN			 ClickAndActivate,
				 UpdatePercent	= TRUE;

	ULONG			 SignalSet;

	struct IntuiMessage	*Massage;
	ULONG			 MsgClass;
	UWORD			 MsgCode,MsgQualifier,LastQualifier = NULL;
	WORD			 MouseX,MouseY;

	UBYTE			 Char,LastChar = 0;
	LONG			 LastWidth = 0;

	UBYTE			 PercentBuffer[80];

	struct TagItem		*TagList;

	STRPTR			 PercentTemplate;

	BOOLEAN			 RingBack = FALSE;

	if(LocaleBase)
		PercentTemplate = "%lD/%lD (%ld%%)";
	else
		PercentTemplate = "%ld/%ld (%ld%%)";

	do
	{
			/* Show where we are. */

		if(Lines && UpdatePercent)
		{
			LONG Width,Len;

			SetAPen(BufferScreen -> BarLayer -> rp,0);
			SetBPen(BufferScreen -> BarLayer -> rp,1);
			SetDrMd(BufferScreen -> BarLayer -> rp,JAM2);

			SPrintf(PercentBuffer,PercentTemplate,TopLine,Lines > NumBufferLines ? Lines - NumBufferLines : 0,(100 * (TopLine + DisplayedLines)) / Lines);

			Len = strlen(PercentBuffer);

			Move(BufferScreen -> BarLayer -> rp,TitleOffset,BufferScreen -> BarLayer -> rp -> Font -> tf_Baseline + 1);
			Text(BufferScreen -> BarLayer -> rp,PercentBuffer,Len);

			Width = TextLength(BufferScreen -> BarLayer -> rp,PercentBuffer,Len);

			if(LastWidth > Width)
			{
				SetAPen(BufferScreen -> BarLayer -> rp,1);

				RectFill(BufferScreen -> BarLayer -> rp,TitleOffset + Width,1,TitleOffset + LastWidth - 1,BufferScreen -> BarLayer -> rp -> Font -> tf_YSize);
			}

			LastWidth = Width;

			UpdatePercent = FALSE;
		}

		SignalSet = Wait(SIG_KILL | SIG_TOFRONT | SIG_UPDATE | SIG_MOVEUP | PORTMASK(BufferWindow -> UserPort));

			/* Leave the town? */

		if(SignalSet & SIG_KILL)
			BufferTerminated = RingBack = TRUE;

			/* Bring our window to the front. */

		if(SignalSet & SIG_TOFRONT)
			BumpWindow(BufferWindow);

			/* We've got one more line in the
			 * buffer.
			 */

		if(SignalSet & SIG_UPDATE)
		{
			if(BufferLines && Lines)
			{
				if(Lines - TopLine > DisplayedLines && DisplayedLines < NumBufferLines)
				{
					LONG i = TopLine + DisplayedLines;

					do
						PrintLine(BufferLines[i++],DisplayedLines++);
					while(DisplayedLines < NumBufferLines && i < Lines);
				}
			}

			SetGadgetAttrs(Scroller,BufferWindow,NULL,
				PGA_Total,	Lines,
				PGA_Visible,	DisplayedLines,
			TAG_DONE);

			UpdatePercent = TRUE;

			Signal(ThisProcess,SIG_HANDSHAKE);
		}

			/* The contents of the buffer have moved
			 * up a line.
			 */

		if(SignalSet & SIG_MOVEUP)
		{
			if(TopLine > 0)
			{
				LastTopLine = --TopLine;

				SetGadgetAttrs(Scroller,BufferWindow,NULL,
					PGA_Top,TopLine,
				TAG_DONE);
			}
			else
			{
				LONG i;

				MarkArea(-1,-1,-1);

				for(i = 0 ; i < NumBufferLines - 1 ; i++)
					BufferLineWidths[i] = BufferLineWidths[i + 1];

				ClipBlit(BPort,0,BufferLineOffsets[1],BPort,0,0,BufferColumnOffsets[NumBufferColumns],BufferLineOffsets[NumBufferLines - 1],MINTERM_COPY);

				PrintLine(BufferLines[NumBufferLines - 1],NumBufferLines - 1);
			}

			UpdatePercent = TRUE;

			Signal(ThisProcess,SIG_HANDSHAKE);
		}

			/* Process the incoming window
			 * input.
			 */

		while(Massage = (struct IntuiMessage *)GetMsg(BufferWindow -> UserPort))
		{
			MsgClass	= Massage -> Class;
			MsgCode		= Massage -> Code;
			MsgQualifier	= Massage -> Qualifier;
			MouseX		= Massage -> MouseX;
			MouseY		= Massage -> MouseY;
			TagList		= (struct TagItem *)Massage -> IAddress;
			Seconds		= Massage -> Seconds;
			Micros		= Massage -> Micros;

			ClickAndActivate = FALSE;

			if(Seconds == LastSeconds && Micros == LastMicros)
			{
				if(Massage -> Class == IDCMP_ACTIVEWINDOW || Massage -> Class == IDCMP_MOUSEBUTTONS)
					ClickAndActivate = TRUE;
			}

			LastSeconds	= Seconds;
			LastMicros	= Micros;

				/* This hack is necessary to obtain the
				 * character codes generated for the cursor
				 * keys. A control or alternate qualifier
				 * would spoil the result (i.e. we would
				 * not get a valid key code).
				 */

			Massage -> Qualifier = NULL;

			Char = KeyConvert(Massage,NULL,NULL);

				/* Just in case anybody needs it... */

			Massage -> Qualifier = MsgQualifier;

			ReplyMsg((struct Message *)Massage);

			if(MsgClass == IDCMP_ACTIVEWINDOW)
				UpdatePercent = TRUE;

			if(MsgClass == IDCMP_IDCMPUPDATE)
			{
				switch(GetTagData(GA_ID,0,TagList))
				{
					case GAD_UP:

						if(TopLine > 0)
						{
							TopLine--;

							MarkArea(-1,-1,-1);

							SetGadgetAttrs(Scroller,BufferWindow,NULL,
								PGA_Top,TopLine,
							TAG_DONE);

							DisplayedLines = RedrawScreen(TopLine);

							UpdatePercent = TRUE;
						}

						break;

					case GAD_DOWN:

						if(TopLine + NumBufferLines < Lines)
						{
							TopLine++;

							MarkArea(-1,-1,-1);

							SetGadgetAttrs(Scroller,BufferWindow,NULL,
								PGA_Top,TopLine,
							TAG_DONE);

							DisplayedLines = RedrawScreen(TopLine);

							UpdatePercent = TRUE;
						}

						break;
				}
			}

			if(MsgClass == IDCMP_MOUSEMOVE || MsgClass == IDCMP_GADGETDOWN || MsgClass == IDCMP_GADGETUP)
			{
				LONG Position;

				GetAttr(PGA_Top,Scroller,(ULONG *)&Position);

				if(Position != TopLine)
				{
					MarkArea(-1,-1,-1);

					DisplayedLines = RedrawScreen(TopLine = Position);

					UpdatePercent = TRUE;
				}
			}

			if(MsgClass == IDCMP_RAWKEY)
			{
				if(MsgCode == HELP_CODE)
					GuideDisplay(CONTEXT_TEXTBUFFER);

				if(LastChar)
				{
					if((MsgCode & IECODE_UP_PREFIX) || !(LastQualifier & IEQUALIFIER_REPEAT))
					{
						SetGadgetAttrs(Scroller,BufferWindow,NULL,
							PGA_Top,TopLine,
						TAG_DONE);

						UpdatePercent = TRUE;
					}
				}

				if(LastChar = Char)
				{
						/* Use the numeric keypad keys to
						 * move through the buffer.
						 */

					if(MsgQualifier & IEQUALIFIER_NUMERICPAD)
					{
							/* Remove the numpad qualifier. */

						MsgQualifier &= ~IEQUALIFIER_NUMERICPAD;

						switch(Char - '0')
						{
								/* Jump to bottom. */

							case 1: Char = CDN;
								MsgQualifier |= IEQUALIFIER_CONTROL;
								break;

								/* Jump to top. */

							case 7: Char = CUP;
								MsgQualifier |= IEQUALIFIER_CONTROL;
								break;

								/* Move one page down. */

							case 3: Char = CDN;
								MsgQualifier |= IEQUALIFIER_LSHIFT;
								break;

								/* Move one page up. */

							case 9: Char = CUP;
								MsgQualifier |= IEQUALIFIER_LSHIFT;
								break;

								/* Move one line down. */

							case 2: Char = CDN;
								break;

								/* Move one line up. */

							case 8: Char = CUP;
								break;
						}
					}

						/* Check cursor keys. */

					switch(Char)
					{
							/* Scroll the buffer up. */

						case CUP:

							if(MsgQualifier & (IEQUALIFIER_CONTROL | IEQUALIFIER_LALT | IEQUALIFIER_RALT))
							{
								MarkArea(-1,-1,-1);

								if(TopLine)
								{
									DisplayedLines = RedrawScreen(TopLine = 0);

									LastChar = 0;

									SetGadgetAttrs(Scroller,BufferWindow,NULL,
										PGA_Top,TopLine,
									TAG_DONE);

									UpdatePercent = TRUE;
								}

								break;
							}

							if(MsgQualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
							{
								LONG NewCurrentLine;

								if((NewCurrentLine = TopLine - NumBufferLines) < 0)
									NewCurrentLine = 0;

								MarkArea(-1,-1,-1);

								if(NewCurrentLine != TopLine)
								{
									DisplayedLines = RedrawScreen(TopLine = NewCurrentLine);

									LastChar = 0;

									SetGadgetAttrs(Scroller,BufferWindow,NULL,
										PGA_Top,TopLine,
									TAG_DONE);

									UpdatePercent = TRUE;
								}

								break;
							}

							if(TopLine)
							{
								MarkArea(-1,-1,-1);

								DisplayedLines = RedrawScreen(--TopLine);

								UpdatePercent = TRUE;
							}

							break;

							/* Scroll the buffer down. */

						case CDN:

							if(MsgQualifier & (IEQUALIFIER_CONTROL | IEQUALIFIER_LALT | IEQUALIFIER_RALT))
							{
								LONG NewCurrentLine;

								if((NewCurrentLine = Lines - NumBufferLines) < 0)
									NewCurrentLine = 0;

								MarkArea(-1,-1,-1);

								if(TopLine != NewCurrentLine)
								{
									DisplayedLines = RedrawScreen(TopLine = NewCurrentLine);

									LastChar = 0;

									SetGadgetAttrs(Scroller,BufferWindow,NULL,
										PGA_Top,TopLine,
									TAG_DONE);

									UpdatePercent = TRUE;
								}

								break;
							}

							if(MsgQualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
							{
								LONG NewCurrentLine;

								if((NewCurrentLine = TopLine + (2 * NumBufferLines)) > Lines)
									NewCurrentLine = Lines;

								if((NewCurrentLine = NewCurrentLine - NumBufferLines) < 0)
									NewCurrentLine = 0;

								MarkArea(-1,-1,-1);

								if(NewCurrentLine != TopLine)
								{
									DisplayedLines = RedrawScreen(TopLine = NewCurrentLine);

									LastChar = 0;

									SetGadgetAttrs(Scroller,BufferWindow,NULL,
										PGA_Top,TopLine,
									TAG_DONE);

									UpdatePercent = TRUE;
								}

								break;
							}

							if(TopLine + NumBufferLines < Lines)
							{
								MarkArea(-1,-1,-1);

								DisplayedLines = RedrawScreen(++TopLine);

								UpdatePercent = TRUE;
							}

							break;
					}

					LastQualifier = MsgQualifier;
				}
				else
					LastQualifier = NULL;

				continue;
			}

				/* User hit a mouse button. */

			if(MsgClass == IDCMP_MOUSEBUTTONS && (!ClickAndActivate || MsgCode != SELECTDOWN) && !(MsgCode & IECODE_UP_PREFIX))
			{
				MarkArea(-1,-1,-1);

					/* Reasonable dimensions? */

				if(MouseY / LocalTextFontHeight < DisplayedLines && MouseX / LocalTextFontWidth < NumBufferColumns)
				{
					ObtainSemaphore(BufferSemaphore);

					BufferClip();

					UpdatePercent = TRUE;

					LastTopLine = TopLine;

					ReleaseSemaphore(BufferSemaphore);

					if(MsgQualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
					{
						struct DataMsg Msg;

						InitMsgItem(&Msg,BufferDestructor);

						Msg . Type = DATAMSGTYPE_WRITECLIP;
						Msg . Size = Config -> ClipConfig -> ClipboardUnit;

						Forbid();

						ClrSignal(SIG_HANDSHAKE);

						PutMsgItem(SpecialQueue,(struct MsgItem *)&Msg);

						Wait(SIG_HANDSHAKE);

						Permit();
					}

					break;
				}
			}

			if(MsgClass == IDCMP_MENUPICK)
			{
				struct MenuItem *MenuItem;

				while(MsgCode != MENUNULL)
				{
					MenuItem = ItemAddress(BufferMenuStrip,MsgCode);

					switch((ULONG)GTMENUITEM_USERDATA(MenuItem))
					{
						case MEN_SEARCH:

							StartBufferSearch(TRUE);

							UpdatePercent = TRUE;

							break;

						case MEN_REPEAT:

							StartBufferSearch(FALSE);

							UpdatePercent = TRUE;

							break;

						case MEN_GOTO:

							if(Window)
								BumpWindow(Window);

							break;

						case MEN_QUITBUF:

							BufferTerminated = TRUE;
							break;

						case MEN_CLEARBUF_CONTENTS:

							if(Lines)
							{
								UpdatePercent = TRUE;

								LT_LockWindow(BufferWindow);

								if(MsgQualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
								{
									FreeBuffer();

									LT_UnlockWindow(BufferWindow);

									FlushMsg(BufferWindow);

									DisplayedLines = RedrawScreen(TopLine = 0);

									SetGadgetAttrs(Scroller,BufferWindow,NULL,
										PGA_Top,	TopLine,
										PGA_Total,	Lines,
										PGA_Visible,	NumBufferLines,
									TAG_DONE);

									continue;
								}
								else
								{
									if(MyEasyRequest(BufferWindow,LocaleString(MSG_TERMBUFFER_BUFFER_STILL_HOLDS_LINES_TXT),LocaleString(MSG_GLOBAL_YES_NO_TXT),Lines))
									{
										FreeBuffer();

										LT_UnlockWindow(BufferWindow);

										FlushMsg(BufferWindow);

										DisplayedLines = RedrawScreen(TopLine = 0);

										SetGadgetAttrs(Scroller,BufferWindow,NULL,
											PGA_Top,	TopLine,
											PGA_Total,	Lines,
											PGA_Visible,	NumBufferLines,
										TAG_DONE);

										continue;
									}
								}

								LT_UnlockWindow(BufferWindow);

								FlushMsg(BufferWindow);
							}

							break;
					}

					MsgCode = MenuItem -> NextSelect;
				}
			}

		}
	}
	while(!BufferTerminated);

	return(RingBack);
}

	/* BufferServer():
	 *
	 *	Asynchronous task to display the data stored in the
	 *	scrollback display buffer.
	 */

STATIC VOID __saveds
BufferServer(VOID)
{
	BOOLEAN			 Defaults;

	struct ColorSpec	 ColorSpec[3];

	LONG			 Width,Height;
	ULONG			 DisplayMode;

	struct Rectangle	 DisplayClip;
	struct DimensionInfo	 DimensionInfo;

	struct TextFont		*LocalFont;

	struct Task		*Father;
	BOOLEAN			 RingBack = TRUE;

	Father = (struct Task *)SysBase -> ThisTask -> tc_UserData;

		/* Clone the global font data. */

	LocalTextFontWidth	= TextFontWidth;
	LocalTextFontHeight	= TextFontHeight;
	LocalTextFontBase	= TextFontBase;

	memcpy(&LocalTextFont,&TextAttr,sizeof(struct TTextAttr));

	LocalTextFont . tta_Name = LocalTextFontName;

	strcpy(LocalTextFontName,TextAttr . tta_Name);

	memcpy(&LocalUserFont,&UserFont,sizeof(struct TTextAttr));

	LocalUserFont . tta_Name = LocalUserFontName;

	strcpy(LocalUserFontName,UserFont . tta_Name);

		/* Reset top line index. */

	LastTopLine = -1;

		/* Up and running... */

	BufferTerminated = FALSE;

	Forbid();

	if(Window && DrawInfo)
	{
		UWORD Colour;

			/* Set up the startup colours for our buffer screen. */

		ColorSpec[0] . ColorIndex = 0;
		Colour = GetRGB4(Window -> WScreen -> ViewPort . ColorMap,DrawInfo -> dri_Pens[BACKGROUNDPEN]);

		ColorSpec[0] . Red		= (Colour >> 8) & 0xF;
		ColorSpec[0] . Green		= (Colour >> 4) & 0xF;
		ColorSpec[0] . Blue		= (Colour     ) & 0xF;

		ColorSpec[1] . ColorIndex = 1;
		Colour = GetRGB4(Window -> WScreen -> ViewPort . ColorMap,DrawInfo -> dri_Pens[TEXTPEN]);

		ColorSpec[1] . Red		= (Colour >> 8) & 0xF;
		ColorSpec[1] . Green		= (Colour >> 4) & 0xF;
		ColorSpec[1] . Blue		= (Colour     ) & 0xF;

		ColorSpec[2] . ColorIndex = -1;

		Defaults = FALSE;
	}
	else
		Defaults = TRUE;

	Permit();

		/* We'll use a fixed screen width, only the
		 * height is adapted from the main screen.
		 */

	DisplayMode = Config -> CaptureConfig -> BufferScreenMode;

	if(ModeNotAvailable(DisplayMode))
		DisplayMode = Config -> ScreenConfig -> DisplayMode;

	if(ModeNotAvailable(DisplayMode))
	{
		struct Screen *PubScreen = LockPubScreen(NULL);

		if(PubScreen)
		{
			DisplayMode = GetVPModeID(&PubScreen -> ViewPort);

			UnlockPubScreen(NULL,PubScreen);
		}
		else
			DisplayMode = DEFAULT_MONITOR_ID | HIRES_KEY;
	}

		/* Set up the actual width of the screen we want. */

	Width = Config -> CaptureConfig -> BufferWidth * LocalTextFontWidth + ARROW_WIDTH + 1;

		/* Get the mode dimension info. */

	if(GetDisplayInfoData(NULL,(APTR)&DimensionInfo,sizeof(struct DimensionInfo),DTAG_DIMS,DisplayMode))
	{
			/* Determine maximum text overscan width. */

		LONG TextWidth = DimensionInfo . TxtOScan . MaxX - DimensionInfo . TxtOScan . MinX + 1;

			/* Too small? */

		if(Width < DimensionInfo . MinRasterWidth)
			Width = DimensionInfo . MinRasterWidth;

			/* Far too large? */

		if(Width > DimensionInfo . MaxRasterWidth)
			Width = DimensionInfo . MaxRasterWidth;

			/* A bit too large? */

		if(Width > TextWidth)
			Width = TextWidth;

		if(LocalFont = OpenFont(&LocalTextFont))
		{
				/* Inquire the text overscan dimensions. */

			if(QueryOverscan(DisplayMode,&DisplayClip,OSCAN_TEXT))
			{
					/* Centre the buffer screen. */

				if(DisplayClip . MaxX - DisplayClip . MinX + 1 > Width)
				{
					LONG Differ = (DisplayClip . MaxX - DisplayClip . MinX + 1 - Width) / 2;

					switch(Config -> CaptureConfig -> BufferScreenPosition)
					{
						case SCREEN_LEFT:

							DisplayClip . MaxX = DisplayClip . MinX + Width;
							break;

						case SCREEN_RIGHT:

							DisplayClip . MinX = DisplayClip . MaxX - Width;
							break;

						case SCREEN_CENTRE:

							DisplayClip . MinX += Differ;
							DisplayClip . MaxX -= Differ;

							break;
					}
				}

					/* Open a single bitplane clone of the main screen. */

				if(BufferScreen = (struct Screen *)OpenScreenTags(NULL,
					SA_Title,	LocaleString(MSG_TERMBUFFER_TERM_BUFFER_TXT),
					SA_Depth,	1,
					SA_DClip,	&DisplayClip,
					SA_DisplayID,	DisplayMode,
					SA_Font,	&LocalUserFont,
					SA_Behind,	TRUE,
					SA_AutoScroll,	TRUE,

					Defaults ? TAG_IGNORE : SA_Colors,ColorSpec,
				TAG_END))
				{
					if(BufferVisualInfo = GetVisualInfo(BufferScreen,TAG_DONE))
					{
						LocalizeMenu(BufferMenu,MSG_TERMBUFFER_PROJECT_MEN);

						if(BufferMenuStrip = CreateMenus(BufferMenu,TAG_DONE))
						{
							if(BufferDrawInfo = GetScreenDrawInfo(BufferScreen))
							{
								if(!CreateMenuGlyphs(BufferScreen,BufferDrawInfo,&BufferAmigaGlyph,&BufferCheckGlyph))
								{
									FreeScreenDrawInfo(BufferScreen,BufferDrawInfo);

									BufferDrawInfo = NULL;
								}
							}

							if(LayoutMenus(BufferMenuStrip,BufferVisualInfo,
								BufferAmigaGlyph ? GTMN_AmigaKey :  TAG_IGNORE, BufferAmigaGlyph,
								BufferCheckGlyph ? GTMN_Checkmark : TAG_IGNORE, BufferCheckGlyph,

								GTMN_TextAttr,		&LocalUserFont,
								GTMN_NewLookMenus,	TRUE,
							TAG_DONE))
							{
								Height = (BufferScreen -> Height - (BufferScreen -> BarHeight + 2)) / LocalTextFontHeight;

									/* Open a cute window on our buffer screen. */

								if(BufferWindow = OpenWindowTags(NULL,
									WA_Top,		BufferScreen -> BarHeight + 2,
									WA_Left,	0,
									WA_Width,	BufferScreen -> Width,
									WA_Height,	Height * LocalTextFontHeight,
									WA_Backdrop,	TRUE,
									WA_Borderless,	TRUE,
									WA_SmartRefresh,FALSE,
									WA_CustomScreen,BufferScreen,
									WA_RMBTrap,	TRUE,
									WA_NewLookMenus,TRUE,
									WA_RptQueue,	1,
									WA_IDCMP,	IDCMP_IDCMPUPDATE | IDCMP_RAWKEY | IDCMP_INACTIVEWINDOW | IDCMP_ACTIVEWINDOW | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | IDCMP_GADGETUP | IDCMP_GADGETDOWN | IDCMP_MOUSEMOVE,

									BufferAmigaGlyph ? WA_AmigaKey  : TAG_IGNORE, BufferAmigaGlyph,
									BufferCheckGlyph ? WA_Checkmark : TAG_IGNORE, BufferCheckGlyph,
								TAG_DONE))
								{
									if(BufferLineWidths = (UWORD *)AllocVecPooled(2 * sizeof(UWORD) * (BufferWindow -> Height / LocalTextFontHeight + 1) + sizeof(UWORD) * (BufferWindow -> Width / LocalTextFontWidth + 1),MEMF_ANY | MEMF_CLEAR))
									{
										UWORD	Index;
										WORD	i;

										BufferLineOffsets	= &BufferLineWidths[BufferWindow -> Height / LocalTextFontHeight + 1];
										BufferColumnOffsets	= &BufferLineOffsets[BufferWindow -> Height / LocalTextFontHeight + 1];

										for(i = Index = 0 ; i < BufferWindow -> Height / LocalTextFontHeight + 1 ; i++)
										{
											BufferLineOffsets[i] = Index;

											Index += LocalTextFontHeight;
										}

										for(i = Index = 0 ; i < BufferWindow -> Width / LocalTextFontWidth + 1 ; i++)
										{
											BufferColumnOffsets[i] = Index;

											Index += LocalTextFontWidth;
										}

										if(CreateScroller(BufferWindow -> Height))
										{
												/* Signal our father process that
												 * we're running.
												 */

											Signal(Father,SIG_HANDSHAKE);

											Father = NULL;

											ObtainSemaphore(&BufferTaskSemaphore);

											BufferTask = SysBase -> ThisTask;

											ReleaseSemaphore(&BufferTaskSemaphore);

											SetMenuStrip(BufferWindow,BufferMenuStrip);

											AddGList(BufferWindow,Scroller,(UWORD)-1,(UWORD)-1,NULL);
											RefreshGList(Scroller,BufferWindow,NULL,(UWORD)-1);

												/* Determine maximum dimensions of
												 * the buffer screen (in rows and
												 * columns).
												 */

											NumBufferColumns	= (BufferWindow -> Width - (ARROW_WIDTH + 1)) / LocalTextFontWidth;
											NumBufferLines		= BufferWindow -> Height / LocalTextFontHeight;

											if(TopLine == -1 || !Config -> CaptureConfig -> RememberBufferScreen)
											{
												switch(Config -> CaptureConfig -> OpenBufferScreen)
												{
													case BUFFER_TOP:

														TopLine = 0;
														break;

													case BUFFER_END:

														if((TopLine = Lines - NumBufferLines) < 0)
															TopLine = 0;

														break;

													default:

														TopLine = 0;
														break;
												}
											}

											if(TopLine > Lines - NumBufferLines)
												TopLine = 0;

											BPort = BufferWindow -> RPort;

											SetFont(BPort,LocalFont);

												/* Bring the screen to the front. */

											BumpWindow(BufferWindow);

												/* Set the drawing pens for the window. */

											SetAPen(BPort,1);
											SetBPen(BPort,0);
											SetDrMd(BPort,JAM2);

												/* Initial creation of the buffer display. */

											DisplayedLines = RedrawScreen(TopLine);

											SetGadgetAttrs(Scroller,BufferWindow,NULL,
												PGA_Top,	TopLine,
												PGA_Total,	Lines,
												PGA_Visible,	NumBufferLines,
											TAG_DONE);

											BufferWindow -> Flags &= ~WFLG_RMBTRAP;

											RingBack = HandleBuffer(TextLength(BufferScreen -> BarLayer -> rp,LocaleString(MSG_TERMBUFFER_TERM_BUFFER_TXT),strlen(LocaleString(MSG_TERMBUFFER_TERM_BUFFER_TXT))) + TextLength(BufferScreen -> BarLayer -> rp," ",1) + 4);

											RemoveGList(BufferWindow,Scroller,(UWORD)-1);

											DeleteScroller();
										}

										FreeVecPooled(BufferLineWidths);
									}

									if(Window)
										BumpWindow(Window);

									MarkArea(-1,-1,-1);

									ScreenToBack(BufferScreen);

									BufferWindow -> Flags |= WFLG_RMBTRAP;

									ClearMenuStrip(BufferWindow);

									LT_DeleteWindowLock(BufferWindow);

									CloseWindow(BufferWindow);
								}
							}

							DisposeObject(BufferAmigaGlyph);
							DisposeObject(BufferCheckGlyph);

							FreeScreenDrawInfo(BufferScreen,BufferDrawInfo);

							FreeMenus(BufferMenuStrip);
						}

						FreeVisualInfo(BufferVisualInfo);
					}

					CloseScreen(BufferScreen);
				}
			}

			CloseFont(LocalFont);
		}
	}

		/* Release the search buffer data. */

	if(BufferSearchInfo)
	{
		DeleteSearchInfo(BufferSearchInfo);

		BufferSearchInfo = NULL;
	}

	ObtainSemaphore(&BufferTaskSemaphore);

	Forbid();

	BufferTask = NULL;

	ReleaseSemaphore(&BufferTaskSemaphore);

	if(RingBack)
	{
		if(Father)
			Signal(Father,SIG_HANDSHAKE);
		else
			Signal(ThisProcess,SIG_HANDSHAKE);
	}
}

	/* LaunchBuffer():
	 *
	 *	Launch the buffer process.
	 */

BYTE
LaunchBuffer()
{
	ObtainSemaphore(&BufferTaskSemaphore);

		/* Is the buffer process already running? */

	if(BufferTask)
	{
			/* Tell it to bring its screen to the front. */

		Signal(BufferTask,SIG_TOFRONT);

		ReleaseSemaphore(&BufferTaskSemaphore);

			/* Return success. */

		return(TRUE);
	}
	else
	{
		struct Task	*Child;
		BYTE		 Result = FALSE;

		ReleaseSemaphore(&BufferTaskSemaphore);

		Forbid();

			/* Launch the buffer process. */

		if(Child = (struct Task *)CreateNewProcTags(
			NP_Entry,	BufferServer,
			NP_Name,	"term Buffer Process",
			NP_Priority,	SysBase -> ThisTask -> tc_Node . ln_Pri,
			NP_StackSize,	4000,
			NP_WindowPtr,	-1,
		TAG_END))
		{
			Child -> tc_UserData = SysBase -> ThisTask;

			ClrSignal(SIG_HANDSHAKE);

			Wait(SIG_HANDSHAKE);

			ObtainSemaphore(&BufferTaskSemaphore);

			if(BufferTask)
				Result = TRUE;

			ReleaseSemaphore(&BufferTaskSemaphore);
		}

		Permit();

			/* Return the result. */

		return(Result);
	}
}

	/* TerminateBuffer():
	 *
	 *	Terminate the buffer process.
	 */

VOID
TerminateBuffer()
{
	ObtainSemaphore(&BufferTaskSemaphore);

	if(BufferTask)
	{
		Forbid();

		Signal(BufferTask,SIG_KILL);

		ReleaseSemaphore(&BufferTaskSemaphore);

		ClrSignal(SIG_HANDSHAKE);

		Wait(SIG_HANDSHAKE);

		Permit();
	}
	else
		ReleaseSemaphore(&BufferTaskSemaphore);

	LastTopLine = -1;
}
