/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : Forms.c                                */
/*                                                 */
/* Purpose: Functions to manage HTML forms.        */
/*                                                 */
/* Author : Merlyn Kline for Customer browser     */
/*          This source adapted by A.D.Hodgkinson  */
/*                                                 */
/* History: 28-Jan-97: Created.                    */
/***************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "swis.h"
#include "flex.h"

#include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "svcprint.h"
#include "Global.h"
#include "FromROSLib.h"
#include "Utils.h"

#include "Browser.h"
#include "FetchPage.h"
#include "FontManage.h"
#include "Images.h"
#include "Redraw.h"
#include "TokenUtils.h"
#include "Toolbars.h"

#include "Forms.h"

#include "trace.h"

/* Local memory allocation granularity control */

#define F_BLOCKSIZE 1024

/* Characters used in temporary local encoding of form data */

#define ENCODE_DATASEP         1 /* Usually becomes '?' */
#define ENCODE_FIELDSEP        2 /* Usually becomes '&' */
#define ENCODE_VALUESEP        3 /* Usually becomes '=' */

#define FORM_AUTOSCROLL_MARGIN 128

#define FORM_SELECTED(p)       (!!((*(p)) & 1))
#define FORM_SELCHAR           'Y'
#define FORM_UNSELCHAR         'N'

/* Miscellaneous local definitions */

#define Forms_Menu_Separator     "---"
#define Forms_Menu_Separator_Len 3

/* Local compilation options */

#undef ARROWS_MOVE_OUT

/* Local structures */

typedef struct form_header
{
  HStream * token;  /* Token defining this form      */
  int       fields; /* Number of fields on this form */

} form_header;

typedef struct form_field_header
{
  form_fieldtype   type;
  HStream        * token; /* Defining token           */
  int              size;  /* Number of words occupied */

} form_field_header;

typedef struct form_field
{
  form_field_header header;
  form_field_value  value;

} form_field;

/* Statics */

static browser_data * fe_browser    = 0;    /* Browser containing current edit, 0 = no edits */
static HStream      * fe_token      = NULL; /* Currently edited token, NULL = none           */
static int            fe_index      = 0;    /* Index of caret into text area when editing    */
static int            fe_xscroll    = 0;    /* Scroll offset of edited field (OS coords)     */
static int            fe_yscroll    = 0;    /* Scroll offset of edited field (lines)         */
static int            fe_single     = 1;    /* Edited field is single line field             */
static int            fe_password   = 0;    /* Edited field is a password field              */

static wimp_menustr * fe_menu       = NULL; /* The current form menu for SELECTs      */
static browser_data * fe_mbrowser   = NULL; /* Browser we clicked menu on for SELECTs */
static HStream      * fe_mtoken     = NULL; /* Token we clicked menu on for SELECTs   */
static int            fe_lastkey    = 0;    /* Last key run through form_process_key  */

// /*************************************************/
// /* form_show_edit_token()                        */
// /*                                               */
// /* Ensures that the currently editing item, if   */
// /* there is one, is visible in the browser       */
// /* window that the form lies in.                 */
// /*************************************************/
//
// static void form_show_edit_token(void)
// {
//   /* If there is a currently editing browser and token, */
//   /* and that token is not fully visible, reshow the    */
//   /* item.                                              */
//
//   if (
//        fe_browser                                &&
//        fe_token                                  &&
//        !form_token_visible(fe_browser, fe_token)
//      )
//
//      browser_show_token(fe_browser, fe_token, 0);
// }
//
// /*************************************************/
// /* form_token_visible()                          */
// /*                                               */
// /* Checks if a given forms item, indentified by  */
// /* an HStream structure, is visible in the       */
// /* browser window holding that form.             */
// /*                                               */
// /* The forms item should be one which will       */
// /* return sensible information when run through  */
// /* form_input_box (e.g. a writable field, text   */
// /* area, etc.).                                  */
// /*                                               */
// /* Parameters: Pointer to a browser_data struct  */
// /*             relevant to the form;             */
// /*                                               */
// /*             Pointer to an HStream struct      */
// /*             representing the field.           */
// /*                                               */
// /* Returns:    1 if the field is fully visible,  */
// /*             else 0.                           */
// /*************************************************/
//
// static int form_token_visible(browser_data * b, HStream * token)
// {
//   WimpGetWindowStateBlock state;
//   int                     ymin, ymax, lh, lb, fh, htop, hbot;
//   BBox                    box;
//
//   /* Get the window state */
//
//   state.window_handle = b->window_handle;
//
//   if (wimp_get_window_state(&state)) return 0;
//
//   /* Work out the page coordinates at the top and bottom */
//   /* of the visible area                                 */
//
//   if (!controls.swap_bars)
//   {
//     htop = toolbars_button_height(b) + toolbars_url_height(b);
//     hbot = toolbars_status_height(b);
//   }
//   else
//   {
//     htop = toolbars_status_height(b);
//     hbot = toolbars_button_height(b) + toolbars_url_height(b);
//   }
//
//   if (htop) htop += wimpt_dy();
//   if (hbot) hbot += wimpt_dy();
//
//   ymax = coords_y_toworkarea(state.visible_area.ymax - htop;
//                              (WimpRedrawWindowBlock *) &state);
//
//   ymin = coords_y_toworkarea(state.visible_area.ymin + hbot;
//                              (WimpRedrawWindowBlock *) &state);
//
//   /* Get the position of the forms item */
//
//   if (!form_input_box(b, token, &box, &lh, &lb, &fh)) return 0;
//
//   /* Work ouf if it is visible */
//
//   if (box.ymin > ymin && box.ymax < ymax) return 1;
//   if (box.ymax < ymin || box.ymin > ymin) return 0;
//
//   if (box.ymax - box.ymin > ymax - ymin) return 1;
//
//   return 0;
// }

/*************************************************/
/* form_ensure_free()                            */
/*                                               */
/* Ensures that a given amount of free space is  */
/* present in the form store. This may not mean  */
/* that the block extends due to granularity of  */
/* allocation considerations.                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form store;       */
/*                                               */
/*             Space to ensure is present.       */
/*                                               */
/* Assumes:    The space may be 0, in which case */
/*             the block is freed, and there may */
/*             or may not be allocated store on  */
/*             entry. I.e., this copes with just */
/*             about everything.                 */
/*************************************************/

static _kernel_oserror * form_ensure_free(browser_data * b, int free)
{
  int ok = 1, oldsize, alloc;

  /* Work out how much extra space needs allocating to */
  /* give the requested free.                          */

  if (b->fdata)
  {
    int used;

    alloc   = 0;
    used    = *(int *)b->fdata; /* Amount of the block that's actually in use */
    oldsize = flex_size(&b->fdata);

    if (oldsize - used < free) alloc = free - (oldsize - used);
  }
  else
  {
    oldsize = 0;
    alloc   = free;
  }

  /* Proceed if that amount is not zero */

  if (alloc > 0)
  {
    /* Granularise the amount */

    alloc = alloc / F_BLOCKSIZE;
    alloc = (alloc + 1) * F_BLOCKSIZE;

    /* Allocate a new block or extent the existing one */

    if (!oldsize)
    {
      if (!flex_alloc(&b->fdata, alloc)) ok = 0;
    }
    else
    {
      if (!flex_extend(&b->fdata, alloc + oldsize)) ok = 0;
    }

    /* Report if the claim fails */

    if (!ok) return make_no_cont_memory_error(2);

    #ifdef TRACE
      flexcount += alloc;
      if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
    #endif

    /* If the block was not allocated before, then this */
    /* function itself has just used 4 bytes of it - in */
    /* storing the used space counter. So set this up   */
    /* to hold the right amount.                        */

    if (!oldsize) *(int *) b->fdata = 4;
  }

  return NULL;
}

/*************************************************/
/* form_new_form()                               */
/*                                               */
/* Create a new form associated with the given   */
/* token.                                        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form and token;   */
/*                                               */
/*             The token's address.              */
/*************************************************/

_kernel_oserror * form_new_form(browser_data * b, HStream * token)
{
  _kernel_oserror * e;
  form_header     * p;

  /* Claim memory for the form_header structure */

  e = form_ensure_free(b, sizeof(form_header));
  if (e) return e;

  /* The first word of the fdata data block that holds  */
  /* forms information says how many bytes in the       */
  /* block are actually used - including that first     */
  /* word. So the form_header needs to be placed at     */
  /* an address past this used space.                   */
  /*                                                    */
  /* It is up to users of the block to increase the     */
  /* used size counter, so form_ensure_free will not    */
  /* have incremented this yet.                         */

  p = (form_header *) (((int) b->fdata) + *(int *) b->fdata);

  /* Increment the used space counter */

  *(int *) b->fdata += sizeof(form_header);

  /* Fill in the structure and increment the forms counter */

  p->token  = token;
  p->fields = 0;

  b->nforms++;

  return NULL;
}

/*************************************************/
/* form_discard()                                */
/*                                               */
/* Discards all the forms in a given view,       */
/* freeing associated memory.                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the view.             */
/*************************************************/

_kernel_oserror * form_discard(browser_data * b)
{
  form_cancel_edit(b);

  b->nforms = 0;

  if (b->fdata)
  {
    #ifdef TRACE
      flexcount -= flex_size(&b->fdata);
      if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
    #endif

    flex_free(&b->fdata);
    b->fdata = NULL;
  }

  return NULL;
}

/*************************************************/
/* form_find_record()                            */
/*                                               */
/* Locates the form record governing the given   */
/* token or return NULL.                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             The token address;                */
/*                                               */
/*             1 to locate the header record for */
/*             the form, else the field record   */
/*             will be located.                  */
/*                                               */
/* Returns:    Pointer to record, as a void *.   */
/*************************************************/

static void * form_find_record(browser_data * b, HStream * token, int header)
{
  form_header * hp;
  form_field  * fp;
  int           i, j, o, used;

  /* If there's no block, can't do anything */

  if (!b->fdata) return NULL;

  /* Set i to point past the first word, which holds the */
  /* used space counter (set 'used' to the value of that */
  /* counter, too).                                      */

  i    = 4;
  used = *(int *) b->fdata;

  /* Loop round as long as the offset counter i is inside */
  /* the amount of used data.                             */

  while (i < used)
  {
    /* All forms start with a form_header structure, so */
    /* set the header pointer hp to point to this.      */

    hp = (form_header *) (((int) b->fdata) + i);

    /* If the header's representing token matches the one */
    /* given to the function, then return 1) the pointer  */
    /* to it, if the header item was requested in the     */
    /* function's parameters, or 2) NULL, if the header   */
    /* item was not requested - if we've found the token  */
    /* in a header it can't possibly exist in a form      */
    /* field record too.                                  */

    if (hp->token == token) return (header ? hp : NULL);

    /* Set 'o' so that i + o gives an offset into the flex */
    /* block of the next form field record.                */

    o = sizeof(form_header);

    for (j = 0; j < hp->fields; j++)
    {
      /* As for the form header, find the form field record */

      fp = (form_field *) (((int) b->fdata) + i + o);

      /* And again, if the token matches that given to the */
      /* function, return the pointer to the header or the */
      /* pointer to the form record as indicated by the    */
      /* 'header' flag passed to the function.             */

      if (fp->header.token == token) return (header ? (void *) hp : (void *) fp);

      /* The 'size' field of the form field header in */
      /* the form field record holds the size of that */
      /* field in words; so increase the offset 'o'   */
      /* by this multiplied by 4 to get to the next   */
      /* form field.                                  */

      o += fp->header.size * 4;
    }

    /* Skip 'i' onto the next form header item */

    i += o;
  }

  return NULL;
}

/*************************************************/
/* form_validate_select()                        */
/*                                               */
/* Deals with clicks on selection lists (aka.    */
/* pop-up menus in forms) - either to select an  */
/* item for the first time in an unselected      */
/* group of menu items, or to ensure that the    */
/* selection which has been made is valid.       */
/* Consequently, if more than one item in the    */
/* array is selected and the field should only   */
/* allow one at a time, everything but the first */
/* selected item in the array will be            */
/* deselected.                                   */
/*                                               */
/* This function initiates appropriate redraws.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             The token address for the list;   */
/*                                               */
/*             The item number selected in the   */
/*             menu.                             */
/*************************************************/

static _kernel_oserror * form_validate_select(browser_data * b, HStream * tp, int deflt)
{
  int          changed;
  int          n;
  form_field * fp;
  char       * p;

  /* If the SELECT item can take multiple selections, there's */
  /* no work to do, so exit.                                  */

  if (HtmlSELECTmultiple(tp)) return NULL;

  /* Otherwise, find the form field record */

  fp = form_find_record(b, tp, 0);

  /* If not a SELECT field or the record couldn't be found, */
  /* exit.                                                  */

  if (!fp || fp->header.type != form_select) return NULL;

  changed = 0;

  /* Point to the 'selection' char array for the item */
  /* (see Forms.h).                                   */

  p = fp->value.select.selection;
  n = 0;

  /* Go through the array character by character, */
  /* representing items in the select list.       */

  while (*p)
  {
    /* n gets incremented when a selected item is */
    /* encountered. So the first time a selected  */
    /* item is hit, the following test fails.     */

    if (*p == FORM_SELCHAR && n)
    {
      /* However, the next time one is hit, this */
      /* will succeed; in that case, since we    */
      /* can only have one item selected at a    */
      /* time, deselect the item.                */

      changed = 1;
      *p      = FORM_UNSELCHAR;
    }

    /* (Mark the first occurrence of a selected item in 'n' */

    if (*p == FORM_SELCHAR) n++;

    p++;
  }

  /* If n is zero, no items were selected. In this case, can */
  /* simply select the item given in the function call and   */
  /* flag the change in 'changed'.                           */

  if (!n)
  {
    changed = 1;
    fp->value.select.selection[deflt] = FORM_SELCHAR;
  }

  /* If the item selected in the menu has now changed, */
  /* redraw the field to reflect the new selection.    */

  if (changed) browser_update_token(b, tp, 0, 0);

  return NULL;
}

/*************************************************/
/* form_validate_radio()                         */
/*                                               */
/* Validate radio icons including the indicated  */
/* token; i.e. ensure that only one is selected. */
/*                                               */
/* If more than one is selected, all but the     */
/* last are deselected (unless the passed token  */
/* is selected). If none are selected, that's    */
/* allowed (Netscape Navigator - hmph).          */
/*                                               */
/* This behaviour is important when loading      */
/* forms as it ensures that the correct default  */
/* selection is applied (if the fourth button is */
/* selected by default, when the first is added  */
/* it will be alone and deselected so it will    */
/* get selected  - this must be overridden when  */
/* the fourth button is added to the page). It   */
/* also ensures sensible behaviour when the user */
/* clicks on the selected button.                */
/*                                               */
/* This function initiates appropriate redraws.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             The token address for the radio.  */
/*************************************************/

