/* 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   : Browser.c                              */
/*                                                 */
/* Purpose: Browser window services.               */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 15-Mar-97: Created from Windows.c.     */
/***************************************************/

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

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

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

#include "toolbox.h"
#include "window.h"

#include "Dialler.h"
#include "NestWimp.h"

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

#include "CSIM.h"
#include "Fetch.h" /* (Which itself includes URLstat.h) */
#include "Frames.h"
#include "Forms.h"
#include "History.h"
#include "Images.h"
#include "Mouse.h"
#include "PrintStyle.h"
#include "Redraw.h"
#include "Reformat.h"
#include "TokenUtils.h"
#include "Toolbars.h"

#include "Browser.h"

/* Static function prototypes */

static HStream         * browser_find_selectable_top_r (browser_data * b, reformat_cell * cell, HStream ** current, int y_origin, WimpGetWindowStateBlock * s);
static HStream         * browser_find_selectable_bot_r (browser_data * b, reformat_cell * cell, HStream ** current, int y_origin, WimpGetWindowStateBlock * s);
static int               browser_navigate_map          (browser_data * b, int key);

static _kernel_oserror * browser_redraw_border         (browser_data * b, HStream * token);
static HStream         * browser_get_pointer_token_r   (browser_data * b, reformat_cell * cell, WimpGetPointerInfoBlock * p, WimpGetWindowStateBlock * state, int * ox, int * oy);
static int               browser_top_line_r            (browser_data * b, reformat_cell * cell, reformat_cell ** ret_cell, WimpGetWindowStateBlock * s, int fully_visible);
static int               browser_bottom_line_r         (browser_data * b, reformat_cell * cell, reformat_cell ** ret_cell, WimpGetWindowStateBlock * s, int fully_visible);
static _kernel_oserror * browser_update_token_r        (browser_data * b, reformat_cell * cell, HStream * token, int first, int last, int chunk, int base_x, int base_y, int noback, HStream * nocontent);

static _kernel_oserror * browser_set_look_r            (browser_data * b, ObjectId source, int underline_links, int use_source_cols, int show_foreground, int show_background);

/* Local statics */

static ObjectId pointer_is_over = 0; /* Object that the pointer is over, if any */

/*************************************************/
/* browser_scroll_page_v()                       */
/*                                               */
/* Scrolls a page vertically by a given amount.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the page;             */
/*                                               */
/*             A WimpOpenWindowBlock pointer,    */
/*             holding the window's current      */
/*             details (e.g. visible area) or    */
/*             NULL if this is not known;        */
/*                                               */
/*             1 to scroll up, else down;        */
/*                                               */
/*             1 to page up/down, else 0;        */
/*                                               */
/*             1 to move one line, else 0;       */
/*                                               */
/*             An amount to scroll by, ignored   */
/*             unless the above two parameters   */
/*             are zero;                         */
/*                                               */
/*             Pointer to an int, in which 1 is  */
/*             written if the window didn't      */
/*             shift scroll position as it was   */
/*             at the limit of its work area,    */
/*             else 0 is written.                */
/*************************************************/

_kernel_oserror * browser_scroll_page_v(browser_data * b, WimpOpenWindowBlock * o, int dir, int page, int line, int amount, int * limit)
{
  int                       scrollby;
  WimpGetWindowStateBlock   open;
  _kernel_oserror         * e;

  /* Work out the WimpOpenWindowBlock if NULL was passed in */

  if (!o)
  {
    open.window_handle = b->window_handle;

    e = wimp_get_window_state(&open);
    if (e) return e;

    o = (WimpOpenWindowBlock *) &open;
  }

  if (limit)
  {
    /* If required, see if we're at the limit of the scroll position */

    BBox extent;
    int  scrollmax;

    e = window_get_extent(0, b->self_id, &extent);
    if (e) return e;

    scrollmax = !dir ? extent.ymin + (o->visible_area.ymax - o->visible_area.ymin) : 0;

    if (scrollmax == o->yscroll) *limit = 1;
    else                         *limit = 0;
  }

  /* Work out how much to scroll by */

  if      (page) scrollby = o->visible_area.ymax - o->visible_area.ymin - toolbars_url_height(b) - toolbars_button_height(b) - toolbars_status_height(b) - wimpt_dy();
  else if (line) scrollby = 42;
  else           scrollby = amount;

  /* If greater than zero, move the page */

  if (scrollby > 0)
  {
    if (!dir) scrollby = -scrollby;

    o->yscroll += scrollby;

    return wimp_open_window(o);
  }

  return NULL;
}

/*************************************************/
/* browser_scroll_page_h()                       */
/*                                               */
/* Scrolls a page horizontally a given amount.   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the page;             */
/*                                               */
/*             A WimpOpenWindowBlock pointer,    */
/*             holding the window's current      */
/*             details (e.g. visible area) or    */
/*             NULL if this is not known;        */
/*                                               */
/*             1 to scroll left, else right;     */
/*                                               */
/*             1 to page left/right, else 0;     */
/*                                               */
/*             1 to move one line, else 0;       */
/*                                               */
/*             An amount to scroll by, ignored   */
/*             unless the above two parameters   */
/*             are zero;                         */
/*                                               */
/*             Pointer to an int, in which 1 is  */
/*             written if the window didn't      */
/*             shift scroll position as it was   */
/*             at the limit of its work area,    */
/*             else 0 is written.                */
/*************************************************/

_kernel_oserror * browser_scroll_page_h(browser_data * b, WimpOpenWindowBlock * o, int dir, int page, int line, int amount, int * limit)
{
  int                       scrollby;
  WimpGetWindowStateBlock   open;
  _kernel_oserror         * e;

  /* Work out the WimpOpenWindowBlock if NULL was passed in */

  if (!o)
  {
    open.window_handle = b->window_handle;

    e = wimp_get_window_state(&open);
    if (e) return e;

    o = (WimpOpenWindowBlock *) &open;
  }

  if (limit)
  {
    /* If required, see if we're at the limit of the scroll position */

    BBox extent;
    int  scrollmax;

    e = window_get_extent(0, b->self_id, &extent);
    if (e) return e;

    scrollmax = !dir ? extent.xmin - (o->visible_area.xmax - o->visible_area.xmin) : 0;

    if (scrollmax == o->xscroll) *limit = 1;
    else                         *limit = 0;
  }

  /* Work out how much to scroll by */

  if      (page) scrollby = o->visible_area.ymax - o->visible_area.ymin;
  else if (line) scrollby = 42;
  else           scrollby = amount;

  /* If greater than zero, move the page */

  if (scrollby > 0)
  {
    if (dir) scrollby = -scrollby;

    o->xscroll += scrollby;

    return wimp_open_window(o);
  }

  return NULL;
}

/*************************************************/
/* browser_scroll_page_by_key()                  */
/*                                               */
/* Scrolls a page according to a given key code. */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the page;             */
/*                                               */
/*             Key code from the Wimp (to define */
/*             left / right, page movement or    */
/*             line movement, etc.);             */
/*                                               */
/*             Pointer to an int, in which 1 is  */
/*             written if the window didn't      */
/*             shift scroll position as it was   */
/*             at the limit of its work area,    */
/*             else 0 is written (this only      */
/*             applies for vertical scrolling -  */
/*             the written value will always be  */
/*             0 if moving horizontally).        */
/*************************************************/

_kernel_oserror * browser_scroll_page_by_key(browser_data * b, int key, int * limit)
{
  int                       page = 0, line = 0, dir = 0;
  WimpGetWindowStateBlock   s;
  _kernel_oserror         * e;

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

  if (
       key == akbd_UpK                         ||
       key == akbd_PageUpK                     ||
       key == akbd_LeftK                       ||
       key == akbd_HomeK                       ||
       key == akbd_UpK   + akbd_Ctl            ||
       key == akbd_UpK   + akbd_Ctl + akbd_Sh  ||
       key == akbd_LeftK + akbd_Ctl            ||
       key == akbd_LeftK + akbd_Ctl + akbd_Sh
     )
     dir = 1;

  if (
       key == akbd_PageUpK                     ||
       key == akbd_PageDownK
     )
     page = 1;

  if (
       key == akbd_UpK                         ||
       key == akbd_DownK                       ||
       key == akbd_LeftK                       ||
       key == akbd_RightK                      ||
       key == akbd_UpK    + akbd_Ctl + akbd_Sh ||
       key == akbd_DownK  + akbd_Ctl + akbd_Sh ||
       key == akbd_LeftK  + akbd_Ctl + akbd_Sh ||
       key == akbd_RightK + akbd_Ctl + akbd_Sh
     )
     line = 1;

  if (
       key == akbd_LeftK                       ||
       key == akbd_RightK                      ||
       key == akbd_LeftK  + akbd_Ctl           ||
       key == akbd_LeftK  + akbd_Ctl + akbd_Sh ||
       key == akbd_RightK + akbd_Ctl           ||
       key == akbd_RightK + akbd_Ctl + akbd_Sh
     )
  {
    /* For left/right key presses, want to make sure as */
    /* the caller that the input focus is not in a      */
    /* writable icon, or if it is, the effect of having */
    /* the page scroll as the caret tries to move has   */
    /* been taken into account (e.g. the caret is known */
    /* to be at the start/end of the writable's text).  */

    if (limit) *limit = 0;

    return browser_scroll_page_h(b,
                                 (WimpOpenWindowBlock *) &s,
                                 dir,
                                 page,
                                 line,
                                 (!(page + line) ? 0x1000000 : 0),
                                 NULL);
  }
  else
  {
    e = browser_scroll_page_v(b,
                              (WimpOpenWindowBlock *) &s,
                              dir,
                              page,
                              line,
                              (!(page + line) ? 0x1000000 : 0),
                              limit);
    if (e) return e;

    /* For Home, make sure the page is scrolled to the far left */

    if (!page && !line && dir) return browser_scroll_page_h(b,
                                                            (WimpOpenWindowBlock *) &s,
                                                            1,
                                                            0,
                                                            0,
                                                            0x1000000,
                                                            NULL);

// This seems generally undesirable...
//
//    /* For End, make sure the page is scrolled to the far right */
//
//    if (!page && !line && !dir) return browser_scroll_page_h(b,
//                                                             (WimpOpenWindowBlock *) &s,
//                                                             0,
//                                                             0,
//                                                             0,
//                                                             0x1000000,
//                                                             NULL);

  }

  return NULL;
}

/*************************************************/
/* browser_find_first_selectable()               */
/*                                               */
/* Examines the visible area of a given browser  */
/* window to see if a selectable token is        */
/* present in it, and returns the address of the */
/* token if so. The token returned may not be    */
/* fully visible - the caller must use           */
/* browser_check_visible on the returned token   */
/* if the token must be fully visible.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             A WimpGetWindowStateBlock pointer */
/*             holding details of the window     */
/*             (if NULL because the caller       */
/*             doesn't have this information to  */
/*             hand, the function will work it   */
/*             out);                             */
/*                                               */
/*             Direction to search in; 1 for     */
/*             bottom right to top left, 0 for   */
/*             top left to bottom right.         */
/*                                               */
/* Returns:    Pointer to the token to select,   */
/*             or NULL if none are visible.      */
/*************************************************/

HStream * browser_find_first_selectable(browser_data * b, WimpGetWindowStateBlock * s, int dir)
{
  HStream                 * token_null = NULL;
  WimpGetWindowStateBlock   state;

  if (!b) return NULL;

  /* Get the window state if it wasn't given */

  if (!s)
  {
    state.window_handle = b->window_handle;

    if (wimp_get_window_state(&state)) return NULL;

    s = &state;
  }

  /* Find the selectable, marking that nothing is to be */
  /* skipped (*(&token_null) = NULL).                   */

  if (!dir) return browser_find_selectable_top_r(b, b->cell, &token_null, 0, s);
  else      return browser_find_selectable_bot_r(b, b->cell, &token_null, 0, s);
}

/*************************************************/
/* browser_find_another_selectable()             */
/*                                               */
/* Takes a given selected token, and finds the   */
/* previous or next selectable, optionally       */
/* constraining the search to moving to a new    */
/* line, rather than allowing to stay on the     */
/* same one.                                     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             Pointer to the currently selected */
/*             token;                            */
/*                                               */
/*             Direction to search in; 1 for     */
/*             bottom right to top left, 0 for   */
/*             top left to bottom right;         */
/*                                               */
/*             Constraint (1 to allow tokens on  */
/*             the same line, else must go to a  */
/*             new line).                        */
/*                                               */
/* Returns:    Pointer to the new token that     */
/*             should be selected, or NULL for   */
/*             none.                             */
/*************************************************/

HStream * browser_find_another_selectable(browser_data * b, HStream * current, int dir, int constrain)
{
  WimpGetWindowStateBlock   state;
  HStream                 * current_rec = current;

  /* Get the browser window's state */

  if (!b) return NULL;

  state.window_handle = b->window_handle;

  if (wimp_get_window_state(&state)) return NULL;

  /* Ensure that the selected token in current_rec is */
  /* at the top of the tokens representing the same   */
  /* selectable.                                      */

  tokenutils_anchor_range(b, current_rec, &current_rec, NULL);

  /* Call the relevant function to find the item */

  if (!dir) return browser_find_selectable_top_r(b, b->cell, &current_rec, 0, &state);
  else      return browser_find_selectable_bot_r(b, b->cell, &current_rec, 0, &state);
}

/*************************************************/
/* browser_find_selectable_top_r()               */
/*                                               */
/* Recursive back-end to the 'from top-left'     */
/* call to browser_find_another_selectable and   */
/* browser_find_first_selectable.                */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             holding the lines to scan;        */
/*                                               */
/*             Pointer to a pointer to the       */
/*             currently selected token (this    */
/*             will be written to) - if there is */
/*             no such token, this should point  */
/*             to a word holding NULL;           */
/*                                               */
/*             y origin of that cell, in OS      */
/*             units from the top left of the    */
/*             whole page;                       */
/*                                               */
/*             A WimpGetWindowStateBlock pointer */
/*             holding details of the window;    */
/*                                               */
/* Returns:    As browser_find_first_selectable. */
/*************************************************/

