/* 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   : Frames.c                               */
/*                                                 */
/* Purpose: Frame handling functions for the       */
/*          browser.                               */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 19-Mar-97: Created.                    */
/***************************************************/

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

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

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

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

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

#include "NestWimp.h"

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

#include "Browser.h"
#include "Fetch.h" /* (Which itself includes URLstat.h) */
#include "FetchPage.h"
#include "Images.h"
#include "Memory.h"
#include "Mouse.h"
#include "Redraw.h"
#include "Reformat.h"
#include "Toolbars.h"
#include "URLutils.h"
#include "Windows.h"

#include "Frames.h"

/* Locals */

static int            highlight_timer  = 0;
static int            highlight_for    = 0;
static ObjectId       highlight_top    = 0;
static ObjectId       highlight_bottom = 0;
static ObjectId       highlight_left   = 0;
static ObjectId       highlight_right  = 0;

/* Static function prototypes */

static _kernel_oserror * frames_find_widths            (browser_data * b, int available);
static _kernel_oserror * frames_find_heights           (browser_data * b, int available);
static int               frames_check_recursion        (browser_data * parent, browser_data * child, HStream * token);
static _kernel_oserror * frames_reopen_frame           (browser_data * cb, browser_data * parent, BBox * frame_box);
static void              frames_collapse_child_tree    (browser_data * base, browser_data * real_parent, browser_data * close);

static browser_data    * frames_find_next_frame        (browser_data * check, browser_data * current, int * found);
static browser_data    * frames_find_previous_frame    (browser_data * check, browser_data * current, int * found);

static int               frames_remove_highlight_timer (int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle);

/*************************************************/
/* frames_find_widths()                          */
/*                                               */
/* Constructs an array pointed to by the         */
/* frame_widths field of a browser_data struct,  */
/* containing the widths of frames described by  */
/* the token pointed to in the frameset field of */
/* the browser_data structure.                   */
/*                                               */
/* These frame widths are set to occupy the      */
/* entire space given to the function; any       */
/* border and scroll bar size considerations     */
/* must therefore be done externally.            */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the frameset;         */
/*                                               */
/*             The available width the frameset  */
/*             must fit inside, in OS units.     */
/*************************************************/

static _kernel_oserror * frames_find_widths(browser_data * b, int available)
{
  _kernel_oserror * e;
  int               tw, units;
  int               col, cols, left, stars;

  cols = b->frameset->cols;
  if (!cols) cols = 1;

  /* Allocate memory for the array */

  e = memory_set_chunk_size(b, NULL, CK_FWID, cols * sizeof(int));
  if (e) return e;

  /* Ensure 'available' is a whole number of pixels */

  available &= ~(wimpt_dx() - 1);

  /* Fast simple case - only one column */

  if (cols == 1)
  {
    b->frame_widths[0] = available;
    return NULL;
  }

  /* Initial conditions */

  left  = available;
  stars = 0;

  /* First pass; subtract from the overall width, */
  /* the width of percentage specified frames     */
  /* and absolute pixel size specified frames.    */

  for (col = 0; col < cols; col ++)
  {
    tw     = ((int *) (b->frameset->value))[col];
    units  = tw & ~ROWCOL_VALUE;
    tw    &= ROWCOL_VALUE;

    if (units & ROWCOL_PERCENT)
    {
      tw    = ((available * tw) / 100) & ~(wimpt_dx() - 1);
      left -= tw;

      b->frame_widths[col] = tw;
    }
    else if (units & ROWCOL_STAR)
    {
      stars += tw;
    }
    else
    {
      tw    = (tw * 2) & ~(wimpt_dx() - 1);
      left -= tw;

      b->frame_widths[col] = tw;
    }
  }

  /* Second pass; allocate a fraction of the   */
  /* remaining space to star specified frames. */

  if (stars)
  {
    int remaining;
    int rem_stars = stars;

    remaining = left;

    for (col = 0; col < cols; col ++)
    {
      tw     = ((int *) (b->frameset->value))[col];
      units  = tw & ~ROWCOL_VALUE;
      tw    &= ROWCOL_VALUE;

      if (units & ROWCOL_STAR)
      {
        rem_stars -= tw;

        if (rem_stars) tw = (remaining * tw / stars) & ~(wimpt_dx() - 1);
        else           tw = left;

        if (tw < controls.minimum_frame_width) tw = controls.minimum_frame_width;

        left -= tw;

        b->frame_widths[col] = tw;
      }
    }
  }

  /* Third pass; simple scale to fit all the frames in the */
  /* available space (scaling up or down). 'left' holds,   */
  /* in OS units, the amount left / overshot in the        */
  /* available width.                                      */

  if (left != 0)
  {
    int basic, remainder, left2 = available;

    if (available == left) left = available + 1;

    for (col = 0; col < cols; col ++)
    {
      left2                 -=
      b->frame_widths[col]  =

      (b->frame_widths[col] * available / (available - left)) & ~(wimpt_dx() - 1);
    }

    /* 'left2' is in OS units, but represents a whole number of */
    /* pixels due to careful use of wimpt_dx above. To cope     */
    /* with rounding correctly during rescaling the widths      */
    /* (see below), want this now in pixel values.              */

    left = left2 / wimpt_dx();

    basic     = left / cols;
    remainder = left - (basic * cols);

    basic *= wimpt_dx();

    for (col = 0; col < cols; col ++)
    {
      if (remainder < 0)
      {
        b->frame_widths[col] += basic - wimpt_dx();
        remainder ++;
      }
      else if (remainder > 0)
      {
        b->frame_widths[col] += basic + wimpt_dx();
        remainder --;
      }
      else b->frame_widths[col] += basic;
    }
  }

  return NULL;
}

/*************************************************/
/* frames_find_heights()                         */
/*                                               */
/* Constructs an array pointed to by the         */
/* frame_heights field of a browser_data struct, */
/* containing the heights of frames described by */
/* the token pointed to in the frameset field of */
/* the browser_data structure.                   */
/*                                               */
/* These frame heights are set to occupy the     */
/* entire space given to the function; any       */
/* border and scroll bar size considerations     */
/* must therefore be done externally.            */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the frameset;         */
/*                                               */
/*             The available height the frameset */
/*             must fit inside, in OS units.     */
/*************************************************/

static _kernel_oserror * frames_find_heights(browser_data * b, int available)
{
  _kernel_oserror * e;
  int               th, units;
  int               row, rows, left, stars;

  rows = b->frameset->rows;
  if (!rows) rows = 1;

  /* Allocate memory for the array */

  e = memory_set_chunk_size(b, NULL, CK_FHEI, rows * sizeof(int));
  if (e) return e;

  /* Ensure 'available' is a whole number of pixels */

  available &= ~(wimpt_dy() - 1);

  /* Fast simple case - only one row */

  if (rows == 1)
  {
    b->frame_heights[0] = available;
    return NULL;
  }

  /* Initial conditions */

  left  = available;
  stars = 0;

  /* First pass; subtract from the overall height, */
  /* the height of percentage specified frames     */
  /* and absolute pixel size specified frames.     */

  for (row = 0; row < rows; row ++)
  {
    th     = ((int *) (b->frameset->name))[row];
    units  = th & ~ROWCOL_VALUE;
    th    &= ROWCOL_VALUE;

    if (units & ROWCOL_PERCENT)
    {
      th    = ((available * th) / 100) & ~(wimpt_dy() - 1);
      left -= th;

      b->frame_heights[row] = th;
    }
    else if (units & ROWCOL_STAR)
    {
      stars += th;
    }
    else
    {
      th    = (th * 2) & ~(wimpt_dy() - 1);
      left -= th;

      b->frame_heights[row] = th;
    }
  }

  /* Second pass; allocate a fraction of the   */
  /* remaining space to star specified frames. */

  if (stars)
  {
    int remaining;
    int rem_stars = stars;

    remaining = left;

    for (row = 0; row < rows; row ++)
    {
      th     = ((int *) (b->frameset->name))[row];
      units  = th & ~ROWCOL_VALUE;
      th    &= ROWCOL_VALUE;

      if (units & ROWCOL_STAR)
      {
        rem_stars -= th;

        if (rem_stars) th = (remaining * th / stars) & ~(wimpt_dy() - 1);
        else           th = left;

        if (th < controls.minimum_frame_height) th = controls.minimum_frame_height;

        left -= th;

        b->frame_heights[row] = th;
      }
    }
  }

  /* Third pass; simple scale to fit all the frames in the */
  /* available space (scaling up or down). 'left' holds,   */
  /* in OS units, the amount left / overshot in the        */
  /* available height.                                     */

  if (left != 0)
  {
    int basic, remainder, left2 = available;

    if (available == left) left = available + 1;

    for (row = 0; row < rows; row ++)
    {
      left2                 -=
      b->frame_heights[row]  =

      (b->frame_heights[row] * available / (available - left)) & ~(wimpt_dy() - 1);
    }

    /* 'left2' is in OS units, but represents a whole number of */
    /* pixels due to careful use of wimpt_dy above. To cope     */
    /* with rounding correctly during rescaling the heights     */
    /* (see below), want this now in pixel values.              */

    left = left2 / wimpt_dy();

    basic     = left / rows;
    remainder = left - (basic * rows);

    basic *= wimpt_dy();

    for (row = 0; row < rows; row ++)
    {
      if (remainder < 0)
      {
        b->frame_heights[row] += basic - wimpt_dy();
        remainder ++;
      }
      else if (remainder > 0)
      {
        b->frame_heights[row] += basic + wimpt_dy();
        remainder --;
      }
      else b->frame_heights[row] += basic;
    }
  }

  return NULL;
}

