]> git.ipfire.org Git - thirdparty/newt.git/blobdiff - form.c
install python modules to purelib and platlib
[thirdparty/newt.git] / form.c
diff --git a/form.c b/form.c
index a158659e44a0ce878f18ecfeafe394083aa56f47..24c601d41f3772f8c02e4ef04facf0f1431baac7 100644 (file)
--- a/form.c
+++ b/form.c
+#include "config.h"
+
+#include <sys/types.h>
+
+#include <slang.h>
 #include <stdarg.h>
 #include <stdlib.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#include <sys/time.h>
+
+#ifdef USE_GPM
+#include <ctype.h>
+#include <sys/time.h>      /* timeval */
+#include <sys/socket.h>    /* socket() */
+#include <sys/un.h>        /* struct sockaddr_un */
+#include <sys/fcntl.h>     /* O_RDONLY */
+#include <sys/stat.h>      /* stat() */
+#include <termios.h>       /* winsize */
+#include <unistd.h>
+#include <sys/kd.h>        /* KDGETMODE */
+#include <signal.h>
+#include <stdio.h>
+#endif
 
 #include "newt.h"
 #include "newt_pr.h"
 
+#ifdef USE_GPM
+/*....................................... The connection data structure */
+
+typedef struct Gpm_Connect {
+  unsigned short eventMask, defaultMask;
+  unsigned short minMod, maxMod;
+  int pid;
+  int vc;
+}              Gpm_Connect;
+
+/*....................................... Stack struct */
+typedef struct Gpm_Stst {
+  Gpm_Connect info;
+  struct Gpm_Stst *next;
+} Gpm_Stst;
+
+enum Gpm_Etype {
+  GPM_MOVE=1,
+  GPM_DRAG=2,   /* exactly one of the bare ones is active at a time */
+  GPM_DOWN=4,
+  GPM_UP=  8,
+
+#define GPM_BARE_EVENTS(type) ((type)&(0x0f|GPM_ENTER|GPM_LEAVE))
+
+  GPM_SINGLE=16,            /* at most one in three is set */
+  GPM_DOUBLE=32,
+  GPM_TRIPLE=64,            /* WARNING: I depend on the values */
+
+  GPM_MFLAG=128,            /* motion during click? */
+  GPM_HARD=256,             /* if set in the defaultMask, force an already
+                   used event to pass over to another handler */
+
+  GPM_ENTER=512,            /* enter event, user in Roi's */
+  GPM_LEAVE=1024            /* leave event, used in Roi's */
+};
+
+/*....................................... The reported event */
+
+enum Gpm_Margin {GPM_TOP=1, GPM_BOT=2, GPM_LFT=4, GPM_RGT=8};
+
+typedef struct Gpm_Event {
+  unsigned char buttons, modifiers;  /* try to be a multiple of 4 */
+  unsigned short vc;
+  short dx, dy, x, y;
+  enum Gpm_Etype type;
+  int clicks;
+  enum Gpm_Margin margin;
+}              Gpm_Event;
+
+static int Gpm_Open(Gpm_Connect *conn, int flag);
+static int Gpm_Close(void);
+
+static int gpm_fd=-1;
+static int gpm_flag=0;
+static int gpm_tried=0;
+Gpm_Stst *gpm_stack=NULL;
+static char *gpm_sock_name=NULL;
+static struct sigaction gpm_saved_suspend_hook;
+static struct sigaction gpm_saved_winch_hook;
+
+#define GPM_XTERM_ON
+#define GPM_XTERM_OFF
+#define GPM_NODE_DEV "/dev/gpmctl"
+#define GPM_NODE_CTL GPM_NODE_DEV
+
+static inline int putdata(int where,  Gpm_Connect *what)
+{
+  if (write(where,what,sizeof(Gpm_Connect))!=sizeof(Gpm_Connect))
+    {
+      return -1;
+    }
+  return 0;
+}
+
+static void gpm_winch_hook (int signum)
+{
+  if (SIG_IGN != gpm_saved_winch_hook.sa_handler &&
+      SIG_DFL != gpm_saved_winch_hook.sa_handler) {
+    gpm_saved_winch_hook.sa_handler(signum);
+  } /*if*/
+}
+
+static void gpm_suspend_hook (int signum)
+{
+  Gpm_Connect gpm_connect;
+  sigset_t old_sigset;
+  sigset_t new_sigset;
+  struct sigaction sa;
+  int success;
+
+  sigemptyset (&new_sigset);
+  sigaddset (&new_sigset, SIGTSTP);
+  sigprocmask (SIG_BLOCK, &new_sigset, &old_sigset);
+
+  /* Open a completely transparent gpm connection */
+  gpm_connect.eventMask = 0;
+  gpm_connect.defaultMask = ~0;
+  gpm_connect.minMod = ~0;
+  gpm_connect.maxMod = 0;
+  /* cannot do this under xterm, tough */
+  success = (Gpm_Open (&gpm_connect, 0) >= 0);
+
+  /* take the default action, whatever it is (probably a stop :) */
+  sigprocmask (SIG_SETMASK, &old_sigset, 0);
+  sigaction (SIGTSTP, &gpm_saved_suspend_hook, 0);
+  kill (getpid (), SIGTSTP);
+
+  /* in bardo here */
+
+  /* Reincarnation. Prepare for another death early. */
+  sigemptyset(&sa.sa_mask);
+  sa.sa_handler = gpm_suspend_hook;
+  sa.sa_flags = SA_NOMASK;
+  sigaction (SIGTSTP, &sa, 0);
+
+  /* Pop the gpm stack by closing the useless connection */
+  /* but do it only when we know we opened one.. */
+  if (success) {
+    Gpm_Close ();
+  } /*if*/
+}
+
+static int Gpm_Open(Gpm_Connect *conn, int flag)
+{
+  char tty[32];
+  char *term;
+  int i;
+  struct sockaddr_un addr;
+  Gpm_Stst *new;
+
+  /*....................................... First of all, check xterm */
+
+  if ((term=(char *)getenv("TERM")) && !strncmp(term,"xterm",5))
+    {
+      if (gpm_tried) return gpm_fd; /* no stack */
+      gpm_fd=-2;
+      GPM_XTERM_ON;
+      gpm_flag=1;
+      return gpm_fd;
+    }
+  /*....................................... No xterm, go on */
+
+
+  /*
+   * So I chose to use the current tty, instead of /dev/console, which
+   * has permission problems. (I am fool, and my console is
+   * readable/writeable by everybody.
+   *
+   * However, making this piece of code work has been a real hassle.
+   */
+
+  if (!gpm_flag && gpm_tried) return -1;
+  gpm_tried=1; /* do or die */
+
+  new=malloc(sizeof(Gpm_Stst));
+  if (!new) return -1;
+
+  new->next=gpm_stack;
+  gpm_stack=new;
+
+  conn->pid=getpid(); /* fill obvious values */
+
+  if (new->next)
+    conn->vc=new->next->info.vc; /* inherit */
+  else
+    {
+      conn->vc=0;                 /* default handler */
+      if (flag>0)
+        {  /* forced vc number */
+          conn->vc=flag;
+          sprintf(tty,"/dev/tty%i",flag);
+        }
+      else if (flag==0)  /* use your current vc */
+        {
+          char *t = ttyname(0); /* stdin */
+          if (!t) t = ttyname(1); /* stdout */
+          if (!t) goto err;
+          strcpy(tty,t);
+          if (strncmp(tty,"/dev/tty",8) || !isdigit(tty[8]))
+            goto err;
+          conn->vc=atoi(tty+8);
+        }
+      else /* a default handler -- use console */
+        sprintf(tty,"/dev/tty0");
+
+    }
+
+  new->info=*conn;
+
+  /*....................................... Connect to the control socket */
+
+  if (!(gpm_flag++))
+    {
+
+      if ( (gpm_fd=socket(AF_UNIX,SOCK_STREAM,0))<0 )
+        {
+          goto err;
+        }
+
+      bzero((char *)&addr,sizeof(addr));
+      addr.sun_family=AF_UNIX;
+      if (!(gpm_sock_name = tempnam (0, "gpm"))) {
+        goto err;
+      } /*if*/
+      strncpy (addr.sun_path, gpm_sock_name, sizeof (addr.sun_path));
+      if (bind (gpm_fd, (struct sockaddr*)&addr,
+                sizeof (addr.sun_family) + strlen (addr.sun_path))==-1) {
+        goto err;
+      } /*if*/
+
+      bzero((char *)&addr,sizeof(addr));
+      addr.sun_family=AF_UNIX;
+      strcpy(addr.sun_path, GPM_NODE_CTL);
+      i=sizeof(addr.sun_family)+strlen(GPM_NODE_CTL);
+
+      if ( connect(gpm_fd,(struct sockaddr *)(&addr),i)<0 )
+        {
+          struct stat stbuf;
+
+          /*
+           * Well, try to open a chr device called /dev/gpmctl. This should
+           * be forward-compatible with a kernel server
+           */
+          close(gpm_fd); /* the socket */
+          if ((gpm_fd=open(GPM_NODE_DEV,O_RDWR))==-1) {
+            goto err;
+          } /*if*/
+          if (fstat(gpm_fd,&stbuf)==-1 || (stbuf.st_mode&S_IFMT)!=S_IFCHR)
+            goto err;
+        }
+    }
+  /*....................................... Put your data */
+
+  if (putdata(gpm_fd,conn)!=-1)
+    {
+      /* itz Wed Dec 16 23:22:16 PST 1998 use sigaction, the old
+         code caused a signal loop under XEmacs */
+      struct sigaction sa;
+      sigemptyset(&sa.sa_mask);
+
+#if (defined(SIGWINCH))
+      /* And the winch hook .. */
+      sa.sa_handler = gpm_winch_hook;
+      sa.sa_flags = 0;
+      sigaction(SIGWINCH, &sa, &gpm_saved_winch_hook);
+#endif
+
+#if (defined(SIGTSTP))
+      if (gpm_flag == 1) {
+        /* Install suspend hook */
+        sa.sa_handler = SIG_IGN;
+        sigaction(SIGTSTP, &sa, &gpm_saved_suspend_hook);
+
+        /* if signal was originally ignored, job control is not supported */
+        if (gpm_saved_suspend_hook.sa_handler != SIG_IGN) {
+          sa.sa_flags = SA_NOMASK;
+          sa.sa_handler = gpm_suspend_hook;
+          sigaction(SIGTSTP, &sa, 0);
+        } /*if*/
+      } /*if*/
+#endif
+
+    } /*if*/
+  return gpm_fd;
+
+  /*....................................... Error: free all memory */
+ err:
+  do
+    {
+      new=gpm_stack->next;
+      free(gpm_stack);
+      gpm_stack=new;
+    }
+  while(gpm_stack);
+  if (gpm_fd>=0) close(gpm_fd);
+  if (gpm_sock_name) {
+    unlink(gpm_sock_name);
+    free(gpm_sock_name);
+    gpm_sock_name = NULL;
+  } /*if*/
+  gpm_flag=0;
+  gpm_fd=-1;
+  return -1;
+}
+
+/*-------------------------------------------------------------------*/
+static int Gpm_Close(void)
+{
+  Gpm_Stst *next;
+
+  gpm_tried=0; /* reset the error flag for next time */
+  if (gpm_fd==-2) /* xterm */
+    GPM_XTERM_OFF;
+  else            /* linux */
+    {
+      if (!gpm_flag) return 0;
+      next=gpm_stack->next;
+      free(gpm_stack);
+      gpm_stack=next;
+      if (next)
+        putdata(gpm_fd,&(next->info));
+
+      if (--gpm_flag) return -1;
+    }
+
+  if (gpm_fd>=0) close(gpm_fd);
+  gpm_fd=-1;
+  if (gpm_sock_name) {
+    unlink(gpm_sock_name);
+    free(gpm_sock_name);
+    gpm_sock_name = NULL;
+  }
+#ifdef SIGTSTP
+  sigaction(SIGTSTP, &gpm_saved_suspend_hook, 0);
+#endif
+#ifdef SIGWINCH
+  sigaction(SIGWINCH, &gpm_saved_winch_hook, 0);
+#endif
+  return 0;
+}
+
+/*-------------------------------------------------------------------*/
+static int Gpm_GetEvent(Gpm_Event *event)
+{
+  int count;
+
+  if (!gpm_flag) return 0;
+
+  if ((count=read(gpm_fd,event,sizeof(Gpm_Event)))!=sizeof(Gpm_Event))
+    {
+      if (count==0)
+        {
+          Gpm_Close();
+          return 0;
+        }
+      return -1;
+    }
+  return 1;
+}
+#endif
+
+/****************************************************************************
+    These forms handle vertical scrolling of components with a height of 1
+
+    Horizontal scrolling won't work, and scrolling large widgets will fail
+    miserably. It shouldn't be too hard to fix either of those if anyone
+    cares to. I only use scrolling for listboxes and text boxes though so
+    I didn't bother.
+*****************************************************************************/
+
+struct element {
+    newtComponent co;
+};
+
+struct fdInfo {
+    int fd;
+    int flags;
+};
+
 struct form {
     int numCompsAlloced;
-    newtComponent * comps;
+    struct element * elements;
     int numComps;
     int currComp;
+    int fixedHeight;
+    int flags;
+    int vertOffset;
+    newtComponent vertBar, exitComp;
+    const char * help;
+    int numRows;
+    int * hotKeys;
+    int numHotKeys;
+    int background;
+    int numFds;
+    struct fdInfo * fds;
+    int maxFd;
+    int timer;    /* in milliseconds */
+    struct timeval lastTimeout;
+    void * helpTag;
+    newtCallback helpCb;
 };
 