HStream * browser_find_selectable_top_r(browser_data * b, reformat_cell * cell, HStream ** current, int y_origin, WimpGetWindowStateBlock * s)
{
  HStream * t    = NULL;
  HStream * last = NULL;
  int       exit;
  int       ytop, ybot, htop, hbot;
  int       line, chunk, chunkmax;

  if (!cell || !cell->nlines || !cell->ldata || !cell->cdata) return NULL;

  #ifdef TRACE
    if (tl & (1u<<22)) Printf("browser_find_selectable_top_r: Proceeding for %p, cell %p\n", b, cell);
  #endif

  /* Work out where the visible page region starts and ends */

  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();

  ytop = s->yscroll - htop;
  ybot = s->yscroll - (s->visible_area.ymax - s->visible_area.ymin) + hbot;

  #ifdef TRACE
    if (tl & (1u<<22)) Printf("browser_find_selectable_top_r: ytop, ybot: -%d, -%d\n",-ytop,-ybot);
  #endif

  /* Go through the cell's chunks, getting tokens for as long */
  /* as the chunk holding lines are in the visible area.      */

  line = 0;
  exit = 0;

  /* Find the line visible at the top of the window. This can be slow if */
  /* there are a lot of lines, so use a Cunning Plan - divide the window */
  /* extent (take the y coordinate of the last line) by the number of    */
  /* lines to get the average line height, use this and the ytop coord   */
  /* to have a good guess at the line, then move a short distance up or  */
  /* down to get the actual correct line.                                */
  /*                                                                     */
  /* Of course, this only works for the main line list. It could be      */
  /* adjusted for tables, but there's no time to do it right now...      */
  /* Tables are rarely large enough to need it anyway.                   */

  if (cell != b->cell)
  {
    while (line < cell->nlines && y_origin + cell->ldata[line].y > ytop) line ++;
  }
  else
  {
    int extent  = cell->ldata[cell->nlines - 1].y;
    int average = extent / cell->nlines;
    int startat;

    if (average) startat = ytop / average;
    else         startat =0;

    if (startat < 0)             startat = 0;
    if (startat >= cell->nlines) startat = cell->nlines - 1;

    if (y_origin + cell->ldata[startat].y > ytop)
    {
      int lastline = -1;

      line = startat;
      while (line < cell->nlines && y_origin + cell->ldata[line].y > ytop) lastline = line, line ++;

      if (lastline >= 0) line = lastline;
      else               line = startat;
    }
    else
    {
      line = startat;
      while (line >= 0 && y_origin + cell->ldata[line].y <= ytop) line --;

      if (line < 0) line = 0;
    }
  }

  /* Proceed until the line visible at the bottom */

  while (line < cell->nlines && y_origin + cell->ldata[line].y + cell->ldata[line].h > ybot && !exit)
  {
    #ifdef TRACE
      if (tl & (1u<<22)) Printf("browser_find_selectable_top_r: Line %d\n",line);
    #endif

    chunk    = cell->ldata[line].chunks;
    chunkmax = cell->ldata[line].n + chunk;

    while (chunk < chunkmax && !exit)
    {
      /* Find the token represented by this chunk */

      t = cell->cdata[chunk].t;
      if (!t) break;

      #ifdef TRACE
        if (tl & (1u<<22))
        {
          Printf("browser_find_selectable_top_r: Chunk %d of %d, token %p\n", chunk, chunkmax, t);
          if (t == last) Printf("browser_find_selectable_top_r: t = last, so won't deal with this chunk\n");
        }
      #endif

      /* Several chunks can represent the same token - don't want to */
      /* deal with it multiple times, though.                        */

      if (t != last)
      {
        last = t;

        /* Exit successfully (exit = 1, t != NULL) if the token can be */
        /* selected and is visible.                                    */

        if (!*current && CanBeSelected(t) && browser_check_visible(b, s, t))
        {
          #ifdef TRACE
            if (tl & (1u<<22)) Printf("browser_find_selectable_top_r: ** Found %p **\n", t);
          #endif

          exit = 1;
          break;
        }

        /* If we're supposed to select the token after *current - i.e. *current */
        /* is still not NULL - then find the first token making up that same    */
        /* link. If this matches *current, we've found that link - so, clear    */
        /* *current and skip past the link.                                     */

        if (*current && CanBeSelected(t))
        {
          HStream * top, * bot;

          tokenutils_anchor_range(b, t, &top, &bot);

          if (top == *current)
          {
            *current = NULL;

            tokenutils_find_token(b, cell, bot, NULL, NULL, &line, &chunk);
          }
        }

        /* Deal with tables */

        if (t->tagno == TAG_TABLE)
        {
          table_stream   * table     = (table_stream *) t;
          table_row      * row       = table->List;
          table_headdata * head      = NULL;
          HStream        * found     = NULL;
          reformat_cell  * c         = NULL;
          reformat_cell  * cellarray = table->cells;
          int              cellmax   = table->ColSpan * table->RowSpan;
          int              cellindex;
          int              xorg, yorg;

          #ifdef TRACE
            if (tl & (1u<<22)) Printf("browser_find_selectable_top_r: Dealing with table\n");
          #endif

          /* Proceed if the cell array can be found */

          if (cellarray)
          {
            while (row && !found)
            {
              head = row->List;

              while (
                      head                           &&
                      !found                         &&
                      head->RowOffs < table->RowSpan &&
                      head->ColOffs < table->ColSpan
                    )
              {
                switch (head->Tag)
                {
                  case TagTableData:
                  case TagTableHead:
                  {
                    /* Find the reformat_cell structure for this table cell */

                    cellindex = head->RowOffs * table->ColSpan + head->ColOffs;

                    if (cellindex < cellmax)
                    {
                      c = &cellarray[cellindex];

                      #ifdef TRACE
                        if (tl & (1u<<22)) Printf("browser_find_selectable_top_r: Cell index %d, cell %p\n",cellindex,cellarray);
                      #endif

                      convert_pair_to_os(c->x, c->y, &xorg, &yorg);

                      /* Recursive call to look at cell contents */

                      found = browser_find_selectable_top_r(b,
                                                            c,
                                                            current,
                                                            cell->ldata[line].y + cell->ldata[line].h + y_origin + yorg,
                                                            s);

                      #ifdef TRACE
                        if (tl & (1u<<22)) Printf("browser_find_selectable_top_r: Cell index %d, cell %p - Found: %p\n",cellindex, cellarray, found);
                      #endif
                    }
                  }

                /* Closure of 'switch (head->Tag)' */
                }

                head = head->Next;

              /* Closure of 'while (head && ...)' */
              }

              row = row->Next;

            /* Closure of 'while (row && ...)' */
            }

          /* Closure of 'if (cellarray)' */
          }

          if (found)
          {
            t    = found;
            exit = 1;

            #ifdef TRACE
              if (tl & (1u<<22)) Printf("browser_find_selectable_top_r: ** Found %p in table, exitting **\n",t);
            #endif

            break;
          }
          #ifdef TRACE

            else if (tl & (1u<<22)) Printf("browser_find_selectable_top_r: Nothing found in table\n");

          #endif
        }

      /* Closure of 'if (t != last)' */
      }

      chunk ++;

    /* Closure of 'while' loop scanning chunks */
    }

    line ++;

  /* Closure of 'while' loop scanning the lines */
  }

  /* If exit wasn't forced, we scanned the whole of the visible line list */
  /* for this cell and didn't find a visible, selectable token.           */

  if (!exit) t = NULL;

  /* Return the found value, be it NULL or a valid selectable token */

  #ifdef TRACE
    if (tl & (1u<<22)) Printf("browser_find_selectable_top_r: -- Returning %p --\n", t);
  #endif

  return t;
}

/*************************************************/
/* browser_find_selectable_bot_r()               */
/*                                               */
/* Recursive back-end to the 'from bottom-right' */
/* call to browser_find_another_selectable and   */
/* browser_find_first_selectable.                */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             holding the lines to scan;        */
/*                                               */
/*             Pointer to a pointer to the       */
/*             currently selected token (this    */
/*             will be written to) - if there is */
/*             no such token, this should point  */
/*             to a word holding NULL;           */
/*                                               */
/*             y origin of that cell, in OS      */
/*             units from the top left of the    */
/*             whole page;                       */
/*                                               */
/*             A WimpGetWindowStateBlock pointer */
/*             holding details of the window.    */
/*                                               */
/* Returns:    As browser_find_first_selectable. */
/*************************************************/

HStream * browser_find_selectable_bot_r(browser_data * b, reformat_cell * cell, HStream ** current, int y_origin, WimpGetWindowStateBlock * s)
{
  HStream * t    = NULL;
  HStream * last = NULL;
  int       exit;
  int       ytop, ybot, htop, hbot;
  int       line, chunk, chunkmin;

  if (!cell || !cell->nlines || !cell->ldata || !cell->cdata) return NULL;

  #ifdef TRACE
    if (tl & (1u<<22)) Printf("browser_find_selectable_bot_r: Proceeding for %p, cell %p\n", b, cell);
  #endif

  /* Work out where the visible page region starts and ends */

  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();

  ytop = s->yscroll - htop;
  ybot = s->yscroll - (s->visible_area.ymax - s->visible_area.ymin) + hbot;

  #ifdef TRACE
    if (tl & (1u<<22)) Printf("browser_find_selectable_bot_r: ytop, ybot: -%d, -%d\n",-ytop,-ybot);
  #endif

  /* Go through the cell's chunks, getting tokens for as long */
  /* as the chunk holding lines are in the visible area.      */

  line = cell->nlines - 1;
  exit = 0;

  /* Find the line visible at the top of the window. This can be slow if */
  /* there are a lot of lines, so use a Cunning Plan - divide the window */
  /* extent (take the y coordinate of the last line) by the number of    */
  /* lines to get the average line height, use this and the ybot coord   */
  /* to have a good guess at the line, then move a short distance up or  */
  /* down to get the actual correct line.                                */
  /*                                                                     */
  /* Of course, this only works for the main line list. It could be      */
  /* adjusted for tables, but there's no time to do it right now...      */
  /* Tables are rarely large enough to need it anyway.                   */

  if (cell != b->cell)
  {
    while (line >= 0 && y_origin + cell->ldata[line].y + cell->ldata[line].h < ybot) line --;
  }
  else
  {
    int extent  = cell->ldata[cell->nlines - 1].y;
    int average = extent / cell->nlines;
    int startat = ybot / average + 1;

    if (startat < 0)             startat = 0;
    if (startat >= cell->nlines) startat = cell->nlines - 1;

    if (y_origin + cell->ldata[startat].y + cell->ldata[startat].h < ybot)
    {
      int lastline = -1;

      line = startat;
      while (line >= 0 && y_origin + cell->ldata[line].y + cell->ldata[line].h < ybot) lastline = line, line --;

      if (lastline >= 0) line = lastline;
      else               line = startat;
    }
    else
    {
      line = startat;
      while (line < cell->nlines && y_origin + cell->ldata[line].y + cell->ldata[line].h >= ybot) line ++;

      if (line >= cell->nlines) line = cell->nlines - 1;
    }
  }

  /* Proceed until the line visible at the top */

  while (line >= 0 && y_origin + cell->ldata[line].y < ytop && !exit)
  {
    #ifdef TRACE
      if (tl & (1u<<22)) Printf("browser_find_selectable_bot_r: Line %d\n",line);
    #endif

    chunkmin = cell->ldata[line].chunks;
    chunk    = cell->ldata[line].n + chunkmin - 1;

    while (chunk >= chunkmin && !exit)
    {
      /* Find the token represented by this chunk */

      t = cell->cdata[chunk].t;
      if (!t) break;

      #ifdef TRACE
        if (tl & (1u<<22))
        {
          Printf("browser_find_selectable_bot_r: Chunk %d of %d minimum, token %p\n", chunk, chunkmin, t);
          if (t == last) Printf("browser_find_selectable_bot_r: t = last, so won't deal with this chunk\n");
        }
      #endif

      /* Several chunks can represent the same token - don't want to */
      /* deal with it multiple times, though.                        */

      if (t != last)
      {
        last = t;

        /* Exit successfully (exit = 1, t != NULL) if the token can be */
        /* selected and is visible.                                    */

        if (!*current && CanBeSelected(t) && browser_check_visible(b, s, t))
        {
          #ifdef TRACE
            if (tl & (1u<<22)) Printf("browser_find_selectable_bot_r: ** Found %p **\n", t);
          #endif

          exit = 1;
          break;
        }

        /* If we're supposed to select the token after *current - i.e. *current */
        /* is still not NULL - then, having made sure that the current token    */
        /* 't' represents an anchor, find the first token making up that same   */
        /* link. If this matches *current, we've found that link - so, clear    */
        /* *current and skip over the link.                                     */

        if (*current && CanBeSelected(t))
        {
          HStream * top, * bot;

          tokenutils_anchor_range(b, t, &top, &bot);

          if (top == *current)
          {
            *current = NULL;

            tokenutils_find_token(b, cell, top, &line, &chunk, NULL, NULL);
          }
        }

        /* Deal with tables */

        if (t->tagno == TAG_TABLE)
        {
          table_stream   * table     = (table_stream *) t;
          table_row      * row       = NULL;
          table_headdata * head      = NULL;
          HStream        * found     = NULL;
          reformat_cell  * c         = NULL;
          reformat_cell  * cellarray = table->cells;
          int              cellmax   = table->ColSpan * table->RowSpan;
          int              cellindex;
          int              xorg, yorg;

          #ifdef TRACE
            if (tl & (1u<<22)) Printf("browser_find_selectable_bot_r: Dealing with table\n");
          #endif

          /* Proceed if the cell array can be found */

          if (cellarray)
          {
            /* Start on the last row and work backwards */

            row = table->List;

            while (row && row->Next) row = row->Next;

            while (row && !found)
            {
              head = row->List;

              /* Start on the last cell and work backwards */

              while (head && head->Next) head = head->Next;

              while (
                      head                           &&
                      !found                         &&
                      head->RowOffs < table->RowSpan &&
                      head->ColOffs < table->ColSpan
                    )
              {
                switch (head->Tag)
                {
                  case TagTableData:
                  case TagTableHead:
                  {
                    /* Find the reformat_cell structure for this table cell */

                    cellindex = head->RowOffs * table->ColSpan + head->ColOffs;

                    if (cellindex < cellmax)
                    {
                      c = &cellarray[cellindex];

                      #ifdef TRACE
                        if (tl & (1u<<22)) Printf("browser_find_selectable_bot_r: Cell index %d, cell %p\n",cellindex,cellarray);
                      #endif

                      convert_pair_to_os(c->x, c->y, &xorg, &yorg);

                      /* Recursive call to look at cell contents */

                      found = browser_find_selectable_bot_r(b,
                                                            c,
                                                            current,
                                                            cell->ldata[line].y + cell->ldata[line].h + y_origin + yorg,
                                                            s);

                      #ifdef TRACE
                        if (tl & (1u<<22)) Printf("browser_find_selectable_bot_r: Cell index %d, cell %p - Found: %p\n",cellindex, cellarray, found);
                      #endif
                    }
                  }

                /* Closure of 'switch (head->Tag)' */
                }

                head = head->Prev;

              /* Closure of 'while (head && ...)' */
              }

              row = row->Prev;

            /* Closure of 'while (row && ...)' */
            }

          /* Closure of 'if (cellarray)' */
          }

          if (found)
          {
            t    = found;
            exit = 1;

            #ifdef TRACE
              if (tl & (1u<<22)) Printf("browser_find_selectable_bot_r: ** Found %p in table, exitting **\n",t);
            #endif

            break;
          }
          #ifdef TRACE

            else if (tl & (1u<<22)) Printf("browser_find_selectable_bot_r: Nothing found in table\n");

          #endif
        }

      /* Closure of 'if (t != last)' */
      }

      chunk --;

    /* Closure of 'while' loop scanning chunks */
    }

    line --;

  /* Closure of 'while' loop scanning the lines */
  }

  /* If exit wasn't forced, we scanned the whole of the visible line list */
  /* for this cell and didn't find a visible, selectable token.           */

  if (!exit) t = NULL;

  /* Return the found value, be it NULL or a valid selectable token */

  #ifdef TRACE
    if (tl & (1u<<22)) Printf("browser_find_selectable_bot_r: -- Returning %p --\n", t);
  #endif

  return t;
}