/*************************************************/
/* frames_get_rc_info()                          */
/*                                               */
/* Returns the number of rows and columns in a   */
/* frameset, and the row and column that a       */
/* specific child lies in.                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which is the parent of the set of */
/*             frames in question;               */
/*                                               */
/*             The child number, or -1 if not    */
/*             interested in this info;          */
/*                                               */
/*             Pointer to an int, into which the */
/*             number of rows is placed;         */
/*                                               */
/*             Pointer to an int, into which the */
/*             number of columns is placed;      */
/*                                               */
/*             Pointer to an int, into which the */
/*             row the child lies in is placed;  */
/*                                               */
/*             Pointer to an int, into which the */
/*             column the child lies in is       */
/*             placed.                           */
/*                                               */
/* Assumes:    Any of the four int pointers may  */
/*             be NULL.                          */
/*************************************************/

void frames_get_rc_info(browser_data * parent, int child,
                        int * retrows, int * retcols, int * retrow, int * retcol)
{
  int rows = 0, row = 0, cols = 0, col = 0;

  if (parent->nchildren)
  {
    rows = parent->frameset->rows;
    cols = parent->frameset->cols;

    if (!rows) rows = 1;
    if (!cols) cols = 1;

    if (child >= 0)
    {
      row = child / rows;
      col = child % cols;

      if (row <  0)    row = 0;
      if (row >= rows) row = rows - 1;
      if (col <  0)    col = 0;
      if (col >= cols) col = cols - 1;
    }
  }

  if (retrows) *retrows = rows;
  if (retcols) *retcols = cols;
  if (retrow)  *retrow  = row;
  if (retcol)  *retcol  = col;
}

/*************************************************/
/* frames_find_pointer_in_frameset()             */
/*                                               */
/* Works out where in a frameset a given screen  */
/* coordinate is. The returned row and column    */
/* values are -1 if the coordinate lies within   */
/* a row or column, or are the row below, or     */
/* column to the right of the coordinate if it   */
/* lies in the border between two rows or        */
/* columns. So the value 0 would never be        */
/* returned for either.                          */
/*                                               */
/* In the event that the pointer lies between a  */
/* row or column but that row or column edge     */
/* cannot be resized due to specifiers in the    */
/* HTML source for the frameset, -2 will be      */
/* returned.                                     */
/*                                               */
/* Given that this is likely to be called by     */
/* routines handling border-driven resizing of   */
/* frames, the routine can constrain the pointer */
/* to a bounding box appropriate to the frames   */
/* surrounding it if required. This may be done  */
/* via. a mouse_rectangle or Wimp_DragBox.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             representing the parent frame;    */
/*                                               */
/*             X screen coordinate to check;     */
/*                                               */
/*             Y screen coordinate to check;     */
/*                                               */
/*             Pointer to an int, into which the */
/*             row number (see above) will be    */
/*             written;                          */
/*                                               */
/*             Pointer to an int, into which the */
/*             column number (see above) will be */
/*             written;                          */
/*                                               */
/*             Pointer to an int, into which the */
/*             pointer's x offset from the left  */
/*             edge of the frame left of it, if  */
/*             applicable, is returned;          */
/*                                               */
/*             Pointer to an int, into which the */
/*             pointer's y offset from the top   */
/*             edge of the frame below it, if    */
/*             applicable, is returned;          */
/*                                               */
/*             1 to constrain the pointer (again */
/*             see above for more details) with  */
/*             Wimp_DragBox, 2 to constrain with */
/*             mouse_rectangle, 0 to not do any  */
/*             pointer constraint.               */
/*                                               */
/* Assumes:    Either of the four int pointers   */
/*             may be NULL.                      */
/*************************************************/

_kernel_oserror * frames_find_pointer_in_frameset(browser_data * handle, int x, int y, int * retrow, int * retcol,
                                                  int * offset_left, int * offset_top, int constrain)
{
  _kernel_oserror           * e;
  browser_data              * child;
  WimpGetWindowOutlineBlock   co;
  int                         oft,  ofl;
  int                         rows, cols;
  int                         row,  col;
  int                         i,    c;
  BBox                        span;

  if (offset_left) *offset_left = 0;
  if (offset_top)  *offset_top  = 0;

  oft = ofl = 0;

  /* Find the number of rows and columns */

  rows = handle->frameset->rows;
  cols = handle->frameset->cols;

  if (!rows) rows = 1;
  if (!cols) cols = 1;

  /* First find the row that the pointer lies between */

  row = -1, col = -1;

  for (i = 0; i < rows; i++)
  {
    /* What child number are we on? */

    c = i * cols;

    if (c < handle->nchildren)
    {
      /* Get this child frame's outline coordinates */

      child            = handle->children[c];
      co.window_handle = child->window_handle;

      e = wimp_get_window_outline(&co);
      if (e) return e;

      /* First case - pointer y coord lies within the bounding y */
      /* coords of the window, so it lies in this row. In this   */
      /* case, we've a column-only drag, so don't care about the */
      /* row. We can just exit, leaving row at -1.               */

      if (co.outline.ymax > y && co.outline.ymin < y) break;

      /* Second case - pointer y coord lies above the top of the */
      /* frame. So we've gone past it; in that case, the pointer */
      /* must lie between this row and the previous one.         */

      if (co.outline.ymax <= y)
      {
        row = i; /* So row points to the row below the pointer */
        oft = y - co.outline.ymax;

        if (offset_top) *offset_top = oft;

        break;
      }
    }
  }

  /* Right, next find the column the pointer lies between */

  for (i = 0; i < cols; i++)
  {
    if (i < handle->nchildren)
    {
      /* Get this child frame's outline coordinates */

      child            = handle->children[i];
      co.window_handle = child->window_handle;

      e = wimp_get_window_outline(&co);
      if (e) return e;

      /* As for rows, first case is the x coord lying within the */
      /* bounding x coords of the window.                        */

      if (co.outline.xmin < x && co.outline.xmax > x) break;

      /* Second case, the pointer x coord lies left of the left  */
      /* hand side of the frame, so we've gone past it.          */

      if (co.outline.xmin >= x)
      {
        col = i;
        ofl = co.outline.xmin - x;

        if (offset_left) *offset_left = ofl;

        break;
      }
    }
  }

  /* At this point, row holds -1 if the pointer lies within a  */
  /* particular row, or holds the row number below the pointer */
  /* if it lies between two rows. Similarly, col holds -1 if   */
  /* the pointer lies within a particular column, or holds the */
  /* column number to the right of the pointer if it lies      */
  /* between two columns.                                      */

  if (
       row == 0        ||
       col == 0        ||
       row >= rows ||
       col >= cols
     )
  {
    if (retrow) *retrow = -1;
    if (retcol) *retcol = -1;

    return NULL;
  }

  /* Are we allowed to resize this edge? */

  if (row > 0 && !frames_can_resize_top(handle, row * cols)) row = -2;
  if (col > 0 && !frames_can_resize_left(handle, col))       col = -2;

  /* Set the return values */

  if (retrow) *retrow = row;
  if (retcol) *retcol = col;

  /* If required, constrain the pointer */

  if (constrain)
  {
    int th, hh, vw;

    /* Find the tool sizes; we reduce the size of the bounding box */
    /* for the pointer by the relevant tool size times two to make */
    /* sure the frames don't get squashed too small, as well as    */
    /* considering the border width and original drag position.    */

    windows_return_tool_sizes(&th, &hh, &vw);

    span.xmin = span.xmax = x;
    span.ymin = span.ymax = y;

    /* Finding the outlines again for four frames  */
    /* may duplicate some, or even all of the work */
    /* done above but this keeps the code clear    */
    /* and easy to understand. This is a very      */
    /* infrequent operation (only at the start of  */
    /* a drag, for example) so it doesn't need to  */
    /* be that fast.                               */

    if (row > 0)
    {
      /* Get the top frame outline coordinates */

      c = (row - 1) * cols;

      if (c < handle->nchildren)
      {
        child            = handle->children[c];
        co.window_handle = child->window_handle;

        e = wimp_get_window_outline(&co);
        if (e) return e;

        span.ymax = y + co.outline.ymax - co.outline.ymin - hh * 2;
      }

      /* Get the bottom frame outline coordinates */

      c = row * cols;

      if (c < handle->nchildren)
      {
        child            = handle->children[c];
        co.window_handle = child->window_handle;

        e = wimp_get_window_outline(&co);
        if (e) return e;

        span.ymin = y - (co.outline.ymax - co.outline.ymin - hh * 2);
      }

      if (span.ymax < span.ymin) span.ymax = span.ymin = y;
    }

    if (col > 0)
    {
      /* Get the left frame outline coordinates */

      c = col - 1;

      if (c < handle->nchildren)
      {
        child            = handle->children[c];
        co.window_handle = child->window_handle;

        e = wimp_get_window_outline(&co);
        if (e) return e;

        span.xmin = x - (co.outline.xmax - co.outline.xmin - vw * 2);
      }

      /* Get the right hand frame outline coordinates */

      c = col;

      if (c < handle->nchildren)
      {
        child            = handle->children[c];
        co.window_handle = child->window_handle;

        e = wimp_get_window_outline(&co);
        if (e) return e;

        span.xmax = x + co.outline.xmax - co.outline.xmin - vw * 2;
      }

      if (span.xmax < span.xmin) span.xmax = span.xmin = x;
    }

    /* Set the bounding box */

    if (constrain == 1)
    {
      WimpDragBox drag;

      drag.wimp_window       = handle->window_handle;
      drag.drag_type         = Wimp_DragBox_DragPoint;

      drag.dragging_box.xmin = 0;
      drag.dragging_box.ymin = 0;
      drag.dragging_box.xmax = 0;
      drag.dragging_box.ymax = 0;
      drag.parent_box        = span;

      drag.workspace         = NULL;
      drag.draw              = NULL;
      drag.remove            = NULL;
      drag.move              = NULL;

      wimp_drag_box(&drag);
    }
    else if (constrain == 2) mouse_rectangle(&span, 1);
  }

  /* Finished */

  return NULL;
}