static _kernel_oserror * form_validate_radio(browser_data * b, HStream * token)
{
  form_field  * fp;
  form_header * hp;
  HStream     * tp;
  const char  * name;
  int           selectthis;
  int           foundone;
  int           i;

  /* Find the record associated with the given token, */
  /* and exit if it can't be found or doesn't         */
  /* represent a radio group.                         */

  fp = form_find_record(b, token, 0);

  if (!fp || fp->header.type != form_radio) return NULL;

  /* Set 'selectthis' if the radio is selected, and set */
  /* 'name' to the radio's name.                        */

  selectthis = fp->value.checked;
  name       = HtmlINPUTname(token);

  /* Find the form header and first record of the form that */
  /* the given token lies in.                               */

  hp = form_find_record(b, token, 1);
  if (!hp) return NULL;

  fp = (form_field *) (((int) hp) + sizeof(form_header));

  /* Loop round all the fields in this group */

  foundone = 0;

  for (i = 0; i < hp->fields; i++)
  {
    /* Proceed if this field is a radio button */

    if (fp->header.type == form_radio)
    {
      tp = fp->header.token;

      /* See if the name of the token representing this radio */
      /* matches the name of the token given to the function  */

      if (name && HtmlINPUTname(tp) && !strcmp(name, HtmlINPUTname(tp)))
      {
        /* If so, it's in the same group of radio buttons. */

        if (
             fp->value.checked &&          /* So if the item is selected, */
             (
               foundone ||                 /* a selected one has been found before, or */
               (
                 selectthis &&             /* the given token was selected, */
                 fp->header.token != token /* and this field does not use the same token... */
               )
             )
           )
        {
          /* ...Then deselect this item and redraw it */

          fp->value.checked = 0;
          browser_update_token(b, fp->header.token, 0, 0);
        }

        /* Otherwise, this is the first item in the group that has been */
        /* found selected, so set foundone to flag this.                */

        if (fp->value.checked) foundone = 1;
      }
    }

    /* Point to the next form field record */

    fp = (form_field *) (((int) fp) + fp->header.size * 4);
  }

  /* Finished */

  return NULL;
}

/*************************************************/
/* form_set_field_space()                        */
/*                                               */
/* Set the space in bytes occupied by a field,   */
/* *not* including the header.                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to the token representing */
/*             the field;                        */
/*                                               */
/*             Space to set for the field, not   */
/*             including the header, in bytes.   */
/*************************************************/

static _kernel_oserror * form_set_field_space(browser_data * b, HStream * token, int space)
{
  _kernel_oserror * e;
  form_field      * fp;
  int               oldspace, offset;

  /* Find the form field record */

  fp = form_find_record(b, token, 0);
  if (!fp) return NULL;

  /* Get the offset of that record in the data block   */
  /* and determine the amount of space it already uses */

  offset   = ((int) fp) - ((int) b->fdata);
  oldspace = fp->header.size * 4;

  /* Add the size of the form header to the requested */
  /* form field space and word align it               */

  space += sizeof(form_field_header);
  space  = (int) WordAlign(space);

  /* If asking for more space, try to claim that memory */
  /* (if asking for less, leave the data block at its   */
  /* old size).                                         */

  if (space > oldspace)
  {
    e = form_ensure_free(b, space - oldspace);
    if (e) return e;
  }

  /* If the amount of space required is different from */
  /* the space already used, move data around in the   */
  /* data block as appropriate                         */

  if (space != oldspace)
  {
    /* Shuffle data above the field up or down so that the  */
    /* field now has the new amount of space (be it more or */
    /* less than before) free.                              */

    memmove((char *) (((int) b->fdata) + offset + space),
            (char *) (((int) b->fdata) + offset + oldspace),
            *((int *) b->fdata) - offset - oldspace);

    /* Update the data block's used space counter */

    *(int *) b->fdata += space - oldspace;

    /* Refind the field record (it may have moved) */

    fp = form_find_record(b, token, 0);
    if (!fp) return NULL;

    /* Update the field record's used words counter */

    fp->header.size = space / 4;
  }

  return NULL;
}

/*************************************************/
/* form_put_field()                              */
/*                                               */
/* Sets the value in a field.                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to the token representing */
/*             this field;                       */
/*                                               */
/*             The value, as a char * - this is  */
/*             as for form_new_field below,      */
/*             except for SELECT fields, where   */
/*             it is treated as a pointer to a   */
/*             zero terminated list of bytes     */
/*             where FORM_UNSELCHAR indicates    */
/*             the item is not selected, and     */
/*             FORM_SELCHAR indicates it is      */
/*             selected;                         */
/*                                               */
/*             1 to update (redraw) the token,   */
/*             else 0.                           */
/*************************************************/

_kernel_oserror * form_put_field(browser_data * b, HStream * token, char * value, int update)
{
  _kernel_oserror * e;
  form_field      * fp;
  int               changed = 0;

  /* Find the field record */

  fp = form_find_record(b, token, 0);

  /* Only proceed if it can be found */

  if (fp)
  {
    switch (fp->header.type)
    {
      /* Writable items - text areas, normal single-line writables, */
      /* and single-line password writables.                        */

      case form_textarea: /* same as form_password: no break */
      case form_text    : /* same as form_password: no break */
      case form_password:
      {
        /* If a value for the field (i.e. a string for the writable field)  */
        /* has been given and it is different from the current field value, */
        /* ensure there is space for the new text, copy it in, and flag     */
        /* that there has been a change to the item with 'changed'.         */

        if (value && fp->value.text && strcmp(value, fp->value.text))
        {
          e = form_set_field_space(b, token, strlen(value) + 1);
          if (e) return e;

          fp = form_find_record(b, token, 0);
          if (!fp) return NULL;

          strcpy(fp->value.text, value);

          changed = 1;
        }
      }
      break;

      /* Checkbox (also known as an option button) */

      case form_checkbox:
      {
        /* If the 'value' parameter passed to the function is NULL, the */
        /* item will be deselected, else it will be selected.           */

        changed           = ((!value) != (!fp->value.checked));
        fp->value.checked = !!value;
      }
      break;

      /* Radio buttons */

      case form_radio:
      {
        /* Set the item's selected state as for option boxes above */

        changed           = ((!value) != (!fp->value.checked));
        fp->value.checked = !!value;

        /* For radios, need to make sure only one is selected */

        form_validate_radio(b, token);
      }
      break;

      /* Selection lists (pop-up menus) */

      case form_select:
      {
        char * o;

        /* If value is NULL, change it to be an empty string instead - */
        /* avoids having to keep checking for (value == NULL) later.   */

        value = value ? value : fp->value.select.selection;

        /* Try to find the first selected item in the field; */
        /* if not found, point to the first item.            */

        o = strchr(value, FORM_SELCHAR);
        if (!o) o = value;

        /* If the value passed to the function differs from that */
        /* already set for the field, change to the new value    */
        /* (which must be the same size - the number of items in */
        /* the field can't change) and flag the alteration in    */
        /* 'changed'.                                            */

        if (strcmp(value, fp->value.select.selection))
        {
          strcpy(fp->value.select.selection, value);
          changed = 1;
        }

        /* Ensure that this leaves a valid selection in the list */
        /* (some SELECT field types only allow one item to be    */
        /* selected at any one time).                            */

        form_validate_select(b, token, o - value);
      }
      break;
    }
  }

  /* Only update the token if it's changed, even if the   */
  /* 'update' parameter says the token should be redrawn. */
  /* This minimises flicker.                              */

  if (changed && update) browser_update_token(b, token, 0, 0);

  return NULL;
}

/*************************************************/
/* form_build_selection()                        */
/*                                               */
/* Takes the value of a SELECT field - a list of */
/* items on a menu - which will contain a        */
/* series of FORM_SELCHAR and FORM_UNSELCHAR     */
/* characters, and fills in a given char array   */
/* based on this value.                          */
/*                                               */
/* The first 4 bytes of the value field hold an  */
/* int describing the number of entries in the   */
/* field. 8 bytes in are the SEL or UNSEL chars, */
/* followed immediately by two strings           */
/* describing the menu entry (menu text and the  */
/* field name for form submisson).               */
/*                                               */
/* Parameters: Pointer to the value string;      */
/*                                               */
/*             Pointer to a char array big       */
/*             enough to hold the selection      */
/*             array information (e.g. make it   */
/*             Limits_SelectItems + 1 in size to */
/*             hold selections plus terminator). */
/*************************************************/

static void form_build_selection(const int * value, char * selection)
{
  int    i, n;
  char * p;

  /* Find the number of entries and point to the first in 'p' */

  p  = (char *) (value + 2);
  n  = value[0];

  /* Can't go past the internal limit (see top of this file) */

  if (n > Limits_SelectItems) n = Limits_SelectItems;

  /* Loop through the entries */

  for (i = 0; i < n; i++)
  {
    /* FORM_SELECTED checks bit 0 of the character pointed */
    /* to by p, returning 1 if it is set.                  */

    selection[i] = FORM_SELECTED(p) ? FORM_SELCHAR : FORM_UNSELCHAR;

    /* Move past the selection character */

    p ++;

    /* Move past the menu entry text */

    p += strlen(p) + 1;

    /* Move past the menu entry name */

    p += strlen(p) + 1;
  }

  /* Terminate the list */

  selection[i] = 0;
}

/*************************************************/
/* form_new_field()                              */
/*                                               */
/* Add a new field to the last form in the list  */
/* (always the right one due to the sequential   */
/* nature of HTML forms). The value is           */
/* initialised as passed in; for radios and      */
/* checkboxes this is taken to be NULL = not     */
/* selected, otherwise selected. For SELECT      */
/* fields, this is taken as a pointer to the     */
/* value part of the token; this will be         */
/* converted to a zero terminated list of bytes  */
/* where FORM_UNSELCHAR = not selected,          */
/* FORM_SELCHAR = selected.                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to a token describing the */
/*             new item;                         */
/*                                               */
/*             The field type (see the           */
/*             form_fieldtype definition in      */
/*             Forms.h);                         */
/*                                               */
/*             Value for the field (see          */
/*             comments above).                  */
/*************************************************/

_kernel_oserror * form_new_field(browser_data * b, HStream * token, form_fieldtype type, char * value)
{
  _kernel_oserror * e;
  int               space, c;
  form_field      * p;
  form_header     * head;
  char              select[Limits_SelectItems + 1];

  /* Did we hit a forms element with no FORM tag ever appearing */
  /* before it in the document?                                 */

  if (!b->fdata) RetError(form_new_form(b, NULL));

  /* Need space for the form header to start with */

  space = sizeof(form_field_header);

  /* Work out the space needed for the given form field type */

  switch (type)
  {
    case form_textarea: value = value ? value : ""; space += strlen(value) + 1; break;
    case form_text:     value = value ? value : ""; space += strlen(value) + 1; break;
    case form_password: value = value ? value : ""; space += strlen(value) + 1; break;

    case form_checkbox: space += 4; break;
    case form_radio:    space += 4; break;

    case form_select:
    {
      form_build_selection((int *) value, select);

      value  = select;
      space += strlen(value) + 1 + sizeof(fv_select) - 4;
    }
    break;

    /* Other types have no data storage and the value part of the */
    /* structure is overwritten by the next field entry.          */
  }

  /* Word align the required space and allocate it */

  space = (int) WordAlign(space);

  e = form_ensure_free(b, space);
  if (e) return e;

  /* *(int *) b->fdata holds the amount of used space in the data */
  /* block, so the following sets p to the first unused bit of    */
  /* space in that block.                                         */

  p = (form_field *) (((int) b->fdata) + *(int *) b->fdata);

  /* Update the used space counter */

  *(int *) b->fdata += space;

  /* Fill int the structure */

  p->header.type  = type;
  p->header.token = token;
  p->header.size  = space / 4; /* (Size in words) */

  /* Fill in the value */

  switch(type)
  {
    case form_textarea: /* same as password: no break */
    case form_text:     /* same as password: no break */

    case form_password: strcpy(p->value.text, value);
    break;

    case form_checkbox: /* same as radio: no break */

    case form_radio:    p->value.checked = !!value;
    break;

    case form_select:
    {
      p->value.select.scroll = 0;
      strcpy(p->value.select.selection, value);
    }
    break;

    /* Other types have no data storage and the value part of the */
    /* structure is overwritten by the next field entry.          */
  }

  head = (form_header *) (((int) b->fdata) + 4);
  c    = b->nforms;

  /* Find the header record for this form. */

  while ((--c) > 0)
  {
    int           n = head->fields;
    form_field * fp = (form_field *) (((int) head) + sizeof(form_header));

    while ((n--) > 0) fp = (form_field *) (((int) fp) + fp->header.size * 4);

    head = (form_header *) fp;
  }

  /* Increment the field counter for the form */

  head->fields++;

  /* For radio groups or selection lists, check that a */
  /* valid arrangement of selections has been made     */

  if (type == form_radio)  form_validate_radio (b, token);
  if (type == form_select) form_validate_select(b, token, 0);

  /* Start an image fetch for TYPE=IMAGE items */

  if (type == form_image)
  {
    e = image_new_image(b, HtmlINPUTsrc(token), token, 0);
  }

  /* Finished */

  return NULL;
}

/*************************************************/
/* form_get_field()                              */
/*                                               */
/* Returns the field value for a given forms     */
/* item based on a given token.                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the item for which   */
/*             the field value is required.      */
/*                                               */
/* Returns:    The field value (see Forms.h)     */
/*************************************************/

form_field_value * form_get_field(browser_data * b, HStream * token)
{
  form_field * fp;

  fp = form_find_record(b, token, 0);

  return (fp ? &fp->value : NULL);
}

/*************************************************/
/* form_select_text()                            */
/*                                               */
/* Returns a pointer to a string to use for      */
/* the display component of a SELECT field based */
/* on the what is selected within it.            */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the SELECT field;    */
/*                                               */
/*             An array of chars describing the  */
/*             selection, e.g. as filled in by   */
/*             form_build_selection.             */
/*                                               */
/* Returns:    Pointer to the text to use. This  */
/*             may be a text item within the     */
/*             forms item itself or a general    */
/*             response inside the Messages file */
/*             lookup buffer - so if the caller  */
/*             wants to use lookup_token or any  */
/*             function which calls it but must  */
/*             retain the text across the call,  */
/*             that caller must copy the text to */
/*             a private buffer first.           */
/*************************************************/

static char * form_select_text(browser_data * b, HStream * tp, char * selection)
{
  int          i, n, s;
  const int  * pi;
  const char * p;
  char       * f;

  s  = 0;

  /* Find the number of entries in the list and skip to the first */
  /* one in 'p' and 'f'.                                          */

  pi = HtmlSELECToptions(tp);
  n  = pi[0];
  p  = (const char *) (pi + 2);
  f  = (char *) p;

  if (n > Limits_SelectItems) n = Limits_SelectItems;

  /* Loop through the entries */

  for (i = 0; i < n; i++)
  {
    /* If an item is selected, remember the pointer to it in 'f' */
    /* and count how many times this has happened in 's'.        */

    if (selection[i] == FORM_SELCHAR)
    {
      s++;

      f = (char *) p;
    }

    /* Move past the selected/unselected indicator char */

    p++;

    /* Move past the menu entry text */

    p += strlen(p) + 1;

    /* Move past the menu entry name */

    p += strlen(p) + 1;
  }

  /* If there were no selected items, return an appropriate message */

  if (!s)    return lookup_token("selNONE:<None>",0,0);

  /* If there were many selected items, return an appropriate message */

  if (s > 1) return lookup_token("selMANY:<Many>",0,0);

  /* Otherwise, return the entry text ('+ 1' gets past the */
  /* selected/unselected indicator char for the entry).    */

  return f + 1;
}

/*************************************************/
/* form_get_field_text()                         */
/*                                               */
/* Returns the text associated with a given      */
/* forms item (identified by an HStream struct), */
/* taking care of the different types of field.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the field.           */
/*                                               */
/* Returns:    Pointer to the associated text.   */
/*************************************************/