/*************************************************/
/* browser_move_selection()                      */
/*                                               */
/* Moves the selected item up or down (to a      */
/* previous link, picture or forms item, or to a */
/* next item).                                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the selected item;    */
/*                                               */
/*             Key press from a keyboard handler */
/*             (used to determine the direction  */
/*             and distance of motion).          */
/*                                               */
/* Returns:    1 if the keypress was used for    */
/*             something, else 0 (e.g. there are */
/*             no more objects to select).       */
/*************************************************/

int browser_move_selection(browser_data * b, int key)
{
  int                       page = 0, line = 0, dir = 0, horiz = 0;
  WimpGetWindowStateBlock   s;
  HStream                 * new            = NULL;
  HStream                 * first_selected = NULL;
  HStream                 * last_selected  = NULL;
  browser_data            * ancestor       = utils_ancestor(b);
  browser_data            * owner;

  owner             = ancestor->selected_owner;
  if (!owner) owner = b;

  if (!choices.keyboard_ctrl) return 0;

  if (ancestor->in_image_map || b->in_image_map)
  {
    /* If browser_navigate_map returns a non-zero value, */
    /* drop the keypress through.                        */

    key = browser_navigate_map(b, key);
    if (!key) return 1;
  }

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

  /* Ctrl+Shift+Arrows nudges the page scroll position, */
  /* it doesn't move any selections.                    */

  if (
       key == akbd_UpK    + akbd_Ctl + akbd_Sh  ||
       key == akbd_DownK  + akbd_Ctl + akbd_Sh  ||
       key == akbd_LeftK  + akbd_Ctl + akbd_Sh  ||
       key == akbd_RightK + akbd_Ctl + akbd_Sh
     )
     return 0;

  /* Otherwise, work out which direction to move in, is this line or */
  /* page movement, etc.                                             */

  if (
       key == akbd_UpK               ||
       key == akbd_PageUpK           ||
       key == akbd_LeftK             ||
       key == akbd_HomeK             ||
       key == akbd_UpK   + akbd_Ctl  ||
       key == akbd_LeftK + akbd_Ctl
     )
     dir = 1;

  if (
       key == akbd_PageUpK           ||
       key == akbd_PageDownK
     )
     page = 1;

  if (
       key == akbd_UpK               ||
       key == akbd_DownK             ||
       key == akbd_LeftK             ||
       key == akbd_RightK
     )
     line = 1;

  if (
       key == akbd_LeftK             ||
       key == akbd_RightK            ||
       key == akbd_LeftK  + akbd_Ctl ||
       key == akbd_RightK + akbd_Ctl
     )
     horiz = 1;

  /* If there's no selected token, or there is but it's not visible, */
  /* then reselect as appropriate from the tokens (if any) currently */
  /* visible on the page.                                            */

  if (ancestor->selected) tokenutils_anchor_range(owner,
                                                  ancestor->selected,

                                                  &first_selected,
                                                  &last_selected);
  else first_selected = last_selected = NULL;

  if (
       line &&
       (
         !ancestor->selected ||
         (
           ancestor->selected                                &&
           !browser_check_visible(owner, &s, first_selected) &&
           !browser_check_visible(owner, &s, last_selected)
         )
       )
     )
  {
    ancestor->selected       = NULL; /* No redraw problems as the conditions above ensure selected token is not in visible area now */
    ancestor->selected_owner = NULL;
    new                      = browser_find_first_selectable(owner, &s, dir);
  }

  /* Alternatively, if there's a selected token, move up or down from it */

  else if (line && ancestor->selected)
  {
    new = browser_find_another_selectable(owner, ancestor->selected, dir, horiz);

    /* If up/down is used but the next item is not visible on screen so */
    /* the page would scroll, want to first ensure that everything on   */
    /* the current line is selected - i.e. try moving horizontally.     */

    if (
         new                                   &&
         !horiz                                &&
         !browser_check_visible(owner, &s, new)
       )
       new = browser_find_another_selectable(owner, ancestor->selected, dir, 1);
  }

  if (new)
  {
//    if (
//         tokenutils_within_distance(owner,
//                                    new,
//                                    ancestor->selected,
//                                    s.visible_area.ymax -
//                                    s.visible_area.ymin -
//                                    toolbars_status_height(b) -
//                                    toolbars_url_height(b) -
//                                    toolbars_button_height(b))
//       )

    if (browser_check_visible(owner, &s, new))
    {
      /* If there are any forms menus open, close them */

      if (menusrc == Menu_Form) form_abandon_menu();

      browser_ensure_visible(owner, &s, new);
      browser_select_token(owner, new, 0);

      /* Update the status bar */

      toolbars_update_status(owner, Toolbars_Status_LinkTo);

      /* Move inside writable elements */

      if (
           form_token_cursor_editable(owner, new)
         )
         form_click_field(owner,
                          new,
                          ((key == akbd_DownK || key == akbd_RightK) ? 0 : 2),
                          0, 0);

      /* Turn the pointer off, and reset the check to see if */
      /* the user has moved it manually.                     */

      mouse_pointer_off();
      mouse_force_unused();

      return 1;
    }
    else
    {
      /* This is so that if there's another selectable, but it's off screen, */
      /* the user doesn't suddenly start scrolling left / right, only up or  */
      /* down to reach the next selectable.                                  */

      if (horiz) return 1;
    }
  }

  return 0;
}

/*************************************************/
/* browser_navigate_map()                        */
/*                                               */
/* Moves around an image map by keyboard control */
/* with the machine single tasking during a key  */
/* autorepeat.                                   */
/*                                               */
/* To exit this 'mode', drop off the image map   */
/* or follow a link with Return.                 */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the browser the image */
/*             map lies in;                      */
/*                                               */
/*             Wimp keycode used to move in it.  */
/*                                               */
/* Returns:    0 if the pointer is still in the  */
/*             map, else a Wimp key code saying  */
/*             which way it fell off.            */
/*************************************************/

static int browser_navigate_map(browser_data * b, int key)
{
  HStream                 * tp;
  int                       map_x  = -1;
  int                       map_y  = -1;
  WimpGetPointerInfoBlock   p;

  if (!wimp_get_pointer_info(&p))
  {
    tp = browser_get_pointer_token(b, &p, NULL, NULL);

    if (
         tp                     &&
         redraw_selected(b, tp) &&
         (
           (
             (tp->style & IMG) &&
             (tp->type  & TYPE_ISMAP)
           )
           ||
           (
             tp->tagno         == TAG_INPUT &&
             HtmlINPUTtype(tp) == inputtype_IMAGE
           )
           ||
           (
             tp->type & TYPE_ISCLIENTMAP
           )
         )
       )
    {
      int       last_move = 4;
      int       last_key, start_key;
      int       time_delay, time_now;
      int       repeat_delay, repeat_rate;
      HStream * current;

      _swix(OS_Byte,
            _INR(0,1) | _OUT(1),

            121, /* Keyboard scan */
            0,   /* Scan all keys */

            &start_key); /* Record the key(s) being pressed (not the Wimp keycode) */

      mouse_watch_pointer_control(0);
      mouse_pointer_on();
      b->pointer_over = NULL;
      browser_pointer_check(0, NULL,NULL, b);

      /* Get the keyboard repeat rate and delay */

      _swix(OS_Byte,
            _INR(0, 2) | _OUTR(1, 2),

            196, /* Read auto-repeat delay */
            0,
            255,

            &repeat_delay,
            &repeat_rate);

      /* Read the current time into time_delay and set it negative, */
      /* to flag that the repeat delay part of autorepeat is active */

      _swix(OS_ReadMonotonicTime, _OUT(0), &time_delay);
      time_delay = -time_delay + 1; /* +1 as a '>' is used in the comparisson later, not '>='. */

      do
      {
        /* Move according to key used */

        if (key == akbd_UpK)    p.y += last_move;
        if (key == akbd_DownK)  p.y -= last_move;
        if (key == akbd_LeftK)  p.x -= last_move;
        if (key == akbd_RightK) p.x += last_move;

        if (key == akbd_Sh + akbd_UpK)    p.y += last_move * 3;
        if (key == akbd_Sh + akbd_DownK)  p.y -= last_move * 3;
        if (key == akbd_Sh + akbd_LeftK)  p.x -= last_move * 3;
        if (key == akbd_Sh + akbd_RightK) p.x += last_move * 3;

        map_x = p.x, map_y = p.y;

        mouse_to(p.x, p.y, 1);

        /* Loop round waiting for the keyboard repeat delay or rate */

        do
        {
          _swix(OS_Byte,
                _INR(0,1) | _OUT(1),

                121, /* Keyboard scan */
                0,   /* Scan all keys */

                &last_key);

          _swix(OS_ReadMonotonicTime, _OUT(0), &time_now);

          /* If time_delay is negative, wait for the repeat delay */

          if (time_delay < 0)
          {
            if (time_now + time_delay > repeat_delay) time_delay = time_now;
          }

          /* Otherwise, wait for the repeat rate */

          else
          {
            if (time_now - time_delay > repeat_rate) time_delay = time_now, last_move += 1;
          }
        }
        while (time_now != time_delay && last_key != 255 && last_key == start_key);

        /* Make sure that next time round the loop, the exit condition above doesn't */
        /* immediately activate, giving about 1 centisecond of very fast repeats...  */

        time_delay = time_now - 1; /* Hence the use of '>' rather than '>=' above */

        _swix(OS_Byte, _INR(0,1), 21, 0); /* Flush keyboard buffer */

        current = browser_get_pointer_token(b, &p, NULL, NULL);

        /* Keep going whilst we're over the same token and keys are being pressed */
      }
      while (tp == current && last_key != 255 && last_key == start_key);

      /* If we're on the same token, exit, claiming the key press. */
      /* Else, allow the press to drop through (so you'll go on to */
      /* select the next/previous token if running off the edge of */
      /* the image).                                               */

      if (tp == current) return 0;

      /* Otherwise, reenable pointer watching etc. and allow the */
      /* key press to drop to through.                           */
    }

    /* (This code also executes if the pointer isn't */
    /* over an image map any more - e.g. the user    */
    /* moved the mouse away).                        */

    mouse_set_pointer_shape(Mouse_Shape_Normal);
    debounce_keypress();

    /* Clear the flag saying an image map is selected */
    /* and sort out mouse pointer issues              */

    {
      browser_data * ancestor = utils_ancestor(b);

      mouse_pointer_off();
      b->in_image_map = ancestor->in_image_map = 0;
      b->pointer_over = ancestor->pointer_over = NULL;
      browser_pointer_check(0, NULL,NULL, b);
      mouse_watch_pointer_control(1);
    }

    return key;
  }

  return 0;
}

/*************************************************/
/* browser_fetch_url()                           */
/*                                               */
/* Looks in the browser_data structure given to  */
/* the function and returns the URL that is      */
/* currently being fetched, or NULL for none.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the fetch.            */
/*                                               */
/* Returns:    Pointer to the URL string or NULL */
/*             if there is no fetch URL present. */
/*************************************************/

char * browser_fetch_url(browser_data * b)
{
  return b->urlfdata;
}

/*************************************************/
/* browser_current_url()                         */
/*                                               */
/* Returns a pointer to the current URL being    */
/* displayed in a browser window, unless there   */
/* is none, in which case the fetch URL is       */
/* given; the pointer may still be NULL though.  */
/*                                               */
/* Parameters: A pointer to a browser_data       */
/*             structure relevant to the window. */
/*                                               */
/* Returns:    Pointer to the displayed URL, or  */
/*             the fetch URL if none is          */
/*             displayed.                        */
/*************************************************/

char * browser_current_url(browser_data * b)
{
  /* NB, should this function ever become more complex than this, */
  /* note that external callers rely on it NOT corrupting the     */
  /* 'tokens' buffer used by lookup_token or lookup_choice - i.e. */
  /* this function may not call these.                            */

  return b->urlddata;
}

/*************************************************/
/* browser_current_title()                       */
/*                                               */
/* Examines an HStream list for a given browser  */
/* trying to find a TITLE tag; if it finds it,   */
/* a pointer to the title text is returned.      */
/*                                               */
/* Parameters: A pointer to a browser_data       */
/*             structure relevant to the HStream */
/*             list.                             */
/*                                               */
/* Returns:    Pointer to the page title, or     */
/*             NULL if none is found.            */
/*************************************************/

char * browser_current_title(browser_data * b)
{
  HStream * current = b->stream;

  while (current)
  {
    if (ISHEAD(current) && current->tagno == TAG_TITLE && current->text) return current->text;

    current = current->next;
  }

  return NULL;
}

/*************************************************/
/* browser_destroy_source()                      */
/*                                               */
/* Pass a browser_data structure pointer, and if */
/* this points to a block of memory holding      */
/* fetched HTML source, free up that block.      */
/*                                               */
/* Parameters: A pointer to a browser_data       */
/*             structure relevant to the source. */
/*************************************************/

void browser_destroy_source(browser_data * b)
{
  if (b->source)
  {
    #ifdef TRACE
      if (tl & (1u<<12)) Printf("browser_destroy_source: flex_free block %p which held page source\n",&b->source);
      flexcount -= flex_size((flex_ptr) &b->source);
      if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
    #endif

    flex_free((flex_ptr) &b->source);
    b->source = NULL;
  }
}

/*************************************************/
/* browser_pointer_entering()                    */
/*                                               */
/* Called when the pointer goes over a browser   */
/* window. Installs a null event handler to      */
/* watch over the pointer's position relative to */
/* links on the page.                            */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

int browser_pointer_entering(int eventcode, WimpPollBlock * block, IdBlock * idb, void * handle)
{
  browser_data * b = NULL;

  /* If dragging, don't want to know about this at all (at least */
  /* for now, when drags only correspond to resizing frames)     */

  if (drag_in_progress) return 0;

  /* Quick sanity check (this can and does happen - Toolbox oddities) */

  if (!idb->self_id) return 0;

  /* Is this a browser we know about? */

  ChkError(toolbox_get_client_handle(0, idb->self_id, (void *) &b));

  if (is_known_browser(b)) register_null_claimant(Wimp_ENull, (WimpEventHandler *) browser_pointer_check, b);

  pointer_is_over = idb->self_id;

  return 0;
}

/*************************************************/
/* browser_pointer_leaving()                     */
/*                                               */
/* Called when the pointer goes out of a browser */
/* window. Deinstalls a null event handler that  */
/* watched over the pointer's position relative  */
/* to links on the page.                         */
/*                                               */
/* Note that objects may be deleted and this     */
/* function wouldn't be called, so anything that */
/* goes in here should be echoed somewhere in    */
/* windows_close_browser.                        */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

