// -------------------------------------------------------------------------------------
// ExecScrollText.m
// (Indent:4, Tabs:4)
// -------------------------------------------------------------------------------------
// Copyright 1996 Persistent Technologies, Inc. - all rights reserved
// -------------------------------------------------------------------------------------
// This source code comes with no warranty of any kind, and the user assumes all 
// responsibility for its use.
// -------------------------------------------------------------------------------------
#import <appkit/appkit.h>
#import <libc.h>
#import <mach/cthreads.h>
#import <stdlib.h>
#import <stdarg.h>
#import <string.h>
#import <pwd.h>
#import <sys/types.h>
#import <sys/wait.h>
#import "ExecScrollText.h"

// -------------------------------------------------------------------------------------
// null text attribute structure
static NXColor			nullColor = { 0 };
static textAttr_t		nullAttr = { (id)nil, 0 };
#define	isNullAttr(X)	(!X.fontId && !X.colorMode)

// -------------------------------------------------------------------------------------
// Object notified when command completes

/* ExecScrollText delegate */
@interface Object(ExecScrollText_Delegate)
- (void)monitorOutput:scrollId buffer:(const char*)buffer len:(int)len;
@end

// -------------------------------------------------------------------------------------
// ExecScrollText private methods
@interface ExecScrollText(Private)
- _setTextAttrFont:fontId color:(int)mode:(NXColor)color;
- (BOOL)_appendTextToView:(const char*)buffer len:(int)len attr:(textAttr_t)tAttr;
- _appendTextAndMakeVisible:(const char*)buffer attr:(textAttr_t)tAttr;
@end

// -------------------------------------------------------------------------------------
/* convert color to gray */
static float cvtColor2Gray(NXColor color)
{
	float	gray;
	NXConvertColorToGray(color, &gray);
	return gray;
}

// -------------------------------------------------------------------------------------
@implementation ExecScrollText

// -------------------------------------------------------------------------------------

/* init */
- initExecScrollText:(id)anObject
{
	[super init];
	scrollView  = anObject;
	textView    = [scrollView docView];
	wasEditable = [textView isEditable];
	autoLf      = NO;
	runAttr     = nullAttr;
	//[textView setEditable:NO];
	//[textView setMonoFont:NO];
	return self;
}

/* initializes the outlet (to be executed by main thread only!) */
+ newExecScrollText:(id)anObject
{
	return [[self alloc] initExecScrollText:anObject];
}

/* return textView id (docView) */
- docView
{
	return textView;
}

/* return scroll view */
- scrollView
{
	return scrollView;
}

/* set delegate */
- setDelegate:(id)theDelegate
{
	delegate = theDelegate;
	return self;
}

/* return delegate */
- (id)delegate
{
	return delegate;
}

/* set auto linefeed mode */
- setAutoLineFeed:(BOOL)mode
{
	autoLf = mode;
	return self;
}

/* free object */
- free
{
	// all pending commands should be killed here
	return [super free];
}

// --------------------------------------------------------------------------------

/* set font */
- _setTextAttrFont:fontId color:(int)mode:(NXColor)color
{
	textAttr_t	tAttr = nullAttr;
	tAttr.fontId	= fontId;
	tAttr.colorMode	= mode;
	tAttr.color		= color;
	[self _appendTextAndMakeVisible:(char*)nil attr:tAttr];
	return self;
}

/* set font */
- setTextAttributeFont:fontId
{
	return [self _setTextAttrFont:fontId color:0:nullColor];
}

// --------------------------------------------------------------------------------

/* set gray */
- setTextAttributeGray:(float)aGray
{
	return [self _setTextAttrFont:(id)nil color:1:NXConvertGrayToColor(aGray)];
}

/* set gray */
- setTextAttributeColor:(NXColor)aColor
{
	return [self _setTextAttrFont:(id)nil color:2:aColor];
}

// --------------------------------------------------------------------------------

/* set default tabs */
- setTabStops:(float*)tabArray count:(int)c
{
	NXTextStyle	style = *((NXTextStyle*)[textView defaultParaStyle]);
	style.numTabs = (short)c;
	style.tabs = (NXTabStop*)malloc(sizeof(NXTabStop) * style.numTabs);
	while (--c >= 0) { style.tabs[c].kind = NX_LEFTTAB; style.tabs[c].x = tabArray[c]; }
	[textView setParaStyle:(void*)&style];
	return self;
}

/* repeat given tab multiple times */
- setTab:(float)tabSize count:(int)c
{
	int			i;
	NXTextStyle	style = *((NXTextStyle*)[textView defaultParaStyle]);
	style.numTabs = (short)c;
	style.tabs = (NXTabStop*)malloc(sizeof(NXTabStop) * style.numTabs);
	for (i = 0; i < c; i++) {
		style.tabs[i].kind = NX_LEFTTAB;
		style.tabs[i].x = (float)(i + 1) * tabSize;
	}
	[textView setParaStyle:(void*)&style];
	return self;
}

