/*
**	$Id: termBuffer.c,v 1.8 92/05/15 22:24:49 olsen Sta Locker: olsen $
**	$Revision: 1.8 $
**	$Date: 92/05/15 22:24:49 $
**
**	Support routines for the log book (review buffer)
**
**	Copyright © 1990-1992 by Olaf `Olsen' Barthel & MXM
**		All Rights Reserved
*/

#include "termGlobal.h"

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

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

STATIC struct RastPort	*BPort;
STATIC APTR		 BufferVisualInfo;

STATIC LONG		 BufLine,BufCols;

STATIC BYTE		 MarkedArea;

STATIC BYTE		 BufferTerminated;

enum	{	MEN_SEARCH,MEN_REPEAT,MEN_GOTO,MEN_CLEARBUF,MEN_CLOSEBUF,MEN_QUITBUF };

enum	{	GAD_SCROLL };

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},
	{  NM_ITEM, NULL,		 0 , 0, 0, (APTR)MEN_CLOSEBUF},
	{  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}
};

	/* AllocString(UBYTE *String,WORD Len):
	 *
	 +	Allocate space for a string.
	 */

STATIC UBYTE *
AllocString(UBYTE *String,WORD Len)
{
	ULONG *Mem;

	if(Mem = AllocMem(sizeof(ULONG) + Len,MEMF_ANY))
	{
		*Mem++ = Len;

		CopyMem(String,Mem,Len);

		String = (UBYTE *)Mem;

		return(String);
	}

	return(NULL);
}

	/* FreeString(UBYTE *String):
	 *
	 *	Free the space occupied by a string.
	 */

STATIC VOID
FreeString(UBYTE *String)
{
	FreeMem(&((ULONG *)String)[-1],((ULONG *)String)[-1] + sizeof(ULONG));
}

	/* AddLine(UBYTE *Line,LONG Size):
	 *
	 *	Add a line to the display buffer.
	 */

VOID
AddLine(UBYTE *Line,LONG Size)
{
	if(!BufferClosed)
	{
		ULONG Signals = 0;

			/* Is the buffer array initialized? */

		if(BufferLines)
		{
				/* Pick up the global access semaphore
				 * (two tasks are sharing the data).
				 */

			ObtainSemaphore(BufferSemaphore);

				/* Check for limit. */

			if(Config . MaxLogBuffSize && BufferSpace >= Config . MaxLogBuffSize)
				goto Wrap;

				/* We've reached the last line in the buffer. */

			if(Lines == MaxLines)
			{
				UBYTE **MoreBuffer;
				LONG i;

					/* Allocate space for some more lines. */

				if(MoreBuffer = (UBYTE **)AllocVec((MaxLines + 100) * sizeof(UBYTE *),MEMF_ANY|MEMF_CLEAR))
				{
						/* Copy the old lines to the new
						 * buffer.
						 */

					for(i = 0 ; i < Lines ; i++)
						MoreBuffer[i] = BufferLines[i];

						/* Free the old lines. */

					FreeVec(BufferLines);

						/* Set the new buffer. */

					MaxLines += 100;

					BufferLines = MoreBuffer;
				}
				else
				{
						/* We couldn't get enough memory
						 * to extend the number of lines
						 * in the buffer, so we'll have
						 * to wrap the contents of the
						 * buffer around.
						 */

					if(Lines)
					{
Wrap:						BufferSpace -= ((ULONG *)BufferLines[0])[-1];

						FreeString(BufferLines[0]);

						for(i = 1 ; i < MaxLines ; i++)
							BufferLines[i - 1] = BufferLines[i];

						Lines--;

							/* Tell the buffer task to
							 * refresh the display.
							 */

						Signals = SIGBREAKF_CTRL_F;
					}
				}
			}

				/* Allocate a new line and copy the buffer contents
				 * into it.
				 */

			if(BufferLines[Lines] = AllocString(Line,Size))
			{
				Lines++;

				BufferSpace += Size;
                        }

			ReleaseSemaphore(BufferSemaphore);

				/* Tell the buffer task to update the display. */

			if(!Signals)
			{
				Signals = SIGBREAKF_CTRL_E;

				UpdateReview(FALSE);
			}
			else
				UpdateReview(TRUE);

			if(BufferProcess)
				Signal(BufferProcess,Signals);
		}
	}
}

	/* ClearBuffer():
	 *
	 *	Clear the current display buffer and flush the contents
	 *	of the buffered capture file handles.
	 */