/*************************************************/
/* frames_can_resize_top()                       */
/*                                               */
/* Returns 1 if the top edge of a given frame    */
/* may be dragged to resize the frame.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which is the parent of the set of */
/*             frames in question;               */
/*                                               */
/*             Number of the child to check.     */
/*                                               */
/* Returns:    1 if the frame's top edge can be  */
/*             dragged to resize it, else 0 (due */
/*             to NORESIZE specified on that     */
/*             frame or frames surrounding it).  */
/*************************************************/

int frames_can_resize_top(browser_data * parent, int child)
{
  int            row, rows, col, cols;
  int            checkcol, checkchild;
  browser_data * checkc;
  int            canresize = 1;
  HStream      * frametoken;

  if (!parent->nchildren) return 0;

  frametoken = parent->children[child]->frame;

  if (frametoken && (frametoken->type & TYPE_NORESIZE)) return 0;

  /* Find out the number of rows and columns, and the */
  /* row and column number the given child falls in.  */

  frames_get_rc_info(parent, child, &rows, &cols, &row, &col);

  for (checkcol = 0; checkcol < cols; checkcol ++)
  {
    /* Check all columns in the row the child is in for NORESIZE frames */

    checkchild = row * cols + checkcol;
    checkc     = parent->children[checkchild];
    frametoken = checkc->frame;

    if (frametoken && (frametoken->type & TYPE_NORESIZE))
    {
      canresize = 0;
      break;
    }

    /* If not already on row 0, check the row above for NORESIZE frames */

    if (row)
    {
      checkchild = (row - 1) * cols + checkcol;
      checkc     = parent->children[checkchild];
      frametoken = checkc->frame;

      if (frametoken && (frametoken->type & TYPE_NORESIZE))
      {
        canresize = 0;
        break;
      }
    }
  }

  return canresize;
}

/*************************************************/
/* frames_can_resize_bottom()                    */
/*                                               */
/* Returns 1 if the bottom edge of a given frame */
/* may be dragged to resize the frame.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which is the parent of the set of */
/*             frames in question;               */
/*                                               */
/*             Number of the child to check.     */
/*                                               */
/* Returns:    1 if the frame's bottom edge can  */
/*             be dragged to resize it, else 0   */
/*             (due to NORESIZE specified on     */
/*             that frame or frames surrounding  */
/*             it).                              */
/*************************************************/

int frames_can_resize_bottom(browser_data * parent, int child)
{
  int            row, rows, col, cols;
  int            checkcol, checkchild;
  browser_data * checkc;
  int            canresize = 1;
  HStream      * frametoken;

  if (!parent->nchildren) return 0;

  frametoken = parent->children[child]->frame;

  if (frametoken && (frametoken->type & TYPE_NORESIZE)) return 0;

  /* Find out the number of rows and columns, and the */
  /* row and column number the given child falls in.  */

  frames_get_rc_info(parent, child, &rows, &cols, &row, &col);

  for (checkcol = 0; checkcol < cols; checkcol ++)
  {
    /* Check all columns in the row the child is in for NORESIZE frames */

    checkchild = row * cols + checkcol;
    checkc     = parent->children[checkchild];
    frametoken = checkc->frame;

    if (frametoken && (frametoken->type & TYPE_NORESIZE))
    {
      canresize = 0;
      break;
    }

    /* If not already on the last row, check the row below for NORESIZE frames */

    if (row < rows - 1)
    {
      checkchild = (row + 1) * cols + checkcol;
      checkc     = parent->children[checkchild];
      frametoken = checkc->frame;

      if (frametoken && (frametoken->type & TYPE_NORESIZE))
      {
        canresize = 0;
        break;
      }
    }
  }

  return canresize;
}

/*************************************************/
/* frames_can_resize_left()                      */
/*                                               */
/* Returns 1 if the left edge of a given frame   */
/* may be dragged to resize the frame.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which is the parent of the set of */
/*             frames in question;               */
/*                                               */
/*             Number of the child to check.     */
/*                                               */
/* Returns:    1 if the frame's left edge can be */
/*             dragged to resize it, else 0 (due */
/*             to NORESIZE specified on that     */
/*             frame or frames surrounding it).  */
/*************************************************/

int frames_can_resize_left(browser_data * parent, int child)
{
  int            row, rows, col, cols;
  int            checkrow, checkchild;
  browser_data * checkc;
  int            canresize = 1;
  HStream      * frametoken;

  if (!parent->nchildren) return 0;

  frametoken = parent->children[child]->frame;

  if (frametoken && (frametoken->type & TYPE_NORESIZE)) return 0;

  /* Find out the number of rows and columns, and the */
  /* row and column number the given child falls in.  */

  frames_get_rc_info(parent, child, &rows, &cols, &row, &col);

  for (checkrow = 0; checkrow < rows; checkrow ++)
  {
    /* Check all rows in the column the child is in for NORESIZE frames */

    checkchild = col + checkrow * cols;
    checkc     = parent->children[checkchild];
    frametoken = checkc->frame;

    if (frametoken && (frametoken->type & TYPE_NORESIZE))
    {
      canresize = 0;
      break;
    }

    /* If not already on column 0, check the column to the left for NORESIZE frames */

    if (col)
    {
      checkchild = col - 1 + checkrow * cols;
      checkc     = parent->children[checkchild];
      frametoken = checkc->frame;

      if (frametoken && (frametoken->type & TYPE_NORESIZE))
      {
        canresize = 0;
        break;
      }
    }
  }

  return canresize;
}

/*************************************************/
/* frames_can_resize_right()                     */
/*                                               */
/* Returns 1 if the right edge of a given frame  */
/* may be dragged to resize the frame.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which is the parent of the set of */
/*             frames in question;               */
/*                                               */
/*             Number of the child to check.     */
/*                                               */
/* Returns:    1 if the frame's right edge can   */
/*             be dragged to resize it, else 0   */
/*             (due to NORESIZE specified on     */
/*             that frame or frames surrounding  */
/*             it).                              */
/*************************************************/

int frames_can_resize_right(browser_data * parent, int child)
{
  int            row, rows, col, cols;
  int            checkrow, checkchild;
  browser_data * checkc;
  int            canresize = 1;
  HStream      * frametoken;

  if (!parent->nchildren) return 0;

  frametoken = parent->children[child]->frame;

  if (frametoken && (frametoken->type & TYPE_NORESIZE)) return 0;

  /* Find out the number of rows and columns, and the */
  /* row and column number the given child falls in.  */

  frames_get_rc_info(parent, child, &rows, &cols, &row, &col);

  for (checkrow = 0; checkrow < rows; checkrow ++)
  {
    /* Check all rows in the column the child is in for NORESIZE frames */

    checkchild = col + checkrow * cols;
    checkc     = parent->children[checkchild];
    frametoken = checkc->frame;

    if (frametoken && (frametoken->type & TYPE_NORESIZE))
    {
      canresize = 0;
      break;
    }

    /* If not already on the last column, check the column to the right for NORESIZE frames */

    if (col < cols - 1)
    {
      checkchild = col + 1 + checkrow * cols;
      checkc     = parent->children[checkchild];
      frametoken = checkc->frame;

      if (frametoken && (frametoken->type & TYPE_NORESIZE))
      {
        canresize = 0;
        break;
      }
    }
  }

  return canresize;
}

/*************************************************/
/* frames_redraw_borders()                       */
/*                                               */
/* Redraws plinth borders and a resize sprite    */
/* indicator around frames in a given parent.    */
/*                                               */
/* Parameters: Pointer to a parent browser_data  */
/*             struct;                           */
/*                                               */
/*             A WimpRedrawWindow block          */
/*             describing the redraw region.     */
/*************************************************/