char * form_get_field_text(browser_data * b, HStream * token)
{
  form_field * fp;

  /* Try to find the record, exit if this fails */

  fp = form_find_record(b, token, 0);
  if (!fp) return NULL;

  switch (fp->header.type)
  {
    case form_text:     /* Same as form_password */
    case form_textarea: /* Same as form_password */
    case form_password:
    {
      /* For these items, just a simple text string */

      return fp->value.text;
    }
    break;

    case form_checkbox:
    case form_radio:
    {
      /* Radios and checkboxes have no associated text */

      return NULL;
    }
    break;

    case form_select:
    {
      /* Select items need to return either the contents of a */
      /* selected menu item, or appropriate messages for many */
      /* or no selections.                                    */

      return form_select_text(b,
                              token,
                              fp->value.select.selection);
    }
    break;
  }

  return NULL;
}

/*************************************************/
/* form_reset_form()                             */
/*                                               */
/* Returns a form to the state it was in when    */
/* fetched, discarding any changes within it.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to an HStream struct that */
/*             identifies the form to reset.     */
/*************************************************/

static _kernel_oserror * form_reset_form(browser_data * b, HStream * token)
{
  _kernel_oserror * e;
  form_header     * hp;

  /* Find the header record for the form */

  hp = form_find_record(b, token, 1);

  if (hp)
  {
    form_field * fp;
    int          i, j;
    char       * value;
    HStream    * tp;
    char         select[Limits_SelectItems + 1];

    /* Loop through all the form's fields */

    for (i = 0; i < hp->fields; i++)
    {
      /* Find the header again in case it has moved */

      hp = form_find_record(b, token, 1);

      /* Find the form field record - first, get the */
      /* first item based on the form_header pointer */

      fp = (form_field *) (((int) hp) + sizeof(form_header));

      /* Now skip along to the 'i'th entry, skipping along */
      /* by however many words each entry says it uses.    */

      for (j = 0; j < i; j++) fp = (form_field *) (((int) fp) + fp->header.size * 4);

      /* Get the associated HStream structure - all the */
      /* field values to write will come from this,     */
      /* thus restoring the initial form state.         */

      tp    = fp->header.token;
      value = NULL;

      switch(fp->header.type)
      {
        case form_text:     /* Same as form_password */
        case form_textarea: /* Same as form_password */
        case form_password:
        {
          /* Value is a simple text string for these types */

          value = tp->text ? tp->text : "";
        }
        break;

        case form_checkbox:
        case form_radio:
        {
          /* Value is NULL for unselected, non-NULL for selected */

          value = (char *) HtmlINPUTchecked(tp);
        }
        break;

        case form_select:
        {
          /* Value is the array of FORM_SELCHAR and FORM_UNSELCHAR characters */

          form_build_selection(HtmlSELECToptions(tp), select);
          value = select;
        }
        break;
      }

      /* Write the value for the field */

      e = form_put_field(b, tp, value, 1);
      if (e) return e;
    }
  }

  return NULL;
}

/*************************************************/
/* form_get_linesize()                           */
/*                                               */
/* On the basis of a font bounding box, return a */
/* baseline offset and line height for a text    */
/* area.                                         */
/*                                               */
/* Parameters: Pointer to a BBox holding the     */
/*             minimum bounding box needed to    */
/*             enclose any char in the font;     */
/*                                               */
/*             Pointer to an int, into which the */
/*             line's height is returned;        */
/*                                               */
/*             Pointer to an int, into which a   */
/*             font baseline offset is returned. */
/*                                               */
/* Assumes:    That the pointers are not NULL.   */
/*************************************************/

void form_get_linesize(BBox * fontbox, int * lh, int * lb)
{
  /* lb = font baseline offset from line bottom.    */

  *lb = 4 - fontbox->ymin;

  if (*lb < 0)   *lb  = 0;
  if ((*lb) & 3) *lb += 4 - ((*lb) & 3); /* (Rounding is done to ensure consistent spacing) */

  /* lh = line height */

  *lh = *lb + fontbox->ymax;

  if ((*lh) & 3) *lh += 4 - ((*lh) & 3);
}

/*************************************************/
/* form_textarea_find_caret()                    */
/*                                               */
/* Locates the caret index in the string from    */
/* the x position (in OS units) and y position   */
/* (in lines) relative to the top left of a      */
/* text area.                                    */
/*                                               */
/* Parameters: Pointer to the string inside the  */
/*             text area;                        */
/*                                               */
/*             Font handle used by the area;     */
/*                                               */
/*             Pointer to an int, into which the */
/*             index into the string is written; */
/*                                               */
/*             X offset of caret from top left,  */
/*             in OS units;                      */
/*                                               */
/*             Y offset of caret from top left,  */
/*             in lines.                         */
/*                                               */
/* Assumes:    That the int pointer is not NULL. */
/*************************************************/

static void form_textarea_find_caret(const char * p, int fh, int * index, int x, int y)
{
  int    i, l, w;
  char   c;
  char * t;
  char   passcode[] = FE_PassCode;

  /* Locate the start of the line indicated by 'y' */

  i = 0;

  while (y > 0 && p[i])
  {
    if (p[i] == '\n') y--;
    i++;
  }

  p += i;

  /* Locate the end of the line */

  t = strchr(p, '\n');
  if (!t) t = strchr(p, 0);

  /* Force a terminator there, if there wasn't one already */

   c = *t;
  *t = 0;

  /* Get the index in this string from the font library */

  fm_get_string_width(fh,
                      fe_password ? passcode : p,
                      x * 400 + 6 * 400,
                      strlen(p),
                      -1,
                      &l,
                      &w);

  /* Restore the character overwritten by the terminator */

  *t = c;

  /* Write the caret index to the int given by 'index' */

  *index = l + i;

  return;
}

/*************************************************/
/* form_textarea_caretpos()                      */
/*                                               */
/* Returns a horizontal offset in OS units and   */
/* (for text areas as opposed to single line     */
/* writables) a vertical offset in lines, at     */
/* which the caret should be placed to be in a   */
/* given index into a given string.              */
/*                                               */
/* Parameters: Pointer to the string;            */
/*                                               */
/*             The RISC OS Font Manager font     */
/*             handle for the font that the text */
/*             is to be rendered in;             */
/*                                               */
/*             Index into the string;            */
/*                                               */
/*             Pointer to an int, into which     */
/*             an offset from the left hand side */
/*             of the text field, in OS units,   */
/*             is placed;                        */
/*                                               */
/*             Pointer to an int, in which an    */
/*             offset in lines from the top of   */
/*             the text field is placed.         */
/*************************************************/

static void form_textarea_caretpos(const char * p, int fh, int index, int * x, int * y)
{
  int          ox, oy, i, li;
  const char * t;
  const char * l;
  char         passcode[] = FE_PassCode;

  i = li = oy = ox = 0;

  l = t = p;

  /* Whilst within the given index and still inside the given */
  /* string, search for new line characters. Increment the    */
  /* counter for the number of lines from the top for each.   */
  /* Other counters are updated so that the horizontal code   */
  /* below only works on the fragment of string that lies on  */
  /* the last line found here.                                */

  while (i < index && *t)
  {
    if (*t == '\n')
    {
      li = i + 1;
      l  = t + 1;

      oy++;
    }

    t++;
    i++;
  }

  /* For the x offset, work out the width of the string between */
  /* the start of the last line found above and the given index */
  /* position into the string.                                  */

  fm_get_string_width(fh,
                      fe_password ? passcode : l,
                      0x1000000,
                      index - li,
                      -1,
                      &i,
                      &ox);

  /* Convert to OS units */

  convert_to_os(ox, &ox);

  /* Return the values */

  if (x) *x = ox;
  if (y) *y = oy;
}

/*************************************************/
/* form_input_box()                              */
/*                                               */
/* Returns information about a given input box   */
/* (writable), if found.                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the field;           */
/*                                               */
/*             Pointer to a BBox which will be   */
/*             filled with the bounding box of   */
/*             the field;                        */
/*                                               */
/*             Pointer to an int, into which the */
/*             line height for the field is      */
/*             written;                          */
/*                                               */
/*             Pointer to an int, into which the */
/*             baseline offset for the field is  */
/*             written;                          */
/*                                               */
/*             Pointer to an int, into which the */
/*             handle of the font used by the    */
/*             field is written.                 */
/*                                               */
/* Returns:    1 if the field was found, else 0. */
/*                                               */
/* Assumes:    None of the pointers may be NULL. */
/*************************************************/

static int form_input_box(browser_data * b, HStream * tp, BBox * box, int * lh, int * lb, int * fhand)
{
  int             l, c, x, y, fh, t = 0;
  int             orx,ory;
  int             depth;
  token_path    * path;
  reformat_cell * cell;

  /* Find the token */

  depth = tokenutils_line_range(b, tp, &l, &c, NULL, NULL, &path);
  if (l < 0) return 0;

  /* Find out the x and y offset of the cell the */
  /* token lies in.                              */

  tokenutils_token_offset(b, path, &orx, &ory);

  /* If there are valid entries in the token_path structure, */
  /* need to find out what line list browser_update_token_r  */
  /* should be called on. Otherwise, it's the main line      */
  /* list.                                                   */

  cell = tokenutils_find_cell(b->cell, depth, path);

  if (path) free (path);

  if (!cell) cell = b->cell, orx = 0, ory = 0;

  /* Get the position of this token and the font it uses */

  y  = ory + cell->ldata[l].y + cell->ldata[l].b;
  x  = orx + redraw_token_x(b, cell, tp, l);
  fh = fm_find_token_font(b, tp, 0);

  /* Return the font handle */

  *fhand = fh;

  /* Get the font bounding box and get line size details */

  fm_font_box(fh, box);
  form_get_linesize(box, lh, lb);

  /* For a text area, need to adjust the y coordinates of */
  /* the bounding box to account for the number of lines  */
  /* the area has.                                        */

  if (tp->tagno == TAG_TEXTAREA)
  {
    int r;

    r = tp->rows;
    if (r < 2) r = 2;

    box->ymin -= *lh * (r - 1);
  }

  /* Account for the border */

  box->ymin = box->ymin + y - 8;
  box->ymax = box->ymax + y + 8;

  /* Work out the x ranges */

  box->xmin = x + 4;

  convert_pair_to_os(cell->cdata[c].w, t, &x, &t);

  box->xmax = box->xmin + x - 4 - 4;

  return 1;
}

/*************************************************/
/* form_abandon_menu()                           */
/*                                               */
/* Closes a form menu, so that there is no       */
/* currently editing form any more.              */
/*************************************************/

void form_abandon_menu(void)
{
  /* Only proceed if there's a menu to abandon */

  if (fe_menu)
  {
    /* Close the menu, free memory associated with it, */
    /* and clear the status information associated     */
    /* with it.                                        */

    wimp_create_menu((wimp_menustr *) - 1, 0, 0);

    free (fe_menu);

    fe_menu     = NULL;
    fe_mbrowser = NULL;
    fe_mtoken   = NULL;

    menusrc = Menu_None;
  }
}

/*************************************************/
/* form_end_edit()                               */
/*                                               */
/* Finishes editing a form, optionally           */
/* discarding changes and moving the input focus */
/* to a general position in the browser.         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             1 to keep changes, else discard;  */
/*                                               */
/*             1 to move the input focus, else   */
/*             leave it alone.                   */
/*                                               */
/* Assumes:    If not moving the input focus,    */
/*             the browser_data pointer may be   */
/*             NULL.                             */
/*************************************************/

_kernel_oserror * form_end_edit(browser_data * b, int keepchanges, int movefocus)
{
  _kernel_oserror * e = NULL;

  /* Proceed if something is being edited */

  if (
       (
         !b              ||
         b == fe_browser
       )
       && fe_browser
       && fe_token
     )
  {
    int u;

    /* u is set if scroll positions were non-zero for the field */

    u = fe_xscroll || fe_yscroll;

    fe_xscroll = fe_yscroll = fe_index = 0;

    /* Redraw the field if it was scrolled somewhere */

    if (u)
    {
      e = browser_update_token(fe_browser, fe_token, 1, 0);
      if (e) return e;
    }

    /* Discard the currently editing form details, so nothing */
    /* is being edited anymore.                               */

    fe_browser = NULL;
    fe_token   = NULL;

    /* Possibly move the focus back to a general position */

    if (movefocus) e = browser_give_general_focus(b);
  }

  /* Close any open menus and return */

  form_abandon_menu();

  return e;
}

/*************************************************/
/* form_create_menu()                            */
/*                                               */
/* Builds and opens a menu for a SELECT field.   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the field.           */
/*************************************************/