// --------------------------------------------------------------------------------

/* clear text scroll view area */
- clearScrollText
{
	[textView setEditable:YES];
	[textView setText:""];
	if (!wasEditable) [textView setEditable:NO];
	[scrollView display];
	return self;
}
- clear:sender
{
	return [self clearScrollText];
}
    
// --------------------------------------------------------------------------------
// append text to scroll view

/* append buffer to view: return YES if text was visible */
- (BOOL)_appendTextToView:(const char*)buffer len:(int)len attr:(textAttr_t)tAttr
{
	int		txtLen;
	NXSelPt	startPt, endPt;
	NXRect	rect;
	
	/* check for font/gray change (save state) */
	if (tAttr.fontId) {
		runAttr.fontId = tAttr.fontId;
	}
	if (tAttr.colorMode) {
		runAttr.colorMode = tAttr.colorMode;
		runAttr.color = tAttr.color;
	}
	if (!buffer || !*buffer || (len == 0)) return NO;
	
	/* get ready to print text */
	[textView getVisibleRect:&rect];                      // visible rectangle
	[textView setEditable:YES];
	txtLen = [textView textLength];
	[textView setSel:txtLen :txtLen];
	[textView getSel:&startPt :&endPt];                   // selected coordinates

	/* set text run attributes if specified */
	if (!isNullAttr(runAttr)) {
		if ([textView isMonoFont]) [textView setMonoFont:NO];
		if (!txtLen) { [textView replaceSel:" "]; [textView setSel:0 :1]; }
		if (runAttr.fontId) [textView setSelFont:runAttr.fontId];
		if (runAttr.colorMode == 1) [textView setSelGray:cvtColor2Gray(runAttr.color)];
		else
		if (runAttr.colorMode == 2) [textView setSelColor:runAttr.color];
		runAttr = nullAttr;
	}
	
	/* print text */
	if (len > 0) [textView replaceSel:buffer length:len];
	else [textView replaceSel:buffer];
	if (!wasEditable) [textView setEditable:NO];

	/* delegate monitor of text written to textview */
	if (delegate && [delegate respondsTo:@selector(monitorOutput:buffer:len:)]) {
		[delegate monitorOutput:self buffer:buffer len:len];
	}
	
	return (rect.origin.y + rect.size.height > endPt.y);  // was visible?
}

/* append text to view and scroll to visible */
- _appendTextAndMakeVisible:(const char*)buffer attr:(textAttr_t)tAttr
{
	BOOL	wasVisible;
  
	/* print buffer */
	wasVisible = [self _appendTextToView:buffer len:-1 attr:tAttr];
	if (autoLf && buffer) [self _appendTextToView:"\n" len:-1 attr:nullAttr];
	if (wasVisible) [textView scrollSelToVisible];
  
	return self;
}

// --------------------------------------------------------------------------------
// append a formatted text string message into a text view

/* append text unformatted */
- (int)textPrint:(const char*)buffer
{
	[self _appendTextAndMakeVisible:buffer attr:nullAttr];
	return strlen(buffer);
}

/* append text with variable args into textView */
- (int)textPrintf:(const char*)fmt args:(va_list)args
{
	char	tempString[textStringSIZE] = { 0 };
	int		retVal = vsprintf(tempString, fmt, args);
	[self _appendTextAndMakeVisible:tempString attr:nullAttr];
	return retVal;
}

/* append text with variable args into textView */
- (int)textPrintf:(const char*)fmt, ...
{
	va_list		args;
	int			retVal;
	va_start(args, fmt);
	retVal = [self textPrintf:fmt args:args];     
	va_end(args);
	return retVal;
}

// --------------------------------------------------------------------------------
// executing commands within a shell, using the scroll text as output

/* execute a command */
- (ExecRunCommand*)runCommand:(const char*)command user:(const char*)user
{
	return [ExecRunCommand runCommand:command user:user output:self];
}

/* execute a command (don't return until it's finished) */
- (int)system:(const char*)command user:(const char*)user
{
	return [ExecRunCommand system:command user:user output:self];
}

/* command output call-back */
- (void)commandOutput:(id)execId buffer:(const char*)buffer len:(int)len
{
	if (delegate && [delegate respondsTo:@selector(commandOutput:buffer:len:)]) {
		[delegate commandOutput:execId buffer:buffer len:len];
	}
	if ([self _appendTextToView:buffer len:len attr:nullAttr]) {
		[textView scrollSelToVisible];
	}
}

/* command completion call-back */
- (void)commandDidComplete:(id)execId withError:(int)errorCode
{
	if (delegate && [delegate respondsTo:@selector(commandDidComplete:withError:)]) {
		[delegate commandDidComplete:execId withError:errorCode];
	}
}

@end