int browser_pointer_leaving(int eventcode, WimpPollBlock * block, IdBlock * idb, void * handle)
{
  browser_data * b = NULL;

  /* Quick sanity check (as for browser_pointer_entering) */

  if (!idb->self_id) return 0;

  #ifdef TRACE

    /* If the pointer is leaving, we should know what it was over */

    if (idb->self_id != pointer_is_over)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      sprintf(erb.errmess,
              "Existing pointer_is_over ID %08x doesn't match ID %08x given to browser_pointer_leaving",
              pointer_is_over,
              idb->self_id);

      show_error_ret(&erb);
    }

  #endif

  ChkError(toolbox_get_client_handle(0, idb->self_id, (void *) &b));

  if (is_known_browser(b))
  {
    deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) browser_pointer_check, b);

    if (!drag_in_progress)
    {
      mouse_set_pointer_shape(Mouse_Shape_Normal);
      if (mouse_pointer_is_on()) toolbars_cancel_status(b, Toolbars_Status_LinkTo);

      b->pointer_over = NULL;

    }

    pointer_is_over = 0;

    return 1;
  }

  pointer_is_over = 0;

  return 0;
}

/*************************************************/
/* browser_pointer_over_deleted()                */
/*                                               */
/* Checks to see if the pointer is over an       */
/* object that has just been deleted, and if     */
/* so, deregisters any pointer-related event     */
/* handlers associated with it.                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window / object.  */
/*************************************************/

void browser_pointer_over_deleted(browser_data * b)
{
  if (pointer_is_over && pointer_is_over == b->self_id)
  {
    deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) browser_pointer_check, b);
    mouse_set_pointer_shape(Mouse_Shape_Normal);
  }
}

/*************************************************/
/* browser_pointer_check()                       */
/*                                               */
/* Checks the pointer position relative to any   */
/* links on the page; if it is over one,         */
/* the pointer shape is changed and the status   */
/* bar updated. Alternatively, it is changed     */
/* back to the normal pointer shape and the      */
/* status bar put back to its Ready state.       */
/*                                               */
/* This function will also change the pointer to */
/* a shape indicating frame borders may be       */
/* dragged to resize them, where appropriate.    */
/*                                               */
/* Parameters are as for a standard Wimp event   */
/* handler, though only browser_data * handle is */
/* of interest (it points to a browser_data      */
/* struct relevant to the window in question).   */
/*                                               */
/* The function may be used as a NULL handler,   */
/* so never return 1 from it (you'll claim the   */
/* null event - this is Bad).                    */
/*************************************************/

int browser_pointer_check(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  int                       changed;
  HStream                 * tp = NULL;
  WimpGetPointerInfoBlock   p;

  /* If we're dragging, exit */

  if (drag_in_progress) return 0;

  if (wimp_get_pointer_info(&p)) return 0;

  /* If this browser has children, we must be over frame borders */

  if (handle->nchildren && !choices.fixed_pointer)
  {
    int row, col;

    if (!frames_find_pointer_in_frameset(handle,
                                         p.x,
                                         p.y,
                                         &row,
                                         &col,
                                         NULL,
                                         NULL,
                                         0))
    {
      if      (row > 0   && col < 0)   mouse_set_pointer_shape(Mouse_Shape_UD);
      else if (row < 0   && col > 0)   mouse_set_pointer_shape(Mouse_Shape_LR);
      else if (row > 0   && col > 0)   mouse_set_pointer_shape(Mouse_Shape_UDLR);
      else if (row == -2 || col == -2) mouse_set_pointer_shape(Mouse_Shape_NoResize);
      else                             mouse_set_pointer_shape(Mouse_Shape_Normal);
    }

    return 0;
  }

  /* Find the token that the pointer is over */

  tp = browser_get_pointer_token(handle,&p,NULL,NULL);

  if (
       choices.highlight_links      &&
       tp                           &&
       CanBeSelected(tp)            &&
       mouse_pointer_is_on()        &&
       !redraw_selected(handle, tp)
     )
  {
    browser_clear_selection(handle, 0);
    browser_select_token(handle, tp, 0);
  }

  /* Is this an image map? If so, have the coordinates of the pointer */
  /* over the map changed?                                            */

  if (tp && (tp->type & (TYPE_ISCLIENTMAP | TYPE_ISMAP)))
  {
    int nx, ny;

    /* Find out which pixel we clicked on */

    ChkError(image_return_click_offset(handle, tp, &p, &nx, &ny));

    if (nx != handle->map_x || ny != handle->map_y)
    {
      handle->map_x = nx;
      handle->map_y = ny;
      changed       = 1;
    }
    else changed = 0;
  }

  /* Otherwise, work out if we've changed from the token address. */

  else changed = (tp != handle->pointer_over);

  /* Sometimes pointer_over has to be zeroed because a token */
  /* list is getting freed, e.g. at the beginning of a new   */
  /* fetch. In this case, we need to always restore the      */
  /* pointer shape if tp is NULL.                            */

  if (tp == NULL) changed = 1;

  /* If this isn't the same as the token the pointer */
  /* was last recorded as being over...              */

  if (changed)
  {
    handle->pointer_over = tp;

    if (!choices.fixed_pointer)
    {
      /* If the Choices say the pointer can change shape, */
      /* and the pointer is over an identified token,     */
      /* set the pointer to ptr_link. Else restore the    */
      /* pointer to its normal shape.                     */

      if (
           tp &&
           (
             (tp->type & TYPE_ISCLIENTMAP) ||
             (ISLINK(tp))
           )
         )
      {
        int dealt_with = 0;

        /* Client side map */

        if (tp->type & TYPE_ISCLIENTMAP)
        {
          char * url;

          /* Find out what we're over - affects the pointer shape, you see... */

          csim_return_info(handle,
                           tp,
                           handle->map_x,
                           handle->map_y,
                           &url,
                           NULL,
                           NULL);

          /* Use a Link pointer for areas defined in the map */

          if (url && *url)
          {
            mouse_set_pointer_shape(Mouse_Shape_Link);
            dealt_with = 1;
          }
        }

        /* For anything which isn't a client side map, we must have */
        /* an anchor.                                               */

        if (!dealt_with && tp->anchor && *tp->anchor)
        {
          /* If this is a server side map, use the Map pointer */

          if (tp->type & TYPE_ISMAP)
          {
            mouse_set_pointer_shape(Mouse_Shape_Map);
          }

          /* Otherwise, use the normal Link pointer */

          else
          {
            mouse_set_pointer_shape(Mouse_Shape_Link);
          }

          dealt_with = 1;
        }

        /* If we've still not worked out what this is, cancel any */
        /* LinkTo message (e.g. over a client side image map with */
        /* no alternative URLs and not on any area defined by the */
        /* map itself).                                           */

        if (!dealt_with)
        {
          handle->pointer_over = NULL;
          mouse_set_pointer_shape(Mouse_Shape_Normal);
        }
      }

      else if (
                tp                     &&
                tp->tagno == TAG_INPUT &&
                HtmlINPUTtype(tp) == inputtype_IMAGE
              )
              mouse_set_pointer_shape(Mouse_Shape_Link);
      else
      {
        handle->pointer_over = NULL;
        mouse_set_pointer_shape(Mouse_Shape_Normal);
      }
    }

    /* If the mouse pointer is on, then update the status bar */
    /* to reflect the link it is over (or cancel a LinkTo     */
    /* message if it has moved off a link),                   */

    if (mouse_pointer_is_on())
    {
      if (handle->pointer_over) toolbars_update_status(handle, Toolbars_Status_LinkTo);
      else                      toolbars_cancel_status(handle, Toolbars_Status_LinkTo);
    }
  }

  return 0;
}

/*************************************************/
/* browser_get_pointer_token()                   */
/*                                               */
/* Returns the token number that the pointer is  */
/* over (if any) and an X and Y offset into      */
/* the line chunk representing that token.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             for the window the pointer is     */
/*             over;                             */
/*                                               */
/*             Pointer to a block describing     */
/*             the pointer condition;            */
/*                                               */
/*             Pointer to an int, in which the X */
/*             offset into the chunk is placed;  */
/*             Same for the Y offset.            */
/*                                               */
/* Returns:    Address of the token the pointer  */
/*             is over, or NULL for unknown /    */
/*             none.                             */
/*                                               */
/* Assumes:    The pointers to the ints for the  */
/*             X and Y offsets *can* be NULL.    */
/*************************************************/

HStream * browser_get_pointer_token(browser_data * b, WimpGetPointerInfoBlock * p, int * ox, int * oy)
{
  WimpGetWindowStateBlock   state;
  reformat_cell           * cell = b->cell;

  /* If we're dragging, return NULL */

  if (drag_in_progress) return NULL;

  /* No point proceeding if there are no lines */

  if (!cell->ldata) return NULL;

  /* No point searching the tokens if we're not over the right window and */
  /* then only in the display area of that window.                        */

  if (b->window_handle != p->window_handle || p->icon_handle < -1) return NULL;

  /* Return 0 if there's an error getting the window's state information */

  state.window_handle = p->window_handle;
  if (wimp_get_window_state(&state)) return NULL;

  return browser_get_pointer_token_r(b, cell, p, &state, ox, oy);
}

/*************************************************/
/* browser_get_pointer_token_r()                 */
/*                                               */
/* Recursive back-end to                         */
/* browser_get_pointer_token.                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             for the window the pointer is     */
/*             over;                             */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             holding the line/chunk data to    */
/*             check against the pointer         */
/*             position;                         */
/*                                               */
/*             Pointer to a block describing     */
/*             the pointer condition;            */
/*                                               */
/*             A WimpGetWindowStateBlock pointer */
/*             where the block contains details  */
/*             of the browser window;            */
/*                                               */
/*             Pointer to an int, in which the X */
/*             offset into the chunk is placed;  */
/*                                               */
/*             Same for the Y offset.            */
/*                                               */
/* Returns:    As browser_get_pointer_token.     */
/*                                               */
/* Assumes:    As browser_get_pointer_token.     */
/*************************************************/

static HStream * browser_get_pointer_token_r(browser_data * b, reformat_cell * cell, WimpGetPointerInfoBlock * p,
                                             WimpGetWindowStateBlock * state, int * ox, int * oy)
{
  int x, y, line;

  /* Convert the pointer's screen x and y coordinates to window coordinatess */

  x = coords_x_toworkarea(p->x, (WimpRedrawWindowBlock *) state);
  y = coords_y_toworkarea(p->y, (WimpRedrawWindowBlock *) state);

  /* Find the line that the pointer is over */

  line = browser_line_at_y(b, cell, y);
  if (line < 0) return 0;

  /* Convert the x coordinate to millipoints */

  convert_to_points(x, &x);

  if (ox) *ox = 0;
  if (oy) *oy = 0;

  /* Proceed only if the line has some chunks associated with it */

  if (cell->ldata[line].n)
  {
    int       cx, n, i;
    HStream * tp;

    /* Find the token's address for the first chunk on the line, */
    /* work out where its X coordinate should be and compare     */
    /* this to the pointer X. If the pointer X is less than the  */
    /* line X, the pointer lies to the left of all the chunks so */
    /* exit here with 0.                                         */

    tp = cell->cdata[cell->ldata[line].chunks].t;

    cx = redraw_start_x(b,
                        cell,
                        tp,
                        line);

    convert_to_points(cx, &cx);

    if (x < cx) return 0;

    /* For each chunk, take the calculated left hand coordinate */
    /* for the whole line and add the width of each chunk until */
    /* the pointer X lies to the left of the calculated coord.  */

    n = 0;
    i = cell->ldata[line].n - 1;

    while (i >= 0 && x > (cx + cell->cdata[cell->ldata[line].chunks + n].w))
    {
      cx += cell->cdata[cell->ldata[line].chunks + n].w;
      n++;
      i--;
    }

    /* If a chunk was found that the pointer is over... */

    if (i >= 0)
    {
      BBox box;

      /* Get the address of the token corresponding to the chunk */

      tp = cell->cdata[cell->ldata[line].chunks + n].t;

      #ifdef TRACE
        if (tl & (1u<<3))
        {
          static tracelastchunk = -1;

          if (tracelastchunk != cell->ldata[line].chunks + n)
          {
            Printf("Chunk : %d, token: %d\n",cell->ldata[line].chunks + n,cell->cdata[cell->ldata[line].chunks + n].t);
            Printf("Text  : '%s'\n",tp->text);
            Printf("Style : %p\n",(void *) tp->style);

            if (ISLINK(tp))
            {
              Printf("Link  : '%s'\n",tp->anchor);
              Printf("Target: '%s'\n",tp->target);
            }

            tracelastchunk = cell->ldata[line].chunks + n;
          }
        }
      #endif

      /* If the token represents a table... */

      if (tp->tagno == TAG_TABLE)
      {
        /* In this case there are table streams hung from d->cdata */

        table_stream            * table     = (table_stream *) tp;
        table_row               * row       = NULL;
        table_headdata          * head      = NULL;
        reformat_cell           * cellarray = table->cells;
        reformat_cell           * c;
        WimpGetWindowStateBlock   s         = *state;
        HStream                 * over      = NULL;
        int                       tablx, tably;
        int                       lineh;
        int                       cellindex;
        int                       cellcount = 0;
        int                       cellmax   = table->ColSpan * table->RowSpan;

        /* Only proceed if there are table cells to redraw */

        if (cellarray)
        {
          /* Get the bottom left of the table in tablx, tably, */
          /* in window coords, units of millipoints.           */

          tablx = cx;
          convert_to_points(cell->ldata[line].y, &tably);

          /* Get the line height in millipoints */

          convert_to_points(cell->ldata[line].h, &lineh);

          /* Table cells aren't scrolled! */

          s.xscroll = s.yscroll = 0;

          /* Start going through the rows */

          row = table->List;

          while (row && !over && cellcount < cellmax)
          {
            head = row->List;

            while (head && !over && cellcount < cellmax)
            {
              switch (head->Tag)
              {
                case TagTableData:
                case TagTableHead:
                {
                  cellindex = head->RowOffs * table->ColSpan + head->ColOffs;

                  if (cellindex < cellmax)
                  {
                    c = &cellarray[cellindex];

                    /* No point proceeding without lines to scan */

                    if (c->nlines)
                    {
                      /* Set the visible area BBox to be that of the current cell */
                      /* (this will be in millipoint window coords)               */

                      s.visible_area.xmin = tablx + c->x;
                      s.visible_area.ymin = tably + c->y + lineh - c->cellheight;
                      s.visible_area.xmax = s.visible_area.xmin + c->cellwidth  - 1;
                      s.visible_area.ymax = s.visible_area.ymin + c->cellheight - 1;

                      /* Convert the above to OS units, then to screen coords */

                      convert_box_to_os(&s.visible_area, &s.visible_area);
                      coords_box_toscreen(&s.visible_area, (WimpRedrawWindowBlock *) state);

                      /* Recursive call */

                      over = browser_get_pointer_token_r(b, c, p, &s, ox, oy);

                      /* If something has been found, may need to fill in ox and oy */

                      if (over)
                      {
                        /* If an address of an int to return Y information to was given... */

                        if (oy)
                        {
                          /* Set the int to hold the distance from the top of the line */
                          /* that the pointer was at.                                  */

                          convert_to_os(lineh, oy);
                          *oy -= (p->y - s.visible_area.ymin);

                          if (*oy < 0) *oy = 0;
                          if (*oy > s.visible_area.ymax) *oy = s.visible_area.ymax;
                        }

                        /* Similarly for X information, get the X offset into the chunk */

                        if (ox)
                        {
                          *ox = p->x - s.visible_area.xmin;

                          if (ISLINK(over)) *ox -= over->maxlen * 2;

                          if (*ox < 0) *ox = 0;
                          if (*ox > s.visible_area.xmax) *ox = s.visible_area.xmax;
                        }
                      }

                    /* Closure of 'if (c->nlines)' */
                    }

                  /* Closure of 'if (cellindex < cellmax)' */
                  }

                /* Closure of specific 'case' item */
                }
                break;

              /* Closure of 'switch (head->Tag)' */
              }

              cellcount++;

              head = head->Next;

            /* Closure of 'while (head)' */
            }

            row = row->Next;

          /* Closure of 'while (row)' */
          }

        /* Closure of 'if (cellarray)' */
        }

        return over;

      /* Closure of 'if (tp->tagno == TAG_TABLE)' */
      }

      /* If the token represents an image... */

      else if (
                (tp->style & IMG) ||
                (ISOBJECT(tp))    ||
                (
                  tp->tagno == TAG_INPUT &&
                  HtmlINPUTtype(tp) == inputtype_IMAGE
                )
              )
      {
        int brdr = 0;

        if (ISOBJECT(tp)) reformat_get_object_size(b, tp, &box);
        else              reformat_get_image_size (b, tp, &box);

        /* Correct for the border size if the image is a link, */
        /* or if this is an Object.                            */

        if (ISLINK(tp) && (tp->style & IMG)) brdr = tp->maxlen * 2;
        else if (ISOBJECT(tp))               brdr = HtmlOBJECTborder(tp) * 2;

        if (brdr)
        {
          box.xmin += brdr;
          box.ymin += brdr;
          box.xmax -= brdr;
          box.ymax -= brdr;
        }
      }
      else
      {
        /* The token does not represent an image, and so is text. */

        box.xmin = 0;

        /* Convert the width of the line chunk to OS units */

        convert_to_os(cell->cdata[cell->ldata[line].chunks + n].w, &box.xmax);

        /* Set the Y coordinates of the BBox structure to the base */
        /* line and base line plus height of the chunk.            */

        box.ymin = -cell->ldata[line].b;
        box.ymax =  cell->ldata[line].h - cell->ldata[line].b;
      }

      /* If an address of an int to return Y information to was given... */

      if (oy)
      {
        /* Set the int to hold the distance from the bottom of the line */
        /* that the pointer was at.                                     */

        *oy = (cell->ldata[line].y + cell->ldata[line].b + box.ymax) - y;

        if (*oy < 0) *oy = 0;
        if (*oy >= (box.ymax - box.ymin)) *oy = box.ymax - box.ymin - 1;
      }

      /* Similarly for X information, get the X offset into the chunk */

      if (ox)
      {
        *ox = x - cx;

        convert_to_os(*ox, ox);

        if (ISLINK(tp)) *ox -= tp->maxlen * 2;
        if (*ox < 0) *ox = 0;
        if (*ox >= box.xmax) *ox = box.xmax - 1;

      }

      /* Return the token address */

      return cell->cdata[cell->ldata[line].chunks + n].t;
    }
  }

  return NULL;
}