static _kernel_oserror * form_create_menu(browser_data * b, HStream * tp)
{
  form_field              * fp;
  char                    * p, * o;
  const int               * pi;
  int                       w, i, n, size;
  WimpGetPointerInfoBlock   m;
  wimp_menuhdr            * mhp;
  wimp_menuitem           * mip;

  /* If there's an existing menu open and it isn't the one */
  /* we want to create, ensure it is closed first.         */

  if (b != fe_mbrowser || tp != fe_mtoken) form_abandon_menu();

  /* Find the form field record */

  fp = form_find_record(b, tp, 0);

  /* If the token doesn't represent a SELECT field, exit */

  if (!fp || fp->header.type != form_select) return NULL;

  /* Otherwise, point p to the value of the given token, */
  /* and thus get the number of items in 'n'.            */

  size = sizeof(wimp_menuhdr);
  pi   = HtmlSELECToptions(tp);
  n    = pi[0];

  /* Can't create a field if there are no items! */

  if (n < 1)
  {
    bbc_vdu(7);
    return NULL;
  }

  /* Limit check the number of items */

  if (n > Limits_SelectItems) n = Limits_SelectItems;

  /* Skip 'p' to the first item's selected/deselected char */

  p = (char *)&pi[2];

  /* Keep a running count of the width used so far in 'w'      */
  /* (for earlier OS versions which need this to be specified) */

  w = 8; /* Sensible starting width */

  /* Loop round the items, working out the data size they */
  /* will occupy in the menu.                             */

  for (i = 0; i < n; i++)
  {
    /* Skip past the selected/deselected char */

    p++;

    /* String length of the menu text, plus terminator, */
    /* plus the menu item structure                     */

    size += strlen(p) + 1 + sizeof(wimp_menuitem);

    /* If this item is wider than the highest recorded so */
    /* far, set 'w' to the width.                         */

    if (strlen(p) > w) w = strlen(p);

    /* Skip past the menu item text */

    p += strlen(p) + 1;

    /* Skip past the menu item name */

    p += strlen(p) + 1;
  }

  /* If fe_menu is already allocated, then the same menu has */
  /* already been opened - so don't need to change the block */
  /* size. Otherwise, try to claim memory for the menu.      */

  if (!fe_menu) fe_menu = malloc(size + 4);
  if (!fe_menu) return make_no_cont_memory_error(3);

  /* Zero the contents */

  memset(fe_menu, 0, size + 4);

  /* Set mhp to point to the menu header, and mip */
  /* to point to the first menu item.             */

  mhp = (wimp_menuhdr  *) fe_menu;
  mip = (wimp_menuitem *) (((int) mhp) + sizeof(wimp_menuhdr));

  /* Use the item name for a menu title */

  strncpy(mhp->title, HtmlSELECTname(tp), 12);

  /* If the title is now a NULL string or is a full */
  /* 12 chars long excluding terminator, replace it */
  /* with a standard title from the Messages file.  */

  if (!mhp->title[0] || mhp->title[11]) strcpy(mhp->title, lookup_token("selTITL:Select",0,0));

  /* Fill in the other header details */

  mhp->tit_fcol  = 7;
  mhp->tit_bcol  = 2;
  mhp->work_fcol = 7;
  mhp->work_bcol = 0;
  mhp->width     = w * 16;  // !! Need to read the font size properly, here.
  mhp->height    = 44;
  mhp->gap       = 0;

  /* Point to the first item's selected/deselected char */

  p = (char *)(HtmlSELECToptions(tp) + 2);

  /* Point past the nth menu item structure (note the pointer */
  /* arithmetic...) - i.e. the limit of the menu item         */
  /* structures. This space is used for indirected item text. */

  o = (char *) (mip + n);

  /* Loop through all menu items */

  for (i = 0; i < n; i++)
  {
    /* Set the flags for the last menu item and give a */
    /* tick if the item is selected.                   */

    mip->flags = ((i == n - 1) ? wimp_MLAST : 0) |
                 ((fp->value.select.selection[i] == FORM_SELCHAR) ? wimp_MTICK : 0);

    /* There is no submenu */

    mip->submenu   = (wimp_menuptr) -1;

    /* Set the entry's icon flags */

    mip->iconflags = wimp_ITEXT              | /* A plain text item    */
                     wimp_IFILLED            | /* Background is filled */
                     wimp_INDIRECT           | /* Item is indirected   */
                     (wimp_BSELNOTIFY << 12) | /* 'Select' button type */
                     (7<<24);                  /* FG colour 7 (black)  */

    /* Buffer for the indirected text */

    mip->data.indirecttext.buffer      = o;

    /* No validation string */

    mip->data.indirecttext.validstring = NULL;
    mip->data.indirecttext.bufflen     = 0;

    /* Skip past the selected/deselected char */

    p++;

    /* Copy the item text into the buffer */

    strcpy(o, p);

    /* Skip past the menu item text */

    p += strlen(p) + 1;

    /* Skip past the menu item name */

    p += strlen(p) + 1;

    /* Increment the pointer to the indirected text buffer */
    /* past the menu item text just written in             */

    o += strlen(o) + 1;

// Hmph. Nice idea, but trouble is if you then select
// an item in the menu below a separator it'll be the
// wrong one - item 3 in the menu is item 4 in the list
// of menu options if there's a separator at item 2
// of that list, say.
//
// Get round to sorting this out eventually though as
// a load of hyphens in an anti-aliased proportional font
// do *not* look good as a separator!
//
//    /* If not on the last item, look ahead to see if a separator is present */
//
//    if (i != n - 1)
//    {
//      char * check = p;
//
//      check++;
//
//      /* If the string starts with a separator, assume it is meant to be */
//      /* an item separator... Add the flag to the current item to put a  */
//      /* real separator under it, skip p past this entry, and increment  */
//      /* the item counter to skip the separator entry. If the current    */
//      /* entry has now become the last one, set the 'last' flag.         */
//
//      if (!strncmp(check, Forms_Menu_Separator, Forms_Menu_Separator_Len))
//      {
//        p++;
//        p += strlen(p) + 1;
//        p += strlen(p) + 1;
//
//        mip->flags |= wimp_MSEPARATE;
//
//        i++;
//        if (i == n - 1) mip->flags |= wimp_MLAST;
//      }
//    }

    /* Advance to the next item (note pointer arithmetic) */

    mip++;
  }

  /* Find the position of the pointer, and record that a */
  /* menu from a form is opened (or about to be!) in the */
  /* menusrc global.                                     */

  wimp_get_pointer_info(&m);
  menusrc = Menu_Form;

  /* Work out where to open the menu */

  {
    int                       depth, line, chunk;
    int                       width, height;
    int                       x, y, orx, ory, x_add;
    BBox                      box;
    fm_face                   fh;
    token_path              * path  = NULL;
    reformat_cell           * cell;
    WimpGetWindowStateBlock   s;
    _kernel_oserror         * e;

    /* Find the line the token is in */

    depth = tokenutils_line_range(b, tp, &line, &chunk, NULL, NULL, &path);

    /* Find out the x and y offset of the cell the */
    /* token lies in.                              */

    tokenutils_token_offset(b, path, &orx, &ory);

    /* If there are valid entries in the token_path structure, */
    /* need to find out what line list to use.                 */

    cell = tokenutils_find_cell(b->cell, depth, path);

    if (path) free (path);

    if (!cell) cell = b->cell, orx = 0, ory = 0;

    /* Find the field's x and y offsets */

    x = orx + redraw_chunk_x(b, cell, chunk, line);
    y = ory + cell->ldata[line].y;

    convert_to_os(cell->cdata[chunk].w, &x_add);
    x += x_add;

    /* Get the window state to convert x and y to screen coords */

    s.window_handle = b->window_handle;
    e = wimp_get_window_state(&s);
    if (e) return e;

    x = coords_x_toscreen(x, (WimpRedrawWindowBlock *) &s);
    y = coords_y_toscreen(y, (WimpRedrawWindowBlock *) &s);

    /* Find out the size of the menu icon */

    read_sprite_size("fgright", &width, &height);

    /* Find out the height of the display region */

    fh = fm_find_token_font(b, tp, 0);
    fm_font_box(fh, &box);

    /* Adjust y appropriately */

    y += cell->ldata[line].b + (box.ymin + height + box.ymax) / 2;

    return wimp_create_menu(fe_menu, x - 4, y);
  }
}

/*************************************************/
/* form_start_select_edit()                      */
/*                                               */
/* Sets up a form SELECT field as the currently  */
/* editing item within that field, creating and  */
/* opening the associated menu.                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the SELECT field.    */
/*************************************************/

static _kernel_oserror * form_start_select_edit(browser_data * b, HStream * token)
{
  _kernel_oserror * e = form_create_menu(b, token);

  if (e) return e;

  fe_mbrowser = b;
  fe_mtoken   = token;

  return NULL;
}

/*************************************************/
/* form_autoscroll()                             */
/*                                               */
/* Scrolls a given browser window to ensure that */
/* the item the caret is in is fully visible.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form.             */
/*************************************************/

static _kernel_oserror * form_autoscroll(browser_data * b)
{
  WimpGetCaretPositionBlock c;

  wimp_get_caret_position(&c);

  /* Only proceed if the caret is inside the window, and visible */

  if (c.window_handle == b->window_handle && c.xoffset >= 0 && c.height > 0)
  {
    WimpGetWindowStateBlock   state;
    _kernel_oserror         * e;
    BBox                      box;
    int                       sc;
    int                       width, height, margin;
    int                       htop, hbot;

    state.window_handle = b->window_handle;
    e = wimp_get_window_state(&state);
    if (e) return e;

    /* Convert the visible area to window coordinates */

    box = state.visible_area;

    coords_box_toworkarea(&box, (WimpRedrawWindowBlock *) &state);

    /* Correct for the toolbars */

    if (!controls.swap_bars)
    {
      htop = toolbars_button_height(b) + toolbars_url_height(b);
      hbot = toolbars_status_height(b);
    }
    else
    {
      htop = toolbars_status_height(b);
      hbot = toolbars_button_height(b) + toolbars_url_height(b);
    }

    if (htop) htop += wimpt_dy();
    if (hbot) hbot += wimpt_dy();

    box.ymax -= htop;
    box.ymin += hbot;

    /* Find the visible area size */

    width  = box.xmax - box.xmin;
    height = box.ymax - box.ymin;

    /* Flag that scrolling isn't needed to start with */

    sc = 0;

    /* Vertical issues first */

    margin = FORM_AUTOSCROLL_MARGIN;
    if (height - c.height < margin * 2) margin = height / 4;

    /* The caret is scrolled off the top of the window */

    if (c.yoffset > box.ymax - c.height - margin)
    {
      state.yscroll += (c.yoffset - box.ymax + c.height + margin);
      sc = 1;
    }

    /* The caret is scrolled off the left of the window */

    if (c.yoffset < box.ymin + margin)
    {
      state.yscroll += (c.yoffset - box.ymin - margin);
      sc = 1;
    }

    /* Now the horizontal */

    margin = FORM_AUTOSCROLL_MARGIN;
    if (width < margin * 2) margin = width / 4;

    /* The caret is scrolled off the right of the window */

    if (c.xoffset > box.xmax - margin)
    {
      state.xscroll += (c.xoffset - box.xmax + margin);
      sc = 1;
    }

    /* The caret is scrolled off the left of the window */

    if (c.xoffset < box.xmin + margin)
    {
      state.xscroll += (c.xoffset - box.xmin - margin);
      sc = 1;
    }

    /* If the scroll position changed in the above code, reflect that new position */

    if (sc) wimp_open_window((WimpOpenWindowBlock *) &state);
  }

  return NULL;
}

/*************************************************/
/* form_start_textarea_edit()                    */
/*                                               */
/* Starts an edit in a given text area (either   */
/* a text area object or a single line writable) */
/* placing the caret in a specified position.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to a token representing   */
/*             the item to edit;                 */
/*                                               */
/*             Position mode for the caret:      */
/*             0 = near mouse,                   */
/*             1 = start of area for text area,  */
/*                 end of line for writable,     */
/*             2 = end of line.                  */
/*************************************************/

static _kernel_oserror * form_start_textarea_edit(browser_data * b, HStream * tp, int mode)
{
  BBox                      box;
  int                       lh, lb, fh, x, y;
  WimpGetPointerInfoBlock   m;
  WimpGetWindowStateBlock   state;
  _kernel_oserror         * e;

  if (fe_browser != b || fe_token != tp)
  {
    /* Cancel any existing edits and set library variables */
    /* to indicate the token and nature of token that is   */
    /* now being edited.                                   */

    e = form_end_edit(b, 1, 0);
    if (e) return e;

    fe_single   = tp->tagno != TAG_TEXTAREA;
    fe_password = tp->tagno == TAG_INPUT && HtmlINPUTtype(tp) == inputtype_PASSWORD;
    fe_browser  = b;
    fe_token    = tp;
  }

  switch (mode)
  {
    case 0: /* Place caret near the mouse pointer */
    {
      form_input_box(b, fe_token, &box, &lh, &lb, &fh);

      /* The above call accounts for the border with of the */
      /* item; strip that away.                             */

      box.xmin += 8;
      box.ymin += 8;
      box.xmax -= 8;
      box.ymax -= 8;

      /* Work out the where the pointer is in window coordinates */

      e = wimp_get_pointer_info(&m);
      if (e) return e;

      state.window_handle = b->window_handle;
      e = wimp_get_window_state(&state);
      if (e) return e;

      x = coords_x_toworkarea(m.x, (WimpRedrawWindowBlock *) &state);
      y = coords_y_toworkarea(m.y, (WimpRedrawWindowBlock *) &state);

      /* Get x as an OS unit offset from the left hand edge of the */
      /* forms item, and y as a line number.                       */

      x -= box.xmin;
      x += fe_xscroll;
      y  = fe_yscroll + (box.ymax - y) / lh;

      form_textarea_find_caret(form_get_field_text(b, fe_token), fh, &fe_index, x, y);
    }
    break;

    case 1: /* Place caret at start of line for text area, or end of line for single line writable */
    {
      if (fe_single) fe_index = strlen(form_get_field_text(b, fe_token));
      else           fe_index = 0;

      break;
    }

    case 2: fe_index = strlen(form_get_field_text(b, fe_token)); /* Place caret at end of line */
    break;

    default: fe_index = 0; /* Fallback position - caret at start of line */
    break;
  }

  form_give_focus(b);
  form_autoscroll(b);

  return NULL;
}

/*************************************************/
/* form_check_scroll_field()                     */
/*                                               */
/* For a given text area or single line writable */
/* ensure that the caret is shown at a given x   */
/* and y position, when the line height is lh,   */
/* scrolling the field contents if necessary.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the text field;       */
/*                                               */
/*             Pointer to a BBox holding the     */
/*             window coord bounding box of the  */
/*             text field, *excluding* any       */
/*             borders;                          */
/*                                               */
/*             X offset into field, in OS units, */
/*             for the caret;                    */
/*                                               */
/*             Number of lines below the first   */
/*             one for the caret.                */
/*                                               */
/*             The line height in OS units.      */
/*************************************************/

static void form_check_scroll_field(browser_data * b, BBox * box, int x, int y, int lh)
{
  int dx, dy, t;

  dx = dy = 0;
  t  = box->xmax - box->xmin; /* Field's width in OS units */

  /* If the desired position is less than the current positon, scroll left */

  if (x - fe_xscroll < 0) dx = x - fe_xscroll;

  /* If the desired position is greater than the current position plus */
  /* the field width, scroll right                                     */

  if (x - fe_xscroll > t) dx = x - t - fe_xscroll;

  t = (box->ymax - box->ymin + 16) / lh; /* Field's height in lines; + 16 puts the borders back for this calculation */

  if (!fe_single) /* Only multi-line text areas can scroll vertically */
  {
    /* If the desired line is less than the current one, scroll down */

    if (y - fe_yscroll < 0) dy = y - fe_yscroll;

    /* If the desired line is greater than the current position plus the */
    /* height of the field (in lines), scroll up.                        */

    if (y - fe_yscroll >= t) dy = (y - t + 1) - fe_yscroll;
  }

  /* If dx or dy are non-zero, need to scroll */

  if (dx || dy)
  {
    BBox                      obox;
    WimpGetCaretPositionBlock c;

    /* Round the absolute value of x scroll up to a multiple of 4 */

    if (dx < 0)
    {
      if ((-dx) & 3) dx -= (4 - ((-dx) & 3));
    }
    else if (dx & 3) dx += 4 - (dx & 3);

    /* Find the caret */

    wimp_get_caret_position(&c);

    /* If the caret is not in a specific icon and is in the specified */
    /* browser window, hide it whilst things are scrolled to avoid    */
    /* redraw problems.                                               */

    if (
         c.icon_handle < 0                   &&
         c.window_handle == b->window_handle
       )
       wimp_set_caret_position(b->window_handle, -1, 0, 0, -1, -1);

    /* Increment the internal scroll positions */

    fe_xscroll += dx;
    fe_yscroll += dy;

    obox = *box;

    /* Work out the region to copy for the scroll, and block copy it */

    if (dx < 0) obox.xmax += dx;
    else        obox.xmin += dx;

    if (dy < 0) obox.ymin -= dy * lh;
    else        obox.ymax -= dy * lh;

    wimp_block_copy(b->window_handle,

                    obox.xmin,
                    obox.ymin,
                    obox.xmax,
                    obox.ymax,

                    obox.xmin - dx,
                    obox.ymin + dy * lh);

    /* Update any required regions for horizontal scrolling */

    if (dx)
    {
      WimpRedrawWindowBlock r;

      r.window_handle = b->window_handle;
      r.visible_area  = *box;

      if (dx < 0) r.visible_area.xmax = r.visible_area.xmin - dx;
      else        r.visible_area.xmin = r.visible_area.xmax - dx;

      browser_update(b, &r, 1, 0);
    }

    /* Update any required regions for vertical scrolling */

    if (dy)
    {
      WimpRedrawWindowBlock r;

      r.window_handle = b->window_handle;
      r.visible_area  = *box;

      if (dy < 0) r.visible_area.ymin = r.visible_area.ymax + dy * lh;
      else        r.visible_area.ymax = r.visible_area.ymin + dy * lh;

      browser_update(b, &r, 1, 0);
    }

    /* If the caret, when checked earlier, was not in a specific icon and */
    /* was in the specified browser window, put the caret back.           */

    if (
         c.icon_handle < 0                   &&
         c.window_handle == b->window_handle
       )
    {
      wimp_set_caret_position(c.window_handle,
                              c.icon_handle,
                              c.xoffset,
                              c.yoffset,
                              c.height,
                              c.index);
    }
  }
}

/*************************************************/
/* form_give_focus()                             */
/*                                               */
/* If the library is editing some forms field,   */
/* and this can take an input focus, give the    */
/* focus to the field and return a success flag. */
/* (Editing is started with form_click_field,    */
/* for example, and ended with form_end_edit).   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the forms item that   */
/*             is to be given the caret.         */
/*                                               */
/* Returns:    1 if the caret is placed in the   */
/*             item, else 0.                     */
/*************************************************/

