/* Generated by Interface Builder */

#import "ComScroll.h"
#import "CText.h"
#import <appkit/nextstd.h>
#import <appkit/Application.h>
#import <appkit/Window.h>
#import <appkit/Font.h>
#import <appkit/Panel.h>

extern char defaultfont[] ;

extern void error(), configure() ;

static void getptys (master, slave)
int *master, *slave ;
{
   char device[12];
   char *block, *num;
   char *blockLoc;
   char *numLoc;
   char *msLoc;
   struct sgttyb setp =
      {B9600, B9600, (char)0x7f, (char)0x15, (CRMOD|ANYP)};
   struct tchars setc =
      {CINTR, CQUIT, CSTART, CSTOP, CEOF, CBRK};
   struct ltchars sltc =
      {CSUSP, CDSUSP, CRPRNT, CFLUSH, CWERASE, CLNEXT};
   int lset =
      (LCRTBS|LCRTERA|LCRTKIL|LCTLECH|LPENDIN|LDECCTQ);
   int setd = NTTYDISC;

   strcpy(device, "/dev/pty??");
   blockLoc = &device[ strlen("/dev/pty") ];
   numLoc = &device[ strlen("/dev/pty?") ];
   msLoc = &device[ strlen("/dev/") ];
   for (block = "pqrs"; *block; block++) {
      *blockLoc = *block;
      for (num = "0123456789abcdef"; *num; num++) {
         *numLoc = *num;
         *master = open(device, O_RDWR);
         if (*master >= 0) {
            *msLoc = 't';
            *slave = open(device, O_RDWR);
            if (*slave >= 0) {
               (void) ioctl(*slave, TIOCSETP, (char *)&setp);
               (void) ioctl(*slave, TIOCSETC, (char *)&setc);
               (void) ioctl(*slave, TIOCSETD, (char *)&setd);
               (void) ioctl(*slave, TIOCSLTC, (char *)&sltc);
               (void) ioctl(*slave, TIOCLSET, (char *)&lset);
               return;
            }
         }
      }
   }
   *master = -1;
   *slave = -1;
}

@implementation ComScroll

+ newFrame:(const NXRect *)frameRect
{
   self = [super newFrame:frameRect] ;
   [self setVertScrollerRequired: YES] ;
   [self setBackgroundGray: NX_WHITE] ;
   [self awake] ;
   childID = 0 ;
   waiting = 0 ;
   return self ;
}

- awake
{
   NXRect trect ;
   Font *mf = 0 ;

   configure() ;
   if (defaultfont[0]) {
      char *p = defaultfont ;
      char fname[100] ;
      char *q = fname ;
      float size ;

      for (; *p && *p != ':'; p++, q++)
         *q = *p ;
      *q = 0 ;
      if (*p && sscanf(p+1, "%f", &size)==1)
         mf = [Font newFont:fname size:size] ;
   }
   if (mf == 0)
      mf = [Font newFont:"Ohlfs" size:10] ;
   trect.origin.x = trect.origin.y = 0 ;
   [self getContentSize: &(trect.size)] ;
   text = [[CText alloc]
                  initFrame:&trect text:NULL alignment:NX_LEFTALIGNED] ;
   [text setFont:mf] ;
   [text notifyAncestorWhenFrameChanged:YES] ;
// [text setEditable:NO] ;
   [text setEditable:YES] ;
   [text setHorizResizable:NO] ;
   [text setVertResizable:YES] ;
   [text setMinSize:&(trect.size)] ;
   trect.size.height = 1000000 ;
   trect.size.width = 1200 ;
   [text setMaxSize:&(trect.size)] ;
   [self setAutoresizeSubviews:YES] ;
   [self setAutosizing: NX_HEIGHTSIZABLE | NX_WIDTHSIZABLE] ;
   [text setMonoFont:YES] ;
   [text setOpaque:YES] ;
   [text setFontPanelEnabled:YES] ;
   [self setDocView:text] ;
   [text setDelegate:self] ;
   [text setController:self] ;
   return self ;
}

- addText:(const char *)s
{
   [text setSel:[text textLength] :0];
   [text replaceSel:s] ;
   [text scrollSelToVisible];
   return self;
}

- handler:(int)fd
{
   int r ;
   char outbuf[1024] ;

   if (childID == 0)
      return self ;
/* printf("In handler\n") ; */
   while ((r = read(fd, outbuf, 1023)) > 0) {
/*    printf("<%d> bytes\n", r) ; */
      if (waiting) {
         struct timeval tv ;
         struct timezone tzp ;

         gettimeofday(&tv, &tzp) ;
         if (tv.tv_sec > waittime.tv_sec ||
             (tv.tv_sec == waittime.tv_sec && tv.tv_usec > waittime.tv_usec)) {
            [[self window] makeKeyAndOrderFront:0] ;
            waiting = 0 ;
         }
      }
      if (eofcount > 0) {
         write(fd, eofchar, 1) ;
         eofcount-- ;
      }
      outbuf[r] = 0 ;
      [self addText:outbuf] ;
   }
   if (r == 0)
      [self closeChild:fd] ;
   else if (errno != EWOULDBLOCK) {
      [self closeChild:fd] ;
   }
/* printf("Out handler\n") ; */
   return self ;
}