/*************************************************/
/* browser_line_at_y()                           */
/*                                               */
/* Returns the line number for a given y window  */
/* coordinate.                                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             holding the line information;     */
/*                                               */
/*             The y coordinate.                 */
/*                                               */
/* Returns:    The line number in which the Y    */
/*             coordinate lives, or -1 for none  */
/*             / an error.                       */
/*************************************************/

int browser_line_at_y(browser_data * b, reformat_cell * cell, int y)
{
  int l;

  if (!cell) cell = b->cell;

  /* Find a line who's y coordinate is lower than the given one, */
  /* i.e. its baseline is the first one of all lines in th cell  */
  /* that lies below that coordinate.                            */

  for (l = 0; (l <= cell->nlines) && (cell->ldata[l].y > y); l++);

  /* Either we have run out of lines, or the top of the line we    */
  /* found also lies below the given coordinate, in which case the */
  /* coordinate does not lie within that line and we should not    */
  /* return its number.                                            */

  if (l >= cell->nlines || cell->ldata[l].y + cell->ldata[l].h < y) return -1;

  return l;
}

/*************************************************/
/* browser_top_line()                            */
/*                                               */
/* Returns the line number displayed at the top  */
/* of the visible area of the browser window.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             Pointer to a word into which the  */
/*             address of a reformat_cell will   */
/*             be returned - the given line lies */
/*             in this cell;                     */
/*                                               */
/*             A WimpGetWindowStateBlock pointer */
/*             holding details of the browser    */
/*             window;                           */
/*                                               */
/*             1 if the line must be wholly      */
/*             visible, else 0 if it may be just */
/*             partially visible.                */
/*                                               */
/* Returns:    Directly, the number of the line  */
/*             displayed at the top of the       */
/*             window, or -1 for none / an       */
/*             error.                            */
/*                                               */
/* Assumes:    That *none* of the parameter      */
/*             pointers are NULL.                */
/*************************************************/

int browser_top_line(browser_data * b, reformat_cell ** ret_cell,
                     WimpGetWindowStateBlock * s, int fully_visible)
{
  if (!ret_cell) return -1;

  *ret_cell = NULL;

  return browser_top_line_r(b, b->cell, ret_cell, s, fully_visible);
}

/*************************************************/
/* browser_top_line_r()                          */
/*                                               */
/* Recursive back-end to browser_top_line.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             holding the cells to scan;        */
/*                                               */
/*             Pointer to a word into which the  */
/*             address of a reformat_cell will   */
/*             be returned - the given line lies */
/*             in this cell;                     */
/*                                               */
/*             A WimpGetWindowStateBlock pointer */
/*             holding details of the browser    */
/*             window;                           */
/*                                               */
/*             1 if the line must be wholly      */
/*             visible, else 0 if it may be just */
/*             partially visible.                */
/*                                               */
/* Returns:    As browser_top_line.              */
/*                                               */
/* Assumes:    As browser_top_line.              */
/*************************************************/

static int browser_top_line_r(browser_data * b, reformat_cell * cell, reformat_cell ** ret_cell,
                              WimpGetWindowStateBlock * s, int fully_visible)
{
  int y, l, htop;

  if (!cell) cell = b->cell;

  if (!cell->nlines || !cell->ldata || !cell->cdata) return -1;

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

  if (htop) htop += wimpt_dy();

  y = s->yscroll - htop;

  l = 0;

  /* Find the line */

  while (
          l < cell->nlines &&
          cell->ldata[l].y + (fully_visible ? cell->ldata[l].h : 0) > y
        )
        l++;

  /* If l >= number of lines, nothing was found */

  if (l >= cell->nlines) return -1;
  else
  {
    HStream * first_token = cell->cdata[cell->ldata[l].chunks].t;

    *ret_cell = cell;

    /* Otherwise, is there a table in the line? */

    if (first_token->tagno == TAG_TABLE)
    {
      table_stream   * table     = (table_stream *) first_token;
      table_row      * row       = NULL;
      table_headdata * head      = NULL;
      reformat_cell  * c         = NULL;
      reformat_cell  * cellarray = table->cells;
      int              cellmax   = table->ColSpan * table->RowSpan;
      int              cellcount = 0;
      int              cellindex;

      /* Proceed if the cell array can be found */

      if (cellarray)
      {
        row = table->List;

        while (row && cellcount < cellmax)
        {
          head = row->List;

          while (head && cellcount < cellmax)
          {
            switch (head->Tag)
            {
              case TagTableData:
              case TagTableHead:
              {
                /* Find the reformat_cell structure for this table cell */

                cellindex = head->RowOffs * table->ColSpan + head->ColOffs;

                if (cellindex < cellmax)
                {
                  c = &cellarray[cellindex];

                  /* If it has lines, recursively call this function to find the */
                  /* cell line at the top of the window.                         */

                  if (c->nlines)
                  {
                    l = browser_top_line_r(b, c, ret_cell, s, fully_visible);

                    /* If a line can be found inside this cell then return it immediately, */
                    /* rather than worrying about the other cells. Otherwise, continue     */
                    /* scanning cells. Since table cells are arranged left right, top to   */
                    /* bottom, and we are looking for the top line, this will work OK.     */

                    if (l >= 0) return l;
                  }
                }
              }
              break;
            }

            cellcount ++;

            head = head->Next;

          /* Closure of 'while (head && ...)' */
          }

          row = row->Next;

        /* Closure of 'while (row && ...)' */
        }

      /* Closure of 'if (cellarray)' */
      }

    /* Closure of 'if (first_token->tagno == TAG_TABLE)' */
    }

  /* Closure of 'else' case for 'if (l >= cell->nlines)' */
  }

  return l;
}

/*************************************************/
/* browser_bottom_line()                         */
/*                                               */
/* Returns the line number displayed at the      */
/* bottom of the visible area of the browser     */
/* window.                                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             Pointer to a word into which the  */
/*             address of a reformat_cell will   */
/*             be returned - the given line lies */
/*             in this cell;                     */
/*                                               */
/*             A WimpGetWindowStateBlock pointer */
/*             holding details of the browser    */
/*             window;                           */
/*                                               */
/*             1 if the line must be wholly      */
/*             visible, else 0 if it may be just */
/*             partially visible.                */
/*                                               */
/* Returns:    Directly, the number of the line  */
/*             displayed at the bottom of the    */
/*             window, or -1 for none / an       */
/*             error.                            */
/*                                               */
/* Assumes:    That none of the parameter        */
/*             pointers are NULL.                */
/*************************************************/

int browser_bottom_line(browser_data * b, reformat_cell ** ret_cell,
                        WimpGetWindowStateBlock * s, int fully_visible)
{
  if (!ret_cell) return -1;

  *ret_cell = NULL;

  return browser_bottom_line_r(b, b->cell, ret_cell, s, fully_visible);
}

/*************************************************/
/* browser_bottom_line_r()                       */
/*                                               */
/* Recursive back-end to browser_bottom_line.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             holding the cells to scan;        */
/*                                               */
/*             Pointer to a word into which the  */
/*             address of a reformat_cell will   */
/*             be returned - the given line lies */
/*             in this cell;                     */
/*                                               */
/*             A WimpGetWindowStateBlock pointer */
/*             holding details of the browser    */
/*             window;                           */
/*                                               */
/*             1 if the line must be wholly      */
/*             visible, else 0 if it may be just */
/*             partially visible.                */
/*                                               */
/* Returns:    As browser_bottom_line.           */
/*                                               */
/* Assumes:    As browser_bottom_line.           */
/*************************************************/

static int browser_bottom_line_r(browser_data * b, reformat_cell * cell, reformat_cell ** ret_cell,
                                 WimpGetWindowStateBlock * s, int fully_visible)
{
  int y, l, hbot;

  if (!cell) cell = b->cell;

  if (!cell->nlines || !cell->ldata || !cell->cdata) return -1;

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

  if (hbot) hbot += wimpt_dy();

  y = s->yscroll                                    -
      (s->visible_area.ymax - s->visible_area.ymin) -
      hbot;

  l = cell->nlines - 1;

  /* Find the line */

  while (
          l >= 0 &&
          cell->ldata[l].y + (fully_visible ? 0 : cell->ldata[l].h) < y
        )
        l--;

  /* If l < 0, nothing was found */

  if (l < 0) return -1;
  else
  {
    HStream * first_token = cell->cdata[cell->ldata[l].chunks].t;

    *ret_cell = cell;

    /* Otherwise, is there a table in the line? */

    if (first_token->tagno == TAG_TABLE)
    {
      table_stream   * table     = (table_stream *) first_token;
      table_row      * row       = NULL;
      table_headdata * head      = NULL;
      reformat_cell  * c         = NULL;
      reformat_cell  * cellarray = table->cells;
      int              cellmax   = table->ColSpan * table->RowSpan;
      int              cellcount = 0;
      int              cellindex;

      /* Proceed if the cell array can be found */

      if (cellarray)
      {
        /* Since we're finding the bottom line and table cells are arranged */
        /* left to right, top to bottom, we need to start at the last row   */
        /* of the last column.                                              */

        row = table->List;

        while (row && row->Next) row = row->Next;

        while (row && cellcount < cellmax)
        {
          head = row->List;

          while (head && head->Next) head = head->Next;

          while (head && cellcount < cellmax)
          {
            switch (head->Tag)
            {
              case TagTableData:
              case TagTableHead:
              {
                /* Find the reformat_cell structure for this table cell */

                cellindex = head->RowOffs * table->ColSpan + head->ColOffs;

                if (cellindex < cellmax)
                {
                  c = &cellarray[cellindex];

                  /* If it has lines, recursively call this function to find the */
                  /* cell line at the top of the window.                         */

                  if (c->nlines)
                  {
                    l = browser_bottom_line_r(b, c, ret_cell, s, fully_visible);

                    /* If a line can be found inside this cell then return it immediately, */
                    /* rather than worrying about the other cells. Otherwise, continue     */
                    /* scanning cells. Since table cells are arranged left right, top to   */
                    /* bottom, and we are looking for the top line, this will work OK.     */

                    if (l >= 0) return l;
                  }
                }
              }
              break;
            }

            cellcount ++;

//            head = head->Prev;

          /* Closure of 'while (head && ...)' */
          }

//          row = row->Prev;

        /* Closure of 'while (row && ...)' */
        }

      /* Closure of 'if (cellarray)' */
      }

    /* Closure of 'if (first_token->tagno == TAG_TABLE)' */
    }

  /* Closure of 'else' case for 'if (l >= cell->nlines)' */
  }

  return l;
}

/*************************************************/
/* browser_redraw_border()                       */
/*                                               */
/* Does a series of Wimp_ForceRedraw calls that  */
/* cause a border of 4 OS units around a given   */
/* image to be redrawn.                          */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to a token representing   */
/*             the image.                        */
/*************************************************/