VOID
ClearBuffer()
{
	LONG i;

	if(BufferProcess)
	{
		Signal(BufferProcess,SIGBREAKF_CTRL_C);

		Wait(SIGBREAKF_CTRL_C);
	}

	if(PrinterCapture)
		Flush(PrinterCapture);

		/* Free the contents of the display buffer. */

	if(BufferLines)
	{
		for(i = 0 ; i < Lines ; i++)
		{
			if(BufferLines[i])
				FreeString(BufferLines[i]);
		}

		FreeVec(BufferLines);

		BufferLines = NULL;

		Lines = 0;

		MaxLines = 100;

		BufferLines = (UBYTE **)AllocVec(MaxLines * sizeof(UBYTE *),MEMF_ANY|MEMF_CLEAR);
	}

	BufferSpace = 0;
}

	/* StoreBuffer(APTR Buffer,LONG Size):
	 *
	 *	Store data in the display buffer.
	 */

VOID
StoreBuffer(APTR Buffer,LONG Size)
{
	STATIC UBYTE	 LineBuffer[200];
	STATIC LONG 	 BufferCount = 0;

	UBYTE		*CharBuffer = Buffer;
	LONG		 i;

	for(i = 0 ; i < Size ; i++)
	{
			/* Look which char we are to handle. */

		switch(CharBuffer[i])
		{
				/* Ignore the following characters. */

			case FFD:
			case BEL:
			case DEL:
			case VTB:
			case XON:
			case XOF:	continue;

				/* Move the cursor to the next tab
				 * stop.
				 */

			case TAB:	if(BufferCount + 8 < LastColumn)
					{
						strcpy(&LineBuffer[BufferCount],"        ");

						BufferCount += 8;

						break;
					}

					continue;

				/* Move the cursor one step back. */

			case BKS:	if(BufferCount)
						BufferCount--;

					continue;

				/* Ignore carriage return (should have been
				 * filtered already).
				 */

			case RET:	continue;

				/* Terminate the current line. */

			case ENT:	AddLine(LineBuffer,BufferCount);

					BufferCount = 0;
					continue;

				/* Stuff the character into the buffer. */

			default:	LineBuffer[BufferCount++] = CharBuffer[i];
					break;
		}

			/* The line is full, add it to the display buffer. */

		if(BufferCount > LastColumn || BufferCount == sizeof(LineBuffer))
		{
			AddLine(LineBuffer,BufferCount);

			BufferCount = 0;
		}
	}
}

	/* FlushIMsg(struct Window *Window):
	 *
	 +	Clear the MsgPort of a window.
	 */

STATIC VOID
FlushIMsg(struct Window *Window)
{
	struct IntuiMessage *Massage;

	while(Massage = GT_GetIMsg(Window -> UserPort))
		GT_ReplyIMsg(Massage);
}

	/* CreateAllGadgets():
	 *
	 *	Create all the gadgets required by the
	 *	buffer screen (i.e. the scroller gadget).
	 */

STATIC struct Gadget *
CreateAllGadgets(struct Gadget **GadgetArray,struct Gadget **GadgetList)
{
	struct Gadget		*Gadget;
	struct NewGadget	 NewGadget;

	memset(&NewGadget,0,sizeof(struct NewGadget));

	if(Gadget = CreateContext(GadgetList))
	{
		NewGadget . ng_GadgetText	= "";
		NewGadget . ng_Width		= 16;
		NewGadget . ng_Height		= BufferWindow -> Height;
		NewGadget . ng_TextAttr		= &DefaultFont;
		NewGadget . ng_VisualInfo	= BufferVisualInfo;
		NewGadget . ng_GadgetID		= 0;
		NewGadget . ng_LeftEdge		= BufferWindow -> Width - 16;
		NewGadget . ng_TopEdge		= 0;

		GadgetArray[0] = Gadget = CreateGadget(SCROLLER_KIND,Gadget,&NewGadget,
			GTSC_Arrows,	(Config . DisplayMode & LACE) ? 16 : 8,
			PGA_Freedom,	LORIENT_VERT,
		TAG_DONE);
	}

	return(Gadget);
}

	/* GetSearchString(UBYTE *Buffer):
	 *
	 *	Prompt user for a string to search the buffer for.
	 */