static void cm_handler(fd, sender)
int fd ;
id sender ;
{
   [sender handler:fd] ;
}

- closeChild:(int)fd
{
   if (childID) {
      if (fdadded)
         DPSRemoveFD(fd) ;
      childID = 0 ;
      close(fd) ;
      masterFD = 0 ;
   }
// [text setEditable:NO] ;
   [text setEditable:YES] ;
   return self ;
}

static void msleep(long len) {
   struct timeval tv ;

   tv.tv_usec = len % 1000000 ;
   tv.tv_sec = len / 1000000 ;
   select(0, 0, 0, 0, &tv) ;
}

- killOff
{
   if (childID) {
      kill(childID, SIGTERM) ;
      childID = 0 ;
   }
   return self ;
}

- logCommand:(const char *)s inBack:(int)async
{
   int masterPty, slavePty ;
   int tty, numFds, fd;
   int processGroup;
   int pidChild;
   int i ;

   while (wait3(0, WNOHANG, 0) > 0) ; /* allow zombies to go away */
   if (childID)
      if (masterFD)
         [self handler:masterFD] ;
   if (childID) {  // are we busy??
/*    i = NXRunAlertPanel(NULL, "Command window busy",
    "Cancel request", "Force EOF and override", "Force ^C and override", 0) ;
 */
      i = NX_ALERTALTERNATE ;
      if (i != NX_ALERTDEFAULT) {
         if (i == NX_ALERTALTERNATE)
            eofchar[0] = 4 ;
         else
            eofchar[0] = 3 ;
         write(masterFD, eofchar, 1) ;
         eofcount = 3 ;
         for (i=0; i<5; i++) {
            if (childID) {
               msleep(300000) ;
               [self handler:masterFD] ;
            }
         }
      }
   }
   if (childID) {
      // we tried nicely, now we get mean.
      kill(childID, SIGTERM) ;
      msleep(300000) ;
      [self handler:masterFD] ;
      if (childID)
         [self closeChild:masterFD] ;
   }
   if (childID) {
      error("you'll have to intercede for me; I couldn't get it to die.") ;
      return 0 ;
   }
   [self addText:" - "] ;
   [self addText:s] ;
   [self addText:"\n"] ;
   tty = open("/dev/tty", O_RDWR);
   getptys(&masterPty, &slavePty);
   if (masterPty <= 0 || slavePty <= 0)
       error("! error grabbing ptys for subprocess") ;

   // remove the controlling tty if launched from a shell,
   // but not Workspace;
   // so that we have job control over the parent application in shell
   // and so that subprocesses can be restarted in Workspace
   if  (tty<0) {
      if ((tty = open("/dev/tty", 2))>=0) {
         ioctl(tty, TIOCNOTTY, 0);
         close(tty);
      }
   } else {
      close(tty) ;
   }
   switch (pidChild = vfork()) {
   case -1:   // error
      error("! error starting UNIX vfork of subprocess.") ;
   case 0:   // child
      dup2(slavePty, 0);
      dup2(slavePty, 1);
      dup2(slavePty, 2);
      numFds = getdtablesize();
      for (fd=3; fd<numFds; fd++)
         close(fd);
      processGroup = getpid();
      ioctl(0, TIOCSPGRP, (char *)&processGroup);
      setpgrp (0, processGroup);
      execl("/bin/csh", "csh", "-c", s, 0) ;
      error("! vfork (child)") ;
   default:   // parent
      close(slavePty);
      childID = pidChild ;
      eofcount = 3 ;
      if (async) {
         [text setEditable:YES] ;
         if (fcntl(masterPty, F_SETFL, FNDELAY) == -1)
            error("! couldn't fcntl") ;
         DPSAddFD(masterPty, cm_handler, self, NX_MODALRESPTHRESHOLD) ;
         fdadded = 1 ;
         masterFD = masterPty ;
         eofcount = 0 ;
      } else {
         struct timezone tzp ;

         fdadded = 0 ;
         masterFD = 0 ;
         eofcount = 3 ;
         eofchar[0] = 4 ;
         write(masterPty, "\004", 1) ;
         waiting = ! [[self window] isVisible] ;
         gettimeofday(&waittime, &tzp) ;
         waittime.tv_sec += 5 ; /* pop up after five seconds if necessary */
         [self handler:masterPty] ;
      }
      break ;
   }
   return self ;
}

- abortCommand:sender
{
    return self;
}

- sendToMaster:(const char *)s
{
   if (masterFD)
      write(masterFD, s, strlen(s)) ;
   return self ;
}

- (BOOL)textWillChange:textObject
{
    [text setSel:[text textLength] :0];
    [text scrollSelToVisible];
    return NO;
}
- clear
{
   [text selectAll:self] ;
   if ([text isEditable])
      [text delete:self] ;
   else {
      [text setEditable:YES] ;
      [text delete:self] ;
      [text setEditable:NO] ;
   }
   return self ;
}

- text
{
   return text ;
}
@end