static _kernel_oserror * browser_redraw_border(browser_data * b, HStream * token)
{
  BBox                      r1;
  int                       more, x, y, w, h;
  _kernel_oserror         * e;
  WimpGetWindowStateBlock   state;
  WimpRedrawWindowBlock     redraw;

  /* Find the position of the image */

  e = reformat_get_image_size(b, token, &r1);
  if (e) return e;

  w = r1.xmax - r1.xmin;
  h = r1.ymax - r1.ymin;

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

  /* Find the x and y coordinates of the image's bottom left hand corner */

  if (image_get_token_image_position(b, token, &x, &y)) return NULL;

  /* Work out the screen coordinates of the image, with an extra */
  /* amount account for the selection border.                    */

  r1.xmin = x - 4;
  r1.ymin = y - 4;
  r1.xmax = r1.xmin + w + 8;
  r1.ymax = r1.ymin + h + 8;

  redraw.xscroll = state.xscroll;
  redraw.yscroll = state.yscroll;

  /* Bottom edge */

  redraw.visible_area.xmin = r1.xmin, redraw.visible_area.ymin = r1.ymin;
  redraw.visible_area.xmax = r1.xmax, redraw.visible_area.ymax = r1.ymin + 4;

  e = wimp_update_window(&redraw, &more);
  if (e) return e;

  if (more)
  {
    e = redraw_draw(b, &redraw, 0, token);
    if (e) return e;
  }

  /* Left edge */

  redraw.visible_area.xmin = r1.xmin,     redraw.visible_area.ymin = r1.ymin;
  redraw.visible_area.xmax = r1.xmin + 4, redraw.visible_area.ymax = r1.ymax;

  e = wimp_update_window(&redraw, &more);
  if (e) return e;

  if (more)
  {
    e = redraw_draw(b, &redraw, 0, token);
    if (e) return e;
  }

  /* Top edge */

  redraw.visible_area.xmin = r1.xmin, redraw.visible_area.ymin = r1.ymax - 4;
  redraw.visible_area.xmax = r1.xmax, redraw.visible_area.ymax = r1.ymax;

  e = wimp_update_window(&redraw, &more);
  if (e) return e;

  if (more)
  {
    e = redraw_draw(b, &redraw, 0, token);
    if (e) return e;
  }

  /* Right edge */

  redraw.visible_area.xmin = r1.xmax - 4, redraw.visible_area.ymin = r1.ymin;
  redraw.visible_area.xmax = r1.xmax,     redraw.visible_area.ymax = r1.ymax;

  e = wimp_update_window(&redraw, &more);
  if (e) return e;

  if (more) return redraw_draw(b, &redraw, 0, token);

  return NULL;
}

/*************************************************/
/* browser_update()                              */
/*                                               */
/* Updates a window contents, using calls to     */
/* Wimp_UpdateWindow (so this can be used for    */
/* animations, etc., as the Wimp won't clear     */
/* the redraw area first).                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             Pointer to a RedrawWindow block,  */
/*             with details of the area to       */
/*             redraw within it;                 */
/*                                               */
/*             1 to not draw backgrounds, or 0   */
/*             to allow them to be drawn;        */
/*                                               */
/*             0 to draw tokens normally, else a */
/*             pointer to a token which is not   */
/*             to have its contents redrawn,     */
/*             except as a selection indicator   */
/*             (see redraw_draw for more).       */
/*************************************************/

_kernel_oserror * browser_update(browser_data * b, WimpRedrawWindowBlock * r, int noback, HStream * nocontent)
{
  int more;

  r->window_handle = b->window_handle;

  wimp_update_window(r, &more);

  if (more) return redraw_draw(b, r, noback, nocontent);

  return NULL;
}

/*************************************************/
/* browser_update_token()                        */
/*                                               */
/* Redraws a given token, trying to minimise any */
/* flicker as the redraw is done.                */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token to redraw;  */
/*                                               */
/*             The token address;                */
/*                                               */
/*             1 to not draw backgrounds, or 0   */
/*             to allow them to be drawn;        */
/*                                               */
/*             0 to draw tokens normally, else a */
/*             pointer to a token which is not   */
/*             to have its contents redrawn,     */
/*             except as a selection indicator   */
/*             (see redraw_draw for more).       */
/*                                               */
/* Returns:    Pointer to a _kernel_oserror      */
/*             struct, which is NULL unless the  */
/*             actual redraw call fails - so if  */
/*             the given token does not appear   */
/*             to be represented by any line,    */
/*             the routine fails silently.       */
/*************************************************/

