/* 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 "TBEvents.h"
#include "Utils.h"

#include "Browser.h"
#include "Fetch.h"
#include "FetchPage.h"
#include "Images.h"
#include "Memory.h"
#include "Reformat.h"
#include "Toolbars.h"
#include "URLutils.h"
#include "Windows.h"

#include "Frames.h"

/* Locals */

static ObjectId highlight_top    = 0;
static ObjectId highlight_bottom = 0;
static ObjectId highlight_left   = 0;
static ObjectId highlight_right  = 0;

static int      highlight_timer  = 0;
static int      highlight_for    = 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 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    (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 ++;
    }
    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;

    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)
      {
        tw = (remaining * tw / stars) & ~(wimpt_dx() - 1);
        if (tw < choices.minfrmwidth) tw = choices.minfrmwidth;

        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 ++;
    }
    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;

    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)
      {
        th = (remaining * th / stars) & ~(wimpt_dy() - 1);
        if (th < choices.minfrmheight) th = choices.minfrmheight;

        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.                           */
/*                                               */
/* Returns:    See parameters list.              */
/*                                               */
/* 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_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_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                       topbar_height, bottombar_height;
  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 */

  topbar_height    = toolbars_url_height(b) + toolbars_button_height(b); /* Physically, the button bar and URL bar are just one real toolbar */
  bottombar_height = toolbars_status_height(b);

  if (topbar_height)    topbar_height    += wimpt_dy(); /* Account for lower border of upper toolbar, if present */
  if (bottombar_height) bottombar_height += 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
                - topbar_height
                - bottombar_height;

  /* 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 - topbar_height;

  /* 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 * wimpt_dx() * (num_cols - 1);
  available_h -= token->indent * wimpt_dy() * (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_set_tools(last_browser, &frame_box, 0, 0, 0, 0);

      /* Make the child inherit the parent's display characteristics */

      last_browser->underlinelks  = b->underlinelks;
      last_browser->displayimages = b->displayimages;
      last_browser->plainback     = b->plainback;
      last_browser->sourcecolours = b->sourcecolours;

      /* 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 * wimpt_dx();
    }

    frame_box.ymax -= height + token->indent * wimpt_dy();
  }

  /* 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) return NULL;

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

  if (b->nchildren <= b->filling_frame) return NULL;

  #ifdef TRACE
    if (tl & (1u<<17)) Printf("frames_define_frame: Have not defined all characteristics, 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);
}

/*************************************************/
/* frames_resize_frameset()                      */
/*                                               */
/* For a given parent browser_data structure,    */
/* resize a frameset within its window.          */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             representing the parent;          */
/*                                               */
/*             A WimpOpenWindowBlock pointer,    */
/*             with the block holding the new    */
/*             BBox of the parent.               */
/*************************************************/

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

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

  if (!b->nchildren) return NULL;

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

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

  s.visible_area = o->visible_area;

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

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

  topbar_height    = toolbars_url_height(b) + toolbars_button_height(b); /* Physically, the button bar and URL bar are just one real toolbar */
  bottombar_height = toolbars_status_height(b);

  if (topbar_height)    topbar_height    += wimpt_dy(); /* Account for lower border of upper toolbar, if present */
  if (bottombar_height) bottombar_height += 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
                - topbar_height
                - bottombar_height;

  /* 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 - topbar_height;

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

  token = b->frameset;

  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 * wimpt_dx() * (num_cols - 1);
  available_h -= token->indent * wimpt_dy() * (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 */

      {
        browser_data            * cb;
        WimpGetWindowStateBlock   frame_state;
        IdBlock                   idb;
        WimpPollBlock             block;

        cb = b->children[child];

        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 -= vw;
        if (frame_state.flags & WimpWindow_HScroll) frame_box.ymin += hh;

        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 = b->self_id;

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

      /* Prepare for the next frame */

      frame_box.xmin += width + token->indent * wimpt_dx();
      child++;
    }

    frame_box.ymax -= height + token->indent * wimpt_dy();
  }

  /* 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_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, WebServ 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.                         */
/*************************************************/

void frames_abort_fetching(browser_data * b, int stop_images)
{
  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 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 WebServ 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 (fixed.stopwebserv && choices.full_screen && !b->ancestor) utils_stop_webserv();
}

/*************************************************/
/* frames_collapse_child_tree()                  */
/*                                               */
/* Used during traversal of the child tree to    */
/* close down unwanted frames.                   */
/*                                               */
/* 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;            */
/*             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).            */
/*                                               */
/* If the second 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.             */
/*************************************************/

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()                          */
/*                                               */
/* Examines the 'target' field of a tag (token)  */
/* and returns, given the browser that the tag   */
/* lies in, the browser to which the target      */
/* refers.                                       */
/*                                               */
/* If you want to pass in a specific target      */
/* string, just throw an HStream on the stack    */
/* and make the 'target' field point to the      */
/* string - the function doesn't look at any     */
/* other field in the HStream.                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             that the tag lies in;             */
/*                                               */
/*             Address of the token.             */
/*                                               */
/* 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:    None of the parameter pointers    */
/*             are NULL. In the case of named    */
/*             target windows where the name     */
/*             can't be found, NULL is returned; */
/*             the caller should then open a new */
/*             window with the given name.       */
/*************************************************/

browser_data * frames_find_target(browser_data * b, HStream * t)
{
  browser_data * ancestor;

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

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

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

    if (!utils_strcasecmp(t->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(t->target, "_self")) /* Open within the frame */
    {
      return b;
    }
    else if (!utils_strcasecmp(t->target, "_blank")) /* Open in a new, blank window */
    {
      return NULL;
    }
    else if (!utils_strcasecmp(t->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(*t->target)) /* Named window */
      {
        browser_data * named;

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

        named = frames_find_named(ancestor, t->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, t->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 (
       t &&
       (
         !t->target ||
         (
           t->target   &&
           !*t->target
         )
       )
     )
     return b;

  /* If all else fails, want to try getting to the highest possible */
  /* level - after all, accidentally opening what was meant to be a */
  /* full size page inside some tiny frame would be pretty bad...!  */

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

  return ancestor;
}

/*************************************************/
/* 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 = current->ancestor;
  if (!ancestor) 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.                 */
/*                                               */
/* Currently, the highlight will not move with   */
/* the parent browser window and will only show  */
/* 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 = b->ancestor;
  WimpGetWindowStateBlock   s;
  WindowShowObjectBlock     show;
  _kernel_oserror         * e;
  BBox                      top, bottom, left, right;

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

  if (!choices.keyboardctl) return NULL;

  /* Otherwise, proceed */

  if (!ancestor) 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_choice("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, 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 toph, both;

    toph = toolbars_button_height(b) + toolbars_url_height(b);
    both = toolbars_status_height(b);

    if (toph) toph += wimpt_dy();
    if (both) both += wimpt_dy();

    s.visible_area.ymax -= toph;
    s.visible_area.ymin += both;
  }

  /* 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;  /**************************************/

  /* Now 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.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;

  /* Finished */

  return NULL;
}

/*************************************************/
/* frames_remove_highlight()                     */
/*                                               */
/* 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(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;

    /* Remove the handler */

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

  return 0;
}

/*************************************************/