void frames_redraw_borders(browser_data * b, WimpRedrawWindowBlock * r)
{
  int                         child;
  unsigned int                colour;
  BBox                        sbox;
  browser_data              * c;
  WimpGetWindowOutlineBlock   co;

  sbox = r->redraw_area;

  /* The bottom bit of the colour field is set to indicate */
  /* that a colour is present, as opposed to having no     */
  /* colour set. In the latter case default to background  */
  /* grey, in the former strip off the set bit. Then set   */
  /* that colour. Remember, we try to find a colour in the */
  /* FRAME tags, then failing that look in the FRAMESET    */
  /* (as in NN or MSIE 4).                                 */

  colour = 0;

  for (child = 0; child < b->nchildren && !(colour & 1); child ++)
  {
    c = b->children[child];
    if (c->frame) colour = c->frame->maxlen;
  }

  if (!(colour & 1)) colour = b->frameset->maxlen;

  if (!(colour & 1)) colour = Redraw_Colour_WNGrey;
  else               colour &= ~1;

  redraw_set_colour(colour);

  bbc_rectanglefill(sbox.xmin,
                    sbox.ymin,
                    sbox.xmax - sbox.xmin + 4,
                    sbox.ymax - sbox.ymin + 4);

  #ifdef TRACE

    if (tl & (1u<<23))
    {
      redraw_set_colour(0xff884400);
      bbc_rectangle(sbox.xmin,
                    sbox.ymin,
                    sbox.xmax - sbox.xmin - 1,
                    sbox.ymax - sbox.ymin - 1);

      redraw_set_colour(0xffaa6600);
      bbc_rectangle(sbox.xmin + 2,
                    sbox.ymin + 2,
                    sbox.xmax - sbox.xmin - 5,
                    sbox.ymax - sbox.ymin - 5);

      redraw_set_colour(0xffcc8800);
      bbc_rectangle(sbox.xmin + 4,
                    sbox.ymin + 4,
                    sbox.xmax - sbox.xmin - 9,
                    sbox.ymax - sbox.ymin - 9);
    }

  #endif

  /* Now loop round the children, drawing a 3D border around them */
  /* for border spacings >= 2, a black line for spacing 1, or     */
  /* nothing for spacing 0 (shouldn't then get redraw events for  */
  /* the parent, but you never know - e.g. user could have got a  */
  /* patch that allowed the frames to be moved by dragging on     */
  /* their work area).                                            */

  if (b->frameset->indent) /* Holds frame spacing (equiv. to border width) */
  {
    for (child = 0; child < b->nchildren; child ++)
    {
      c                = b->children[child];
      co.window_handle = c->window_handle;

      if (!wimp_get_window_outline(&co))
      {
        if (b->frameset->indent >= 2) redraw_set_colour(Redraw_Colour_AlmostWhite);
        else                          redraw_set_colour(0);

        /* Bottom edge */

        bbc_rectanglefill(co.outline.xmin,
                          co.outline.ymin - 2,
                          co.outline.xmax - co.outline.xmin + 1,
                          1);

        bbc_rectanglefill(co.outline.xmax,
                          co.outline.ymin,
                          1,
                          co.outline.ymax - co.outline.ymin - 1);

        /* Right hand edge */

        if (b->frameset->indent >= 2) redraw_set_colour(Redraw_Colour_PlinthGrey);

        /* Top edge */

        bbc_rectanglefill(co.outline.xmin - 2,
                          co.outline.ymax,
                          co.outline.xmax - co.outline.xmin + 3,
                          1);

        /* Left hand edge */

        bbc_rectanglefill(co.outline.xmin - 2,
                          co.outline.ymin - 2,
                          1,
                          co.outline.ymax - co.outline.ymin + 1);

        /* If the edges are draggable - for frame resizing - plot icons */
        /* indicate this.                                               */

        {
          int  width, height;
          BBox icon;

          /* Get the sprite size, work out a bounding box and plot */
          /* this as a virtual icon.                               */

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

          /* Can only plot sprites if there's room... The additions to width and */
          /* height account for the border plotted above.                        */

          if (
               width  + 4 <= b->frameset->indent * wimpt_dx() &&
               height + 4 <= b->frameset->indent * wimpt_dy()
             )
          {
            WimpPlotIconBlock block;

            block.flags = 0x1700311B;
            block.data.ist.buffer = "";
            block.data.ist.validation = "Sresizeframe";
            block.data.ist.buffer_size = 4;

            if (frames_can_resize_right(b, child) && !printing)
            {
              /* Plot to right of frame */

              icon.xmin = co.outline.xmax + b->frameset->indent - width / 2;
              icon.ymin = co.outline.ymin + (co.outline.ymax - co.outline.ymin - height) / 2;
              icon.xmax = icon.xmin + width;
              icon.ymax = icon.ymin + height;

              coords_box_toworkarea(&icon, r);
              block.bbox  = icon;

              _swix(Wimp_PlotIcon,
                    _IN(1) | _INR(4,5),

                    &block,
                    0,
                    0);
            }

            if (frames_can_resize_bottom(b, child) && !printing)
            {
              /* Plot below the frame */

              icon.xmin = co.outline.xmin + (co.outline.xmax - co.outline.xmin - width) / 2;
              icon.ymin = co.outline.ymin - height / 2 - b->frameset->indent;
              icon.xmax = icon.xmin + width;
              icon.ymax = icon.ymin + height;

              coords_box_toworkarea(&icon, r);
              block.bbox  = icon;

              _swix(Wimp_PlotIcon,
                    _IN(1) | _INR(4,5),

                    &block,
                    0,
                    0);
            }

          /* Closure of long 'if' checking that there is room to plot */
          /* a resize handle for this frame, given that the frame     */
          /* itself doesn't have noresize specified. The code above   */
          /* executes if noresize is not specified and there's room   */
          /* to do the plot.                                          */
          }

        /* Closure of code block dealing with resize handle plotting */
        }

      /* Closure of long 'if' ensuring a wimp_get_window_outline */
      /* call didn't return an error - the code above executes   */
      /* if there was no error.                                  */
      }

    /* Closure of 'for' looping round all children */
    }

  /* Closure of long 'if' checking that the frameset has spacing */
  /* between frames. The code above executes if so.              */
  }
}

/*************************************************/
/* frames_define_frameset()                      */
/*                                               */
/* For a given parent browser_data structure,    */
/* sets up a frameset within it. Frame details   */
/* for each of the children thus generated are   */
/* filled in later, when getting each frame      */
/* token from the HTML library.                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             representing the parent;          */
/*                                               */
/*             Pointer to a token defining the   */
/*             frameset.                         */
/*                                               */
/* Assumes:    If the window already has a       */
/*             frameset defined, this set and    */
/*             all nested sets above it will be  */
/*             destroyed before the new one is   */
/*             created.                          */
/*************************************************/