STATIC BYTE
GetSearchString(UBYTE *Buffer)
{
	struct Gadget	*GadgetList = NULL;
	struct Gadget	*GadgetArray[4];
	struct Window	*PanelWindow;
	LONG		 Width;
	UBYTE		 OtherBuffer[256];
	BYTE		 Success = FALSE;

	strcpy(OtherBuffer,Buffer);

	if(CreateAllGetsGadgets(FALSE,OtherBuffer,LocaleString(MSG_TERMBUFFER_ENTER_SEARCH_STRING_TXT),&Width,&GadgetArray[0],&GadgetList,BufferVisualInfo,BufferScreen -> WBorTop + BufferScreen -> Font -> ta_YSize + 1,BufferScreen))
	{
		if(PanelWindow = OpenWindowTags(NULL,
			WA_Width,	Width,
			WA_Height,	58,

			WA_Left,	(BufferScreen -> Width - Width) >> 1,
			WA_Top,		(BufferScreen -> Height - 56) >> 1,

			WA_Activate,	TRUE,
			WA_DragBar,	TRUE,
			WA_DepthGadget,	TRUE,
			WA_CloseGadget,	TRUE,
			WA_RMBTrap,	TRUE,
			WA_CustomScreen,BufferScreen,

			WA_IDCMP,	IDCMP_ACTIVEWINDOW | IDCMP_CLOSEWINDOW | IDCMP_VANILLAKEY | IDCMP_GADGETDOWN | BUTTONIDCMP | STRINGIDCMP,

			WA_Title,	LocaleString(MSG_TERMBUFFER_ENTER_SEARCH_STRING_TXT),
		TAG_DONE))
		{
			struct IntuiMessage	*Massage;
			ULONG			 Class,Code;
			struct Gadget		*Gadget;
			BYTE			 Terminated = FALSE;
			ULONG			 SignalSet;

			AddGList(PanelWindow,GadgetList,(UWORD)-1,(UWORD)-1,NULL);
			RefreshGList(GadgetList,PanelWindow,NULL,(UWORD)-1);
			GT_RefreshWindow(PanelWindow,NULL);

			ActiveGadget = NULL;

			while(!Terminated)
			{
				SignalSet = Wait((1 << PanelWindow -> UserPort -> mp_SigBit) | SIGBREAKF_CTRL_D | SIGBREAKF_CTRL_C);

				if(SignalSet & SIGBREAKF_CTRL_D)
					BumpWindow(PanelWindow);

				if(SignalSet & SIGBREAKF_CTRL_C)
				{
					BufferTerminated = TRUE;

					Terminated = TRUE;
				}

				if(SignalSet & (1 << PanelWindow -> UserPort -> mp_SigBit))
				{
					while(!Terminated && (Massage = (struct IntuiMessage *)GT_GetIMsg(PanelWindow -> UserPort)))
					{
						Class	= Massage -> Class;
						Code	= Massage -> Code;
						Gadget	= (struct Gadget *)Massage -> IAddress;

						GT_ReplyIMsg(Massage);

						if(Class == IDCMP_VANILLAKEY)
							KeySelect(GadgetArray,3,Code,PanelWindow,&Gadget,&Class,&Code);

						if(Class == IDCMP_GADGETDOWN)
						{
							if((Gadget -> GadgetType & GTYP_GTYPEMASK) == GTYP_STRGADGET)
								ActiveGadget = Gadget;
						}

						if(Class == IDCMP_ACTIVEWINDOW && ActiveGadget)
							ActivateGadget(ActiveGadget,PanelWindow,NULL);

						if(Class == IDCMP_CLOSEWINDOW)
							Terminated = TRUE;

						if(Class == IDCMP_GADGETUP)
						{
							switch(Gadget -> GadgetID)
							{
								case 0: if(Code != '\t' && ((struct StringInfo *)GadgetArray[0] -> SpecialInfo) -> Buffer[0])
									{
										strcpy(Buffer,GT_STRING(GadgetArray[0]));

										Success = TRUE;

										Terminated = TRUE;
									}

									break;

								case 1:	if(((struct StringInfo *)GadgetArray[0] -> SpecialInfo) -> Buffer[0])
									{
										strcpy(Buffer,GT_STRING(GadgetArray[0]));

										Success = TRUE;

										Terminated = TRUE;
									}

									break;

								case 3:	Terminated = TRUE;
									break;
							}
						}
					}
				}
			}

			RemoveGList(PanelWindow,GadgetList,(UWORD)-1);

			CloseWindow(PanelWindow);
		}
	}

	FreeGadgets(GadgetList);

	return(Success);
}

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