int form_give_focus(browser_data * b)
{
  BBox box;
  int  lh, lb, fh, ox, oy;

  /* If not editing, no need to do anything */

  if (!b || b != fe_browser || !fe_token) return 0;

  /* Try to get the bounding box of the item */

  if (
       !form_input_box(b,
                       fe_token,
                       &box,
                       &lh,
                       &lb,
                       &fh)
     )
     return 0;

  /* The above call accounts for the item's border; */
  /* strip this away.                               */

  box.xmin += 8;
  box.ymin += 8;
  box.xmax -= 8;
  box.ymax -= 8;

  /* Get the x offset in OS units and y offset in lines */
  /* that the caret should be placed at.                */

  form_textarea_caretpos(form_get_field_text(b, fe_token),
                         fh,
                         fe_index,
                         &ox,
                         &oy);

  /* Ensure a multiple line item (i.e. text area) is */
  /* scrolled to show the line the caret will be     */
  /* moved to.                                       */

  form_check_scroll_field(b,
                          &box,
                          ox,
                          oy,
                          lh);

  /* Position the caret */

  if (
       wimp_set_caret_position(fe_browser->window_handle,
                               -1,
                               box.xmin + ox - fe_xscroll,
                               box.ymax - lh * (oy - fe_yscroll + 1) + 4,
                               lh - 2,
                               0)
     )
     return 0;

  return 1;
}

/*************************************************/
/* form_extend_flex()                            */
/*                                               */
/* Extends a given flex block by a given amount, */
/* returning the offset into the block of the    */
/* new data or -1 to show failure.               */
/*                                               */
/* Parameters: flex_ptr for the flex block;      */
/*             Amount to extend by.              */
/*                                               */
/* Returns:    Offset into the block of the new  */
/*             data, or -1 if the claim failed.  */
/*************************************************/

static int form_extend_flex(void **data, int size)
{
  int o;

  /* If the block already exists, extend it; else, */
  /* create it.                                    */

  if (*data)
  {
    o = flex_size(data);
    if (!flex_extend(data, o + size)) return -1;
  }
  else
  {
    o = 0;
    if (!flex_alloc(data, size)) return -1;
  }

  #ifdef TRACE
    flexcount += size;
    if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
  #endif

  return o;
}

/*************************************************/
/* form_encode_flex_data()                       */
/*                                               */
/* Encodes the given flex data ready for a form  */
/* submission.                                   */
/*                                               */
/* Spaces, ENCODE_DATASEP, ENCODE_FIELDSEP and   */
/* ENCODE_VALUESEP characters are replaced by    */
/* special alternatives ('+', '?', '&' and '='   */
/* respectively), whilst all other non-          */
/* alphanumeric characters are escaped ('%'      */
/* followed by the ASCII code in hex).           */
/*                                               */
/* Parameters: Pointer to the flex anchor        */
/*             pointing to the data;             */
/*                                               */
/*             Encoding type [not implemented];  */
/*                                               */
/*             Offset in block to start encoding */
/*             at.                               */
/*                                               */
/* Returns:    1 if successful, 0 if failed (for */
/*             example, ran out of memory when   */
/*             extending flex block to hold      */
/*             escaped character sequences).     */
/*************************************************/

static int form_encode_flex_data(void ** data, const char * enctype, int start_at)
{
  int    i, o;
  char * p;

  enctype = enctype; /* [Not implemented] Avoid compiler warning for now */

  o       = start_at;

  /* First, escape sequences */

  while (*(((char *) *data) + o))
  {
    p = ((char *) *data) + o;

    /* If not an alphanumeric character and not one of the */
    /* 'special' characters, escape it                     */

    if (
         !isalnum((int) *p)

         /* (As not escaped by e.g. Netscape Navigator (TM) */

         && *p != '_'
         && *p != '@'
         && *p != '.'
         && *p != '-'
         && *p != '*'

         /* Specials - see code below */

         && *p != ' '
         && *p != ENCODE_DATASEP
         && *p != ENCODE_FIELDSEP
         && *p != ENCODE_VALUESEP
       )
    {
      char code[10];

      if (*p == '\n') strcpy (code, "%0D%0A");
      else            sprintf(code, "%%%02X", *p);

      if (!form_extend_flex(data, strlen(code) - 1)) return 0;

      memmove(((char *) *data) + o + strlen(code),
              ((char *) *data) + o + 1,
              flex_size(data) - o - strlen(code));

      strncpy(((char *) *data) + o,
              code,
              strlen(code));

      o += strlen(code) - 1;
    }
    o++;
  }

  /* Now do simple encodings */

  p = (char *) *data;
  o = flex_size(data);

  for (i = 0; i < o; i++, p++)
  {
    switch (*p)
    {
      case ' ':              *p = '+'; break;
      case ENCODE_DATASEP:   *p = '?'; break;
      case ENCODE_FIELDSEP:  *p = '&'; break;
      case ENCODE_VALUESEP:  *p = '='; break;
    }
  }

  return 1;
}

/*************************************************/
/* form_field_data_size()                        */
/*                                               */
/* For a given field in a given browser, returns */
/* the total size requirement including name     */
/* and value, ready for encoding a reply to the  */
/* server (so this will consider what particular */
/* value sizes to count if something is selected */
/* or unselected, etc.).                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the field;            */
/*                                               */
/*             Pointer to the form_field struct  */
/*             for the item;                     */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the item.            */
/*                                               */
/* Returns:    Total required memory size needed */
/*             to hold the item.                 */
/*************************************************/

static int form_field_data_size(browser_data * b, form_field * fp, HStream * tp, unsigned int x, unsigned int y)
{
  int size;

  size = 0;

  if (HtmlELEMENTname(tp))
  {
    size = strlen(HtmlELEMENTname(tp)) + 1; /* add one for the ' = ' sign */

    switch(fp->header.type)
    {
      case form_textarea: /* form_textarea same as form_password: no break */
      case form_text:     /* form_text same as form_password: no break */
      case form_password:
      {
        /* Size of the text string in the item */

        size += strlen(fp->value.text);
      }
      break;

      case form_checkbox: /* form_checkbox same as form_radio: no break */
      case form_radio:
      {
        /* If checked, return the size of the value string, if there */
        /* is one, or 0 if not checked.                              */

        if (fp->value.checked)
        {
          if (HtmlINPUTvalue(tp)) size += strlen(HtmlINPUTvalue(tp));
          else                    size += 2;
        }
        else size = 0;
      }
      break;

      case form_select:
      {
        int    i, n, s;
        char * p;
        char * q;

        /* For select fields, return the menu item name if one is */
        /* specified (*q is non-zero) or the menu item text if no */
        /* name is present (use p).                               */

        s = 0;
        n = HtmlSELECToptions(tp)[0];
        p = (char *)(HtmlSELECToptions(tp) + 2);

        for (i = 0; i < n; i++)
        {
          p++;

          q = p + strlen(p) + 1;

          if (
               fp->value.select.selection[i] == FORM_SELCHAR
             )
             s += size + strlen(q) + 1; /* Plus one for & separator */

          p = q + strlen(q) + 1 ;
        }

        if (s) s--; /* One of the & separators need not be included */

        size = s;
      }
      break;

      /* Image buttons send "name.x=123&name.y=123" */

      case form_image:
      {
        int  len;

        /* We've already added up length of "name=", this gives us */
        /* length of "name.x=&name.y="                             */

        size = (size + 2) * 2 + 1;

        /* Add in length of the two coordinates (note this is a */
        /* very slow function call)                             */

        len = utils_len_printf("%u%u", x, y);

        if (len >= 0) size += len;
        else          size += 64;  /* If len is < 0 there was an error, so play it safe */

        break;
      }

      /* The size of the submit button's text */

      case form_button:
      case form_submit:
      {
        size += strlen(form_button_text(tp));
      }
      break;

      /* The size of the item's value string */

      case form_hidden:
      {
        if (HtmlINPUTvalue(tp)) size += strlen(HtmlINPUTvalue(tp));
        else                    size  = 0;
      }
      break;
    }
  }

  return size;
}

/*************************************************/
/* form_build_data()                             */
/*                                               */
/* Build the data structure for a form in the    */
/* indicated flex block. The token identifies    */
/* the form and if it is a submit token it is    */
/* included in the data structure; other submit  */
/* tokens are not. The flex block does not exist */
/* before calling this routine. The form data    */
/* are encoded according to the specification.   */
/*                                               */
/* If the form METHOD is GET, the URL is         */
/* included as part of this data.                */
/*                                               */
/* x and y are coords for forms submitted by     */
/* INPUT TYPE = IMAGE.                           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the form;            */
/*                                               */
/*             X coord for INPUT TYPE = IMAGE    */
/*             submissions;                      */
/*                                               */
/*             Y coord for INPUT TYPE = IMAGE    */
/*             submissions;                      */
/*                                               */
/*             Pointer to the flex anchor for    */
/*             the flex block in which the data  */
/*             will be built.                    */
/*************************************************/

static _kernel_oserror * form_build_data(browser_data * b, HStream * token, int x, int y, char ** data)
{
  int           i, o, ho, fields, additions_start = 0;
  form_header * hp;
  form_field  * fp;
  HStream     * tp;
  int           first;

  #ifdef TRACE

    if (*data)
    {
      erb.errnum = Utils_Error_Custom_Normal;

      strcpy(erb.errmess,
             "Flex anchor is not NULL in form_build_data - flex error could have resulted had execution proceeded...");

      /* (Some routines may not echo this, and whilst we risk  */
      /* getting the error twice, returning an error is useful */
      /* as hopefully the errant caller will exit early rather */
      /* than dig itself in any deeper).                       */

      show_error_ret(&erb);

      return &erb;
    }

  #endif

  /* Claim 1 byte in the block for a terminator - should there */
  /* be no fields to add, the block will still hold valid data */

  if (form_extend_flex((void **) data, 1) < 0)
  {
    return make_no_cont_memory_error(4);
  }

  **data = 0;

  /* Find the field header and work out its offset in the data block */

  hp = form_find_record(b, token, 1);
  if (!hp) return NULL;

  ho = ((int) hp) - (int) b->fdata;

  tp = hp->token;

  if (HtmlFORMmethod(tp) == formmethod_GET && HtmlFORMaction(tp))
  {
    /* A GET form  - put the URL in */

    int i;
    int len = strlen(HtmlFORMaction(tp));

    /* Only '+1' as whilst we need a field separator and a terminator */
    /* on top, we've already allocated 1 byte above.                  */

    i = form_extend_flex((void **) data, len + 1);
    if (i < 0) return make_no_cont_memory_error(5);

    strcpy(*data, HtmlFORMaction(tp));

    additions_start      = len + 1;

    *((*data) + len)     = ENCODE_DATASEP;
    *((*data) + len + 1) = 0;
  }

  /* Get the number of fields, flag that this is the first time */
  /* around in 'first', and set the offset into the block of    */
  /* the first item after the header in 'o'.                    */

  fields = hp->fields;
  first  = 1;
  o      = sizeof(form_header);

  /* Loop through all fields */

  for (i = 0; i < fields; i++)
  {
    fp = (form_field *) (((int) b->fdata) + ho + o);
    tp = fp->header.token;

    /* Only proceed with encoding the contents if the item is named, */
    /* isn't a reset button, a submit button, or an image submit     */
    /* button. The exception for the latter two is if the token      */
    /* given to the function is that representing the button, in     */
    /* which case the item will be included in the encoded data.     */

    if (
         HtmlELEMENTname(tp)                                           &&
         fp->header.type != form_reset                                 &&
         (fp->header.type != form_submit || fp->header.token == token) &&
         (fp->header.type != form_image  || fp->header.token == token)
       )
    {
      int size;

      /* Find out how much space the encoded item will need */

      size = form_field_data_size(b, fp, tp, x, y);

      if (size)
      {
        int  offset;
        char *p;

        /* Try to claim enough memory to hold the item. If this is the first  */
        /* time round the loop add nothing, else add 1 for a field separator. */

        offset = form_extend_flex((void **) data, size + (first ? 0 : 1));

        if (offset < 0) return make_no_cont_memory_error(6);

        /* May have moved, so recalculate... */

        fp = (form_field  *) (((int) b->fdata) + ho + o);
        hp = (form_header *) (((int) b->fdata) + ho);
        tp = fp->header.token;

        p  = (*data) + offset - 1;

        /* If not the first time round, add in a field separator */

        if (!first) *p++ = ENCODE_FIELDSEP;

        /* Copy the item name to the block */

        *p = 0;

        strcpy(p, HtmlELEMENTname(tp));

        /* Put in a name/value separator */

         p   = strchr(p, 0);
        *p++ = ENCODE_VALUESEP;
        *p   = 0;

        switch(fp->header.type)
        {
          case form_textarea: /* form_textarea same as form_password: no break */
          case form_text:     /* form_text same as form_password: no break */
          case form_password:
          {
            /* Simple text string for these items */

            strcpy(p, fp->value.text);
          }
          break;

          case form_checkbox: /* form_checkbox same as form_radio: no break */
          case form_radio:
          {
            /* If the item has a value, use this. Otherwise use the default */
            /* string of 'on'.                                              */

            if (HtmlINPUTvalue(tp)) strcpy(p, HtmlINPUTvalue(tp));
            else                    strcpy(p, "on");
          }
          break;

          case form_select:
          {
            int    i, n, first;
            char * f;
            char * q;

            first = 1;
            n     = HtmlSELECToptions(tp)[0];
            f     = (char *)(HtmlSELECToptions(tp) + 2);

            for (i = 0; i < n; i++)
            {
              f++;

              /* q points to the menu item name (if it has one) */

              q = f + strlen(f) + 1;

              if (fp->value.select.selection[i] == FORM_SELCHAR)
              {
                if (!first)
                {
                  *p++ = ENCODE_FIELDSEP;

                  strcpy(p, HtmlSELECTname(tp));

                   p   = strchr(p, 0);
                  *p++ = ENCODE_VALUESEP;
                  *p   = 0;
                }

                /* Use the menu item name if there is one, else the item text */

                strcpy(p, q);

                p = strchr(p, 0);

                first = 0;
              }

              /* Move to the next item */

              f = q + strlen(q) + 1;
            }
          }
          break;

          case form_image:
          {
            /* Need to back up a bit */
            p += sprintf(p-1, ".x%c%u%c%s.y%c%u", ENCODE_VALUESEP, x, ENCODE_FIELDSEP, HtmlINPUTname(tp), ENCODE_VALUESEP, y) - 1;
          }
          break;

          case form_button:
          case form_submit:
          {
            /* Use the button text */

            strcpy(p, form_button_text(tp));
          }
          break;

          case form_hidden:
          {
            /* Use the item value */

            if (HtmlINPUTvalue(tp)) strcat(p, HtmlINPUTvalue(tp));
          }
          break;
        }

        first = 0;
      }
    }

    /* Skip to the next item */

    o += fp->header.size * 4;
  }

  /* Refind the header record and the token */
  /* representing it, to get the encoding   */
  /* type for the form.                     */

  hp = form_find_record(b, token, 1);
  tp = hp->token;

  /* Now encode the block according to the specified encoding type */

  if (!form_encode_flex_data((void **) data, HtmlFORMenctype(tp), additions_start))
  {
    return make_no_cont_memory_error(7);
  }

  /* Finished */

  return NULL;
}