_kernel_oserror * frames_define_frameset(browser_data * b, HStream * token)
{
  _kernel_oserror         * e;
  WimpGetWindowStateBlock   s;
  BBox                      frame_box;
  int                       rows, cols;
  int                       num_rows, num_cols;
  int                       width, height;
  int                       htop, hbot;
  int                       available_w, available_h;
  int                       start_x, start_y, accum_x, accum_y;
  int                       th, hh, vw;

  #ifdef TRACE
    if (tl & (1u<<17)) Printf("frames_define_frameset: Called with browser_data %p\n",b);
  #endif

  /* For ancestor windows, need to get the whole window */
  /* state and use the visible area, thus taking        */
  /* account of scrollbar presence etc.                 */

  if (!b->ancestor || !nested_wimp)
  {
    s.window_handle = b->window_handle;
    e = wimp_get_window_state(&s);
    if (e) return e;
  }

  /* However, for frames with frames about to be defined */
  /* inside them, we know that the scrollbars in this    */
  /* frame will disappear (if the nested wimp is in use) */
  /* and so need to use the window outline, not the      */
  /* visible area, as the latter may not be up to date.  */

  else
  {
    WimpGetWindowOutlineBlock o;

    o.window_handle = b->window_handle;
    e = wimp_get_window_outline(&o);
    if (e) return e;

    s.window_handle = o.window_handle;
    s.visible_area  = o.outline;
  }

  windows_return_tool_sizes(&th, &hh, &vw);

  /* Destroy any existing frameset */

  frames_collapse_set(b);

  /* Set up the basic starting parameters */

  b->nchildren     = 0;
  b->filling_frame = 0;
  b->frameset      = token;

  /* Work out the amount of visible area that toolbars are consuming */

  if (!controls.swap_bars)
  {
    htop = toolbars_url_height(b) + toolbars_button_height(b); /* Physically, the button bar and URL bar are just one real toolbar */
    hbot = toolbars_status_height(b);
  }
  else
  {
    htop = toolbars_status_height(b);
    hbot = toolbars_url_height(b) + toolbars_button_height(b);
  }

  if (htop) htop += wimpt_dy(); /* Account for lower border of upper toolbar, if present */
  if (hbot) hbot += wimpt_dy(); /* Account for upper border of lower toolbar, if present */

  /* Work out the available frame space width and height */

  available_w = s.visible_area.xmax - s.visible_area.xmin;
  available_h = s.visible_area.ymax - s.visible_area.ymin - htop - hbot;

  /* Start defining frames from the top left hand side of the visible area */

  start_x = s.visible_area.xmin;
  start_y = s.visible_area.ymax - htop;

  /* Work out the number of rows / columns */

  num_rows = token->rows;
  if (!num_rows) num_rows = 1;

  num_cols = token->cols;
  if (!num_cols) num_cols = 1;

  /* Account for frame spacing */

  available_w -= token->indent * 2 * (num_cols - 1); /* Scale to 2 OS units/pixel */
  available_h -= token->indent * 2 * (num_rows - 1);

  #ifdef TRACE
    if (tl & (1u<<17))
    {
      Printf("frames_define_frameset: avail_w %d\n"
             "                        avail_h %d\n"
             "                        start_x %d\n"
             "                        start_y %d\n"
             "                        rows    %d\n"
             "                        cols    %d\n\n",
             available_w,
             available_h,
             start_x,
             start_y,
             num_rows,
             num_cols);
      {
        int i;

        Printf("frames_define_frameset: Size enumeration:\n\n");

        for (i = 0; i < num_rows; i++)
        {
          if (token->name) Printf("Row %d = %p\n",i,((int *) token->name)[i]);
          else             Printf("Row %d = undefined\n",i);
        }

        Printf("\n");

        for (i = 0; i < num_cols; i++)
        {
          if (token->value) Printf("Col %d = %p\n",i,((int *) token->value)[i]);
          else              Printf("Col %d = undefined\n",i);
        }

        Printf("\nframes_define_frameset: Proceeding\n");
      }
    }
  #endif

  /* Get all the sizes */

  frames_find_widths (b, available_w);
  frames_find_heights(b, available_h);

  /* Loop round defining each child */

  frame_box.ymax = start_y;
  accum_y        = 0;

  for (rows = 0; rows < num_rows; rows ++)
  {
    frame_box.xmin = start_x;
    accum_x        = 0;
    height         = b->frame_heights[rows];

    accum_y += height;

    if (rows == num_rows - 1)
    {
      /* For the last row, ensure it fills up the height - rounding errors in scaling */
      /* for the find_height calls often make this necessary.                         */

      if (accum_y != available_h) height -= accum_y - available_h, accum_y = available_h;
    }

    for (cols = 0; cols < num_cols; cols ++)
    {
      width = b->frame_widths[cols];

      accum_x += width;

      if (cols == num_cols - 1)
      {
        /* For the last column, ensure it fills up the width - rounding errors in scaling */
        /* for the find_width calls often make this necessary.                            */

        if (accum_x != available_w) width -= accum_x - available_w, accum_x = available_w;
      }

      /* Adjust the open coordinates to account for the scroll bars */
      /* present in the Res file. If these are not present, the     */
      /* reformatter will try to use the horizontal space a         */
      /* vertical bar normally occupies and things will get worse   */
      /* from there...                                              */

      frame_box.xmax = frame_box.xmin + width  - vw;
      frame_box.ymin = frame_box.ymax - height + hh;

      windows_create_browser("", b, &frame_box, NULL, Windows_CreateBrowser_Normal);
      windows_set_tools(last_browser, &frame_box, 0, 0, 0, 0);

      /* Make the child inherit some of the parent's characteristics */

      last_browser->underline_links = b->underline_links;
      last_browser->use_source_cols = b->use_source_cols;
      last_browser->show_foreground = b->show_foreground;
      last_browser->show_background = b->show_background;

      /* If this is the first frame for the ancestor, make it selected */

      if (last_browser->ancestor->nchildren == 1) last_browser->frame_selected = 1;

      /* Increment the position counters and loop round again */

      frame_box.xmin += width + token->indent * 2; /* Scale to 2 OS units/pixel */
    }

    frame_box.ymax -= height + token->indent * 2;
  }

  /* Finally, do a redraw of the parent to ensure borders are */
  /* up to date.                                              */

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

  e = wimp_force_redraw(b->window_handle,
                        s.visible_area.xmin,
                        s.visible_area.ymin,
                        s.visible_area.xmax,
                        s.visible_area.ymax);
  if (e) return e;

  #ifdef TRACE
    if (tl & (1u<<17)) Printf("frames_define_frameset: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* frames_check_recursion()                      */
/*                                               */
/* Checks to see if a frameset is defining       */
/* itself recursively.                           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which is the parent of the frame  */
/*             being checked;                    */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             which represents the frame being  */
/*             checked;                          */
/*                                               */
/*             Pointer to an HStream struct that */
/*             represents the <FRAME...> tag     */
/*             which is filling in the frame     */
/*             being checked.                    */
/*                                               */
/* Returns:    1 if the definition is recursive, */
/*             else 0.                           */
/*************************************************/

static int frames_check_recursion(browser_data * parent, browser_data * child, HStream * token)
{
  while (parent)
  {
    if (browser_fetch_url(parent)   && !strcmp(browser_fetch_url(parent),   token->src)) return 1;
    if (browser_current_url(parent) && !strcmp(browser_current_url(parent), token->src)) return 1;

    parent = parent->real_parent;
  }

  return 0;
}

/*************************************************/
/* frames_define_frame()                         */
/*                                               */
/* For a given parent browser_data structure,    */
/* fills in the next unfilled frame.             */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             representing the parent;          */
/*                                               */
/*             Pointer to the token that is to   */
/*             define the child characteristics. */
/*                                               */
/* Assumes:    If the parent has no children,    */
/*             the routine silently fails;       */
/*             If the parent's children are all  */
/*             defined (i.e. there appear to be  */
/*             more <frame> tags than defined by */
/*             the initial <frameset> the        */
/*             routine, again, silently fails.   */
/*************************************************/

_kernel_oserror * frames_define_frame(browser_data * b, HStream * token)
{
  browser_data    * child;
  _kernel_oserror * e;

  #ifdef TRACE
    if (tl & (1u<<17)) Printf("frames_define_frame: Called with browser_data %p\n",b);
  #endif

  if (b->nchildren <= b->filling_frame)
  {
    #ifdef TRACE
      if (tl & (1u<<17)) Printf("frames_define_frame: nchildren %d, filling_frame %d - exitting!",b->nchildren,b->filling_frame);
    #endif

    return NULL;
  }

  #ifdef TRACE
    if (tl & (1u<<17)) Printf("frames_define_frame: Have not defined all children, so proceeding\n",b);
  #endif

  child = (browser_data *) b->children[b->filling_frame++];

  child->frame = token;

  if (token->name && *token->name)
  {
    e = memory_set_chunk_size(child, NULL, CK_NAME, strlen(token->name) + 1);
    if (e) return e;

    strcpy(child->window_name, token->name);
  }

  /* Set scrolling details */
  {
    int scroll;

    scroll = token->type & TYPE_SCROLLING_MASK;

    if      (scroll == TYPE_SCROLLING_NO)   child->frame_hscroll = 0;
    else if (scroll == TYPE_SCROLLING_AUTO) child->frame_hscroll = 1;
    else if (scroll == TYPE_SCROLLING_YES)  child->frame_hscroll = 2;

    child->frame_vscroll = child->frame_hscroll;
  }

  /* About to try and fetch the URL defined for the frame, */
  /* but need to first check it's not recursive.           */

  if (frames_check_recursion(b, child, token))
  {
    #ifdef TRACE
      if (tl & (1u<<17)) Printf("frames_define_frame: Exitting with no fetch (recursive frameset)\n");
    #endif

    #ifdef STRICT_PARSER

      erb.errnum = Utils_Error_Custom_Message;

      StrNCpy0(erb.errmess,
               lookup_token("FramRcrs:Frames definition references itself recursively; could not proceed with the frames layout.",
                            0,0));

      show_error_ret(&erb);

    #endif

    return NULL;
  }

  #ifdef TRACE
    if (tl & (1u<<17)) Printf("frames_define_frame: Exitting through fetchpage_new()\n",b);
  #endif

  return fetchpage_new(child, token->src, 1, 0);
}

/*************************************************/
/* frames_reopen_frame()                         */
/*                                               */
/* Reopens a given frame to a given outline      */
/* size.                                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             representing the frame;           */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             representing the parent, or NULL  */
/*             if not known;                     */
/*                                               */
/*             Pointer to a BBox describing the  */
/*             new outline (screen coordinates)  */
/*             for the frame.                    */
/*************************************************/

static _kernel_oserror * frames_reopen_frame(browser_data * cb, browser_data * parent, BBox * frame_box)
{
  _kernel_oserror         * e = NULL;
  WimpGetWindowStateBlock   frame_state;
  IdBlock                   idb;
  WimpPollBlock             block;
  int                       title_height, hscroll_height, vscroll_width;

  if (!cb) return NULL;
  if (!parent) parent = cb->parent;
  if (!parent) parent = cb->real_parent;
  if (!parent) parent = cb->ancestor;
  if (!parent) return NULL;

  /* Get the tool sizes */

  windows_return_tool_sizes(&title_height, &hscroll_height, &vscroll_width);

  /* Get the frame's window details */

  frame_state.window_handle = cb->window_handle;

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

  /* Fill in the open window request block with the new size */
  /* details, etc.                                           */

  block.open_window_request.window_handle = frame_state.window_handle;

  if (frame_state.flags & WimpWindow_VScroll) frame_box->xmax -= vscroll_width;
  if (frame_state.flags & WimpWindow_HScroll) frame_box->ymin += hscroll_height;

  block.open_window_request.visible_area = *frame_box;
  block.open_window_request.xscroll      = frame_state.xscroll;
  block.open_window_request.yscroll      = frame_state.yscroll;

  /* Sort out the window to open behind. */

  block.open_window_request.behind = find_behind(block.open_window_request.window_handle);

  /* Fill in the ID block and call the open window function */

  idb.self_id   = cb->self_id;
  idb.parent_id = parent->self_id;

  windows_open_browser(0, &block, &idb, cb);

  /* If the frame was highlighted, move the highlight too */

  if (cb == highlight_frame)
  {
    e = frames_highlight_frame(cb);
    if (e) return e;
  }

  return NULL;
}

/*************************************************/
/* frames_resize_frameset()                      */
/*                                               */
/* For a given parent browser_data structure,    */
/* resize a frameset within its window. Compare  */
/* with frames_resize_frame(), which also        */
/* resizes the parent (which is useful when the  */
/* natural progression of a calling function     */
/* would not include this itself).               */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             representing the parent;          */
/*                                               */
/*             Pointer to a BBox describing the  */
/*             required new window outline (in   */
/*             screen coordinates) of the parent */
/*             frame.                            */
/*************************************************/

_kernel_oserror * frames_resize_frameset(browser_data * b, BBox * new_outline)
{
  _kernel_oserror         * e;
  WimpGetWindowStateBlock   s;
  BBox                      frame_box;
  HStream                 * token;
  int                       rows, cols;
  int                       num_rows, num_cols;
  int                       width, height;
  int                       htop, hbot;
  int                       available_w, available_h;
  int                       start_x, start_y, accum_x, accum_y;
  int                       th, hh, vw;
  int                       child;

  #ifdef TRACE
    if (tl & (1u<<17)) Printf("frames_resize_frameset: Called with browser_data %p\n",b);
  #endif

  /* Nothing to resize if there are no children */

  if (!b->nchildren) return NULL;

  /* Get the frame window details and tool sizes */

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

  s.visible_area = *new_outline;

  windows_return_tool_sizes(&th, &hh, &vw);

  /* Work out the amount of visible area that toolbars are consuming */

  if (!controls.swap_bars)
  {
    htop = toolbars_url_height(b) + toolbars_button_height(b); /* Physically, the button bar and URL bar are just one real toolbar */
    hbot = toolbars_status_height(b);
  }
  else
  {
    htop = toolbars_status_height(b);
    hbot = toolbars_url_height(b) + toolbars_button_height(b);
  }

  if (htop) htop += wimpt_dy(); /* Account for lower border of upper toolbar, if present */
  if (hbot) hbot += wimpt_dy(); /* Account for upper border of lower toolbar, if present */

  /* Work out the available frame space width and height */

  available_w = s.visible_area.xmax - s.visible_area.xmin;

  available_h = s.visible_area.ymax - s.visible_area.ymin
                - htop
                - hbot;

  /* Start resizing frames from the top left hand side of the visible area */

  start_x = s.visible_area.xmin;
  start_y = s.visible_area.ymax - htop;

  /* Work out the number of rows / columns */

  token = b->frameset;

  num_rows = token->rows;
  num_cols = token->cols;

  if (!num_rows) num_rows = 1;
  if (!num_cols) num_cols = 1;

  /* Account for frame spacing */

  available_w -= token->indent * 2 * (num_cols - 1); /* Scale to 2 OS units/pixel */
  available_h -= token->indent * 2 * (num_rows - 1);

  #ifdef TRACE
    if (tl & (1u<<17))
    {
      Printf("frames_resize_frameset: avail_w %d\n"
             "                        avail_h %d\n"
             "                        start_x %d\n"
             "                        start_y %d\n"
             "                        rows    %d\n"
             "                        cols    %d\n\n",
             available_w,
             available_h,
             start_x,
             start_y,
             num_rows,
             num_cols);
      {
        int i;

        Printf("frames_resize_frameset: Size enumeration:\n\n");

        for (i = 0; i < num_rows; i++)
        {
          if (token->name) Printf("Row %d = %p\n",i,((int *) token->name)[i]);
          else             Printf("Row %d = undefined\n",i);
        }

        Printf("\n");

        for (i = 0; i < num_cols; i++)
        {
          if (token->value) Printf("Col %d = %p\n",i,((int *) token->value)[i]);
          else              Printf("Col %d = undefined\n",i);
        }

        Printf("\nframes_resize_frameset: Proceeding\n");
      }
    }
  #endif

  /* Get all the sizes */

  frames_find_widths (b, available_w);
  frames_find_heights(b, available_h);

  /* Loop round resizing each child */

  frame_box.ymax = start_y;
  accum_y        = 0;
  child          = 0;

  for (rows = 0; rows < num_rows; rows ++)
  {
    frame_box.xmin = start_x;
    accum_x        = 0;
    height         = b->frame_heights[rows];

    accum_y += height;

    if (rows == num_rows - 1)
    {
      /* For the last row, ensure it fills up the height - rounding errors in scaling */
      /* for the find_height calls often make this necessary.                         */

      if (accum_y != available_h) height -= accum_y - available_h, accum_y = available_h;
    }

    for (cols = 0; cols < num_cols; cols ++)
    {
      width = b->frame_widths[cols];

      accum_x += width;

      if (cols == num_cols - 1)
      {
        /* For the last column, ensure it fills up the width - rounding errors in scaling */
        /* for the find_width calls often make this necessary.                            */

        if (accum_x != available_w) width -= accum_x - available_w, accum_x = available_w;
      }

      frame_box.xmax = frame_box.xmin + width;
      frame_box.ymin = frame_box.ymax - height;

      /* Handle reopening the frame */

      e = frames_reopen_frame(b->children[child], b, &frame_box);
      if (e) return e;

      /* Prepare for the next frame */

      frame_box.xmin += width + token->indent * 2; /* Scale to 2 OS units/pixel */
      child++;
    }

    frame_box.ymax -= height + token->indent * 2;
  }

  /* Now do a redraw of the parent, to ensure that borders are up to date */

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

  e = wimp_force_redraw(b->window_handle,
                        s.visible_area.xmin,
                        s.visible_area.ymin,
                        s.visible_area.xmax,
                        s.visible_area.ymax);
  if (e) return e;

  #ifdef TRACE
    if (tl & (1u<<17)) Printf("frames_resize_frameset: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* frames_resize_frame()                         */
/*                                               */
/* Resizes a given frame and its contents (e.g.  */
/* any nested frames). Compare with              */
/* frames_resize_frameset(), which doesn't       */
/* resize the parent - just the contents (which  */
/* is useful when the natural progression of a   */
/* calling function would resize the parent      */
/* anyway).                                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             representing the frame;           */
/*                                               */
/*             Pointer to a BBox describing the  */
/*             required new frame outline (in    */
/*             screen coordinates).              */
/*************************************************/

_kernel_oserror * frames_resize_frame(browser_data * b, BBox * new_outline)
{
  _kernel_oserror * e;

  /* Update any child frames first, if needed */

  if (b->nchildren)
  {
    e = frames_resize_frameset(b, new_outline);
    if (e) return e;
  }

  /* Now resize the given frame */

  return frames_reopen_frame(b, NULL, new_outline);
}

/*************************************************/
/* frames_lock_frameset()                        */
/*                                               */
/* Examines the window sizes of a collection of  */
/* frames in a frameset, and rewrites the size   */
/* descriptions in the frameset representing     */
/* HStream structures. This, then, would mean    */
/* that for example, subsequent resizes of the   */
/* ancestor window would maintain that new       */
/* relative layout, rather than 'snapping back'  */
/* to values specified in the HStream structure. */
/*                                               */
/* Where possible, the nature of the value is    */
/* maintained - e.g. a pixel value will stay     */
/* as such, as will a percentage value (they     */
/* don't all become pixel sizes). At present,    */
/* all units are preserved though note that      */
/* significant rounding errors can occur with    */
/* percentage values.                            */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             representing the parent for the   */
/*             frameset to lock.                 */
/*************************************************/

void frames_lock_frameset(browser_data * b)
{
  browser_data              * child;
  WimpGetWindowOutlineBlock   co;
  int                         units;
  int                         av_w, av_h;
  int                         wid,  hei;
  int                         col,  cols;
  int                         row,  rows;
  int                         tw,   th;

  if (!b->nchildren) return;

  rows = b->frameset->rows;
  cols = b->frameset->cols;

  /* Get the parent width/height to find available space. */

  co.window_handle = b->window_handle;
  if (wimp_get_window_outline(&co)) return;

  av_w = co.outline.xmax - co.outline.xmin;
  av_h = co.outline.ymax - co.outline.ymin;

  /* Now go through the columns. */

  for (col = 0; col < cols; col++)
  {
    tw    = ((int *) (b->frameset->value))[col];
    units = tw & ~ROWCOL_VALUE;

    /* Find the child and get information on it */

    child            = b->children[col];
    co.window_handle = child->window_handle;

    if (!wimp_get_window_outline(&co))
    {
      wid = co.outline.xmax - co.outline.xmin;

      /* Work out what the widths should be in the */
      /* given units.                              */

      if (units & ROWCOL_PERCENT)
      {
        tw = (100 * wid) / av_w;
      }
      else if (units & ROWCOL_STAR)
      {
        /* This works as 2*,* is the same as 422*,211*; */
        /* it's just fractions. So we can maintain the  */
        /* units but keep full accuracy here.           */

        tw = wid;
      }
      else
      {
        tw = wid / wimpt_dx();
      }

      /* Put the units field back in */

      tw |= units;

      /* Write the value to the token */

      ((int *) (b->frameset->value))[col] = tw;
    }
  }

  /* Now do rows - code is directly analogous to the  */
  /* columns stuff above, hence the lack of comments. */

  for (row = 0; row < rows; row++)
  {
    th    = ((int *) (b->frameset->name))[row];
    units = th & ~ROWCOL_VALUE;

    child            = b->children[row * (cols ? cols : 1)];
    co.window_handle = child->window_handle;

    if (!wimp_get_window_outline(&co))
    {
      hei = co.outline.ymax - co.outline.ymin;

      if (units & ROWCOL_PERCENT)
      {
        th = (100 * hei) / av_h;
      }
      else if (units & ROWCOL_STAR)
      {
        th = hei;
      }
      else
      {
        th = hei / wimpt_dy();
      }

      th |= units;

      ((int *) (b->frameset->name))[row] = th;
    }
  }

  /* ...and that's it. */
}

/*************************************************/
/* frames_fetching()                             */
/*                                               */
/* Equivalent to fetch_fetching() in Fetch.c,    */
/* but returns 1 if any of the frames in a       */
/* frameset are fetching, else 0.                */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which is the parent of any frames */
/*             to check (so for an entire        */
/*             frameset, pass the ancestor in).  */
/*                                               */
/* Returns:    1 if any of the frames are        */
/*             fetching, else 0.                 */
/*************************************************/

int frames_fetching(browser_data * b)
{
  int i;

  /* Recurse for child frames */

  if (b->nchildren)
  {
    for (i = 0; i < b->nchildren; i++)
    {
      if (frames_fetching(b->children[i])) return 1;
    }
  }

  if (fetch_fetching(b)) return 1;

  return 0;
}

/*************************************************/
/* frames_abort_fetching()                       */
/*                                               */
/* Stops any page or image fetching, and stops   */
/* any current reformatting, in the given frame  */
/* and all of its children (if it has any). For  */
/* ancestor browser_data structures, if the      */
/* relevant Choices and Messages file options    */
/* have been set, WebServe will be instructed to */
/* stop all fetching and ditch any objects that  */
/* are half fetched.                             */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the fetches to abort; */
/*                                               */
/*             1 to also stop image fetching,    */
/*             else 0 to let image fetches       */
/*             continue;                         */
/*                                               */
/*             1 to also stop file spooling      */
/*             (i.e. the save_link flag is set), */
/*             else 0 to allow it to continue.   */
/*************************************************/

void frames_abort_fetching(browser_data * b, int stop_images, int stop_spools)
{
  int i;

  /* Recurse for child frames */

  if (b->nchildren)
  {
    for (i = 0; i < b->nchildren; i++)
    {
      frames_abort_fetching(b->children[i], stop_images, stop_spools);
    }
  }

  /* May not want to stop file saves */

  if (!stop_spools && b->save_link) return;

  /* Stop the main fetch, stop any reformatting, and */
  /* stop image fetching.                            */

  fetch_cancel(b);
  reformat_stop(b);

  if (stop_images) image_abort_fetches(b);

  /* If this is the ancestor, tell WebServe to stop all activity */
  /* as well (if we're running full screen). Can't do this if    */
  /* targetting a frame as we may still want the other frames    */
  /* to keep fetching (e.g. the images in a navigation panel     */
  /* may still be coming in whilst that panel is used to open a  */
  /* link in some other frame).                                  */

  if (controls.stop_proxy && choices.full_screen && !b->ancestor) utils_stop_proxy();
}

/*************************************************/
/* frames_collapse_child_tree()                  */
/*                                               */
/* Used during traversal of the child tree to    */
/* close down unwanted frames. See the parameter */
/* list for the three browser_data structs       */
/* required.                                     */
/*                                               */
/* If the third browser has children the         */
/* function will call itself with the given base */
/* browser until the browser to close has no     */
/* children. It then closes it (unless it's the  */
/* same as the base browser, in which case the   */
/* final exit condition is reached) and          */
/* decrements the parent's child counter.        */
/* Recursion then collapses a level.             */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             for the base browser - that is,   */
/*             the one that won't be closed;     */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             that has the entry in its array   */
/*             of children for the browser given */
/*             in the next parameter, i.e. the   */
/*             parent browser;                   */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             that may have children, which     */
/*             is one of those that will be      */
/*             closed (unless it's the same as   */
/*             the base structure, which will    */
/*             tend to be the case on first      */
/*             call of the function).            */
/*************************************************/

static void frames_collapse_child_tree(browser_data * base, browser_data * real_parent, browser_data * close)
{
  if (!base || !close) return;

  while (close->nchildren) frames_collapse_child_tree(base, close, (browser_data *) close->children[close->nchildren - 1]);

  /* At this stage, might have called ourselves many times or */
  /* not at all. In any event, we've reached a browser with   */
  /* no children - it's at the end of the tree. So if this    */
  /* isn't the base browser - at which point the tree must be */
  /* closed down - close this browser.                        */

  if (close != base)
  {
    windows_close_browser(close);

    if (real_parent)
    {
      real_parent->nchildren --;

      if (!real_parent->nchildren) memory_set_chunk_size(real_parent, NULL, CK_CHIL, 0);
    }
  }

  close->frameset = NULL;

  return;
}

/*************************************************/
/* frames_collapse_set()                         */
/*                                               */
/* Given a parent browser, close all children.   */
/*                                               */
/* Parameters: Pointer to parent browser_data    */
/*             struct.                           */
/*************************************************/

void frames_collapse_set(browser_data * b)
{
  _swix(Hourglass_Start, _IN(0), 10);

  frames_collapse_child_tree(b, NULL, b);

  _swix(Hourglass_Off, 0);
}

/*************************************************/
/* frames_find_named()                           */
/*                                               */
/* If a given name is found attached to the      */
/* given window or one of its children, return   */
/* the browser_data struct for that window (else */
/* NULL). The check is case insensitive.         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             representing the parent (it and   */
/*             all its children are searched for */
/*             the name);                        */
/*                                               */
/*             Pointer to the name.              */
/*                                               */
/* Returns:    Pointer to the browser_data       */
/*             struct of that name, or NULL if   */
/*             not found.                        */
/*************************************************/

browser_data * frames_find_named(browser_data * parent, char * name)
{
  int child = 0;

  if (!parent || !name || (name && !*name)) return NULL;

  /* If the parent matches the given name, return it */

  if (parent->window_name && *parent->window_name && !utils_strcasecmp(parent->window_name, name)) return parent;

  /* Otherwise, go through the list of children */

  if (parent->nchildren)
  {
    for (child = 0; child < parent->nchildren; child ++)
    {
      browser_data * found;

      found = frames_find_named((browser_data *) parent->children[child], name);

      if (found) return found;
    }
  }

  return NULL;
}

/*************************************************/
/* frames_find_target()                          */
/*                                               */
/* Given a base browser and a frame target name, */
/* returns the browser to which the target       */
/* refers.                                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             that the tag lies in;             */
/*                                               */
/*             Pointer to the target string.     */
/*                                               */
/* Returns:    Pointer to the targetted browser, */
/*             or NULL to create a new window.   */
/*             Note that if a new window is not  */
/*             to be opened, NULL will *never*   */
/*             be passed, even if the target     */
/*             field of the given token is (say) */
/*             NULL itself. I.e., the caller     */
/*             doesn't need to worry about such  */
/*             cases.                            */
/*                                               */
/* Assumes:    In the case of named target       */
/*             windows where the name cannot be  */
/*             found, NULL is returned - the     */
/*             caller is expected to then open a */
/*             new window named after the target */
/*             given to this function.           */
/*************************************************/

browser_data * frames_find_target(browser_data * b, const char * target)
{
  browser_data * ancestor;

  #ifdef TRACE
    if (tl & (1u<<17)) Printf("frames_find_target: Called for %p, target %p\n",b,target);
  #endif

  /* Only proceed if there's a browser, a token, and that */
  /* token has a target which isn't a null string.        */

  if (b && target && *target)
  {
    #ifdef TRACE
      if (tl & (1u<<17)) Printf("frames_find_target: Target = '%s'\n",target);
    #endif

    if (!utils_strcasecmp(target, "_top")) /* Open in the top level window */
    {
      ancestor = b->ancestor;
      if (!ancestor) ancestor = b->real_parent;
      if (!ancestor) ancestor = b;

      return ancestor;
    }
    else if (!utils_strcasecmp(target, "_self")) /* Open within the frame */
    {
      return b;
    }
    else if (!utils_strcasecmp(target, "_blank")) /* Open in a new, blank window */
    {
      return NULL;
    }
    else if (!utils_strcasecmp(target, "_parent")) /* Open in the frame's parent */
    {
      browser_data * parent = b->real_parent;

      /* _parent is taken by most page authors to mean the complete parent document, */
      /* not the parent frame. That is, if one document defines within itself a      */
      /* nested frameset, then _parent does not refer to any of those nested frames; */
      /* it refers to the frame holding the orignal document.                        */
      /*                                                                             */
      /* Consequently, we need to follow real_parent pointers until we reach a child */
      /* frame which has a non-NULL display URL field, i.e. a frame which has frames */
      /* but has got them by fetching a document.                                    */

      while (parent->real_parent && !parent->urlddata) parent = parent->real_parent;

      if (!parent) parent = b->parent;
      if (!parent) parent = b->ancestor;
      if (!parent) parent = b;

      return parent;
    }
    else
    {
      /* According to the frame spec at the time of writing, */
      /* names must start with an alphanumeric character.    */

      if (isalnum(*target)) /* Named window */
      {
        browser_data * named;

        ancestor = utils_ancestor(b);

        named = frames_find_named(ancestor, (char *) target);

        /* If the name isn't found, try all windows, rather */
        /* than just children of the specified one.         */

        if (!named)
        {
          browser_data * found = NULL;

          named = last_browser;

          while (named && !found)
          {
            if (
                 named->window_name                               &&
                 *named->window_name                              &&
                 !utils_strcasecmp(named->window_name, target)
               )
               found = named;

            else named = named->previous;
          }

          /* If the name still isn't there, supposed to open a */
          /* new window with this name. Flag this by returning */
          /* NULL.                                             */

          if (!found) return NULL;
          else        return found;
        }

        return named;
      }
    }
  }

  /* For null/unspecified targets, use the same frame */

  if (!target || !*target) return b;

  /* If all else fails, we don't recognise the name or it is */
  /* illegal. Safest to open in a new window, leaving the    */
  /* original document intact.                               */

  return NULL;
}

/*************************************************/
/* frames_find_another_frame()                   */
/*                                               */
/* Given a browser_data structure which is a     */
/* child frame, finds the next structure in the  */
/* frame layout - intended for keyboard          */
/* navigation through frames.                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which is the current child frame; */
/*                                               */
/*             Direction - 0 to find the logical */
/*             next frame in the set, 1 to find  */
/*             the logical previous.             */
/*                                               */
/* Returns:    Pointer to a browser_data struct  */
/*             which is the next child frame.    */
/*************************************************/

browser_data * frames_find_another_frame(browser_data * current, int dir)
{
  browser_data * ancestor;
  int            found = 0;

  if (!current) return NULL;

  ancestor = utils_ancestor(current);

  /* Finding the logical next frame */

  if (!dir)
  {
    browser_data * next;

    next = frames_find_next_frame(ancestor, current, &found);

    /* If 'next' is NULL, it could be because the current frame is  */
    /* the last one in the group. So try searching again from the   */
    /* ancestor (the routine will have updated 'found' so that it   */
    /* knows not to wait until it's got the current frame anymore). */

    if (!next) return frames_find_next_frame(ancestor, current, &found);

    return next;
  }
  else
  {
    browser_data * previous;

    previous = frames_find_previous_frame(ancestor, current, &found);

    if (!previous) return frames_find_previous_frame(ancestor, current, &found);

    return previous;
  }

  return NULL;
}

/*************************************************/
/* frames_find_next_frame()                      */
/*                                               */
/* Recursive back-end to                         */
/* frames_find_another_frame, for finding the    */
/* logical next frame.                           */
/*                                               */
/* Parameters: Pointer to the browser_data       */
/*             struct to compare with the        */
/*             current one;                      */
/*                                               */
/*             Pointer to the browser_data       */
/*             struct which is the current one;  */
/*                                               */
/*             Pointer to an int which should    */
/*             contain 0 on entry and will be    */
/*             written to by the function.       */
/*                                               */
/* Returns:    As frames_find_next_frame.        */
/*************************************************/

static browser_data * frames_find_next_frame(browser_data * check, browser_data * current, int * found)
{
  browser_data * b = NULL;
  int            c = 0;

  while (!b && c < check->nchildren)
  {
    if (check->children[c]->nchildren)
    {
      /* Recursive call, if the child has children */

      b = frames_find_next_frame(check->children[c], current, found);
    }
    else
    {
      /* 'found' is set to 1 if the 'current' browser_data struct */
      /* has been found. If so, we can use the next struct that   */
      /* has no children. If not, and the struct being examined   */
      /* is the same as 'current', set 'found'.                   */

      if (*found)
      {
        if (check->children[c] != current) return check->children[c];
      }
      else
      {
        if (check->children[c] == current) *found = 1;
      }
    }

    c++;
  }

  return b;
}

/*************************************************/
/* frames_find_previous_frame()                  */
/*                                               */
/* Recursive back-end to                         */
/* frames_find_another_frame, for finding the    */
/* logical previous frame.                       */
/*                                               */
/* Parameters: Pointer to the browser_data       */
/*             struct to compare with the        */
/*             current one;                      */
/*                                               */
/*             Pointer to the browser_data       */
/*             struct which is the current one;  */
/*                                               */
/*             Pointer to an int which should    */
/*             contain 0 on entry and will be    */
/*             written to by the function.       */
/*                                               */
/* Returns:    As frames_find_next_frame.        */
/*************************************************/

static browser_data * frames_find_previous_frame(browser_data * check, browser_data * current, int * found)
{
  browser_data * b = NULL;
  int            c = check->nchildren - 1;

  while (!b && c >= 0)
  {
    if (check->children[c]->nchildren)
    {
      /* Recursive call, if the child has children */

      b = frames_find_previous_frame(check->children[c], current, found);
    }
    else
    {
      /* 'found' is set to 1 if the 'current' browser_data struct */
      /* has been found. If so, we can use the next struct that   */
      /* has no children. If not, and the struct being examined   */
      /* is the same as 'current', set 'found'.                   */

      if (*found)
      {
        if (check->children[c] != current) return check->children[c];
      }
      else
      {
        if (check->children[c] == current) *found = 1;
      }
    }

    c--;
  }

  return b;
}

/*************************************************/
/* frames_highlight_frame()                      */
/*                                               */
/* Highlights a frame by showing a border around */
/* it, made of 'Highlight' borderless window     */
/* objects in the Res file. The highlight is     */
/* removed by a NULL poll timer.                 */
/*                                               */
/* Won't highlight ancestor windows, and only    */
/* highlights if keyboard control is enabled.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the frame to be       */
/*             highlighted.                      */
/*************************************************/

_kernel_oserror * frames_highlight_frame(browser_data * b)
{
  browser_data            * ancestor;
  WimpGetWindowStateBlock   s;
  WindowShowObjectBlock     show;
  _kernel_oserror         * e;
  BBox                      top, bottom, left, right;

  /* Don't do anything if not using keyboard control */

  if (!choices.keyboard_ctrl) return NULL;

  /* Don't do anything if an ancestor */

  if (!b->ancestor) return NULL;

  /* Otherwise, proceed */

  ancestor = utils_ancestor(b);

  s.window_handle = b->window_handle;

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

  /* If not already present, create the highlight window objects */

  if (!highlight_top)
  {
    e = toolbox_create_object(0, "Highlight", &highlight_top);
    if (e) return e;

    e = toolbox_create_object(0, "Highlight", &highlight_bottom);
    if (e) return e;

    e = toolbox_create_object(0, "Highlight", &highlight_left);
    if (e) return e;

    e = toolbox_create_object(0, "Highlight", &highlight_right);
    if (e) return e;

    /* Work out how long to leave the highlight visible */

    highlight_for = atoi(lookup_control("ShowFHighFor:30",0,0));

    if (highlight_for < 5)    highlight_for = 5;
    if (highlight_for > 1000) highlight_for = 3000;

    /* Register a null claimant to get rid of the objects shortly */

    register_null_claimant(Wimp_ENull, (WimpEventHandler *) frames_remove_highlight_timer, b);
  }

  /* Reset the highlight removal timer */

  e = _swix(OS_ReadMonotonicTime,
            _OUT(0),

            &highlight_timer);

  if (e) return e;

  /* Adjust the visible area coordinates for toolbars */

  {
    int htop, hbot;

    if (!controls.swap_bars)
    {
      htop = toolbars_url_height(b) + toolbars_button_height(b); /* Physically, the button bar and URL bar are just one real toolbar */
      hbot = toolbars_status_height(b);
    }
    else
    {
      htop = toolbars_status_height(b);
      hbot = toolbars_url_height(b) + toolbars_button_height(b);
    }

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

    s.visible_area.ymax -= htop;
    s.visible_area.ymin += hbot;
  }

  /* Work out the visible areas that the highlight windows should take */

  top.xmin    = s.visible_area.xmin + 2;  /**************************************/
  top.ymin    = s.visible_area.ymax - 6;  /* If a lower case letter shows where */
  top.xmax    = s.visible_area.xmax - 2;  /* the appropriate box covers pixels, */
  top.ymax    = s.visible_area.ymax - 2;  /* and the upper case letters show    */
                                          /* the actual pixels referenced by    */
  bottom.xmin = s.visible_area.xmin + 6;  /* (xmin, ymin) and (xmax, ymax),     */
  bottom.ymin = s.visible_area.ymin + 2;  /* then we are calculating:           */
  bottom.xmax = s.visible_area.xmax - 2;  /*                         S          */
  bottom.ymax = s.visible_area.ymin + 6;  /*           ssssssssssssss           */
                                          /*           s         T Rs           */
  left.xmin   = s.visible_area.xmin + 2;  /*           s ttttttttrr s           */
  left.ymin   = s.visible_area.ymin + 2;  /*           s TtLtttttrr s           */
  left.xmax   = s.visible_area.xmin + 6;  /*           s ll      RrBs           */
  left.ymax   = s.visible_area.ymax - 6;  /*           s llbbbbbbbb s           */
                                          /*           s LlBbbbbbbb s           */
  right.xmin  = s.visible_area.xmax - 6;  /*           s            s           */
  right.ymin  = s.visible_area.ymin + 6;  /*           Ssssssssssssss           */
  right.xmax  = s.visible_area.xmax - 2;  /*                                    */
  right.ymax  = s.visible_area.ymax - 2;  /**************************************/

  /* Show the objects */

  show.xscroll              = 0;
  show.yscroll              = 0;
  show.behind               = -1;
  show.visible_area         = top;
  show.parent_window_handle = ancestor->window_handle;
  show.alignment_flags      = 0;


  e = toolbox_show_object(Toolbox_ShowObject_AsSubWindow, highlight_top,    Toolbox_ShowObject_FullSpec, &show, ancestor->self_id, -1);
  if (e) return e;

  /* The show call above can modify the contents of the 'show' */
  /* block passed to it, so need to fill in the values again   */
  /* for each call.                                            */

  show.xscroll              = 0;
  show.yscroll              = 0;
  show.behind               = -1;
  show.visible_area         = bottom;
  show.parent_window_handle = ancestor->window_handle;
  show.alignment_flags      = 0;

  e = toolbox_show_object(Toolbox_ShowObject_AsSubWindow, highlight_bottom, Toolbox_ShowObject_FullSpec, &show, ancestor->self_id, -1);
  if (e) return e;

  /* Show them */

  show.xscroll              = 0;
  show.yscroll              = 0;
  show.behind               = -1;
  show.visible_area         = left;
  show.parent_window_handle = ancestor->window_handle;
  show.alignment_flags      = 0;

  e = toolbox_show_object(Toolbox_ShowObject_AsSubWindow, highlight_left,   Toolbox_ShowObject_FullSpec, &show, ancestor->self_id, -1);
  if (e) return e;

  show.xscroll              = 0;
  show.yscroll              = 0;
  show.behind               = -1;
  show.visible_area         = right;
  show.parent_window_handle = ancestor->window_handle;
  show.alignment_flags      = 0;

  e = toolbox_show_object(Toolbox_ShowObject_AsSubWindow, highlight_right,  Toolbox_ShowObject_FullSpec, &show, ancestor->self_id, -1);
  if (e) return e;

  /* Remember the highlighted frame, for functions other than */
  /* the null handler that removes the highlight (which gets  */
  /* passed the value directly).                              */

  highlight_frame = b;

  /* Finished */

  return NULL;
}

/*************************************************/
/* frames_remove_highlight_timer()               */
/*                                               */
/* A NULL event handler which times how long the */
/* frame highlight objects have been visible     */
/* and removes them after a time defined by the  */
/* Choices file 'ShowFHighFor' entry.            */
/*                                               */
/* Registered by frames_highlight_frame.         */
/*                                               */
/* Parameters as standard for a Wimp event       */
/* handler.                                      */
/*************************************************/

static int frames_remove_highlight_timer(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  int time;

  if (_swix(OS_ReadMonotonicTime,
            _OUT(0),

            &time)) return 0;

  if (time - highlight_timer > highlight_for)
  {
    /* Delete the objects (so removing the highlight) */

    if (highlight_top)    toolbox_delete_object(0, highlight_top);
    if (highlight_bottom) toolbox_delete_object(0, highlight_bottom);
    if (highlight_left)   toolbox_delete_object(0, highlight_left);
    if (highlight_right)  toolbox_delete_object(0, highlight_right);

    /* Reset the Object IDs, so that the highlight routine knows it needs */
    /* to recreate the objects and reinstall this handler                 */

    highlight_top = highlight_bottom = highlight_left = highlight_right = 0;

    /* Clear the internal record of the highlighted frame */

    highlight_frame = NULL;

    /* Remove the handler */

    deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) frames_remove_highlight_timer, handle);
  }

  return 0;
}

/*************************************************/
/* frames_remove_highlight()                     */
/*                                               */
/* Removes the highlight objects from around a   */
/* frame, regardless of which frame is           */
/* highlighted.                                  */
/*************************************************/

_kernel_oserror * frames_remove_highlight(void)
{
  _kernel_oserror * e;

  /* Delete the objects (so removing the highlight) */

  if (highlight_top)
  {
    e = toolbox_delete_object(0, highlight_top);
    if (e) return e;
  }

  if (highlight_bottom)
  {
    e = toolbox_delete_object(0, highlight_bottom);
    if (e) return e;
  }

  if (highlight_left)
  {
    e = toolbox_delete_object(0, highlight_left);
    if (e) return e;
  }

  if (highlight_right)
  {
    e = toolbox_delete_object(0, highlight_right);
    if (e) return e;
  }

  /* Reset the Object IDs, so that the highlight routine knows it needs */
  /* to recreate the objects and reinstall this handler                 */

  highlight_top = highlight_bottom = highlight_left = highlight_right = 0;

  /* Clear the internal record of the highlighted frame */

  highlight_frame = NULL;

  return NULL;
}

/*************************************************/
/* frames_remove_highlight_if_present()          */
/*                                               */
/* Removes the highlight objects from around a   */
/* given frame, if that frame is highlighted.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the highlight.        */
/*************************************************/

_kernel_oserror * frames_remove_highlight_if_present(browser_data * b)
{
  if (b && b == highlight_frame) return frames_remove_highlight();

  return NULL;
}