/*
**	termTextBuffer.c
**
**	Support routines for the text buffer.
**
**	Copyright © 1990-1995 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 BYTE			 BufferSignal;

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 BYTE			 SearchForward		= TRUE,
				 IgnoreCase		= TRUE,
				 WholeWords		= FALSE,
				 NeedClipConversion	= FALSE;

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

STATIC struct Image		*UpImage,
				*DownImage;

STATIC UWORD			*BufferLineWidths,
				*BufferLineOffsets,
				*BufferColumnOffsets;

STATIC ULONG			 ArrowWidth,
				 ArrowHeight;

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

				ArrowWidth	= ARROW_WIDTH;
				ArrowHeight	= ARROW_HEIGHT;

				GetAttr(IA_Height,	UpImage,&ArrowHeight);
				GetAttr(IA_Width,	UpImage,&ArrowWidth);

				ScrollerHeight = Height - 2 * ArrowHeight;

				LeftEdge = BufferScreen -> Width - ArrowWidth;

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

					GA_Top,		0,
					GA_Left,	LeftEdge,
					GA_Width,	ArrowWidth,
					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,	ArrowHeight,
						GA_Width,	ArrowWidth,
						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 + ArrowHeight,
							GA_Height,	ArrowHeight,
							GA_Width,	ArrowWidth,
							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,NeedClipConversion);

							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]),NeedClipConversion);

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

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

							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)
							{
								if(NeedClipConversion)
								{
									UBYTE	Buffer[256];
									LONG	Len,i;
									STRPTR	Dest,
										Source	= &BufferLines[Marker -> FirstLine][Marker -> FirstColumn];
									LONG	Size	= Marker -> LastColumn - Marker -> FirstColumn;
									UBYTE	c;

									BOOL	FirstWrite = TRUE;

									do
									{
										Len = MIN(Size,256);

										Size -= Len;

										for(i = 0, Dest = Buffer ; i < Len ; i++)
										{
											if(c = ISOConversion[*Source++])
												*Dest++ = c;
											else
												*Dest++ = ' ';
										}

										if(Dest > Buffer)
										{
											if(FirstWrite)
											{
												FirstWrite = FALSE;

												SaveClip(Buffer,(ULONG)Dest - (ULONG)&Buffer[0]);
											}
											else
												AddClip(Buffer,(ULONG)Dest - (ULONG)&Buffer[0]);
										}
									}
									while(Size > 0);
								}
								else
									SaveClip(&BufferLines[Marker -> FirstLine][Marker -> FirstColumn],Marker -> LastColumn - Marker -> FirstColumn);
							}
							else
								BufferClipPage(Marker);
						}
					}
				}

				ClrWait(BufferWindow);
			}
		}

		FreeVecPooled(Marker);
	}
}

STATIC VOID __regargs
StartSearch(struct SearchInfo *SearchInfo,STRPTR SearchBuffer)
{
	LT_LockWindow(BufferWindow);

	if(Lines)
	{
		LONG LineNumber;

		ObtainSemaphore(BufferSemaphore);

		LineNumber = SearchTextBuffer(SearchInfo);

		ReleaseSemaphore(BufferSemaphore);

		if(LineNumber == -1)
		{
			MyEasyRequest(BufferWindow,LocaleString(MSG_TERMBUFFER_DID_NOT_FIND_TXT),LocaleString(MSG_GLOBAL_CONTINUE_TXT),SearchBuffer);

			FlushMsg(BufferWindow);

			SearchInfo -> 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(SearchInfo -> FoundX,LineNumber - TopLine,SearchInfo -> 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,1L << BufferSignal);
}

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];
	UBYTE			 SearchBuffer[256];

	struct TagItem		*TagList;

	STRPTR			 PercentTemplate;

	BOOLEAN			 RingBack = FALSE;

	struct SearchContext	*Context	= NULL;
	struct SearchInfo	*SearchInfo	= NULL;

	struct Hook		 HistoryHook;

	HistoryHook . h_Data = &TextBufferHistory;

	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)
		{
			if(Context)
				LT_ShowWindow(Context -> SearchHandle,TRUE);

			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))
		{
			if(Context && Context -> SearchWindow == Massage -> IDCMPWindow)
			{
				MsgClass = NULL;

				if(HandleSearchMessage(Context,&Massage))
				{
					BOOLEAN Ok = Context -> Ok;

					DeleteSearchContext(Context);

					Context = NULL;

					if(Ok)
					{
						if(SearchInfo)
							DeleteSearchInfo(SearchInfo);

						if(SearchInfo = CreateSearchInfo(SearchBuffer,SearchForward,IgnoreCase,WholeWords))
							StartSearch(SearchInfo,SearchBuffer);
					}
					else
					{
						if(SearchInfo)
							DeleteSearchInfo(SearchInfo);

						SearchInfo = NULL;
					}
				}
			}
			else
			{
				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(0);

									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(NewCurrentLine);

									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(NewCurrentLine);

									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(NewCurrentLine);

									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 + 1);

								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(1L << BufferSignal);

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

						Wait(1L << BufferSignal);

						Permit();
					}

					break;
				}
			}

			if(MsgClass == IDCMP_MENUHELP)
				GuideDisplay(CONTEXT_BUFFER_MENU);

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

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


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

								if(Context)
									LT_ShowWindow(Context -> SearchHandle,TRUE);
								else
									Context = CreateSearchContext(BufferWindow,SearchBuffer,&HistoryHook,&SearchForward,&IgnoreCase,&WholeWords);

								UpdatePercent = TRUE;

								break;

							case MEN_REPEAT:

								if(Context)
									LT_ShowWindow(Context -> SearchHandle,TRUE);
								else
								{
									if(SearchInfo)
										StartSearch(SearchInfo,SearchBuffer);
									else
										Context = CreateSearchContext(BufferWindow,SearchBuffer,&HistoryHook,&SearchForward,&IgnoreCase,&WholeWords);
								}

								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);

										break;
									}
									else
									{
										if(Config -> MiscConfig -> ProtectiveMode)
										{
											if(!MyEasyRequest(BufferWindow,LocaleString(MSG_TERMBUFFER_BUFFER_STILL_HOLDS_LINES_TXT),LocaleString(MSG_GLOBAL_YES_NO_TXT),Lines))
											{
												LT_UnlockWindow(BufferWindow);

												break;
											}
										}

										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);

										break;
									}

									LT_UnlockWindow(BufferWindow);

									FlushMsg(BufferWindow);
								}

								break;
						}

						MsgCode = MenuItem -> NextSelect;
					}
					else
						break;
				}
			}

		}
	}
	while(!BufferTerminated);

	if(Context)
		DeleteSearchContext(Context);

	if(SearchInfo)
		DeleteSearchInfo(SearchInfo);

	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;

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

	LocalTextFont . tta_Name	= LocalTextFontName;
	LocalTextFont . tta_YSize	= Config -> TerminalConfig -> TextFontHeight;

	strcpy(LocalTextFontName,Config -> TerminalConfig -> TextFontName);

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

	LocalUserFont . tta_Name = LocalUserFontName;

	strcpy(LocalUserFontName,UserFont . tta_Name);

	if(!Config -> CaptureConfig -> ConvertChars && Config -> TerminalConfig -> FontMode != FONT_STANDARD)
	{
		strcpy(LocalTextFontName,Config -> TerminalConfig -> IBMFontName);

		LocalTextFont . tta_YSize = Config -> TerminalConfig -> IBMFontHeight;

		NeedClipConversion = TRUE;
	}
	else
		NeedClipConversion = FALSE;

	if(LocalFont = OpenDiskFont(&LocalTextFont))
	{
		LocalTextFontBase	= LocalFont -> tf_Baseline;
		LocalTextFontWidth	= LocalFont -> tf_XSize;
		LocalTextFontHeight	= LocalTextFont . tta_YSize;

		CloseFont(LocalFont);
	}

		/* 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 + ArrowWidth + 1;

	if((BufferSignal = AllocSignal(-1)) != -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 = OpenDiskFont(&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_Left,	DisplayClip . MinX,
						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 | IDCMP_MENUHELP,
										WA_MenuHelp,	TRUE,

										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))
											{
												ObtainSemaphore(&BufferTaskSemaphore);

												BufferTask = SysBase -> ThisTask;

												ReleaseSemaphore(&BufferTaskSemaphore);

													/* Signal our father process that
													 * we're running.
													 */

												Signal(Father,SIG_HANDSHAKE);

												Father = NULL;

												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 - (ArrowWidth + 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);
			}
		}

		FreeSignal(BufferSignal);
	}

	ObtainSemaphore(&BufferTaskSemaphore);

	BufferTask = NULL;

	ReleaseSemaphore(&BufferTaskSemaphore);

	Forbid();

	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;
}