/*************************************************/
/* form_submission_details()                     */
/*                                               */
/* For a given token within a form, return the   */
/* URL of submission and a flag to say if the    */
/* submission should be done by POST.            */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to a token in the form;   */
/*                                               */
/*             Pointer to an int, in which 1 is  */
/*             written if the submission method  */
/*             is POST, 0 is written if it is    */
/*             GET, else the value is unchanged. */
/*                                               */
/* Returns:    Pointer to the URL to submit the  */
/*             form to, or NULL if unknown (also */
/*             see the parameters list).         */
/*                                               */
/* Assumes:    The int pointer may be NULL.      */
/*************************************************/

const char * form_submission_information(browser_data * b, HStream * token, int * post)
{
  HStream     * tp;
  form_header * hp;

  hp = form_find_record(b, token, 1);
  if (!hp) return NULL;

  tp = hp->token;
  if (!tp) return NULL;

  if (post)
  {
    if      (HtmlFORMmethod(tp) == formmethod_POST) *post = 1;
    else if (HtmlFORMmethod(tp) == formmethod_GET)  *post = 0;
  }

  return HtmlFORMaction(tp);
}

/*************************************************/
/* form_submit_form()                            */
/*                                               */
/* Submits the contents of a form to a URL       */
/* specified by the given token (usually, the    */
/* token representing the Submit button in the   */
/* form).                                        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to the token which had a  */
/*             front-end representation of       */
/*             itself activated to submit the    */
/*             form.                             */
/*************************************************/

static _kernel_oserror * form_submit_form(browser_data * b, HStream * token, int x, int y)
{
  char            * data = NULL;
  _kernel_oserror * e;
  form_header     * hp;
  HStream         * tp;
  int               adj;

  adj = controls.ignore_adjust ? 0 : adjust();

  /* Finish any current editing and find the header record for the form */

  e = form_finish_edit(b);
  if (e) return e;

  hp = form_find_record(b, token, 1);
  if (!hp) return NULL;

  tp = hp->token;

  /* For GET forms, we must have a URL to submit to in the 'anchor' field */

  if (HtmlFORMmethod(tp) == formmethod_GET && HtmlFORMaction(tp))
  {
    e = form_build_data(b, token, x, y, &data);

    if (!e) e = fetchpage_fetch_targetted(b,
                                          data,
                                          HtmlFORMtarget(tp),
                                          NULL,
                                          adj);

    if (data)
    {
      #ifdef TRACE
        flexcount -= sizeof(&data);
        if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
      #endif

      flex_free((void **) &data);
    }
  }

  /* For POST types, must have a URL too, but use the 'post_data' block */
  /* of the browser_data structure so that fetcher routines know to     */
  /* encode the data in the header, rather than the URL                 */

  else if (HtmlFORMmethod(tp) == formmethod_POST && HtmlFORMaction(tp))
  {
    /* We may already have some data - should free it first if so! */

    if (b->post_data) flex_free(&b->post_data);

    /* Right, now construct the data block */

    e = form_build_data(b, token, x, y, (char **) &b->post_data);
    tp = hp->token;

    if (e)
    {
      if (b->post_data)
      {
        #ifdef TRACE
          flexcount -= sizeof(&b->post_data);
          if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
        #endif

        flex_free(&b->post_data);
      }
    }
    else e = fetchpage_fetch_targetted(b,
                                       HtmlFORMaction(tp),
                                       HtmlFORMtarget(tp),
                                       NULL,
                                       adj);
  }

  /* If the above fail, can't submit the form */

  else
  {
    erb.errnum = Utils_Error_Custom_Message;

    StrNCpy0(erb.errmess,
             lookup_token("CantSubmit:Can't find where to send the form to from this web page",
                          0,
                          0));

    e = &erb;
  }

  return e;
}

/*************************************************/
/* form_click_field()                            */
/*                                               */
/* Processes a click on a form field.            */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form;             */
/*                                               */
/*             Pointer to the token for the item */
/*             that was clicked upon;            */
/*                                               */
/*             Position mode for the caret:      */
/*             0 = near mouse,                   */
/*             1 = start of area for text area,  */
/*                 end of line for writable,     */
/*             2 = end of line for writable, end */
/*                 of all text for text area;    */
/*                                               */
/*             x-coordinate (if input image)     */
/*                                               */
/*             y-coordinate (if input image)     */
/*************************************************/

_kernel_oserror * form_click_field(browser_data * b, HStream * token, int mode, int x, int y)
{
  form_field      * fp;
  _kernel_oserror * e = NULL;

  /* If something is being edited that's not the given browser, end that edit */

  if (fe_browser && fe_browser != b)
  {
    e = form_end_edit(fe_browser, 1, 0);
    if (e) return e;
  }

  fp = form_find_record(b, token, 0);

  /* If it has a record in this module, edit it */

  if (fp)
  {
    switch (fp->header.type)
    {
      /* For text / password items, position the caret */

      case form_text:     /* same as form_password: no break */
      case form_textarea: /* same as form_password: no break */
      case form_password: e = form_start_textarea_edit(b, token, mode);
      break;

      /* For check (option) boxes and radios, toggle or values as appropriate */

      case form_checkbox: /* As for radios, so no 'break' */
      case form_radio:    e = form_put_field(b, token, (char *) !fp->value.checked, 1);
      break;

      /* For selection boxes (display fields with menus), open the menu */

      case form_select:   e = form_start_select_edit(b, token);
      break;

      /* For 'submit' buttons, slab the button in and out and submit the form */

      case form_image:
      case form_submit:
      {
        browser_flash_token(b, token);
        e = form_submit_form(b, token, x, y);
      }
      break;

      /* Don't submit BUTTON items */

      case form_button:
      {
        browser_flash_token(b, token);

        /* Tell the user we can't do anything more with this... */

        erb.errnum = Utils_Error_Custom_Message;

        StrNCpy0(erb.errmess,
                 lookup_token("NoJavascript:Sorry, the browser does not support the JavaScript code required to make this button work.",
                              0,
                              0));

        show_error_ret(&erb);
      }
      break;

      /* For 'reset' buttons, slab the button in, reset the form and slab it out again */

      case form_reset:
      {
        browser_highlight_token(b, token);

        form_cancel_edit(b);
        e = form_reset_form(b, token);

        if (!e) browser_clear_highlight(b, 1);
      }
      break;
    }
  }

  return e;
}

/*************************************************/
/* form_cancel_edit()                            */
/*                                               */
/* Terminates any editing in the specified       */
/* browser, or all browser forms, discarding any */
/* changes.                                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form, or NULL to  */
/*             cancel any edits in any browsers. */
/*************************************************/

_kernel_oserror * form_cancel_edit(browser_data * b)
{
  return form_end_edit(b, 0, 1);
}

/*************************************************/
/* form_finish_edit()                            */
/*                                               */
/* Terminates any editing in the specified       */
/* browser, or all browser forms, keeping any    */
/* changes.                                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the form, or NULL to  */
/*             cancel any edits in any browsers. */
/*************************************************/

_kernel_oserror * form_finish_edit(browser_data * b)
{
  return form_end_edit(b, 1, 1);
}

/*************************************************/
/* form_button_text()                            */
/*                                               */
/* Returns a pointer to the text that should be  */
/* placed in a given button.                     */
/*                                               */
/* Parameters: Pointer to the token representing */
/*             the button.                       */
/*                                               */
/* Returns:    Pointer to the text that should   */
/*             go in that button, according to   */
/*             whether it is a Submit, Reset, or */
/*             unknown button type.              */
/*************************************************/

const char * form_button_text(HStream * tp)
{
  const char * p;

  p = HtmlINPUTvalue(tp);

       if (!p && HtmlINPUTtype(tp) == inputtype_SUBMIT) p = lookup_token("Submit:Submit",0,0);
  else if (!p && HtmlINPUTtype(tp) == inputtype_BUTTON) p = "";
  else if (!p && HtmlINPUTtype(tp) == inputtype_RESET)  p = lookup_token("Reset:Reset",  0,0);

  if (!p) p = lookup_token("Unknown:Action",0,0);

  return p;
}

/*************************************************/
/* form_cursor_editable()                        */
/*                                               */
/* Returns 1 if the given field can be edited    */
/* via. the keyboard (i.e. it is a password      */
/* field, a single line writable, or a text area */
/* field).                                       */
/*                                               */
/* Parameters: The form_fieldtype of the field.  */
/*                                               */
/* Returns:    1 if this can be edited via. the  */
/*             keyboard, else 0.                 */
/*************************************************/

static int form_cursor_editable(form_fieldtype type)
{
  return (type == form_password || type == form_text || type == form_textarea);
}

/*************************************************/
/* form_token_cursor_editable()                  */
/*                                               */
/* Finds out if a given token represents a       */
/* cursor editable forms element.                */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the forms;            */
/*                                               */
/*             Pointer to the token.             */
/*                                               */
/* Returns:    1 if the token represents a       */
/*             cursor editable forms element,    */
/*             else 0.                           */
/*************************************************/

int form_token_cursor_editable(browser_data * b, HStream * token)
{
  form_header * hp;
  form_field  * fp;
  int           i = 0;

  /* Find the header item */

  hp = form_find_record(b, token, 1);

  if (!hp) return 0;

  /* Search for the field record */

  fp = (form_field *) (((int) hp) + sizeof(form_header));

  while (fp->header.token != token && i < hp->fields)
  {
    fp = (form_field *) (((int) fp) + fp->header.size * 4);
    i++;
  }

  /* Record not found */

  if (i == hp->fields) return 0;

  /* Record is found */

  return form_cursor_editable(fp->header.type);
}

/*************************************************/
/* form_next_field()                             */
/*                                               */
/* Moves the editing position on to the next     */
/* editable field in the form. If there is no    */
/* next field, optionally submit the form.       */
/*                                               */
/* Parameters: 1 to automatically submit the     */
/*             form if there are no more         */
/*             editable fields, else 0;          */
/*                                               */
/*             Pointer to an int, into which 0   */
/*             may be written if the keypress    */
/*             ends up in fact not being used    */
/*             for some reason.                  */
/*                                               */
/* Assumes:    The int pointer may be NULL.      */
/*************************************************/

static _kernel_oserror * form_next_field(int auto_submit, int * used)
{
  form_header * hp;
  form_field  * fp;
  int           i, found;
  HStream     * first;
  HStream     * next;

  found = 0;
  first = next = NULL;

  /* Find the details of the currently editing form */

  hp = form_find_record(fe_browser, fe_token, 1);
  fp = (form_field *) (((int) hp) + sizeof(form_header));

  /* Go through all the fields for the current form */

  for (i = 0; i < hp->fields; i++)
  {
    /* Can this field be edited from the keyboard? */

    if (form_cursor_editable(fp->header.type))
    {
      /* For the first editable field, set 'first' to point */
      /* to the token representing it. This may be used if  */
      /* there are no other editable fields to go to, but   */
      /* the auto submit flag is unset.                     */

      if (!first) first = fp->header.token;

      /* If 'next' is unset and the currently editing element */
      /* (represented by fe_token, see below) has been found, */
      /* then set 'next' to the current token - which must be */
      /* the first editable element available after fe_token. */

      if (!next && found) next = fp->header.token;
    }

    /* If this element is represented by the same token as   */
    /* the currently editing element, set 'found' to 1. Next */
    /* time around the loop, the code above may well then    */
    /* set 'next' to point to the next token that can be     */
    /* used for editing.                                     */

    if (fp->header.token == fe_token) found = 1;

    /* Move on to the next element. */

    fp = (form_field *) (((int) fp) + fp->header.size * 4);
  }

  /* If 'next' is NULL, either the currently editing element */
  /* was not found or there is no editable item after it.    */

  if (!next)
  {
    /* Either submit the form, or jump back up to the first editable */
    /* element.                                                      */

    if (auto_submit) return (form_submit_form(fe_browser, fe_token, 0, 0));
    else next = first;
  }

  /* If using keyboard control, want to try to find a generally */
  /* selectable token to move to (a wider search than the above */
  /* writable-only one). If this fails, go back to the token    */
  /* worked out above (this shouldn't really happen unless      */
  /* there was 2D movement possible at this stage - may not be  */
  /* able to find a selectable on the next line, but may have   */
  /* a writable forms field on the same one).                   */

  if (choices.keyboard_ctrl && fe_lastkey != akbd_TabK) /* Allow Tab to move between form elements only */
  {
    browser_data * b;
    browser_data * ancestor;
    browser_data * owner;
    HStream      * t;

    /* Remember the current editing details and cancel the existing edit, */
    /* ready for movement to a new token.                                 */

    b        = fe_browser;
    t        = fe_token;
    ancestor = utils_ancestor(b);

    owner = ancestor->selected_owner;

    form_end_edit(fe_browser, 1, 1); /* fe_browser etc. are now invalid, don't use again! */

    /* If the user has combined mouse and keyboard control, may have the */
    /* editing token different from the selected token. In this case,    */
    /* want to move from the caret, not wherever the selection may be.   */

    if (owner && ancestor->selected != t)
    {
      browser_clear_selection(owner, 0);

      browser_select_token(b, t, 0);

      owner = b;
    }

    /* Try moving the selection */

    if (!browser_move_selection(owner, fe_lastkey))
    {
      /* The selection failed, so try staying on the currently editing token */
      /* (with keyboard control, when you get to the bottom of the page you  */
      /* don't wrap to the top - so although the normal forms keyboard edit  */
      /* would go back up to the first writable item, if available, this     */
      /* shouldn't happen here if we are to be consistent).                  */
      /*                                                                     */
      /* Of course, we don't want to move back to the token if it's not      */
      /* visible at this moment; it can be very disconcerting to jump back   */
      /* up after using, say, page up / page down to move around.            */

      if (browser_check_visible(owner, NULL, t) && ancestor->selected != t)
      {
        browser_select_token(owner, t, 0);

        return form_click_field(owner, t, 1, 0, 0);
      }
      else
      {
        /* Not doing anything, so mark the keypress as unused */

        if (used) *used = 0;
      }
    }

    /* If the selection did move, may need to turn off the caret */

    else if (ancestor->selected != t && !form_token_cursor_editable(owner, ancestor->selected)) return wimp_set_caret_position(ancestor->window_handle, -1, 0, 0, -1, -1);
  }
  else
  {
    /* If there is indeed a next element, and it isn't the same as the */
    /* current one, move to it.                                        */

    if (next && next != fe_token)
    {
      if (choices.keyboard_ctrl) browser_select_token(fe_browser, next, 0); /* If moving by Tab, make sure the selection keeps up */
      return form_click_field(fe_browser, next, 1, 0, 0);
    }
  }

  return NULL;
}

/*************************************************/
/* form_previous_field()                         */
/*                                               */
/* Moves the editing position up to the previous */
/* editable field in the form.                   */
/*                                               */
/* Parameters: Pointer to an int, into which 0   */
/*             may be written if the keypress    */
/*             ends up in fact not being used    */
/*             for some reason.                  */
/*                                               */
/* Assumes:    The int pointer may be NULL.      */
/*************************************************/