-static void formDraw(newtComponent co);
-static void gotoComponent(struct form * form, int newComp);
+static void gotoComponent(newtComponent co, int newComp);
 static struct eventResult formEvent(newtComponent co, struct event ev);
+static struct eventResult sendEvent(newtComponent comp, struct event ev);
+static void formPlace(newtComponent co, int left, int top);
 
-static struct componentOps formOps = {
-    formDraw,
+/* Global, ick */
+static newtCallback helpCallback;
+
+/* this isn't static as grid.c tests against it to find forms */
+struct componentOps formOps = {
+    newtDrawForm,
     formEvent,
     newtFormDestroy,
+    formPlace,
+    newtDefaultMappedHandler,
 } ;
 
-newtComponent newtForm(void) {
+int needResize = 0;
+
+static inline int componentFits(newtComponent co, int compNum) {
+    struct form * form = co->data;
+    struct element * el = form->elements + compNum;
+
+    if (co->top > el->co->top)
+       return 0;
+    if (co->top + co->height < el->co->top + el->co->height)
+       return 0;
+
+    return 1;
+}
+
+newtComponent newtForm(newtComponent vertBar, void * help, int flags) {
     newtComponent co;
     struct form * form;
 
@@ -32,55 +451,142 @@ newtComponent newtForm(void) {
     co->height = 0;
     co->top = -1;
     co->left = -1;
+    co->isMapped = 0;
 
-    co->takesFocus = 0;
+    co->takesFocus = 0;                        /* we may have 0 components */
     co->ops = &formOps;
+    co->callback = NULL;
+    co->destroyCallback = NULL;
 
-    form = malloc(sizeof(*form));
+    form->help = help;
+    form->flags = flags;
     form->numCompsAlloced = 5;
     form->numComps = 0;
     form->currComp = -1;
-    form->comps = malloc(sizeof(*(form->comps)) * form->numCompsAlloced);
+    form->vertOffset = 0;
+    form->fixedHeight = 0;
+    form->numRows = 0;
+    form->numFds = 0;
+    form->maxFd = 0;
+    form->fds = NULL;
+    form->elements = malloc(sizeof(*(form->elements)) * form->numCompsAlloced);
+
+    form->background = COLORSET_WINDOW;
+    form->hotKeys = malloc(sizeof(int));
+    form->numHotKeys = 0;
+    form->timer = 0;
+    form->lastTimeout.tv_sec = form->lastTimeout.tv_usec = 0;
+    if (!(form->flags & NEWT_FLAG_NOF12)) {
+       newtFormAddHotKey(co, NEWT_KEY_F12);
+    }
+
+    if (vertBar)
+       form->vertBar = vertBar;
+    else
+       form->vertBar = NULL;
+
+    form->helpTag = help;
+    form->helpCb = helpCallback;
 
     return co;
 }
 
-void newtFormAddComponent(newtComponent co, newtComponent newco) {
+newtComponent newtFormGetCurrent(newtComponent co) {
     struct form * form = co->data;
-    int delta;
 
-    if (form->numCompsAlloced == form->numComps) {
-       form->numCompsAlloced += 5;
-       form->comps = realloc(form->comps, 
-                             sizeof(*(form->comps)) * form->numCompsAlloced);
+    if (form->currComp == -1) return 0;
+    return form->elements[form->currComp].co;
+}
+
+static void formScroll(newtComponent co, int delta) {
+    struct form * form = co->data;
+    struct element * el;
+    int i, newVertOffset = form->vertOffset + delta;
+
+    if (newVertOffset < 0)
+       newVertOffset = 0;
+    if (newVertOffset > form->numRows - co->height)
+       newVertOffset = form->numRows - co->height;
+
+    delta = newVertOffset - form->vertOffset;
+    form->vertOffset = newVertOffset;
+
+    for (i = 0, el = form->elements; i < form->numComps; i++, el++) {
+       if (el->co == form->vertBar)
+           continue;
+       el->co->ops->place(el->co, el->co->left, el->co->top - delta);
     }
+}
 
-    form->comps[form->numComps++] = newco;
+int newtFormGetScrollPosition(newtComponent co) {
+    struct form * form = co->data;
 
-    if (co->left == -1) {
-       co->left = newco->left;
-       co->top = newco->top;
-       co->width = newco->width;
-       co->height = newco->height;
-    } else {
-       if (co->left > newco->left) {
-           delta = co->left - newco->left;
-           co->left -= delta;
-           co->width += delta;
-       }
+    return form->vertOffset;
+}
 
-       if (co->top > newco->top) {
-           delta = co->top - newco->top;
-           co->top -= delta;
-           co->height += delta;
-       }
+void newtFormSetScrollPosition(newtComponent co, int position) {
+    struct form * form = co->data;
+
+    if (form->numRows == 0)
+       newtFormSetSize(co);
+    formScroll(co, position - form->vertOffset);
+}
+
+void newtFormSetCurrent(newtComponent co, newtComponent subco) {
+    struct form * form = co->data;
+    int i, new;
+
+    for (i = 0; i < form->numComps; i++) {
+        if (form->elements[i].co == subco) break;
+    }
 
-       if ((co->left + co->width) < (newco->left + newco->width)) 
-           co->left = (newco->left + newco->width) - co->width;
+    if (form->elements[i].co != subco) return;
+    new = i;
 
-       if ((co->top + co->height) < (newco->top + newco->height)) 
-           co->top = (newco->top + newco->height) - co->height;
+    if (co->isMapped && !componentFits(co, new)) {
+       gotoComponent(co, -1);
+       formScroll(co, form->elements[new].co->top - co->top - 1);
     }
+
+    gotoComponent(co, new);
+}
+
+void newtFormSetTimer(newtComponent co, int millisecs) {
+    struct form * form = co->data;
+
+    form->timer = millisecs;
+    form->lastTimeout.tv_usec = 0;
+    form->lastTimeout.tv_sec = 0;
+}
+
+void newtFormSetHeight(newtComponent co, int height) {
+    struct form * form = co->data;
+
+    form->fixedHeight = 1;
+    co->height = height;
+}
+
+void newtFormSetWidth(newtComponent co, int width) {
+    co->width = width;
+}
+
+void newtFormAddComponent(newtComponent co, newtComponent newco) {
+    struct form * form = co->data;
+
+    co->takesFocus = 1;
+
+    if (form->numCompsAlloced == form->numComps) {
+       form->numCompsAlloced += 5;
+       form->elements = realloc(form->elements,
+                           sizeof(*(form->elements)) * form->numCompsAlloced);
+    }
+
+    form->elements[form->numComps].co = newco;
+
+    if (newco->takesFocus && form->currComp == -1)
+       form->currComp = form->numComps;
+
+    form->numComps++;
 }
 
 void newtFormAddComponents(newtComponent co, ...) {
@@ -91,149 +597,635 @@ void newtFormAddComponents(newtComponent co, ...) {
 
     while ((subco = va_arg(ap, newtComponent)))
        newtFormAddComponent(co, subco);
+
     va_end(ap);
 }
 
-static void formDraw(newtComponent co) {
+static void formPlace(newtComponent co, int left, int top) {
     struct form * form = co->data;
-    newtComponent subco;
+    int vertDelta, horizDelta;
+    struct element * el;
     int i;
 
-    for (i = 0; i < form->numComps; i++) {
-       subco = form->comps[i];
-       subco->ops->draw(subco);
+    vertDelta = top - co->top;
+    horizDelta = left - co->left;
+    co->top = top;
+    co->left = left;
+
+    for (i = 0, el = form->elements; i < form->numComps; i++, el++) {
+       el->co->ops->place(el->co, el->co->left + horizDelta,
+               el->co->top + vertDelta);
     }
 }
 
+void newtDrawForm(newtComponent co) {
+    struct form * form = co->data;
+    struct element * el;
+    int i;
+
+    newtFormSetSize(co);
+
+    SLsmg_set_color(form->background);
+    newtClearBox(co->left, co->top, co->width, co->height);
+
+    for (i = 0, el = form->elements; i < form->numComps; i++, el++) {
+       /* only draw it if it'll fit on the screen vertically
+          (the scrollbar *always* fits somewhere) */
+       if (el->co == form->vertBar || componentFits(co, i)) {
+           el->co->ops->mapped(el->co, 1);
+           el->co->ops->draw(el->co);
+       } else {
+           el->co->ops->mapped(el->co, 0);
+       }
+    }
+
+    if (form->vertBar)
+       newtScrollbarSet(form->vertBar, form->vertOffset,
+                        form->numRows - co->height);
+}
+
 static struct eventResult formEvent(newtComponent co, struct event ev) {
     struct form * form = co->data;
-    newtComponent subco = form->comps[form->currComp];
-    int new;
+    newtComponent subco = form->elements[form->currComp].co;
+    int new, wrap = 0;
     struct eventResult er;
+    int dir = 0, page = 0;
+    int i, num, found;
+    struct element * el;
 
     er.result = ER_IGNORED;
+    if (!form->numComps) return er;
+
+    if (form->currComp == -1) return er;
+
+    switch (ev.when) {
+      case EV_EARLY:
+         if (ev.event == EV_KEYPRESS) {
+           if (ev.u.key == NEWT_KEY_TAB) {
+               er.result = ER_SWALLOWED;
+               dir = 1;
+               wrap = 1;
+           } else if (ev.u.key == NEWT_KEY_UNTAB) {
+               er.result = ER_SWALLOWED;
+               dir = -1;
+               wrap = 1;
+           }
+       }
+
+       if (form->numComps) {
+           i = form->currComp;
+           num = 0;
+           while (er.result == ER_IGNORED && num != form->numComps ) {
+               er = form->elements[i].co->ops->event(form->elements[i].co, ev);
+
+               num++;
+               i++;
+               if (i == form->numComps) i = 0;
+           }
+       }
 
-    switch (ev.event) {
-      case EV_FOCUS:
-      case EV_UNFOCUS:
-       er = subco->ops->event(subco, ev);
        break;
-     
-      case EV_KEYPRESS:
-       if (ev.u.key == NEWT_KEY_TAB) {
-           er.result = ER_NEXTCOMP;
-       } 
 
-       if (er.result == ER_IGNORED) {
-           /* let the current component handle the event */
-           er = subco->ops->event(subco, ev);
+      case EV_NORMAL:
+         if (ev.event == EV_MOUSE) {
+             found = 0;
+             for (i = 0, el = form->elements; i < form->numComps; i++, el++) {
+                 if ((el->co->top <= ev.u.mouse.y) &&
+                     (el->co->top + el->co->height > ev.u.mouse.y) &&
+                     (el->co->left <= ev.u.mouse.x) &&
+                     (el->co->left + el->co->width > ev.u.mouse.x)) {
+                     found = 1;
+                     if (el->co->takesFocus) {
+                         gotoComponent(co, i);
+                         subco = form->elements[form->currComp].co;
+                     }
+                 }
+                 /* If we did not find a co to send this event to, we
+                    should just swallow the event here. */
+             }
+             if (!found) {
+                 er.result = ER_SWALLOWED;
+
+                 return er;
+             }
+         }
+       er = subco->ops->event(subco, ev);
+       switch (er.result) {
+         case ER_NEXTCOMP:
+           er.result = ER_SWALLOWED;
+           dir = 1;
+           break;
+
+         case ER_EXITFORM:
+           form->exitComp = subco;
+           break;
+
+         default:
+           break;
        }
+       break;
+
+      case EV_LATE:
+       er = subco->ops->event(subco, ev);
 
        if (er.result == ER_IGNORED) {
-           /* handle default events */
-           if (er.result == ER_IGNORED) {
-               switch (ev.u.key) {
-                 case NEWT_KEY_UP:
-                 case NEWT_KEY_LEFT:
-                 case NEWT_KEY_BKSPC:
-                   er.result = ER_PREVCOMP;
-                   break;
+           switch (ev.u.key) {
+             case NEWT_KEY_UP:
+             case NEWT_KEY_LEFT:
+             case NEWT_KEY_BKSPC:
+               er.result = ER_SWALLOWED;
+               dir = -1;
+               break;
 
-                 case NEWT_KEY_DOWN:
-                 case NEWT_KEY_RIGHT:
-                 case NEWT_KEY_ENTER:
-                   er.result = ER_NEXTCOMP;
-                   break;
-               }
+             case NEWT_KEY_DOWN:
+             case NEWT_KEY_RIGHT:
+               er.result = ER_SWALLOWED;
+               dir = 1;
+               break;
+
+            case NEWT_KEY_PGUP:
+               er.result = ER_SWALLOWED;
+               dir = -1;
+               page = 1;
+               break;
+
+            case NEWT_KEY_PGDN:
+               er.result = ER_SWALLOWED;
+               dir = 1;
+               page = 1;
+               break;
            }
        }
     }
 
-    /* we try and do previous/next actions ourselves if possible */
-    if (er.result == ER_PREVCOMP) {
-       if (form->currComp > 0) {
-           new = form->currComp - 1;
-           gotoComponent(form, new);
-           er.result = ER_SWALLOWED;
-       } 
-    } else if (er.result == ER_NEXTCOMP) {
-       new = form->currComp + 1;
-       if (new < form->numComps) {
-           gotoComponent(form, new);
-           er.result = ER_SWALLOWED;
+    if (dir) {
+       new = form->currComp;
+
+       if (page) {
+           new += dir * co->height;
+           if (new < 0)
+               new = 0;
+           else if (new >= form->numComps)
+               new = (form->numComps - 1);
+
+           while (!form->elements[new].co->takesFocus &&
+                   new - dir >= 0 && new - dir < form->numComps)
+               new -= dir;
+       } else {
+           do {
+               new += dir;
+
+               if (wrap) {
+                   if (new < 0)
+                       new = form->numComps - 1;
+                   else if (new >= form->numComps)
+                       new = 0;
+                   if (new == form->currComp)
+                       /* back where we started */
+                       return er;
+               } else if (new < 0 || new >= form->numComps)
+                   return er;
+           } while (!form->elements[new].co->takesFocus);
        }
+
+       /* make sure this component is visible */
+       if (!componentFits(co, new)) {
+           int vertDelta;
+
+           gotoComponent(co, -1);
+
+           if (dir < 0) {
+               /* make the new component the first one */
+               vertDelta = form->elements[new].co->top - co->top;
+           } else {
+               /* make the new component the last one */
+               vertDelta = (form->elements[new].co->top +
+                                       form->elements[new].co->height) -
+                                   (co->top + co->height);
+           }
+
+           formScroll(co, vertDelta);
+           newtDrawForm(co);
+       }
+
+       gotoComponent(co, new);
+       er.result = ER_SWALLOWED;
     }
 
     return er;
 }
 
+/* Destroy a component.  Components which have been added to a form
+ * are destroyed when the form is destroyed; this is just for the
+ * (rare) case of components which for whatever reason weren't added
+ * to a form.
+ */
+void newtComponentDestroy(newtComponent co) {
+    /* If the user registered a destroy callback for this component,
+     * now is a good time to call it.
+     */
+    if (co->destroyCallback)
+        co->destroyCallback(co, co->destroyCallbackData);
+
+    if (co->ops->destroy) {
+        co->ops->destroy(co);
+    } else {
+        if (co->data) free(co->data);
+       free(co);
+    }
+}
+
 /* this also destroys all of the components on the form */
 void newtFormDestroy(newtComponent co) {
-    int i;
     newtComponent subco;
     struct form * form = co->data;
+    int i;
 
     /* first, destroy all of the components */
     for (i = 0; i < form->numComps; i++) {
-       subco = form->comps[i];
-       if (subco->ops->destroy) {
-           subco->ops->destroy(subco);
-       } else {
-           if (subco->data) free(subco->data);
-           free(subco);
-       }       
+       subco = form->elements[i].co;
+       newtComponentDestroy(subco);
     }
 
-    free(form->comps);
+    if (form->hotKeys) free(form->hotKeys);
+
+    free(form->elements);
     free(form);
     free(co);
 }
 
 newtComponent newtRunForm(newtComponent co) {
+    struct newtExitStruct es;
+
+    newtFormRun(co, &es);
+    if (es.reason == NEWT_EXIT_HOTKEY) {
+       if (es.u.key == NEWT_KEY_F12) {
+           es.reason = NEWT_EXIT_COMPONENT;
+           es.u.co = co;
+       } else {
+           return NULL;
+       }
+    } else if (es.reason == NEWT_EXIT_ERROR)
+       return NULL;
+
+    return es.u.co;
+}
+
+void newtFormAddHotKey(newtComponent co, int key) {
+    struct form * form = co->data;
+
+    form->numHotKeys++;
+    form->hotKeys = realloc(form->hotKeys, sizeof(int) * form->numHotKeys);
+    form->hotKeys[form->numHotKeys - 1] = key;
+}
+
+void newtFormSetSize(newtComponent co) {
+    struct form * form = co->data;
+    int delta, i, first;
+    struct element * el;
+
+    form->numRows = 0;
+
+    co->width = 0;
+    if (!form->fixedHeight) co->height = 0;
+
+    co->top = -1;
+    co->left = -1;
+    first = 1;
+
+    for (i = 0, el = form->elements; i < form->numComps; i++, el++) {
+       if (el->co->ops == &formOps)
+           newtFormSetSize(el->co);
+       else if (el->co == form->vertBar)
+           continue;
+
+       if (first) {
+           co->top = el->co->top;
+           co->left = el->co->left;
+           first = 0;
+       }
+
+       if (co->left > el->co->left) {
+           delta = co->left - el->co->left;
+           co->left -= delta;
+           co->width += delta;
+       }
+
+       if (co->top > el->co->top) {
+           delta = co->top - el->co->top;
+           co->top -= delta;
+           form->numRows += delta;
+           if (!form->fixedHeight)
+               co->height += delta;
+       }
+
+       if ((co->left + co->width) < (el->co->left + el->co->width))
+           co->width = (el->co->left + el->co->width) - co->left;
+
+       if (!form->fixedHeight) {
+           if ((co->top + co->height) < (el->co->top + el->co->height))
+               co->height = (el->co->top + el->co->height) - co->top;
+       }
+
+       if ((el->co->top + el->co->height - co->top) > form->numRows) {
+           form->numRows = el->co->top + el->co->height - co->top;
+       }
+    }
+
+    co->top += form->vertOffset;
+}
+
+void newtFormRun(newtComponent co, struct newtExitStruct * es) {
     struct form * form = co->data;
     struct event ev;
     struct eventResult er;
-    int key;
+    int key, i, max;
+    int done = 0;
+    fd_set readSet, writeSet, exceptSet;
+    struct timeval nextTimeout, now, timeout;
+#ifdef USE_GPM
+    int x, y;
+    Gpm_Connect conn;
+    Gpm_Event event;
 
-    /* first, draw all of the components */
-    formDraw(co);
+    /* Set up GPM interface */
+    conn.eventMask   = ~GPM_MOVE;
+    conn.defaultMask = GPM_MOVE;
+    conn.minMod      = 0;
+    conn.maxMod      = 0;
 
-    if (form->currComp == -1)
-       gotoComponent(form, 0);
-    else
-       gotoComponent(form, form->currComp);
-  
-    do {
+    Gpm_Open(&conn, 0);
+#endif
+
+    /* draw all of the components */
+    newtDrawForm(co);
+
+    if (form->currComp == -1) {
+       if (form->numComps)
+           gotoComponent(co, 0);
+    } else
+       gotoComponent(co, form->currComp);
+
+    while (!done) {
        newtRefresh();
-       key = newtGetKey(); 
 
-       ev.event = EV_KEYPRESS;
-       ev.u.key = key;
-       er = formEvent(co, ev);
+       FD_ZERO(&readSet);
+       FD_ZERO(&writeSet);
+       FD_ZERO(&exceptSet);
+       FD_SET(0, &readSet);
+#ifdef USE_GPM
+       if (gpm_fd > 0) {
+           FD_SET(gpm_fd, &readSet);
+       }
+       max = form->maxFd > gpm_fd ? form->maxFd : gpm_fd;
+#else
+       max = form->maxFd;
+#endif
+
+       for (i = 0; i < form->numFds; i++) {
+           if (form->fds[i].flags & NEWT_FD_READ)
+               FD_SET(form->fds[i].fd, &readSet);
+           if (form->fds[i].flags & NEWT_FD_WRITE)
+               FD_SET(form->fds[i].fd, &writeSet);
+           if (form->fds[i].flags & NEWT_FD_EXCEPT)
+               FD_SET(form->fds[i].fd, &exceptSet);
+       }
+
+       if (form->timer) {
+           /* Calculate when we next need to return with a timeout. Do
+              this inside the loop in case a callback resets the timer. */
+           gettimeofday(&now, 0);
+
+           if ((!form->lastTimeout.tv_sec && !form->lastTimeout.tv_usec) ||
+                   now.tv_sec < form->lastTimeout.tv_sec ||
+                   (now.tv_sec == form->lastTimeout.tv_sec &&
+                    now.tv_usec < form->lastTimeout.tv_usec))
+               form->lastTimeout = now;
+
+           nextTimeout.tv_sec = form->lastTimeout.tv_sec + 
+                   (form->timer / 1000);
+           nextTimeout.tv_usec = form->lastTimeout.tv_usec + 
+                                   (form->timer % 1000) * 1000;
+
+           if (now.tv_sec > nextTimeout.tv_sec) {
+               timeout.tv_sec = timeout.tv_usec = 0;
+           } else if (now.tv_sec == nextTimeout.tv_sec) {
+               timeout.tv_sec = 0;
+               if (now.tv_usec > nextTimeout.tv_usec)
+                   timeout.tv_usec = 0;
+               else
+                   timeout.tv_usec = nextTimeout.tv_usec - now.tv_usec;
+           } else if (now.tv_sec < nextTimeout.tv_sec) {
+               timeout.tv_sec = nextTimeout.tv_sec - now.tv_sec;
+               if (now.tv_usec > nextTimeout.tv_usec)
+                   timeout.tv_sec--,
+                   timeout.tv_usec = nextTimeout.tv_usec + 1000000 -
+                                       now.tv_usec;
+               else 
+                   timeout.tv_usec = nextTimeout.tv_usec - now.tv_usec;
+           }
+       } else {
+           timeout.tv_sec = timeout.tv_usec = 0;
+       }
+
+       if (needResize) {
+               needResize = 0;
+               newtResizeScreen(1);
+
+               /* The application may want to handle the resize */
+               for (i = 0; i < form->numHotKeys; i++) {
+                   if (form->hotKeys[i] == NEWT_KEY_RESIZE) {
+                       es->reason = NEWT_EXIT_HOTKEY;
+                       es->u.key = NEWT_KEY_RESIZE;
+                       done = 1;
+                       break;
+                   }
+               }
+               if (done)
+                   break;
+       }
+
+       i = select(max + 1, &readSet, &writeSet, &exceptSet, 
+                       form->timer ? &timeout : NULL);
+       if (i < 0) continue;    /* ?? What should we do here? */
+
+       if (i == 0) {
+           done = 1;
+           es->reason = NEWT_EXIT_TIMER;
+           gettimeofday(&form->lastTimeout, NULL);
+       } else
+#ifdef USE_GPM
+       if (gpm_fd > 0 && FD_ISSET(gpm_fd, &readSet)) {
+           Gpm_GetEvent(&event);
+
+           if (event.type & GPM_DOWN) {
+               /* Transform coordinates to current window */
+               newtGetWindowPos(&x, &y);
+
+               ev.event = EV_MOUSE;
+               ev.u.mouse.type = MOUSE_BUTTON_DOWN;
+               ev.u.mouse.x = event.x - x - 1;
+               ev.u.mouse.y = event.y - y - 1;
+
+               /* Send the form the event */
+               er = sendEvent(co, ev);
+
+               if (er.result == ER_EXITFORM) {
+                   done = 1;
+                   es->reason = NEWT_EXIT_COMPONENT;
+                   es->u.co = form->exitComp;
+               }
+
+           }
+       } else
+#endif
+       {
+           if (FD_ISSET(0, &readSet)) {
+
+               key = newtGetKey();
+
+               for (i = 0; i < form->numHotKeys; i++) {
+                   if (form->hotKeys[i] == key) {
+                       es->reason = NEWT_EXIT_HOTKEY;
+                       es->u.key = key;
+                       done = 1;
+                       break;
+                   }
+               }
+
+               if (key == NEWT_KEY_F1 && form->helpTag && form->helpCb) {
+                   if (form->currComp != -1) {
+                       ev.event = EV_UNFOCUS;
+                       sendEvent(form->elements[form->currComp].co, ev);
+                   }
+                   form->helpCb(co, form->helpTag);
+                   if (form->currComp != -1) {
+                       ev.event = EV_FOCUS;
+                       sendEvent(form->elements[form->currComp].co, ev);
+                   }
+               }
+
+               if (key == NEWT_KEY_ERROR) {
+                   es->u.watch = -1;
+                   es->reason = NEWT_EXIT_ERROR;
+                   done = 1;
+               }
+
+               if (!done) {
+                   ev.event = EV_KEYPRESS;
+                   ev.u.key = key;
+
+                   er = sendEvent(co, ev);
 
-       /* EV_NEXTCOMP and EV_PREVCOMP should cause wrapping */
-    } while (er.result != ER_EXITFORM);
+                   if (er.result == ER_EXITFORM) {
+                       done = 1;
+                       es->reason = NEWT_EXIT_COMPONENT;
+                       es->u.co = form->exitComp;
+                   }
+               }
+           } else {
+               for (i = 0; i < form->numFds; i++) {
+                   if (((form->fds[i].flags & NEWT_FD_READ)
+                       && FD_ISSET(form->fds[i].fd, &readSet))
+                       || ((form->fds[i].flags & NEWT_FD_WRITE)
+                       && FD_ISSET(form->fds[i].fd, &writeSet))
+                       || ((form->fds[i].flags & NEWT_FD_EXCEPT)
+                       && FD_ISSET(form->fds[i].fd, &exceptSet))) break;
+               }
+               if(i < form->numFds)
+                   es->u.watch = form->fds[i].fd;
+               else
+                   es->u.watch = -1;
 
+               es->reason = NEWT_EXIT_FDREADY;
+               done = 1;
+           }
+       }
+    }
     newtRefresh();
+#ifdef USE_GPM
+    Gpm_Close();
+#endif
+}
+
+static struct eventResult sendEvent(newtComponent co, struct event ev) {
+    struct eventResult er;
+
+    ev.when = EV_EARLY;
+    er = co->ops->event(co, ev);
+
+    if (er.result == ER_IGNORED) {
+       ev.when = EV_NORMAL;
+       er = co->ops->event(co, ev);
+    }
 
-    return form->comps[form->currComp];
+    if (er.result == ER_IGNORED) {
+       ev.when = EV_LATE;
+       er = co->ops->event(co, ev);
+    }
 
+    return er;
 }
 
-static void gotoComponent(struct form * form, int newComp) {
-    newtComponent co;
+static void gotoComponent(newtComponent co, int newComp) {
+    struct form * form = co->data;
     struct event ev;
 
     if (form->currComp != -1) {
        ev.event = EV_UNFOCUS;
-       co = form->comps[form->currComp];
-       co->ops->event(co, ev);
+       sendEvent(form->elements[form->currComp].co, ev);
     }
 
     form->currComp = newComp;
-   
-    ev.event = EV_FOCUS;
-    co = form->comps[form->currComp];
-    co->ops->event(co, ev);
+
+    if (form->currComp != -1) {
+       ev.event = EV_FOCUS;
+       ev.when = EV_NORMAL;
+       sendEvent(form->elements[form->currComp].co, ev);
+    }
+
+    if (co->callback)
+       co->callback(co, co->callbackData);
+}
+
+void newtComponentAddCallback(newtComponent co, newtCallback f, void * data) {
+    co->callback = f;
+    co->callbackData = data;
+}
+
+/* Add a callback which is called when the component is destroyed. */
+void newtComponentAddDestroyCallback(newtComponent co,
+                               newtCallback f, void * data) {
+    co->destroyCallback = f;
+    co->destroyCallbackData = data;
+}
+
+void newtComponentTakesFocus(newtComponent co, int val) {
+    co->takesFocus = val;
+}
+
+void newtFormSetBackground(newtComponent co, int color) {
+    struct form * form = co->data;
+
+    form->background = color;
+}
+
+void newtFormWatchFd(newtComponent co, int fd, int fdFlags) {
+    struct form * form = co->data;
+    int i;
+
+    for (i = 0; i < form->numFds; i++)
+      if (form->fds[i].fd == fd)
+       break;
+
+    if(i >= form->numFds)
+      form->fds = realloc(form->fds, (++form->numFds) * sizeof(*form->fds));
+
+    form->fds[i].fd = fd;
+    form->fds[i].flags = fdFlags;
+    if (form->maxFd < fd) form->maxFd = fd;
+}
+
+void newtSetHelpCallback(newtCallback cb) {
+    helpCallback = cb;
 }