_kernel_oserror * browser_update_token(browser_data * b, HStream * token, int noback, HStream * nocontent)
{
  token_path      * path  = NULL;
  int               first = -1;
  int               fchnk = -1;
  int               last  = -1;
  int               depth;
  reformat_cell   * cell  = NULL;
  _kernel_oserror * e     = NULL;
  int               x, y;

  /* Find the range of lines the token spans */

  depth = tokenutils_line_range(b, token, &first, &fchnk, &last, NULL, &path);

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

  tokenutils_token_offset(b, path, &x, &y);

  /* 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)
  {
    e = browser_update_token_r(b, cell, token, first, fchnk, last, x, y, noback, nocontent);
  }
  else
  {
    e = browser_update_token_r(b, b->cell, token, first, fchnk, last, 0, 0, noback, nocontent);
  }

  return e;
}

/*************************************************/
/* browser_update_token_r()                      */
/*                                               */
/* Recursive back-end to browser_update_token.   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token to redraw;  */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             which holds the token or a table  */
/*             holding the token;                */
/*                                               */
/*             First line to check in the cell;  */
/*                                               */
/*             First chunk on the first line;    */
/*                                               */
/*             Last line to check in the cell;   */
/*                                               */
/*             The token address;                */
/*                                               */
/*             Cell origin x (window coords);    */
/*                                               */
/*             Cell origin y (window coords);    */
/*                                               */
/*             1 to not draw backgrounds, or 0   */
/*             to allow them to be drawn;        */
/*                                               */
/*             0 to draw tokens normally, else a */
/*             pointer to a token which is not   */
/*             to have its contents redrawn,     */
/*             except as a selection indicator   */
/*             (see redraw_draw for more).       */
/*                                               */
/* Returns:    As browser_update_token.          */
/*************************************************/

static _kernel_oserror * browser_update_token_r(browser_data * b, reformat_cell * cell, HStream * token,
                                                int first, int chunk, int last, int base_x, int base_y,
                                                int noback, HStream * nocontent)
{
  int                     l, widen;
  WimpRedrawWindowBlock   r;
  _kernel_oserror       * e;
  int                     x, y;

  if (!cell) cell = b->cell;

  /* Items that may have borders drawn around them need to have */
  /* the redraw area widened to cope with that.                 */

  if ((token->style & FORM) || (token->style & IMG)) widen = 4;
  else                                               widen = 0;

  if (first >= 0)
  {
    for (l = first; l <= last; l++)
    {
      /* Get the y coordinate of the bottom of the line into y */

      y = base_y + cell->ldata[l].y;

      /* Get the left hand x coordinate in x - the given token for the */
      /* first line, or the left hand edge for subsequent ones.        */

      if (l == first) x = base_x + redraw_token_x(b, cell, token, l);
      else            x = base_x + redraw_start_x(b, cell, cell->cdata[cell->ldata[l].chunks].t, l);

      /* Fill in the redraw block's visible area field */

      r.visible_area.xmin = x - 4;
      r.visible_area.ymin = y - widen;

      /* xmax is up to the last chunk's right hand edge for anything but */
      /* the last line, when we go as far as chunks that use this token. */

      if (l == last)
      {
        int w = 0, c, mc;

        /* For the first line, the chunk to start counting on will be  */
        /* defined by the given token; otherwise, it will be the first */
        /* one on the line.                                            */

        if (l == first) c = chunk;
        else            c = cell->ldata[l].chunks;

        /* Count up to either a chunk which doesn't use the given token */
        /* or is the last chunk on the line                             */

        mc = cell->ldata[l].chunks + cell->ldata[l].n;

        while (c < mc && cell->cdata[c].t == token) w += cell->cdata[c].w, c++;

        /* Convert to OS units and add to the left hand edge */

        convert_to_os(w, &w);

        r.visible_area.xmax = w + r.visible_area.xmin + 8;
      }
      else
      {
        /* Width of the last chunk */

        convert_to_os(cell->cdata[cell->ldata[l].chunks + cell->ldata[l].n - 1].w, &r.visible_area.xmax);

        /* Add the left hand edge of that chunk */

        r.visible_area.xmax += base_x + redraw_chunk_x(b, cell, cell->ldata[l].chunks + cell->ldata[l].n - 1, l) + 8;
      }

      /* ymax is the bottom line plus the line height */

      r.visible_area.ymax = r.visible_area.ymin + cell->ldata[l].h + widen * 2 - (widen == 0);

      e = browser_update(b, &r, noback, nocontent);
      if (e) return e;
    }
  }

  return NULL;
}

/*************************************************/
/* browser_update_bottom()                       */
/*                                               */
/* Redraws the browser window, from a given      */
/* work area y coordinate downwards.             */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window to redraw; */
/*                                               */
/*             The y coordinate to redraw from.  */
/*************************************************/

_kernel_oserror * browser_update_bottom(browser_data * b, int top_y)
{
  int                       more;
  _kernel_oserror         * e;
  WimpRedrawWindowBlock     r;

  #ifdef TRACE
    if (tl & (1u<<9)) Printf("\nbrowser_update_bottom: Called, -top_y = %d\n",-top_y);
  #endif

  /* Fill in the visible area and scroll info from the */
  /* GetWindowState call                               */

  r.window_handle = b->window_handle;

  /* Due to the way the veneer works and the structure is defined, */
  /* the visible_area here is actually the redraw block. The call  */
  /* to wimp_update_window will exit eventually through the        */
  /* Wimp_GetRectangle SWI, which fills in 'r' properly.           */

  r.visible_area.xmin = -0x1000001; /* Big numbers to ensure the whole */
  r.visible_area.xmax = 0x1000000;  /* work area width is redrawn      */

  r.visible_area.ymin = -0x1000001; /* Go right to the bottom          */
  r.visible_area.ymax = top_y;

  /* The redraw loop itself */

  wimp_update_window(&r,&more);

  /* 'r' now holds information that more sensibly relates to its field names... */

  if (more)
  {
    e = redraw_draw(b, &r, 0, 0);

    if (e) return e;
  }

  #ifdef TRACE
    else if (tl & (1u<<9)) Printf("\nbrowser_update_bottom: Nothing to redraw\n");

    if (tl & (1u<<9)) Printf("\nbrowser_update_bottom: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* browser_highlight_token()                     */
/*                                               */
/* Redraws a given token in a highlighted state; */
/* see redraw_token_colour in Redraw.c for the   */
/* colour it will be drawn as.                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             The token address.                */
/*************************************************/

void browser_highlight_token(browser_data * b, HStream * token)
{
  int noback = 0;

  /* Ensure all current highlighting is cleared and remember  */
  /* that this token has been highlighted in the browser_data */
  /* structure.                                               */

  browser_clear_highlight(b, 1);
  b->highlight=token;

  /* If the token represents a link, that link may be made of several */
  /* tokens (e.g. a heading where font sizes were used to make the    */
  /* first letters bigger, or where there is a style change in the    */
  /* middle to highlight some search result for a Web search engine,  */
  /* say). So need to find the first of the list of tokens with the   */
  /* same anchor, and the last.                                       */

  if ((token->style & A) && token->anchor)
  {
    HStream * top = NULL;
    HStream * end = NULL;

    tokenutils_anchor_range(b, token, &top, &end);

    /* Now redraw between those two tokens */

    if (top && end)
    {
      while (top != end->next)
      {
        #ifndef ANTI_TWITTER
          if (
               b->background_image < 0 ||
               (
                 (top->style & IMG) &&
                 (
                   (
                     b->show_foreground               &&
                     image_token_plot_started(b, top)
                   )
                   || b->displayed == Display_External_Image
                 )
               )
             )
             noback = 1;

          else noback = 0;
        #endif

        b->highlight = top;
        browser_update_token(b, top, noback, 0);
        top = top->next;
      }
    }
    else
    {
      /* Something went wrong above, or there is only one token  */
      /* for this link - so use quicker redraw code.             */

      browser_update_token(b, token, noback, 0);
    }
  }

  /* If the token is not a link, just redraw it, as asked. */

  else browser_update_token(b, token, noback, 0);
}

/*************************************************/
/* browser_clear_highlight()                     */
/*                                               */
/* Removes a highlight shown using the           */
/* browser_highlight_token function, with an     */
/* optional delay before doing so.               */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             1 to wait first, else 0.          */
/*************************************************/

void browser_clear_highlight(browser_data * b, int wait)
{
  int noback = 0;

  /* If nothing's highlighted, don't need to do anything else! */

  if (b->highlight)
  {
    HStream * token;

    /* Clear the highlight, possibly waiting 20 centiseconds first. */

    token = b->highlight;
    b->highlight = NULL;

    if (wait)
    {
      int start_time, now;

      _swix(OS_ReadMonotonicTime, _OUT(0), &start_time);
      now = start_time;

      while (now - start_time <= 20) _swix(OS_ReadMonotonicTime, _OUT(0), &now);
    }

    /* For details on this, see browser_highlight_token. Basically, */
    /* may need to redraw several tokens if the highlighted one was */
    /* a link, if they all represent the same link.                 */

    if ((token->style & A) && token->anchor)
    {
      HStream * top = NULL;
      HStream * end = NULL;

      tokenutils_anchor_range(b, token, &top, &end);

      /* Now redraw between those two tokens */

      if (top && end)
      {
        while (top != end->next)
        {
          #ifndef ANTI_TWITTER
            if (
                 b->background_image < 0 ||
                 (
                   (top->style & IMG) &&
                   (
                     (
                       b->show_foreground               &&
                       image_token_plot_started(b, top)
                     )
                     || b->displayed == Display_External_Image
                   )
                 )
               )
               noback = 1;

            else noback = 0;
          #endif

          browser_update_token(b, top, noback, 0);
          top = top->next;
        }
      }
      else
      {
        /* Something went wrong above, or there is only one token  */
        /* for this link - so use quicker redraw code.             */

        browser_update_token(b, token, noback, 0);
      }
    }
    else browser_update_token(b, token, noback, 0);
  }
}

/*************************************************/
/* browser_flash_token()                         */
/*                                               */
/* 'Flashes' a token, by highlighting it with    */
/* browser_highlight_token and then clearing     */
/* the highlight after a short delay.            */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             The token address.                */
/*************************************************/

void browser_flash_token(browser_data * b, HStream * token)
{
  browser_highlight_token(b, token);
  browser_clear_highlight(b, 1);
}

/*************************************************/
/* browser_select_token()                        */
/*                                               */
/* Redraws a given token in a selected state.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             The token address;                */
/*                                               */
/*             1 to scroll the window if needed  */
/*             to keep the token in the visible  */
/*             area, else 0.                     */
/*************************************************/

void browser_select_token(browser_data * b, HStream * token, int visible)
{
  int            noback   = 0;
  browser_data * ancestor = utils_ancestor(b);

  /* Ensure any selected token is deselected, and remember */
  /* that this token has been marked as selected in the    */
  /* browse_data structure.                                */

  if (ancestor->selected_owner) browser_clear_selection(ancestor->selected_owner, 0);

  ancestor->selected       = token;
  ancestor->selected_owner = b;

  /* The selected frame is always the one with the highlighted token in it */

  if (ancestor->selected_frame != b)
  {
    frames_highlight_frame(b);

    ancestor->selected_frame = b;
  }

  /* If asked to, ensure the token is visible */

  if (visible)
  {
    WimpGetWindowStateBlock s;

    s.window_handle = b->window_handle;

    if (!wimp_get_window_state(&s)) browser_ensure_visible(b, &s, token);
  }

  /* If the token represents a link, that link may be made of several */
  /* tokens (e.g. a heading where font sizes were used to make the    */
  /* first letters bigger, or where there is a style change in the    */
  /* middle to highlight some search result for a Web search engine,  */
  /* say). So need to find the first of the list of tokens with the   */
  /* same anchor, and the last.                                       */

  if ((token->style & A) && token->anchor)
  {
    HStream * top = NULL;
    HStream * end = NULL;

    tokenutils_anchor_range(b, token, &top, &end);

    /* Now redraw between those two tokens */

    if (top && end)
    {
      while (top != end->next)
      {
        #ifndef ANTI_TWITTER
          if (
               b->background_image < 0 ||
               (
                 (top->style & IMG) &&
                 (
                   (
                     b->show_foreground               &&
                     image_token_plot_started(b, top)
                   )
                   || b->displayed == Display_External_Image
                 )
               )
             )
             noback = 1;

          else noback = 0;
        #endif

        b->selected = top;

        if (
             b->show_foreground               &&
             (top->style & IMG)               &&
             (ISLINK(top))                    &&
             !top->maxlen                     && /* maxlen=0 means there's no border already there */
             image_token_plot_started(b, top)
           )
           browser_redraw_border(b, top);

        else browser_update_token(b, top, noback, 0);

        top = top->next;
      }
    }
    else
    {
      /* Something went wrong above, or there is only one token  */
      /* for this link - so use quicker redraw code.             */

      if (
           b->show_foreground                 &&
           (token->style & IMG)               &&
           (ISLINK(token))                    &&
           !top->maxlen                       && /* maxlen=0 means there's no border already there */
           image_token_plot_started(b, token)
         )
         browser_redraw_border(b, token);

      else browser_update_token(b, token, noback, 0);
    }
  }

  /* If the token is not a link, just redraw it, as asked. */

  else browser_update_token(b, token, noback, 0);

  return;
}

/*************************************************/
/* browser_clear_selection()                     */
/*                                               */
/* Removes a selection shown using the           */
/* browser_select_token function, with an        */
/* delay before doing so.                        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             1 to wait first, else 0.          */
/*************************************************/

void browser_clear_selection(browser_data * b, int wait)
{
  int            noback   = 0;
  browser_data * ancestor = utils_ancestor(b);
  browser_data * owner;

  if (!ancestor) return;

  /* If nothing's selected, don't need to do anything else! */

  if (ancestor->selected)
  {
    HStream * token;

    /* Clear the selection, possibly waiting 20 centiseconds first. */

    token = ancestor->selected;
    owner = ancestor->selected_owner;

    ancestor->selected       = NULL;
    ancestor->selected_owner = NULL;

    if (wait)
    {
      int start_time, now;

      _swix(OS_ReadMonotonicTime, _OUT(0), &start_time);
      now = start_time;

      while (now - start_time <= 20) _swix(OS_ReadMonotonicTime, _OUT(0), &now);
    }

    /* For details on this, see browser_select_token. Basically, may */
    /* need to redraw several tokens if the selected one was a link, */
    /* if they all represent the same link.                          */

    if ((token->style & A) && token->anchor)
    {
      HStream * top = NULL;
      HStream * end = NULL;

      tokenutils_anchor_range(owner, token, &top, &end);

      /* Now redraw between those two tokens */

      if (top && end)
      {
        while (top != end->next)
        {
          #ifndef ANTI_TWITTER
            /* If using anti-twitter redraws, always redraw the background.   */
            /* Otherwise, redraw it unless this is a text item (well, neither */
            /* an image nor a form element) as for text items, can just       */
            /* replot the text.                                               */

            if (
                 !(top->style & IMG)  &&
                 !(top->style & FORM) &&
                 owner->background_image < 0
               )
               noback = 1;

            else noback = 0;
          #endif

          /* Images which are links and are having only the selection marker */
          /* redrawn go through a special routine.                           */

          if (
               owner->show_foreground               &&
               (top->style & IMG)                   &&
               (ISLINK(top))                        &&
               !top->maxlen                         && /* maxlen=0 means there's no border already there */
               image_token_plot_started(owner, top)
             )
             browser_redraw_border(owner, top);

          else browser_update_token(owner, top, noback, 0);

          top = top->next;
        }
      }
      else
      {
        /* Something went wrong above, or there is only one token  */
        /* for this link - so use quicker redraw code.             */

        if (
             owner->show_foreground                 &&
             (token->style & IMG)                   &&
             (ISLINK(token))                        &&
             !top->maxlen                           && /* maxlen=0 means there's no border already there */
             image_token_plot_started(owner, token)
           )
           browser_redraw_border(owner, token);

        else browser_update_token(owner, token, noback, 0);
      }
    }
    else browser_update_token(owner, token, noback, 0);
  }

  return;
}

/*************************************************/
/* browser_show_token()                          */
/*                                               */
/* Shows a given token at the top of the browser */
/* window.                                       */
/*                                               */
/* If the token is near the bottom of the page,  */
/* then the page extent is increased so that the */
/* window may still be scrolled to show the      */
/* token at the top. Despite introducing some    */
/* dead space at the page base, it gets very     */
/* confusing - particularly with Find functions  */
/* - to not be able to rely on the token being   */
/* actually moved to the top of the window.      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             The token address;                */
/*                                               */
/*             Offset into the token data which  */
/*             the chunk representing the token  */
/*             must straddle - i.e., it must     */
/*             start at less than or equal to    */
/*             this offset and end at greater    */
/*             than it.                          */
/*                                               */
/* Returns:    1 for success, 0 for failure.     */
/*************************************************/

int browser_show_token(browser_data * b, HStream * token, int offset)
{
  WimpGetWindowStateBlock   s;
  token_path              * path = NULL;
  reformat_cell           * cell = NULL;
  int                       orx, ory;
  int                       c, l, topy, htop, fy;
  int                       found;
  int                       depth;

  if (!token) return 0;

  /* Find the range of lines the token spans */

  depth = tokenutils_line_range(b, token, &l, NULL, 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 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;
  }

  /* Can't do anything if there are no lines in the cell */

  if (!cell->nlines || !cell->ldata || !cell->cdata) return 0;

  /* Start by checking the token isn't already at the top of the window */

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

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

  if (htop) htop += wimpt_dy();

  topy = s.yscroll - htop;

  l = 0;
  while (l < cell->nlines && cell->ldata[l].y + cell->ldata[l].h + ory > topy) l++;

  if (l < cell->nlines)
  {
    for (c = 0; c < cell->ldata[l].n; c++);
    {
      /* If the chunk represents the given token,                          */
      /* its data offset is less than or equal to the offset specified, or */
      /* its data offset plus length is greater than the offset specified, */
      /* then we've found the token, it's already at the top so just exit  */
      /* but flag that the routine was successful.                         */

      if (
           cell->cdata[cell->ldata[l].chunks + c].t == token &&
           cell->cdata[cell->ldata[l].chunks + c].o <= offset &&
           cell->cdata[cell->ldata[l].chunks + c].l + cell->cdata[cell->ldata[l].chunks + c].o > offset
         )
         return 1;
    }
  }

  /* It wasn't at the top, so need to do a bit more work. */

  l  = topy  = 0;
  fy = found = 0;

  /* Loop through all the lines */

  while (l < cell->nlines && !found)
  {
    /* Set y to the coordinate of the topmost part of this line (hence the '-1') */

    topy = cell->ldata[l].y + cell->ldata[l].h + ory - 1;

    /* Loop through this line's chunks */

    for (c = 0; c < cell->ldata[l].n && !found; c++)
    {
      /* Again, if the chunk represents the given token,                         */
      /* has an offset below or equal to the given offset, or                    */
      /* either has a data offset plus length greater than the offset specified  */
      /* or has zero data length,                                                */
      /* then flag that we've found the token and get the y coordinate of the    */
      /* top of this line into fy.                                               */

      if (
           cell->cdata[cell->ldata[l].chunks + c].t == token &&
           cell->cdata[cell->ldata[l].chunks + c].o <= offset &&
           (
             cell->cdata[cell->ldata[l].chunks + c].l + cell->cdata[cell->ldata[l].chunks + c].o > offset ||
             cell->cdata[cell->ldata[l].chunks + c].l == 0
           )
         )
         fy = topy, found = 1;
    }

    l++;
  }

  /* If found = 0, the loop didn't find the requested token, */
  /* so exit, flagging failure.                              */

  if (!found) return 0;

  /* Jump to the scroll position recorded in fy. */

  s.yscroll = fy + htop;

  /* Is the window extent great enough? */

  {
    BBox extent;
    int  bottom_coord;

    window_get_extent(0, b->self_id, &extent);

    bottom_coord = s.yscroll - (s.visible_area.ymax - s.visible_area.ymin);

    /* If the extent is not great enough, increase it */

    if (extent.ymin >= bottom_coord)
    {
      extent.ymin = bottom_coord;

      if (window_set_extent(0, b->self_id, &extent)) return 0;
    }
  }

  if (wimp_open_window((WimpOpenWindowBlock *) &s)) return 0;

  return 1;
}

/*************************************************/
/* browser_ensure_visible()                      */
/*                                               */
/* Ensures that a given token is wholly visible  */
/* in the browser window, scrolling down or up   */
/* if needed.                                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             A WimpGetWindowStateBlock pointer */
/*             for the browser window;           */
/*                                               */
/*             Pointer to the token itself.      */
/*                                               */
/* Returns:    1 if the window was scrolled,     */
/*             else 0. If there is an error      */
/*             internally, 2 is returned.        */
/*************************************************/

int browser_ensure_visible(browser_data * b, WimpGetWindowStateBlock * state, HStream * token)
{
  int             lfir, llas, ltop, lbot;
  int             ytop, ybot, htop, hbot;
  HStream       * ttop;
  HStream       * tend;
  reformat_cell * cell_s  = NULL, * cell_e = NULL;
  token_path    * path_s  = NULL, * path_e = NULL;
  int             depth_s = 0;
  int             depth_e = 0;
  int             orx_s, ory_s;
  int             orx_e, ory_e;

  if (!token) return 0;

  /* Find the first and last lines to include this token range */

  tokenutils_anchor_range(b, token, &ttop, &tend);

  if (!ttop || !tend) depth_s = tokenutils_line_range(b, token, &lfir, NULL, &llas, NULL, &path_s);
  else
  {
    depth_s = tokenutils_line_range(b, ttop, &lfir, NULL, NULL, NULL, &path_s);
    depth_e = tokenutils_line_range(b, tend, NULL, NULL, &llas, NULL, &path_e);
  }

  /* If the first line can't be found, can't proceed */

  if (lfir < 0) return 0;

  /* Find the start/end origin offset */

  tokenutils_token_offset(b, path_s, &orx_s, &ory_s);
  tokenutils_token_offset(b, path_s, &orx_e, &ory_e);

  /* Find the start/end cell (*should* be the same...) */

  cell_s = tokenutils_find_cell(b->cell, depth_s, path_s);
  cell_e = tokenutils_find_cell(b->cell, depth_e, path_e);

  if (!cell_s)
  {
    cell_s = b->cell;
    orx_s  = 0;
    ory_s  = 0;
  }

  if (!cell_e)
  {
    cell_e = b->cell;
    orx_e  = 0;
    ory_e  = 0;
  }

  /* Free the token_path arrays */

  if (path_s) free (path_s);
  if (path_e) free (path_e);

  /* If the last line couldn't be found, the token only */
  /* spans the one line.                                */

  if (llas < 0) llas = lfir, cell_e = cell_s;

  /* Work out the top and bottom y coordinates that the token spans */

  ltop = ory_s + cell_s->ldata[lfir].y + cell_s->ldata[lfir].h - 1; /* '-1' as we want this to be an inclusive coord */
  lbot = ory_e + cell_e->ldata[llas].y;

  /* Work out where the visible page region starts and ends */
  /* (affected by window scrolling and toolbar presence).   */

  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();

  ytop = state->yscroll - htop;
  ybot = state->yscroll - (state->visible_area.ymax - state->visible_area.ymin) + hbot;

  /* It would be possible for the token to be big, or the window to be small, */
  /* and both the bottom and top of it be off the window. In this case, take  */
  /* the case where the top is not visible over the case where the bottom is  */
  /* not visible.                                                             */

  if (ltop > ytop)
  {
    state->yscroll = ltop + htop + 8; /* '+8' = aesthetics */
    if (wimp_open_window((WimpOpenWindowBlock *) state)) return 2;
    else return 1;
  }
  else if (lbot < ybot)
  {
    state->yscroll = lbot + (state->visible_area.ymax - state->visible_area.ymin) - hbot - 8; /* '-8 = aesthetics */
    if (wimp_open_window((WimpOpenWindowBlock *) state)) return 2;
    else return 1;
  }

  return 0;
}

/*************************************************/
/* browser_check_visible()                       */
/*                                               */
/* Checks to see if a given token is wholly or   */
/* partially visible in a given browser window.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             A WimpGetWindowStateBlock pointer */
/*             for the browser window (may be    */
/*             NULL);                            */
/*                                               */
/*             Pointer to the token itself.      */
/*                                               */
/* Returns:    1 if the token is wholly visible, */
/*             2 if it is partially visible,     */
/*             else 0. If there is an error      */
/*             internally, 0 is returned.        */
/*                                               */
/* Assumes:    If the WimpGetWindowStateBlock    */
/*             pointer is NULL, the function     */
/*             will find the information out     */
/*             itself. The block pointer can be  */
/*             passed in as calling functions    */
/*             may well already have this block  */
/*             available, so it makes sense to   */
/*             avoid finding it out again.       */
/*************************************************/

int browser_check_visible(browser_data * b, WimpGetWindowStateBlock * state, HStream * token)
{
  WimpGetWindowStateBlock   s;
  int                       lfir, llas, ltop, lbot;
  int                       ytop, ybot, htop, hbot;
  HStream                 * ttop;
  HStream                 * tend;
  reformat_cell           * cell_s  = NULL, * cell_e = NULL;
  token_path              * path_s  = NULL, * path_e = NULL;
  int                       depth_s = 0;
  int                       depth_e = 0;
  int                       orx_s, ory_s;
  int                       orx_e, ory_e;

  if (!token) return 0;

  /* Get the window state, if necessary */

  if (!state)
  {
    s.window_handle = b->window_handle;
    if (wimp_get_window_state(&s)) return 0;

    state = &s;
  }

  /* Find the first and last lines to include this token range */

  tokenutils_anchor_range(b, token, &ttop, &tend);

  if (!ttop || !tend) depth_s = tokenutils_line_range(b, token, &lfir, NULL, &llas, NULL, &path_s);
  else
  {
    depth_s = tokenutils_line_range(b, ttop, &lfir, NULL, NULL, NULL, &path_s);
    depth_e = tokenutils_line_range(b, tend, NULL, NULL, &llas, NULL, &path_e);
  }

  /* If the first line can't be found, can't proceed */

  if (lfir < 0) return 0;

  /* Find the start/end origin offset */

  tokenutils_token_offset(b, path_s, &orx_s, &ory_s);
  tokenutils_token_offset(b, path_s, &orx_e, &ory_e);

  /* Find the start/end cell (*should* be the same...) */

  cell_s = tokenutils_find_cell(b->cell, depth_s, path_s);
  cell_e = tokenutils_find_cell(b->cell, depth_e, path_e);

  if (!cell_s)
  {
    cell_s = b->cell;
    orx_s  = 0;
    ory_s  = 0;
  }

  if (!cell_e)
  {
    cell_e = b->cell;
    orx_e  = 0;
    ory_e  = 0;
  }

  /* Free the token_path arrays */

  if (path_s) free (path_s);
  if (path_e) free (path_e);

  /* If the last line couldn't be found, the token only */
  /* spans the one line.                                */

  if (llas < 0) llas = lfir, cell_e = cell_s;

  /* Work out where the visible page region starts and ends */
  /* (affected by window scrolling and toolbar presence).   */

  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();

  ytop = state->yscroll - htop;
  ybot = state->yscroll - (state->visible_area.ymax - state->visible_area.ymin) + hbot;

  /* Work out the top and bottom y coordinates that the token spans */

  ltop = ory_s + cell_s->ldata[lfir].y + cell_s->ldata[lfir].h - 1; /* '-1' as we want this to be an inclusive coord */
  lbot = ory_e + cell_e->ldata[llas].y;

  /* Is the token fully visible? */

  if (ltop <= ytop && lbot >= ybot) return 1;

  /* Work out the top and bottom to check for partial visibility */

  ltop = ory_s + cell_s->ldata[lfir].y;
  lbot = ory_e + cell_e->ldata[llas].y + cell_e->ldata[llas].h - 1;

  /* Is the token partially visible? */

  if (ltop <= ytop && lbot >= ybot) return 2;

  /* The token is not in the visible area */

  return 0;
}

/*************************************************/
/* browser_show_named_anchor()                   */
/*                                               */
/* Given an anchor name, ensures that the window */
/* is scrolled to show the token associated      */
/* with that anchor name. If no match can be     */
/* found between the given name and the names of */
/* the tokens, the routine gives an appropriate  */
/* error message back to the user.               */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             Pointer to the anchor name.       */
/*************************************************/

void browser_show_named_anchor(browser_data * b, char * anchor)
{
  char name[Limits_NamedAnchor];

  memset(name, 0, sizeof(name));
  strncpy(name, anchor, sizeof(name) - 2);

  if (
       !browser_show_token(b,
                           fetch_find_anchor_token(b, name),
                           0)
     )
  {
    /* Some broken pages specify the names with a hash in front, */
    /* and still expect them to work. Try to deal with that.     */

    memmove(name + 1, name, strlen(name));
    name[0] = '#';

    if (
         !browser_show_token(b,
                             fetch_find_anchor_token(b, name),
                             0)
       )
    {
      erb.errnum = Utils_Error_Custom_Message;

      /* Give a different message if still fetching */

      if (fetch_fetching(b))
      {
        StrNCpy0(erb.errmess,
                 lookup_token("NoLabelF:The label '%0' cannot be found, but the page is still fetching - try again when the page fetch has finished.",
                              0,
                              anchor))
      }
      else
      {
        StrNCpy0(erb.errmess,
                 lookup_token("NoLabel:The label '%0' cannot be found on this page.",
                              0,
                              anchor))
      }

      /* Report the error and continue from here */

      show_error_ret(&erb);
    }
  }

  return;
}

/*************************************************/
/* browser_display_local_reference()             */
/*                                               */
/* Checks a given URL against a given base URL,  */
/* and if it contains a local reference (i.e.    */
/* has '#<name>' at the end) but otherwise       */
/* matches the base URL, will try to find and    */
/* subsequently display the reference on the     */
/* page.                                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the page;             */
/*                                               */
/*             Pointer to the requested URL      */
/*             which may contain the reference;  */
/*                                               */
/*             Pointer to the base URL against   */
/*             which the first is compared.      */
/*                                               */
/* Returns:    1 if the reference is to be shown */
/*             (though if the reference may not  */
/*             actually be found on the page) or */
/*             0 if the reference will not be    */
/*             shown, because the base URL does  */
/*             not match the requested URL.      */
/*                                               */
/* Assumes:    Either pointer may be NULL,       */
/*             though this will ensure that 0 is */
/*             returned and thus it's not much   */
/*             use giving null pointers.         */
/*             The application must be able to   */
/*             write to the URL data.            */
/*************************************************/

int browser_display_local_reference(browser_data * b, char * url_requested, char * url_current)
{
  char * p1, * p2;

  /* Can't do anything if null pointers are given */

  if (!url_requested || !url_current) return 0;

  /* Can't do anything if the requested URL doesn't have a local reference in it */

  p1 = fetch_find_name_tag(url_requested);
  if (!p1) return 0;

  /* If the base URL contains a reference then point p2 to the first */
  /* character after the main URL (i.e. the '#'), else point p2 to   */
  /* the end of the string.                                          */

  p2 = fetch_find_name_tag(url_current);
  if (!p2) p2 = strchr(url_current, 0);

  /* If the two URLs don't match, return 0 */

  if (
       utils_strncasecmp(url_requested,
                         url_current,
                         (int) p1 - (int) url_requested)
     )
     return 0;

  /* Otherwise, show the reference */

  browser_show_named_anchor(b, p1 + 1);

  return 1;
}

/*************************************************/
/* browser_set_look()                            */
/*                                               */
/* Sets the 'look' of a browser window - i.e.    */
/* underlined links, using document or default   */
/* colour schemes, etc.                          */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             to alter (any in a frameset will  */
/*             do, as all frames are updated);   */
/*                                               */
/*             Object ID of the item generating  */
/*             this change, if appropriate (or   */
/*             zero if not);                     */
/*                                               */
/*             1 to underline links, 0 not to,   */
/*             -1 to not change this state;      */
/*                                               */
/*             1 to use document colours, 0 to   */
/*             use defaults, -1 to not change    */
/*             this state;                       */
/*                                               */
/*             1 to show foreground images, 0    */
/*             not to (any pending image fetches */
/*             are started up again if 1 is      */
/*             given), or -1 to not change this  */
/*             state;                            */
/*                                               */
/*             1 to show background images, 0    */
/*             not to (any pending image fetch   */
/*             for this is started if 1 is       */
/*             given), or -1 to not change this  */
/*             state.                            */
/*************************************************/

_kernel_oserror * browser_set_look(browser_data * b, ObjectId source, int underline_links,
                                   int use_source_cols, int show_foreground, int show_background)
{
  /* Check the browser_data pointer is valid */

  if (!b || !is_known_browser(b)) return NULL;

  /* Find the ancestor and call the recursive back-end with it */

  return browser_set_look_r(utils_ancestor(b),
                            source,
                            underline_links,
                            use_source_cols,
                            show_foreground,
                            show_background);
}

/*************************************************/
/* browser_set_look_r()                          */
/*                                               */
/* Recursive back-end to browser_set_look.       */
/*                                               */
/* Note that if open, the Print Style dialogue   */
/* is updated with these changes, if they apply  */
/* to the browser to which it is relevant (if    */
/* any).                                         */
/*                                               */
/* Parameters: As for browser_set_look. All      */
/*             children of the browser_data      */
/*             struct, along with that given     */
/*             struct, will be updated.          */
/*************************************************/

static _kernel_oserror * browser_set_look_r(browser_data * b, ObjectId source, int underline_links,
                                            int use_source_cols, int show_foreground, int show_background)
{
  int child;
  int redraw = 0;

  /* Scan the child tree */

  if (b->nchildren)
  {
    for (child = 0; child < b->nchildren; child ++)
    {
      RetError(browser_set_look_r(b->children[child],
                                  source,
                                  underline_links,
                                  use_source_cols,
                                  show_foreground,
                                  show_background));
    }
  }

  /* Work through the four options, updating the browser if they */
  /* seem to have changed.                                       */

  if (underline_links >= 0 && underline_links != b->underline_links)
  {
    b->underline_links = underline_links;
    redraw             = 1;
  }

  if (use_source_cols >= 0 && use_source_cols != b->use_source_cols)
  {
    b->use_source_cols = use_source_cols;
    redraw             = 1;
  }

  if (show_foreground >= 0 && show_foreground != b->show_foreground)
  {
    b->show_foreground = show_foreground;
    redraw             = 1;

    /* Restart fetches if required */

    if (show_foreground) image_restart_fetches(b, 1, 0);
  }

  if (show_background >= 0 && show_background != b->show_background)
  {
    b->show_background = show_background;

    /* No need to redraw if there's no background image to show or remove */

    redraw = b->background_image >= 0 ? 1 : 0;

    /* Again restart fetches if required */

    if (show_background) image_restart_fetches(b, 0, 1);
  }

  /* If required, redraw the browser to reflect the changes */

  if (redraw)
  {
    WimpGetWindowStateBlock s;

    s.window_handle = b->window_handle;
    RetError(wimp_get_window_state(&s));

    coords_box_toworkarea(&s.visible_area, (WimpRedrawWindowBlock *) &s);

    RetError(wimp_force_redraw(b->window_handle,
                               s.visible_area.xmin,
                               s.visible_area.ymin,
                               s.visible_area.xmax,
                               s.visible_area.ymax));
  }

  /* If there's a Print Style dialogue opened for this browser, the */
  /* following call will update it appropriately.                   */

  return printstyle_set_look(source,
                             b->self_id,
                             underline_links,
                             use_source_cols,
                             show_foreground,
                             show_background);
}

/*************************************************/
/* browser_give_general_focus()                  */
/*                                               */
/* Places the caret in a browser window, but in  */
/* no particular icon - it will appear in the    */
/* URL bar if one is visible, else the main      */
/* window will gain the input focus but the      */
/* caret will not be visible.                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window.           */
/*************************************************/

_kernel_oserror * browser_give_general_focus(browser_data * b)
{
  _kernel_oserror * e;
  ObjectId          t;

  if (!b) return NULL;

  /* Try to put the caret in a form - exit with NULL if it succeeds */

  if (form_give_focus(b)) return NULL;

  /* OK, put the focus in the URL bar, or if not there, in the main */
  /* window (the caret will be invisible in this latter case).      */
  /*                                                                */
  /* In the special case of the URL bar and status display being    */
  /* merged, the focus is given to the URL writable only if it is   */
  /* currently visible in the window.                               */

  t = toolbars_get_upper(b);

  if (
       t          &&
       b->url_bar &&
       !gadget_hidden(t, URLBarWrit)
     )
     return gadget_set_focus(0, t, URLBarWrit);

  else
  {
    browser_data * ancestor = utils_ancestor(b);

    /* Only give input focus to the ancestor */

    e = wimp_set_caret_position(ancestor->window_handle, -1, 0, 0, -1, -1);
    if (e) return e;

    /* May need to select a new object */

    if (!ancestor->selected && choices.keyboard_ctrl)
    {
      browser_data * next = frames_find_another_frame(ancestor, 0);

      if (next)
      {
        ancestor->selected = browser_find_first_selectable(next, NULL, 0);

        if (ancestor->selected)
        {
          ancestor->selected_owner = next;
          ancestor->selected_frame = b;

          frames_highlight_frame(b);
        }
      }
    }
  }

  return NULL;
}

/*************************************************/
/* browser_inherit()                             */
/*                                               */
/* Makes a given child browser inherit some of   */
/* the characteristics of a given parent.        */
/*                                               */
/* The post_data field is handled specially.     */
/* Please see browser_inherit_post_data for      */
/* details.                                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             representing the parent;          */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             representing the child.           */
/*************************************************/

_kernel_oserror * browser_inherit(browser_data * parent, browser_data * child)
{
  /* Be very cautious! */

  if (!is_known_browser(parent) || !is_known_browser(child)) return NULL;

  /* The local History */

  RetError(history_inherit(parent, child));

  /* Some display flags */

  child->underline_links = parent->underline_links;
  child->show_foreground = parent->show_foreground;
  child->show_background = parent->show_background;
  child->use_source_cols = parent->use_source_cols;

  /* To finish, deal with post_data information */

  return browser_inherit_post_data(parent, child);
}

/*************************************************/
/* browser_inherit_post_data()                   */
/*                                               */
/* Part of browser_inherit which needs to be     */
/* used sometimes for windows that already exist */
/* but are being targetted by a POST form        */
/* submission.                                   */
/*                                               */
/* If there is a flex block attached through the */
/* post_data field of the parent, the contents   */
/* will be copied into a flex block attached to  */
/* the child's post_data field and then *freed*  */
/* in the parent.                                */
/*                                               */
/* This is because at present, the only time the */
/* parent will have such a block is if either an */
/* a button that submits a POST request was      */
/* clicked upon, or if that button targets       */
/* another browser window. In this case, you     */
/* don't want to leave the data attached to the  */
/* parent or the next fetch it does will         */
/* erroneously be sent as POST itself...!        */
/*                                               */
/* NB If the child which is to receive the flex  */
/* data from the parent (assuming the parent has */
/* any to give!) already had stuff attached to   */
/* post_data, this will obviously be freed       */
/* first.                                        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             representing the parent;          */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             representing the child.           */
/*************************************************/

_kernel_oserror * browser_inherit_post_data(browser_data * parent, browser_data * child)
{
  /* Once more, because we may be called from anywhere, */
  /* be very cautious.                                  */

  if (!is_known_browser(parent) || !is_known_browser(child)) return NULL;

  /* If the parent had extra data, we should carry that forward (this */
  /* may have been an adjust-click on a Submit button for a POST form */
  /* for example).                                                    */

  if (parent->post_data)
  {
    int success;
    int size = flex_size(&parent->post_data);

    if (size)
    {
      /* Clear any data in the child */

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

      /* Allocate an appropriate chunk of memory in the child */

      success = flex_alloc(&child->post_data, size);

      /* If the allocation succeeded, so switch off flex budging and */
      /* copy the block contents to the child browser                */

      if (success)
      {
        int oldstate = flex_set_budge(0);

        memcpy(child->post_data, parent->post_data, size);

        /* Restore flex's budge state */

        flex_set_budge(oldstate);
      }

      /* At this point, whether the new flex allocation worked */
      /* or not, free the parent's block (see the comments at  */
      /* the top of the function for more details).            */

      flex_free(&parent->post_data);

      /* If the flex allocation failed, return an error */

      if (!success) return make_no_fetch_memory_error(16);
    }
  }

  return NULL;
}