static _kernel_oserror * form_previous_field(int * used)
{
  form_header * hp;
  form_field  * fp;
  int           i, found;
  HStream     * last;
  HStream     * previous;

  found = 0;
  previous = last = NULL;

  /* Find the details of the currently editing form */

  hp = form_find_record(fe_browser, fe_token, 1);
  fp = (form_field *) (((int) hp) + sizeof(form_header));

  /* Go through all the fields */

  for (i = 0; i < hp->fields; i++)
  {
    /* If we find the currently editing element, set 'found' to 1 */

    if (fp->header.token == fe_token) found = 1;

    /* Is the current element editable from the keyboard? */

    if (form_cursor_editable(fp->header.type))
    {
      /* If so, set 'last' to it. This may be used if there */
      /* are no previous elements to go to - by the end of  */
      /* the for loop, this will point to the last keyboard */
      /* editable element.                                  */

      last = fp->header.token;

      /* If 'found' isn't set, then remember the current */
      /* element in 'previous'. If 'found' is eventually */
      /* set by the above code, this will mean that      */
      /* 'previous' stays set on the keyboard editable   */
      /* field before the current one.                   */

      if (!found) previous = fp->header.token;
    }

    /* Move to the next field */

    fp = (form_field *) (((int) fp) + fp->header.size * 4);
  }

  /* If there is no previous item, wrap around to the last one */

  if (!previous) previous = last;

  /* If using keyboard control, want to try to find a generally */
  /* selectable token to move to (a wider search than the above */
  /* writable-only one). If this fails, go back to the token    */
  /* worked out above (this shouldn't really happen unless      */
  /* there was 2D movement possible at this stage - may not be  */
  /* able to find a selectable on a previous line, but may have */
  /* a writable forms field on the same one).                   */

  if (choices.keyboard_ctrl && fe_lastkey != akbd_TabK + akbd_Sh) /* Allow Tab to move between form elements only */
  {
    browser_data * b;
    browser_data * ancestor;
    browser_data * owner;
    HStream      * t;

    /* Remember the current editing details and cancel the existing edit, */
    /* ready for movement to a new token.                                 */

    b        = fe_browser;
    t        = fe_token;
    ancestor = utils_ancestor(b);

    owner = ancestor->selected_owner;

    form_end_edit(fe_browser, 1, 1); /* fe_browser etc. are now invalid, don't use again! */

    /* If the user has combined mouse and keyboard control, may have the */
    /* editing token different from the selected token. In this case,    */
    /* want to move from the caret, not wherever the selection may be.   */

    if (ancestor->selected != t)
    {
      browser_clear_selection(owner, 0);

      browser_select_token(b, t, 0);

      owner = b;
    }

    /* Try moving the selection */

    if (!browser_move_selection(owner, fe_lastkey))
    {
      /* The selection failed; proceed in the same manner as form_next_field */

      if (browser_check_visible(owner, NULL, t) && ancestor->selected != t)
      {
        browser_select_token(owner, t, 0);

        return form_click_field(owner, t, 2, 0, 0);
      }
      else
      {
        /* Not doing anything, so mark the keypress as unused */

        if (used) *used = 0;
      }
    }

    /* If the selection did move, may need to turn off the caret */

    else if (ancestor->selected != t && !form_token_cursor_editable(owner, ancestor->selected)) wimp_set_caret_position(ancestor->window_handle, -1, 0, 0, -1, -1);
  }
  else
  {
    /* If there is a previous item, and it's not the same as the */
    /* current one, move into it.                                */

    if (previous && previous != fe_token)
    {
      if (choices.keyboard_ctrl) browser_select_token(fe_browser, previous, 0); /* If moving by Tab, make sure the selection keeps up */
      return form_click_field(fe_browser, previous, 2, 0, 0);
    }
  }

  return NULL;
}

/*************************************************/
/* form_cursor_right()                           */
/*                                               */
/* Handles moving the cursor (caret) right in a  */
/* keyboard editable form field.                 */
/*                                               */
/* Parameters: Pointer to an int, into which 0   */
/*             may be written if the keypress    */
/*             ends up in fact not being used    */
/*             for some reason.                  */
/*                                               */
/* Assumes:    The int pointer may be NULL.      */
/*************************************************/

static _kernel_oserror * form_cursor_right(int * used)
{
  /* Proceed only if there's something being edited */

  if (fe_token && fe_browser)
  {
    const char * p;

    p = form_get_field_text(fe_browser, fe_token);

    /* If we aren't on the terminator already, move forward */

    if (p[fe_index])
    {
      fe_index++;
      form_give_focus(fe_browser);

      return form_autoscroll(fe_browser);
    }
    else if (choices.keyboard_ctrl)
    {
      /* For keyboard control, jump to the next field */

      return form_next_field(0, used);
    }
  }

  /* Doing nothing, so mark the keypress as unused */

  else
  {
    if (used) *used = 0;
  }

  return NULL;
}

/*************************************************/
/* form_cursor_bottom()                          */
/*                                               */
/* Moves the caret to the bottom right of a      */
/* writable forms item (text area or single line */
/* text fields).                                 */
/*************************************************/

static _kernel_oserror * form_cursor_bottom(void)
{
  /* Can only proceed if something's being edited */

  if (fe_token && fe_browser)
  {
    const char * p;

    p = form_get_field_text(fe_browser, fe_token);

    /* Only move if not already on the last character */

    if (p[fe_index])
    {
      fe_index = strlen(p);
      form_give_focus(fe_browser);
      form_autoscroll(fe_browser);
    }
  }

  return NULL;
}

/*************************************************/
/* form_cursor_eol()                             */
/*                                               */
/* Move the cursor to the end of the line in a   */
/* writable forms element.                       */
/*************************************************/

static _kernel_oserror * form_cursor_eol(void)
{
  if (fe_token && fe_browser)
  {
    const char * p;
    int          o;

    p = form_get_field_text(fe_browser, fe_token);
    o = fe_index;

    /* Keep going forward to a null or newline character */
    /* marking the end of the line                       */

    while (p[fe_index] && p[fe_index] != '\n') fe_index++;

    /* If the index has changed, do the appropriate */
    /* updates on the page.                         */

    if (o != fe_index)
    {
      form_give_focus(fe_browser);
      form_autoscroll(fe_browser);
    }
  }
  return NULL;
}

/*************************************************/
/* form_cursor_bol()                             */
/*                                               */
/* Move the cursor to the start of the line in a */
/* writable forms element.                       */
/*************************************************/

static _kernel_oserror * form_cursor_bol(void)
{
  if (fe_token && fe_browser && fe_index)
  {
    const char * p;
    int          o;

    p = form_get_field_text(fe_browser, fe_token);
    o = fe_index;

    /* Keep backing up until reaching the zero */
    /* index position, or for text areas, a    */
    /* newline character marking the end of a  */
    /* previous line.                          */

    do
    {
      fe_index--;
    }
    while (fe_index >= 0 && p[fe_index] != '\n');

    fe_index++;

    /* If the index has changed, do the appropriate */
    /* updates on the page.                         */

    if (o != fe_index)
    {
      form_give_focus(fe_browser);
      form_autoscroll(fe_browser);
    }
  }

  return NULL;
}

/*************************************************/
/* form_cursor_left()                            */
/*                                               */
/* Handles moving the cursor (caret) left in a   */
/* keyboard editable form field.                 */
/*                                               */
/* Parameters: Pointer to an int, into which 0   */
/*             may be written if the keypress    */
/*             ends up in fact not being used    */
/*             for some reason.                  */
/*                                               */
/* Assumes:    The int pointer may be NULL.      */
/*************************************************/

static _kernel_oserror * form_cursor_left(int * used)
{
  /* Proceed only if there's something being edited */

  if (fe_token && fe_browser)
  {
    /* Can only go back if we aren't already at the start */

    if (fe_index)
    {
      fe_index--;
      form_give_focus(fe_browser);

      return form_autoscroll(fe_browser);
    }
    else if (choices.keyboard_ctrl)
    {
      /* For keyboard control, jump to the previous field */

      return form_previous_field(used);
    }
  }

  /* Doing nothing, so mark the keypress as unused */

  else
  {
    if (used) *used = 0;
  }

  return NULL;
}

/*************************************************/
/* form_cursor_top()                             */
/*                                               */
/* Moves the caret to the top left of a writable */
/* forms item (text area or single line item).   */
/*************************************************/

static _kernel_oserror * form_cursor_top(void)
{
  /* Proceed only if there's something being edited */
  /* and the caret isn't already at the start       */

  if (fe_token && fe_browser && fe_index)
  {
    fe_index = 0;
    form_give_focus(fe_browser);
    form_autoscroll(fe_browser);
  }

  return NULL;
}

/*************************************************/
/* form_cursor_y()                               */
/*                                               */
/* Handles moving the caret up or down between   */
/* editable form elements. If overall keyboard   */
/* control is enabled, this may jump out of the  */
/* form and select objects on the page.          */
/*                                               */
/* Parameters: Direction; negative for up,       */
/*             positive for down, with the       */
/*             number of lines to move found     */
/*             based on the magnitude of the     */
/*             number. For single line items,    */
/*             only the sign is of interest; for */
/*             multiple line items, 1 means '1   */
/*             line', 2 means 'page up/down'.    */
/*                                               */
/*             Pointer to an int, into which 0   */
/*             may be written if the keypress    */
/*             ends up in fact not being used    */
/*             for some reason.                  */
/*                                               */
/* Assumes:    The int pointer may be NULL.      */
/*************************************************/

static _kernel_oserror * form_cursor_y(int dir, int * used)
{
  /* Can only move if we're editing some form, and a direction */
  /* has been given.                                           */

  if (fe_token && fe_browser && dir)
  {
    const char * p;
    int          o, x, y, fh;
    HStream    * tp;

    #ifndef ARROWS_MOVE_OUT

      if (choices.keyboard_ctrl)
      {

    #endif

        /* For single line items, just look at the relevant direction to move */

        if (fe_single && dir < 0) return form_previous_field(used);
        if (fe_single && dir > 0) return form_next_field(0, used);

    #ifndef ARROWS_MOVE_OUT

      }
      else
      {
        if (fe_single && dir != 0) return NULL;
      }

    #endif

    /* For multiline items (e.g. text areas), get need to move up or */
    /* down within the element.                                      */

    p  = form_get_field_text(fe_browser, fe_token);
    tp = fe_token;
    fh = fm_find_token_font(fe_browser, tp, 0);

    /* Get the caret position inside the element */

    form_textarea_caretpos(p, fh, fe_index, &x, &y);

    /* 'o' holds the index into the string for whatever */
    /* line of text the caret is currently in.          */

    o = fe_index;

    /* Work out where to move to */

    if (dir == 1 || dir == -1) y += dir;
    else if (dir)
    {
      int rows = tp->rows;

      if (rows < 2) rows = 2;

      if (dir == 2) y += rows;
      else          y -= rows;

      if (y < 0) y = 0;
    }

    #ifndef ARROWS_MOVE_OUT

      if (choices.keyboard_ctrl)
      {

    #endif

        /* If y < 0, we've dropped over the top of the element so move */
        /* up to the previous field.                                   */

        if (y < 0) return form_previous_field(used);

    #ifndef ARROWS_MOVE_OUT

      }
      else
      {
        if (y < 0) y = 0;
      }

      if (y < 0) y = 0;

    #endif

    /* If this really *is* a text area, then move the caret based on */
    /* the new 'y' coordinate. The call returns the new index into   */
    /* whatever text the caret is in, into fe_index.                 */

    if (tp->tagno == TAG_TEXTAREA) form_textarea_find_caret(p, fh, &fe_index, x, y);

    #ifndef ARROWS_MOVE_OUT

      if (choices.keyboard_ctrl)
      {

    #endif

        /* If the index into the string hasn't changed, then the caret */
        /* must have already been at the end of the text; in this      */
        /* case, move on to the next form element.                     */

        if (o == fe_index && dir > -2 && dir < 2) return form_next_field(0, used);

    #ifndef ARROWS_MOVE_OUT

      }

    #endif

    /* Otherwise, ensure the caret appearance and window scroll */
    /* positions are correct for the current caret position.    */

    form_give_focus(fe_browser);

    return form_autoscroll(fe_browser);
  }

  /* Doing nothing, so mark the keypress as unused */

  else
  {
    if (used) *used = 0;
  }

  return NULL;
}

/*************************************************/
/* form_insert_character()                       */
/*                                               */
/* Inserts a character into the currently        */
/* editing writable / text area.                 */
/*                                               */
/* Parameters: Key code, to give char to insert; */
/*                                               */
/*             Pointer to an int, in which 1 is  */
/*             written if the key press leads to */
/*             an insertion, else 0 is written.  */
/*                                               */
/* Assumes:    The int pointer may not be NULL.  */
/*************************************************/

static _kernel_oserror * form_insert_character(int key, int * used)
{
  char                  * p;
  HStream               * tp;
  _kernel_oserror       * e;
  WimpRedrawWindowBlock   r;
  int                     x, y, fh, lh, lb;

  /* Can we deal with the key? */

  if (
       (
         key < 32 &&
         key != 13
       )
       || key >  255
       || key == 127
       || !fe_token
       || !fe_browser
     )
  {
    /* No, so exit */

    *used = 0;
    return NULL;
  }

  /* Yes, so proceed */

  *used = 1;

  p     = form_get_field_text(fe_browser, fe_token);
  tp    = fe_token;

  /* If the key is 'return' and the writable has just */
  /* the one line, move to the next field.            */

  if (fe_single && key == 13) return form_next_field(1, NULL);

  /* Are we going to exceed the field maximum length? */

  if (HtmlINPUTmaxlength(fe_token) && strlen(p) >= HtmlINPUTmaxlength(fe_token)) return NULL;

  /* Otherwise, try to allocate space for the extra character */

  e = form_set_field_space(fe_browser, fe_token, strlen(p) + 2);
  if (e) return e;

  /* Make sure the field text and token pointers are up to date */

  p  = form_get_field_text(fe_browser, fe_token);
  tp = fe_token;

  /* Get details about the caret position for redraw later */

  form_input_box(fe_browser, fe_token, &r.visible_area, &lh, &lb, &fh);
  form_textarea_caretpos(p, fh, fe_index, &x, &y);

  /* Move the text above the caret position up and */
  /* insert the character (possibly modified)      */

  memmove(p + fe_index + 1, p + fe_index, strlen(p) - fe_index + 1);

  r.visible_area.ymax -= (y - fe_yscroll) * lh + 4;

  /* Alter code 13 to code 10 for internal consistency */

  if (key == 13) p[fe_index] = 10;
  else
  {
    p[fe_index] = key;
    r.visible_area.ymin  = r.visible_area.ymax - lh;
    r.visible_area.xmin += x - fe_xscroll;
  }

  /* Update the redraw area and exit through the routine */
  /* that updates the cursor position by moving it right */

  browser_update(fe_browser, &r, 1, 0);

  return form_cursor_right(NULL);
}

/*************************************************/
/* form_delete_character()                       */
/*                                               */
/* Deletes a character from the currently        */
/* editing writable / text area, to the left or  */
/* if required to the right.                     */
/*                                               */
/* Parameters: 0 to delete to the left, else     */
/*             delete right.                     */
/*************************************************/

static _kernel_oserror * form_delete_character(int right)
{
  _kernel_oserror       * e;
  char                  * p, c;
  int                     x, y, fh, lh, lb;
  WimpRedrawWindowBlock   r;

  /* If deleting left but already on the left of the field, exit */

  if (!right && !fe_index) return NULL;

  /* To simplify things, the rest of the routine handles deleting */
  /* right - so for left deletion, just move the cursor left on   */
  /* character first.                                             */

  if (!right) form_cursor_left(NULL);

  /* Get the field text, and exit if the cursor is on a */
  /* null character - i.e. can't delete right if at the */
  /* end of the string.                                 */

  p = form_get_field_text(fe_browser, fe_token);
  if (!p[fe_index]) return NULL;

  /* Remember the character that is about to be deleted */
  /* and get various details about the caret position   */

  c = p[fe_index];
  form_input_box(fe_browser, fe_token, &r.visible_area, &lh, &lb, &fh);
  form_textarea_caretpos(p, fh, fe_index, &x, &y);

  /* Delete the character */

  memmove(p + fe_index, p + fe_index + 1, strlen(p) - fe_index);
  e = form_set_field_space(fe_browser, fe_token, strlen(p) + 1);
  if (e) return e;

  /* From the value of the deleted character work out, for */
  /* text areas, what updates need to be done              */

  r.visible_area.ymax -= (y - fe_yscroll) * lh + 4;
  if(c != '\n')
  {
    r.visible_area.ymin = r.visible_area.ymax - lh;
    r.visible_area.xmin+= x - fe_xscroll;
  }

  /* Finally, exit by redraw the window as required */

  return browser_update(fe_browser, &r, 1, 0);
}