STATIC VOID
PrintLine(UBYTE *Buffer,LONG LineNumber)
{
	WORD Length = ((ULONG *)Buffer)[-1];

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

	if(Length < BufCols)
	{
		SetAPen(BPort,0);

		RectFill(BPort,Length << 3,LineNumber << 3,(BufCols << 3) - 1,((LineNumber + 1) << 3) - 1);

		SetAPen(BPort,1);
	}

		/* Print the text. */

	if(Length)
	{
		Move(BPort,0,(LineNumber << 3) + 6);

		if(Length > BufCols)
			Text(BPort,Buffer,BufCols);
		else
			Text(BPort,Buffer,Length);
	}
}

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

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

		/* Determine last line to display. */

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

		/* Obtain the access semaphore and display the single
		 * lines.
		 */

	ObtainSemaphore(BufferSemaphore);

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

	ReleaseSemaphore(BufferSemaphore);

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

	if(Line < BufLine)
	{
		SetAPen(BPort,0);

		RectFill(BPort,0,Line << 3,BufferWindow -> Width - (1 + 16),BufferWindow -> Height - 1);

		SetAPen(BPort,1);
	}

	return(Line);
}

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

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

	if(OldColumn != Column || OldLine != Line || OldLength != Length)
	{
		if(OldColumn != -1)
			ClipBlit(BPort,0,0,BPort,OldColumn << 3,OldLine << 3,OldLength << 3,8,0x50);

		MarkedArea = TRUE;

		if(Column != -1)
			ClipBlit(BPort,0,0,BPort,Column << 3,Line << 3,Length << 3,8,0x50);
		else
			MarkedArea = FALSE;
	}

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

	/* Search():
	 *
	 *	String search function, based on the Boyer-Moore search
	 *	algorithm.
	 */

STATIC LONG
Search(UBYTE *Pattern,LONG *FoundX,LONG *FoundY,LONG *SaveX)
{
	UBYTE	Distance[256],NewPattern[256],PatternWidth,SearchWidth;
	LONG	LineNumber = -1,i;
	WORD	j,k,l;

		/* Determine length of search pattern. */

	PatternWidth = strlen(Pattern);

		/* Convert search pattern to upper cse. */

	for(i = 0 ; i <= PatternWidth ; i++)
		NewPattern[i] = ToUpper(Pattern[i]);

		/* Create distance table. */

	for(i = 0 ; i < 256 ; i++)
		Distance[i] = PatternWidth;

	for(i = 0 ; i < PatternWidth - 1 ; i++)
		Distance[NewPattern[i]] = PatternWidth - i - 1;

		/* Run down the buffer. */

	for(i = *FoundY ; i < Lines ; i++)
	{
			/* Is there anything to search for? */

		if(SearchWidth = ((ULONG *)BufferLines[i])[-1])
		{
				/* Casteth the magick spelle of
				 * Boyer-Moore...
				 */

			j = PatternWidth;

			do
			{
				k = PatternWidth;
				l = j;

				do
				{
					k--;
					l--;
				}
				while(k >= 0 && NewPattern[k] == ToUpper(BufferLines[i][l]));

				j += Distance[ToUpper(BufferLines[i][j - 1])];
			}
			while(k >= 0 && j <= SearchWidth);

				/* Found first occurence, set up the
				 * data and quit.
				 */

			if(k < 0)
			{
				*FoundX	= l + 1;
				*FoundY	= LineNumber = i;

				*SaveX	= l + 1;

				if(BufferLines[i][*FoundX + 1])
					*FoundX++;
				else
				{
					if(*FoundY - 1 < Lines)
					{
						*FoundX = 0;
						*FoundY++;
					}
				}

				return(LineNumber);
			}
			else
				*FoundX = 0;
		}
	}

	return(LineNumber);
}

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

VOID __saveds
BufferServer()
{
	struct ColorSpec	 ColorSpec[3];

	ULONG			 SignalSet;

	struct IntuiMessage	*Massage;
	ULONG			 Class,Code,Qualifier;
	UBYTE			 Char,LastChar = 0;

	LONG			 CurrentLine,DisplayedLines;

	UBYTE			 PercentBuffer[80];

	struct Menu		*BufferMenuStrip;

	STATIC UBYTE		 SearchBuffer[256];
	LONG			 LineNumber,SaveX,FoundX = 0,FoundY = 0;
	ULONG			 DisplayMode;

	struct Rectangle	 DisplayClip;

	UWORD			 SomeColour;

	struct Gadget		*GadgetList = NULL;
	struct Gadget		*GadgetArray[1];

	BufferTerminated = FALSE;

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

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

	switch(Config . ColourMode)
	{
		case COLOUR_EIGHT:	SomeColour = Config . Colours[7];
					break;

		case COLOUR_SIXTEEN:	SomeColour = Config . Colours[15];
					break;

		case COLOUR_AMIGA:
		default:		SomeColour = Config . Colours[1];
					break;
	}

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

	ColorSpec[2] . ColorIndex	= -1;

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

	DisplayMode = Config . DisplayMode;

		/* Inquire the text overscan dimensions. */

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

		if(DisplayClip . MaxX - DisplayClip . MinX + 1 > 640 + 16)
		{
			WORD Differ = ((DisplayClip . MaxX - DisplayClip . MinX + 1) - (640 + 16)) >> 1;

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

			/* 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,	&DefaultFont,
			SA_Behind,	TRUE,
			SA_AutoScroll,	TRUE,
			SA_Colors,	ColorSpec,
		TAG_END))
		{
			if(BufferVisualInfo = GetVisualInfo(BufferScreen,TAG_DONE))
			{
				LocalizeMenu(BufferMenu,MSG_TERMBUFFER_PROJECT_MEN);

				if(BufferMenuStrip = CreateMenus(BufferMenu,
					GTMN_FrontPen, 0,
				TAG_DONE))
				{
					if(LayoutMenus(BufferMenuStrip,BufferVisualInfo,
						GTMN_TextAttr,&DefaultFont,
					TAG_DONE))
					{
						WORD TitleOffset;

						TitleOffset = (strlen(LocaleString(MSG_TERMBUFFER_TERM_BUFFER_TXT)) + 1) * 8 + 4;

							/* 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,	BufferScreen -> Height - (Screen -> BarHeight + 2),
							WA_Backdrop,	TRUE,
							WA_Borderless,	TRUE,
							WA_SmartRefresh,FALSE,
							WA_CustomScreen,BufferScreen,
							WA_RMBTrap,	TRUE,
							WA_RptQueue,	1,
							WA_IDCMP,	IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS | IDCMP_MOUSEMOVE | IDCMP_MENUPICK | IDCMP_INACTIVEWINDOW | ARROWIDCMP | SCROLLERIDCMP,
						TAG_DONE))
						{
							if(CreateAllGadgets(&GadgetArray[0],&GadgetList))
							{
									/* Signal our father process that
									 * we're running.
									 */

								Signal(ThisProcess,SIGBREAKF_CTRL_C);

								SetMenuStrip(BufferWindow,BufferMenuStrip);

								AddGList(BufferWindow,GadgetList,(UWORD)-1,(UWORD)-1,NULL);
								RefreshGList(GadgetList,BufferWindow,NULL,(UWORD)-1);
								GT_RefreshWindow(BufferWindow,NULL);

Restart:								/* Set the IBM font if necessary. */

								BPort = BufferWindow -> RPort;

								if(Config . Font == FONT_IBM && IBM)
									SetFont(BPort,IBM);

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

								BufCols	= (BufferWindow -> Width - 16) >> 3;
								BufLine	= BufferWindow -> Height >> 3;

									/* 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(CurrentLine = 0);

								GT_SetGadgetAttrs(GadgetArray[GAD_SCROLL],BufferWindow,NULL,
									GTSC_Top,	0,
									GTSC_Total,	Lines,
									GTSC_Visible,	BufLine,
								TAG_DONE);

								BufferWindow -> Flags &= ~WFLG_RMBTRAP;

								while(!BufferTerminated)
								{
										/* Show where we are. */

									if(Lines)
									{
										extern APTR LocaleBase;

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

										if(LocaleBase)
											SPrintf(PercentBuffer,"%lD/%lD (%ld%%)                  ",CurrentLine,Lines > BufLine ? Lines - BufLine : 0,(100 * (CurrentLine + DisplayedLines)) / Lines);
										else
											SPrintf(PercentBuffer,"%ld/%ld (%ld%%)                  ",CurrentLine,Lines > BufLine ? Lines - BufLine : 0,(100 * (CurrentLine + DisplayedLines)) / Lines);

										Move(BufferScreen -> BarLayer -> rp,TitleOffset,7);
										Text(BufferScreen -> BarLayer -> rp,PercentBuffer,strlen(PercentBuffer));
									}

									SignalSet = Wait(SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D | SIGBREAKF_CTRL_E | SIGBREAKF_CTRL_F | (1 << BufferWindow -> UserPort -> mp_SigBit));

										/* Leave the town? */

									if(SignalSet & SIGBREAKF_CTRL_C)
										BufferTerminated = TRUE;

										/* Bring our window to the front. */

									if(SignalSet & SIGBREAKF_CTRL_D)
										BumpWindow(BufferWindow);

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

									if(SignalSet & SIGBREAKF_CTRL_E)
									{
										if(Lines - CurrentLine > DisplayedLines && DisplayedLines < BufLine)
										{
											ObtainSemaphore(BufferSemaphore);

											PrintLine(BufferLines[CurrentLine + DisplayedLines],DisplayedLines++);

											ReleaseSemaphore(BufferSemaphore);
										}

										GT_SetGadgetAttrs(GadgetArray[GAD_SCROLL],BufferWindow,NULL,
											GTSC_Total,	Lines,
											GTSC_Visible,	BufLine,
										TAG_DONE);
									}

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

									if(SignalSet & SIGBREAKF_CTRL_F)
										DisplayedLines = RedrawScreen(CurrentLine);

										/* Process the incoming window
										 * input.
										 */

									while(Massage = (struct IntuiMessage *)GT_GetIMsg(BufferWindow -> UserPort))
									{
										Class		= Massage -> Class;
										Code		= Massage -> Code;
										Qualifier	= Massage -> Qualifier;

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

										GT_ReplyIMsg(Massage);

										if(Class == IDCMP_MOUSEMOVE)
										{
											if(Code != CurrentLine)
											{
												if(ABS(CurrentLine - Code) == 1)
												{
													Qualifier	= NULL;
													Class		= IDCMP_RAWKEY;

													if(Code > CurrentLine)
														Code = CDN;
													else
														Code = CUP;

													goto UseKey;
												}
												else
												{
													if(MarkedArea)
														MarkArea(-1,-1,-1);

													DisplayedLines = RedrawScreen(CurrentLine = Code);
												}
											}
										}

										if(Class == IDCMP_RAWKEY)
										{
											if(LastChar && (Code & IECODE_UP_PREFIX))
											{
												GT_SetGadgetAttrs(GadgetArray[GAD_SCROLL],BufferWindow,NULL,
													GTSC_Top,	CurrentLine,
													GTSC_Total,	Lines,
													GTSC_Visible,	BufLine,
												TAG_DONE);
											}

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

UseKey:												if(Qualifier & IEQUALIFIER_NUMERICPAD)
												{
														/* Remove the numpad qualifier. */

													Qualifier &= ~IEQUALIFIER_NUMERICPAD;

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

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

															/* Jump to top. */

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

															/* Move one page down. */

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

															/* Move one page up. */

														case 9: Char = CUP;
															Qualifier |= 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(Qualifier & IEQUALIFIER_CONTROL)
															{
																if(MarkedArea)
																	MarkArea(-1,-1,-1);

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

																	GT_SetGadgetAttrs(GadgetArray[GAD_SCROLL],BufferWindow,NULL,
																		GTSC_Top,	0,
																		GTSC_Total,	Lines,
																		GTSC_Visible,	BufLine,
																	TAG_DONE);
																}

																break;
															}

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

																if((NewCurrentLine = CurrentLine - BufLine) < 0)
																	NewCurrentLine = 0;

																if(MarkedArea)
																	MarkArea(-1,-1,-1);

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

																	GT_SetGadgetAttrs(GadgetArray[GAD_SCROLL],BufferWindow,NULL,
																		GTSC_Top,	CurrentLine,
																		GTSC_Total,	Lines,
																		GTSC_Visible,	BufLine,
																	TAG_DONE);
																}

																break;
															}

															if(CurrentLine)
															{
																if(MarkedArea)
																	MarkArea(-1,-1,-1);

																if(DisplayedLines)
																	ScrollRaster(BPort,0,-8,0,0,(BufCols << 3) - 1,(DisplayedLines << 3) - 1);

																CurrentLine--;

																ObtainSemaphore(BufferSemaphore);

																PrintLine(BufferLines[CurrentLine],0);

																ReleaseSemaphore(BufferSemaphore);

																if(DisplayedLines < BufLine)
																	DisplayedLines++;
															}

															break;

														/* Scroll the buffer down. */
	
													case CDN:	if(Qualifier & IEQUALIFIER_CONTROL)
															{
																LONG NewCurrentLine;

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

																if(MarkedArea)
																	MarkArea(-1,-1,-1);

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

																	GT_SetGadgetAttrs(GadgetArray[GAD_SCROLL],BufferWindow,NULL,
																		GTSC_Top,	CurrentLine,
																		GTSC_Total,	Lines,
																		GTSC_Visible,	BufLine,
																	TAG_DONE);
																}

																break;
															}

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

																if((NewCurrentLine = CurrentLine + (2 * BufLine)) > Lines)
																	NewCurrentLine = Lines;

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

																if(MarkedArea)
																	MarkArea(-1,-1,-1);

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

																	GT_SetGadgetAttrs(GadgetArray[GAD_SCROLL],BufferWindow,NULL,
																		GTSC_Top,	CurrentLine,
																		GTSC_Total,	Lines,
																		GTSC_Visible,	BufLine,
																	TAG_DONE);
																}

																break;
															}

															if(CurrentLine + BufLine < Lines)
															{
																if(MarkedArea)
																	MarkArea(-1,-1,-1);

																if(DisplayedLines)
																	ScrollRaster(BPort,0,8,0,0,(BufCols << 3) - 1,(DisplayedLines << 3) - 1);

																ObtainSemaphore(BufferSemaphore);

																PrintLine(BufferLines[CurrentLine + BufLine],BufLine - 1);

																ReleaseSemaphore(BufferSemaphore);

																CurrentLine++;

																if(DisplayedLines < BufLine)
																	DisplayedLines++;
															}

															break;

													default:	break;
												}
											}

											continue;
										}

											/* User hit a mouse button. */

										if(Class == IDCMP_MOUSEBUTTONS && !(Code & IECODE_UP_PREFIX))
										{
											BYTE SkipLoop = FALSE;
											LONG ThisLine,ThisColumn,MyColumn,LastColumn,SomeColumn;
											UBYTE *TheLine;

												/* Remember initial mouse position. */

											ThisColumn	= Massage -> MouseX >> 3;
											ThisLine	= Massage -> MouseY >> 3;

											if(MarkedArea)
												MarkArea(-1,-1,-1);

												/* Reasonable dimensions? */

											if(ThisLine < DisplayedLines)
											{
													/* Find the approriate line and its length. */

												TheLine		= BufferLines[CurrentLine + ThisLine];
												LastColumn	= ((ULONG *)BufferLines[CurrentLine + ThisLine])[-1];

													/* Resonable dimensions? */

												if(ThisColumn < LastColumn)
												{
													MyColumn = ThisColumn;

													ReportMouse(TRUE,BufferWindow);

														/* Loop until left mouse button is release. */

													while(!SkipLoop)
													{
														SignalSet = Wait(SIGBREAKF_CTRL_F | (1 << BufferWindow -> UserPort -> mp_SigBit));

															/* Contents of the buffer has changed! */

														if(SignalSet & SIGBREAKF_CTRL_F)
														{
															if(FoundY)
																FoundY--;

															DisplayBeep(BufferScreen);
															break;
														}

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

															ReplyMsg(Massage);

																/* Mouse left inner space. */

															if(Class == IDCMP_INACTIVEWINDOW)
															{
																SkipLoop = TRUE;

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

																break;
															}

																/* We're finished! */

															if(Class == IDCMP_MOUSEBUTTONS && (Code == SELECTUP || Code == MIDDLEUP))
															{
																SkipLoop = TRUE;

																	/* Did we get a reasonable mouse
																	 * position?
																	 */

																if(MyColumn != ThisColumn)
																{
																		/* Preserve right order of
																		 * numbers.
																		 */

																	if(MyColumn < ThisColumn)
																	{
																		LONG Help;

																		Help		= ThisColumn;
																		ThisColumn	= MyColumn;
																		MyColumn	= Help;
																	}

																		/* Restore the line. */

																	if(MyColumn < LastColumn)
																		LastColumn = MyColumn;

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

																		/* Clip the contents of the line to
																		 * the clipboard.
																		 */

																	SaveClip(&TheLine[ThisColumn],MyColumn - ThisColumn);

																		/* Clipping on the buffer screen is to work
																		 * similar to clipping on the main screen,
																		 * i.e. feed the line into the input
																		 * stream if a shift key is held down.
																		 */

																	if((Qualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT)) || Code == MIDDLEUP)
																		Signal(ThisProcess,SIG_CLIP);
																}

																break;
															}

																/* The mouse has moved. */

															if(Class == IDCMP_MOUSEMOVE)
															{
																STATIC LONG OldColumn = ~0;

																	/* Determine new mouse position. */

																SomeColumn = MyColumn;

																MyColumn = Massage -> MouseX >> 3;

																if((Massage -> MouseY >> 3) < ThisLine)
																	MyColumn = 0;

																if((Massage -> MouseY >> 3) > ThisLine)
																	MyColumn = LastColumn;

																	/* Don't redraw the line if nothing
																	 * has changed.
																	 */

																if(OldColumn != MyColumn)
																{
																	OldColumn = MyColumn;

																		/* Reasonable position? */

																	if(MyColumn <= LastColumn)
																	{
																		if(MyColumn >= 0)
																		{
																				/* Highlight the selected
																				 * area (invert).
																				 */

																			if(MyColumn != ThisColumn)
																			{
																				if(MyColumn < ThisColumn)
																					MarkArea(MyColumn,ThisLine,ThisColumn - MyColumn);
																				else
																					MarkArea(ThisColumn,ThisLine,MyColumn - ThisColumn);
																			}
																		}
																	}
																	else
																		MyColumn = SomeColumn;
																}
															}
														}
													}

													ReportMouse(FALSE,BufferWindow);
												}
											}
										}

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

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

												switch((ULONG)GTMENUITEM_USERDATA(MenuItem))
												{
													case MEN_SEARCH:	BlockWindow(BufferWindow);

GetTheString:															if(Lines)
																{
																	if(GetSearchString(SearchBuffer))
																	{
																		FoundX = FoundY = 0;

SearchForIt:																	LineNumber = -1;

																		if(FoundY == 0 && CurrentLine != 0)
																			FoundY = CurrentLine;

																		ObtainSemaphore(BufferSemaphore);

																		LineNumber = Search(SearchBuffer,&FoundX,&FoundY,&SaveX);

																		ReleaseSemaphore(BufferSemaphore);

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

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

																			ReleaseWindows();

																			FlushIMsg(BufferWindow);

																			FoundX = FoundY = 0;

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

																				DisplayedLines = RedrawScreen(CurrentLine = 0);

																				GT_SetGadgetAttrs(GadgetArray[GAD_SCROLL],BufferWindow,NULL,
																					GTSC_Top,	CurrentLine,
																					GTSC_Total,	Lines,
																					GTSC_Visible,	BufLine,
																				TAG_DONE);
																			}
																			else
																			{
																				if(LineNumber > CurrentLine + DisplayedLines - 1)
																				{
																					if(MarkedArea)
																						MarkArea(-1,-1,-1);
	
																					if(LineNumber >= Lines - BufLine)
																					{
																						LONG NewCurrentLine;

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

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

																							GT_SetGadgetAttrs(GadgetArray[GAD_SCROLL],BufferWindow,NULL,
																								GTSC_Top,	CurrentLine,
																								GTSC_Total,	Lines,
																								GTSC_Visible,	BufLine,
																							TAG_DONE);
																						}
																					}
																					else
																					{
																						DisplayedLines = RedrawScreen(CurrentLine = LineNumber);

																						GT_SetGadgetAttrs(GadgetArray[GAD_SCROLL],BufferWindow,NULL,
																							GTSC_Top,	CurrentLine,
																							GTSC_Total,	Lines,
																							GTSC_Visible,	BufLine,
																						TAG_DONE);
																					}
																				}
																			}

																			MarkArea(SaveX,LineNumber - CurrentLine,strlen(SearchBuffer));
																		}

																		FoundY = (FoundY + 1) % Lines;
																	}
																}
																else
																	MyEasyRequest(BufferWindow,LocaleString(MSG_GLOBAL_NOTHING_IN_THE_BUFFER_TXT),LocaleString(MSG_GLOBAL_CONTINUE_TXT));

																ReleaseWindow(BufferWindow);
																break;

													case MEN_REPEAT:	BlockWindow(BufferWindow);

																if(!SearchBuffer[0])
																	goto GetTheString;
																else
																	goto SearchForIt;

													case MEN_GOTO:		BumpWindow(TopWindow);
																break;

													case MEN_QUITBUF:
													case MEN_CLOSEBUF:	BufferTerminated = TRUE;
																break;

													case MEN_CLEARBUF:	if(Lines)
																{
																	BlockWindows();

																	BlockWindow(BufferWindow);

																	if(Qualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
																	{
																		ClearBuffer();

																		MarkedArea = FALSE;

																		ReleaseWindow(BufferWindow);

																		ReleaseWindows();

																		FlushIMsg(BufferWindow);

																		goto Restart;
																	}
																	else
																	{
																		if(MyEasyRequest(BufferWindow,LocaleString(MSG_TERMBUFFER_BUFFER_STILL_HOLDS_LINES_TXT),LocaleString(MSG_GLOBAL_YES_NO_TXT),Lines))
																		{
																			ClearBuffer();

																			MarkedArea = FALSE;

																			ReleaseWindow(BufferWindow);

																			ReleaseWindows();

																			FlushIMsg(BufferWindow);

																			goto Restart;
																		}
																	}

																	ReleaseWindow(BufferWindow);

																	ReleaseWindows();

																	FlushIMsg(BufferWindow);
																}

																break;

													default:		break;
												}

												Code = MenuItem -> NextSelect;
											}
										}
									}
								}

								RemoveGList(BufferWindow,GadgetList,(UWORD)-1);
							}

							FreeGadgets(GadgetList);

							BumpWindow(TopWindow);

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

							ScreenToBack(BufferScreen);

							BufferWindow -> Flags |= WFLG_RMBTRAP;

							ClearMenuStrip(BufferWindow);

							CloseWindow(BufferWindow);
						}
					}

					FreeMenus(BufferMenuStrip);
				}

				FreeVisualInfo(BufferVisualInfo);
			}

			CloseScreen(BufferScreen);
		}
	}

	Forbid();

	BufferProcess = NULL;

	Signal(ThisProcess,SIGBREAKF_CTRL_C);
}