/*************************************************/
/* form_delete_from_caret()                      */
/*                                               */
/* Deletes a chunk of the current line, from the */
/* caret position to its end.                    */
/*                                               */
/* Assumes:    That there is a currently editing */
/*             item, and it's a text area or     */
/*             single line writable.             */
/*************************************************/

static _kernel_oserror * form_delete_from_caret(void)
{
  _kernel_oserror       * e;
  char                  * p;
  int                     x, y, fh, lh, lb;
  int                     end, len;
  WimpRedrawWindowBlock   r;

  p = form_get_field_text(fe_browser, fe_token);
  if (!p) return NULL;

  /* Find out where the current line (or whole piece of text, */
  /* for a single line writable) ends.                        */

  len = strlen(p);
  if (!len) return NULL;

  if (!p[fe_index] || p[fe_index] == '\n') return NULL;

  end = fe_index;

  while (end <= len && p[end] && p[end] != '\n') end++;

  /* Remove an appropriate chunk of the string */

  memmove(p + fe_index, p + end, len - end + 1);

  e = form_set_field_space(fe_browser, fe_token, strlen(p) + 1);
  if (e) return e;

  /* Work out redraw details and exit through the redraw routine */

  form_input_box(fe_browser, fe_token, &r.visible_area, &lh, &lb, &fh);
  form_textarea_caretpos(p, fh, fe_index, &x, &y);

  r.visible_area.ymax -= (y - fe_yscroll) * lh + 4;
  r.visible_area.ymin  = r.visible_area.ymax - lh;
  r.visible_area.xmin += x - fe_xscroll;

  return browser_update(fe_browser, &r, 1, 0);
}

/*************************************************/
/* form_delete_line()                            */
/*                                               */
/* Clears the current line of the currently      */
/* editing text item.                            */
/*                                               */
/* Assumes:    That there is a currently editing */
/*             item, and it's a text area or     */
/*             single line writable.             */
/*************************************************/

static _kernel_oserror * form_delete_line(void)
{
  _kernel_oserror       * e;
  char                  * p;
  int                     x, y, fh, lh, lb;
  int                     index, start, end, len;
  WimpRedrawWindowBlock   r;

  p = form_get_field_text(fe_browser, fe_token);
  if (!p) return NULL;

  /* Find out where the current line (or whole piece of text, */
  /* for a single line writable) starts and ends.             */

  len = strlen(p);
  if (!len) return NULL;

  /* If we're on the end of a line, step back one. If this */
  /* leaves fe_index pointing to an end of line character  */
  /* still, then the line was already empty so return.     */

  index = fe_index;

  if (!p[index] || p[index] == '\n') index--;
  if (index < 0) index = 0;
  if (!p[index] || p[index] == '\n') return NULL;

  /* Otherwise, look for end of line characters to either side */
  /* of the adjusted fe_index.                                 */

  start = end = index;

  while (start > 0  && p[start] && p[start] != '\n') start--;
  while (end <= len && p[end]   && p[end]   != '\n') end++;

  /* Set the new index and remove an appropriate chunk of the string */

  if (p[start] == '\n') fe_index = start + 1;
  else                  fe_index = start;

  memmove(p + fe_index, p + end, len - end + 1);

  e = form_set_field_space(fe_browser, fe_token, strlen(p) + 1);
  if (e) return e;

  /* Move the caret */

  form_give_focus(fe_browser);
  form_autoscroll(fe_browser);

  /* Work out redraw details and exit through the redraw routine */

  form_input_box(fe_browser, fe_token, &r.visible_area, &lh, &lb, &fh);
  form_textarea_caretpos(p, fh, fe_index, &x, &y);

  r.visible_area.ymax -= (y - fe_yscroll) * lh + 4;
  r.visible_area.ymin  = r.visible_area.ymax - lh;
  r.visible_area.xmin += x - fe_xscroll;

  return browser_update(fe_browser, &r, 1, 0);
}

/*************************************************/
/* form_process_key()                            */
/*                                               */
/* Processes a given key stroke in the context   */
/* of forms handling.                            */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the forms;            */
/*                                               */
/*             Pointer to an int in which the    */
/*             Wimp key code should be stored.   */
/*                                               */
/* Returns:    The key code will be set to zero  */
/*             if the key is processed by the    */
/*             function (so that a caller can    */
/*             know if the keypress should be    */
/*             passed on further).               */
/*************************************************/

_kernel_oserror * form_process_key(browser_data * b, int * key)
{
  _kernel_oserror * e    = NULL;
  int               used = 0;

  if (fe_browser && b == fe_browser && fe_token)
  {
    used = 1;

    fe_lastkey = *key;

    switch (*key)
    {
      case 8:   /* Ctl+H */        e = form_delete_character(0);           break;
      case 21:  /* Ctl+U */        e = form_delete_line();                 break;
      case 27:  /* Escape */       e = form_cancel_edit(b);                break;
      case 30:  /* Home */         e = form_cursor_top();                  break;
      case 127: /* Backspace */    e = form_delete_character(0);           break;
      case akbd_CopyK + akbd_Ctl:  e = form_delete_from_caret();           break;
      case akbd_RightK:            e = form_cursor_right(&used);           break;
      case akbd_RightK + akbd_Ctl: e = form_cursor_eol();                  break;
      case akbd_LeftK:             e = form_cursor_left(&used);            break;
      case akbd_LeftK + akbd_Ctl:  e = form_cursor_bol();                  break;
      case akbd_TabK:              e = form_next_field(0, &used);          break;
      case akbd_DownK:             e = form_cursor_y(1, &used);            break;
      case akbd_DownK + akbd_Sh:   e = form_cursor_y(2, &used);            break;
      case akbd_TabK + akbd_Sh:    e = form_previous_field(&used);         break;
      case akbd_UpK:               e = form_cursor_y(-1, &used);           break;
      case akbd_UpK + akbd_Sh:     e = form_cursor_y(-2, &used);           break;
      case akbd_CopyK:             e = form_delete_character(1);           break;
      case akbd_UpK + akbd_Ctl:    e = form_cursor_top();                  break;
      case akbd_DownK + akbd_Ctl:  e = form_cursor_bottom();               break;

      default:                     e = form_insert_character(*key, &used); break;
    }
  }

  if (used) *key = 0;

  return e;
}

/*************************************************/
/* form_textarea_redraw()                        */
/*                                               */
/* Redraws the text in a text area object.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the text area;        */
/*                                               */
/*             Pointer to the token representing */
/*             the text area;                    */
/*                                               */
/*             Pointer to a BBox holding the     */
/*             bounding box of the text area, in */
/*             screen coords (so OS units);      */
/*                                               */
/*             Pointer to a BBox holding the     */
/*             current graphics window (usually  */
/*             from a call to the Wimp to redraw */
/*             something - remember that this    */
/*             can't be read during a printing   */
/*             routine as VDU variables may not  */
/*             be read during printing);         */
/*                                               */
/*             A font handle for the font to use */
/*             for the redraw;                   */
/*                                               */
/*             1 if the text area has multiple   */
/*             lines, 0 to render all text on    */
/*             one line only;                    */
/*                                               */
/*             1 if the text area represents a   */
/*             password object (show the text as */
/*             a line of *s, instead of the      */
/*             actual chars), else 0.            */
/*************************************************/

void form_textarea_redraw(browser_data * b, HStream * token, BBox *box, BBox *gw, int fh,
                          int multiline, int password)
{
  int    nxmin, nymin, nxmax, nymax;
  int    lh, lb, l;
  int    xs, ys, y;
  BBox   area, fontbox;
  char   c;
  char * p, *t;
  char   passcode[] = FE_PassCode;

  /* Set 'area' to hold the bounding box of the text area */

  area = *box;

  /* Bring the coordinates in to account for the text area's border */

  area.xmin += 8;
  area.ymin += 8;
  area.xmax -= 8;
  area.ymax -= 8;

  /* Get nxmin, nxmax, nymin anx nymax to hold the coordinates of */
  /* a BBox formed by the intersection of the text area and the   */
  /* current graphics rectangle.                                  */

  nxmin = area.xmin > gw->xmin ? area.xmin : gw->xmin;
  nxmax = area.xmax < gw->xmax ? area.xmax : gw->xmax;
  nymin = area.ymin > gw->ymin ? area.ymin : gw->ymin;
  nymax = area.ymax < gw->ymax ? area.ymax : gw->ymax;

  /* Conver to pixels ready to set a graphics window */

  nxmin &= ~(wimpt_dx() - 1);
  nymin &= ~(wimpt_dy() - 1);
  nxmax &= ~(wimpt_dx() - 1);
  nymax &= ~(wimpt_dy() - 1);

  /* If the minimum coords are less than / equal to the maximums, */
  /* there is nothing to redraw.                                  */

  if (nxmin >= nxmax || nymin >= nymax) return;

  /* Otherwise, set the graphics rectangle. */

  bbc_gwindow(nxmin, nymin, nxmax - 1, nymax - 1);

  /* Get the height of an individual line, based on the height of */
  /* the font to be used plus vertical spacing considerations.    */

  fm_font_box(fh, &fontbox);

  form_get_linesize(&fontbox, &lh, &lb);

  /* Get the text for the text area */

  p = form_get_field_text(b, token);

  /* Only proceed if there's text to draw */

  if (p)
  {
    xs = ys = 0;

    /* If this is an element that the forms library is already handling */
    /* because it has the input focus, then use the scroll offset that  */
    /* is recorded for the object.                                      */

    if (b == fe_browser && token == fe_token)
    {
      xs = fe_xscroll;
      ys = fe_yscroll * lh;
    }

    y = area.ymax + ys;

    do
    {
      /* The text may be terminated by newlines or null bytes; if */
      /* a newline, this implies a split point for a text area.   */

      t         = strchr(p, '\n');
      if (!t) t = strchr(p, 0);

      /* Ensure for C's purposes (sigh) that there is a zero terminator */
      /* at the end of the chunk to plot.                               */

       c = *t;
      *t = 0;

      l = strlen(p);

      /* If the line is in the visible region, plot it. Note the use of */
      /* the FE_PassCode line of asterisks - if a password is being     */
      /* entered, as many of these as there are characters in the       */
      /* real string will be displayed instead of that string.          */

      if (y - lh < area.ymax) fm_putsl(fh,
                                       area.xmin - xs,
                                       y - lh + lb,
                                       password ? passcode : p,
                                       l,
                                       1,
                                       0);
      y -= lh;

      /* Restore the character that was altered to a null byte earlier */

      *t = c;

      /* Advance the string pointer past the text just plotted */

      p += l;

      /* If we're on a newline, there must be another chunk of string to */
      /* plot, so move the pointer past it.                              */

      if (*p == '\n') p++;

      /* Keep looping whilst in the visible area and there's data left to   */
      /* plot, provided that this was flagged as a multiline object redraw. */
    }
    while (y>area.ymin && multiline && *p);
  }

  /* Restore the previous graphics rectangle */

  bbc_gwindow(gw->xmin, gw->ymin, gw->xmax - 1, gw->ymax - 1);
}

/*************************************************/
/* form_select_menu_event()                      */
/*                                               */
/* Handles menu selections in SELECT fields.     */
/*                                               */
/* Parameters: Pointer to a WimpPollBlock struct */
/*             describing the selection event.   */
/*************************************************/

void form_select_menu_event(WimpPollBlock * e)
{
  /* Only proceed if this is definitely from a form-derived menu */

  if (menusrc == Menu_Form && fe_menu && fe_mbrowser && fe_mtoken)
  {
    int                       n, o;
    HStream                 * tp;
    form_field              * fp;
    WimpGetPointerInfoBlock   m;
    char                      select[Limits_SelectItems + 1];

    /* Find the form record, exit if it isn't a SELECT field */

    fp = form_find_record(fe_mbrowser, fe_mtoken, 0);
    if (fp->header.type != form_select) return;

    /* Check that the selected item is within the apparent range */
    /* of items that the menu should have                        */

    tp = fe_mtoken;
    n  = HtmlSELECToptions(tp)[0];
    o  = e->menu_selection[0];

    if (o >= n) return;

    /* After this, 'select' will hold an array of FORM_SELCHAR and */
    /* FORM_UNSELCHAR characters describing which menu items are   */
    /* selected or deselected.                                     */

    strcpy(select, fp->value.select.selection);

    /* For menus which can have multiple items selected, just invert */
    /* the selection type. Otherwise, need to make sure that any     */
    /* other selected item is cleared.                               */

    if (HtmlSELECTmultiple(tp))
    {
      select[o] = (select[o] == FORM_SELCHAR) ? FORM_UNSELCHAR : FORM_SELCHAR;
    }
    else
    {
      int i, c;

      for (i = 0; i < n; i++)
      {
        if (i == o) select[i] = (select[i] == FORM_SELCHAR) ? FORM_UNSELCHAR : FORM_SELCHAR;
        else select[i] = FORM_UNSELCHAR;
      }

      c = 0;

      for (i = 0; i < n; i++)
      {
        if (select[i] == FORM_SELCHAR) c++;
      }

      if (!c) select[o] = FORM_SELCHAR;
    }

    /* Update the browser with the new selection details */

    form_put_field(fe_mbrowser, fe_mtoken, select, 1);

    /* If Adjust was used, reopen the menu */

    wimp_get_pointer_info(&m);

    if (m.button_state & 3) form_create_menu(fe_mbrowser, fe_mtoken);
  }
}

/*************************************************/
/* form_check_caret()                            */
/*                                               */
/* Ensure the caret is correctly located in a    */
/* given browser window.                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window. This may  */
/*             be NULL to ensure it is correct   */
/*             in the currently editing form,    */
/*             else it will check that the given */
/*             browser holds the currently       */
/*             editing form and only check the   */
/*             caret if so.                      */
/*************************************************/

void form_check_caret(browser_data * b)
{
  /* Ensure we were given the browser holding the currently */
  /* editing form, or no browser at all, before trying to   */
  /* position the caret.                                    */

  if (
       (
         !b              ||
         b == fe_browser
       )
       && fe_browser
       && fe_token
     )
  {
    /* If the caret can't be placed, finish editing in this form */

    if (!form_give_focus(fe_browser)) form_finish_edit(fe_browser);
  }
}

/*************************************************/
/* form_caret_may_need_moving()                  */
/*                                               */
/* Whereas form_check_caret will finish editing  */
/* in a window if the caret can't be placed,     */
/* this function will continue to try placing it */
/* as long as the given browser matches the      */
/* editing browser. This was designed for after  */
/* the reformatter had ben called, to try and    */
/* keep the caret in the window where possible.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the editing token.    */
/*             If there is no form edit going on */
/*             in this window, the function will */
/*             do nothing; otherwise, if there's */
/*             an editing token too, it'll make  */
/*             sure the caret (if any) is        */
/*             correctly positioned.             */
/*************************************************/

void form_caret_may_need_moving(browser_data * b)
{
  if (
       fe_browser        &&
       fe_token          &&
       b == fe_browser
     )
     form_give_focus(b);
}